From 372d48c97bd65560191a63244016cd58abc46a33 Mon Sep 17 00:00:00 2001 From: asfernandes Date: Sun, 23 Oct 2011 01:31:11 +0000 Subject: [PATCH] Improvement CORE-3639 - Allow the use of multiple WHEN MATCHED / NOT MATCHED clauses in MERGE, as per the SQL 2008 specification. Also updated MERGE and RETURNING docs, and fixed a bug with MERGE WHEN MATCHED DELETE and RETURNING. --- doc/sql.extensions/README.merge.txt | 20 +- doc/sql.extensions/README.returning | 9 +- src/dsql/StmtNodes.cpp | 321 +++++++++++++++++----------- src/dsql/StmtNodes.h | 44 ++-- src/dsql/parse.y | 45 ++-- 5 files changed, 279 insertions(+), 160 deletions(-) diff --git a/doc/sql.extensions/README.merge.txt b/doc/sql.extensions/README.merge.txt index 7f57ab5444..10b066b270 100644 --- a/doc/sql.extensions/README.merge.txt +++ b/doc/sql.extensions/README.merge.txt @@ -15,21 +15,24 @@ MERGE statement INTO [ [AS] ] USING
[ [AS] ] ON - [ ] - [ ] + ... + + + ::= + | + ::= - WHEN MATCHED THEN + WHEN MATCHED [ AND ] THEN UPDATE SET ::= - WHEN NOT MATCHED THEN + WHEN NOT MATCHED [ AND ] THEN INSERT [ ] VALUES Syntax rules: - 1. At least one of and should be specified - and each one should not be specified more than one time. + 1. At least one of and should be specified. Scope: DSQL, PSQL @@ -52,4 +55,9 @@ MERGE statement UPDATE is called when a record exist in the left table (INTO), otherwise INSERT is called. + As soon is decided if the source matched or not a record in the target, the set of the + correspondent (WHEN MATCHED / WHEN NOT MATCHED) statements are evaluated in the order specified, + to check its optional conditions. The first statement which have its condition evaluated to true + is the one which will be executed, and the subsequent ones will be ignored. + If no record is returned in the join, INSERT is not called. diff --git a/doc/sql.extensions/README.returning b/doc/sql.extensions/README.returning index 508685c939..7e282b5b34 100644 --- a/doc/sql.extensions/README.returning +++ b/doc/sql.extensions/README.returning @@ -4,7 +4,7 @@ RETURNING clause Function: Allow to return the column values actually stored in the table as a result of the "INSERT", - "UPDATE OR INSERT", UPDATE and DELETE statements. + "UPDATE OR INSERT", UPDATE, DELETE and MERGE statements. The most common usage is to retrieve the value of the primary key generated inside a BEFORE-trigger. Authors: @@ -41,12 +41,15 @@ RETURNING clause so the existing connectivity drivers should support this feature automagically. 4. Any explicit record change (update or delete) performed by AFTER-triggers is ignored by the RETURNING clause. - 5. OLD and NEW could be used in RETURNING clause of UPDATE and INSERT OR UPDATE statements. + 5. OLD and NEW could be used in RETURNING clause of UPDATE, INSERT OR UPDATE and MERGE statements. 6. In UPDATE and INSERT OR UPDATE statements, unqualified or qualified by table name/alias fields are resolved to NEW. + 7. In MERGE WHEN MATCHED UPDATE and MERGE WHEN NOT MATCHED statements, unqualified or qualified + by table name/alias fields are resolved to NEW. In MERGE WHEN MATCHED DELETE they are + resolved to OLD. Limitations: - 1. The modify statement (INSERT, UPDATE, DELETE) should operate in no more than one record + 1. The modify statement (INSERT, UPDATE, DELETE, MERGE) should operate in no more than one record (i.e. should be singleton). 2. The statement always return one row in DSQL, even if no record is inserted/updated/deleted. This may be changed in the future (i.e. return empty resultset). diff --git a/src/dsql/StmtNodes.cpp b/src/dsql/StmtNodes.cpp index ac02b90d19..3eb55fb375 100644 --- a/src/dsql/StmtNodes.cpp +++ b/src/dsql/StmtNodes.cpp @@ -128,12 +128,12 @@ namespace // Play with contexts for RETURNING purposes. // Its assumed that oldContext is already on the stack. // Changes oldContext name to "OLD". - ReturningProcessor(DsqlCompilerScratch* aScratch, dsql_ctx* oldContext, dsql_ctx* modContext) + ReturningProcessor(DsqlCompilerScratch* aScratch, dsql_ctx* aOldContext, dsql_ctx* modContext) : scratch(aScratch), - autoAlias(&oldContext->ctx_alias, OLD_CONTEXT), - autoInternalAlias(&oldContext->ctx_internal_alias, oldContext->ctx_alias), - autoFlags(&oldContext->ctx_flags, oldContext->ctx_flags | CTX_system | CTX_returning), - hasModContext(modContext != NULL) + oldContext(aOldContext), + oldAlias(oldContext->ctx_alias), + oldInternalAlias(oldContext->ctx_internal_alias), + autoFlags(&oldContext->ctx_flags, oldContext->ctx_flags | CTX_system | CTX_returning) { // Clone the modify/old context and push with name "NEW" in a greater scope level. @@ -141,21 +141,26 @@ namespace if (modContext) { - // push the modify context in the same scope level + // Push the modify context in the same scope level. scratch->context->push(modContext); *newContext = *modContext; newContext->ctx_flags |= CTX_system; } else { + // Create the target (= OLD) context and push it on the stack. + dsql_ctx* targetContext = FB_NEW(scratch->getPool()) dsql_ctx(scratch->getPool()); + *targetContext = *oldContext; + targetContext->ctx_flags &= ~CTX_system; // resolve unqualified fields + scratch->context->push(targetContext); + // This is NEW in the context of a DELETE. Mark it as NULL. *newContext = *oldContext; newContext->ctx_flags |= CTX_null; - - // Remove the system flag, so unqualified fields could be resolved to this context. - oldContext->ctx_flags &= ~CTX_system; } + oldContext->ctx_alias = oldContext->ctx_internal_alias = OLD_CONTEXT; + newContext->ctx_alias = newContext->ctx_internal_alias = MAKE_cstring(NEW_CONTEXT)->str_data; newContext->ctx_flags |= CTX_returning; @@ -164,10 +169,12 @@ namespace ~ReturningProcessor() { + oldContext->ctx_alias = oldAlias; + oldContext->ctx_internal_alias = oldInternalAlias; + // Restore the context stack. scratch->context->pop(); - if (hasModContext) - scratch->context->pop(); + scratch->context->pop(); } // Process the RETURNING clause. @@ -208,8 +215,8 @@ namespace private: DsqlCompilerScratch* scratch; - AutoSetRestore autoAlias; - AutoSetRestore autoInternalAlias; + dsql_ctx* oldContext; + string oldAlias, oldInternalAlias; AutoSetRestore autoFlags; bool hasModContext; }; @@ -4752,8 +4759,6 @@ StmtNode* MergeNode::dsqlPass(DsqlCompilerScratch* dsqlScratch) dsql_nod* source = dsqlUsing; // USING dsql_nod* target = dsqlRelation; // INTO - dsql_nod* updDelCondition = dsqlWhenMatchedCondition; - dsql_nod* insCondition = dsqlWhenNotMatchedCondition; // Build a join between USING and INTO tables. RseNode* join = FB_NEW(pool) RseNode(pool); @@ -4763,7 +4768,7 @@ StmtNode* MergeNode::dsqlPass(DsqlCompilerScratch* dsqlScratch) join->dsqlFrom->nod_arg[0] = source; // Left join if WHEN NOT MATCHED is present. - if (dsqlWhenNotMatchedPresent) + if (dsqlWhenNotMatched.hasData()) join->rse_jointype = blr_left; join->dsqlFrom->nod_arg[1] = target; @@ -4777,13 +4782,40 @@ StmtNode* MergeNode::dsqlPass(DsqlCompilerScratch* dsqlScratch) dsql_nod* querySpecNod = MAKE_node(Dsql::nod_class_exprnode, 1); querySpecNod->nod_arg[0] = reinterpret_cast(querySpec); - if (updDelCondition || insCondition) + dsql_nod* matchedConditions = NULL; + dsql_nod* notMatchedConditions = NULL; + + for (Matched* matched = dsqlWhenMatched.begin(); matched != dsqlWhenMatched.end(); ++matched) + { + if (matched->condition) + matchedConditions = PASS1_compose(matchedConditions, matched->condition, blr_or); + else + { + matchedConditions = NULL; + break; + } + } + + for (NotMatched* notMatched = dsqlWhenNotMatched.begin(); + notMatched != dsqlWhenNotMatched.end(); + ++notMatched) + { + if (notMatched->condition) + notMatchedConditions = PASS1_compose(notMatchedConditions, notMatched->condition, blr_or); + else + { + notMatchedConditions = NULL; + break; + } + } + + if (matchedConditions || notMatchedConditions) { const char* targetName = ExprNode::as(target)->alias.nullStr(); if (!targetName) targetName = ExprNode::as(target)->dsqlName.c_str(); - if (dsqlWhenMatchedPresent) // WHEN MATCHED + if (dsqlWhenMatched.hasData()) // WHEN MATCHED { MissingBoolNode* missingNode = FB_NEW(pool) MissingBoolNode( pool, MAKE_node(Dsql::nod_class_exprnode, 1)); @@ -4796,14 +4828,14 @@ StmtNode* MergeNode::dsqlPass(DsqlCompilerScratch* dsqlScratch) querySpec->dsqlWhere = MAKE_node(Dsql::nod_class_exprnode, 1); querySpec->dsqlWhere->nod_arg[0] = reinterpret_cast(notNode); - } - if (updDelCondition) - querySpec->dsqlWhere = PASS1_compose(querySpec->dsqlWhere, updDelCondition, blr_and); + if (matchedConditions) + querySpec->dsqlWhere = PASS1_compose(querySpec->dsqlWhere, matchedConditions, blr_and); + } dsql_nod* temp = NULL; - if (dsqlWhenNotMatchedPresent) // WHEN NOT MATCHED + if (dsqlWhenNotMatched.hasData()) // WHEN NOT MATCHED { MissingBoolNode* missingNode = FB_NEW(pool) MissingBoolNode( pool, MAKE_node(Dsql::nod_class_exprnode, 1)); @@ -4813,8 +4845,8 @@ StmtNode* MergeNode::dsqlPass(DsqlCompilerScratch* dsqlScratch) temp = MAKE_node(Dsql::nod_class_exprnode, 1); temp->nod_arg[0] = reinterpret_cast(missingNode); - if (insCondition) - temp = PASS1_compose(temp, insCondition, blr_and); + if (notMatchedConditions) + temp = PASS1_compose(temp, notMatchedConditions, blr_and); querySpec->dsqlWhere = PASS1_compose(querySpec->dsqlWhere, temp, blr_or); } @@ -4845,113 +4877,124 @@ StmtNode* MergeNode::dsqlPass(DsqlCompilerScratch* dsqlScratch) DsqlContextStack usingCtxs; dsqlGetContexts(usingCtxs, source); - StmtNode* update = NULL; - StmtNode* matchedRet = NULL; + StmtNode* processedRet = NULL; StmtNode* nullRet = NULL; - if (dsqlWhenMatchedPresent && dsqlWhenMatchedAssignments) + StmtNode* update = NULL; + IfNode* lastIf = NULL; + + for (Matched* matched = dsqlWhenMatched.begin(); matched != dsqlWhenMatched.end(); ++matched) { - // Get the assignments of the UPDATE dsqlScratch. - CompoundStmtNode* stmts = dsqlWhenMatchedAssignments; - Array org_values, new_values; + IfNode* thisIf = FB_NEW(pool) IfNode(pool); - // Separate the new and org values to process in correct contexts. - for (size_t i = 0; i < stmts->statements.getCount(); ++i) + if (matched->assignments) { - const AssignmentNode* const assign = stmts->statements[i]->as(); - fb_assert(assign); - org_values.add(assign->dsqlAsgnFrom); - new_values.add(assign->dsqlAsgnTo); - } + // Get the assignments of the UPDATE dsqlScratch. + CompoundStmtNode* stmts = matched->assignments; + Array orgValues, newValues; - // Build the MODIFY node. - ModifyNode* modify = FB_NEW(pool) ModifyNode(pool); + // Separate the new and org values to process in correct contexts. + for (size_t i = 0; i < stmts->statements.getCount(); ++i) + { + const AssignmentNode* const assign = stmts->statements[i]->as(); + fb_assert(assign); + orgValues.add(assign->dsqlAsgnFrom); + newValues.add(assign->dsqlAsgnTo); + } - dsql_ctx* const old_context = dsqlGetContext(target); - dsql_nod** ptr; + // Build the MODIFY node. + ModifyNode* modify = FB_NEW(pool) ModifyNode(pool); + thisIf->trueAction = modify; - modify->dsqlContext = old_context; + dsql_ctx* const oldContext = dsqlGetContext(target); - ++dsqlScratch->scopeLevel; // Go to the same level of source and target contexts. + modify->dsqlContext = oldContext; - for (DsqlContextStack::iterator itr(usingCtxs); itr.hasData(); ++itr) - dsqlScratch->context->push(itr.object()); // push the USING contexts - - dsqlScratch->context->push(old_context); // process old context values - - for (ptr = org_values.begin(); ptr != org_values.end(); ++ptr) - *ptr = PASS1_node_psql(dsqlScratch, *ptr, false); - - // And pop the contexts. - dsqlScratch->context->pop(); - dsqlScratch->context->pop(); - --dsqlScratch->scopeLevel; - - // Process relation. - modify->dsqlRelation = PASS1_relation(dsqlScratch, dsqlRelation); - dsql_ctx* mod_context = dsqlGetContext(modify->dsqlRelation); - - // Process new context values. - for (ptr = new_values.begin(); ptr != new_values.end(); ++ptr) - *ptr = PASS1_node_psql(dsqlScratch, *ptr, false); - - dsqlScratch->context->pop(); - - if (dsqlReturning) - { - // Repush the source contexts. ++dsqlScratch->scopeLevel; // Go to the same level of source and target contexts. for (DsqlContextStack::iterator itr(usingCtxs); itr.hasData(); ++itr) dsqlScratch->context->push(itr.object()); // push the USING contexts - dsqlScratch->context->push(old_context); // process old context values + dsqlScratch->context->push(oldContext); // process old context values - mod_context->ctx_scope_level = old_context->ctx_scope_level; + if (matched->condition) + thisIf->dsqlCondition = PASS1_node_psql(dsqlScratch, matched->condition, false); - matchedRet = modify->statement2 = ReturningProcessor( - dsqlScratch, old_context, mod_context).process(dsqlReturning, NULL); + dsql_nod** ptr; - nullRet = dsqlNullifyReturning(dsqlScratch, modify, false); + for (ptr = orgValues.begin(); ptr != orgValues.end(); ++ptr) + *ptr = PASS1_node_psql(dsqlScratch, *ptr, false); - // And pop them. + // And pop the contexts. dsqlScratch->context->pop(); dsqlScratch->context->pop(); --dsqlScratch->scopeLevel; + + // Process relation. + modify->dsqlRelation = PASS1_relation(dsqlScratch, dsqlRelation); + dsql_ctx* modContext = dsqlGetContext(modify->dsqlRelation); + + // Process new context values. + for (ptr = newValues.begin(); ptr != newValues.end(); ++ptr) + *ptr = PASS1_node_psql(dsqlScratch, *ptr, false); + + dsqlScratch->context->pop(); + + if (dsqlReturning) + { + StmtNode* updRet = ReturningProcessor::clone(dsqlScratch, dsqlReturning, processedRet); + + // Repush the source contexts. + ++dsqlScratch->scopeLevel; // Go to the same level of source and target contexts. + + for (DsqlContextStack::iterator itr(usingCtxs); itr.hasData(); ++itr) + dsqlScratch->context->push(itr.object()); // push the USING contexts + + dsqlScratch->context->push(oldContext); // process old context values + + modContext->ctx_scope_level = oldContext->ctx_scope_level; + + processedRet = modify->statement2 = ReturningProcessor( + dsqlScratch, oldContext, modContext).process(dsqlReturning, updRet); + + nullRet = dsqlNullifyReturning(dsqlScratch, modify, false); + + // And pop them. + dsqlScratch->context->pop(); + dsqlScratch->context->pop(); + --dsqlScratch->scopeLevel; + } + + // Recreate the list of assignments. + + CompoundStmtNode* assignStatements = FB_NEW(pool) CompoundStmtNode(pool); + modify->statement = assignStatements; + + assignStatements->statements.resize(stmts->statements.getCount()); + + for (size_t i = 0; i < assignStatements->statements.getCount(); ++i) + { + if (!PASS1_set_parameter_type(dsqlScratch, orgValues[i], newValues[i], false)) + PASS1_set_parameter_type(dsqlScratch, newValues[i], orgValues[i], false); + + AssignmentNode* assign = FB_NEW(pool) AssignmentNode(pool); + assign->dsqlAsgnFrom = orgValues[i]; + assign->dsqlAsgnTo = newValues[i]; + assignStatements->statements[i] = assign; + } + + // We do not allow cases like UPDATE SET f1 = v1, f2 = v2, f1 = v3... + dsqlFieldAppearsOnce(newValues, "MERGE"); } - - // Recreate the list of assignments. - - CompoundStmtNode* assignStatements = FB_NEW(pool) CompoundStmtNode(pool); - modify->statement = assignStatements; - - assignStatements->statements.resize(stmts->statements.getCount()); - - for (size_t i = 0; i < assignStatements->statements.getCount(); ++i) + else { - if (!PASS1_set_parameter_type(dsqlScratch, org_values[i], new_values[i], false)) - PASS1_set_parameter_type(dsqlScratch, new_values[i], org_values[i], false); + // Build the DELETE node. + EraseNode* erase = FB_NEW(pool) EraseNode(pool); + thisIf->trueAction = erase; - AssignmentNode* assign = FB_NEW(pool) AssignmentNode(pool); - assign->dsqlAsgnFrom = org_values[i]; - assign->dsqlAsgnTo = new_values[i]; - assignStatements->statements[i] = assign; - } + dsql_ctx* context = dsqlGetContext(target); + erase->dsqlContext = context; - // We do not allow cases like UPDATE SET f1 = v1, f2 = v2, f1 = v3... - dsqlFieldAppearsOnce(new_values, "MERGE"); - - update = modify; - } - else if (dsqlWhenMatchedPresent && !dsqlWhenMatchedAssignments) - { - // build the DELETE node - EraseNode* erase = FB_NEW(pool) EraseNode(pool); - dsql_ctx* context = dsqlGetContext(target); - erase->dsqlContext = context; - - if (dsqlReturning) - { ++dsqlScratch->scopeLevel; // Go to the same level of source and target contexts. for (DsqlContextStack::iterator itr(usingCtxs); itr.hasData(); ++itr) @@ -4959,10 +5002,18 @@ StmtNode* MergeNode::dsqlPass(DsqlCompilerScratch* dsqlScratch) dsqlScratch->context->push(context); // process old context values - matchedRet = erase->statement = ReturningProcessor( - dsqlScratch, context, NULL).process(dsqlReturning, NULL); + if (matched->condition) + thisIf->dsqlCondition = PASS1_node_psql(dsqlScratch, matched->condition, false); - nullRet = dsqlNullifyReturning(dsqlScratch, erase, false); + if (dsqlReturning) + { + StmtNode* delRet = ReturningProcessor::clone(dsqlScratch, dsqlReturning, processedRet); + + processedRet = erase->statement = ReturningProcessor( + dsqlScratch, context, NULL).process(dsqlReturning, delRet); + + nullRet = dsqlNullifyReturning(dsqlScratch, erase, false); + } // And pop the contexts. dsqlScratch->context->pop(); @@ -4970,13 +5021,27 @@ StmtNode* MergeNode::dsqlPass(DsqlCompilerScratch* dsqlScratch) --dsqlScratch->scopeLevel; } - update = erase; + if (lastIf) + lastIf->falseAction = thisIf->dsqlCondition ? thisIf : thisIf->trueAction; + else + update = thisIf->dsqlCondition ? thisIf : thisIf->trueAction; + + lastIf = thisIf; + + // If a statement executes unconditionally, no other will ever execute. + if (!thisIf->dsqlCondition) + break; } StmtNode* insert = NULL; + lastIf = NULL; - if (dsqlWhenNotMatchedPresent) + for (NotMatched* notMatched = dsqlWhenNotMatched.begin(); + notMatched != dsqlWhenNotMatched.end(); + ++notMatched) { + IfNode* thisIf = FB_NEW(pool) IfNode(pool); + ++dsqlScratch->scopeLevel; // Go to the same level of the source context. for (DsqlContextStack::iterator itr(usingCtxs); itr.hasData(); ++itr) @@ -4988,29 +5053,32 @@ StmtNode* MergeNode::dsqlPass(DsqlCompilerScratch* dsqlScratch) // Build the INSERT node. StoreNode* store = FB_NEW(pool) StoreNode(pool); store->dsqlRelation = dsqlRelation; - store->dsqlFields = dsqlWhenNotMatchedFields; - store->dsqlValues = dsqlWhenNotMatchedValues; + store->dsqlFields = notMatched->fields; + store->dsqlValues = notMatched->values; - store = store->internalDsqlPass(dsqlScratch, false)->as(); + thisIf->trueAction = store = store->internalDsqlPass(dsqlScratch, false)->as(); fb_assert(store); + if (notMatched->condition) + thisIf->dsqlCondition = PASS1_node_psql(dsqlScratch, notMatched->condition, false); + // Restore the scope level. --dsqlScratch->scopeLevel; - StmtNode* insRet = ReturningProcessor::clone(dsqlScratch, dsqlReturning, matchedRet); - if (dsqlReturning) { - dsql_ctx* const old_context = dsqlGetContext(target); - dsqlScratch->context->push(old_context); + StmtNode* insRet = ReturningProcessor::clone(dsqlScratch, dsqlReturning, processedRet); + + dsql_ctx* const oldContext = dsqlGetContext(target); + dsqlScratch->context->push(oldContext); dsql_ctx* context = dsqlGetContext(store->dsqlRelation); - context->ctx_scope_level = old_context->ctx_scope_level; + context->ctx_scope_level = oldContext->ctx_scope_level; - store->statement2 = ReturningProcessor( - dsqlScratch, old_context, context).process(dsqlReturning, insRet); + processedRet = store->statement2 = ReturningProcessor( + dsqlScratch, oldContext, context).process(dsqlReturning, insRet); - if (!matchedRet) + if (!processedRet) nullRet = dsqlNullifyReturning(dsqlScratch, store, false); dsqlScratch->context->pop(); @@ -5020,7 +5088,16 @@ StmtNode* MergeNode::dsqlPass(DsqlCompilerScratch* dsqlScratch) dsqlScratch->context->pop(); --dsqlScratch->scopeLevel; - insert = store; + if (lastIf) + lastIf->falseAction = thisIf->dsqlCondition ? thisIf : thisIf->trueAction; + else + insert = thisIf->dsqlCondition ? thisIf : thisIf->trueAction; + + lastIf = thisIf; + + // If a statement executes unconditionally, no other will ever execute. + if (!thisIf->dsqlCondition) + break; } MissingBoolNode* missingNode = FB_NEW(pool) MissingBoolNode( diff --git a/src/dsql/StmtNodes.h b/src/dsql/StmtNodes.h index ba2ac889cc..77177db08c 100644 --- a/src/dsql/StmtNodes.h +++ b/src/dsql/StmtNodes.h @@ -937,18 +937,39 @@ public: class MergeNode : public TypedNode { public: + struct Matched + { + Matched() + : assignments(NULL), + condition(NULL) + { + } + + CompoundStmtNode* assignments; + dsql_nod* condition; + }; + + struct NotMatched + { + NotMatched() + : fields(NULL), + values(NULL), + condition(NULL) + { + } + + dsql_nod* fields; + dsql_nod* values; + dsql_nod* condition; + }; + explicit MergeNode(MemoryPool& pool, dsql_nod* val = NULL) : TypedNode(pool), dsqlRelation(NULL), dsqlUsing(NULL), dsqlCondition(NULL), - dsqlWhenMatchedPresent(false), - dsqlWhenMatchedAssignments(NULL), - dsqlWhenMatchedCondition(NULL), - dsqlWhenNotMatchedPresent(false), - dsqlWhenNotMatchedFields(NULL), - dsqlWhenNotMatchedValues(NULL), - dsqlWhenNotMatchedCondition(NULL), + dsqlWhenMatched(pool), + dsqlWhenNotMatched(pool), dsqlReturning(NULL) { } @@ -961,13 +982,8 @@ public: dsql_nod* dsqlRelation; dsql_nod* dsqlUsing; dsql_nod* dsqlCondition; - bool dsqlWhenMatchedPresent; - CompoundStmtNode* dsqlWhenMatchedAssignments; - dsql_nod* dsqlWhenMatchedCondition; - bool dsqlWhenNotMatchedPresent; - dsql_nod* dsqlWhenNotMatchedFields; - dsql_nod* dsqlWhenNotMatchedValues; - dsql_nod* dsqlWhenNotMatchedCondition; + Firebird::Array dsqlWhenMatched; + Firebird::Array dsqlWhenNotMatched; ReturningClause* dsqlReturning; }; diff --git a/src/dsql/parse.y b/src/dsql/parse.y index c85028a76e..f8e51b1c60 100644 --- a/src/dsql/parse.y +++ b/src/dsql/parse.y @@ -4789,47 +4789,62 @@ merge ; merge_when_clause($mergeNode) - : merge_when_matched_clause($mergeNode) merge_when_not_matched_clause($mergeNode) - | merge_when_not_matched_clause($mergeNode) merge_when_matched_clause($mergeNode) - | merge_when_matched_clause($mergeNode) + : merge_when_matched_clause($mergeNode) | merge_when_not_matched_clause($mergeNode) + | merge_when_clause merge_when_matched_clause($mergeNode) + | merge_when_clause merge_when_not_matched_clause($mergeNode) ; merge_when_matched_clause($mergeNode) : WHEN MATCHED merge_update_specification($mergeNode) - { $mergeNode->dsqlWhenMatchedPresent = true; } ; merge_when_not_matched_clause($mergeNode) : WHEN NOT MATCHED merge_insert_specification($mergeNode) - { $mergeNode->dsqlWhenNotMatchedPresent = true; } ; merge_update_specification($mergeNode) : THEN UPDATE SET assignments - { $mergeNode->dsqlWhenMatchedAssignments = $4; } + { + MergeNode::Matched when; + when.assignments = $4; + $mergeNode->dsqlWhenMatched.add(when); + } | AND search_condition THEN UPDATE SET assignments { - $mergeNode->dsqlWhenMatchedAssignments = $6; - $mergeNode->dsqlWhenMatchedCondition = $2; + MergeNode::Matched when; + when.assignments = $6; + when.condition = $2; + $mergeNode->dsqlWhenMatched.add(when); } | THEN KW_DELETE - // Nothing to do here. + { + MergeNode::Matched when; + $mergeNode->dsqlWhenMatched.add(when); + } | AND search_condition THEN KW_DELETE - { $mergeNode->dsqlWhenMatchedCondition = $2; } + { + MergeNode::Matched when; + when.condition = $2; + $mergeNode->dsqlWhenMatched.add(when); + } ; merge_insert_specification($mergeNode) : THEN INSERT ins_column_parens_opt VALUES '(' value_list ')' { - $mergeNode->dsqlWhenNotMatchedFields = make_list($3); - $mergeNode->dsqlWhenNotMatchedValues = make_list($6); + MergeNode::NotMatched when; + when.fields = make_list($3); + when.values = make_list($6); + $mergeNode->dsqlWhenNotMatched.add(when); } | AND search_condition THEN INSERT ins_column_parens_opt VALUES '(' value_list ')' { - $mergeNode->dsqlWhenNotMatchedFields = make_list($5); - $mergeNode->dsqlWhenNotMatchedValues = make_list($8); - $mergeNode->dsqlWhenNotMatchedCondition = $2; + MergeNode::NotMatched when; + when.fields = make_list($5); + when.values = make_list($8); + when.condition = $2; + $mergeNode->dsqlWhenNotMatched.add(when); } ;