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

Feature #6681 and fix for #6942.

- #6681 - Support for WHEN NOT MATCHED BY SOURCE for MERGE statement [CORE6448].
- #6942 - Incorrect singleton error with MERGE and RETURNING.
This commit is contained in:
Adriano dos Santos Fernandes 2021-09-02 10:00:15 -03:00
parent aaedaa7afd
commit 854b809c77
10 changed files with 489 additions and 192 deletions

View File

@ -3,7 +3,7 @@ MERGE statement
-----------------
Function:
Read data from the source and INSERT or UPDATE in the target table depending on a
Read data from the source and INSERT, UPDATE or DELETE in the target table depending on a
condition.
Author:
@ -18,23 +18,28 @@ MERGE statement
<merge when>...
[<plan clause>]
[<order by clause>]
<returning clause>
[<returning clause>]
<merge when> ::=
<merge when matched> |
<merge when not matched>
<merge when not matched by target> |
<merge when not matched by source>
<merge when matched> ::=
WHEN MATCHED [ AND <condition> ] THEN
{ UPDATE SET <assignment list> | DELETE }
<merge when not matched> ::=
WHEN NOT MATCHED [ AND <condition> ] THEN
<merge when not matched by target> ::=
WHEN NOT MATCHED [ BY TARGET ] [ AND <condition> ] THEN
INSERT [ <left paren> <column list> <right paren> ]
VALUES <left paren> <value list> <right paren>
<merge when not matched by source> ::=
WHEN NOT MATCHED BY SOURCE [ AND <condition> ] THEN
{ UPDATE SET <assignment list> | DELETE }
Syntax rules:
1. At least one of <merge when matched> or <merge when not matched> should be specified.
1. At least one <merge when> clause should be specified.
Scope:
DSQL, PSQL
@ -52,14 +57,40 @@ MERGE statement
INSERT (id, name)
VALUES (cd.id, cd.name)
Notes:
A right join is made between INTO and USING tables using the condition.
UPDATE is called when a record exist in the left table (INTO), otherwise
INSERT is called.
2.
MERGE
INTO customers c
USING new_customers nc
ON (c.id = nc.id)
WHEN MATCHED THEN
UPDATE SET
name = cd.name
WHEN NOT MATCHED BY SOURCE THEN
DELETE
As soon it's decided if the source matched or not a record in the target, the set of the
Notes:
A join is made between USING and INTO tables.
The join type depends on the presence of
<merge when not matched by source> and <merge when not matched by target>:
- <merge when not matched by target> + <merge when not matched by source>: FULL JOIN
- <merge when not matched by source>: RIGHT JOIN
- <merge when not matched by target>: LEFT JOIN
- only <merge when matched>: INNER JOIN
As soon it's decided if the source and target has a matching, the set of the
corresponding (WHEN MATCHED / WHEN NOT MATCHED) statements is evaluated in the order specified,
to check their optional conditions. The first statement which has 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.
If no record is returned in the join, no action will be called.
<merge when matched> is called when a match between source and target exists.
UPDATE or DELETE will change the target table.
<merge when not matched by target> is called when a source record matches no record in target.
INSERT will change the target table.
<merge when not matched by source> is called when a target record matches no record in source.
UPDATE or DELETE will change the target table.
That clause is allowed only since v5.

View File

@ -140,6 +140,12 @@ namespace Firebird {
: FB_NEW_POOL(getPool()) Entry(e, 0);
}
void push(const Stack& s)
{
for (const_iterator itr(s); itr.hasData(); ++itr)
push(itr.object());
}
Object pop()
{
fb_assert(stk);

View File

@ -486,6 +486,7 @@ static const TOK tokens[] =
{TOK_TAGS, "TAGS", true},
{TOK_TAN, "TAN", true},
{TOK_TANH, "TANH", true},
{TOK_TARGET, "TARGET", true},
{TOK_TEMPORARY, "TEMPORARY", true},
{TOK_THEN, "THEN", false},
{TOK_TIES, "TIES", true},

View File

@ -8499,14 +8499,32 @@ bool DsqlMapNode::dsqlMatch(DsqlCompilerScratch* dsqlScratch, const ExprNode* ot
//--------------------
DerivedFieldNode::DerivedFieldNode(MemoryPool& pool, const MetaName& aName, USHORT aScope,
ValueExprNode* aValue)
: TypedNode<ValueExprNode, ExprNode::TYPE_DERIVED_FIELD>(pool),
name(aName),
value(aValue),
context(NULL),
scope(aScope)
void DerivedFieldNode::getContextNumbers(Firebird::SortedArray<USHORT>& contextNumbers, const DsqlContextStack& contextStack)
{
for (DsqlContextStack::const_iterator stack(contextStack); stack.hasData(); ++stack)
{
const auto* const context = stack.object();
if (context->ctx_win_maps.hasData())
{
for (const auto& winMap : context->ctx_win_maps)
{
// bottleneck
fb_assert(winMap->context <= MAX_UCHAR);
if (!contextNumbers.exist(winMap->context))
contextNumbers.add(winMap->context);
}
}
else
{
// bottleneck
fb_assert(context->ctx_context <= MAX_UCHAR);
if (!contextNumbers.exist(context->ctx_context))
contextNumbers.add(context->ctx_context);
}
}
}
string DerivedFieldNode::internalPrint(NodePrinter& printer) const
@ -8653,29 +8671,8 @@ void DerivedFieldNode::genBlr(DsqlCompilerScratch* dsqlScratch)
{
if (context->ctx_main_derived_contexts.hasData())
{
HalfStaticArray<USHORT, 4> derivedContexts;
for (DsqlContextStack::const_iterator stack(context->ctx_main_derived_contexts);
stack.hasData(); ++stack)
{
const dsql_ctx* const derivedContext = stack.object();
if (derivedContext->ctx_win_maps.hasData())
{
for (auto& winMap : derivedContext->ctx_win_maps)
{
// bottleneck
fb_assert(winMap->context <= MAX_UCHAR);
derivedContexts.add(winMap->context);
}
}
else
{
// bottleneck
fb_assert(derivedContext->ctx_context <= MAX_UCHAR);
derivedContexts.add(derivedContext->ctx_context);
}
}
SortedArray<USHORT> derivedContexts;
getContextNumbers(derivedContexts, context->ctx_main_derived_contexts);
const FB_SIZE_T derivedContextsCount = derivedContexts.getCount();

View File

@ -1058,7 +1058,25 @@ class DerivedFieldNode : public TypedNode<ValueExprNode, ExprNode::TYPE_DERIVED_
{
public:
DerivedFieldNode(MemoryPool& pool, const MetaName& aName, USHORT aScope,
ValueExprNode* aValue);
ValueExprNode* aValue)
: TypedNode<ValueExprNode, ExprNode::TYPE_DERIVED_FIELD>(pool),
name(aName),
value(aValue),
context(NULL),
scope(aScope)
{
}
// Construct already processed node.
DerivedFieldNode(MemoryPool& pool, dsql_ctx* aContext, ValueExprNode* aValue)
: TypedNode<ValueExprNode, ExprNode::TYPE_DERIVED_FIELD>(pool),
value(aValue),
context(aContext),
scope(0)
{
}
static void getContextNumbers(Firebird::SortedArray<USHORT>& contextNumbers, const DsqlContextStack& contextStack);
virtual void getChildren(NodeRefsHolder& holder, bool dsql) const
{

View File

@ -78,7 +78,7 @@ static void dsqlGenReturning(DsqlCompilerScratch* dsqlScratch, ReturningClause*
Nullable<USHORT> localTableNumber);
static void dsqlGenReturningLocalTableCursor(DsqlCompilerScratch* dsqlScratch, ReturningClause* returning,
USHORT localTableNumber);
static void dsqlGenReturningLocalTableDecl(DsqlCompilerScratch* dsqlScratch, ReturningClause* returning, USHORT tableNumber);
static void dsqlGenReturningLocalTableDecl(DsqlCompilerScratch* dsqlScratch, USHORT tableNumber);
static dsql_ctx* dsqlGetContext(const RecordSourceNode* node);
static void dsqlGetContexts(DsqlContextStack& contexts, const RecordSourceNode* node);
static StmtNode* dsqlNullifyReturning(DsqlCompilerScratch*, StmtNode* input);
@ -2365,7 +2365,7 @@ void EraseNode::genBlr(DsqlCompilerScratch* dsqlScratch)
dsqlScratch->appendUChar(blr_begin);
tableNumber = dsqlScratch->localTableNumber++;
dsqlGenReturningLocalTableDecl(dsqlScratch, dsqlReturning, tableNumber.value);
dsqlGenReturningLocalTableDecl(dsqlScratch, tableNumber.value);
}
else
{
@ -5508,8 +5508,10 @@ StmtNode* MergeNode::dsqlPass(DsqlCompilerScratch* dsqlScratch)
join->dsqlFrom->items[0] = source;
// Left join if WHEN NOT MATCHED is present.
if (whenNotMatched.hasData())
// Choose join type.
if (whenNotMatchedBySource.hasData())
join->rse_jointype = whenNotMatchedByTarget.hasData() ? blr_full : blr_right;
else if (whenNotMatchedByTarget.hasData())
join->rse_jointype = blr_left;
join->dsqlFrom->items[1] = target;
@ -5520,64 +5522,13 @@ StmtNode* MergeNode::dsqlPass(DsqlCompilerScratch* dsqlScratch)
querySpec->dsqlFrom->items[0] = join;
querySpec->rse_plan = plan;
BoolExprNode* matchedConditions = nullptr;
BoolExprNode* notMatchedConditions = nullptr;
for (auto& matched : whenMatched)
{
if (matched.condition)
matchedConditions = PASS1_compose(matchedConditions, matched.condition, blr_or);
else
{
matchedConditions = nullptr;
break;
}
}
for (auto& notMatched : whenNotMatched)
{
if (notMatched.condition)
notMatchedConditions = PASS1_compose(notMatchedConditions, notMatched.condition, blr_or);
else
{
notMatchedConditions = nullptr;
break;
}
}
if (matchedConditions || notMatchedConditions)
{
const char* targetName = target->alias.nullStr();
if (!targetName)
targetName = target->dsqlName.c_str();
if (whenMatched.hasData()) // WHEN MATCHED
{
auto missingNode = FB_NEW_POOL(pool) MissingBoolNode(
pool, FB_NEW_POOL(pool) RecordKeyNode(pool, blr_dbkey, targetName));
querySpec->dsqlWhere = FB_NEW_POOL(pool) NotBoolNode(pool, missingNode);
if (matchedConditions)
querySpec->dsqlWhere = PASS1_compose(querySpec->dsqlWhere, matchedConditions, blr_and);
}
if (whenNotMatched.hasData()) // WHEN NOT MATCHED
{
BoolExprNode* temp = FB_NEW_POOL(pool) MissingBoolNode(pool,
FB_NEW_POOL(pool) RecordKeyNode(pool, blr_dbkey, targetName));
if (notMatchedConditions)
temp = PASS1_compose(temp, notMatchedConditions, blr_and);
querySpec->dsqlWhere = PASS1_compose(querySpec->dsqlWhere, temp, blr_or);
}
}
const auto selectExpr = FB_NEW_POOL(pool) SelectExprNode(pool);
selectExpr->querySpec = querySpec;
selectExpr->orderClause = order;
if (returning && dsqlScratch->isPsql())
selectExpr->dsqlFlags |= RecordSourceNode::DFLAG_SINGLETON;
const auto dsqlSelect = FB_NEW_POOL(pool) SelectNode(pool);
dsqlSelect->dsqlExpr = selectExpr;
@ -5589,17 +5540,57 @@ StmtNode* MergeNode::dsqlPass(DsqlCompilerScratch* dsqlScratch)
source = processedRse->dsqlStreams->items[0];
target = nodeAs<RelationSourceNode>(processedRse->dsqlStreams->items[1]);
mergeNode->targetContext = dsqlGetContext(target);
mergeNode->oldContext = dsqlGetContext(target);
DsqlContextStack usingCtxs;
PASS1_expand_contexts(usingCtxs, source->dsqlContext);
DerivedFieldNode::getContextNumbers(mergeNode->usingContexts, usingCtxs);
usingCtxs.clear();
dsqlGetContexts(usingCtxs, source);
bool useMatchedConditions = whenMatched.hasData();
bool useNotMatchedByTargetConditions = whenNotMatchedByTarget.hasData();
bool useNotMatchedBySourceConditions = whenNotMatchedBySource.hasData();
for (auto& matched : whenMatched)
{
if (!matched.condition)
{
useMatchedConditions = false;
break;
}
}
for (auto& notMatched : whenNotMatchedByTarget)
{
if (!notMatched.condition)
{
useNotMatchedByTargetConditions = false;
break;
}
}
for (auto& notMatched : whenNotMatchedBySource)
{
if (!notMatched.condition)
{
useNotMatchedBySourceConditions = false;
break;
}
}
BoolExprNode* matchedConditions = nullptr;
BoolExprNode* notMatchedByTargetConditions = nullptr;
BoolExprNode* notMatchedBySourceConditions = nullptr;
for (auto& matched : whenMatched)
{
auto& processedMatched = mergeNode->whenMatched.add();
processedMatched.assignments = matched.assignments;
if (matched.assignments)
if (matched.assignments) // SET
{
// Get the assignments of the UPDATE dsqlScratch.
// Separate the new and org values to process in correct contexts.
@ -5611,24 +5602,25 @@ StmtNode* MergeNode::dsqlPass(DsqlCompilerScratch* dsqlScratch)
processedMatched.processedFields.add(assign->asgnTo);
}
const auto oldContext = dsqlGetContext(target);
{ // scope
// Go to the same level of source and target contexts.
AutoSetRestore<USHORT> autoScopeLevel(&dsqlScratch->scopeLevel, dsqlScratch->scopeLevel + 1);
DsqlContextStack::AutoRestore autoContext(*dsqlScratch->context);
++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
dsqlScratch->context->push(usingCtxs); // push the USING contexts
dsqlScratch->context->push(mergeNode->oldContext); // push the OLD context
processedMatched.condition = doDsqlPass(dsqlScratch, matched.condition, false);
if (useMatchedConditions)
{
matchedConditions = PASS1_compose(matchedConditions,
doDsqlPass(dsqlScratch, matched.condition, false), blr_or);
}
for (auto& ptr : processedMatched.processedValues)
ptr = doDsqlPass(dsqlScratch, ptr, false);
// And pop the contexts.
dsqlScratch->context->pop();
dsqlScratch->context->pop();
--dsqlScratch->scopeLevel;
}
// Process relation.
processedMatched.modifyRelation = PASS1_relation(dsqlScratch, relation);
@ -5642,23 +5634,17 @@ StmtNode* MergeNode::dsqlPass(DsqlCompilerScratch* dsqlScratch)
if (returning)
{
// Repush the source contexts.
++dsqlScratch->scopeLevel; // Go to the same level of source and target contexts.
// Go to the same level of source and target contexts.
AutoSetRestore<USHORT> autoScopeLevel(&dsqlScratch->scopeLevel, dsqlScratch->scopeLevel + 1);
DsqlContextStack::AutoRestore autoContext(*dsqlScratch->context);
for (DsqlContextStack::iterator itr(usingCtxs); itr.hasData(); ++itr)
dsqlScratch->context->push(itr.object()); // push the USING contexts
dsqlScratch->context->push(usingCtxs); // push the USING contexts
dsqlScratch->context->push(mergeNode->oldContext); // push the OLD context
dsqlScratch->context->push(oldContext); // process old context values
modContext->ctx_scope_level = oldContext->ctx_scope_level;
modContext->ctx_scope_level = mergeNode->oldContext->ctx_scope_level;
mergeNode->returning = processedMatched.processedReturning = dsqlProcessReturning(dsqlScratch,
oldContext, modContext, returning, dsqlScratch->isPsql());
// And pop them.
dsqlScratch->context->pop();
dsqlScratch->context->pop();
--dsqlScratch->scopeLevel;
mergeNode->oldContext, modContext, returning, dsqlScratch->isPsql());
}
auto valueIt = processedMatched.processedValues.begin();
@ -5674,43 +5660,51 @@ StmtNode* MergeNode::dsqlPass(DsqlCompilerScratch* dsqlScratch)
// We do not allow cases like UPDATE SET f1 = v1, f2 = v2, f1 = v3...
dsqlFieldAppearsOnce(processedMatched.processedFields, "MERGE");
}
else
else // DELETE
{
++dsqlScratch->scopeLevel; // Go to the same level of source and target contexts.
// Go to the same level of source and target contexts.
AutoSetRestore<USHORT> autoScopeLevel(&dsqlScratch->scopeLevel, dsqlScratch->scopeLevel + 1);
DsqlContextStack::AutoRestore autoContext(*dsqlScratch->context);
for (DsqlContextStack::iterator itr(usingCtxs); itr.hasData(); ++itr)
dsqlScratch->context->push(itr.object()); // push the USING contexts
dsqlScratch->context->push(mergeNode->targetContext); // process old context values
dsqlScratch->context->push(usingCtxs); // push the USING contexts
dsqlScratch->context->push(mergeNode->oldContext); // push the OLD context
processedMatched.condition = doDsqlPass(dsqlScratch, matched.condition, false);
mergeNode->returning = processedMatched.processedReturning = dsqlProcessReturning(dsqlScratch,
mergeNode->targetContext, nullptr, returning, dsqlScratch->isPsql());
// And pop the contexts.
dsqlScratch->context->pop();
dsqlScratch->context->pop();
--dsqlScratch->scopeLevel;
}
}
for (auto& notMatched : whenNotMatched)
if (useMatchedConditions)
{
++dsqlScratch->scopeLevel; // Go to the same level of the source context.
matchedConditions = PASS1_compose(matchedConditions,
doDsqlPass(dsqlScratch, matched.condition, false), blr_or);
}
for (DsqlContextStack::iterator itr(usingCtxs); itr.hasData(); ++itr)
dsqlScratch->context->push(itr.object()); // push the USING contexts
mergeNode->returning = processedMatched.processedReturning = dsqlProcessReturning(dsqlScratch,
mergeNode->oldContext, nullptr, returning, dsqlScratch->isPsql());
}
}
for (auto& notMatched : whenNotMatchedByTarget)
{
// Go to the same level of the source context.
AutoSetRestore<USHORT> autoScopeLevel(&dsqlScratch->scopeLevel, dsqlScratch->scopeLevel + 1);
DsqlContextStack::AutoRestore autoContext(*dsqlScratch->context);
dsqlScratch->context->push(usingCtxs); // push the USING contexts
// The INSERT relation should be processed in a higher level than the source context.
++dsqlScratch->scopeLevel;
auto& processedNotMatched = mergeNode->whenNotMatched.add();
auto& processedNotMatched = mergeNode->whenNotMatchedByTarget.add();
processedNotMatched.overrideClause = notMatched.overrideClause;
processedNotMatched.condition = doDsqlPass(dsqlScratch, notMatched.condition, false);
if (useNotMatchedByTargetConditions)
{
notMatchedByTargetConditions = PASS1_compose(notMatchedByTargetConditions,
doDsqlPass(dsqlScratch, notMatched.condition, false), blr_or);
}
{ // scope
DsqlContextStack::AutoRestore autoContext(*dsqlScratch->context);
DsqlContextStack::AutoRestore autoContextForValues(*dsqlScratch->context);
const auto values = doDsqlPass(dsqlScratch, notMatched.values, false);
@ -5808,23 +5802,171 @@ StmtNode* MergeNode::dsqlPass(DsqlCompilerScratch* dsqlScratch)
if (returning)
{
const auto oldContext = dsqlGetContext(target);
dsqlScratch->context->push(oldContext);
dsqlScratch->context->push(mergeNode->oldContext);
const auto storeContext = dsqlGetContext(processedNotMatched.storeRelation);
storeContext->ctx_scope_level = oldContext->ctx_scope_level;
storeContext->ctx_scope_level = mergeNode->oldContext->ctx_scope_level;
mergeNode->returning = processedNotMatched.processedReturning = dsqlProcessReturning(dsqlScratch,
oldContext, storeContext, returning, dsqlScratch->isPsql());
mergeNode->oldContext, storeContext, returning, dsqlScratch->isPsql());
dsqlScratch->context->pop();
}
}
// Pop the USING context.
for (auto& notMatched : whenNotMatchedBySource)
{
auto& processedNotMatched = mergeNode->whenNotMatchedBySource.add();
processedNotMatched.assignments = notMatched.assignments;
if (notMatched.assignments) // SET
{
// Get the assignments of the UPDATE dsqlScratch.
// Separate the new and org values to process in correct contexts.
for (auto& stmt : notMatched.assignments->statements)
{
const auto assign = nodeAs<AssignmentNode>(stmt);
fb_assert(assign);
processedNotMatched.processedValues.add(assign->asgnFrom);
processedNotMatched.processedFields.add(assign->asgnTo);
}
++dsqlScratch->scopeLevel; // Go to the same level of source and target contexts.
dsqlScratch->context->push(mergeNode->oldContext); // push the OLD context
processedNotMatched.condition = doDsqlPass(dsqlScratch, notMatched.condition, false);
if (useNotMatchedBySourceConditions)
{
notMatchedBySourceConditions = PASS1_compose(notMatchedBySourceConditions,
doDsqlPass(dsqlScratch, notMatched.condition, false), blr_or);
}
for (auto& ptr : processedNotMatched.processedValues)
ptr = doDsqlPass(dsqlScratch, ptr, false);
// And pop the context.
dsqlScratch->context->pop();
--dsqlScratch->scopeLevel;
// Process relation.
processedNotMatched.modifyRelation = PASS1_relation(dsqlScratch, relation);
auto modContext = dsqlGetContext(processedNotMatched.modifyRelation);
// Process new context values.
for (auto& ptr : processedNotMatched.processedFields)
ptr = doDsqlPass(dsqlScratch, ptr, false);
dsqlScratch->context->pop();
if (returning)
{
// Go to the same level of source and target contexts.
AutoSetRestore<USHORT> autoScopeLevel(&dsqlScratch->scopeLevel, dsqlScratch->scopeLevel + 1);
DsqlContextStack::AutoRestore autoContext(*dsqlScratch->context);
dsqlScratch->context->push(usingCtxs); // push the USING contexts
dsqlScratch->context->push(mergeNode->oldContext); // push the OLD context
modContext->ctx_scope_level = mergeNode->oldContext->ctx_scope_level;
mergeNode->returning = processedNotMatched.processedReturning = dsqlProcessReturning(dsqlScratch,
mergeNode->oldContext, modContext, returning, dsqlScratch->isPsql());
}
auto valueIt = processedNotMatched.processedValues.begin();
for (auto& field : processedNotMatched.processedFields)
{
if (!PASS1_set_parameter_type(dsqlScratch, *valueIt, field, false))
PASS1_set_parameter_type(dsqlScratch, field, *valueIt, false);
++valueIt;
}
// We do not allow cases like UPDATE SET f1 = v1, f2 = v2, f1 = v3...
dsqlFieldAppearsOnce(processedNotMatched.processedFields, "MERGE");
}
else // DELETE
{
// Go to the same level of source and target contexts.
AutoSetRestore<USHORT> autoScopeLevel(&dsqlScratch->scopeLevel, dsqlScratch->scopeLevel + 1);
DsqlContextStack::AutoRestore autoContext(*dsqlScratch->context);
dsqlScratch->context->push(mergeNode->oldContext); // push the OLD context
processedNotMatched.condition = doDsqlPass(dsqlScratch, notMatched.condition, false);
if (useNotMatchedBySourceConditions)
{
notMatchedBySourceConditions = PASS1_compose(notMatchedBySourceConditions,
doDsqlPass(dsqlScratch, notMatched.condition, false), blr_or);
}
dsqlScratch->context->push(usingCtxs); // push the USING contexts
mergeNode->returning = processedNotMatched.processedReturning = dsqlProcessReturning(dsqlScratch,
mergeNode->oldContext, nullptr, returning, dsqlScratch->isPsql());
}
}
const auto targetDbKey = [dsqlScratch, mergeNode]() {
auto relNode = FB_NEW_POOL(dsqlScratch->getPool()) RelationSourceNode(dsqlScratch->getPool());
relNode->dsqlContext = mergeNode->oldContext;
auto recKeyNode = FB_NEW_POOL(dsqlScratch->getPool()) RecordKeyNode(dsqlScratch->getPool(), blr_dbkey);
recKeyNode->dsqlRelation = relNode;
return recKeyNode;
};
const auto sourceDbKey = [dsqlScratch, source]() {
auto relNode = FB_NEW_POOL(dsqlScratch->getPool()) RelationSourceNode(dsqlScratch->getPool());
relNode->dsqlContext = source->dsqlContext;
return FB_NEW_POOL(dsqlScratch->getPool()) DerivedFieldNode(dsqlScratch->getPool(), source->dsqlContext,
MAKE_constant("1", CONSTANT_BOOLEAN));
};
if (join->rse_jointype != blr_inner)
{
if (whenMatched.hasData())
{
matchedConditions = PASS1_compose(
PASS1_compose(
FB_NEW_POOL(pool) NotBoolNode(pool, FB_NEW_POOL(pool) MissingBoolNode(pool, targetDbKey())),
join->rse_jointype == blr_left ?
nullptr :
FB_NEW_POOL(pool) NotBoolNode(pool, FB_NEW_POOL(pool) MissingBoolNode(pool, sourceDbKey())),
blr_and
),
matchedConditions,
blr_and);
}
if (whenNotMatchedByTarget.hasData())
{
notMatchedByTargetConditions = PASS1_compose(
FB_NEW_POOL(pool) MissingBoolNode(pool, targetDbKey()),
notMatchedByTargetConditions,
blr_and);
}
if (whenNotMatchedBySource.hasData())
{
notMatchedBySourceConditions = PASS1_compose(
FB_NEW_POOL(pool) MissingBoolNode(pool, sourceDbKey()),
notMatchedBySourceConditions,
blr_and);
}
}
fb_assert(!mergeNode->rse->dsqlWhere);
mergeNode->rse->dsqlWhere = PASS1_compose(mergeNode->rse->dsqlWhere, matchedConditions, blr_or);
mergeNode->rse->dsqlWhere = PASS1_compose(mergeNode->rse->dsqlWhere, notMatchedByTargetConditions, blr_or);
mergeNode->rse->dsqlWhere = PASS1_compose(mergeNode->rse->dsqlWhere, notMatchedBySourceConditions, blr_or);
if (!dsqlScratch->isPsql())
{
// Describe it as TYPE_RETURNING_CURSOR if RETURNING is present or as INSERT otherwise.
@ -5845,7 +5987,8 @@ string MergeNode::internalPrint(NodePrinter& printer) const
NODE_PRINT(printer, usingClause);
NODE_PRINT(printer, condition);
//// FIXME-PRINT: NODE_PRINT(printer, whenMatched);
//// FIXME-PRINT: NODE_PRINT(printer, whenNotMatched);
//// FIXME-PRINT: NODE_PRINT(printer, whenNotMatchedByTarget);
//// FIXME-PRINT: NODE_PRINT(printer, whenNotMatchedBySource);
NODE_PRINT(printer, returning);
NODE_PRINT(printer, rse);
@ -5861,7 +6004,7 @@ void MergeNode::genBlr(DsqlCompilerScratch* dsqlScratch)
dsqlScratch->appendUChar(blr_begin);
tableNumber = dsqlScratch->localTableNumber++;
dsqlGenReturningLocalTableDecl(dsqlScratch, returning, tableNumber.value);
dsqlGenReturningLocalTableDecl(dsqlScratch, tableNumber.value);
}
// Put src info for blr_for.
@ -5871,34 +6014,117 @@ void MergeNode::genBlr(DsqlCompilerScratch* dsqlScratch)
// Generate FOR loop
dsqlScratch->appendUChar(blr_for);
dsqlScratch->putBlrMarkers(MARK_FOR_UPDATE | MARK_MERGE);
if (returning && dsqlScratch->isPsql())
dsqlScratch->appendUChar(blr_singular);
GEN_rse(dsqlScratch, rse);
// Build body of FOR loop
if (whenNotMatchedBySource.hasData())
{
dsqlScratch->appendUChar(blr_if);
dsqlScratch->appendUChar(blr_missing);
dsqlScratch->appendUChar(blr_derived_expr);
dsqlScratch->appendUChar(usingContexts.getCount());
for (auto usingContext : usingContexts)
GEN_stuff_context_number(dsqlScratch, usingContext);
dsqlScratch->appendUChar(blr_literal);
dsqlScratch->appendUChar(blr_bool);
dsqlScratch->appendUChar(1);
for (auto nextNotMatched = whenNotMatchedBySource.begin(), notMatched = nextNotMatched++;
notMatched != whenNotMatchedBySource.end();
notMatched = nextNotMatched++)
{
const bool isLast = nextNotMatched == whenNotMatchedBySource.end();
if (notMatched->condition || !isLast)
{
dsqlScratch->appendUChar(blr_if);
if (whenNotMatched.isEmpty())
if (notMatched->condition)
notMatched->condition->genBlr(dsqlScratch);
else
{
dsqlScratch->appendUChar(blr_eql);
dsqlScratch->appendUChar(blr_literal);
dsqlScratch->appendUChar(blr_bool);
dsqlScratch->appendUChar(1);
dsqlScratch->appendUChar(blr_literal);
dsqlScratch->appendUChar(blr_bool);
dsqlScratch->appendUChar(1);
}
}
if (notMatched->assignments) // UPDATE
{
dsqlScratch->appendUChar(returning ? blr_modify2 : blr_modify);
GEN_stuff_context(dsqlScratch, oldContext);
GEN_stuff_context(dsqlScratch, notMatched->modifyRelation->dsqlContext);
dsqlScratch->putBlrMarkers(MARK_MERGE);
dsqlScratch->appendUChar(blr_begin);
auto valueIt = notMatched->processedValues.begin();
for (auto& field : notMatched->processedFields)
{
dsqlScratch->appendUChar(blr_assignment);
(*valueIt++)->genBlr(dsqlScratch);
field->genBlr(dsqlScratch);
}
dsqlScratch->appendUChar(blr_end);
if (returning)
dsqlGenReturning(dsqlScratch, notMatched->processedReturning, tableNumber);
}
else // DELETE
{
if (returning)
{
dsqlScratch->appendUChar(blr_begin);
dsqlGenReturning(dsqlScratch, notMatched->processedReturning, tableNumber);
}
dsqlScratch->appendUChar(blr_erase);
GEN_stuff_context(dsqlScratch, oldContext);
dsqlScratch->putBlrMarkers(MARK_MERGE);
if (returning)
dsqlScratch->appendUChar(blr_end);
}
if (notMatched->condition && isLast)
dsqlScratch->appendUChar(blr_end);
}
if (whenNotMatchedByTarget.isEmpty() && whenMatched.isEmpty())
dsqlScratch->appendUChar(blr_end);
}
if (whenNotMatchedByTarget.hasData() || whenMatched.hasData())
{
dsqlScratch->appendUChar(blr_if);
if (whenNotMatchedByTarget.isEmpty())
dsqlScratch->appendUChar(blr_not);
dsqlScratch->appendUChar(blr_missing);
dsqlScratch->appendUChar(blr_dbkey);
GEN_stuff_context(dsqlScratch, targetContext);
GEN_stuff_context(dsqlScratch, oldContext);
}
//// TODO: It should be possible, under certain circunstances, to use single blr_store for inserts
//// and single blr_modify for updates.
//// However, if there are inserts with different override clauses or deletes, this is not possible.
for (auto nextNotMatched = whenNotMatched.begin(), notMatched = nextNotMatched++;
notMatched != whenNotMatched.end();
for (auto nextNotMatched = whenNotMatchedByTarget.begin(), notMatched = nextNotMatched++;
notMatched != whenNotMatchedByTarget.end();
notMatched = nextNotMatched++)
{
const bool isLast = nextNotMatched == whenNotMatched.end();
const bool isLast = nextNotMatched == whenNotMatchedByTarget.end();
if (notMatched->condition || !isLast)
{
@ -5973,7 +6199,7 @@ void MergeNode::genBlr(DsqlCompilerScratch* dsqlScratch)
if (matched->assignments) // UPDATE
{
dsqlScratch->appendUChar(returning ? blr_modify2 : blr_modify);
GEN_stuff_context(dsqlScratch, targetContext);
GEN_stuff_context(dsqlScratch, oldContext);
GEN_stuff_context(dsqlScratch, matched->modifyRelation->dsqlContext);
dsqlScratch->putBlrMarkers(MARK_MERGE);
@ -6002,7 +6228,7 @@ void MergeNode::genBlr(DsqlCompilerScratch* dsqlScratch)
}
dsqlScratch->appendUChar(blr_erase);
GEN_stuff_context(dsqlScratch, targetContext);
GEN_stuff_context(dsqlScratch, oldContext);
dsqlScratch->putBlrMarkers(MARK_MERGE);
if (returning)
@ -6013,8 +6239,11 @@ void MergeNode::genBlr(DsqlCompilerScratch* dsqlScratch)
dsqlScratch->appendUChar(blr_end);
}
if (whenNotMatched.hasData() != whenMatched.hasData())
if ((whenNotMatchedByTarget.hasData() || whenMatched.hasData()) &&
whenNotMatchedByTarget.hasData() != whenMatched.hasData())
{
dsqlScratch->appendUChar(blr_end);
}
if (returning && !dsqlScratch->isPsql())
{
@ -6425,7 +6654,7 @@ void ModifyNode::genBlr(DsqlCompilerScratch* dsqlScratch)
if (dsqlReturning && !dsqlScratch->isPsql())
{
if (dsqlCursorName.isEmpty())
dsqlGenReturningLocalTableDecl(dsqlScratch, dsqlReturning, dsqlReturningLocalTableNumber.value);
dsqlGenReturningLocalTableDecl(dsqlScratch, dsqlReturningLocalTableNumber.value);
else
{
dsqlScratch->appendUChar(blr_send);
@ -7333,7 +7562,7 @@ void StoreNode::genBlr(DsqlCompilerScratch* dsqlScratch)
if (dsqlReturning && !dsqlScratch->isPsql())
{
if (dsqlRse)
dsqlGenReturningLocalTableDecl(dsqlScratch, dsqlReturning, dsqlReturningLocalTableNumber.value);
dsqlGenReturningLocalTableDecl(dsqlScratch, dsqlReturningLocalTableNumber.value);
else if (!(dsqlScratch->flags & DsqlCompilerScratch::FLAG_UPDATE_OR_INSERT))
{
dsqlScratch->appendUChar(blr_send);
@ -9388,17 +9617,17 @@ static void dsqlGenReturningLocalTableCursor(DsqlCompilerScratch* dsqlScratch, R
}
// Generate BLR for returning's local table declaration.
static void dsqlGenReturningLocalTableDecl(DsqlCompilerScratch* dsqlScratch, ReturningClause* returning, USHORT tableNumber)
static void dsqlGenReturningLocalTableDecl(DsqlCompilerScratch* dsqlScratch, USHORT tableNumber)
{
dsqlScratch->appendUChar(blr_dcl_local_table);
dsqlScratch->appendUShort(tableNumber);
dsqlScratch->appendUChar(blr_dcl_local_table_format);
dsqlScratch->appendUShort(returning->first->items.getCount());
dsqlScratch->appendUShort(dsqlScratch->returningClause->second->items.getCount());
for (auto& retSource : returning->first->items)
for (auto& retTarget : dsqlScratch->returningClause->second->items)
{
dsc fieldDesc;
DsqlDescMaker::fromNode(dsqlScratch, &fieldDesc, retSource);
DsqlDescMaker::fromNode(dsqlScratch, &fieldDesc, retTarget);
GEN_descriptor(dsqlScratch, &fieldDesc, true);
}

View File

@ -1147,7 +1147,9 @@ public:
explicit MergeNode(MemoryPool& pool)
: TypedNode<DsqlOnlyStmtNode, StmtNode::TYPE_MERGE>(pool),
whenMatched(pool),
whenNotMatched(pool)
whenNotMatchedByTarget(pool),
whenNotMatchedBySource(pool),
usingContexts(pool)
{
}
@ -1160,13 +1162,15 @@ public:
NestConst<RecordSourceNode> usingClause;
NestConst<BoolExprNode> condition;
Firebird::ObjectsArray<Matched> whenMatched;
Firebird::ObjectsArray<NotMatched> whenNotMatched;
Firebird::ObjectsArray<NotMatched> whenNotMatchedByTarget;
Firebird::ObjectsArray<Matched> whenNotMatchedBySource;
NestConst<PlanNode> plan;
NestConst<ValueListNode> order;
NestConst<ReturningClause> returning;
NestConst<RseNode> rse;
dsql_ctx* targetContext = nullptr;
dsql_ctx* oldContext = nullptr;
Firebird::SortedArray<USHORT> usingContexts;
};

View File

@ -684,6 +684,7 @@ using namespace Firebird;
// tokens added for Firebird 5.0
%token <metaNamePtr> TARGET
%token <metaNamePtr> TIMEZONE_NAME
%token <metaNamePtr> UNICODE_CHAR
%token <metaNamePtr> UNICODE_VAL
@ -6586,9 +6587,17 @@ merge_when_matched_clause($mergeNode)
%type merge_when_not_matched_clause(<mergeNode>)
merge_when_not_matched_clause($mergeNode)
: WHEN NOT MATCHED
{ $<mergeNotMatchedClause>$ = &$mergeNode->whenNotMatched.add(); }
merge_insert_specification(NOTRIAL($<mergeNotMatchedClause>4))
: WHEN NOT MATCHED by_target_noise
{ $<mergeNotMatchedClause>$ = &$mergeNode->whenNotMatchedByTarget.add(); }
merge_insert_specification(NOTRIAL($<mergeNotMatchedClause>5))
| WHEN NOT MATCHED BY SOURCE
{ $<mergeMatchedClause>$ = &$mergeNode->whenNotMatchedBySource.add(); }
merge_update_specification(NOTRIAL($<mergeMatchedClause>6), NOTRIAL(&$mergeNode->relation->dsqlName))
;
by_target_noise
: // empty
| BY TARGET
;
%type merge_update_specification(<mergeMatchedClause>, <metaNamePtr>)
@ -9105,7 +9114,8 @@ non_reserved_word
| ZONE
| DEBUG // added in FB 4.0.1
| PKCS_1_5
| TIMEZONE_NAME // added in FB 5.0
| TARGET // added in FB 5.0
| TIMEZONE_NAME
| UNICODE_CHAR
| UNICODE_VAL
;

View File

@ -178,7 +178,6 @@ using namespace Firebird;
static string pass1_alias_concat(const string&, const string&);
static void pass1_expand_contexts(DsqlContextStack& contexts, dsql_ctx* context);
static ValueListNode* pass1_group_by_list(DsqlCompilerScratch*, ValueListNode*, ValueListNode*);
static ValueExprNode* pass1_make_derived_field(thread_db*, DsqlCompilerScratch*, ValueExprNode*);
static RseNode* pass1_rse(DsqlCompilerScratch*, RecordSourceNode*, ValueListNode*, RowsClause*, bool, USHORT);
@ -955,8 +954,9 @@ DeclareCursorNode* PASS1_cursor_name(DsqlCompilerScratch* dsqlScratch, const Met
// Extract relation and procedure context and expand derived child contexts.
static void pass1_expand_contexts(DsqlContextStack& contexts, dsql_ctx* context)
void PASS1_expand_contexts(DsqlContextStack& contexts, dsql_ctx* context)
{
//// TODO: LocalTableSourceNode
if (context->ctx_relation || context->ctx_procedure ||
context->ctx_map || context->ctx_win_maps.hasData())
{
@ -968,7 +968,7 @@ static void pass1_expand_contexts(DsqlContextStack& contexts, dsql_ctx* context)
else
{
for (DsqlContextStack::iterator i(context->ctx_childs_derived_table); i.hasData(); ++i)
pass1_expand_contexts(contexts, i.object());
PASS1_expand_contexts(contexts, i.object());
}
}
@ -1089,7 +1089,7 @@ RseNode* PASS1_derived_table(DsqlCompilerScratch* dsqlScratch, SelectExprNode* i
context->ctx_childs_derived_table.push(childCtx);
// Collect contexts that will be used for blr_derived_expr generation.
pass1_expand_contexts(context->ctx_main_derived_contexts, childCtx);
PASS1_expand_contexts(context->ctx_main_derived_contexts, childCtx);
}
while (temp.hasData())

View File

@ -42,6 +42,7 @@ void PASS1_check_unique_fields_names(Jrd::StrArray& names, const Jrd::CompoundSt
Jrd::BoolExprNode* PASS1_compose(Jrd::BoolExprNode*, Jrd::BoolExprNode*, UCHAR);
Jrd::DeclareCursorNode* PASS1_cursor_name(Jrd::DsqlCompilerScratch*, const Jrd::MetaName&, USHORT, bool);
Jrd::RseNode* PASS1_derived_table(Jrd::DsqlCompilerScratch*, Jrd::SelectExprNode*, const char*, bool);
void PASS1_expand_contexts(Jrd::DsqlContextStack& contexts, Jrd::dsql_ctx* context);
Jrd::ValueListNode* PASS1_expand_select_list(Jrd::DsqlCompilerScratch*, Jrd::ValueListNode*, Jrd::RecSourceListNode*);
void PASS1_expand_select_node(Jrd::DsqlCompilerScratch*, Jrd::ExprNode*, Jrd::ValueListNode*, bool);
void PASS1_field_unknown(const TEXT*, const TEXT*, const Jrd::ExprNode*);