| 1 | #include "duckdb/transaction/meta_transaction.hpp" |
| 2 | #include "duckdb/main/client_context.hpp" |
| 3 | #include "duckdb/main/attached_database.hpp" |
| 4 | #include "duckdb/transaction/transaction_manager.hpp" |
| 5 | |
| 6 | namespace duckdb { |
| 7 | |
| 8 | MetaTransaction::MetaTransaction(ClientContext &context_p, timestamp_t start_timestamp_p, idx_t catalog_version_p) |
| 9 | : context(context_p), start_timestamp(start_timestamp_p), catalog_version(catalog_version_p), read_only(true), |
| 10 | active_query(MAXIMUM_QUERY_ID), modified_database(nullptr) { |
| 11 | } |
| 12 | |
| 13 | MetaTransaction &MetaTransaction::Get(ClientContext &context) { |
| 14 | return context.transaction.ActiveTransaction(); |
| 15 | } |
| 16 | |
| 17 | ValidChecker &ValidChecker::Get(MetaTransaction &transaction) { |
| 18 | return transaction.transaction_validity; |
| 19 | } |
| 20 | |
| 21 | Transaction &Transaction::Get(ClientContext &context, AttachedDatabase &db) { |
| 22 | auto &meta_transaction = MetaTransaction::Get(context); |
| 23 | return meta_transaction.GetTransaction(db); |
| 24 | } |
| 25 | |
| 26 | Transaction &MetaTransaction::GetTransaction(AttachedDatabase &db) { |
| 27 | auto entry = transactions.find(x: &db); |
| 28 | if (entry == transactions.end()) { |
| 29 | auto new_transaction = db.GetTransactionManager().StartTransaction(context); |
| 30 | if (!new_transaction) { |
| 31 | throw InternalException("StartTransaction did not return a valid transaction" ); |
| 32 | } |
| 33 | new_transaction->active_query = active_query; |
| 34 | all_transactions.push_back(x: &db); |
| 35 | transactions[&db] = new_transaction; |
| 36 | return *new_transaction; |
| 37 | } else { |
| 38 | D_ASSERT(entry->second->active_query == active_query); |
| 39 | return *entry->second; |
| 40 | } |
| 41 | } |
| 42 | |
| 43 | Transaction &Transaction::Get(ClientContext &context, Catalog &catalog) { |
| 44 | return Transaction::Get(context, db&: catalog.GetAttached()); |
| 45 | } |
| 46 | |
| 47 | string MetaTransaction::Commit() { |
| 48 | string error; |
| 49 | // commit transactions in reverse order |
| 50 | for (idx_t i = all_transactions.size(); i > 0; i--) { |
| 51 | auto db = all_transactions[i - 1]; |
| 52 | auto entry = transactions.find(x: db.get()); |
| 53 | if (entry == transactions.end()) { |
| 54 | throw InternalException("Could not find transaction corresponding to database in MetaTransaction" ); |
| 55 | } |
| 56 | auto &transaction_manager = db->GetTransactionManager(); |
| 57 | auto transaction = entry->second; |
| 58 | if (error.empty()) { |
| 59 | // commit |
| 60 | error = transaction_manager.CommitTransaction(context, transaction); |
| 61 | } else { |
| 62 | // we have encountered an error previously - roll back subsequent entries |
| 63 | transaction_manager.RollbackTransaction(transaction); |
| 64 | } |
| 65 | } |
| 66 | return error; |
| 67 | } |
| 68 | |
| 69 | void MetaTransaction::Rollback() { |
| 70 | // rollback transactions in reverse order |
| 71 | for (idx_t i = all_transactions.size(); i > 0; i--) { |
| 72 | auto db = all_transactions[i - 1]; |
| 73 | auto &transaction_manager = db->GetTransactionManager(); |
| 74 | auto entry = transactions.find(x: db.get()); |
| 75 | D_ASSERT(entry != transactions.end()); |
| 76 | auto transaction = entry->second; |
| 77 | transaction_manager.RollbackTransaction(transaction); |
| 78 | } |
| 79 | } |
| 80 | |
| 81 | idx_t MetaTransaction::GetActiveQuery() { |
| 82 | return active_query; |
| 83 | } |
| 84 | |
| 85 | void MetaTransaction::SetActiveQuery(transaction_t query_number) { |
| 86 | active_query = query_number; |
| 87 | for (auto &entry : transactions) { |
| 88 | entry.second->active_query = query_number; |
| 89 | } |
| 90 | } |
| 91 | |
| 92 | void MetaTransaction::ModifyDatabase(AttachedDatabase &db) { |
| 93 | if (db.IsSystem() || db.IsTemporary()) { |
| 94 | // we can always modify the system and temp databases |
| 95 | return; |
| 96 | } |
| 97 | if (!modified_database) { |
| 98 | modified_database = &db; |
| 99 | return; |
| 100 | } |
| 101 | if (&db != modified_database.get()) { |
| 102 | throw TransactionException( |
| 103 | "Attempting to write to database \"%s\" in a transaction that has already modified database \"%s\" - a " |
| 104 | "single transaction can only write to a single attached database." , |
| 105 | db.GetName(), modified_database->GetName()); |
| 106 | } |
| 107 | } |
| 108 | |
| 109 | } // namespace duckdb |
| 110 | |