diff --git a/lang_helpers/gds_codes.ftn b/lang_helpers/gds_codes.ftn index a7db936f68..a5475c3f58 100644 --- a/lang_helpers/gds_codes.ftn +++ b/lang_helpers/gds_codes.ftn @@ -1948,6 +1948,8 @@ C -- PARAMETER (GDS__truncate_monitor = 335545267) INTEGER*4 GDS__truncate_context PARAMETER (GDS__truncate_context = 335545268) + INTEGER*4 GDS__merge_dup_update + PARAMETER (GDS__merge_dup_update = 335545269) 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 7e77fadd53..2bcb131df1 100644 --- a/lang_helpers/gds_codes.pas +++ b/lang_helpers/gds_codes.pas @@ -1943,6 +1943,8 @@ const gds_truncate_monitor = 335545267; isc_truncate_context = 335545268; gds_truncate_context = 335545268; + isc_merge_dup_update = 335545269; + gds_merge_dup_update = 335545269; isc_gfix_db_name = 335740929; gds_gfix_db_name = 335740929; isc_gfix_invalid_sw = 335740930; diff --git a/src/dsql/StmtNodes.cpp b/src/dsql/StmtNodes.cpp index 83376e0250..5159e4010f 100644 --- a/src/dsql/StmtNodes.cpp +++ b/src/dsql/StmtNodes.cpp @@ -2651,6 +2651,9 @@ const StmtNode* EraseNode::erase(thread_db* tdbb, jrd_req* request, WhichTrigger return parentStmt; } + if (forNode && (marks & StmtNode::MARK_MERGE)) + forNode->checkRecordUpdated(tdbb, request, rpb); + // If the stream was sorted, the various fields in the rpb are probably junk. // Just to make sure that everything is cool, refetch and release the record. @@ -2697,6 +2700,9 @@ const StmtNode* EraseNode::erase(thread_db* tdbb, jrd_req* request, WhichTrigger EXE_execute_triggers(tdbb, &relation->rel_post_erase, rpb, NULL, TRIGGER_DELETE, POST_TRIG); } + if (forNode && (marks & StmtNode::MARK_MERGE)) + forNode->setRecordUpdated(tdbb, request, rpb); + // Call IDX_erase (which checks constraints) after all post erase triggers have fired. // This is required for cascading referential integrity, which can be implemented as // post_erase triggers. @@ -4839,7 +4845,11 @@ DmlNode* ForNode::parse(thread_db* tdbb, MemoryPool& pool, CompilerScratch* csb, ForNode* node = FB_NEW_POOL(pool) ForNode(pool); if (csb->csb_blr_reader.peekByte() == blr_marks) - node->forUpdate = (PAR_marks(csb) & StmtNode::MARK_FOR_UPDATE) != 0; + { + unsigned marks = PAR_marks(csb); + node->forUpdate = (marks & StmtNode::MARK_FOR_UPDATE) != 0; + node->isMerge = (marks & StmtNode::MARK_MERGE) != 0; + } if (csb->csb_blr_reader.peekByte() == (UCHAR) blr_stall) node->stall = PAR_parse_stmt(tdbb, csb); @@ -4956,8 +4966,9 @@ void ForNode::genBlr(DsqlCompilerScratch* dsqlScratch) dsqlScratch->appendUChar(blr_for); - if (forUpdate) - dsqlScratch->putBlrMarkers(StmtNode::MARK_FOR_UPDATE); + const unsigned marks = (forUpdate ? StmtNode::MARK_FOR_UPDATE : 0) | (isMerge ? StmtNode::MARK_MERGE : 0); + if (marks) + dsqlScratch->putBlrMarkers(marks); if (!statement || dsqlForceSingular) dsqlScratch->appendUChar(blr_singular); @@ -5027,7 +5038,7 @@ StmtNode* ForNode::pass2(thread_db* tdbb, CompilerScratch* csb) if (rse->flags & RseNode::FLAG_WRITELOCK) withLock = true; - impureOffset = CMP_impure(csb, sizeof(Impure)); + impureOffset = CMP_impure(csb, isMerge ? sizeof(ImpureMerge) : sizeof(Impure)); return this; } @@ -5035,7 +5046,8 @@ StmtNode* ForNode::pass2(thread_db* tdbb, CompilerScratch* csb) const StmtNode* ForNode::execute(thread_db* tdbb, jrd_req* request, ExeState* /*exeState*/) const { jrd_tra* transaction = request->req_transaction; - Impure* impure = request->getImpure(impureOffset); + ImpureMerge* merge = request->getImpure(impureOffset); + Impure* impure = merge; switch (request->req_operation) { @@ -5043,6 +5055,8 @@ const StmtNode* ForNode::execute(thread_db* tdbb, jrd_req* request, ExeState* /* // initialize impure values impure->savepoint = 0; impure->writeLockMode = false; + if (isMerge) + merge->recUpdated = nullptr; if (!(transaction->tra_flags & TRA_system) && transaction->tra_save_point && @@ -5122,6 +5136,13 @@ const StmtNode* ForNode::execute(thread_db* tdbb, jrd_req* request, ExeState* /* } cursor->close(tdbb); + + if (isMerge) + { + delete merge->recUpdated; + merge->recUpdated = nullptr; + } + return parentStmt; } } @@ -5130,14 +5151,12 @@ const StmtNode* ForNode::execute(thread_db* tdbb, jrd_req* request, ExeState* /* return NULL; } - bool ForNode::isWriteLockMode(jrd_req* request) const { const Impure* impure = request->getImpure(impureOffset); return impure->writeLockMode; } - void ForNode::setWriteLockMode(jrd_req* request) const { Impure* impure = request->getImpure(impureOffset); @@ -5146,6 +5165,32 @@ void ForNode::setWriteLockMode(jrd_req* request) const impure->writeLockMode = true; } +void ForNode::checkRecordUpdated(thread_db* tdbb, jrd_req* request, record_param* rpb) const +{ + jrd_rel* relation = rpb->rpb_relation; + if (!isMerge || relation->isVirtual() || relation->rel_file || relation->rel_view_rse) + return; + + ImpureMerge* impure = request->getImpure(impureOffset); + + if (!impure->recUpdated) + return; + + if (impure->recUpdated->test(rpb->rpb_number.getValue())) + Arg::Gds(isc_merge_dup_update).raise(); +} + +void ForNode::setRecordUpdated(thread_db* tdbb, jrd_req* request, record_param* rpb) const +{ + jrd_rel* relation = rpb->rpb_relation; + if (!isMerge || relation->isVirtual() || relation->rel_file || relation->rel_view_rse) + return; + + ImpureMerge* impure = request->getImpure(impureOffset); + + RBM_SET(tdbb->getDefaultPool(), &impure->recUpdated, rpb->rpb_number.getValue()); +} + //-------------------- @@ -5538,6 +5583,7 @@ StmtNode* MergeNode::dsqlPass(DsqlCompilerScratch* dsqlScratch) forNode->dsqlForceSingular = true; forNode->forUpdate = true; + forNode->isMerge = true; // Get the already processed relations. RseNode* processedRse = nodeAs(forNode->rse->dsqlStreams->items[0]); @@ -5736,7 +5782,6 @@ StmtNode* MergeNode::dsqlPass(DsqlCompilerScratch* dsqlScratch) // Build the INSERT node. StoreNode* store = FB_NEW_POOL(pool) StoreNode(pool); - // TODO: store->marks |= StmtNode::MARK_MERGE; store->dsqlRelation = relation; store->dsqlFields = notMatched->fields; store->dsqlValues = notMatched->values; @@ -6614,6 +6659,9 @@ const StmtNode* ModifyNode::modify(thread_db* tdbb, jrd_req* request, WhichTrigg TRIGGER_UPDATE, POST_TRIG); } + if (forNode && (marks & StmtNode::MARK_MERGE)) + forNode->setRecordUpdated(tdbb, request, orgRpb); + // Now call IDX_modify_check_constrints after all post modify triggers // have fired. This is required for cascading referential integrity, // which can be implemented as post_erase triggers. @@ -6650,6 +6698,9 @@ const StmtNode* ModifyNode::modify(thread_db* tdbb, jrd_req* request, WhichTrigg impure->sta_state = 0; RLCK_reserve_relation(tdbb, transaction, relation, true); + if (forNode && (marks & StmtNode::MARK_MERGE)) + forNode->checkRecordUpdated(tdbb, request, orgRpb); + // If the stream was sorted, the various fields in the rpb are // probably junk. Just to make sure that everything is cool, // refetch and release the record. diff --git a/src/dsql/StmtNodes.h b/src/dsql/StmtNodes.h index de57a00c7a..1f9ebb6265 100644 --- a/src/dsql/StmtNodes.h +++ b/src/dsql/StmtNodes.h @@ -920,6 +920,7 @@ public: cursor(NULL), parBlrBeginCnt(0), forUpdate(false), + isMerge(false), withLock(false) { } @@ -937,6 +938,10 @@ public: bool isWriteLockMode(jrd_req* request) const; void setWriteLockMode(jrd_req* request) const; + // Used by UPDATE and DELETE sub-statements of MERGE + void checkRecordUpdated(thread_db* tdbb, jrd_req* request, record_param* rpb) const; + void setRecordUpdated(thread_db* tdbb, jrd_req* request, record_param* rpb) const; + public: struct Impure { @@ -944,6 +949,11 @@ public: bool writeLockMode; // true - driven statement (UPDATE\DELETE\SELECT WITH LOCK) works in "write lock" mode, false - normal mode }; + struct ImpureMerge : Impure + { + RecordBitmap* recUpdated; // updated and deleted records by MERGE statement + }; + NestConst dsqlSelect; NestConst dsqlInto; DeclareCursorNode* dsqlCursor; @@ -956,6 +966,7 @@ public: NestConst cursor; int parBlrBeginCnt; bool forUpdate; // part of UPDATE\DELETE\MERGE statement + bool isMerge; // part of MERGE statement bool withLock; // part of SELECT ... WITH LOCK statement }; diff --git a/src/include/gen/codetext.h b/src/include/gen/codetext.h index c42c0c0d61..e63ac2fc61 100644 --- a/src/include/gen/codetext.h +++ b/src/include/gen/codetext.h @@ -970,6 +970,7 @@ static const struct { {"truncate_warn", 335545266}, {"truncate_monitor", 335545267}, {"truncate_context", 335545268}, + {"merge_dup_update", 335545269}, {"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 bae6d6be9a..d034288de9 100644 --- a/src/include/gen/iberror.h +++ b/src/include/gen/iberror.h @@ -1004,6 +1004,7 @@ const ISC_STATUS isc_suspend_without_returns = 335545265L; const ISC_STATUS isc_truncate_warn = 335545266L; const ISC_STATUS isc_truncate_monitor = 335545267L; const ISC_STATUS isc_truncate_context = 335545268L; +const ISC_STATUS isc_merge_dup_update = 335545269L; const ISC_STATUS isc_gfix_db_name = 335740929L; const ISC_STATUS isc_gfix_invalid_sw = 335740930L; const ISC_STATUS isc_gfix_incmp_sw = 335740932L; @@ -1494,7 +1495,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 = 1438; +const ISC_STATUS isc_err_max = 1439; #else /* c definitions */ @@ -2468,6 +2469,7 @@ const ISC_STATUS isc_err_max = 1438; #define isc_truncate_warn 335545266L #define isc_truncate_monitor 335545267L #define isc_truncate_context 335545268L +#define isc_merge_dup_update 335545269L #define isc_gfix_db_name 335740929L #define isc_gfix_invalid_sw 335740930L #define isc_gfix_incmp_sw 335740932L @@ -2958,7 +2960,7 @@ const ISC_STATUS isc_err_max = 1438; #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 1438 +#define isc_err_max 1439 #endif diff --git a/src/include/gen/msgs.h b/src/include/gen/msgs.h index 2e85b9708d..01f1e84f3f 100644 --- a/src/include/gen/msgs.h +++ b/src/include/gen/msgs.h @@ -973,6 +973,7 @@ Data source : @4"}, /* eds_statement */ {335545266, "String truncated warning due to the following reason"}, /* truncate_warn */ {335545267, "Monitoring data does not fit into the field"}, /* truncate_monitor */ {335545268, "Engine data does not fit into return value of system function"}, /* truncate_context */ + {335545269, "Multiple source records cannot match the same target during MERGE"}, /* merge_dup_update */ {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 edf92ff4f0..867f9523bf 100644 --- a/src/include/gen/sql_code.h +++ b/src/include/gen/sql_code.h @@ -969,6 +969,7 @@ static const struct { {335545266, 304}, /* 946 truncate_warn */ {335545267, 304}, /* 947 truncate_monitor */ {335545268, 304}, /* 948 truncate_context */ + {335545269, -811}, /* 949 merge_dup_update */ {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 96bb4dae85..2997dfb43b 100644 --- a/src/include/gen/sql_state.h +++ b/src/include/gen/sql_state.h @@ -969,6 +969,7 @@ static const struct { {335545266, "01004"}, // 946 truncate_warn {335545267, "01004"}, // 947 truncate_monitor {335545268, "01004"}, // 948 truncate_context + {335545269, "21000"}, // 949 merge_dup_update {335740929, "00000"}, // 1 gfix_db_name {335740930, "00000"}, // 2 gfix_invalid_sw {335740932, "00000"}, // 4 gfix_incmp_sw diff --git a/src/msgs/facilities2.sql b/src/msgs/facilities2.sql index bcd38d3a16..4299ec0cf7 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 (?, ?, ?, ?); -- -('2020-03-04 16:39:50', 'JRD', 0, 949) +('2020-06-02 11:58:00', 'JRD', 0, 950) ('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 6a434171bb..58fbd036ca 100644 --- a/src/msgs/messages2.sql +++ b/src/msgs/messages2.sql @@ -1056,6 +1056,7 @@ Data source : @4', NULL, NULL) ('truncate_warn', NULL, 'cvt.cpp', NULL, 0, 946, NULL, 'String truncated warning due to the following reason', NULL, NULL); ('truncate_monitor', NULL, 'Monitoring.cpp', NULL, 0, 947, NULL, 'Monitoring data does not fit into the field', NULL, NULL); ('truncate_context', NULL, 'SysFunction.cpp', NULL, 0, 948, NULL, 'Engine data does not fit into return value of system function', NULL, NULL); +('merge_dup_update', NULL, 'StmtNodes.cpp', NULL, 0, 949, NULL, 'Multiple source records cannot match the same target during MERGE', 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 e85428a9ef..a685071278 100644 --- a/src/msgs/system_errors2.sql +++ b/src/msgs/system_errors2.sql @@ -955,6 +955,7 @@ set bulk_insert INSERT INTO SYSTEM_ERRORS (SQL_CODE, SQL_CLASS, SQL_SUBCLASS, FA (304, '01', '004', 0, 946, 'truncate_warn', NULL, NULL) (304, '01', '004', 0, 947, 'truncate_monitor', NULL, NULL) (304, '01', '004', 0, 948, 'truncate_context', NULL, NULL) +(-811, '21', '000', 0, 949, 'merge_dup_update', NULL, NULL) -- GFIX (-901, '00', '000', 3, 1, 'gfix_db_name', NULL, NULL) (-901, '00', '000', 3, 2, 'gfix_invalid_sw', NULL, NULL)