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:
parent
a508ca21e2
commit
9cf05fccde
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
};
|
||||
|
||||
|
@ -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},
|
||||
|
@ -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
|
||||
|
||||
|
@ -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 */
|
||||
|
@ -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 */
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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);
|
||||
|
@ -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)
|
||||
|
Loading…
Reference in New Issue
Block a user