8
0
mirror of https://github.com/FirebirdSQL/firebird.git synced 2025-01-22 16:43:03 +01:00

Fixed bug CORE-2274 : MERGE non-standard behaviour, accepts multiple matches

This commit is contained in:
hvlad 2020-06-02 14:17:09 +03:00
parent a508ca21e2
commit 9cf05fccde
12 changed files with 85 additions and 11 deletions

View File

@ -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

View File

@ -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;

View File

@ -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<Impure>(impureOffset);
ImpureMerge* merge = request->getImpure<ImpureMerge>(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<Impure>(impureOffset);
return impure->writeLockMode;
}
void ForNode::setWriteLockMode(jrd_req* request) const
{
Impure* impure = request->getImpure<Impure>(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<ImpureMerge>(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<ImpureMerge>(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<RseNode>(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.

View File

@ -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<SelectNode> dsqlSelect;
NestConst<ValueListNode> dsqlInto;
DeclareCursorNode* dsqlCursor;
@ -956,6 +966,7 @@ public:
NestConst<Cursor> 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
};

View File

@ -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},

View File

@ -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

View File

@ -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 */

View File

@ -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 */

View File

@ -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

View File

@ -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)

View File

@ -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);

View File

@ -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)