mirror of
https://github.com/FirebirdSQL/firebird.git
synced 2025-01-22 20:43:02 +01:00
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.
This commit is contained in:
parent
8abb27e974
commit
372d48c97b
@ -15,21 +15,24 @@ MERGE statement
|
||||
INTO <table or view> [ [AS] <correlation name> ]
|
||||
USING <table or view or derived table> [ [AS] <correlation name> ]
|
||||
ON <condition>
|
||||
[ <merge when matched> ]
|
||||
[ <merge when not matched> ]
|
||||
<merge when>...
|
||||
<returning clause>
|
||||
|
||||
<merge when> ::=
|
||||
<merge when matched> |
|
||||
<merge when not matched>
|
||||
|
||||
<merge when matched> ::=
|
||||
WHEN MATCHED THEN
|
||||
WHEN MATCHED [ AND <condition> ] THEN
|
||||
UPDATE SET <assignment list>
|
||||
|
||||
<merge when not matched> ::=
|
||||
WHEN NOT MATCHED THEN
|
||||
WHEN NOT MATCHED [ AND <condition> ] THEN
|
||||
INSERT [ <left paren> <column list> <right paren> ]
|
||||
VALUES <left paren> <value list> <right paren>
|
||||
|
||||
Syntax rules:
|
||||
1. At least one of <merge when matched> and <merge when not matched> should be specified
|
||||
and each one should not be specified more than one time.
|
||||
1. At least one of <merge when matched> and <merge when not matched> 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.
|
||||
|
@ -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).
|
||||
|
@ -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<string> autoAlias;
|
||||
AutoSetRestore<string> autoInternalAlias;
|
||||
dsql_ctx* oldContext;
|
||||
string oldAlias, oldInternalAlias;
|
||||
AutoSetRestore<USHORT> 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<dsql_nod*>(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<RelationSourceNode>(target)->alias.nullStr();
|
||||
if (!targetName)
|
||||
targetName = ExprNode::as<RelationSourceNode>(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<dsql_nod*>(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<dsql_nod*>(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<dsql_nod*> 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<AssignmentNode>();
|
||||
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<dsql_nod*> 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<AssignmentNode>();
|
||||
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<StoreNode>();
|
||||
thisIf->trueAction = store = store->internalDsqlPass(dsqlScratch, false)->as<StoreNode>();
|
||||
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(
|
||||
|
@ -937,18 +937,39 @@ public:
|
||||
class MergeNode : public TypedNode<DsqlOnlyStmtNode, StmtNode::TYPE_MERGE>
|
||||
{
|
||||
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<DsqlOnlyStmtNode, StmtNode::TYPE_MERGE>(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<Matched> dsqlWhenMatched;
|
||||
Firebird::Array<NotMatched> dsqlWhenNotMatched;
|
||||
ReturningClause* dsqlReturning;
|
||||
};
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
;
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user