use of herddb.model.Transaction in project herddb by diennea.
the class TableSpaceManager method executeStatementAsyncInternal.
private CompletableFuture<StatementExecutionResult> executeStatementAsyncInternal(Statement statement, StatementEvaluationContext context, TransactionContext transactionContext, boolean rollbackOnError) throws StatementExecutionException {
Transaction transaction = transactions.get(transactionContext.transactionId);
if (transaction != null && !transaction.tableSpace.equals(tableSpaceName)) {
return Futures.exception(new StatementExecutionException("transaction " + transaction.transactionId + " is for tablespace " + transaction.tableSpace + ", not for " + tableSpaceName));
}
if (transactionContext.transactionId > 0 && transaction == null) {
return Futures.exception(new StatementExecutionException("transaction " + transactionContext.transactionId + " not found on tablespace " + tableSpaceName));
}
boolean isTransactionCommand = statement instanceof CommitTransactionStatement || statement instanceof RollbackTransactionStatement || // AlterTable implictly commits the transaction
statement instanceof AlterTableStatement;
if (transaction != null) {
transaction.touch();
if (!isTransactionCommand) {
transaction.increaseRefcount();
}
}
CompletableFuture<StatementExecutionResult> res;
try {
if (statement instanceof TableAwareStatement) {
res = executeTableAwareStatement(statement, transaction, context);
} else if (statement instanceof SQLPlannedOperationStatement) {
res = executePlannedOperationStatement(statement, transactionContext, context);
} else if (statement instanceof BeginTransactionStatement) {
if (transaction != null) {
res = Futures.exception(new StatementExecutionException("transaction already started"));
} else {
res = beginTransactionAsync(context, true);
}
} else if (statement instanceof CommitTransactionStatement) {
res = commitTransaction((CommitTransactionStatement) statement, context);
} else if (statement instanceof RollbackTransactionStatement) {
res = rollbackTransaction((RollbackTransactionStatement) statement, context);
} else if (statement instanceof CreateTableStatement) {
res = CompletableFuture.completedFuture(createTable((CreateTableStatement) statement, transaction, context));
} else if (statement instanceof CreateIndexStatement) {
res = CompletableFuture.completedFuture(createIndex((CreateIndexStatement) statement, transaction, context));
} else if (statement instanceof DropTableStatement) {
res = CompletableFuture.completedFuture(dropTable((DropTableStatement) statement, transaction, context));
} else if (statement instanceof DropIndexStatement) {
res = CompletableFuture.completedFuture(dropIndex((DropIndexStatement) statement, transaction, context));
} else if (statement instanceof AlterTableStatement) {
res = CompletableFuture.completedFuture(alterTable((AlterTableStatement) statement, transactionContext, context));
} else {
res = Futures.exception(new StatementExecutionException("unsupported statement " + statement).fillInStackTrace());
}
} catch (StatementExecutionException error) {
res = Futures.exception(error);
}
if (transaction != null && !isTransactionCommand) {
res = res.whenComplete((a, b) -> {
transaction.decreaseRefCount();
});
}
if (rollbackOnError) {
long txId = transactionContext.transactionId;
if (txId > 0) {
res = res.whenComplete((xx, error) -> {
if (error != null) {
LOGGER.log(Level.FINE, tableSpaceName + " force rollback of implicit transaction " + txId, error);
try {
rollbackTransaction(new RollbackTransactionStatement(tableSpaceName, txId), context).get();
} catch (InterruptedException ex) {
Thread.currentThread().interrupt();
error.addSuppressed(ex);
} catch (ExecutionException ex) {
error.addSuppressed(ex.getCause());
}
throw new HerdDBInternalException(error);
}
});
}
}
return res;
}
use of herddb.model.Transaction in project herddb by diennea.
the class TableSpaceManager method checkpoint.
// visible for testing
public TableSpaceCheckpoint checkpoint(boolean full, boolean pin, boolean alreadLocked) throws DataStorageManagerException, LogNotAvailableException {
if (virtual) {
return null;
}
if (recoveryInProgress) {
LOGGER.log(Level.INFO, "Checkpoint for tablespace {0} skipped. Recovery is still in progress", tableSpaceName);
return null;
}
long _start = System.currentTimeMillis();
LogSequenceNumber logSequenceNumber = null;
LogSequenceNumber _logSequenceNumber = null;
Map<String, LogSequenceNumber> checkpointsTableNameSequenceNumber = new HashMap<>();
try {
List<PostCheckpointAction> actions = new ArrayList<>();
long lockStamp = 0;
if (!alreadLocked) {
lockStamp = acquireWriteLock("checkpoint");
}
try {
logSequenceNumber = log.getLastSequenceNumber();
if (logSequenceNumber.isStartOfTime()) {
LOGGER.log(Level.INFO, "{0} checkpoint {1} at {2}. skipped (no write ever issued to log)", new Object[] { nodeId, tableSpaceName, logSequenceNumber });
return new TableSpaceCheckpoint(logSequenceNumber, checkpointsTableNameSequenceNumber);
}
LOGGER.log(Level.INFO, "{0} checkpoint start {1} at {2}", new Object[] { nodeId, tableSpaceName, logSequenceNumber });
if (actualLogSequenceNumber == null) {
throw new DataStorageManagerException("actualLogSequenceNumber cannot be null");
}
// TODO: transactions checkpoint is not atomic
Collection<Transaction> currentTransactions = new ArrayList<>(transactions.values());
for (Transaction t : currentTransactions) {
LogSequenceNumber txLsn = t.lastSequenceNumber;
if (txLsn != null && txLsn.after(logSequenceNumber)) {
LOGGER.log(Level.SEVERE, "Found transaction {0} with LSN {1} in the future", new Object[] { t.transactionId, txLsn });
}
}
actions.addAll(dataStorageManager.writeTransactionsAtCheckpoint(tableSpaceUUID, logSequenceNumber, currentTransactions));
actions.addAll(writeTablesOnDataStorageManager(new CommitLogResult(logSequenceNumber, false, true), true));
// we checkpoint all data to disk and save the actual log sequence number
for (AbstractTableManager tableManager : tables.values()) {
if (!tableManager.isSystemTable()) {
TableCheckpoint checkpoint = full ? tableManager.fullCheckpoint(pin) : tableManager.checkpoint(pin);
if (checkpoint != null) {
LOGGER.log(Level.INFO, "checkpoint done for table {0}.{1} (pin: {2})", new Object[] { tableSpaceName, tableManager.getTable().name, pin });
actions.addAll(checkpoint.actions);
checkpointsTableNameSequenceNumber.put(checkpoint.tableName, checkpoint.sequenceNumber);
if (afterTableCheckPointAction != null) {
afterTableCheckPointAction.run();
}
}
}
}
// we are sure that all data as been flushed. upon recovery we will replay the log starting from this position
actions.addAll(dataStorageManager.writeCheckpointSequenceNumber(tableSpaceUUID, logSequenceNumber));
/* Indexes checkpoint is handled by TableManagers */
if (leader) {
log.dropOldLedgers(logSequenceNumber);
}
_logSequenceNumber = log.getLastSequenceNumber();
} finally {
if (!alreadLocked) {
releaseWriteLock(lockStamp, "checkpoint");
}
}
for (PostCheckpointAction action : actions) {
try {
action.run();
} catch (Exception error) {
LOGGER.log(Level.SEVERE, "postcheckpoint error:" + error, error);
}
}
return new TableSpaceCheckpoint(logSequenceNumber, checkpointsTableNameSequenceNumber);
} finally {
long _stop = System.currentTimeMillis();
LOGGER.log(Level.INFO, "{0} checkpoint finish {1} started ad {2}, finished at {3}, total time {4} ms", new Object[] { nodeId, tableSpaceName, logSequenceNumber, _logSequenceNumber, Long.toString(_stop - _start) });
checkpointTimeStats.registerSuccessfulEvent(_stop, TimeUnit.MILLISECONDS);
}
}
use of herddb.model.Transaction in project herddb by diennea.
the class FileDataStorageManager method loadTransactions.
@Override
public void loadTransactions(LogSequenceNumber sequenceNumber, String tableSpace, Consumer<Transaction> consumer) throws DataStorageManagerException {
try {
Path tableSpaceDirectory = getTablespaceDirectory(tableSpace);
Files.createDirectories(tableSpaceDirectory);
Path file = getTablespaceTransactionsFile(tableSpace, sequenceNumber);
boolean exists = Files.isRegularFile(file);
LOGGER.log(Level.INFO, "loadTransactions " + sequenceNumber + " for tableSpace " + tableSpace + " from file " + file + " (exists: " + exists + ")");
if (!exists) {
return;
}
try (InputStream input = new BufferedInputStream(Files.newInputStream(file, StandardOpenOption.READ), 4 * 1024 * 1024);
ExtendedDataInputStream din = new ExtendedDataInputStream(input)) {
// version
long version = din.readVLong();
// flags for future implementations
long flags = din.readVLong();
if (version != 1 || flags != 0) {
throw new DataStorageManagerException("corrupted transaction list file " + file.toAbsolutePath());
}
String readname = din.readUTF();
if (!readname.equals(tableSpace)) {
throw new DataStorageManagerException("file " + file.toAbsolutePath() + " is not for spablespace " + tableSpace);
}
long ledgerId = din.readZLong();
long offset = din.readZLong();
if (ledgerId != sequenceNumber.ledgerId || offset != sequenceNumber.offset) {
throw new DataStorageManagerException("file " + file.toAbsolutePath() + " is not for sequence number " + sequenceNumber);
}
int numTransactions = din.readInt();
for (int i = 0; i < numTransactions; i++) {
Transaction tx = Transaction.deserialize(tableSpace, din);
consumer.accept(tx);
}
}
} catch (IOException err) {
throw new DataStorageManagerException(err);
}
}
use of herddb.model.Transaction in project herddb by diennea.
the class SimpleClientServerTest method testKeepReadLocks.
@Test
public void testKeepReadLocks() throws Exception {
Path baseDir = folder.newFolder().toPath();
try (Server server = new Server(newServerConfigurationWithAutoPort(baseDir))) {
server.start();
server.waitForStandaloneBoot();
ClientConfiguration clientConfiguration = new ClientConfiguration(folder.newFolder().toPath());
try (HDBClient client = new HDBClient(clientConfiguration);
HDBConnection connection = client.openConnection()) {
client.setClientSideMetadataProvider(new StaticClientSideMetadataProvider(server));
assertTrue(connection.waitForTableSpace(TableSpace.DEFAULT, Integer.MAX_VALUE));
long resultCreateTable = connection.executeUpdate(TableSpace.DEFAULT, "CREATE TABLE mytable (id string primary key, n1 long, n2 integer)", 0, false, true, Collections.emptyList()).updateCount;
Assert.assertEquals(1, resultCreateTable);
long tx = connection.beginTransaction(TableSpace.DEFAULT);
long countInsert = connection.executeUpdate(TableSpace.DEFAULT, "INSERT INTO mytable (id,n1,n2) values(?,?,?)", tx, false, true, Arrays.asList("test", 1, 2)).updateCount;
Assert.assertEquals(1, countInsert);
long countInsert2 = connection.executeUpdate(TableSpace.DEFAULT, "INSERT INTO mytable (id,n1,n2) values(?,?,?)", tx, false, true, Arrays.asList("test2", 2, 3)).updateCount;
Assert.assertEquals(1, countInsert2);
connection.commitTransaction(TableSpace.DEFAULT, tx);
// new transaction
tx = connection.beginTransaction(TableSpace.DEFAULT);
// do not keep locks
try (ScanResultSet scan = connection.executeScan(TableSpace.DEFAULT, "SELECT * FROM mytable WHERE id='test'", true, Collections.emptyList(), tx, 0, 10, false)) {
Map<String, Object> record = scan.consume().get(0);
assertEquals(RawString.of("test"), record.get("id"));
assertEquals(Long.valueOf(1), record.get("n1"));
assertEquals(Integer.valueOf(2), record.get("n2"));
Transaction transaction = server.getManager().getTableSpaceManager(TableSpace.DEFAULT).getTransaction(tx);
assertNull(transaction.lookupLock("mytable", Bytes.from_string("test")));
}
// keep locks
try (ScanResultSet scan = connection.executeScan(TableSpace.DEFAULT, "SELECT * FROM mytable WHERE id='test'", true, Collections.emptyList(), tx, 0, 10, true)) {
Map<String, Object> record = scan.consume().get(0);
assertEquals(RawString.of("test"), record.get("id"));
assertEquals(Long.valueOf(1), record.get("n1"));
assertEquals(Integer.valueOf(2), record.get("n2"));
Transaction transaction = server.getManager().getTableSpaceManager(TableSpace.DEFAULT).getTransaction(tx);
assertFalse(transaction.lookupLock("mytable", Bytes.from_string("test")).write);
}
// upgrade lock to write
try (ScanResultSet scan = connection.executeScan(TableSpace.DEFAULT, "SELECT * FROM mytable WHERE id='test' FOR UPDATE", true, Collections.emptyList(), tx, 0, 10, false)) {
Map<String, Object> record = scan.consume().get(0);
assertEquals(RawString.of("test"), record.get("id"));
assertEquals(Long.valueOf(1), record.get("n1"));
assertEquals(Integer.valueOf(2), record.get("n2"));
Transaction transaction = server.getManager().getTableSpaceManager(TableSpace.DEFAULT).getTransaction(tx);
assertTrue(transaction.lookupLock("mytable", Bytes.from_string("test")).write);
}
connection.rollbackTransaction(TableSpace.DEFAULT, tx);
// new transaction
tx = connection.beginTransaction(TableSpace.DEFAULT);
// SELECT FOR UPDATE must hold WRITE LOCK even with keepLocks = false
try (ScanResultSet scan = connection.executeScan(TableSpace.DEFAULT, "SELECT * FROM mytable WHERE id='test' FOR UPDATE", true, Collections.emptyList(), tx, 0, 10, false)) {
Map<String, Object> record = scan.consume().get(0);
assertEquals(RawString.of("test"), record.get("id"));
assertEquals(Long.valueOf(1), record.get("n1"));
assertEquals(Integer.valueOf(2), record.get("n2"));
Transaction transaction = server.getManager().getTableSpaceManager(TableSpace.DEFAULT).getTransaction(tx);
assertTrue(transaction.lookupLock("mytable", Bytes.from_string("test")).write);
}
}
}
}
use of herddb.model.Transaction in project herddb by diennea.
the class TableManager method apply.
@Override
public void apply(CommitLogResult writeResult, LogEntry entry, boolean recovery) throws DataStorageManagerException, LogNotAvailableException {
if (recovery) {
if (writeResult.deferred) {
throw new DataStorageManagerException("impossibile to have a deferred CommitLogResult during recovery");
}
LogSequenceNumber position = writeResult.getLogSequenceNumber();
if (dumpLogSequenceNumber != null && !position.after(dumpLogSequenceNumber)) {
// in "restore mode" the 'position" parameter is from the 'old' transaction log
Transaction transaction = null;
if (entry.transactionId > 0) {
transaction = tableSpaceManager.getTransaction(entry.transactionId);
}
if (transaction != null) {
transaction.touch();
LOGGER.log(Level.FINER, "{0}.{1} keep {2} at {3}, table restored from position {4}, it belongs to transaction {5} which was in progress during the dump of the table", new Object[] { table.tablespace, table.name, entry, position, dumpLogSequenceNumber, entry.transactionId });
} else {
LOGGER.log(Level.FINER, "{0}.{1} skip {2} at {3}, table restored from position {4}", new Object[] { table.tablespace, table.name, entry, position, dumpLogSequenceNumber });
return;
}
} else if (!position.after(bootSequenceNumber)) {
// recovery mode
Transaction transaction = null;
if (entry.transactionId > 0) {
transaction = tableSpaceManager.getTransaction(entry.transactionId);
}
if (transaction != null) {
transaction.touch();
LOGGER.log(Level.FINER, "{0}.{1} keep {2} at {3}, table booted at {4}, it belongs to transaction {5} which was in progress during the flush of the table", new Object[] { table.tablespace, table.name, entry, position, bootSequenceNumber, entry.transactionId });
} else {
LOGGER.log(Level.FINER, "{0}.{1} skip {2} at {3}, table booted at {4}", new Object[] { table.tablespace, table.name, entry, position, bootSequenceNumber });
return;
}
}
}
if (writeResult.sync) {
// wait for data to be stored to log
writeResult.getLogSequenceNumber();
}
switch(entry.type) {
case LogEntryType.DELETE:
{
// remove the record from the set of existing records
Bytes key = entry.key;
if (entry.transactionId > 0) {
Transaction transaction = tableSpaceManager.getTransaction(entry.transactionId);
if (transaction == null) {
/* Ignore missing transaction only if during recovery and ignore property is active */
if (recovery && ignoreMissingTransactionsOnRecovery) {
LOGGER.log(Level.WARNING, "Ignoring delete of {0} due to missing transaction {1}", new Object[] { entry.key, entry.transactionId });
} else {
throw new DataStorageManagerException("no such transaction " + entry.transactionId);
}
} else {
transaction.registerDeleteOnTable(this.table.name, key, writeResult);
}
} else {
applyDelete(key);
}
break;
}
case LogEntryType.UPDATE:
{
Bytes key = entry.key;
Bytes value = entry.value;
if (entry.transactionId > 0) {
Transaction transaction = tableSpaceManager.getTransaction(entry.transactionId);
if (transaction == null) {
/* Ignore missing transaction only if during recovery and ignore property is active */
if (recovery && ignoreMissingTransactionsOnRecovery) {
LOGGER.log(Level.WARNING, "Ignoring update of {0} due to missing transaction {1}", new Object[] { entry.key, entry.transactionId });
} else {
throw new DataStorageManagerException("no such transaction " + entry.transactionId);
}
} else {
transaction.registerRecordUpdate(this.table.name, key, value, writeResult);
}
} else {
applyUpdate(key, value);
}
break;
}
case LogEntryType.INSERT:
{
Bytes key = entry.key;
Bytes value = entry.value;
if (entry.transactionId > 0) {
Transaction transaction = tableSpaceManager.getTransaction(entry.transactionId);
if (transaction == null) {
/* Ignore missing transaction only if during recovery and ignore property is active */
if (recovery && ignoreMissingTransactionsOnRecovery) {
LOGGER.log(Level.WARNING, "Ignoring insert of {0} due to missing transaction {1}", new Object[] { entry.key, entry.transactionId });
} else {
throw new DataStorageManagerException("no such transaction " + entry.transactionId);
}
} else {
transaction.registerInsertOnTable(table.name, key, value, writeResult);
}
} else {
applyInsert(key, value, false);
}
break;
}
case LogEntryType.TRUNCATE_TABLE:
{
applyTruncate();
}
break;
default:
throw new IllegalArgumentException("unhandled entry type " + entry.type);
}
}
Aggregations