internal static void CompleteRowUpdate(SystemTransaction transaction, IMutableTable table, TableName tableName, RowId beforeRowid, RowId afterRowid) { IIndexSetDataSource[] ids = transaction.GetTableIndexes(tableName); foreach (IIndexSetDataSource i in ids) { i.Remove(beforeRowid); i.Insert(afterRowid); } // TODO: check for constraint violations table.Commit(); }
public void CommitTransaction(SystemTransaction transaction) { // If the transaction has had no changes, we return immediately if (!transaction.HasChanges) return; // The transaction we are working on through this commit process, SystemTransaction workingTransaction; // If the given transaction is of the same commit version as the current, // then the working transaction is the given transaction lock (commitSyncRoot) { // The list table ids we updated List<long> modifiedTables = new List<long>(); // If the given transaction is of the same commit version, then the // working version is the given. If there hasn't been a commit since // the given transaction was created then no transaction merge is // necessary. if (transaction.CommitVersion == currentCommitVersion) { workingTransaction = transaction; // We make sure we update the 'modified_tables' list with the set of // unique tables that were changed List<long> alteredTableIds = transaction.AlteredTables; alteredTableIds.Sort(); int sz = alteredTableIds.Count; long lastTableId = -1; // This goes through the altered table list provided by the // transaction and creates a unique set of tables that were // altered. for (int i = 0; i < sz; ++i) { long table_id = alteredTableIds[i]; if (table_id != lastTableId) { modifiedTables.Add(table_id); lastTableId = table_id; } } } // Otherwise we must create a new working transaction else { // ----------------- // Transaction MERGE // ----------------- // This condition occurs when a transaction has been committed after // the given transaction was created, and we need to move any updates // from the committing transaction to the latest update. workingTransaction = new SystemTransaction(this, db.CreateTransaction(), currentCommitVersion, transaction.User.Verified()); // SystemTable merges, // First of all replay the general database updates as described in // the journal (table and index creation/drops etc). TransactionJournal tranJournal = transaction.Journal; IEnumerator<JournalEntry> i = tranJournal.GetEnumerator(); // The list of tables created in this transaction List<long> tablesCreated = new List<long>(); while (i.MoveNext()) { JournalEntry entry = i.Current; JournalCommandCode command = entry.Code; long ident = entry.TableId; // Table commands, if (command == JournalCommandCode.TableCreate) { // Copy the created table from the original transaction to the // working transaction. // We check the table exists in the transaction. If it doesn't // it means the table was dropped. if (transaction.TableExists(ident)) { // Get the name of the object TableName tname = transaction.GetObjectName(ident); if (workingTransaction.TableExists(tname)) { // Create fails, the table name exists in the current // transaction throw new CommitException("CREATE TABLE '" + tname + "' failed: table with name exists in " + "current transaction"); } // Otherwise copy the table workingTransaction.CopyTableFrom(transaction, ident); tablesCreated.Add(ident); } } // When the table is structurally changed (columns, added/removed, // etc) else if (command == JournalCommandCode.TableAlter) { long tableId = ident; // If this table was created by this transaction, then we don't // need to worry, we have the most recent version of the table // structure. // Otherwise, the table was altered during the transaction, so // we need to copy the most recent version if it hasn't changed. if (!tablesCreated.Contains(tableId)) { // Check it exists in the current // If it doesn't it means it was dropped if (transaction.TableExists(tableId)) { // Check the table exists if (!workingTransaction.TableExists(tableId)) { throw new CommitException("ALTER TABLE '" + transaction.GetObjectName(tableId) + "' failed: table does not exist in the " + "current transaction"); } // Check the version of the table we are dropping is the same // version and exists. long ver1 = transaction.GetTableVersion(tableId); long ver2 = workingTransaction.GetTableVersion(tableId); // If the versions are different, some modification has happened // to the table so we generate an error if (ver1 != ver2) throw new CommitException("ALTER TABLE '" + transaction.GetObjectName(tableId) + "' failed: Table was modified by a concurrent transaction"); // Okay, we can now copy the table. We drop the existing table // and copy the new one over. workingTransaction.DropTable(tableId); workingTransaction.CopyTableFrom(transaction, tableId); } } } else if (command == JournalCommandCode.TableDrop) { long table_id = ident; // Check the table exists if (!workingTransaction.TableExists(table_id)) { throw new CommitException("DROP TABLE '" + transaction.GetObjectName(table_id) + "' failed: Table does not exist in the current transaction"); } // Check the version of the table we are dropping is the same // version and exists. long ver1 = transaction.GetTableVersion(table_id); long ver2 = workingTransaction.GetTableVersion(table_id); // If the versions are different, some modification has happened // to the table so we generate an error if (ver1 != ver2) { throw new CommitException("DROP TABLE '" + transaction.GetObjectName(table_id) + "' failed: Table was modified by a concurrent transaction"); } // Drop the table workingTransaction.DropTable(table_id); } // Index commands, else if (command == JournalCommandCode.IndexAdd) { long indexId = ident; // The name of the table of the index TableName indexTname = transaction.GetIndexName(indexId).TableName; // If the above returns null, it means the index no longer // exists so we skip if (indexTname != null) { // If the table doesn't exist in the working transaction, if (!workingTransaction.TableExists(indexTname)) throw new CommitException("CREATE INDEX on '" + indexTname + "' failed: table does not exist in the current transaction"); // Get the table id long tableId = workingTransaction.GetTableId(indexTname); // NOTE: This check ensures the index we copy from the transaction // we are committing is correct and up to date. We should, // perhaps, rewrite this to rebuild the index if there were // concurrent modifications to the table. // If the versions of the table we are creating the index on is // different then we fail, long ver1 = transaction.GetTableVersion(tableId); long ver2 = workingTransaction.GetTableVersion(tableId); // If the versions are different, some modification has happened // to the table so we generate an error if (ver1 != ver2) { throw new CommitException("CREATE INDEX on '" + indexTname + "' failed: Table was modified by " + "a concurrent transaction"); } // Copy the created index, workingTransaction.CopyIndexFrom(transaction, ident); } } else if (command == JournalCommandCode.IndexDelete) { long indexId = ident; // Drop the index. This fails if the index doesn't exist in the // current version. Note that this will succeed if there were // modifications to the table by a concurrent transaction. workingTransaction.DropIndex(indexId); } // Otherwise unrecognized else { throw new ApplicationException("Unknown journal entry: " + command); } } // Now replay the table level operations, // The list of all tables changed during the lifespan of the // transaction, List<long> alteredTableIds = transaction.AlteredTables; alteredTableIds.Sort(); long lastTableId = -1; int sz = alteredTableIds.Count; // For each table id that was altered, for (int n = 0; n < sz; ++n) { long tableId = alteredTableIds[n]; if (tableId != lastTableId) { // The SystemTable object SystemTable currentTable = workingTransaction.GetTable(tableId); // If the table no longer exists we ignore the journal and go to // the next table. We assume the table drop has not broken // database integrity if we have reached this part. if (currentTable != null) { // Add this to the list of tables we updated modifiedTables.Add(tableId); // The table name TableName tableName = currentTable.Name; // The indexes on this table SystemIndexSetDataSource[] indexes = workingTransaction.GetTableIndexes(tableName); // The table from which we are merging in entries, SystemTable toMerge = transaction.GetTable(tableId); // Get the journal for this table id, TransactionJournal journal = toMerge.Journal; // Replay the operations in the journal, IEnumerator<JournalEntry> ti = journal.GetEnumerator(); while (ti.MoveNext()) { JournalEntry entry = ti.Current; JournalCommandCode command = entry.Code; RowId ident = entry.RowId; if (command == JournalCommandCode.RowAdd) { currentTable.CopyRowIdFrom(toMerge, ident); // Update the indexes foreach (IIndexSetDataSource idx in indexes) { idx.Insert(ident); } } else if (command == JournalCommandCode.RowRemove) { // This will commit exception if the row no longer exists. currentTable.RemoveRowId(ident); // Update the indexes foreach (IIndexSetDataSource idx in indexes) { idx.Remove(ident); } } else if (command == JournalCommandCode.ColumnAdd) { // Can be ignored, this is handled by a structural change // entry in the transaction log, and copying the table over // from the lower version transaction. } else if (command == JournalCommandCode.ColumnRemove) { // Can be ignored, this is handled by a structural change // entry in the transaction log, and copying the table over // from the lower version transaction. } } } lastTableId = tableId; } } // We have now successfully replayed all the operations on the working // transaction. The following artifacts may now be in the database; // 1) Unique constraints may be violated // 2) Referential integrity may be violated // 3) Check constraints need to be checked again if they contain // nested query functions. } // Go through the list of updated tables and increment the commit // version on each of them foreach (long modifiedTableId in modifiedTables) { workingTransaction.IncrementTableVersion(modifiedTableId); } // Check any deferred constraints on the working_transaction // TODO: all the table/index merge functionality here... try { // And commit the working transaction db.CommitTransaction(workingTransaction.State); // Increment the commit version number ++currentCommitVersion; } catch (Exception e) { throw new CommitException("The database state failed to commit the transaction changes: " + e.Message, e); } } }