use of herddb.model.Record in project herddb by diennea.
the class TableManager method executeGetAsync.
private CompletableFuture<StatementExecutionResult> executeGetAsync(GetStatement get, Transaction transaction, StatementEvaluationContext context) {
Bytes key;
try {
key = Bytes.from_nullable_array(get.getKey().computeNewValue(null, context, tableContext));
} catch (StatementExecutionException validationError) {
return Futures.exception(validationError);
}
Predicate predicate = get.getPredicate();
boolean requireLock = get.isRequireLock();
boolean useWriteLock = requireLock && context.isForceAcquireWriteLock();
long transactionId = transaction != null ? transaction.transactionId : 0;
LockHandle lock = (transaction != null || requireLock) ? (useWriteLock ? lockForWrite(key, transaction) : lockForRead(key, transaction)) : null;
CompletableFuture<StatementExecutionResult> res = null;
try {
if (transaction != null) {
if (transaction.recordDeleted(table.name, key)) {
res = CompletableFuture.completedFuture(GetResult.NOT_FOUND(transactionId));
} else {
Record loadedInTransaction = transaction.recordUpdated(table.name, key);
if (loadedInTransaction != null) {
if (predicate != null && !predicate.evaluate(loadedInTransaction, context)) {
res = CompletableFuture.completedFuture(GetResult.NOT_FOUND(transactionId));
} else {
res = CompletableFuture.completedFuture(new GetResult(transactionId, loadedInTransaction, table));
}
} else {
loadedInTransaction = transaction.recordInserted(table.name, key);
if (loadedInTransaction != null) {
if (predicate != null && !predicate.evaluate(loadedInTransaction, context)) {
res = CompletableFuture.completedFuture(GetResult.NOT_FOUND(transactionId));
} else {
res = CompletableFuture.completedFuture(new GetResult(transactionId, loadedInTransaction, table));
}
}
}
}
}
if (res == null) {
Long pageId = keyToPage.get(key);
if (pageId == null) {
res = CompletableFuture.completedFuture(GetResult.NOT_FOUND(transactionId));
} else {
Record loaded = fetchRecord(key, pageId, null);
if (loaded == null || (predicate != null && !predicate.evaluate(loaded, context))) {
res = CompletableFuture.completedFuture(GetResult.NOT_FOUND(transactionId));
} else {
res = CompletableFuture.completedFuture(new GetResult(transactionId, loaded, table));
}
}
}
if (lock != null) {
if (transaction == null) {
res.whenComplete((r, e) -> {
locksManager.releaseReadLock(lock);
});
} else if (!context.isForceRetainReadLock() && !lock.write) {
transaction.releaseLockOnKey(table.name, key, locksManager);
}
}
return res;
} catch (HerdDBInternalException err) {
return Futures.exception(err);
}
}
use of herddb.model.Record in project herddb by diennea.
the class TableManager method applyDelete.
private void applyDelete(Bytes key) throws DataStorageManagerException {
/* This could be a normal or a temporary modifiable page */
final Long pageId = keyToPage.remove(key);
if (pageId == null) {
throw new IllegalStateException("corrupted transaction log: key " + key + " is not present in table " + table.tablespace + "." + table.name);
}
if (LOGGER.isLoggable(Level.FINEST)) {
LOGGER.log(Level.FINEST, "Deleted key " + key + " from page " + pageId + " from table " + table.tablespace + "." + table.name);
}
/*
* We'll try to remove the record if in a writable page, otherwise we'll simply set the old page
* as dirty.
*/
final Map<String, AbstractIndexManager> indexes = tableSpaceManager.getIndexesOnTable(table.name);
/*
* When index is enabled we need the old value to update them, we'll force the page load only if that
* record is really needed.
*/
final DataPage page;
final Record previous;
if (indexes == null) {
/* We don't need the page if isn't loaded or isn't a mutable new page */
page = newPages.get(pageId);
if (page != null) {
pageReplacementPolicy.pageHit(page);
previous = page.get(key);
if (previous == null) {
throw new IllegalStateException("corrupted PK: old page " + pageId + " for deleted record at " + key + " was not found in table " + table.tablespace + "." + table.name);
}
} else {
previous = null;
}
} else {
/* We really need the page for update index old values */
page = loadPageToMemory(pageId, false);
previous = page.get(key);
if (previous == null) {
throw new IllegalStateException("corrupted PK: old page " + pageId + " for deleted record at " + key + " was not found in table " + table.tablespace + "." + table.name);
}
}
if (page == null || page.immutable) {
/* Unloaded or immutable, set it as dirty */
pageSet.setPageDirty(pageId, previous);
} else {
/* Mutable page, need to check if still modifiable or already unloaded */
final Lock lock = page.pageLock.readLock();
lock.lock();
try {
if (page.writable) {
/* We can modify the page directly */
page.remove(key);
} else {
/* Unfortunately is not writable (anymore), set it as dirty */
pageSet.setPageDirty(pageId, previous);
}
} finally {
lock.unlock();
}
}
if (indexes != null) {
/* If there are indexes e have already forced a page load and previous record has been loaded */
DataAccessor values = previous.getDataAccessor(table);
for (AbstractIndexManager index : indexes.values()) {
Bytes indexKey = RecordSerializer.serializeIndexKey(values, index.getIndex(), index.getColumnNames());
index.recordDeleted(key, indexKey);
}
}
}
use of herddb.model.Record in project herddb by diennea.
the class TableManager method executeInsertAsync.
private CompletableFuture<StatementExecutionResult> executeInsertAsync(InsertStatement insert, Transaction transaction, StatementEvaluationContext context) {
/*
an insert can succeed only if the row is valid and the "keys" structure does not contain the requested key
the insert will add the row in the 'buffer' without assigning a page to it
locks: the insert uses global 'insert' lock on the table
the insert will update the 'maxKey' for auto_increment primary keys
*/
Bytes key;
byte[] value;
try {
key = Bytes.from_array(insert.getKeyFunction().computeNewValue(null, context, tableContext));
value = insert.getValuesFunction().computeNewValue(new Record(key, null), context, tableContext);
} catch (StatementExecutionException validationError) {
return Futures.exception(validationError);
} catch (Throwable validationError) {
return Futures.exception(new StatementExecutionException(validationError));
}
List<UniqueIndexLockReference> uniqueIndexes = null;
Map<String, AbstractIndexManager> indexes = tableSpaceManager.getIndexesOnTable(table.name);
if (indexes != null || table.foreignKeys != null) {
try {
DataAccessor values = new Record(key, Bytes.from_array(value)).getDataAccessor(table);
if (table.foreignKeys != null) {
for (ForeignKeyDef fk : table.foreignKeys) {
checkForeignKeyConstraintsAsChildTable(fk, values, context, transaction);
}
}
if (indexes != null) {
for (AbstractIndexManager index : indexes.values()) {
if (index.isUnique()) {
Bytes indexKey = RecordSerializer.serializeIndexKey(values, index.getIndex(), index.getColumnNames());
if (uniqueIndexes == null) {
uniqueIndexes = new ArrayList<>(1);
}
uniqueIndexes.add(new UniqueIndexLockReference(index, indexKey));
} else {
RecordSerializer.validateIndexableValue(values, index.getIndex(), index.getColumnNames());
}
}
}
} catch (IllegalArgumentException | herddb.utils.IllegalDataAccessException | StatementExecutionException err) {
if (err instanceof StatementExecutionException) {
return Futures.exception(err);
} else {
return Futures.exception(new StatementExecutionException(err.getMessage(), err));
}
}
}
final long size = DataPage.estimateEntrySize(key, value);
if (size > maxLogicalPageSize) {
return Futures.exception(new RecordTooBigException("New record " + key + " is to big to be inserted: size " + size + ", max size " + maxLogicalPageSize));
}
CompletableFuture<StatementExecutionResult> res = null;
LockHandle lock = null;
try {
lock = lockForWrite(key, transaction);
if (uniqueIndexes != null) {
for (UniqueIndexLockReference uniqueIndexLock : uniqueIndexes) {
AbstractIndexManager index = uniqueIndexLock.indexManager;
LockHandle lockForIndex = lockForWrite(uniqueIndexLock.key, transaction, index.getIndexName(), index.getLockManager());
if (transaction == null) {
uniqueIndexLock.lockHandle = lockForIndex;
}
if (index.valueAlreadyMapped(uniqueIndexLock.key, null)) {
res = Futures.exception(new UniqueIndexContraintViolationException(index.getIndexName(), key, "key " + key + ", already exists in table " + table.name + " on UNIQUE index " + index.getIndexName()));
}
if (res != null) {
break;
}
}
}
} catch (HerdDBInternalException err) {
res = Futures.exception(err);
}
boolean fallbackToUpsert = false;
if (res == null) {
if (transaction != null) {
if (transaction.recordDeleted(table.name, key)) {
// OK, INSERT on a DELETED record inside this transaction
} else if (transaction.recordInserted(table.name, key) != null) {
// ERROR, INSERT on a INSERTED record inside this transaction
res = Futures.exception(new DuplicatePrimaryKeyException(key, "key " + key + ", decoded as " + RecordSerializer.deserializePrimaryKey(key, table) + ", already exists in table " + table.name + " inside transaction " + transaction.transactionId));
} else if (keyToPage.containsKey(key)) {
if (insert.isUpsert()) {
fallbackToUpsert = true;
} else {
res = Futures.exception(new DuplicatePrimaryKeyException(key, "key " + key + ", decoded as " + RecordSerializer.deserializePrimaryKey(key, table) + ", already exists in table " + table.name + " during transaction " + transaction.transactionId));
}
}
} else if (keyToPage.containsKey(key)) {
if (insert.isUpsert()) {
fallbackToUpsert = true;
} else {
res = Futures.exception(new DuplicatePrimaryKeyException(key, "key " + key + ", decoded as " + RecordSerializer.deserializePrimaryKey(key, table) + ", already exists in table " + table.name));
}
}
}
if (res == null) {
LogEntry entry;
if (fallbackToUpsert) {
entry = LogEntryFactory.update(table, key, Bytes.from_array(value), transaction);
} else {
entry = LogEntryFactory.insert(table, key, Bytes.from_array(value), transaction);
}
CommitLogResult pos = log.log(entry, entry.transactionId <= 0);
res = pos.logSequenceNumber.thenApplyAsync((lsn) -> {
apply(pos, entry, false);
return new DMLStatementExecutionResult(entry.transactionId, 1, key, insert.isReturnValues() ? Bytes.from_array(value) : null);
}, tableSpaceManager.getCallbacksExecutor());
}
if (uniqueIndexes != null) {
// TODO: reverse order
for (UniqueIndexLockReference uniqueIndexLock : uniqueIndexes) {
res = releaseWriteLock(res, uniqueIndexLock.lockHandle, uniqueIndexLock.indexManager.getLockManager());
}
}
if (transaction == null) {
res = releaseWriteLock(res, lock);
}
return res;
}
use of herddb.model.Record in project herddb by diennea.
the class TableManager method scanForIndexRebuild.
@Override
public void scanForIndexRebuild(Consumer<Record> records) throws DataStorageManagerException {
LocalScanPageCache localPageCache = new LocalScanPageCache();
Consumer<Map.Entry<Bytes, Long>> scanExecutor = (Map.Entry<Bytes, Long> entry) -> {
Bytes key = entry.getKey();
LockHandle lock = lockForRead(key, null);
try {
Long pageId = entry.getValue();
if (pageId != null) {
Record record = fetchRecord(key, pageId, localPageCache);
if (record != null) {
records.accept(record);
}
}
} catch (DataStorageManagerException | StatementExecutionException error) {
throw new RuntimeException(error);
} finally {
locksManager.releaseReadLock(lock);
}
};
try {
Stream<Map.Entry<Bytes, Long>> scanner = keyToPage.scanner(null, StatementEvaluationContext.DEFAULT_EVALUATION_CONTEXT(), tableContext, null);
scanner.forEach(scanExecutor);
} catch (StatementExecutionException impossible) {
throw new DataStorageManagerException(impossible);
}
}
use of herddb.model.Record in project herddb by diennea.
the class TableManager method scanWithStream.
private DataScanner scanWithStream(ScanStatement statement, StatementEvaluationContext context, Transaction transaction, boolean lockRequired, boolean forWrite) throws StatementExecutionException {
if (transaction != null) {
transaction.increaseRefcount();
}
try {
final TupleComparator comparator = statement.getComparator();
boolean sorted = comparator != null;
boolean sortedByClusteredIndex = comparator != null && comparator.isOnlyPrimaryKeyAndAscending() && keyToPageSortedAscending;
final Projection projection = statement.getProjection();
final boolean applyProjectionDuringScan = projection != null && !sorted;
ScanLimits limits = statement.getLimits();
int maxRows = limits == null ? 0 : limits.computeMaxRows(context);
int offset = limits == null ? 0 : limits.computeOffset(context);
Stream<DataAccessor> result;
Function<Record, DataAccessor> mapper = (Record record) -> {
DataAccessor tuple;
if (applyProjectionDuringScan) {
tuple = projection.map(record.getDataAccessor(table), context);
} else {
tuple = record.getDataAccessor(table);
}
return tuple;
};
Stream<Record> recordsFromTransactionSorted = streamTransactionData(transaction, statement.getPredicate(), context);
Stream<DataAccessor> fromTransactionSorted = recordsFromTransactionSorted != null ? recordsFromTransactionSorted.map(mapper) : null;
if (fromTransactionSorted != null && comparator != null) {
fromTransactionSorted = fromTransactionSorted.sorted(comparator);
}
Stream<DataAccessor> tableData = streamTableData(statement, context, transaction, lockRequired, forWrite).map(mapper);
if (maxRows > 0) {
if (sortedByClusteredIndex) {
// already sorted if needed
if (fromTransactionSorted != null) {
// already sorted from index
tableData = tableData.limit(maxRows + offset);
fromTransactionSorted = fromTransactionSorted.limit(maxRows + offset);
// we need to re-sort after merging the data
result = Stream.concat(fromTransactionSorted, tableData).sorted(comparator);
} else {
// already sorted from index
tableData = tableData.limit(maxRows + offset);
// no need to re-sort
result = tableData;
}
} else if (sorted) {
// need to sort
tableData = tableData.sorted(comparator);
// already sorted if needed
if (fromTransactionSorted != null) {
tableData = tableData.limit(maxRows + offset);
fromTransactionSorted = fromTransactionSorted.limit(maxRows + offset);
// we need to re-sort after merging the data
result = Stream.concat(fromTransactionSorted, tableData).sorted(comparator);
} else {
tableData = tableData.limit(maxRows + offset);
// no need to sort again
result = tableData;
}
} else if (fromTransactionSorted == null) {
result = tableData;
} else {
result = Stream.concat(fromTransactionSorted, tableData);
}
} else {
if (sortedByClusteredIndex) {
// already sorted from index
if (fromTransactionSorted != null) {
tableData = tableData.sorted(comparator);
// fromTransactionSorted is already sorted
// we need to re-sort
result = Stream.concat(fromTransactionSorted, tableData).sorted(comparator);
} else {
result = tableData;
}
} else if (sorted) {
// we need to re-sort
if (fromTransactionSorted != null) {
result = Stream.concat(fromTransactionSorted, tableData).sorted(comparator);
} else {
result = tableData.sorted(comparator);
}
} else if (fromTransactionSorted != null) {
// no need to sort
result = Stream.concat(fromTransactionSorted, tableData);
} else {
result = tableData;
}
}
if (offset > 0) {
result = result.skip(offset);
}
if (maxRows > 0) {
result = result.limit(maxRows);
}
if (!applyProjectionDuringScan && projection != null) {
result = result.map(r -> projection.map(r, context));
}
String[] fieldNames;
Column[] columns;
if (projection != null) {
fieldNames = projection.getFieldNames();
columns = projection.getColumns();
} else {
fieldNames = table.columnNames;
columns = table.columns;
}
return new StreamDataScanner(transaction, fieldNames, columns, result);
} finally {
if (transaction != null) {
transaction.decreaseRefCount();
}
}
}
Aggregations