mirror of
https://github.com/FirebirdSQL/firebird.git
synced 2025-01-22 18:03:03 +01:00
- #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:
parent
aaedaa7afd
commit
854b809c77
@ -3,7 +3,7 @@ MERGE statement
|
|||||||
-----------------
|
-----------------
|
||||||
|
|
||||||
Function:
|
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.
|
condition.
|
||||||
|
|
||||||
Author:
|
Author:
|
||||||
@ -18,23 +18,28 @@ MERGE statement
|
|||||||
<merge when>...
|
<merge when>...
|
||||||
[<plan clause>]
|
[<plan clause>]
|
||||||
[<order by clause>]
|
[<order by clause>]
|
||||||
<returning clause>
|
[<returning clause>]
|
||||||
|
|
||||||
<merge when> ::=
|
<merge when> ::=
|
||||||
<merge when matched> |
|
<merge when matched> |
|
||||||
<merge when not matched>
|
<merge when not matched by target> |
|
||||||
|
<merge when not matched by source>
|
||||||
|
|
||||||
<merge when matched> ::=
|
<merge when matched> ::=
|
||||||
WHEN MATCHED [ AND <condition> ] THEN
|
WHEN MATCHED [ AND <condition> ] THEN
|
||||||
{ UPDATE SET <assignment list> | DELETE }
|
{ UPDATE SET <assignment list> | DELETE }
|
||||||
|
|
||||||
<merge when not matched> ::=
|
<merge when not matched by target> ::=
|
||||||
WHEN NOT MATCHED [ AND <condition> ] THEN
|
WHEN NOT MATCHED [ BY TARGET ] [ AND <condition> ] THEN
|
||||||
INSERT [ <left paren> <column list> <right paren> ]
|
INSERT [ <left paren> <column list> <right paren> ]
|
||||||
VALUES <left paren> <value 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:
|
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:
|
Scope:
|
||||||
DSQL, PSQL
|
DSQL, PSQL
|
||||||
@ -52,14 +57,40 @@ MERGE statement
|
|||||||
INSERT (id, name)
|
INSERT (id, name)
|
||||||
VALUES (cd.id, cd.name)
|
VALUES (cd.id, cd.name)
|
||||||
|
|
||||||
Notes:
|
2.
|
||||||
A right join is made between INTO and USING tables using the condition.
|
MERGE
|
||||||
UPDATE is called when a record exist in the left table (INTO), otherwise
|
INTO customers c
|
||||||
INSERT is called.
|
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,
|
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
|
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.
|
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.
|
||||||
|
@ -140,6 +140,12 @@ namespace Firebird {
|
|||||||
: FB_NEW_POOL(getPool()) Entry(e, 0);
|
: 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()
|
Object pop()
|
||||||
{
|
{
|
||||||
fb_assert(stk);
|
fb_assert(stk);
|
||||||
|
@ -486,6 +486,7 @@ static const TOK tokens[] =
|
|||||||
{TOK_TAGS, "TAGS", true},
|
{TOK_TAGS, "TAGS", true},
|
||||||
{TOK_TAN, "TAN", true},
|
{TOK_TAN, "TAN", true},
|
||||||
{TOK_TANH, "TANH", true},
|
{TOK_TANH, "TANH", true},
|
||||||
|
{TOK_TARGET, "TARGET", true},
|
||||||
{TOK_TEMPORARY, "TEMPORARY", true},
|
{TOK_TEMPORARY, "TEMPORARY", true},
|
||||||
{TOK_THEN, "THEN", false},
|
{TOK_THEN, "THEN", false},
|
||||||
{TOK_TIES, "TIES", true},
|
{TOK_TIES, "TIES", true},
|
||||||
|
@ -8499,14 +8499,32 @@ bool DsqlMapNode::dsqlMatch(DsqlCompilerScratch* dsqlScratch, const ExprNode* ot
|
|||||||
//--------------------
|
//--------------------
|
||||||
|
|
||||||
|
|
||||||
DerivedFieldNode::DerivedFieldNode(MemoryPool& pool, const MetaName& aName, USHORT aScope,
|
void DerivedFieldNode::getContextNumbers(Firebird::SortedArray<USHORT>& contextNumbers, const DsqlContextStack& contextStack)
|
||||||
ValueExprNode* aValue)
|
|
||||||
: TypedNode<ValueExprNode, ExprNode::TYPE_DERIVED_FIELD>(pool),
|
|
||||||
name(aName),
|
|
||||||
value(aValue),
|
|
||||||
context(NULL),
|
|
||||||
scope(aScope)
|
|
||||||
{
|
{
|
||||||
|
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
|
string DerivedFieldNode::internalPrint(NodePrinter& printer) const
|
||||||
@ -8653,29 +8671,8 @@ void DerivedFieldNode::genBlr(DsqlCompilerScratch* dsqlScratch)
|
|||||||
{
|
{
|
||||||
if (context->ctx_main_derived_contexts.hasData())
|
if (context->ctx_main_derived_contexts.hasData())
|
||||||
{
|
{
|
||||||
HalfStaticArray<USHORT, 4> derivedContexts;
|
SortedArray<USHORT> derivedContexts;
|
||||||
|
getContextNumbers(derivedContexts, context->ctx_main_derived_contexts);
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const FB_SIZE_T derivedContextsCount = derivedContexts.getCount();
|
const FB_SIZE_T derivedContextsCount = derivedContexts.getCount();
|
||||||
|
|
||||||
|
@ -1058,7 +1058,25 @@ class DerivedFieldNode : public TypedNode<ValueExprNode, ExprNode::TYPE_DERIVED_
|
|||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
DerivedFieldNode(MemoryPool& pool, const MetaName& aName, USHORT aScope,
|
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
|
virtual void getChildren(NodeRefsHolder& holder, bool dsql) const
|
||||||
{
|
{
|
||||||
|
@ -78,7 +78,7 @@ static void dsqlGenReturning(DsqlCompilerScratch* dsqlScratch, ReturningClause*
|
|||||||
Nullable<USHORT> localTableNumber);
|
Nullable<USHORT> localTableNumber);
|
||||||
static void dsqlGenReturningLocalTableCursor(DsqlCompilerScratch* dsqlScratch, ReturningClause* returning,
|
static void dsqlGenReturningLocalTableCursor(DsqlCompilerScratch* dsqlScratch, ReturningClause* returning,
|
||||||
USHORT localTableNumber);
|
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 dsql_ctx* dsqlGetContext(const RecordSourceNode* node);
|
||||||
static void dsqlGetContexts(DsqlContextStack& contexts, const RecordSourceNode* node);
|
static void dsqlGetContexts(DsqlContextStack& contexts, const RecordSourceNode* node);
|
||||||
static StmtNode* dsqlNullifyReturning(DsqlCompilerScratch*, StmtNode* input);
|
static StmtNode* dsqlNullifyReturning(DsqlCompilerScratch*, StmtNode* input);
|
||||||
@ -2365,7 +2365,7 @@ void EraseNode::genBlr(DsqlCompilerScratch* dsqlScratch)
|
|||||||
dsqlScratch->appendUChar(blr_begin);
|
dsqlScratch->appendUChar(blr_begin);
|
||||||
|
|
||||||
tableNumber = dsqlScratch->localTableNumber++;
|
tableNumber = dsqlScratch->localTableNumber++;
|
||||||
dsqlGenReturningLocalTableDecl(dsqlScratch, dsqlReturning, tableNumber.value);
|
dsqlGenReturningLocalTableDecl(dsqlScratch, tableNumber.value);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -5508,8 +5508,10 @@ StmtNode* MergeNode::dsqlPass(DsqlCompilerScratch* dsqlScratch)
|
|||||||
|
|
||||||
join->dsqlFrom->items[0] = source;
|
join->dsqlFrom->items[0] = source;
|
||||||
|
|
||||||
// Left join if WHEN NOT MATCHED is present.
|
// Choose join type.
|
||||||
if (whenNotMatched.hasData())
|
if (whenNotMatchedBySource.hasData())
|
||||||
|
join->rse_jointype = whenNotMatchedByTarget.hasData() ? blr_full : blr_right;
|
||||||
|
else if (whenNotMatchedByTarget.hasData())
|
||||||
join->rse_jointype = blr_left;
|
join->rse_jointype = blr_left;
|
||||||
|
|
||||||
join->dsqlFrom->items[1] = target;
|
join->dsqlFrom->items[1] = target;
|
||||||
@ -5520,64 +5522,13 @@ StmtNode* MergeNode::dsqlPass(DsqlCompilerScratch* dsqlScratch)
|
|||||||
querySpec->dsqlFrom->items[0] = join;
|
querySpec->dsqlFrom->items[0] = join;
|
||||||
querySpec->rse_plan = plan;
|
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);
|
const auto selectExpr = FB_NEW_POOL(pool) SelectExprNode(pool);
|
||||||
selectExpr->querySpec = querySpec;
|
selectExpr->querySpec = querySpec;
|
||||||
selectExpr->orderClause = order;
|
selectExpr->orderClause = order;
|
||||||
|
|
||||||
|
if (returning && dsqlScratch->isPsql())
|
||||||
|
selectExpr->dsqlFlags |= RecordSourceNode::DFLAG_SINGLETON;
|
||||||
|
|
||||||
const auto dsqlSelect = FB_NEW_POOL(pool) SelectNode(pool);
|
const auto dsqlSelect = FB_NEW_POOL(pool) SelectNode(pool);
|
||||||
dsqlSelect->dsqlExpr = selectExpr;
|
dsqlSelect->dsqlExpr = selectExpr;
|
||||||
|
|
||||||
@ -5589,17 +5540,57 @@ StmtNode* MergeNode::dsqlPass(DsqlCompilerScratch* dsqlScratch)
|
|||||||
source = processedRse->dsqlStreams->items[0];
|
source = processedRse->dsqlStreams->items[0];
|
||||||
target = nodeAs<RelationSourceNode>(processedRse->dsqlStreams->items[1]);
|
target = nodeAs<RelationSourceNode>(processedRse->dsqlStreams->items[1]);
|
||||||
|
|
||||||
mergeNode->targetContext = dsqlGetContext(target);
|
mergeNode->oldContext = dsqlGetContext(target);
|
||||||
|
|
||||||
DsqlContextStack usingCtxs;
|
DsqlContextStack usingCtxs;
|
||||||
|
|
||||||
|
PASS1_expand_contexts(usingCtxs, source->dsqlContext);
|
||||||
|
DerivedFieldNode::getContextNumbers(mergeNode->usingContexts, usingCtxs);
|
||||||
|
|
||||||
|
usingCtxs.clear();
|
||||||
dsqlGetContexts(usingCtxs, source);
|
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)
|
for (auto& matched : whenMatched)
|
||||||
{
|
{
|
||||||
auto& processedMatched = mergeNode->whenMatched.add();
|
auto& processedMatched = mergeNode->whenMatched.add();
|
||||||
processedMatched.assignments = matched.assignments;
|
processedMatched.assignments = matched.assignments;
|
||||||
|
|
||||||
if (matched.assignments)
|
if (matched.assignments) // SET
|
||||||
{
|
{
|
||||||
// Get the assignments of the UPDATE dsqlScratch.
|
// Get the assignments of the UPDATE dsqlScratch.
|
||||||
// Separate the new and org values to process in correct contexts.
|
// 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);
|
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.
|
dsqlScratch->context->push(usingCtxs); // push the USING contexts
|
||||||
|
dsqlScratch->context->push(mergeNode->oldContext); // push the OLD context
|
||||||
|
|
||||||
for (DsqlContextStack::iterator itr(usingCtxs); itr.hasData(); ++itr)
|
processedMatched.condition = doDsqlPass(dsqlScratch, matched.condition, false);
|
||||||
dsqlScratch->context->push(itr.object()); // push the USING contexts
|
|
||||||
|
|
||||||
dsqlScratch->context->push(oldContext); // process old context values
|
if (useMatchedConditions)
|
||||||
|
{
|
||||||
|
matchedConditions = PASS1_compose(matchedConditions,
|
||||||
|
doDsqlPass(dsqlScratch, matched.condition, false), blr_or);
|
||||||
|
}
|
||||||
|
|
||||||
processedMatched.condition = doDsqlPass(dsqlScratch, matched.condition, false);
|
for (auto& ptr : processedMatched.processedValues)
|
||||||
|
ptr = doDsqlPass(dsqlScratch, ptr, false);
|
||||||
for (auto& ptr : processedMatched.processedValues)
|
}
|
||||||
ptr = doDsqlPass(dsqlScratch, ptr, false);
|
|
||||||
|
|
||||||
// And pop the contexts.
|
|
||||||
dsqlScratch->context->pop();
|
|
||||||
dsqlScratch->context->pop();
|
|
||||||
--dsqlScratch->scopeLevel;
|
|
||||||
|
|
||||||
// Process relation.
|
// Process relation.
|
||||||
processedMatched.modifyRelation = PASS1_relation(dsqlScratch, relation);
|
processedMatched.modifyRelation = PASS1_relation(dsqlScratch, relation);
|
||||||
@ -5642,23 +5634,17 @@ StmtNode* MergeNode::dsqlPass(DsqlCompilerScratch* dsqlScratch)
|
|||||||
|
|
||||||
if (returning)
|
if (returning)
|
||||||
{
|
{
|
||||||
// Repush the source contexts.
|
// Go to the same level of source and target contexts.
|
||||||
++dsqlScratch->scopeLevel; // 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(usingCtxs); // push the USING contexts
|
||||||
dsqlScratch->context->push(itr.object()); // 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 = mergeNode->oldContext->ctx_scope_level;
|
||||||
|
|
||||||
modContext->ctx_scope_level = oldContext->ctx_scope_level;
|
|
||||||
|
|
||||||
mergeNode->returning = processedMatched.processedReturning = dsqlProcessReturning(dsqlScratch,
|
mergeNode->returning = processedMatched.processedReturning = dsqlProcessReturning(dsqlScratch,
|
||||||
oldContext, modContext, returning, dsqlScratch->isPsql());
|
mergeNode->oldContext, modContext, returning, dsqlScratch->isPsql());
|
||||||
|
|
||||||
// And pop them.
|
|
||||||
dsqlScratch->context->pop();
|
|
||||||
dsqlScratch->context->pop();
|
|
||||||
--dsqlScratch->scopeLevel;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
auto valueIt = processedMatched.processedValues.begin();
|
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...
|
// We do not allow cases like UPDATE SET f1 = v1, f2 = v2, f1 = v3...
|
||||||
dsqlFieldAppearsOnce(processedMatched.processedFields, "MERGE");
|
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(usingCtxs); // push the USING contexts
|
||||||
dsqlScratch->context->push(itr.object()); // push the USING contexts
|
dsqlScratch->context->push(mergeNode->oldContext); // push the OLD context
|
||||||
|
|
||||||
dsqlScratch->context->push(mergeNode->targetContext); // process old context values
|
|
||||||
|
|
||||||
processedMatched.condition = doDsqlPass(dsqlScratch, matched.condition, false);
|
processedMatched.condition = doDsqlPass(dsqlScratch, matched.condition, false);
|
||||||
|
|
||||||
mergeNode->returning = processedMatched.processedReturning = dsqlProcessReturning(dsqlScratch,
|
if (useMatchedConditions)
|
||||||
mergeNode->targetContext, nullptr, returning, dsqlScratch->isPsql());
|
{
|
||||||
|
matchedConditions = PASS1_compose(matchedConditions,
|
||||||
|
doDsqlPass(dsqlScratch, matched.condition, false), blr_or);
|
||||||
|
}
|
||||||
|
|
||||||
// And pop the contexts.
|
mergeNode->returning = processedMatched.processedReturning = dsqlProcessReturning(dsqlScratch,
|
||||||
dsqlScratch->context->pop();
|
mergeNode->oldContext, nullptr, returning, dsqlScratch->isPsql());
|
||||||
dsqlScratch->context->pop();
|
|
||||||
--dsqlScratch->scopeLevel;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (auto& notMatched : whenNotMatched)
|
for (auto& notMatched : whenNotMatchedByTarget)
|
||||||
{
|
{
|
||||||
++dsqlScratch->scopeLevel; // Go to the same level of the source context.
|
// Go to the same level of the source context.
|
||||||
|
AutoSetRestore<USHORT> autoScopeLevel(&dsqlScratch->scopeLevel, dsqlScratch->scopeLevel + 1);
|
||||||
|
DsqlContextStack::AutoRestore autoContext(*dsqlScratch->context);
|
||||||
|
|
||||||
for (DsqlContextStack::iterator itr(usingCtxs); itr.hasData(); ++itr)
|
dsqlScratch->context->push(usingCtxs); // push the USING contexts
|
||||||
dsqlScratch->context->push(itr.object()); // push the USING contexts
|
|
||||||
|
|
||||||
// The INSERT relation should be processed in a higher level than the source context.
|
// The INSERT relation should be processed in a higher level than the source context.
|
||||||
++dsqlScratch->scopeLevel;
|
++dsqlScratch->scopeLevel;
|
||||||
|
|
||||||
auto& processedNotMatched = mergeNode->whenNotMatched.add();
|
auto& processedNotMatched = mergeNode->whenNotMatchedByTarget.add();
|
||||||
processedNotMatched.overrideClause = notMatched.overrideClause;
|
processedNotMatched.overrideClause = notMatched.overrideClause;
|
||||||
processedNotMatched.condition = doDsqlPass(dsqlScratch, notMatched.condition, false);
|
processedNotMatched.condition = doDsqlPass(dsqlScratch, notMatched.condition, false);
|
||||||
|
|
||||||
|
if (useNotMatchedByTargetConditions)
|
||||||
|
{
|
||||||
|
notMatchedByTargetConditions = PASS1_compose(notMatchedByTargetConditions,
|
||||||
|
doDsqlPass(dsqlScratch, notMatched.condition, false), blr_or);
|
||||||
|
}
|
||||||
|
|
||||||
{ // scope
|
{ // scope
|
||||||
DsqlContextStack::AutoRestore autoContext(*dsqlScratch->context);
|
DsqlContextStack::AutoRestore autoContextForValues(*dsqlScratch->context);
|
||||||
|
|
||||||
const auto values = doDsqlPass(dsqlScratch, notMatched.values, false);
|
const auto values = doDsqlPass(dsqlScratch, notMatched.values, false);
|
||||||
|
|
||||||
@ -5808,23 +5802,171 @@ StmtNode* MergeNode::dsqlPass(DsqlCompilerScratch* dsqlScratch)
|
|||||||
|
|
||||||
if (returning)
|
if (returning)
|
||||||
{
|
{
|
||||||
const auto oldContext = dsqlGetContext(target);
|
dsqlScratch->context->push(mergeNode->oldContext);
|
||||||
dsqlScratch->context->push(oldContext);
|
|
||||||
|
|
||||||
const auto storeContext = dsqlGetContext(processedNotMatched.storeRelation);
|
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,
|
mergeNode->returning = processedNotMatched.processedReturning = dsqlProcessReturning(dsqlScratch,
|
||||||
oldContext, storeContext, returning, dsqlScratch->isPsql());
|
mergeNode->oldContext, storeContext, returning, dsqlScratch->isPsql());
|
||||||
|
|
||||||
dsqlScratch->context->pop();
|
dsqlScratch->context->pop();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pop the USING context.
|
|
||||||
dsqlScratch->context->pop();
|
|
||||||
--dsqlScratch->scopeLevel;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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())
|
if (!dsqlScratch->isPsql())
|
||||||
{
|
{
|
||||||
// Describe it as TYPE_RETURNING_CURSOR if RETURNING is present or as INSERT otherwise.
|
// 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, usingClause);
|
||||||
NODE_PRINT(printer, condition);
|
NODE_PRINT(printer, condition);
|
||||||
//// FIXME-PRINT: NODE_PRINT(printer, whenMatched);
|
//// 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, returning);
|
||||||
NODE_PRINT(printer, rse);
|
NODE_PRINT(printer, rse);
|
||||||
|
|
||||||
@ -5861,7 +6004,7 @@ void MergeNode::genBlr(DsqlCompilerScratch* dsqlScratch)
|
|||||||
dsqlScratch->appendUChar(blr_begin);
|
dsqlScratch->appendUChar(blr_begin);
|
||||||
|
|
||||||
tableNumber = dsqlScratch->localTableNumber++;
|
tableNumber = dsqlScratch->localTableNumber++;
|
||||||
dsqlGenReturningLocalTableDecl(dsqlScratch, returning, tableNumber.value);
|
dsqlGenReturningLocalTableDecl(dsqlScratch, tableNumber.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Put src info for blr_for.
|
// Put src info for blr_for.
|
||||||
@ -5871,34 +6014,117 @@ void MergeNode::genBlr(DsqlCompilerScratch* dsqlScratch)
|
|||||||
// Generate FOR loop
|
// Generate FOR loop
|
||||||
|
|
||||||
dsqlScratch->appendUChar(blr_for);
|
dsqlScratch->appendUChar(blr_for);
|
||||||
|
|
||||||
dsqlScratch->putBlrMarkers(MARK_FOR_UPDATE | MARK_MERGE);
|
dsqlScratch->putBlrMarkers(MARK_FOR_UPDATE | MARK_MERGE);
|
||||||
|
|
||||||
if (returning && dsqlScratch->isPsql())
|
|
||||||
dsqlScratch->appendUChar(blr_singular);
|
|
||||||
|
|
||||||
GEN_rse(dsqlScratch, rse);
|
GEN_rse(dsqlScratch, rse);
|
||||||
|
|
||||||
// Build body of FOR loop
|
// Build body of FOR loop
|
||||||
|
|
||||||
dsqlScratch->appendUChar(blr_if);
|
if (whenNotMatchedBySource.hasData())
|
||||||
|
{
|
||||||
|
dsqlScratch->appendUChar(blr_if);
|
||||||
|
dsqlScratch->appendUChar(blr_missing);
|
||||||
|
dsqlScratch->appendUChar(blr_derived_expr);
|
||||||
|
dsqlScratch->appendUChar(usingContexts.getCount());
|
||||||
|
|
||||||
if (whenNotMatched.isEmpty())
|
for (auto usingContext : usingContexts)
|
||||||
dsqlScratch->appendUChar(blr_not);
|
GEN_stuff_context_number(dsqlScratch, usingContext);
|
||||||
|
|
||||||
dsqlScratch->appendUChar(blr_missing);
|
dsqlScratch->appendUChar(blr_literal);
|
||||||
dsqlScratch->appendUChar(blr_dbkey);
|
dsqlScratch->appendUChar(blr_bool);
|
||||||
GEN_stuff_context(dsqlScratch, targetContext);
|
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 (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, oldContext);
|
||||||
|
}
|
||||||
|
|
||||||
//// TODO: It should be possible, under certain circunstances, to use single blr_store for inserts
|
//// TODO: It should be possible, under certain circunstances, to use single blr_store for inserts
|
||||||
//// and single blr_modify for updates.
|
//// and single blr_modify for updates.
|
||||||
//// However, if there are inserts with different override clauses or deletes, this is not possible.
|
//// However, if there are inserts with different override clauses or deletes, this is not possible.
|
||||||
|
|
||||||
for (auto nextNotMatched = whenNotMatched.begin(), notMatched = nextNotMatched++;
|
for (auto nextNotMatched = whenNotMatchedByTarget.begin(), notMatched = nextNotMatched++;
|
||||||
notMatched != whenNotMatched.end();
|
notMatched != whenNotMatchedByTarget.end();
|
||||||
notMatched = nextNotMatched++)
|
notMatched = nextNotMatched++)
|
||||||
{
|
{
|
||||||
const bool isLast = nextNotMatched == whenNotMatched.end();
|
const bool isLast = nextNotMatched == whenNotMatchedByTarget.end();
|
||||||
|
|
||||||
if (notMatched->condition || !isLast)
|
if (notMatched->condition || !isLast)
|
||||||
{
|
{
|
||||||
@ -5973,7 +6199,7 @@ void MergeNode::genBlr(DsqlCompilerScratch* dsqlScratch)
|
|||||||
if (matched->assignments) // UPDATE
|
if (matched->assignments) // UPDATE
|
||||||
{
|
{
|
||||||
dsqlScratch->appendUChar(returning ? blr_modify2 : blr_modify);
|
dsqlScratch->appendUChar(returning ? blr_modify2 : blr_modify);
|
||||||
GEN_stuff_context(dsqlScratch, targetContext);
|
GEN_stuff_context(dsqlScratch, oldContext);
|
||||||
GEN_stuff_context(dsqlScratch, matched->modifyRelation->dsqlContext);
|
GEN_stuff_context(dsqlScratch, matched->modifyRelation->dsqlContext);
|
||||||
dsqlScratch->putBlrMarkers(MARK_MERGE);
|
dsqlScratch->putBlrMarkers(MARK_MERGE);
|
||||||
|
|
||||||
@ -6002,7 +6228,7 @@ void MergeNode::genBlr(DsqlCompilerScratch* dsqlScratch)
|
|||||||
}
|
}
|
||||||
|
|
||||||
dsqlScratch->appendUChar(blr_erase);
|
dsqlScratch->appendUChar(blr_erase);
|
||||||
GEN_stuff_context(dsqlScratch, targetContext);
|
GEN_stuff_context(dsqlScratch, oldContext);
|
||||||
dsqlScratch->putBlrMarkers(MARK_MERGE);
|
dsqlScratch->putBlrMarkers(MARK_MERGE);
|
||||||
|
|
||||||
if (returning)
|
if (returning)
|
||||||
@ -6013,8 +6239,11 @@ void MergeNode::genBlr(DsqlCompilerScratch* dsqlScratch)
|
|||||||
dsqlScratch->appendUChar(blr_end);
|
dsqlScratch->appendUChar(blr_end);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (whenNotMatched.hasData() != whenMatched.hasData())
|
if ((whenNotMatchedByTarget.hasData() || whenMatched.hasData()) &&
|
||||||
|
whenNotMatchedByTarget.hasData() != whenMatched.hasData())
|
||||||
|
{
|
||||||
dsqlScratch->appendUChar(blr_end);
|
dsqlScratch->appendUChar(blr_end);
|
||||||
|
}
|
||||||
|
|
||||||
if (returning && !dsqlScratch->isPsql())
|
if (returning && !dsqlScratch->isPsql())
|
||||||
{
|
{
|
||||||
@ -6425,7 +6654,7 @@ void ModifyNode::genBlr(DsqlCompilerScratch* dsqlScratch)
|
|||||||
if (dsqlReturning && !dsqlScratch->isPsql())
|
if (dsqlReturning && !dsqlScratch->isPsql())
|
||||||
{
|
{
|
||||||
if (dsqlCursorName.isEmpty())
|
if (dsqlCursorName.isEmpty())
|
||||||
dsqlGenReturningLocalTableDecl(dsqlScratch, dsqlReturning, dsqlReturningLocalTableNumber.value);
|
dsqlGenReturningLocalTableDecl(dsqlScratch, dsqlReturningLocalTableNumber.value);
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
dsqlScratch->appendUChar(blr_send);
|
dsqlScratch->appendUChar(blr_send);
|
||||||
@ -7333,7 +7562,7 @@ void StoreNode::genBlr(DsqlCompilerScratch* dsqlScratch)
|
|||||||
if (dsqlReturning && !dsqlScratch->isPsql())
|
if (dsqlReturning && !dsqlScratch->isPsql())
|
||||||
{
|
{
|
||||||
if (dsqlRse)
|
if (dsqlRse)
|
||||||
dsqlGenReturningLocalTableDecl(dsqlScratch, dsqlReturning, dsqlReturningLocalTableNumber.value);
|
dsqlGenReturningLocalTableDecl(dsqlScratch, dsqlReturningLocalTableNumber.value);
|
||||||
else if (!(dsqlScratch->flags & DsqlCompilerScratch::FLAG_UPDATE_OR_INSERT))
|
else if (!(dsqlScratch->flags & DsqlCompilerScratch::FLAG_UPDATE_OR_INSERT))
|
||||||
{
|
{
|
||||||
dsqlScratch->appendUChar(blr_send);
|
dsqlScratch->appendUChar(blr_send);
|
||||||
@ -9388,17 +9617,17 @@ static void dsqlGenReturningLocalTableCursor(DsqlCompilerScratch* dsqlScratch, R
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Generate BLR for returning's local table declaration.
|
// 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->appendUChar(blr_dcl_local_table);
|
||||||
dsqlScratch->appendUShort(tableNumber);
|
dsqlScratch->appendUShort(tableNumber);
|
||||||
dsqlScratch->appendUChar(blr_dcl_local_table_format);
|
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;
|
dsc fieldDesc;
|
||||||
DsqlDescMaker::fromNode(dsqlScratch, &fieldDesc, retSource);
|
DsqlDescMaker::fromNode(dsqlScratch, &fieldDesc, retTarget);
|
||||||
GEN_descriptor(dsqlScratch, &fieldDesc, true);
|
GEN_descriptor(dsqlScratch, &fieldDesc, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1147,7 +1147,9 @@ public:
|
|||||||
explicit MergeNode(MemoryPool& pool)
|
explicit MergeNode(MemoryPool& pool)
|
||||||
: TypedNode<DsqlOnlyStmtNode, StmtNode::TYPE_MERGE>(pool),
|
: TypedNode<DsqlOnlyStmtNode, StmtNode::TYPE_MERGE>(pool),
|
||||||
whenMatched(pool),
|
whenMatched(pool),
|
||||||
whenNotMatched(pool)
|
whenNotMatchedByTarget(pool),
|
||||||
|
whenNotMatchedBySource(pool),
|
||||||
|
usingContexts(pool)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1160,13 +1162,15 @@ public:
|
|||||||
NestConst<RecordSourceNode> usingClause;
|
NestConst<RecordSourceNode> usingClause;
|
||||||
NestConst<BoolExprNode> condition;
|
NestConst<BoolExprNode> condition;
|
||||||
Firebird::ObjectsArray<Matched> whenMatched;
|
Firebird::ObjectsArray<Matched> whenMatched;
|
||||||
Firebird::ObjectsArray<NotMatched> whenNotMatched;
|
Firebird::ObjectsArray<NotMatched> whenNotMatchedByTarget;
|
||||||
|
Firebird::ObjectsArray<Matched> whenNotMatchedBySource;
|
||||||
NestConst<PlanNode> plan;
|
NestConst<PlanNode> plan;
|
||||||
NestConst<ValueListNode> order;
|
NestConst<ValueListNode> order;
|
||||||
NestConst<ReturningClause> returning;
|
NestConst<ReturningClause> returning;
|
||||||
|
|
||||||
NestConst<RseNode> rse;
|
NestConst<RseNode> rse;
|
||||||
dsql_ctx* targetContext = nullptr;
|
dsql_ctx* oldContext = nullptr;
|
||||||
|
Firebird::SortedArray<USHORT> usingContexts;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
@ -684,6 +684,7 @@ using namespace Firebird;
|
|||||||
|
|
||||||
// tokens added for Firebird 5.0
|
// tokens added for Firebird 5.0
|
||||||
|
|
||||||
|
%token <metaNamePtr> TARGET
|
||||||
%token <metaNamePtr> TIMEZONE_NAME
|
%token <metaNamePtr> TIMEZONE_NAME
|
||||||
%token <metaNamePtr> UNICODE_CHAR
|
%token <metaNamePtr> UNICODE_CHAR
|
||||||
%token <metaNamePtr> UNICODE_VAL
|
%token <metaNamePtr> UNICODE_VAL
|
||||||
@ -6586,9 +6587,17 @@ merge_when_matched_clause($mergeNode)
|
|||||||
|
|
||||||
%type merge_when_not_matched_clause(<mergeNode>)
|
%type merge_when_not_matched_clause(<mergeNode>)
|
||||||
merge_when_not_matched_clause($mergeNode)
|
merge_when_not_matched_clause($mergeNode)
|
||||||
: WHEN NOT MATCHED
|
: WHEN NOT MATCHED by_target_noise
|
||||||
{ $<mergeNotMatchedClause>$ = &$mergeNode->whenNotMatched.add(); }
|
{ $<mergeNotMatchedClause>$ = &$mergeNode->whenNotMatchedByTarget.add(); }
|
||||||
merge_insert_specification(NOTRIAL($<mergeNotMatchedClause>4))
|
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>)
|
%type merge_update_specification(<mergeMatchedClause>, <metaNamePtr>)
|
||||||
@ -9105,7 +9114,8 @@ non_reserved_word
|
|||||||
| ZONE
|
| ZONE
|
||||||
| DEBUG // added in FB 4.0.1
|
| DEBUG // added in FB 4.0.1
|
||||||
| PKCS_1_5
|
| PKCS_1_5
|
||||||
| TIMEZONE_NAME // added in FB 5.0
|
| TARGET // added in FB 5.0
|
||||||
|
| TIMEZONE_NAME
|
||||||
| UNICODE_CHAR
|
| UNICODE_CHAR
|
||||||
| UNICODE_VAL
|
| UNICODE_VAL
|
||||||
;
|
;
|
||||||
|
@ -178,7 +178,6 @@ using namespace Firebird;
|
|||||||
|
|
||||||
|
|
||||||
static string pass1_alias_concat(const string&, const string&);
|
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 ValueListNode* pass1_group_by_list(DsqlCompilerScratch*, ValueListNode*, ValueListNode*);
|
||||||
static ValueExprNode* pass1_make_derived_field(thread_db*, DsqlCompilerScratch*, ValueExprNode*);
|
static ValueExprNode* pass1_make_derived_field(thread_db*, DsqlCompilerScratch*, ValueExprNode*);
|
||||||
static RseNode* pass1_rse(DsqlCompilerScratch*, RecordSourceNode*, ValueListNode*, RowsClause*, bool, USHORT);
|
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.
|
// 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 ||
|
if (context->ctx_relation || context->ctx_procedure ||
|
||||||
context->ctx_map || context->ctx_win_maps.hasData())
|
context->ctx_map || context->ctx_win_maps.hasData())
|
||||||
{
|
{
|
||||||
@ -968,7 +968,7 @@ static void pass1_expand_contexts(DsqlContextStack& contexts, dsql_ctx* context)
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
for (DsqlContextStack::iterator i(context->ctx_childs_derived_table); i.hasData(); ++i)
|
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);
|
context->ctx_childs_derived_table.push(childCtx);
|
||||||
|
|
||||||
// Collect contexts that will be used for blr_derived_expr generation.
|
// 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())
|
while (temp.hasData())
|
||||||
|
@ -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::BoolExprNode* PASS1_compose(Jrd::BoolExprNode*, Jrd::BoolExprNode*, UCHAR);
|
||||||
Jrd::DeclareCursorNode* PASS1_cursor_name(Jrd::DsqlCompilerScratch*, const Jrd::MetaName&, USHORT, bool);
|
Jrd::DeclareCursorNode* PASS1_cursor_name(Jrd::DsqlCompilerScratch*, const Jrd::MetaName&, USHORT, bool);
|
||||||
Jrd::RseNode* PASS1_derived_table(Jrd::DsqlCompilerScratch*, Jrd::SelectExprNode*, const char*, 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*);
|
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_expand_select_node(Jrd::DsqlCompilerScratch*, Jrd::ExprNode*, Jrd::ValueListNode*, bool);
|
||||||
void PASS1_field_unknown(const TEXT*, const TEXT*, const Jrd::ExprNode*);
|
void PASS1_field_unknown(const TEXT*, const TEXT*, const Jrd::ExprNode*);
|
||||||
|
Loading…
Reference in New Issue
Block a user