From cfbcbeda3c7c7e229afe27611c95935507878830 Mon Sep 17 00:00:00 2001 From: Adriano dos Santos Fernandes Date: Fri, 1 Mar 2019 16:17:19 -0300 Subject: [PATCH] Make it possible to start multiple transactions using the same initial transaction snapshot - CORE-6018 (#193) With this feature it's possible to create parallel (via different attachments) processes reading consistent data from a database. For example, a backup process may create multiple threads paralleling read data from the database. Also a web service may dispatch distributed sub services paralleling doing some processing. That is accomplished creating a transaction with SET TRANSACTION SNAPSHOT [ AT NUMBER ] or isc_tpb_at_snapshot_number. The from the first transaction may be obtained with RDB$GET_CONTEXT('SYSTEM', 'SNAPSHOT_NUMBER') or transaction info call with fb_info_tra_snapshot_number. Also added CORE-6017 - Add transaction info fb_info_tra_snapshot_number. --- doc/README.transaction_at_snapshot.md | 17 +++++ doc/sql.extensions/README.keywords | 1 + lang_helpers/gds_codes.ftn | 2 + lang_helpers/gds_codes.pas | 2 + src/common/classes/BlrWriter.h | 6 ++ src/dsql/Parser.cpp | 12 ++-- src/dsql/StmtNodes.cpp | 8 +++ src/dsql/StmtNodes.h | 1 + src/dsql/parse-conflicts.txt | 2 +- src/dsql/parse.y | 51 +++++++++----- src/include/consts_pub.h | 1 + src/include/gen/codetext.h | 1 + src/include/gen/iberror.h | 6 +- src/include/gen/msgs.h | 1 + src/include/gen/sql_code.h | 1 + src/include/gen/sql_state.h | 1 + src/jrd/inf.cpp | 4 ++ src/jrd/inf_pub.h | 1 + src/jrd/tpc.cpp | 31 +++++++-- src/jrd/tpc_proto.h | 4 +- src/jrd/tra.cpp | 99 +++++++++++++++++++++++++-- src/msgs/facilities2.sql | 2 +- src/msgs/messages2.sql | 1 + src/msgs/system_errors2.sql | 1 + src/yvalve/keywords.cpp | 1 + 25 files changed, 218 insertions(+), 39 deletions(-) create mode 100644 doc/README.transaction_at_snapshot.md diff --git a/doc/README.transaction_at_snapshot.md b/doc/README.transaction_at_snapshot.md new file mode 100644 index 0000000000..a51d662d07 --- /dev/null +++ b/doc/README.transaction_at_snapshot.md @@ -0,0 +1,17 @@ +# Transaction at defined snapshot number + +With this feature it's possible to create parallel (via different attachments) processes reading consistent data from a database. + +For example, a backup process may create multiple threads paralleling reading data from the database. + +Or a web service may dispatch distributed sub services paralleling doing some processing. + +That is accomplished creating a transaction with `SET TRANSACTION SNAPSHOT [ AT NUMBER ]` or `isc_tpb_at_snapshot_number `. + +The `snapshot number` from the first transaction may be obtained with `RDB$GET_CONTEXT('SYSTEM', 'SNAPSHOT_NUMBER')` or transaction info call with `fb_info_tra_snapshot_number`. + +Note that the `snapshot number` passed to the new transaction must be a snapshot of an active transaction. + + +Author: + Adriano dos Santos Fernandes diff --git a/doc/sql.extensions/README.keywords b/doc/sql.extensions/README.keywords index c779cc0354..fd41a56cef 100644 --- a/doc/sql.extensions/README.keywords +++ b/doc/sql.extensions/README.keywords @@ -330,6 +330,7 @@ Firebird 4.0 NATIVE * NORMALIZE_DECFLOAT * NTILE (1) + NUMBER OTHERS OVERRIDING PERCENT_RANK (1) diff --git a/lang_helpers/gds_codes.ftn b/lang_helpers/gds_codes.ftn index 2a78368303..a15f9e76d3 100644 --- a/lang_helpers/gds_codes.ftn +++ b/lang_helpers/gds_codes.ftn @@ -1914,6 +1914,8 @@ C -- PARAMETER (GDS__tom_chacha_key = 335545250) INTEGER*4 GDS__bad_repl_handle PARAMETER (GDS__bad_repl_handle = 335545251) + INTEGER*4 GDS__tra_snapshot_does_not_exist + PARAMETER (GDS__tra_snapshot_does_not_exist = 335545252) INTEGER*4 GDS__gfix_db_name PARAMETER (GDS__gfix_db_name = 335740929) INTEGER*4 GDS__gfix_invalid_sw diff --git a/lang_helpers/gds_codes.pas b/lang_helpers/gds_codes.pas index 1e37c2c011..d7db8c0818 100644 --- a/lang_helpers/gds_codes.pas +++ b/lang_helpers/gds_codes.pas @@ -1909,6 +1909,8 @@ const gds_tom_chacha_key = 335545250; isc_bad_repl_handle = 335545251; gds_bad_repl_handle = 335545251; + isc_tra_snapshot_does_not_exist = 335545252; + gds_tra_snapshot_does_not_exist = 335545252; isc_gfix_db_name = 335740929; gds_gfix_db_name = 335740929; isc_gfix_invalid_sw = 335740930; diff --git a/src/common/classes/BlrWriter.h b/src/common/classes/BlrWriter.h index cec39ac2b2..871eb51d54 100644 --- a/src/common/classes/BlrWriter.h +++ b/src/common/classes/BlrWriter.h @@ -64,6 +64,12 @@ public: appendUShort(val >> 16); } + void appendUInt64(FB_UINT64 val) + { + appendULong(val); + appendULong(val >> 32); + } + void appendUCharRepeated(UCHAR byte, int count) { for (int i = 0; i < count; ++i) diff --git a/src/dsql/Parser.cpp b/src/dsql/Parser.cpp index d0b51832cb..6fc13957e0 100644 --- a/src/dsql/Parser.cpp +++ b/src/dsql/Parser.cpp @@ -600,7 +600,7 @@ int Parser::yylexAux() * * This code recognizes the following token types: * - * NUMBER: string of digits which fits into a 32-bit integer + * NUMBER32BIT: string of digits which fits into a 32-bit integer * * NUMBER64BIT: string of digits whose value might fit into an SINT64, * depending on whether or not there is a preceding '-', which is to @@ -792,7 +792,7 @@ int Parser::yylexAux() // by a set of nibbles, using 0-9, a-f, or A-F. Odd numbers // of nibbles assume a leading '0'. The result is converted // to an integer, and the result returned to the caller. The - // token is identified as a NUMBER if it's a 32-bit or less + // token is identified as a NUMBER32BIT if it's a 32-bit or less // value, or a NUMBER64INT if it requires a 64-bit number. if (c == '0' && lex.ptr + 1 < lex.end && (*lex.ptr == 'x' || *lex.ptr == 'X') && (classes(lex.ptr[1]) & CHR_HEX)) @@ -833,7 +833,7 @@ int Parser::yylexAux() } // we have a valid hex token. Now give it back, either as - // an NUMBER or NUMBER64BIT. + // an NUMBER32BIT or NUMBER64BIT. if (!hexerror) { // if charlen > 8 (something like FFFF FFFF 0, w/o the spaces) @@ -888,7 +888,7 @@ int Parser::yylexAux() } else { - // we have an integer value. we'll return NUMBER. + // we have an integer value. we'll return NUMBER32BIT. // but we have to make a number value to be compatible // with existing code. @@ -933,7 +933,7 @@ int Parser::yylexAux() } yylval.int32Val = (SLONG) value; - return TOK_NUMBER; + return TOK_NUMBER32BIT; } // integer value } // if (!hexerror)... @@ -1111,7 +1111,7 @@ int Parser::yylexAux() { yylval.int32Val = (SLONG) number; //printf ("parse.y %p %d\n", yylval.legacyStr, number); - return TOK_NUMBER; + return TOK_NUMBER32BIT; } else { diff --git a/src/dsql/StmtNodes.cpp b/src/dsql/StmtNodes.cpp index b054681b76..c96d0445e3 100644 --- a/src/dsql/StmtNodes.cpp +++ b/src/dsql/StmtNodes.cpp @@ -8191,6 +8191,14 @@ SetTransactionNode* SetTransactionNode::dsqlPass(DsqlCompilerScratch* dsqlScratc for (RestrictionOption** i = reserveList.begin(); i != reserveList.end(); ++i) genTableLock(dsqlScratch, **i, lockLevel); + if (atSnapshotNumber.specified) + { + dsqlScratch->appendUChar(isc_tpb_at_snapshot_number); + static_assert(sizeof(CommitNumber) == sizeof(FB_UINT64), "sizeof(CommitNumber) == sizeof(FB_UINT64)"); + dsqlScratch->appendUChar(sizeof(CommitNumber)); + dsqlScratch->appendUInt64(atSnapshotNumber.value); + } + if (dsqlScratch->getBlrData().getCount() > 1) // 1 -> isc_tpb_version1 tpb.add(dsqlScratch->getBlrData().begin(), dsqlScratch->getBlrData().getCount()); diff --git a/src/dsql/StmtNodes.h b/src/dsql/StmtNodes.h index 37784a914c..84b566fca4 100644 --- a/src/dsql/StmtNodes.h +++ b/src/dsql/StmtNodes.h @@ -1619,6 +1619,7 @@ public: Nullable lockTimeout; Firebird::Array reserveList; Firebird::UCharBuffer tpb; + Nullable atSnapshotNumber; }; diff --git a/src/dsql/parse-conflicts.txt b/src/dsql/parse-conflicts.txt index 417d595d60..72608bfb7d 100644 --- a/src/dsql/parse-conflicts.txt +++ b/src/dsql/parse-conflicts.txt @@ -1 +1 @@ -56 shift/reduce conflicts, 17 reduce/reduce conflicts. +58 shift/reduce conflicts, 17 reduce/reduce conflicts. diff --git a/src/dsql/parse.y b/src/dsql/parse.y index 64fb62c940..545787a061 100644 --- a/src/dsql/parse.y +++ b/src/dsql/parse.y @@ -335,7 +335,7 @@ using namespace Firebird; %token FLOAT_NUMBER DECIMAL_NUMBER LIMIT64_INT %token LIMIT64_NUMBER %token SYMBOL -%token NUMBER +%token NUMBER32BIT %token STRING %token INTRODUCER @@ -5358,7 +5358,7 @@ tran_option($setTransactionNode) | NO WAIT { setClause($setTransactionNode->wait, "[NO] WAIT", false); } // isolation mode - | isolation_mode + | isolation_mode($setTransactionNode) { setClause($setTransactionNode->isoLevel, "ISOLATION LEVEL", $1); } // misc options | NO AUTO UNDO @@ -5378,24 +5378,38 @@ tran_option($setTransactionNode) restr_list($setTransactionNode) ; -%type isolation_mode -isolation_mode - : ISOLATION LEVEL iso_mode { $$ = $3;} +%type isolation_mode() +isolation_mode($setTransactionNode) + : ISOLATION LEVEL iso_mode($setTransactionNode) { $$ = $3;} | iso_mode ; -%type iso_mode -iso_mode - : snap_shot +%type iso_mode() +iso_mode($setTransactionNode) + : snap_shot($setTransactionNode) { $$ = $1; } | READ UNCOMMITTED version_mode { $$ = $3; } | READ COMMITTED version_mode { $$ = $3; } ; -%type snap_shot -snap_shot - : SNAPSHOT { $$ = SetTransactionNode::ISO_LEVEL_CONCURRENCY; } - | SNAPSHOT TABLE { $$ = SetTransactionNode::ISO_LEVEL_CONSISTENCY; } - | SNAPSHOT TABLE STABILITY { $$ = SetTransactionNode::ISO_LEVEL_CONSISTENCY; } +%type snap_shot() +snap_shot($setTransactionNode) + : SNAPSHOT + { $$ = SetTransactionNode::ISO_LEVEL_CONCURRENCY; } + | SNAPSHOT AT NUMBER snapshot_number + { + setClause($setTransactionNode->atSnapshotNumber, "SNAPSHOT AT NUMBER", (CommitNumber) $4); + $$ = SetTransactionNode::ISO_LEVEL_CONCURRENCY; + } + | SNAPSHOT TABLE + { $$ = SetTransactionNode::ISO_LEVEL_CONSISTENCY; } + | SNAPSHOT TABLE STABILITY + { $$ = SetTransactionNode::ISO_LEVEL_CONSISTENCY; } + ; + +%type snapshot_number +snapshot_number + : NUMBER32BIT { $$ = $1; } + | NUMBER64BIT { $$ = $1.number; } ; %type version_mode @@ -7371,7 +7385,7 @@ u_numeric_constant %type ul_numeric_constant ul_numeric_constant - : NUMBER + : NUMBER32BIT { $$ = MAKE_const_slong($1); } | FLOAT_NUMBER { $$ = MAKE_constant($1->c_str(), CONSTANT_DOUBLE); } @@ -7511,7 +7525,7 @@ signed_short_integer %type nonneg_short_integer nonneg_short_integer - : NUMBER + : NUMBER32BIT { if ($1 > SHRT_POS_MAX) yyabandon(YYPOSNARG(1), -842, isc_expec_short); // Short integer expected @@ -7522,7 +7536,7 @@ nonneg_short_integer %type neg_short_integer neg_short_integer - : NUMBER + : NUMBER32BIT { if ($1 > SHRT_NEG_MAX) yyabandon(YYPOSNARG(1), -842, isc_expec_short); // Short integer expected @@ -7544,7 +7558,7 @@ pos_short_integer %type unsigned_short_integer unsigned_short_integer - : NUMBER + : NUMBER32BIT { if ($1 > SHRT_UNSIGNED_MAX) yyabandon(YYPOSNARG(1), -842, isc_expec_ushort); // Unsigned short integer expected @@ -7561,7 +7575,7 @@ signed_long_integer %type long_integer long_integer - : NUMBER { $$ = $1;} + : NUMBER32BIT { $$ = $1;} ; @@ -8796,6 +8810,7 @@ non_reserved_word | NATIVE | NORMALIZE_DECFLOAT | NTILE + | NUMBER | OLDEST | OTHERS | OVERRIDING diff --git a/src/include/consts_pub.h b/src/include/consts_pub.h index 2caa31045f..e73f4e4237 100644 --- a/src/include/consts_pub.h +++ b/src/include/consts_pub.h @@ -244,6 +244,7 @@ #define isc_tpb_no_auto_undo 20 #define isc_tpb_lock_timeout 21 #define isc_tpb_read_consistency 22 +#define isc_tpb_at_snapshot_number 23 /************************/ diff --git a/src/include/gen/codetext.h b/src/include/gen/codetext.h index 453aba8661..d512f70e85 100644 --- a/src/include/gen/codetext.h +++ b/src/include/gen/codetext.h @@ -953,6 +953,7 @@ static const struct { {"tom_rsa_verify", 335545249}, {"tom_chacha_key", 335545250}, {"bad_repl_handle", 335545251}, + {"tra_snapshot_does_not_exist", 335545252}, {"gfix_db_name", 335740929}, {"gfix_invalid_sw", 335740930}, {"gfix_incmp_sw", 335740932}, diff --git a/src/include/gen/iberror.h b/src/include/gen/iberror.h index 51c547e4b1..07d204a39f 100644 --- a/src/include/gen/iberror.h +++ b/src/include/gen/iberror.h @@ -987,6 +987,7 @@ const ISC_STATUS isc_tom_rsa_sign = 335545248L; const ISC_STATUS isc_tom_rsa_verify = 335545249L; const ISC_STATUS isc_tom_chacha_key = 335545250L; const ISC_STATUS isc_bad_repl_handle = 335545251L; +const ISC_STATUS isc_tra_snapshot_does_not_exist = 335545252L; const ISC_STATUS isc_gfix_db_name = 335740929L; const ISC_STATUS isc_gfix_invalid_sw = 335740930L; const ISC_STATUS isc_gfix_incmp_sw = 335740932L; @@ -1461,7 +1462,7 @@ const ISC_STATUS isc_trace_switch_user_only = 337182757L; const ISC_STATUS isc_trace_switch_param_miss = 337182758L; const ISC_STATUS isc_trace_param_act_notcompat = 337182759L; const ISC_STATUS isc_trace_mandatory_switch_miss = 337182760L; -const ISC_STATUS isc_err_max = 1405; +const ISC_STATUS isc_err_max = 1406; #else /* c definitions */ @@ -2418,6 +2419,7 @@ const ISC_STATUS isc_err_max = 1405; #define isc_tom_rsa_verify 335545249L #define isc_tom_chacha_key 335545250L #define isc_bad_repl_handle 335545251L +#define isc_tra_snapshot_does_not_exist 335545252L #define isc_gfix_db_name 335740929L #define isc_gfix_invalid_sw 335740930L #define isc_gfix_incmp_sw 335740932L @@ -2892,7 +2894,7 @@ const ISC_STATUS isc_err_max = 1405; #define isc_trace_switch_param_miss 337182758L #define isc_trace_param_act_notcompat 337182759L #define isc_trace_mandatory_switch_miss 337182760L -#define isc_err_max 1405 +#define isc_err_max 1406 #endif diff --git a/src/include/gen/msgs.h b/src/include/gen/msgs.h index 066c292b48..b1bcd9ed4e 100644 --- a/src/include/gen/msgs.h +++ b/src/include/gen/msgs.h @@ -956,6 +956,7 @@ Data source : @4"}, /* eds_statement */ {335545249, "Verifying RSA-signed data"}, /* tom_rsa_verify */ {335545250, "Invalid key length @1, need 16 or 32"}, /* tom_chacha_key */ {335545251, "invalid replicator handle"}, /* bad_repl_handle */ + {335545252, "Transaction's base snapshot number does not exist"}, /* tra_snapshot_does_not_exist */ {335740929, "data base file name (@1) already given"}, /* gfix_db_name */ {335740930, "invalid switch @1"}, /* gfix_invalid_sw */ {335740932, "incompatible switch combination"}, /* gfix_incmp_sw */ diff --git a/src/include/gen/sql_code.h b/src/include/gen/sql_code.h index 855e9aa2bc..8b12277404 100644 --- a/src/include/gen/sql_code.h +++ b/src/include/gen/sql_code.h @@ -952,6 +952,7 @@ static const struct { {335545249, -901}, /* 929 tom_rsa_verify */ {335545250, -901}, /* 930 tom_chacha_key */ {335545251, -901}, /* 931 bad_repl_handle */ + {335545252, -901}, /* 932 tra_snapshot_does_not_exist */ {335740929, -901}, /* 1 gfix_db_name */ {335740930, -901}, /* 2 gfix_invalid_sw */ {335740932, -901}, /* 4 gfix_incmp_sw */ diff --git a/src/include/gen/sql_state.h b/src/include/gen/sql_state.h index ea42e53306..b71ba1e949 100644 --- a/src/include/gen/sql_state.h +++ b/src/include/gen/sql_state.h @@ -952,6 +952,7 @@ static const struct { {335545249, "22023"}, // 929 tom_rsa_verify {335545250, "22023"}, // 930 tom_chacha_key {335545251, "08003"}, // 931 bad_repl_handle + {335545252, "0B000"}, // 932 tra_snapshot_does_not_exist {335740929, "00000"}, // 1 gfix_db_name {335740930, "00000"}, // 2 gfix_invalid_sw {335740932, "00000"}, // 4 gfix_incmp_sw diff --git a/src/jrd/inf.cpp b/src/jrd/inf.cpp index d80d582046..27babdf081 100644 --- a/src/jrd/inf.cpp +++ b/src/jrd/inf.cpp @@ -1166,6 +1166,10 @@ void INF_transaction_info(const jrd_tra* transaction, memcpy(buffer, transaction->tra_attachment->att_database->dbb_database_name.c_str(), length); break; + case fb_info_tra_snapshot_number: + length = INF_convert(static_cast(transaction->tra_snapshot_number), buffer); + break; + default: buffer[0] = item; item = isc_info_error; diff --git a/src/jrd/inf_pub.h b/src/jrd/inf_pub.h index e509cbe854..0cfc8ce135 100644 --- a/src/jrd/inf_pub.h +++ b/src/jrd/inf_pub.h @@ -394,6 +394,7 @@ enum info_db_provider #define isc_info_tra_access 9 #define isc_info_tra_lock_timeout 10 #define fb_info_tra_dbpath 11 +#define fb_info_tra_snapshot_number 12 // isc_info_tra_isolation responses #define isc_info_tra_consistency 1 diff --git a/src/jrd/tpc.cpp b/src/jrd/tpc.cpp index 7beca4c765..3585d7067a 100644 --- a/src/jrd/tpc.cpp +++ b/src/jrd/tpc.cpp @@ -759,8 +759,7 @@ void TipCache::remapSnapshots(bool sync) } - -SnapshotHandle TipCache::beginSnapshot(thread_db* tdbb, AttNumber attachmentId, CommitNumber *commitNumber_out) +SnapshotHandle TipCache::beginSnapshot(thread_db* tdbb, AttNumber attachmentId, CommitNumber& commitNumber) { // Can only be called on initialized TipCache fb_assert(m_tpcHeader); @@ -772,16 +771,38 @@ SnapshotHandle TipCache::beginSnapshot(thread_db* tdbb, AttNumber attachmentId, // Remap snapshot list if it has been grown by someone else remapSnapshots(false); + if (commitNumber != 0) + { + SnapshotList* snapshots = m_snapshots->getHeader(); + ULONG slotsUsed = snapshots->slots_used.load(std::memory_order_relaxed); + bool found = false; + + for (SnapshotHandle slotNumber = 0; slotNumber < slotsUsed; ++slotNumber) + { + if (snapshots->slots[slotNumber].attachment_id.load(std::memory_order_relaxed) != 0 && + snapshots->slots[slotNumber].snapshot.load(std::memory_order_relaxed) == commitNumber) + { + found = true; + break; + } + } + + if (!found) + ERR_post(Arg::Gds(isc_tra_snapshot_does_not_exist)); + } + SnapshotHandle slotNumber = allocateSnapshotSlot(); // Note, that allocateSnapshotSlot might remap memory and thus invalidate pointers SnapshotList* snapshots = m_snapshots->getHeader(); // Store snapshot commit number and return handle - SnapshotData *slot = snapshots->slots + slotNumber; + SnapshotData* slot = snapshots->slots + slotNumber; - *commitNumber_out = m_tpcHeader->getHeader()->latest_commit_number.load(std::memory_order_acquire); - slot->snapshot.store(*commitNumber_out, std::memory_order_release); + if (commitNumber == 0) + commitNumber = m_tpcHeader->getHeader()->latest_commit_number.load(std::memory_order_acquire); + + slot->snapshot.store(commitNumber, std::memory_order_release); // Only assign attachment_id after we completed all other work slot->attachment_id.store(attachmentId, std::memory_order_release); diff --git a/src/jrd/tpc_proto.h b/src/jrd/tpc_proto.h index e16ec5dee1..4e7372dbf6 100644 --- a/src/jrd/tpc_proto.h +++ b/src/jrd/tpc_proto.h @@ -118,9 +118,9 @@ public: void updateOldestTransaction(thread_db* tdbb, TraNumber oldest, TraNumber oldestSnapshot); // Create snapshot. The snapshot shall use only versions committed - // before commitNumber_out. Snapshots inhibit GC to some extent. + // before commitNumber (the latest CN when it's 0). Snapshots inhibit GC to some extent. // When snapshot is no longer needed you call endSnapshot. - SnapshotHandle beginSnapshot(thread_db* tdbb, AttNumber attachmentId, CommitNumber *commitNumber_out); + SnapshotHandle beginSnapshot(thread_db* tdbb, AttNumber attachmentId, CommitNumber& commitNumber); // Deallocate snapshot. void endSnapshot(thread_db* tdbb, SnapshotHandle handle, AttNumber attachmentId); diff --git a/src/jrd/tra.cpp b/src/jrd/tra.cpp index 79fb0c2db9..9e5ea727cf 100644 --- a/src/jrd/tra.cpp +++ b/src/jrd/tra.cpp @@ -171,10 +171,11 @@ void TRA_setup_request_snapshot(Jrd::thread_db* tdbb, Jrd::jrd_req* request, boo // we need to set up statement snapshot for read consistency and own it request->req_snapshot.m_owner = request; + request->req_snapshot.m_number = 0; request->req_snapshot.m_handle = tdbb->getDatabase()->dbb_tip_cache->beginSnapshot(tdbb, - tdbb->getAttachment()->att_attachment_id, &request->req_snapshot.m_number); + tdbb->getAttachment()->att_attachment_id, request->req_snapshot.m_number); } @@ -1283,8 +1284,10 @@ void TRA_release_transaction(thread_db* tdbb, jrd_tra* transaction, Jrd::TraceTr } ++transaction->tra_use_count; + if (transaction->tra_lock) LCK_release(tdbb, transaction->tra_lock); + --transaction->tra_use_count; // release the sparse bit map used for commit retain transaction @@ -2732,6 +2735,7 @@ static void transaction_options(thread_db* tdbb, TriState wait, lock_timeout; TriState isolation, read_only, rec_version, read_consistency; bool anylock_write = false; + bool shared_snapshot = false; ++tpb; @@ -2745,6 +2749,14 @@ static void transaction_options(thread_db* tdbb, ERR_post(Arg::Gds(isc_bad_tpb_content) << Arg::Gds(isc_tpb_multiple_txn_isolation)); + if (shared_snapshot) + { + ERR_post( + Arg::Gds(isc_bad_tpb_content) << + Arg::Gds(isc_tpb_conflicting_options) << + Arg::Str("isc_tpb_consistency") << Arg::Str("isc_tpb_at_snapshot_number")); + } + transaction->tra_flags |= TRA_degree3; transaction->tra_flags &= ~TRA_read_committed; break; @@ -2763,6 +2775,14 @@ static void transaction_options(thread_db* tdbb, ERR_post(Arg::Gds(isc_bad_tpb_content) << Arg::Gds(isc_tpb_multiple_txn_isolation)); + if (shared_snapshot) + { + ERR_post( + Arg::Gds(isc_bad_tpb_content) << + Arg::Gds(isc_tpb_conflicting_options) << + Arg::Str("isc_tpb_read_committed") << Arg::Str("isc_tpb_at_snapshot_number")); + } + transaction->tra_flags &= ~TRA_degree3; transaction->tra_flags |= TRA_read_committed; break; @@ -3143,6 +3163,70 @@ static void transaction_options(thread_db* tdbb, } break; + case isc_tpb_at_snapshot_number: + { + const char* option_name = "isc_tpb_at_snapshot_number"; + + if (shared_snapshot) + { + ERR_post( + Arg::Gds(isc_bad_tpb_content) << + Arg::Gds(isc_tpb_multiple_spec) << Arg::Str(option_name)); + } + + if (transaction->tra_flags & TRA_read_committed) + { + ERR_post( + Arg::Gds(isc_bad_tpb_content) << + Arg::Gds(isc_tpb_conflicting_options) << + Arg::Str(option_name) << Arg::Str("isc_tpb_read_committed")); + } + + if (transaction->tra_flags & TRA_degree3) + { + ERR_post( + Arg::Gds(isc_bad_tpb_content) << + Arg::Gds(isc_tpb_conflicting_options) << + Arg::Str(option_name) << Arg::Str("isc_tpb_consistency")); + } + + if (tpb >= end) + { + ERR_post( + Arg::Gds(isc_bad_tpb_content) << + Arg::Gds(isc_tpb_missing_len) << Arg::Str(option_name)); + } + + const USHORT len = *tpb++; + + if (tpb >= end && len > 0) + { + ERR_post( + Arg::Gds(isc_bad_tpb_content) << + Arg::Gds(isc_tpb_missing_value) << Arg::Num(len) << Arg::Str(option_name)); + } + + if (end - tpb < len || len == 0) + { + ERR_post( + Arg::Gds(isc_bad_tpb_content) << + Arg::Gds(isc_tpb_corrupt_len) << Arg::Num(len) << Arg::Str(option_name)); + } + + shared_snapshot = true; + transaction->tra_snapshot_number = isc_portable_integer(tpb, len); + + if (transaction->tra_snapshot_number == 0) + { + ERR_post( + Arg::Gds(isc_bad_tpb_content) << + Arg::Str(option_name)); + } + + tpb += len; + } + break; + default: ERR_post(Arg::Gds(isc_bad_tpb_form)); } @@ -3326,9 +3410,16 @@ static void transaction_start(thread_db* tdbb, jrd_tra* trans) if (!(trans->tra_flags & TRA_read_committed)) { - trans->tra_snapshot_handle = - dbb->dbb_tip_cache->beginSnapshot(tdbb, - attachment->att_attachment_id, &trans->tra_snapshot_number); + try + { + trans->tra_snapshot_handle = dbb->dbb_tip_cache->beginSnapshot( + tdbb, attachment->att_attachment_id, trans->tra_snapshot_number); + } + catch (const Firebird::Exception&) + { + LCK_release(tdbb, lock); + throw; + } } // Next task is to find the oldest active transaction on the system. This diff --git a/src/msgs/facilities2.sql b/src/msgs/facilities2.sql index fb2806e8e1..814d5b90eb 100644 --- a/src/msgs/facilities2.sql +++ b/src/msgs/facilities2.sql @@ -1,7 +1,7 @@ /* MAX_NUMBER is the next number to be used, always one more than the highest message number. */ set bulk_insert INSERT INTO FACILITIES (LAST_CHANGE, FACILITY, FAC_CODE, MAX_NUMBER) VALUES (?, ?, ?, ?); -- -('2019-01-14 20:23:00', 'JRD', 0, 932) +('2019-03-01 12:35:00', 'JRD', 0, 933) ('2015-03-17 18:33:00', 'QLI', 1, 533) ('2018-03-17 12:00:00', 'GFIX', 3, 136) ('1996-11-07 13:39:40', 'GPRE', 4, 1) diff --git a/src/msgs/messages2.sql b/src/msgs/messages2.sql index ac1cb6032c..fc7f4553ac 100644 --- a/src/msgs/messages2.sql +++ b/src/msgs/messages2.sql @@ -1039,6 +1039,7 @@ Data source : @4', NULL, NULL) ('tom_rsa_verify', NULL, 'SysFunction.cpp', NULL, 0, 929, NULL, 'Verifying RSA-signed data', NULL, NULL); ('tom_chacha_key', NULL, 'SysFunction.cpp', NULL, 0, 930, NULL, 'Invalid key length @1, need 16 or 32', NULL, NULL); ('bad_repl_handle', NULL, 'jrd.cpp', NULL, 0, 931, NULL, 'invalid replicator handle', NULL, NULL); +('tra_snapshot_does_not_exist', NULL, 'tpc.cpp', NULL, 0, 932, NULL, 'Transaction''s base snapshot number does not exist', NULL, NULL); -- QLI (NULL, NULL, NULL, NULL, 1, 0, NULL, 'expected type', NULL, NULL); (NULL, NULL, NULL, NULL, 1, 1, NULL, 'bad block type', NULL, NULL); diff --git a/src/msgs/system_errors2.sql b/src/msgs/system_errors2.sql index 14ab8246d4..2a2b6af49a 100644 --- a/src/msgs/system_errors2.sql +++ b/src/msgs/system_errors2.sql @@ -938,6 +938,7 @@ set bulk_insert INSERT INTO SYSTEM_ERRORS (SQL_CODE, SQL_CLASS, SQL_SUBCLASS, FA (-901, '22', '023', 0, 929, 'tom_rsa_verify', NULL, NULL) (-901, '22', '023', 0, 930, 'tom_chacha_key', NULL, NULL) (-901, '08', '003', 0, 931, 'bad_repl_handle', NULL, NULL) +(-901, '0B', '000', 0, 932, 'tra_snapshot_does_not_exist', NULL, NULL) -- GFIX (-901, '00', '000', 3, 1, 'gfix_db_name', NULL, NULL) (-901, '00', '000', 3, 2, 'gfix_invalid_sw', NULL, NULL) diff --git a/src/yvalve/keywords.cpp b/src/yvalve/keywords.cpp index 0714feb7f9..3d4af054b6 100644 --- a/src/yvalve/keywords.cpp +++ b/src/yvalve/keywords.cpp @@ -317,6 +317,7 @@ static const TOK tokens[] = {TOK_NULLIF, "NULLIF", true}, {TOK_NULL, "NULL", false}, {TOK_NULLS, "NULLS", true}, + {TOK_NUMBER, "NUMBER", true}, {TOK_NUMERIC, "NUMERIC", false}, {TOK_OCTET_LENGTH, "OCTET_LENGTH", false}, {TOK_OF, "OF", false},