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
6namespace duckdb {
7
8MetaTransaction::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
13MetaTransaction &MetaTransaction::Get(ClientContext &context) {
14 return context.transaction.ActiveTransaction();
15}
16
17ValidChecker &ValidChecker::Get(MetaTransaction &transaction) {
18 return transaction.transaction_validity;
19}
20
21Transaction &Transaction::Get(ClientContext &context, AttachedDatabase &db) {
22 auto &meta_transaction = MetaTransaction::Get(context);
23 return meta_transaction.GetTransaction(db);
24}
25
26Transaction &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
43Transaction &Transaction::Get(ClientContext &context, Catalog &catalog) {
44 return Transaction::Get(context, db&: catalog.GetAttached());
45}
46
47string 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
69void 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
81idx_t MetaTransaction::GetActiveQuery() {
82 return active_query;
83}
84
85void 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
92void 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