use of herddb.model.StatementEvaluationContext in project herddb by diennea.
the class MemoryHashIndexManager method scanner.
@Override
public Stream<Bytes> scanner(IndexOperation operation, StatementEvaluationContext context, TableContext tableContext) throws StatementExecutionException {
if (operation instanceof SecondaryIndexSeek) {
SecondaryIndexSeek sis = (SecondaryIndexSeek) operation;
SQLRecordKeyFunction value = sis.value;
byte[] refvalue = value.computeNewValue(null, context, tableContext);
List<Bytes> result = data.get(Bytes.from_array(refvalue));
if (result != null) {
return result.stream();
} else {
return Stream.empty();
}
} else if (operation instanceof SecondaryIndexPrefixScan) {
SecondaryIndexPrefixScan sis = (SecondaryIndexPrefixScan) operation;
SQLRecordKeyFunction value = sis.value;
byte[] refvalue = value.computeNewValue(null, context, tableContext);
Predicate<Map.Entry<Bytes, List<Bytes>>> predicate = (Map.Entry<Bytes, List<Bytes>> entry) -> {
Bytes recordValue = entry.getKey();
return recordValue.startsWith(refvalue.length, refvalue);
};
return data.entrySet().stream().filter(predicate).map(entry -> entry.getValue()).flatMap(l -> l.stream());
} else if (operation instanceof SecondaryIndexRangeScan) {
Bytes refminvalue;
SecondaryIndexRangeScan sis = (SecondaryIndexRangeScan) operation;
SQLRecordKeyFunction minKey = sis.minValue;
if (minKey != null) {
refminvalue = Bytes.from_nullable_array(minKey.computeNewValue(null, context, tableContext));
} else {
refminvalue = null;
}
Bytes refmaxvalue;
SQLRecordKeyFunction maxKey = sis.maxValue;
if (maxKey != null) {
refmaxvalue = Bytes.from_nullable_array(maxKey.computeNewValue(null, context, tableContext));
} else {
refmaxvalue = null;
}
Predicate<Map.Entry<Bytes, List<Bytes>>> predicate;
if (refminvalue != null && refmaxvalue == null) {
predicate = (Map.Entry<Bytes, List<Bytes>> entry) -> {
Bytes datum = entry.getKey();
return datum.compareTo(refminvalue) >= 0;
};
} else if (refminvalue == null && refmaxvalue != null) {
predicate = (Map.Entry<Bytes, List<Bytes>> entry) -> {
Bytes datum = entry.getKey();
return datum.compareTo(refmaxvalue) <= 0;
};
} else if (refminvalue != null && refmaxvalue != null) {
predicate = (Map.Entry<Bytes, List<Bytes>> entry) -> {
Bytes datum = entry.getKey();
return datum.compareTo(refmaxvalue) <= 0 && datum.compareTo(refminvalue) >= 0;
};
} else {
predicate = (Map.Entry<Bytes, List<Bytes>> entry) -> {
return true;
};
}
return data.entrySet().stream().filter(predicate).map(entry -> entry.getValue()).flatMap(l -> l.stream());
} else {
throw new UnsupportedOperationException("unsuppported index access type " + operation);
}
}
use of herddb.model.StatementEvaluationContext in project herddb by diennea.
the class TableManager method validateAlterTable.
@Override
public void validateAlterTable(Table table, StatementEvaluationContext context) throws StatementExecutionException {
List<String> columnsChangedFromNullToNotNull = new ArrayList<>();
for (Column c : this.table.columns) {
Column newColumnSpecs = table.getColumn(c.name);
if (newColumnSpecs == null) {
// dropped column
LOGGER.log(Level.INFO, "Table {0}.{1} dropping column {2}", new Object[] { table.tablespace, table.name, c.name });
} else if (newColumnSpecs.type == c.type) {
// no data type change
} else if (ColumnTypes.isNotNullToNullConversion(c.type, newColumnSpecs.type)) {
LOGGER.log(Level.INFO, "Table {0}.{1} making column {2} NULLABLE", new Object[] { table.tablespace, table.name, newColumnSpecs.name });
} else if (ColumnTypes.isNullToNotNullConversion(c.type, newColumnSpecs.type)) {
LOGGER.log(Level.INFO, "Table {0}.{1} making column {2} NOT NULL", new Object[] { table.tablespace, table.name, newColumnSpecs.name });
columnsChangedFromNullToNotNull.add(c.name);
}
}
for (final String column : columnsChangedFromNullToNotNull) {
LOGGER.log(Level.INFO, "Table {0}.{1} validating column {2}, check for NULL values", new Object[] { table.tablespace, table.name, column });
ScanStatement scan = new ScanStatement(this.table.tablespace, this.table, new Predicate() {
@Override
public boolean evaluate(Record record, StatementEvaluationContext context) throws StatementExecutionException {
return record.getDataAccessor(table).get(column) == null;
}
});
// fast fail
scan.setLimits(new ScanLimitsImpl(1, 0));
boolean foundOneNull = false;
try (DataScanner scanner = this.scan(scan, context, null, false, false)) {
foundOneNull = scanner.hasNext();
} catch (DataScannerException err) {
throw new StatementExecutionException(err);
}
if (foundOneNull) {
throw new StatementExecutionException("Found a record in table " + table.name + " that contains a NULL value for column " + column + " ALTER command is not possible");
}
}
// if we are adding new FK we have to check that the FK is not violated
if (table.foreignKeys != null) {
List<ForeignKeyDef> newForeignKeys;
if (this.table.foreignKeys == null) {
newForeignKeys = Arrays.asList(table.foreignKeys);
} else {
Set<String> currentKfs = Stream.of(this.table.foreignKeys).map(f -> f.name.toLowerCase()).collect(Collectors.toSet());
newForeignKeys = Stream.of(table.foreignKeys).filter(fk -> !currentKfs.contains(fk.name)).collect(Collectors.toList());
}
for (ForeignKeyDef newFk : newForeignKeys) {
validateForeignKeyConsistency(newFk, context, null);
}
}
}
use of herddb.model.StatementEvaluationContext in project herddb by diennea.
the class TableManager method executeForeignKeyConstraintsAsParentTable.
private void executeForeignKeyConstraintsAsParentTable(Table childTable, DataAccessor previousValuesOnParentTable, StatementEvaluationContext context, Transaction transaction, boolean delete) throws StatementExecutionException {
// invalidated consistently during DML operations.
for (ForeignKeyDef fk : childTable.foreignKeys) {
String query = parentForeignKeyQueries.computeIfAbsent(childTable.name + "." + fk.name + ".#" + delete, (l -> {
if (fk.onDeleteAction == ForeignKeyDef.ACTION_CASCADE && delete) {
StringBuilder q = new StringBuilder("DELETE FROM ");
q.append(delimit(childTable.tablespace));
q.append(".");
q.append(delimit(childTable.name));
q.append(" WHERE ");
for (int i = 0; i < fk.columns.length; i++) {
if (i > 0) {
q.append(" AND ");
}
q.append(delimit(fk.columns[i]));
q.append("=?");
}
return q.toString();
} else if (fk.onUpdateAction == ForeignKeyDef.ACTION_CASCADE && !delete) {
// the change is more complex, let's keep it for a future work
throw new StatementExecutionException("No supported ON UPDATE CASCADE");
} else if ((fk.onDeleteAction == ForeignKeyDef.ACTION_SETNULL && delete) || (fk.onUpdateAction == ForeignKeyDef.ACTION_SETNULL && !delete)) {
// delete or update it is the same for SET NULL
StringBuilder q = new StringBuilder("UPDATE ");
q.append(delimit(childTable.tablespace));
q.append(".");
q.append(delimit(childTable.name));
q.append(" SET ");
for (int i = 0; i < fk.columns.length; i++) {
if (i > 0) {
q.append(",");
}
q.append(delimit(fk.columns[i]));
q.append("= NULL ");
}
q.append(" WHERE ");
for (int i = 0; i < fk.columns.length; i++) {
if (i > 0) {
q.append(" AND ");
}
q.append(delimit(fk.columns[i]));
q.append("=?");
}
return q.toString();
} else {
// NO ACTION case, check that there is no matching record in the child table that wouble be invalidated
// with '*' we are not going to perform projections or copies
StringBuilder q = new StringBuilder("SELECT * FROM ");
q.append(delimit(childTable.tablespace));
q.append(".");
q.append(delimit(childTable.name));
q.append(" WHERE ");
for (int i = 0; i < fk.columns.length; i++) {
if (i > 0) {
q.append(" AND ");
}
q.append(delimit(fk.columns[i]));
q.append("=?");
}
return q.toString();
}
}));
final List<Object> valuesToMatch = new ArrayList<>(fk.parentTableColumns.length);
for (int i = 0; i < fk.parentTableColumns.length; i++) {
valuesToMatch.add(previousValuesOnParentTable.get(fk.parentTableColumns[i]));
}
TransactionContext tx = transaction != null ? new TransactionContext(transaction.transactionId) : TransactionContext.NO_TRANSACTION;
if (fk.onDeleteAction == ForeignKeyDef.ACTION_CASCADE && delete || fk.onUpdateAction == ForeignKeyDef.ACTION_CASCADE && !delete || fk.onUpdateAction == ForeignKeyDef.ACTION_SETNULL && !delete || fk.onDeleteAction == ForeignKeyDef.ACTION_SETNULL && delete) {
tableSpaceManager.getDbmanager().executeSimpleStatement(tableSpaceManager.getTableSpaceName(), query, valuesToMatch, // every record
-1, // keep read locks in TransactionContext
true, tx, null);
} else {
boolean fkOk;
try (DataScanner scan = tableSpaceManager.getDbmanager().executeSimpleQuery(tableSpaceManager.getTableSpaceName(), query, valuesToMatch, // only one record
1, // keep read locks in TransactionContext
true, tx, null)) {
List<DataAccessor> resultSet = scan.consume();
// we are on the parent side of the relation
// we are okay if there is no matching record
// TODO: return the list of PKs in order to implement CASCADE operations
fkOk = resultSet.isEmpty();
} catch (DataScannerException err) {
throw new StatementExecutionException(err);
}
if (!fkOk) {
throw new ForeignKeyViolationException(fk.name, "foreignKey " + childTable.name + "." + fk.name + " violated");
}
}
}
}
use of herddb.model.StatementEvaluationContext in project herddb by diennea.
the class TableManager method executeDeleteAsync.
private CompletableFuture<StatementExecutionResult> executeDeleteAsync(DeleteStatement delete, Transaction transaction, StatementEvaluationContext context) {
AtomicInteger updateCount = new AtomicInteger();
Holder<Bytes> lastKey = new Holder<>();
Holder<Bytes> lastValue = new Holder<>();
long transactionId = transaction != null ? transaction.transactionId : 0;
Predicate predicate = delete.getPredicate();
List<CompletableFuture<PendingLogEntryWork>> writes = new ArrayList<>();
Map<String, AbstractIndexManager> indexes = tableSpaceManager.getIndexesOnTable(table.name);
ScanStatement scan = new ScanStatement(table.tablespace, table, predicate);
try {
accessTableData(scan, context, new ScanResultOperation() {
@Override
public void accept(Record current, LockHandle lockHandle) throws StatementExecutionException, LogNotAvailableException, DataStorageManagerException {
// ensure we are holding the write locks on every unique index
List<UniqueIndexLockReference> uniqueIndexes = null;
try {
if (indexes != null || childrenTables != null) {
DataAccessor dataAccessor = current.getDataAccessor(table);
if (childrenTables != null) {
for (Table childTable : childrenTables) {
executeForeignKeyConstraintsAsParentTable(childTable, dataAccessor, context, transaction, true);
}
}
if (indexes != null) {
for (AbstractIndexManager index : indexes.values()) {
if (index.isUnique()) {
Bytes indexKey = RecordSerializer.serializeIndexKey(dataAccessor, index.getIndex(), index.getColumnNames());
if (uniqueIndexes == null) {
uniqueIndexes = new ArrayList<>(1);
}
UniqueIndexLockReference uniqueIndexLock = new UniqueIndexLockReference(index, indexKey);
uniqueIndexes.add(uniqueIndexLock);
LockHandle lockForIndex = lockForWrite(uniqueIndexLock.key, transaction, index.getIndexName(), index.getLockManager());
if (transaction == null) {
uniqueIndexLock.lockHandle = lockForIndex;
}
}
}
}
}
} catch (IllegalArgumentException | herddb.utils.IllegalDataAccessException | StatementExecutionException err) {
locksManager.releaseLock(lockHandle);
StatementExecutionException finalError;
if (!(err instanceof StatementExecutionException)) {
finalError = new StatementExecutionException(err.getMessage(), err);
} else {
finalError = (StatementExecutionException) err;
}
CompletableFuture<PendingLogEntryWork> res = Futures.exception(finalError);
if (uniqueIndexes != null) {
for (UniqueIndexLockReference lock : uniqueIndexes) {
res = releaseWriteLock(res, lockHandle, lock.indexManager.getLockManager());
}
}
writes.add(res);
return;
}
LogEntry entry = LogEntryFactory.delete(table, current.key, transaction);
CommitLogResult pos = log.log(entry, entry.transactionId <= 0);
final List<UniqueIndexLockReference> _uniqueIndexes = uniqueIndexes;
writes.add(pos.logSequenceNumber.thenApply(lsn -> new PendingLogEntryWork(entry, pos, lockHandle, _uniqueIndexes)));
lastKey.value = current.key;
lastValue.value = current.value;
updateCount.incrementAndGet();
}
}, transaction, true, true);
} catch (HerdDBInternalException err) {
LOGGER.log(Level.SEVERE, "bad error during a delete", err);
return Futures.exception(err);
}
if (writes.isEmpty()) {
return CompletableFuture.completedFuture(new DMLStatementExecutionResult(transactionId, 0, null, null));
}
if (writes.size() == 1) {
return writes.get(0).whenCompleteAsync((pending, error) -> {
try {
// apply any of the DML operations
if (error == null) {
apply(pending.pos, pending.entry, false);
}
} finally {
releaseMultiplePendingLogEntryWorks(writes);
}
}, tableSpaceManager.getCallbacksExecutor()).thenApply((pending) -> {
return new DMLStatementExecutionResult(transactionId, updateCount.get(), lastKey.value, delete.isReturnValues() ? lastValue.value : null);
});
} else {
return Futures.collect(writes).whenCompleteAsync((pendings, error) -> {
try {
// apply any of the DML operations
if (error == null) {
for (PendingLogEntryWork pending : pendings) {
apply(pending.pos, pending.entry, false);
}
}
} finally {
releaseMultiplePendingLogEntryWorks(writes);
}
}, tableSpaceManager.getCallbacksExecutor()).thenApply((pendings) -> {
return new DMLStatementExecutionResult(transactionId, updateCount.get(), lastKey.value, delete.isReturnValues() ? lastValue.value : null);
});
}
}
use of herddb.model.StatementEvaluationContext in project herddb by diennea.
the class TableSpaceManager method executeTableAwareStatement.
private CompletableFuture<StatementExecutionResult> executeTableAwareStatement(Statement statement, Transaction transaction, StatementEvaluationContext context) throws StatementExecutionException {
long lockStamp = context.getTableSpaceLock();
boolean lockAcquired = false;
if (lockStamp == 0) {
lockStamp = acquireReadLock(statement);
context.setTableSpaceLock(lockStamp);
lockAcquired = true;
}
TableAwareStatement st = (TableAwareStatement) statement;
String table = st.getTable();
AbstractTableManager manager = tables.get(table);
CompletableFuture<StatementExecutionResult> res;
if (manager == null) {
res = Futures.exception(new TableDoesNotExistException("no table " + table + " in tablespace " + tableSpaceName));
} else if (manager.getCreatedInTransaction() > 0 && (transaction == null || transaction.transactionId != manager.getCreatedInTransaction())) {
res = Futures.exception(new TableDoesNotExistException("no table " + table + " in tablespace " + tableSpaceName + ". created temporary in transaction " + manager.getCreatedInTransaction()));
} else {
res = manager.executeStatementAsync(statement, transaction, context);
}
if (lockAcquired) {
res = releaseReadLock(res, lockStamp, statement).whenComplete((s, err) -> {
context.setTableSpaceLock(0);
});
}
return res;
}
Aggregations