mirror of
https://github.com/FirebirdSQL/firebird.git
synced 2025-01-22 17:23: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:
|
||||
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.
|
||||
|
@ -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);
|
||||
|
@ -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},
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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
|
||||
{
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
|
||||
|
@ -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
|
||||
;
|
||||
|
@ -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())
|
||||
|
@ -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*);
|
||||
|
Loading…
Reference in New Issue
Block a user