diff --git a/builds/win32/msvc15/engine.vcxproj b/builds/win32/msvc15/engine.vcxproj
index ced91acb09..520e11f1e5 100644
--- a/builds/win32/msvc15/engine.vcxproj
+++ b/builds/win32/msvc15/engine.vcxproj
@@ -121,6 +121,7 @@
+
diff --git a/builds/win32/msvc15/engine.vcxproj.filters b/builds/win32/msvc15/engine.vcxproj.filters
index eb79d2629a..96e57ad399 100644
--- a/builds/win32/msvc15/engine.vcxproj.filters
+++ b/builds/win32/msvc15/engine.vcxproj.filters
@@ -81,6 +81,9 @@
JRD files\Data Access
+
+ JRD files\Data Access
+
JRD files\Data Access
diff --git a/doc/sql.extensions/README.merge.txt b/doc/sql.extensions/README.merge.txt
index 9987407f50..7ab69b7ddf 100644
--- a/doc/sql.extensions/README.merge.txt
+++ b/doc/sql.extensions/README.merge.txt
@@ -7,7 +7,7 @@ MERGE statement
condition.
Author:
- Adriano dos Santos Fernandes
+ Adriano dos Santos Fernandes
Format:
::=
@@ -16,6 +16,8 @@ MERGE statement
USING [ [AS] ]
ON
...
+ []
+ []
::=
diff --git a/doc/sql.extensions/README.returning b/doc/sql.extensions/README.returning
index 427d72e9fb..9bb33a3e45 100644
--- a/doc/sql.extensions/README.returning
+++ b/doc/sql.extensions/README.returning
@@ -39,11 +39,12 @@ RETURNING clause
Note(s):
1. The INTO part (i.e. the variable list) is allowed in PSQL only (to assign local variables)
and rejected in DSQL.
- 2. In DSQL, values are being returned within the same protocol roundtrip as the INSERT itself
- is executed.
+ 2. In DSQL, INSERT INTO ... VALUES are returned within the same protocol roundtrip as the
+ INSERT is executed - this is not the case for INSERT INTO ... SELECT.
3. If the RETURNING clause is present, then the statement is described as
- isc_info_sql_stmt_exec_procedure by the API (instead of isc_info_sql_stmt_insert),
- so the existing connectivity drivers should support this feature automagically.
+ isc_info_sql_stmt_exec_procedure by the API (for INSERT INTO ... VALUES and statements
+ with WHERE CURRENT OF) and isc_info_sql_stmt_select for the others statements, so the
+ existing connectivity drivers should support this feature automagically.
4. Any explicit record change (update or delete) performed by AFTER-triggers is ignored by
the RETURNING clause.
5. OLD and NEW could be used in RETURNING clause of UPDATE, INSERT OR UPDATE and MERGE statements.
@@ -53,9 +54,13 @@ RETURNING clause
by table name/alias fields are resolved to NEW. In MERGE WHEN MATCHED DELETE they are
resolved to OLD.
8. Since v4 it's possible to use * and alias.*, as well OLD.* and NEW.* where applicable.
+ 9. ORDER BY works with fields from the OLD context (the original record source before changes are applied).
Limitations:
- 1. The modify statement (INSERT, UPDATE, DELETE, MERGE) should operate in no more than one record
- (i.e. should be singleton).
- 2. The statement always return one row in DSQL, even if no record is inserted/updated/deleted.
- This may be changed in the future (i.e. return empty resultset).
+ 1. The modify statement (INSERT, UPDATE, DELETE, UPDATE OR INSERT, MERGE) in PSQL should
+ operate in no more than one record (i.e. should be singleton).
+
+ Changes in v5:
+ 1. Ability to return multiple rows (or no row) in DSQL.
+ 2. Added PLAN and ORDER BY subclauses to MERGE.
+ 3. Added PLAN, ORDER BY and ROWS subclauses to UPDATE OR INSERT.
diff --git a/doc/sql.extensions/README.update_or_insert b/doc/sql.extensions/README.update_or_insert
index 1ff8b3c5a4..ea15faff41 100644
--- a/doc/sql.extensions/README.update_or_insert
+++ b/doc/sql.extensions/README.update_or_insert
@@ -6,12 +6,15 @@ UPDATE OR INSERT statement
Allow to update or insert a record based on the existence (checked with IS NOT DISTINCT) or not of it.
Author:
- Adriano dos Santos Fernandes
+ Adriano dos Santos Fernandes
Syntax rules:
UPDATE OR INSERT INTO [()]
VALUES ()
[MATCHING ()]
+ []
+ []
+ []
[RETURNING [INTO ]]
Scope:
@@ -27,11 +30,9 @@ UPDATE OR INSERT statement
1. When MATCHING is omitted, the existence of a primary key is required.
2. INSERT and UPDATE permissions are needed on .
3. If the RETURNING clause is present, then the statement is described as
- isc_info_sql_stmt_exec_procedure by the API. Otherwise it is described
+ isc_info_sql_stmt_select by the API. Otherwise it is described
as isc_info_sql_stmt_insert.
Limitation:
- 1. A singleton error will be raised if the RETURNING clause is present and more than
- one record match the condition.
- 2. There is no "UPDATE OR INSERT ... SELECT ..." as "INSERT ... SELECT". Use MERGE for
+ 1. There is no "UPDATE OR INSERT ... SELECT ..." as "INSERT ... SELECT". Use MERGE for
this type of functionality.
diff --git a/lang_helpers/gds_codes.pas b/lang_helpers/gds_codes.pas
index 0a0f850e96..02ebf9fd15 100644
--- a/lang_helpers/gds_codes.pas
+++ b/lang_helpers/gds_codes.pas
@@ -1959,6 +1959,8 @@ const
gds_inf_invalid_args = 335545275;
isc_sysf_invalid_null_empty = 335545276;
gds_sysf_invalid_null_empty = 335545276;
+ isc_bad_loctab_num = 335545277;
+ gds_bad_loctab_num = 335545277;
isc_gfix_db_name = 335740929;
gds_gfix_db_name = 335740929;
isc_gfix_invalid_sw = 335740930;
diff --git a/src/dsql/AggNodes.cpp b/src/dsql/AggNodes.cpp
index 67f2011f72..18ed042289 100644
--- a/src/dsql/AggNodes.cpp
+++ b/src/dsql/AggNodes.cpp
@@ -999,6 +999,7 @@ string CountAggNode::internalPrint(NodePrinter& printer) const
return "CountAggNode";
}
+//// TODO: Improve count(*) in local tables.
void CountAggNode::aggInit(thread_db* tdbb, jrd_req* request) const
{
AggNode::aggInit(tdbb, request);
diff --git a/src/dsql/DsqlCompilerScratch.cpp b/src/dsql/DsqlCompilerScratch.cpp
index 0fd80b587e..f1550fe348 100644
--- a/src/dsql/DsqlCompilerScratch.cpp
+++ b/src/dsql/DsqlCompilerScratch.cpp
@@ -960,6 +960,7 @@ RseNode* DsqlCompilerScratch::pass1RseIsRecursive(RseNode* input)
}
}
else if (nodeIs(*pDstTable) || nodeIs(*pDstTable))
+ //// TODO: LocalTableSourceNode
{
if (pass1RelProcIsRecursive(*pDstTable))
{
@@ -990,19 +991,18 @@ bool DsqlCompilerScratch::pass1RelProcIsRecursive(RecordSourceNode* input)
{
MetaName relName;
string relAlias;
- ProcedureSourceNode* procNode;
- RelationSourceNode* relNode;
- if ((procNode = nodeAs(input)))
+ if (auto procNode = nodeAs(input))
{
relName = procNode->dsqlName.identifier;
relAlias = procNode->alias;
}
- else if ((relNode = nodeAs(input)))
+ else if (auto relNode = nodeAs(input))
{
relName = relNode->dsqlName;
relAlias = relNode->alias;
}
+ //// TODO: LocalTableSourceNode
else
return false;
diff --git a/src/dsql/DsqlCompilerScratch.h b/src/dsql/DsqlCompilerScratch.h
index 8ed268ed23..632fbccb13 100644
--- a/src/dsql/DsqlCompilerScratch.h
+++ b/src/dsql/DsqlCompilerScratch.h
@@ -36,6 +36,7 @@ namespace Jrd
class BinaryBoolNode;
class CompoundStmtNode;
class DeclareCursorNode;
+class DeclareLocalTableNode;
class DeclareVariableNode;
class ParameterClause;
class RseNode;
@@ -44,6 +45,9 @@ class TypeClause;
class VariableNode;
class WithClause;
+typedef Firebird::Pair<
+ Firebird::NonPooled, NestConst>> ReturningClause;
+
// DSQL Compiler scratch block - may be discarded after compilation in the future.
class DsqlCompilerScratch : public BlrDebugWriter
@@ -57,7 +61,6 @@ public:
static const unsigned FLAG_BLOCK = 0x0020;
static const unsigned FLAG_RECURSIVE_CTE = 0x0040;
static const unsigned FLAG_UPDATE_OR_INSERT = 0x0080;
- static const unsigned FLAG_MERGE = 0x0100;
static const unsigned FLAG_FUNCTION = 0x0200;
static const unsigned FLAG_SUB_ROUTINE = 0x0400;
static const unsigned FLAG_INTERNAL_REQUEST = 0x0800;
@@ -91,6 +94,8 @@ public:
labels(p),
cursorNumber(0),
cursors(p),
+ localTableNumber(0),
+ localTables(p),
inSelectList(0),
inWhereClause(0),
inGroupByClause(0),
@@ -110,6 +115,7 @@ public:
hiddenVariables(p),
variables(p),
outputVariables(p),
+ returningClause(nullptr),
currCteAlias(NULL),
ctes(p),
cteAliases(p),
@@ -283,6 +289,8 @@ public:
Firebird::Stack labels; // Loop labels
USHORT cursorNumber; // Cursor number
Firebird::Array cursors; // Cursors
+ USHORT localTableNumber; // Local table number
+ Firebird::Array localTables; // Local tables
USHORT inSelectList; // now processing "select list"
USHORT inWhereClause; // processing "where clause"
USHORT inGroupByClause; // processing "group by clause"
@@ -303,6 +311,7 @@ public:
Firebird::Array hiddenVariables; // hidden variables
Firebird::Array variables;
Firebird::Array outputVariables;
+ ReturningClause* returningClause;
const Firebird::string* const* currCteAlias;
private:
diff --git a/src/dsql/ExprNodes.cpp b/src/dsql/ExprNodes.cpp
index 0ef4c1f2d6..723f9edd0b 100644
--- a/src/dsql/ExprNodes.cpp
+++ b/src/dsql/ExprNodes.cpp
@@ -6085,6 +6085,7 @@ ValueExprNode* FieldNode::internalDsqlPass(DsqlCompilerScratch* dsqlScratch, Rec
procNode->dsqlContext = stackContext;
*list = procNode;
}
+ //// TODO: LocalTableSourceNode
fb_assert(*list);
return NULL;
@@ -9457,14 +9458,7 @@ ValueExprNode* OverNode::dsqlPass(DsqlCompilerScratch* dsqlScratch)
static RegisterNode regParameterNode({blr_parameter, blr_parameter2, blr_parameter3});
ParameterNode::ParameterNode(MemoryPool& pool)
- : TypedNode(pool),
- dsqlParameter(NULL),
- message(NULL),
- argFlag(NULL),
- argIndicator(NULL),
- argInfo(NULL),
- dsqlParameterIndex(0),
- argNumber(0)
+ : TypedNode(pool)
{
}
@@ -9536,11 +9530,12 @@ ValueExprNode* ParameterNode::dsqlPass(DsqlCompilerScratch* dsqlScratch)
Arg::Gds(isc_dsql_command_err));
}
- dsql_msg* tempMsg = dsqlParameter ?
- dsqlParameter->par_message : dsqlScratch->getStatement()->getSendMsg();
+ auto msg = dsqlMessage ? dsqlMessage :
+ dsqlParameter ? dsqlParameter->par_message :
+ dsqlScratch->getStatement()->getSendMsg();
- ParameterNode* node = FB_NEW_POOL(dsqlScratch->getPool()) ParameterNode(dsqlScratch->getPool());
- node->dsqlParameter = MAKE_parameter(tempMsg, true, true, dsqlParameterIndex, NULL);
+ auto node = FB_NEW_POOL(dsqlScratch->getPool()) ParameterNode(dsqlScratch->getPool());
+ node->dsqlParameter = MAKE_parameter(msg, true, true, dsqlParameterIndex, nullptr);
node->dsqlParameterIndex = dsqlParameterIndex;
return node;
@@ -9923,11 +9918,12 @@ ValueExprNode* RecordKeyNode::dsqlPass(DsqlCompilerScratch* dsqlScratch)
PASS1_ambiguity_check(dsqlScratch, getAlias(true), contexts);
- RelationSourceNode* relNode = FB_NEW_POOL(dsqlScratch->getPool()) RelationSourceNode(
+ //// TODO: LocalTableSourceNode
+ auto relNode = FB_NEW_POOL(dsqlScratch->getPool()) RelationSourceNode(
dsqlScratch->getPool());
relNode->dsqlContext = context;
- RecordKeyNode* node = FB_NEW_POOL(dsqlScratch->getPool()) RecordKeyNode(dsqlScratch->getPool(), blrOp);
+ auto node = FB_NEW_POOL(dsqlScratch->getPool()) RecordKeyNode(dsqlScratch->getPool(), blrOp);
node->dsqlRelation = relNode;
return node;
@@ -9959,11 +9955,12 @@ ValueExprNode* RecordKeyNode::dsqlPass(DsqlCompilerScratch* dsqlScratch)
if (context->ctx_flags & CTX_null)
return NullNode::instance();
- RelationSourceNode* relNode = FB_NEW_POOL(dsqlScratch->getPool()) RelationSourceNode(
+ //// TODO: LocalTableSourceNode
+ auto relNode = FB_NEW_POOL(dsqlScratch->getPool()) RelationSourceNode(
dsqlScratch->getPool());
relNode->dsqlContext = context;
- RecordKeyNode* node = FB_NEW_POOL(dsqlScratch->getPool()) RecordKeyNode(dsqlScratch->getPool(), blrOp);
+ auto node = FB_NEW_POOL(dsqlScratch->getPool()) RecordKeyNode(dsqlScratch->getPool(), blrOp);
node->dsqlRelation = relNode;
return node;
diff --git a/src/dsql/ExprNodes.h b/src/dsql/ExprNodes.h
index 578283ce1a..d4ee42e9b4 100644
--- a/src/dsql/ExprNodes.h
+++ b/src/dsql/ExprNodes.h
@@ -1585,13 +1585,14 @@ public:
virtual dsc* execute(thread_db* tdbb, jrd_req* request) const;
public:
- dsql_par* dsqlParameter;
+ dsql_msg* dsqlMessage = nullptr;
+ dsql_par* dsqlParameter = nullptr;
NestConst message;
NestConst argFlag;
NestConst argIndicator;
NestConst argInfo;
- USHORT dsqlParameterIndex;
- USHORT argNumber;
+ USHORT dsqlParameterIndex = 0;
+ USHORT argNumber = 0;
};
diff --git a/src/dsql/Nodes.h b/src/dsql/Nodes.h
index 81870c5473..34e98db15c 100644
--- a/src/dsql/Nodes.h
+++ b/src/dsql/Nodes.h
@@ -508,6 +508,7 @@ public:
// RecordSource types
TYPE_AGGREGATE_SOURCE,
+ TYPE_LOCAL_TABLE,
TYPE_PROCEDURE,
TYPE_RELATION,
TYPE_RSE,
@@ -1380,6 +1381,7 @@ public:
TYPE_CONTINUE_LEAVE,
TYPE_CURSOR_STMT,
TYPE_DECLARE_CURSOR,
+ TYPE_DECLARE_LOCAL_TABLE,
TYPE_DECLARE_SUBFUNC,
TYPE_DECLARE_SUBPROC,
TYPE_DECLARE_VARIABLE,
@@ -1412,6 +1414,7 @@ public:
TYPE_STALL,
TYPE_STORE,
TYPE_SUSPEND,
+ TYPE_TRUNCATE_LOCAL_TABLE,
TYPE_UPDATE_OR_INSERT,
TYPE_USER_SAVEPOINT,
diff --git a/src/dsql/StmtNodes.cpp b/src/dsql/StmtNodes.cpp
index 2d334d6042..bfedb904cd 100644
--- a/src/dsql/StmtNodes.cpp
+++ b/src/dsql/StmtNodes.cpp
@@ -73,20 +73,26 @@ template static void dsqlExplodeFields(dsql_rel* relation, Array localTableNumber);
+static void dsqlGenReturningLocalTableCursor(DsqlCompilerScratch* dsqlScratch, ReturningClause* returning,
+ USHORT localTableNumber);
+static void dsqlGenReturningLocalTableDecl(DsqlCompilerScratch* dsqlScratch, ReturningClause* returning, USHORT tableNumber);
static dsql_ctx* dsqlGetContext(const RecordSourceNode* node);
static void dsqlGetContexts(DsqlContextStack& contexts, const RecordSourceNode* node);
-static StmtNode* dsqlNullifyReturning(DsqlCompilerScratch*, StmtNode* input, bool returnList);
+static StmtNode* dsqlNullifyReturning(DsqlCompilerScratch*, StmtNode* input);
static void dsqlFieldAppearsOnce(const Array >& values, const char* command);
static ValueListNode* dsqlPassArray(DsqlCompilerScratch*, ValueListNode*);
static dsql_ctx* dsqlPassCursorContext(DsqlCompilerScratch*, const MetaName&, const RelationSourceNode*);
static RseNode* dsqlPassCursorReference(DsqlCompilerScratch*, const MetaName&, RelationSourceNode*);
static VariableNode* dsqlPassHiddenVariable(DsqlCompilerScratch* dsqlScratch, ValueExprNode* expr);
static USHORT dsqlPassLabel(DsqlCompilerScratch* dsqlScratch, bool breakContinue, MetaName* label);
-static StmtNode* dsqlProcessReturning(DsqlCompilerScratch*, ReturningClause*, StmtNode*);
+static ReturningClause* dsqlProcessReturning(DsqlCompilerScratch* dsqlScratch, dsql_rel* relation, ReturningClause* input, bool singleton);
+static ReturningClause* dsqlProcessReturning(DsqlCompilerScratch* dsqlScratch, dsql_ctx* oldContext,
+ dsql_ctx* modContext, ReturningClause* input, bool singleton);
static void dsqlSetParameterName(DsqlCompilerScratch*, ExprNode*, const ValueExprNode*, const dsql_rel*);
static void dsqlSetParametersName(DsqlCompilerScratch*, CompoundStmtNode*, const RecordSourceNode*);
-
static void cleanupRpb(thread_db* tdbb, record_param* rpb);
static void forceWriteLock(thread_db* tdbb, record_param* rpb, jrd_tra* transaction);
static void makeValidation(thread_db* tdbb, CompilerScratch* csb, StreamType stream,
@@ -135,137 +141,6 @@ namespace
USHORT fldId;
};
- class ReturningProcessor
- {
- public:
- // Play with contexts for RETURNING purposes.
- // Its assumed that oldContext is already on the stack.
- // Changes oldContext name to "OLD".
- ReturningProcessor(DsqlCompilerScratch* aScratch, dsql_ctx* aOldContext, dsql_ctx* modContext,
- ReturningClause* aNode)
- : scratch(aScratch),
- oldContext(aOldContext),
- node(aNode),
- oldAlias(oldContext->ctx_alias),
- oldInternalAlias(oldContext->ctx_internal_alias),
- autoFlags(&oldContext->ctx_flags, oldContext->ctx_flags | CTX_system | CTX_returning),
- autoScopeLevel(&aScratch->scopeLevel, aScratch->scopeLevel + 1),
- autoNodeFirst(node ? &node->first : &temp, node ? node->first : temp)
- {
- // Clone the modify/old context and push with name "NEW" in a greater scope level.
-
- dsql_ctx* newContext = FB_NEW_POOL(scratch->getPool()) dsql_ctx(scratch->getPool());
-
- if (modContext)
- {
- // Push the modify context in the same scope level.
- scratch->context->push(modContext);
- *newContext = *modContext;
- newContext->ctx_flags |= CTX_system;
- }
- else
- {
- // Create the target (= OLD) context and push it on the stack.
- dsql_ctx* targetContext = FB_NEW_POOL(scratch->getPool()) dsql_ctx(scratch->getPool());
- *targetContext = *oldContext;
-
- // ASF: dsql_ctx::operator= do not copy ctx_internal_alias.
- targetContext->ctx_internal_alias = oldContext->ctx_internal_alias;
-
- targetContext->ctx_flags &= ~CTX_system; // resolve unqualified fields
- scratch->context->push(targetContext);
-
- // This is NEW in the context of a DELETE. Mark it as NULL.
- *newContext = *oldContext;
- newContext->ctx_flags |= CTX_null;
- }
-
- oldContext->ctx_alias = oldContext->ctx_internal_alias = OLD_CONTEXT_NAME;
-
- newContext->ctx_alias = newContext->ctx_internal_alias = NEW_CONTEXT_NAME;
- newContext->ctx_flags |= CTX_returning;
- newContext->ctx_scope_level = scratch->scopeLevel;
- scratch->context->push(newContext);
-
- explode(scratch, oldContext->ctx_relation, node);
- }
-
- ~ReturningProcessor()
- {
- oldContext->ctx_alias = oldAlias;
- oldContext->ctx_internal_alias = oldInternalAlias;
-
- // Restore the context stack.
- scratch->context->pop();
- scratch->context->pop();
- }
-
- // Process the RETURNING clause.
- StmtNode* process(StmtNode* stmtNode)
- {
- return dsqlProcessReturning(scratch, node, stmtNode);
- }
-
- // Explode RETURNING * and RETURNING alias.* fields.
- static void explode(DsqlCompilerScratch* scratch, dsql_rel* relation, ReturningClause* node)
- {
- if (!node)
- return;
-
- if (!node->first)
- {
- // Process RETURNING *
- node->first = FB_NEW_POOL(scratch->getPool()) ValueListNode(scratch->getPool(), 0u);
- dsqlExplodeFields(relation, node->first->items, true);
- }
- else
- {
- // Process alias.* items.
- node->first = PASS1_expand_select_list(scratch, node->first, nullptr);
- }
- }
-
- // Clone a RETURNING node without create duplicate parameters.
- static StmtNode* clone(DsqlCompilerScratch* scratch, ReturningClause* unprocessed, StmtNode* processed)
- {
- if (!processed)
- return NULL;
-
- // nod_returning was already processed
- CompoundStmtNode* processedStmt = nodeAs(processed);
- fb_assert(processed);
-
- // And we create a RETURNING node where the targets are already processed.
- CompoundStmtNode* newNode =
- FB_NEW_POOL(scratch->getPool()) CompoundStmtNode(scratch->getPool());
-
- NestConst* srcPtr = unprocessed->first->items.begin();
- NestConst* dstPtr = processedStmt->statements.begin();
-
- for (const NestConst* const end = unprocessed->first->items.end();
- srcPtr != end;
- ++srcPtr, ++dstPtr)
- {
- AssignmentNode* temp = FB_NEW_POOL(scratch->getPool()) AssignmentNode(scratch->getPool());
- temp->asgnFrom = *srcPtr;
- temp->asgnTo = nodeAs(*dstPtr)->asgnTo;
- newNode->statements.add(temp);
- }
-
- return newNode;
- }
-
- private:
- DsqlCompilerScratch* scratch;
- dsql_ctx* oldContext;
- ReturningClause* node;
- string oldAlias, oldInternalAlias;
- AutoSetRestore autoFlags;
- AutoSetRestore autoScopeLevel;
- NestConst temp;
- AutoSetRestore > autoNodeFirst;
- };
-
class SavepointChangeMarker : public Savepoint::ChangeMarker
{
public:
@@ -1428,11 +1303,6 @@ DeclareCursorNode* DeclareCursorNode::pass2(thread_db* tdbb, CompilerScratch* cs
(rse->flags & RseNode::FLAG_SCROLLABLE));
csb->csb_dbg_info->curIndexToName.get(cursorNumber, cursor->name);
- if (cursorNumber >= csb->csb_cursors.getCount())
- csb->csb_cursors.grow(cursorNumber + 1);
-
- csb->csb_cursors[cursorNumber] = cursor;
-
StreamList cursorStreams;
cursor->getAccessPath()->findUsedStreams(cursorStreams);
@@ -1471,6 +1341,121 @@ const StmtNode* DeclareCursorNode::execute(thread_db* /*tdbb*/, jrd_req* request
//--------------------
+static RegisterNode regDeclareLocalTableNode({blr_dcl_local_table});
+
+DmlNode* DeclareLocalTableNode::parse(thread_db* tdbb, MemoryPool& pool, CompilerScratch* csb, const UCHAR /*blrOp*/)
+{
+ auto& blrReader = csb->csb_blr_reader;
+
+ const auto node = FB_NEW_POOL(pool) DeclareLocalTableNode(pool);
+ node->tableNumber = blrReader.getWord();
+
+ csb->csb_localTables.grow(node->tableNumber + 1);
+ fb_assert(!csb->csb_localTables[node->tableNumber]);
+ csb->csb_localTables[node->tableNumber] = node;
+
+ USHORT fieldCount = 0;
+
+ for (UCHAR verb; (verb = blrReader.getByte()) != blr_end;)
+ {
+ switch (verb)
+ {
+ case blr_dcl_local_table_format:
+ if (node->format)
+ PAR_error(csb, Arg::Gds(isc_random) << "duplicate local table format");
+
+ fieldCount = blrReader.getWord();
+ node->format = Format::newFormat(pool, fieldCount);
+ node->format->fmt_length = FLAG_BYTES(fieldCount);
+
+ for (USHORT fieldNum = 0; fieldNum < fieldCount; ++fieldNum)
+ {
+ dsc& fmtDesc = node->format->fmt_desc[fieldNum];
+ //// TODO: Support NOT NULL fields with blr_not_nullable.
+ PAR_desc(tdbb, csb, &fmtDesc, nullptr);
+
+ if (fmtDesc.dsc_dtype >= dtype_aligned)
+ node->format->fmt_length = FB_ALIGN(node->format->fmt_length, type_alignments[fmtDesc.dsc_dtype]);
+
+ fmtDesc.dsc_address = (UCHAR*)(IPTR) node->format->fmt_length;
+ node->format->fmt_length += fmtDesc.dsc_length;
+ }
+
+ break;
+
+ default:
+ PAR_error(csb, Arg::Gds(isc_random) << "Invalid blr_dcl_local_table sub code");
+ }
+ }
+
+ if (fieldCount == 0)
+ PAR_error(csb, Arg::Gds(isc_random) << "Local table without fields");
+
+ return node;
+}
+
+DeclareLocalTableNode* DeclareLocalTableNode::dsqlPass(DsqlCompilerScratch* dsqlScratch)
+{
+ tableNumber = dsqlScratch->localTableNumber++;
+ dsqlScratch->localTables.push(this);
+
+ return this;
+}
+
+string DeclareLocalTableNode::internalPrint(NodePrinter& printer) const
+{
+ StmtNode::internalPrint(printer);
+
+ NODE_PRINT(printer, tableNumber);
+
+ return "DeclareLocalTableNode";
+}
+
+void DeclareLocalTableNode::genBlr(DsqlCompilerScratch* dsqlScratch)
+{
+ dsqlScratch->appendUChar(blr_dcl_local_table);
+ dsqlScratch->appendUShort(tableNumber);
+}
+
+DeclareLocalTableNode* DeclareLocalTableNode::copy(thread_db* tdbb, NodeCopier& copier) const
+{
+ const auto node = FB_NEW_POOL(*tdbb->getDefaultPool()) DeclareLocalTableNode(*tdbb->getDefaultPool());
+ node->format = format;
+ node->tableNumber = tableNumber;
+ return node;
+}
+
+DeclareLocalTableNode* DeclareLocalTableNode::pass2(thread_db* /*tdbb*/, CompilerScratch* csb)
+{
+ impureOffset = csb->allocImpure();
+ return this;
+}
+
+const StmtNode* DeclareLocalTableNode::execute(thread_db* /*tdbb*/, jrd_req* request, ExeState* /*exeState*/) const
+{
+ if (request->req_operation == jrd_req::req_evaluate)
+ request->req_operation = jrd_req::req_return;
+
+ return parentStmt;
+}
+
+DeclareLocalTableNode::Impure* DeclareLocalTableNode::getImpure(thread_db* tdbb, jrd_req* request, bool createWhenDead) const
+{
+ const auto impure = request->getImpure(impureOffset);
+
+ if (createWhenDead && !impure->recordBuffer)
+ {
+ impure->recordBuffer = FB_NEW_POOL(*tdbb->getDefaultPool())
+ RecordBuffer(*tdbb->getDefaultPool(), format);
+ }
+
+ return impure;
+}
+
+
+//--------------------
+
+
static RegisterNode regDeclareSubFuncNode({blr_subfunc_decl});
DmlNode* DeclareSubFuncNode::parse(thread_db* tdbb, MemoryPool& pool, CompilerScratch* csb,
@@ -2276,11 +2261,10 @@ DmlNode* EraseNode::parse(thread_db* /*tdbb*/, MemoryPool& pool, CompilerScratch
StmtNode* EraseNode::dsqlPass(DsqlCompilerScratch* dsqlScratch)
{
- thread_db* tdbb = JRD_get_thread_data(); //necessary?
+ auto relation = dsqlRelation;
- NestConst relation = dsqlRelation;
-
- EraseNode* node = FB_NEW_POOL(dsqlScratch->getPool()) EraseNode(dsqlScratch->getPool());
+ const auto node = FB_NEW_POOL(dsqlScratch->getPool()) EraseNode(dsqlScratch->getPool());
+ node->dsqlCursorName = dsqlCursorName;
if (dsqlCursorName.hasData() && dsqlScratch->isPsql())
{
@@ -2291,9 +2275,7 @@ StmtNode* EraseNode::dsqlPass(DsqlCompilerScratch* dsqlScratch)
dsqlScratch->context->push(node->dsqlContext);
++dsqlScratch->scopeLevel;
- ReturningProcessor::explode(dsqlScratch, node->dsqlContext->ctx_relation, dsqlReturning);
-
- node->statement = dsqlProcessReturning(dsqlScratch, dsqlReturning, statement);
+ node->dsqlReturning = dsqlProcessReturning(dsqlScratch, node->dsqlContext->ctx_relation, dsqlReturning, true);
--dsqlScratch->scopeLevel;
dsqlScratch->context->pop();
@@ -2333,17 +2315,18 @@ StmtNode* EraseNode::dsqlPass(DsqlCompilerScratch* dsqlScratch)
PASS1_limit(dsqlScratch, dsqlRows->length, dsqlRows->skip, rse);
}
- if (dsqlReturning || statement)
+ if (dsqlReturning && dsqlScratch->isPsql())
rse->dsqlFlags |= RecordSourceNode::DFLAG_SINGLETON;
node->dsqlRse = rse;
node->dsqlRelation = nodeAs(rse->dsqlStreams->items[0]);
- ReturningProcessor::explode(dsqlScratch, node->dsqlRelation->dsqlContext->ctx_relation, dsqlReturning);
+ node->dsqlReturning = dsqlProcessReturning(dsqlScratch, node->dsqlRelation->dsqlContext->ctx_relation,
+ dsqlReturning, dsqlCursorName.hasData());
- node->statement = dsqlProcessReturning(dsqlScratch, dsqlReturning, statement);
-
- StmtNode* ret = dsqlNullifyReturning(dsqlScratch, node, true);
+ StmtNode* ret = dsqlCursorName.hasData() ?
+ dsqlNullifyReturning(dsqlScratch, node) :
+ node;
dsqlScratch->context->pop();
@@ -2373,18 +2356,37 @@ string EraseNode::internalPrint(NodePrinter& printer) const
void EraseNode::genBlr(DsqlCompilerScratch* dsqlScratch)
{
- const dsql_msg* message = dsqlGenDmlHeader(dsqlScratch, dsqlRse);
- const dsql_ctx* context;
+ Nullable tableNumber;
- if (dsqlContext)
- context = dsqlContext;
- else
- context = dsqlRelation->dsqlContext;
+ if (dsqlReturning && !dsqlScratch->isPsql())
+ {
+ if (dsqlCursorName.isEmpty())
+ {
+ dsqlScratch->appendUChar(blr_begin);
- if (statement)
+ tableNumber = dsqlScratch->localTableNumber++;
+ dsqlGenReturningLocalTableDecl(dsqlScratch, dsqlReturning, tableNumber.value);
+ }
+ else
+ {
+ dsqlScratch->appendUChar(blr_send);
+ dsqlScratch->appendUChar(dsqlScratch->getStatement()->getReceiveMsg()->msg_number);
+ }
+ }
+
+ if (dsqlRse)
+ {
+ dsqlScratch->appendUChar(blr_for);
+ dsqlScratch->putBlrMarkers(StmtNode::MARK_FOR_UPDATE);
+ GEN_expr(dsqlScratch, dsqlRse);
+ }
+
+ const auto* context = dsqlContext ? dsqlContext : dsqlRelation->dsqlContext;
+
+ if (dsqlReturning)
{
dsqlScratch->appendUChar(blr_begin);
- statement->genBlr(dsqlScratch);
+ dsqlGenReturning(dsqlScratch, dsqlReturning, tableNumber);
}
dsqlScratch->appendUChar(blr_erase);
@@ -2393,11 +2395,17 @@ void EraseNode::genBlr(DsqlCompilerScratch* dsqlScratch)
if (marks)
dsqlScratch->putBlrMarkers(marks);
- if (statement)
+ if (dsqlReturning)
+ {
dsqlScratch->appendUChar(blr_end);
- if (message)
- dsqlScratch->appendUChar(blr_end);
+ if (!dsqlScratch->isPsql() && dsqlCursorName.isEmpty())
+ {
+ dsqlGenReturningLocalTableCursor(dsqlScratch, dsqlReturning, tableNumber.value);
+
+ dsqlScratch->appendUChar(blr_end);
+ }
+ }
}
EraseNode* EraseNode::pass1(thread_db* tdbb, CompilerScratch* csb)
@@ -2434,6 +2442,15 @@ void EraseNode::pass1Erase(thread_db* tdbb, CompilerScratch* csb, EraseNode* nod
tail->csb_flags |= csb_erase;
jrd_rel* const relation = tail->csb_relation;
+
+ //// TODO: LocalTableSourceNode
+ if (!relation)
+ {
+ ERR_post(
+ Arg::Gds(isc_wish_list) <<
+ Arg::Gds(isc_random) << "erase local_table");
+ }
+
view = relation->rel_view_rse ? relation : view;
if (!parent)
@@ -5479,53 +5496,13 @@ const StmtNode* LoopNode::execute(thread_db* /*tdbb*/, jrd_req* request, ExeStat
StmtNode* MergeNode::dsqlPass(DsqlCompilerScratch* dsqlScratch)
{
- // Puts a blr_send before blr_for in DSQL statements.
- class MergeSendNode : public TypedNode
- {
- public:
- MergeSendNode(MemoryPool& pool, StmtNode* aStmt)
- : TypedNode(pool),
- stmt(aStmt)
- {
- }
-
- public:
- virtual string internalPrint(NodePrinter& printer) const
- {
- DsqlOnlyStmtNode::internalPrint(printer);
-
- NODE_PRINT(printer, stmt);
-
- return "MergeSendNode";
- }
-
- // Do not make dsqlPass to process 'stmt'. It's already processed.
-
- virtual void genBlr(DsqlCompilerScratch* dsqlScratch)
- {
- dsql_msg* message = dsqlScratch->getStatement()->getReceiveMsg();
-
- if (!dsqlScratch->isPsql() && message)
- {
- dsqlScratch->appendUChar(blr_send);
- dsqlScratch->appendUChar(message->msg_number);
- }
-
- stmt->genBlr(dsqlScratch);
- }
-
- private:
- StmtNode* stmt;
- };
-
- thread_db* tdbb = JRD_get_thread_data();
- MemoryPool& pool = *tdbb->getDefaultPool();
+ auto& pool = dsqlScratch->getPool();
RecordSourceNode* source = usingClause; // USING
RelationSourceNode* target = relation; // INTO
// Build a join between USING and INTO tables.
- RseNode* join = FB_NEW_POOL(pool) RseNode(pool);
+ const auto join = FB_NEW_POOL(pool) RseNode(pool);
join->dsqlExplicitJoin = true;
join->dsqlFrom = FB_NEW_POOL(pool) RecSourceListNode(pool, 2);
@@ -5538,35 +5515,32 @@ StmtNode* MergeNode::dsqlPass(DsqlCompilerScratch* dsqlScratch)
join->dsqlFrom->items[1] = target;
join->dsqlWhere = condition;
- RseNode* querySpec = FB_NEW_POOL(pool) RseNode(pool);
+ const auto querySpec = FB_NEW_POOL(pool) RseNode(pool);
querySpec->dsqlFrom = FB_NEW_POOL(pool) RecSourceListNode(pool, 1);
querySpec->dsqlFrom->items[0] = join;
+ querySpec->rse_plan = plan;
- BoolExprNode* matchedConditions = NULL;
- BoolExprNode* notMatchedConditions = NULL;
+ BoolExprNode* matchedConditions = nullptr;
+ BoolExprNode* notMatchedConditions = nullptr;
- for (auto matched = whenMatched.begin();
- matched != whenMatched.end();
- ++matched)
+ for (auto& matched : whenMatched)
{
- if (matched->condition)
- matchedConditions = PASS1_compose(matchedConditions, matched->condition, blr_or);
+ if (matched.condition)
+ matchedConditions = PASS1_compose(matchedConditions, matched.condition, blr_or);
else
{
- matchedConditions = NULL;
+ matchedConditions = nullptr;
break;
}
}
- for (auto notMatched = whenNotMatched.begin();
- notMatched != whenNotMatched.end();
- ++notMatched)
+ for (auto& notMatched : whenNotMatched)
{
- if (notMatched->condition)
- notMatchedConditions = PASS1_compose(notMatchedConditions, notMatched->condition, blr_or);
+ if (notMatched.condition)
+ notMatchedConditions = PASS1_compose(notMatchedConditions, notMatched.condition, blr_or);
else
{
- notMatchedConditions = NULL;
+ notMatchedConditions = nullptr;
break;
}
}
@@ -5579,7 +5553,7 @@ StmtNode* MergeNode::dsqlPass(DsqlCompilerScratch* dsqlScratch)
if (whenMatched.hasData()) // WHEN MATCHED
{
- MissingBoolNode* missingNode = FB_NEW_POOL(pool) MissingBoolNode(
+ 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);
@@ -5600,66 +5574,44 @@ StmtNode* MergeNode::dsqlPass(DsqlCompilerScratch* dsqlScratch)
}
}
- SelectExprNode* select_expr = FB_NEW_POOL(dsqlScratch->getPool()) SelectExprNode(dsqlScratch->getPool());
- select_expr->querySpec = querySpec;
+ const auto selectExpr = FB_NEW_POOL(pool) SelectExprNode(pool);
+ selectExpr->querySpec = querySpec;
+ selectExpr->orderClause = order;
- // build a FOR SELECT node
- ForNode* forNode = FB_NEW_POOL(pool) ForNode(pool);
- forNode->dsqlSelect = FB_NEW_POOL(pool) SelectNode(pool);
- forNode->dsqlSelect->dsqlExpr = select_expr;
- forNode->statement = FB_NEW_POOL(pool) CompoundStmtNode(pool);
+ const auto dsqlSelect = FB_NEW_POOL(pool) SelectNode(pool);
+ dsqlSelect->dsqlExpr = selectExpr;
- forNode = forNode->dsqlPass(dsqlScratch);
-
- if (returning)
- forNode->dsqlForceSingular = true;
-
- forNode->forUpdate = true;
- forNode->isMerge = true;
+ const auto mergeNode = FB_NEW_POOL(pool) MergeNode(pool);
+ mergeNode->rse = dsqlSelect->dsqlPass(dsqlScratch)->dsqlRse;
// Get the already processed relations.
- RseNode* processedRse = nodeAs(forNode->rse->dsqlStreams->items[0]);
+ const auto processedRse = nodeAs(mergeNode->rse->dsqlStreams->items[0]);
source = processedRse->dsqlStreams->items[0];
target = nodeAs(processedRse->dsqlStreams->items[1]);
+ mergeNode->targetContext = dsqlGetContext(target);
+
DsqlContextStack usingCtxs;
dsqlGetContexts(usingCtxs, source);
- StmtNode* processedRet = NULL;
- StmtNode* nullRet = NULL;
-
- StmtNode* update = NULL;
- IfNode* lastIf = NULL;
-
- for (auto nextMatched = whenMatched.begin(), matched = nextMatched++;
- matched != whenMatched.end();
- matched = nextMatched++)
+ for (auto& matched : whenMatched)
{
- IfNode* thisIf = FB_NEW_POOL(pool) IfNode(pool);
+ auto& processedMatched = mergeNode->whenMatched.add();
+ processedMatched.assignments = matched.assignments;
- if (matched->assignments)
+ if (matched.assignments)
{
// Get the assignments of the UPDATE dsqlScratch.
- CompoundStmtNode* stmts = matched->assignments;
- Array > orgValues, newValues;
-
// Separate the new and org values to process in correct contexts.
- for (FB_SIZE_T i = 0; i < stmts->statements.getCount(); ++i)
+ for (auto& stmt : matched.assignments->statements)
{
- AssignmentNode* const assign = nodeAs(stmts->statements[i]);
+ const auto assign = nodeAs(stmt);
fb_assert(assign);
- orgValues.add(assign->asgnFrom);
- newValues.add(assign->asgnTo);
+ processedMatched.processedValues.add(assign->asgnFrom);
+ processedMatched.processedFields.add(assign->asgnTo);
}
- // Build the MODIFY node.
- ModifyNode* modify = FB_NEW_POOL(pool) ModifyNode(pool);
- modify->marks |= StmtNode::MARK_MERGE;
- thisIf->trueAction = modify;
-
- dsql_ctx* const oldContext = dsqlGetContext(target);
-
- modify->dsqlContext = oldContext;
+ const auto oldContext = dsqlGetContext(target);
++dsqlScratch->scopeLevel; // Go to the same level of source and target contexts.
@@ -5668,13 +5620,10 @@ StmtNode* MergeNode::dsqlPass(DsqlCompilerScratch* dsqlScratch)
dsqlScratch->context->push(oldContext); // process old context values
- if (matched->condition)
- thisIf->condition = doDsqlPass(dsqlScratch, matched->condition, false);
+ processedMatched.condition = doDsqlPass(dsqlScratch, matched.condition, false);
- NestConst* ptr;
-
- for (ptr = orgValues.begin(); ptr != orgValues.end(); ++ptr)
- *ptr = doDsqlPass(dsqlScratch, *ptr, false);
+ for (auto& ptr : processedMatched.processedValues)
+ ptr = doDsqlPass(dsqlScratch, ptr, false);
// And pop the contexts.
dsqlScratch->context->pop();
@@ -5682,12 +5631,12 @@ StmtNode* MergeNode::dsqlPass(DsqlCompilerScratch* dsqlScratch)
--dsqlScratch->scopeLevel;
// Process relation.
- modify->dsqlRelation = PASS1_relation(dsqlScratch, relation);
- dsql_ctx* modContext = dsqlGetContext(modify->dsqlRelation);
+ processedMatched.modifyRelation = PASS1_relation(dsqlScratch, relation);
+ auto modContext = dsqlGetContext(processedMatched.modifyRelation);
// Process new context values.
- for (ptr = newValues.begin(); ptr != newValues.end(); ++ptr)
- *ptr = doDsqlPass(dsqlScratch, *ptr, false);
+ for (auto& ptr : processedMatched.processedFields)
+ ptr = doDsqlPass(dsqlScratch, ptr, false);
dsqlScratch->context->pop();
@@ -5703,14 +5652,8 @@ StmtNode* MergeNode::dsqlPass(DsqlCompilerScratch* dsqlScratch)
modContext->ctx_scope_level = oldContext->ctx_scope_level;
- { // scope
- ReturningProcessor returningProcessor(dsqlScratch, oldContext, modContext, returning);
- StmtNode* updRet = ReturningProcessor::clone(dsqlScratch, returning, processedRet);
- processedRet = modify->statement2 = returningProcessor.process(updRet);
- }
-
- if (!nullRet)
- nullRet = dsqlNullifyReturning(dsqlScratch, modify, false);
+ mergeNode->returning = processedMatched.processedReturning = dsqlProcessReturning(dsqlScratch,
+ oldContext, modContext, returning, dsqlScratch->isPsql());
// And pop them.
dsqlScratch->context->pop();
@@ -5718,92 +5661,42 @@ StmtNode* MergeNode::dsqlPass(DsqlCompilerScratch* dsqlScratch)
--dsqlScratch->scopeLevel;
}
- // Recreate the list of assignments.
+ auto valueIt = processedMatched.processedValues.begin();
- CompoundStmtNode* assignStatements = FB_NEW_POOL(pool) CompoundStmtNode(pool);
- modify->statement = assignStatements;
-
- assignStatements->statements.resize(stmts->statements.getCount());
-
- for (FB_SIZE_T i = 0; i < assignStatements->statements.getCount(); ++i)
+ for (auto& field : processedMatched.processedFields)
{
- if (!PASS1_set_parameter_type(dsqlScratch, orgValues[i], newValues[i], false))
- PASS1_set_parameter_type(dsqlScratch, newValues[i], orgValues[i], false);
+ if (!PASS1_set_parameter_type(dsqlScratch, *valueIt, field, false))
+ PASS1_set_parameter_type(dsqlScratch, field, *valueIt, false);
- AssignmentNode* assign = FB_NEW_POOL(pool) AssignmentNode(pool);
- assign->asgnFrom = orgValues[i];
- assign->asgnTo = newValues[i];
- assignStatements->statements[i] = assign;
+ ++valueIt;
}
// We do not allow cases like UPDATE SET f1 = v1, f2 = v2, f1 = v3...
- dsqlFieldAppearsOnce(newValues, "MERGE");
+ dsqlFieldAppearsOnce(processedMatched.processedFields, "MERGE");
}
else
{
- // Build the DELETE node.
- EraseNode* erase = FB_NEW_POOL(pool) EraseNode(pool);
- erase->marks |= StmtNode::MARK_MERGE;
- thisIf->trueAction = erase;
-
- dsql_ctx* context = dsqlGetContext(target);
- erase->dsqlContext = 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(context); // process old context values
+ dsqlScratch->context->push(mergeNode->targetContext); // process old context values
- if (matched->condition)
- thisIf->condition = doDsqlPass(dsqlScratch, matched->condition, false);
+ processedMatched.condition = doDsqlPass(dsqlScratch, matched.condition, false);
- if (returning)
- {
- { // scope
- ReturningProcessor returningProcessor(dsqlScratch, context, NULL, returning);
- StmtNode* delRet = ReturningProcessor::clone(dsqlScratch, returning, processedRet);
- processedRet = erase->statement = returningProcessor.process(delRet);
- }
-
- if (!nullRet)
- nullRet = dsqlNullifyReturning(dsqlScratch, erase, 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;
}
-
- if (!thisIf->condition && nextMatched != whenMatched.end())
- {
- ComparativeBoolNode* cmpNode = FB_NEW_POOL(pool) ComparativeBoolNode(pool,
- blr_eql, MAKE_constant("1", CONSTANT_BOOLEAN), MAKE_constant("1", CONSTANT_BOOLEAN));
- cmpNode->dsqlCheckBoolean = true;
-
- NestConst trueCondition(cmpNode);
- thisIf->condition = doDsqlPass(dsqlScratch, trueCondition, false);
- }
-
- if (lastIf)
- lastIf->falseAction = thisIf->condition ? thisIf : thisIf->trueAction;
- else
- update = thisIf->condition ? thisIf : thisIf->trueAction;
-
- lastIf = thisIf;
}
- StmtNode* insert = NULL;
- lastIf = NULL;
-
- for (auto nextNotMatched = whenNotMatched.begin(), notMatched = nextNotMatched++;
- notMatched != whenNotMatched.end();
- notMatched = nextNotMatched++)
+ for (auto& notMatched : whenNotMatched)
{
- IfNode* thisIf = FB_NEW_POOL(pool) IfNode(pool);
-
++dsqlScratch->scopeLevel; // Go to the same level of the source context.
for (DsqlContextStack::iterator itr(usingCtxs); itr.hasData(); ++itr)
@@ -5812,39 +5705,117 @@ StmtNode* MergeNode::dsqlPass(DsqlCompilerScratch* dsqlScratch)
// The INSERT relation should be processed in a higher level than the source context.
++dsqlScratch->scopeLevel;
- // Build the INSERT node.
- StoreNode* store = FB_NEW_POOL(pool) StoreNode(pool);
- store->dsqlRelation = relation;
- store->dsqlFields = notMatched->fields;
- store->dsqlValues = notMatched->values;
- store->overrideClause = notMatched->overrideClause;
+ auto& processedNotMatched = mergeNode->whenNotMatched.add();
+ processedNotMatched.overrideClause = notMatched.overrideClause;
+ processedNotMatched.condition = doDsqlPass(dsqlScratch, notMatched.condition, false);
- bool needSavePoint; // unused
- thisIf->trueAction = store = nodeAs(store->internalDsqlPass(dsqlScratch, false, needSavePoint));
- fb_assert(store);
+ { // scope
+ DsqlContextStack::AutoRestore autoContext(*dsqlScratch->context);
- if (notMatched->condition)
- thisIf->condition = doDsqlPass(dsqlScratch, notMatched->condition, false);
+ const auto values = doDsqlPass(dsqlScratch, notMatched.values, false);
+
+ processedNotMatched.storeRelation = PASS1_relation(dsqlScratch, relation);
+ const auto storeContext = processedNotMatched.storeRelation->dsqlContext;
+ const auto dsqlRelation = storeContext->ctx_relation;
+
+ auto& fields = processedNotMatched.processedFields;
+
+ if (notMatched.fields.hasData())
+ {
+ for (auto& field : notMatched.fields)
+ {
+ fields.add(nullptr);
+ doDsqlPass(dsqlScratch, fields.back(), field, false);
+ }
+
+ // We do not allow cases like INSERT INTO T (f1, f2, f1)...
+ dsqlFieldAppearsOnce(fields, "MERGE");
+
+ // begin IBO hack
+ for (const auto& field : fields)
+ {
+ const dsql_ctx* tmpCtx = nullptr;
+ const TEXT* tmpName = nullptr;
+
+ if (auto fieldNode = nodeAs(field))
+ {
+ tmpCtx = fieldNode->dsqlContext;
+ if (fieldNode->dsqlField)
+ tmpName = fieldNode->dsqlField->fld_name.c_str();
+ }
+ else if (auto derivedField = nodeAs(field))
+ {
+ tmpCtx = derivedField->context;
+ tmpName = derivedField->name.nullStr();
+ }
+
+ if (tmpCtx &&
+ ((tmpCtx->ctx_relation && dsqlRelation->rel_name != tmpCtx->ctx_relation->rel_name) ||
+ tmpCtx->ctx_context != storeContext->ctx_context))
+ {
+ const auto badRel = tmpCtx->ctx_relation;
+
+ PASS1_field_unknown((badRel ? badRel->rel_name.c_str() : NULL),
+ tmpName, notMatched.fields[&field - fields.begin()]);
+ }
+ }
+ // end IBO hack
+ }
+ else
+ {
+ dsqlExplodeFields(dsqlRelation, fields, false);
+
+ for (auto& field : fields)
+ field = doDsqlPass(dsqlScratch, field, false);
+ }
+
+ if (fields.getCount() != values->items.getCount())
+ {
+ // count of column list and value list don't match
+ ERRD_post(
+ Arg::Gds(isc_sqlerr) << Arg::Num(-804) <<
+ Arg::Gds(isc_dsql_var_count_err));
+ }
+
+ auto ptr2 = values->items.begin();
+ for (const auto& ptr : fields)
+ {
+ // *ptr2 is nullptr for DEFAULT
+
+ if (!*ptr2)
+ {
+ const auto field = nodeAs(ptr);
+
+ if (field && field->dsqlField)
+ {
+ *ptr2 = FB_NEW_POOL(dsqlScratch->getPool()) DefaultNode(dsqlScratch->getPool(),
+ dsqlRelation->rel_name, field->dsqlField->fld_name);
+ *ptr2 = doDsqlPass(dsqlScratch, *ptr2, false);
+ }
+ }
+
+ fb_assert(*ptr2);
+ PASS1_set_parameter_type(dsqlScratch, *ptr2, ptr, false);
+
+ ++ptr2;
+ }
+
+ processedNotMatched.values = values;
+ }
// Restore the scope level.
--dsqlScratch->scopeLevel;
if (returning)
{
- dsql_ctx* const oldContext = dsqlGetContext(target);
+ const auto oldContext = dsqlGetContext(target);
dsqlScratch->context->push(oldContext);
- dsql_ctx* context = dsqlGetContext(store->dsqlRelation);
- context->ctx_scope_level = oldContext->ctx_scope_level;
+ const auto storeContext = dsqlGetContext(processedNotMatched.storeRelation);
+ storeContext->ctx_scope_level = oldContext->ctx_scope_level;
- { // scope
- ReturningProcessor returningProcessor(dsqlScratch, oldContext, context, returning);
- StmtNode* insRet = ReturningProcessor::clone(dsqlScratch, returning, processedRet);
- processedRet = store->statement2 = returningProcessor.process(insRet);
- }
-
- if (!nullRet)
- nullRet = dsqlNullifyReturning(dsqlScratch, store, false);
+ mergeNode->returning = processedNotMatched.processedReturning = dsqlProcessReturning(dsqlScratch,
+ oldContext, storeContext, returning, dsqlScratch->isPsql());
dsqlScratch->context->pop();
}
@@ -5852,74 +5823,18 @@ StmtNode* MergeNode::dsqlPass(DsqlCompilerScratch* dsqlScratch)
// Pop the USING context.
dsqlScratch->context->pop();
--dsqlScratch->scopeLevel;
-
- if (!thisIf->condition && nextNotMatched != whenNotMatched.end())
- {
- ComparativeBoolNode* cmpNode = FB_NEW_POOL(pool) ComparativeBoolNode(pool,
- blr_eql, MAKE_constant("1", CONSTANT_BOOLEAN), MAKE_constant("1", CONSTANT_BOOLEAN));
- cmpNode->dsqlCheckBoolean = true;
-
- NestConst trueCondition(cmpNode);
- thisIf->condition = doDsqlPass(dsqlScratch, trueCondition, false);
- }
-
- if (lastIf)
- lastIf->falseAction = thisIf->condition ? thisIf : thisIf->trueAction;
- else
- insert = thisIf->condition ? thisIf : thisIf->trueAction;
-
- lastIf = thisIf;
- }
-
- // Build a IF (target.RDB$DB_KEY IS NULL).
- IfNode* action = FB_NEW_POOL(pool) IfNode(pool);
-
- RecordKeyNode* dbKeyNode = FB_NEW_POOL(pool) RecordKeyNode(pool, blr_dbkey);
- dbKeyNode->dsqlRelation = target;
-
- action->condition = FB_NEW_POOL(pool) MissingBoolNode(pool, dbKeyNode);
-
- if (insert)
- {
- action->trueAction = insert; // then INSERT
- action->falseAction = update; // else UPDATE/DELETE
- }
- else
- {
- // Negate the condition -> IF (target.RDB$DB_KEY IS NOT NULL).
- action->condition = FB_NEW_POOL(pool) NotBoolNode(pool, action->condition);
- action->trueAction = update; // then UPDATE/DELETE
}
if (!dsqlScratch->isPsql())
{
- // Describe it as EXECUTE_PROCEDURE if RETURNING is present or as INSERT otherwise.
- if (returning)
- dsqlScratch->getStatement()->setType(DsqlCompiledStatement::TYPE_EXEC_PROCEDURE);
- else
- dsqlScratch->getStatement()->setType(DsqlCompiledStatement::TYPE_INSERT);
-
- dsqlScratch->flags |= DsqlCompilerScratch::FLAG_MERGE;
+ // Describe it as TYPE_RETURNING_CURSOR if RETURNING is present or as INSERT otherwise.
+ dsqlScratch->getStatement()->setType(returning ?
+ DsqlCompiledStatement::TYPE_RETURNING_CURSOR :
+ DsqlCompiledStatement::TYPE_INSERT);
}
- // Insert the IF inside the FOR SELECT.
- forNode->statement = action;
-
- StmtNode* mergeStmt = forNode;
-
// Setup the main node.
-
- if (nullRet)
- {
- CompoundStmtNode* temp = FB_NEW_POOL(pool) CompoundStmtNode(pool);
- temp->statements.add(nullRet);
- temp->statements.add(forNode);
- mergeStmt = temp;
- }
-
- StmtNode* sendNode = (FB_NEW_POOL(pool) MergeSendNode(pool, mergeStmt))->dsqlPass(dsqlScratch);
-
- return SavepointEncloseNode::make(dsqlScratch->getPool(), dsqlScratch, sendNode);
+ return SavepointEncloseNode::make(pool, dsqlScratch, mergeNode);
}
string MergeNode::internalPrint(NodePrinter& printer) const
@@ -5932,12 +5847,181 @@ string MergeNode::internalPrint(NodePrinter& printer) const
//// FIXME-PRINT: NODE_PRINT(printer, whenMatched);
//// FIXME-PRINT: NODE_PRINT(printer, whenNotMatched);
NODE_PRINT(printer, returning);
+ NODE_PRINT(printer, rse);
return "MergeNode";
}
-void MergeNode::genBlr(DsqlCompilerScratch* /*dsqlScratch*/)
+void MergeNode::genBlr(DsqlCompilerScratch* dsqlScratch)
{
+ Nullable tableNumber;
+
+ if (returning && !dsqlScratch->isPsql())
+ {
+ dsqlScratch->appendUChar(blr_begin);
+
+ tableNumber = dsqlScratch->localTableNumber++;
+ dsqlGenReturningLocalTableDecl(dsqlScratch, returning, tableNumber.value);
+ }
+
+ // Put src info for blr_for.
+ if (hasLineColumn)
+ dsqlScratch->putDebugSrcInfo(line, column);
+
+ // 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
+
+ dsqlScratch->appendUChar(blr_if);
+
+ if (whenNotMatched.isEmpty())
+ dsqlScratch->appendUChar(blr_not);
+
+ dsqlScratch->appendUChar(blr_missing);
+ dsqlScratch->appendUChar(blr_dbkey);
+ GEN_stuff_context(dsqlScratch, targetContext);
+
+ //// 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();
+ notMatched = nextNotMatched++)
+ {
+ const bool isLast = nextNotMatched == whenNotMatched.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);
+ }
+ }
+
+ dsqlScratch->appendUChar(notMatched->overrideClause.specified ? blr_store3 : (returning ? blr_store2 : blr_store));
+
+ if (notMatched->overrideClause.specified)
+ dsqlScratch->appendUChar(UCHAR(notMatched->overrideClause.value));
+
+ GEN_expr(dsqlScratch, notMatched->storeRelation);
+
+ dsqlScratch->appendUChar(blr_begin);
+
+ auto valueIt = notMatched->values->items.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 if (notMatched->overrideClause.specified)
+ dsqlScratch->appendUChar(blr_null);
+
+ if (notMatched->condition && isLast)
+ dsqlScratch->appendUChar(blr_end);
+ }
+
+ for (auto nextMatched = whenMatched.begin(), matched = nextMatched++;
+ matched != whenMatched.end();
+ matched = nextMatched++)
+ {
+ const bool isLast = nextMatched == whenMatched.end();
+
+ if (matched->condition || !isLast)
+ {
+ dsqlScratch->appendUChar(blr_if);
+
+ if (matched->condition)
+ matched->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 (matched->assignments) // UPDATE
+ {
+ dsqlScratch->appendUChar(returning ? blr_modify2 : blr_modify);
+ GEN_stuff_context(dsqlScratch, targetContext);
+ GEN_stuff_context(dsqlScratch, matched->modifyRelation->dsqlContext);
+ dsqlScratch->putBlrMarkers(MARK_MERGE);
+
+ dsqlScratch->appendUChar(blr_begin);
+
+ auto valueIt = matched->processedValues.begin();
+
+ for (auto& field : matched->processedFields)
+ {
+ dsqlScratch->appendUChar(blr_assignment);
+ (*valueIt++)->genBlr(dsqlScratch);
+ field->genBlr(dsqlScratch);
+ }
+
+ dsqlScratch->appendUChar(blr_end);
+
+ if (returning)
+ dsqlGenReturning(dsqlScratch, matched->processedReturning, tableNumber);
+ }
+ else // DELETE
+ {
+ if (returning)
+ {
+ dsqlScratch->appendUChar(blr_begin);
+ dsqlGenReturning(dsqlScratch, matched->processedReturning, tableNumber);
+ }
+
+ dsqlScratch->appendUChar(blr_erase);
+ GEN_stuff_context(dsqlScratch, targetContext);
+ dsqlScratch->putBlrMarkers(MARK_MERGE);
+
+ if (returning)
+ dsqlScratch->appendUChar(blr_end);
+ }
+
+ if (matched->condition && isLast)
+ dsqlScratch->appendUChar(blr_end);
+ }
+
+ if (whenNotMatched.hasData() != whenMatched.hasData())
+ dsqlScratch->appendUChar(blr_end);
+
+ if (returning && !dsqlScratch->isPsql())
+ {
+ dsqlGenReturningLocalTableCursor(dsqlScratch, returning, tableNumber.value);
+
+ dsqlScratch->appendUChar(blr_end);
+ }
}
@@ -6128,14 +6212,13 @@ DmlNode* ModifyNode::parse(thread_db* tdbb, MemoryPool& pool, CompilerScratch* c
StmtNode* ModifyNode::internalDsqlPass(DsqlCompilerScratch* dsqlScratch, bool updateOrInsert)
{
- thread_db* tdbb = JRD_get_thread_data(); // necessary?
- MemoryPool& pool = dsqlScratch->getPool();
+ auto& pool = dsqlScratch->getPool();
// Separate old and new context references.
- Array > orgValues, newValues;
+ Array> orgValues, newValues;
- CompoundStmtNode* assignments = nodeAs(statement);
+ const auto assignments = nodeAs(statement);
fb_assert(assignments);
for (FB_SIZE_T i = 0; i < assignments->statements.getCount(); ++i)
@@ -6151,7 +6234,16 @@ StmtNode* ModifyNode::internalDsqlPass(DsqlCompilerScratch* dsqlScratch, bool up
NestConst* ptr;
- ModifyNode* node = FB_NEW_POOL(pool) ModifyNode(pool);
+ const auto node = FB_NEW_POOL(pool) ModifyNode(pool);
+
+ if (dsqlReturning && !dsqlScratch->isPsql() && dsqlCursorName.isEmpty())
+ {
+ node->dsqlReturningLocalTableNumber = updateOrInsert ?
+ dsqlReturningLocalTableNumber.value :
+ dsqlScratch->localTableNumber++;
+ }
+
+ node->dsqlCursorName = dsqlCursorName;
if (dsqlCursorName.hasData() && dsqlScratch->isPsql())
{
@@ -6183,21 +6275,15 @@ StmtNode* ModifyNode::internalDsqlPass(DsqlCompilerScratch* dsqlScratch, bool up
dsqlScratch->context->push(oldContext); // process old context values
++dsqlScratch->scopeLevel;
- { // scope
- ReturningProcessor returningProcessor(dsqlScratch, oldContext, modContext, dsqlReturning);
-
- if (updateOrInsert)
- statement2 = ReturningProcessor::clone(dsqlScratch, dsqlReturning, statement2);
-
- node->statement2 = returningProcessor.process(statement2);
- }
+ node->dsqlReturning = dsqlProcessReturning(dsqlScratch,
+ oldContext, modContext, dsqlReturning, true);
--dsqlScratch->scopeLevel;
dsqlScratch->context->pop();
// Recreate list of assignments.
- CompoundStmtNode* assignStatements = FB_NEW_POOL(pool) CompoundStmtNode(pool);
+ const auto assignStatements = FB_NEW_POOL(pool) CompoundStmtNode(pool);
node->statement = assignStatements;
assignStatements->statements.resize(assignments->statements.getCount());
@@ -6223,8 +6309,8 @@ StmtNode* ModifyNode::internalDsqlPass(DsqlCompilerScratch* dsqlScratch, bool up
dsql_ctx* mod_context = dsqlGetContext(node->dsqlRelation);
// Process new context values.
- for (ptr = newValues.begin(); ptr != newValues.end(); ++ptr)
- *ptr = doDsqlPass(dsqlScratch, *ptr, false);
+ for (auto& value : newValues)
+ value = doDsqlPass(dsqlScratch, value, false);
dsqlScratch->context->pop();
@@ -6244,7 +6330,7 @@ StmtNode* ModifyNode::internalDsqlPass(DsqlCompilerScratch* dsqlScratch, bool up
rse = FB_NEW_POOL(pool) RseNode(pool);
rse->dsqlFlags = dsqlRseFlags;
- if (dsqlReturning || statement2)
+ if (dsqlReturning && dsqlScratch->isPsql())
rse->dsqlFlags |= RecordSourceNode::DFLAG_SINGLETON;
rse->dsqlStreams = FB_NEW_POOL(pool) RecSourceListNode(pool, 1);
@@ -6264,27 +6350,20 @@ StmtNode* ModifyNode::internalDsqlPass(DsqlCompilerScratch* dsqlScratch, bool up
PASS1_limit(dsqlScratch, dsqlRows->length, dsqlRows->skip, rse);
}
- if (dsqlReturning || statement2)
- {
- ReturningProcessor returningProcessor(dsqlScratch, old_context, mod_context, dsqlReturning);
-
- if (updateOrInsert)
- statement2 = ReturningProcessor::clone(dsqlScratch, dsqlReturning, statement2);
-
- node->statement2 = returningProcessor.process(statement2);
- }
+ node->dsqlReturning = dsqlProcessReturning(dsqlScratch,
+ old_context, mod_context, dsqlReturning, dsqlCursorName.hasData());
node->dsqlRse = rse;
// Process old context values.
- for (ptr = orgValues.begin(); ptr != orgValues.end(); ++ptr)
- *ptr = doDsqlPass(dsqlScratch, *ptr, false);
+ for (auto& value : orgValues)
+ value = doDsqlPass(dsqlScratch, value, false);
dsqlScratch->context->pop();
// Recreate list of assignments.
- CompoundStmtNode* assignStatements = FB_NEW_POOL(pool) CompoundStmtNode(pool);
+ const auto assignStatements = FB_NEW_POOL(pool) CompoundStmtNode(pool);
node->statement = assignStatements;
assignStatements->statements.resize(assignments->statements.getCount());
@@ -6305,11 +6384,9 @@ StmtNode* ModifyNode::internalDsqlPass(DsqlCompilerScratch* dsqlScratch, bool up
dsqlSetParametersName(dsqlScratch, assignStatements, node->dsqlRelation);
- StmtNode* ret = node;
- if (!updateOrInsert)
- ret = dsqlNullifyReturning(dsqlScratch, node, true);
-
- return ret;
+ return dsqlCursorName.hasData() ?
+ dsqlNullifyReturning(dsqlScratch, node) :
+ node;
}
StmtNode* ModifyNode::dsqlPass(DsqlCompilerScratch* dsqlScratch)
@@ -6345,11 +6422,25 @@ string ModifyNode::internalPrint(NodePrinter& printer) const
void ModifyNode::genBlr(DsqlCompilerScratch* dsqlScratch)
{
- RseNode* rse = nodeAs(dsqlRse);
+ if (dsqlReturning && !dsqlScratch->isPsql())
+ {
+ if (dsqlCursorName.isEmpty())
+ dsqlGenReturningLocalTableDecl(dsqlScratch, dsqlReturning, dsqlReturningLocalTableNumber.value);
+ else
+ {
+ dsqlScratch->appendUChar(blr_send);
+ dsqlScratch->appendUChar(dsqlScratch->getStatement()->getReceiveMsg()->msg_number);
+ }
+ }
- const dsql_msg* message = dsqlGenDmlHeader(dsqlScratch, rse);
+ if (dsqlRse)
+ {
+ dsqlScratch->appendUChar(blr_for);
+ dsqlScratch->putBlrMarkers(StmtNode::MARK_FOR_UPDATE);
+ GEN_expr(dsqlScratch, dsqlRse);
+ }
- dsqlScratch->appendUChar(statement2 ? blr_modify2 : blr_modify);
+ dsqlScratch->appendUChar(dsqlReturning ? blr_modify2 : blr_modify);
const dsql_ctx* context;
@@ -6357,6 +6448,7 @@ void ModifyNode::genBlr(DsqlCompilerScratch* dsqlScratch)
context = dsqlContext;
else
{
+ auto rse = nodeAs(dsqlRse);
fb_assert(rse);
context = rse->dsqlStreams->items[0]->dsqlContext;
}
@@ -6370,11 +6462,17 @@ void ModifyNode::genBlr(DsqlCompilerScratch* dsqlScratch)
statement->genBlr(dsqlScratch);
- if (statement2)
- statement2->genBlr(dsqlScratch);
+ if (dsqlReturning)
+ {
+ dsqlGenReturning(dsqlScratch, dsqlReturning, dsqlReturningLocalTableNumber);
- if (message)
- dsqlScratch->appendUChar(blr_end);
+ if (!dsqlScratch->isPsql() &&
+ !(dsqlScratch->flags & DsqlCompilerScratch::FLAG_UPDATE_OR_INSERT) &&
+ dsqlCursorName.isEmpty())
+ {
+ dsqlGenReturningLocalTableCursor(dsqlScratch, dsqlReturning, dsqlReturningLocalTableNumber.value);
+ }
+ }
}
ModifyNode* ModifyNode::pass1(thread_db* tdbb, CompilerScratch* csb)
@@ -6419,6 +6517,15 @@ void ModifyNode::pass1Modify(thread_db* tdbb, CompilerScratch* csb, ModifyNode*
new_tail->csb_flags |= csb_modify;
jrd_rel* const relation = tail->csb_relation;
+
+ //// TODO: LocalTableSourceNode
+ if (!relation)
+ {
+ ERR_post(
+ Arg::Gds(isc_wish_list) <<
+ Arg::Gds(isc_random) << "modify local_table");
+ }
+
view = relation->rel_view_rse ? relation : view;
if (!parent)
@@ -6992,9 +7099,10 @@ DmlNode* StoreNode::parse(thread_db* tdbb, MemoryPool& pool, CompilerScratch* cs
const UCHAR* blrPos = csb->csb_blr_reader.getPos();
- node->relationSource = nodeAs(PAR_parseRecordSource(tdbb, csb));
+ node->target = PAR_parseRecordSource(tdbb, csb);
- if (!node->relationSource)
+ if (!nodeIs(node->target) &&
+ !nodeIs(node->target))
{
csb->csb_blr_reader.setPos(blrPos);
PAR_syntax_error(csb, "relation source");
@@ -7018,13 +7126,16 @@ DmlNode* StoreNode::parse(thread_db* tdbb, MemoryPool& pool, CompilerScratch* cs
StmtNode* StoreNode::internalDsqlPass(DsqlCompilerScratch* dsqlScratch,
bool updateOrInsert, bool& needSavePoint)
{
- thread_db* tdbb = JRD_get_thread_data(); // necessary?
DsqlContextStack::AutoRestore autoContext(*dsqlScratch->context);
dsqlScratch->getStatement()->setType(DsqlCompiledStatement::TYPE_INSERT);
- StoreNode* node = FB_NEW_POOL(dsqlScratch->getPool()) StoreNode(dsqlScratch->getPool());
+ const auto node = FB_NEW_POOL(dsqlScratch->getPool()) StoreNode(dsqlScratch->getPool());
node->overrideClause = overrideClause;
+ node->dsqlReturning = dsqlReturning;
+
+ if (dsqlReturning && !dsqlScratch->isPsql() && (dsqlRse || updateOrInsert))
+ node->dsqlReturningLocalTableNumber = dsqlScratch->localTableNumber++;
// Process SELECT expression, if present
@@ -7035,7 +7146,7 @@ StmtNode* StoreNode::internalDsqlPass(DsqlCompilerScratch* dsqlScratch,
SelectExprNode* selExpr = nodeAs(dsqlRse);
fb_assert(selExpr);
- if (dsqlReturning || statement2)
+ if (dsqlRse && dsqlScratch->isPsql() && dsqlReturning)
selExpr->dsqlFlags |= RecordSourceNode::DFLAG_SINGLETON;
RseNode* rse = PASS1_rse(dsqlScratch, selExpr, false);
@@ -7051,58 +7162,51 @@ StmtNode* StoreNode::internalDsqlPass(DsqlCompilerScratch* dsqlScratch,
// Process relation
- node->dsqlRelation = PASS1_relation(dsqlScratch, dsqlRelation);
- dsql_ctx* context = node->dsqlRelation->dsqlContext;
+ node->target = PASS1_relation(dsqlScratch, target);
+ dsql_ctx* context = node->target->dsqlContext;
dsql_rel* relation = context->ctx_relation;
// If there isn't a field list, generate one
- Array > fields;
+ NestValueArray fields;
if (dsqlFields.hasData())
{
- for (NestConst* i = dsqlFields.begin(); i != dsqlFields.end(); ++i)
+ for (auto& field : dsqlFields)
{
- fields.add(NULL);
- doDsqlPass(dsqlScratch, fields.back(), *i, false);
+ fields.add(nullptr);
+ doDsqlPass(dsqlScratch, fields.back(), field, false);
}
// We do not allow cases like INSERT INTO T (f1, f2, f1)...
dsqlFieldAppearsOnce(fields, "INSERT");
// begin IBO hack
- // 02-May-2004, Nickolay Samofatov. Do not constify ptr further e.g. to
- // const dsql_nod* const* .... etc. It chokes GCC 3.4.0
- NestConst* ptr = fields.begin();
- for (const NestConst* const end = fields.end(); ptr != end; ++ptr)
+ for (const auto& field : fields)
{
- const ValueExprNode* temp2 = *ptr;
+ const dsql_ctx* tmpCtx = nullptr;
+ const TEXT* tmpName = nullptr;
- const dsql_ctx* tmp_ctx = NULL;
- const TEXT* tmp_name = NULL;
- const FieldNode* fieldNode;
- const DerivedFieldNode* derivedField;
-
- if ((fieldNode = nodeAs(temp2)))
+ if (auto fieldNode = nodeAs(field))
{
- tmp_ctx = fieldNode->dsqlContext;
+ tmpCtx = fieldNode->dsqlContext;
if (fieldNode->dsqlField)
- tmp_name = fieldNode->dsqlField->fld_name.c_str();
+ tmpName = fieldNode->dsqlField->fld_name.c_str();
}
- else if ((derivedField = nodeAs(temp2)))
+ else if (auto derivedField = nodeAs(field))
{
- tmp_ctx = derivedField->context;
- tmp_name = derivedField->name.nullStr();
+ tmpCtx = derivedField->context;
+ tmpName = derivedField->name.nullStr();
}
- if (tmp_ctx &&
- ((tmp_ctx->ctx_relation && relation->rel_name != tmp_ctx->ctx_relation->rel_name) ||
- tmp_ctx->ctx_context != context->ctx_context))
+ if (tmpCtx &&
+ ((tmpCtx->ctx_relation && relation->rel_name != tmpCtx->ctx_relation->rel_name) ||
+ tmpCtx->ctx_context != context->ctx_context))
{
- const dsql_rel* bad_rel = tmp_ctx->ctx_relation;
+ const auto badRel = tmpCtx->ctx_relation;
- PASS1_field_unknown((bad_rel ? bad_rel->rel_name.c_str() : NULL),
- tmp_name, dsqlFields[ptr - fields.begin()]);
+ PASS1_field_unknown((badRel ? badRel->rel_name.c_str() : NULL),
+ tmpName, dsqlFields[&field - fields.begin()]);
}
}
// end IBO hack
@@ -7111,13 +7215,13 @@ StmtNode* StoreNode::internalDsqlPass(DsqlCompilerScratch* dsqlScratch,
{
dsqlExplodeFields(relation, fields, false);
- for (NestConst* i = fields.begin(); i != fields.end(); ++i)
- *i = doDsqlPass(dsqlScratch, *i, false);
+ for (auto& field : fields)
+ field = doDsqlPass(dsqlScratch, field, false);
}
// Match field fields and values
- CompoundStmtNode* assignStatements = FB_NEW_POOL(dsqlScratch->getPool()) CompoundStmtNode(dsqlScratch->getPool());
+ const auto assignStatements = FB_NEW_POOL(dsqlScratch->getPool()) CompoundStmtNode(dsqlScratch->getPool());
node->statement = assignStatements;
if (values)
@@ -7129,15 +7233,14 @@ StmtNode* StoreNode::internalDsqlPass(DsqlCompilerScratch* dsqlScratch,
Arg::Gds(isc_dsql_var_count_err));
}
- NestConst* ptr = fields.begin();
- NestConst* ptr2 = values->items.begin();
- for (const NestConst* end = fields.end(); ptr != end; ++ptr, ++ptr2)
+ auto ptr2 = values->items.begin();
+ for (const auto& ptr : fields)
{
- // *ptr2 is NULL for DEFAULT
+ // *ptr2 is nullptr for DEFAULT
if (!*ptr2)
{
- const FieldNode* field = nodeAs(*ptr);
+ const auto field = nodeAs(ptr);
if (field && field->dsqlField)
{
@@ -7149,13 +7252,15 @@ StmtNode* StoreNode::internalDsqlPass(DsqlCompilerScratch* dsqlScratch,
if (*ptr2)
{
- AssignmentNode* temp = FB_NEW_POOL(dsqlScratch->getPool()) AssignmentNode(dsqlScratch->getPool());
+ auto temp = FB_NEW_POOL(dsqlScratch->getPool()) AssignmentNode(dsqlScratch->getPool());
temp->asgnFrom = *ptr2;
- temp->asgnTo = *ptr;
+ temp->asgnTo = ptr;
assignStatements->statements.add(temp);
PASS1_set_parameter_type(dsqlScratch, *ptr2, temp->asgnTo, false);
}
+
+ ++ptr2;
}
}
@@ -7178,13 +7283,7 @@ StmtNode* StoreNode::internalDsqlPass(DsqlCompilerScratch* dsqlScratch,
dsqlScratch->context->push(new_context);
}
- NestConst temp;
- AutoSetRestore > autoNodeFirst(
- dsqlReturning ? &dsqlReturning->first : &temp, dsqlReturning ? dsqlReturning->first : temp);
-
- ReturningProcessor::explode(dsqlScratch, relation, dsqlReturning);
-
- node->statement2 = dsqlProcessReturning(dsqlScratch, dsqlReturning, statement2);
+ node->dsqlReturning = dsqlProcessReturning(dsqlScratch, relation, dsqlReturning, !(dsqlRse || updateOrInsert));
if (updateOrInsert)
{
@@ -7193,15 +7292,11 @@ StmtNode* StoreNode::internalDsqlPass(DsqlCompilerScratch* dsqlScratch,
dsqlScratch->context->pop();
}
- dsqlSetParametersName(dsqlScratch, assignStatements, node->dsqlRelation);
-
- StmtNode* ret = node;
- if (!updateOrInsert)
- ret = dsqlNullifyReturning(dsqlScratch, node, true);
+ dsqlSetParametersName(dsqlScratch, assignStatements, node->target);
dsqlScratch->context->pop();
- return ret;
+ return node;
}
StmtNode* StoreNode::dsqlPass(DsqlCompilerScratch* dsqlScratch)
@@ -7220,7 +7315,7 @@ string StoreNode::internalPrint(NodePrinter& printer) const
{
StmtNode::internalPrint(printer);
- NODE_PRINT(printer, dsqlRelation);
+ NODE_PRINT(printer, target);
NODE_PRINT(printer, dsqlFields);
NODE_PRINT(printer, dsqlValues);
NODE_PRINT(printer, dsqlReturning);
@@ -7229,36 +7324,58 @@ string StoreNode::internalPrint(NodePrinter& printer) const
NODE_PRINT(printer, statement2);
NODE_PRINT(printer, subStore);
//// FIXME-PRINT: NODE_PRINT(printer, validations);
- NODE_PRINT(printer, relationSource);
return "StoreNode";
}
void StoreNode::genBlr(DsqlCompilerScratch* dsqlScratch)
{
- const dsql_msg* message = dsqlGenDmlHeader(dsqlScratch, nodeAs(dsqlRse));
+ if (dsqlReturning && !dsqlScratch->isPsql())
+ {
+ if (dsqlRse)
+ dsqlGenReturningLocalTableDecl(dsqlScratch, dsqlReturning, dsqlReturningLocalTableNumber.value);
+ else if (!(dsqlScratch->flags & DsqlCompilerScratch::FLAG_UPDATE_OR_INSERT))
+ {
+ dsqlScratch->appendUChar(blr_send);
+ dsqlScratch->appendUChar(dsqlScratch->getStatement()->getReceiveMsg()->msg_number);
+ }
+ }
- dsqlScratch->appendUChar(overrideClause.specified ? blr_store3 : (statement2 ? blr_store2 : blr_store));
+ if (dsqlRse)
+ {
+ dsqlScratch->appendUChar(blr_for);
+ dsqlScratch->putBlrMarkers(StmtNode::MARK_FOR_UPDATE);
+ GEN_expr(dsqlScratch, dsqlRse);
+ }
+
+ dsqlScratch->appendUChar(overrideClause.specified ? blr_store3 : (dsqlReturning ? blr_store2 : blr_store));
if (overrideClause.specified)
dsqlScratch->appendUChar(UCHAR(overrideClause.value));
- GEN_expr(dsqlScratch, dsqlRelation);
+ GEN_expr(dsqlScratch, target);
statement->genBlr(dsqlScratch);
- if (statement2)
- statement2->genBlr(dsqlScratch);
+ if (dsqlReturning)
+ {
+ dsqlGenReturning(dsqlScratch, dsqlReturning, dsqlReturningLocalTableNumber);
+
+ if (dsqlReturningLocalTableNumber.isAssigned())
+ {
+ if (dsqlScratch->flags & DsqlCompilerScratch::FLAG_UPDATE_OR_INSERT)
+ dsqlScratch->appendUChar(blr_end); // close blr_if (blr_eql, blr_internal_info)
+
+ dsqlGenReturningLocalTableCursor(dsqlScratch, dsqlReturning, dsqlReturningLocalTableNumber.value);
+ }
+ }
else if (overrideClause.specified)
dsqlScratch->appendUChar(blr_null);
-
- if (message)
- dsqlScratch->appendUChar(blr_end);
}
StoreNode* StoreNode::pass1(thread_db* tdbb, CompilerScratch* csb)
{
- preprocessAssignments(tdbb, csb, relationSource->getStream(), nodeAs(statement), &overrideClause);
+ preprocessAssignments(tdbb, csb, target->getStream(), nodeAs(statement), &overrideClause);
if (pass1Store(tdbb, csb, this))
makeDefaults(tdbb, csb);
@@ -7284,6 +7401,20 @@ bool StoreNode::pass1Store(thread_db* tdbb, CompilerScratch* csb, StoreNode* nod
if (node->subStore)
return false;
+ auto relSource = nodeAs(node->target);
+
+ if (!relSource) // Is this a LocalTableSourceNode?
+ {
+ const StreamType stream = node->target->getStream();
+ const auto tail = &csb->csb_rpt[stream];
+ tail->csb_flags |= csb_store;
+
+ // Apply validation constraints.
+ makeValidation(tdbb, csb, stream, node->validations);
+
+ return false;
+ }
+
jrd_rel* parent = NULL;
jrd_rel* view = NULL;
StreamType parentStream;
@@ -7293,7 +7424,6 @@ bool StoreNode::pass1Store(thread_db* tdbb, CompilerScratch* csb, StoreNode* nod
for (;;)
{
- RelationSourceNode* relSource = node->relationSource;
const StreamType stream = relSource->getStream();
CompilerScratch::csb_repeat* const tail = &csb->csb_rpt[stream];
@@ -7359,7 +7489,7 @@ bool StoreNode::pass1Store(thread_db* tdbb, CompilerScratch* csb, StoreNode* nod
const StreamType newStream = relSource->getStream();
StoreNode* viewNode = FB_NEW_POOL(*tdbb->getDefaultPool()) StoreNode(*tdbb->getDefaultPool());
- viewNode->relationSource = relSource;
+ viewNode->target = relSource;
viewNode->statement = pass1ExpandView(tdbb, csb, stream, newStream, true);
node->subStore = viewNode;
@@ -7372,7 +7502,7 @@ bool StoreNode::pass1Store(thread_db* tdbb, CompilerScratch* csb, StoreNode* nod
// This relation is not actually being updated as this operation
// goes deeper (we have a naturally updatable view).
csb->csb_rpt[stream].csb_flags &= ~csb_view_update;
- node->relationSource = relSource->copy(tdbb, copier);
+ node->target = relSource->copy(tdbb, copier);
}
}
}
@@ -7380,7 +7510,7 @@ bool StoreNode::pass1Store(thread_db* tdbb, CompilerScratch* csb, StoreNode* nod
// Build a default value assignments.
void StoreNode::makeDefaults(thread_db* tdbb, CompilerScratch* csb)
{
- const StreamType stream = relationSource->getStream();
+ const StreamType stream = target->getStream();
jrd_rel* relation = csb->csb_rpt[stream].csb_relation;
vec* vector = relation->rel_fields;
@@ -7462,7 +7592,7 @@ StoreNode* StoreNode::pass2(thread_db* tdbb, CompilerScratch* csb)
// So that the optimizer can use indices for eventually used sub-selects.
StreamList streams;
- streams.add(relationSource->getStream());
+ streams.add(target->getStream());
StreamStateHolder stateHolder(csb, streams);
stateHolder.activate();
@@ -7531,10 +7661,16 @@ const StmtNode* StoreNode::store(thread_db* tdbb, jrd_req* request, WhichTrigger
jrd_tra* transaction = request->req_transaction;
impure_state* impure = request->getImpure(impureOffset);
- const StreamType stream = relationSource->getStream();
+ const StreamType stream = target->getStream();
record_param* rpb = &request->req_rpb[stream];
jrd_rel* relation = rpb->rpb_relation;
+ const auto localTableSource = nodeAs(target);
+ const auto localTable = localTableSource ?
+ request->getStatement()->localTables[localTableSource->tableNumber] :
+ nullptr;
+ const auto localTableImpure = localTable ? localTable->getImpure(tdbb, request) : nullptr;
+
switch (request->req_operation)
{
case jrd_req::req_evaluate:
@@ -7543,7 +7679,8 @@ const StmtNode* StoreNode::store(thread_db* tdbb, jrd_req* request, WhichTrigger
request->req_records_affected.bumpModified(false);
impure->sta_state = 0;
- RLCK_reserve_relation(tdbb, transaction, relation, true);
+ if (relation)
+ RLCK_reserve_relation(tdbb, transaction, relation, true);
break;
case jrd_req::req_return:
@@ -7551,7 +7688,7 @@ const StmtNode* StoreNode::store(thread_db* tdbb, jrd_req* request, WhichTrigger
{
SavepointChangeMarker scMarker(transaction);
- if (relation->rel_pre_store && whichTrig != POST_TRIG)
+ if (relation && relation->rel_pre_store && whichTrig != POST_TRIG)
{
EXE_execute_triggers(tdbb, &relation->rel_pre_store, NULL, rpb,
TRIGGER_INSERT, PRE_TRIG);
@@ -7569,7 +7706,9 @@ const StmtNode* StoreNode::store(thread_db* tdbb, jrd_req* request, WhichTrigger
cleanupRpb(tdbb, rpb);
- if (relation->rel_file)
+ if (localTableSource)
+ localTableImpure->recordBuffer->store(rpb->rpb_record);
+ else if (relation->rel_file)
EXT_store(tdbb, rpb);
else if (relation->isVirtual())
VirtualTable::store(tdbb, rpb);
@@ -7582,13 +7721,14 @@ const StmtNode* StoreNode::store(thread_db* tdbb, jrd_req* request, WhichTrigger
rpb->rpb_number.setValid(true);
- if (relation->rel_post_store && whichTrig != PRE_TRIG)
+ if (relation && relation->rel_post_store && whichTrig != PRE_TRIG)
{
EXE_execute_triggers(tdbb, &relation->rel_post_store, NULL, rpb,
TRIGGER_INSERT, POST_TRIG);
}
- if (!relation->rel_view_rse ||
+ if (!relation ||
+ !relation->rel_view_rse ||
(!subStore && (whichTrig == ALL_TRIGS || whichTrig == POST_TRIG)))
{
request->req_records_inserted++;
@@ -7613,8 +7753,23 @@ const StmtNode* StoreNode::store(thread_db* tdbb, jrd_req* request, WhichTrigger
// exists for the stream and is big enough, and initialize all null flags
// to "missing."
- const Format* format = MET_current(tdbb, relation);
- Record* record = VIO_record(tdbb, rpb, format, tdbb->getDefaultPool());
+ const Format* format = localTableSource ?
+ request->getStatement()->localTables[localTableSource->tableNumber]->format :
+ MET_current(tdbb, relation);
+
+ Record* record;
+
+ if (localTableSource)
+ {
+ record = rpb->rpb_record;
+
+ if (!record)
+ record = rpb->rpb_record = localTableImpure->recordBuffer->getTempRecord();
+
+ record->reset(format);
+ }
+ else
+ record = VIO_record(tdbb, rpb, format, tdbb->getDefaultPool());
rpb->rpb_address = record->getData();
rpb->rpb_length = format->fmt_length;
@@ -7874,14 +8029,11 @@ void SelectNode::genBlr(DsqlCompilerScratch* dsqlScratch)
if (dsqlForUpdate && !rse->dsqlDistinct)
{
RecSourceListNode* streamList = rse->dsqlStreams;
- NestConst* ptr2 = streamList->items.begin();
- for (const NestConst* const end2 = streamList->items.end(); ptr2 != end2; ++ptr2)
+ for (auto& item : streamList->items)
{
- RecordSourceNode* const item = *ptr2;
- RelationSourceNode* relNode;
-
- if (item && (relNode = nodeAs(item)))
+ //// TODO: LocalTableSourceNode
+ if (auto relNode = nodeAs(item))
{
context = relNode->dsqlContext;
const dsql_rel* const relation = context->ctx_relation;
@@ -8769,70 +8921,136 @@ void SetTimeZoneNode::execute(thread_db* tdbb, dsql_req* request, jrd_tra** /*tr
//--------------------
+static RegisterNode regTruncateLocalTableNode({blr_local_table_truncate});
+
+DmlNode* TruncateLocalTableNode::parse(thread_db* tdbb, MemoryPool& pool, CompilerScratch* csb, const UCHAR /*blrOp*/)
+{
+ const auto node = FB_NEW_POOL(pool) TruncateLocalTableNode(pool);
+ node->tableNumber = csb->csb_blr_reader.getWord();
+
+ if (node->tableNumber >= csb->csb_localTables.getCount() || !csb->csb_localTables[node->tableNumber])
+ PAR_error(csb, Arg::Gds(isc_bad_loctab_num) << Arg::Num(node->tableNumber));
+
+ return node;
+}
+
+string TruncateLocalTableNode::internalPrint(NodePrinter& printer) const
+{
+ StmtNode::internalPrint(printer);
+
+ NODE_PRINT(printer, tableNumber);
+
+ return "TruncateLocalTableNode";
+}
+
+void TruncateLocalTableNode::genBlr(DsqlCompilerScratch* dsqlScratch)
+{
+ dsqlScratch->appendUChar(blr_local_table_truncate);
+ dsqlScratch->appendUShort(tableNumber);
+}
+
+TruncateLocalTableNode* TruncateLocalTableNode::copy(thread_db* tdbb, NodeCopier& /*copier*/) const
+{
+ const auto node = FB_NEW_POOL(*tdbb->getDefaultPool()) TruncateLocalTableNode(*tdbb->getDefaultPool());
+ node->tableNumber = tableNumber;
+ return node;
+}
+
+const StmtNode* TruncateLocalTableNode::execute(thread_db* tdbb, jrd_req* request, ExeState* /*exeState*/) const
+{
+ if (request->req_operation == jrd_req::req_evaluate)
+ {
+ const auto localTable = request->getStatement()->localTables[tableNumber];
+
+ if (auto& recordBuffer = localTable->getImpure(tdbb, request, false)->recordBuffer)
+ {
+ delete recordBuffer;
+ recordBuffer = nullptr;
+ }
+
+ request->req_operation = jrd_req::req_return;
+ }
+
+ return parentStmt;
+}
+
+
+//--------------------
+
+
StmtNode* UpdateOrInsertNode::dsqlPass(DsqlCompilerScratch* dsqlScratch)
{
- thread_db* tdbb = JRD_get_thread_data(); // necessary?
- MemoryPool& pool = dsqlScratch->getPool();
+ auto& pool = dsqlScratch->getPool();
if (!dsqlScratch->isPsql())
dsqlScratch->flags |= DsqlCompilerScratch::FLAG_UPDATE_OR_INSERT;
- const MetaName& relation_name = nodeAs(relation)->dsqlName;
- MetaName base_name = relation_name;
+ RelationSourceNode* target = relation;
+
+ const auto querySpec = FB_NEW_POOL(pool) RseNode(pool);
+ querySpec->dsqlExplicitJoin = true;
+ querySpec->dsqlFrom = FB_NEW_POOL(pool) RecSourceListNode(pool, 1);
+ querySpec->dsqlFrom->items[0] = target;
+ querySpec->rse_plan = plan;
+
+ const auto node = FB_NEW_POOL(pool) UpdateOrInsertNode(pool);
+ node->returning = returning;
+
+ const auto& relationName = nodeAs(relation)->dsqlName;
+ MetaName baseName = relationName;
bool needSavePoint;
// Build the INSERT node.
- StoreNode* insert = FB_NEW_POOL(pool) StoreNode(pool);
- insert->dsqlRelation = relation;
- insert->dsqlFields = fields;
- insert->dsqlValues = values;
- insert->dsqlReturning = returning;
- insert->overrideClause = overrideClause;
- insert = nodeAs(insert->internalDsqlPass(dsqlScratch, true, needSavePoint));
- fb_assert(insert);
+ node->storeNode = FB_NEW_POOL(pool) StoreNode(pool);
+ node->storeNode->target = relation;
+ node->storeNode->dsqlFields = fields;
+ node->storeNode->dsqlValues = values;
+ node->storeNode->dsqlReturning = returning;
+ node->storeNode->overrideClause = overrideClause;
+ node->storeNode = nodeAs(node->storeNode->internalDsqlPass(dsqlScratch, true, needSavePoint));
- dsql_ctx* context = insert->dsqlRelation->dsqlContext;
+ auto context = node->storeNode->target->dsqlContext;
DEV_BLKCHK(context, dsql_type_ctx);
- dsql_rel* ctxRelation = context->ctx_relation;
- Array > fieldsCopy = fields;
+ const auto ctxRelation = context->ctx_relation;
+ auto fieldsCopy = fields;
// If a field list isn't present, build one using the same rules of INSERT INTO table VALUES ...
if (fieldsCopy.isEmpty())
dsqlExplodeFields(ctxRelation, fieldsCopy, false);
// Maintain a pair of view's field name / base field name.
- MetaNamePairMap view_fields;
+ MetaNamePairMap viewFields;
if ((ctxRelation->rel_flags & REL_view) && matching.isEmpty())
{
- dsql_rel* base_rel = METD_get_view_base(dsqlScratch->getTransaction(), dsqlScratch,
- relation_name.c_str(), view_fields);
+ auto baseRel = METD_get_view_base(dsqlScratch->getTransaction(), dsqlScratch,
+ relationName.c_str(), viewFields);
// Get the base table name if there is only one.
- if (base_rel)
- base_name = base_rel->rel_name;
+ if (baseRel)
+ baseName = baseRel->rel_name;
else
ERRD_post(Arg::Gds(isc_upd_ins_with_complex_view));
}
- Array > matchingCopy = matching;
- UCHAR equality_type;
+ auto matchingCopy = matching;
+ UCHAR equalityType;
if (matching.hasData())
{
- equality_type = blr_equiv;
+ equalityType = blr_equiv;
dsqlScratch->context->push(context);
++dsqlScratch->scopeLevel;
- Array > matchingFields;
+ Array> matchingFields;
- for (NestConst* i = matchingCopy.begin(); i != matchingCopy.end(); ++i)
+ for (auto& matching : matchingCopy)
{
PsqlChanger changer(dsqlScratch, false);
- matchingFields.add((*i)->dsqlPass(dsqlScratch));
+ matchingFields.add(matching->dsqlPass(dsqlScratch));
}
--dsqlScratch->scopeLevel;
@@ -8842,33 +9060,30 @@ StmtNode* UpdateOrInsertNode::dsqlPass(DsqlCompilerScratch* dsqlScratch)
}
else
{
- equality_type = blr_eql;
+ equalityType = blr_eql;
- METD_get_primary_key(dsqlScratch->getTransaction(), base_name.c_str(), matchingCopy);
+ METD_get_primary_key(dsqlScratch->getTransaction(), baseName.c_str(), matchingCopy);
if (matchingCopy.isEmpty())
- ERRD_post(Arg::Gds(isc_primary_key_required) << base_name);
+ ERRD_post(Arg::Gds(isc_primary_key_required) << baseName);
}
// Build a boolean to use in the UPDATE dsqlScratch.
- BoolExprNode* match = NULL;
+ BoolExprNode* match = nullptr;
USHORT matchCount = 0;
- CompoundStmtNode* list = FB_NEW_POOL(pool) CompoundStmtNode(pool);
+ const auto assignments = FB_NEW_POOL(pool) CompoundStmtNode(pool);
+ auto fieldPtr = fieldsCopy.begin();
+ auto valuePtr = values->items.begin();
- CompoundStmtNode* assignments = FB_NEW_POOL(pool) CompoundStmtNode(pool);
- NestConst* fieldPtr = fieldsCopy.begin();
- NestConst* valuePtr = values->items.begin();
-
- Array >& insertStatements =
- nodeAs(insert->statement)->statements;
+ auto& insertStatements = nodeAs(node->storeNode->statement)->statements;
for (; fieldPtr != fieldsCopy.end(); ++fieldPtr, ++valuePtr)
{
- AssignmentNode* assign = FB_NEW_POOL(pool) AssignmentNode(pool);
+ const auto assign = FB_NEW_POOL(pool) AssignmentNode(pool);
- if (!(assign->asgnFrom = *valuePtr)) // it's NULL for DEFAULT
- assign->asgnFrom = FB_NEW_POOL(pool) DefaultNode(pool, relation_name, (*fieldPtr)->dsqlName);
+ if (!(assign->asgnFrom = *valuePtr)) // it's nullptr for DEFAULT
+ assign->asgnFrom = FB_NEW_POOL(pool) DefaultNode(pool, relationName, (*fieldPtr)->dsqlName);
assign->asgnTo = *fieldPtr;
assignments->statements.add(assign);
@@ -8879,45 +9094,41 @@ StmtNode* UpdateOrInsertNode::dsqlPass(DsqlCompilerScratch* dsqlScratch)
MetaName fieldName;
if ((ctxRelation->rel_flags & REL_view) && matching.isEmpty())
- view_fields.get((*fieldPtr)->dsqlName, fieldName);
+ viewFields.get((*fieldPtr)->dsqlName, fieldName);
else
fieldName = (*fieldPtr)->dsqlName;
if (fieldName.hasData())
{
- for (NestConst* matchingPtr = matchingCopy.begin();
- matchingPtr != matchingCopy.end();
- ++matchingPtr)
+ for (auto& matching : matchingCopy)
{
- const MetaName testField = (*matchingPtr)->dsqlName;
+ const auto testField = matching->dsqlName;
if (testField == fieldName)
{
- if (!*valuePtr) // it's NULL for DEFAULT
+ if (!*valuePtr) // it's nullptr for DEFAULT
ERRD_post(Arg::Gds(isc_upd_ins_cannot_default) << fieldName);
++matchCount;
const FB_SIZE_T fieldPos = fieldPtr - fieldsCopy.begin();
- AssignmentNode* assign2 = nodeAs(insertStatements[fieldPos]);
- NestConst& expr = assign2->asgnFrom;
- ValueExprNode* var = dsqlPassHiddenVariable(dsqlScratch, expr);
+ const auto assign2 = nodeAs(insertStatements[fieldPos]);
+ ValueExprNode* var = dsqlPassHiddenVariable(dsqlScratch, assign2->asgnFrom);
if (var)
{
- AssignmentNode* varAssign = FB_NEW_POOL(pool) AssignmentNode(pool);
- varAssign->asgnFrom = expr;
+ const auto varAssign = FB_NEW_POOL(pool) AssignmentNode(pool);
+ varAssign->asgnFrom = assign2->asgnFrom;
varAssign->asgnTo = var;
+ node->varAssignments.add(varAssign->dsqlPass(dsqlScratch));
- list->statements.add(varAssign);
-
- assign2->asgnFrom = expr = var;
+ assign2->asgnFrom = var;
}
else
var = *valuePtr;
- ComparativeBoolNode* eqlNode = FB_NEW_POOL(pool) ComparativeBoolNode(pool,
- equality_type, *fieldPtr, var);
+ const auto eqlNode = FB_NEW_POOL(pool) ComparativeBoolNode(pool,
+ equalityType, *fieldPtr, var);
match = PASS1_compose(match, eqlNode, blr_and);
}
@@ -8931,55 +9142,28 @@ StmtNode* UpdateOrInsertNode::dsqlPass(DsqlCompilerScratch* dsqlScratch)
if (matching.hasData())
ERRD_post(Arg::Gds(isc_upd_ins_doesnt_match_matching));
else
- ERRD_post(Arg::Gds(isc_upd_ins_doesnt_match_pk) << base_name);
+ ERRD_post(Arg::Gds(isc_upd_ins_doesnt_match_pk) << baseName);
}
// build the UPDATE node
- ModifyNode* update = FB_NEW_POOL(pool) ModifyNode(pool);
- update->dsqlRelation = relation;
- update->statement = assignments;
- update->dsqlBoolean = match;
+ node->modifyNode = FB_NEW_POOL(pool) ModifyNode(pool);
+ node->modifyNode->dsqlRelation = relation;
+ node->modifyNode->statement = assignments;
+ node->modifyNode->dsqlBoolean = match;
+ node->modifyNode->dsqlPlan = plan;
+ node->modifyNode->dsqlOrder = order;
+ node->modifyNode->dsqlRows = rows;
+ node->modifyNode->dsqlReturning = returning;
+ node->modifyNode->dsqlReturningLocalTableNumber = node->storeNode->dsqlReturningLocalTableNumber;
- if (returning)
- {
- update->dsqlRseFlags = RecordSourceNode::DFLAG_SINGLETON;
- update->dsqlReturning = returning;
- update->statement2 = insert->statement2;
- }
-
- update = nodeAs(update->internalDsqlPass(dsqlScratch, true));
- fb_assert(update);
-
- // test if ROW_COUNT = 0
-
- NestConst eqlNode = FB_NEW_POOL(pool) ComparativeBoolNode(pool, blr_eql,
- FB_NEW_POOL(pool) InternalInfoNode(pool, MAKE_const_slong(INFO_TYPE_ROWS_AFFECTED)),
- MAKE_const_slong(0));
-
- const ULONG save_flags = dsqlScratch->flags;
- dsqlScratch->flags |= DsqlCompilerScratch::FLAG_BLOCK; // to compile ROW_COUNT
- eqlNode = doDsqlPass(dsqlScratch, eqlNode);
- dsqlScratch->flags = save_flags;
-
- // If (ROW_COUNT = 0) then INSERT.
- IfNode* ifNode = FB_NEW_POOL(pool) IfNode(pool);
- ifNode->condition = eqlNode;
- ifNode->trueAction = insert;
-
- // Build the temporary vars / UPDATE / IF nodes.
-
- list->statements.add(update);
- list->statements.add(ifNode);
+ node->modifyNode = nodeAs(node->modifyNode->internalDsqlPass(dsqlScratch, true));
+ fb_assert(node->modifyNode);
// If RETURNING is present, type is already DsqlCompiledStatement::TYPE_EXEC_PROCEDURE.
if (!returning)
dsqlScratch->getStatement()->setType(DsqlCompiledStatement::TYPE_INSERT);
- StmtNode* ret = SavepointEncloseNode::make(dsqlScratch->getPool(), dsqlScratch, list);
- if (!needSavePoint || nodeIs(ret))
- return ret;
-
- return FB_NEW_POOL(dsqlScratch->getPool()) SavepointEncloseNode(dsqlScratch->getPool(), ret);
+ return SavepointEncloseNode::make(dsqlScratch->getPool(), dsqlScratch, node);
}
string UpdateOrInsertNode::internalPrint(NodePrinter& printer) const
@@ -8995,8 +9179,40 @@ string UpdateOrInsertNode::internalPrint(NodePrinter& printer) const
return "UpdateOrInsertNode";
}
-void UpdateOrInsertNode::genBlr(DsqlCompilerScratch* /*dsqlScratch*/)
+void UpdateOrInsertNode::genBlr(DsqlCompilerScratch* dsqlScratch)
{
+ dsqlScratch->appendUChar(blr_begin);
+
+ for (auto& varAssign : varAssignments)
+ varAssign->genBlr(dsqlScratch);
+
+ modifyNode->genBlr(dsqlScratch);
+
+ // if ROW_COUNT = 0
+ dsqlScratch->appendUChar(blr_if);
+
+ dsqlScratch->appendUChar(blr_eql);
+
+ dsqlScratch->appendUChar(blr_internal_info);
+
+ dsqlScratch->appendUChar(blr_literal);
+ dsqlScratch->appendUChar(blr_long);
+ dsqlScratch->appendUChar(0);
+ dsqlScratch->appendULong(INFO_TYPE_ROWS_AFFECTED);
+
+ dsqlScratch->appendUChar(blr_literal);
+ dsqlScratch->appendUChar(blr_long);
+ dsqlScratch->appendUChar(0);
+ dsqlScratch->appendULong(0);
+
+ // then INSERT.
+ storeNode->genBlr(dsqlScratch);
+
+ // StoreNode::genBlr closes our blr_if when RETURNING in DSQL is used.
+ if (storeNode->dsqlReturningLocalTableNumber.isUnknown())
+ dsqlScratch->appendUChar(blr_end); // blr_if
+
+ dsqlScratch->appendUChar(blr_end);
}
@@ -9075,161 +9291,203 @@ static dsql_par* dsqlFindRecordVersion(const dsql_req* request, const RelationSo
return candidate;
}
-// Generate DML header for INSERT/UPDATE/DELETE.
-static const dsql_msg* dsqlGenDmlHeader(DsqlCompilerScratch* dsqlScratch, RseNode* dsqlRse)
+// Generate assignment to EOF parameter.
+static void dsqlGenEofAssignment(DsqlCompilerScratch* dsqlScratch, SSHORT value)
{
- const dsql_msg* message = NULL;
- const bool innerSend = !dsqlRse || (dsqlScratch->flags & DsqlCompilerScratch::FLAG_UPDATE_OR_INSERT);
- const bool merge = dsqlScratch->flags & DsqlCompilerScratch::FLAG_MERGE;
+ dsc valueDesc;
+ valueDesc.makeShort(0, &value);
- if (dsqlScratch->getStatement()->getType() == DsqlCompiledStatement::TYPE_EXEC_PROCEDURE &&
- !innerSend && !merge)
+ dsqlScratch->appendUChar(blr_assignment);
+ LiteralNode::genConstant(dsqlScratch, &valueDesc, false);
+ GEN_parameter(dsqlScratch, dsqlScratch->getStatement()->getEof());
+}
+
+static void dsqlGenReturning(DsqlCompilerScratch* dsqlScratch, ReturningClause* returning,
+ Nullable localTableNumber)
+{
+ if (localTableNumber.isAssigned())
{
- if ((message = dsqlScratch->getStatement()->getReceiveMsg()))
+ const USHORT localStoreContext = dsqlScratch->contextNumber++;
+
+ dsqlScratch->appendUChar(blr_store);
+ dsqlScratch->appendUChar(blr_local_table_id);
+ dsqlScratch->appendUShort(localTableNumber.value);
+ dsqlScratch->appendMetaString(""); // alias
+ GEN_stuff_context_number(dsqlScratch, localStoreContext);
+
+ dsqlScratch->appendUChar(blr_begin);
+
+ USHORT fieldNum = 0;
+
+ for (auto& retSource : returning->first->items)
{
- dsqlScratch->appendUChar(blr_send);
- dsqlScratch->appendUChar(message->msg_number);
+ dsqlScratch->appendUChar(blr_assignment);
+ retSource->genBlr(dsqlScratch);
+ dsqlScratch->appendUChar(blr_fid);
+ GEN_stuff_context_number(dsqlScratch, localStoreContext);
+ dsqlScratch->appendUShort(fieldNum++);
}
- }
- if (dsqlRse)
- {
- dsqlScratch->appendUChar(blr_for);
- dsqlScratch->putBlrMarkers(StmtNode::MARK_FOR_UPDATE);
- GEN_expr(dsqlScratch, dsqlRse);
+ dsqlScratch->appendUChar(blr_end);
}
-
- if (dsqlScratch->getStatement()->getType() == DsqlCompiledStatement::TYPE_EXEC_PROCEDURE)
+ else
{
- if ((message = dsqlScratch->getStatement()->getReceiveMsg()))
+ dsqlScratch->appendUChar(blr_begin);
+
+ auto retTargetIt = returning->second->items.begin();
+
+ for (auto& retSource : returning->first->items)
{
- dsqlScratch->appendUChar(blr_begin);
-
- if (innerSend && !merge)
- {
- dsqlScratch->appendUChar(blr_send);
- dsqlScratch->appendUChar(message->msg_number);
- }
+ dsqlScratch->appendUChar(blr_assignment);
+ retSource->genBlr(dsqlScratch);
+ (*retTargetIt++)->genBlr(dsqlScratch);
}
+
+ dsqlScratch->appendUChar(blr_end);
+ }
+}
+
+// Generate BLR for returning's local table cursor.
+static void dsqlGenReturningLocalTableCursor(DsqlCompilerScratch* dsqlScratch, ReturningClause* returning,
+ USHORT localTableNumber)
+{
+ dsqlGenEofAssignment(dsqlScratch, 1);
+
+ const USHORT localForContext = dsqlScratch->contextNumber++;
+
+ dsqlScratch->appendUChar(blr_for);
+ dsqlScratch->appendUChar(blr_rse);
+ dsqlScratch->appendUChar(1);
+ dsqlScratch->appendUChar(blr_local_table_id);
+ dsqlScratch->appendUShort(localTableNumber);
+ dsqlScratch->appendMetaString(""); // alias
+ GEN_stuff_context_number(dsqlScratch, localForContext);
+ dsqlScratch->appendUChar(blr_end);
+
+ dsqlScratch->appendUChar(blr_send);
+ dsqlScratch->appendUChar(dsqlScratch->getStatement()->getReceiveMsg()->msg_number);
+
+ dsqlScratch->appendUChar(blr_begin);
+
+ USHORT fieldNum = 0;
+
+ for (auto& retTarget : returning->second->items)
+ {
+ dsqlScratch->appendUChar(blr_assignment);
+ dsqlScratch->appendUChar(blr_fid);
+ GEN_stuff_context_number(dsqlScratch, localForContext);
+ dsqlScratch->appendUShort(fieldNum++);
+ retTarget->genBlr(dsqlScratch);
}
- return message;
+ dsqlScratch->appendUChar(blr_end);
+
+ dsqlScratch->appendUChar(blr_send);
+ dsqlScratch->appendUChar(dsqlScratch->getStatement()->getReceiveMsg()->msg_number);
+ dsqlGenEofAssignment(dsqlScratch, 0);
+}
+
+// Generate BLR for returning's local table declaration.
+static void dsqlGenReturningLocalTableDecl(DsqlCompilerScratch* dsqlScratch, ReturningClause* returning, USHORT tableNumber)
+{
+ dsqlScratch->appendUChar(blr_dcl_local_table);
+ dsqlScratch->appendUShort(tableNumber);
+ dsqlScratch->appendUChar(blr_dcl_local_table_format);
+ dsqlScratch->appendUShort(returning->first->items.getCount());
+
+ for (auto& retSource : returning->first->items)
+ {
+ dsc fieldDesc;
+ DsqlDescMaker::fromNode(dsqlScratch, &fieldDesc, retSource);
+ GEN_descriptor(dsqlScratch, &fieldDesc, true);
+ }
+
+ dsqlScratch->appendUChar(blr_end);
}
// Get the context of a relation, procedure or derived table.
static dsql_ctx* dsqlGetContext(const RecordSourceNode* node)
{
- const ProcedureSourceNode* procNode;
- const RelationSourceNode* relNode;
- const RseNode* rseNode;
-
- if ((procNode = nodeAs(node)))
+ if (auto procNode = nodeAs(node))
return procNode->dsqlContext;
-
- if ((relNode = nodeAs(node)))
+ else if (auto relNode = nodeAs(node))
return relNode->dsqlContext;
-
- if ((rseNode = nodeAs(node)))
+ //// TODO: LocalTableSourceNode
+ else if (auto rseNode = nodeAs(node))
return rseNode->dsqlContext;
-
- fb_assert(false);
- return NULL;
+ else
+ {
+ fb_assert(false);
+ return nullptr;
+ }
}
// Get the contexts of a relation, procedure, derived table or a list of joins.
static void dsqlGetContexts(DsqlContextStack& contexts, const RecordSourceNode* node)
{
- const ProcedureSourceNode* procNode;
- const RelationSourceNode* relNode;
- const RseNode* rseNode;
-
- if ((procNode = nodeAs(node)))
+ if (auto procNode = nodeAs(node))
contexts.push(procNode->dsqlContext);
- else if ((relNode = nodeAs(node)))
+ else if (auto relNode = nodeAs(node))
contexts.push(relNode->dsqlContext);
- else if ((rseNode = nodeAs(node)))
+ //// TODO: LocalTableSourceNode
+ else if (auto rseNode = nodeAs(node))
{
if (rseNode->dsqlContext) // derived table
contexts.push(rseNode->dsqlContext);
else // joins
{
- NestConst streamList = rseNode->dsqlStreams;
-
- for (NestConst* ptr = streamList->items.begin();
- ptr != streamList->items.end();
- ++ptr)
- {
- dsqlGetContexts(contexts, *ptr);
- }
+ for (auto item : rseNode->dsqlStreams->items)
+ dsqlGetContexts(contexts, item);
}
}
else
- {
fb_assert(false);
- }
}
// Create a compound statement to initialize returning parameters.
-static StmtNode* dsqlNullifyReturning(DsqlCompilerScratch* dsqlScratch, StmtNode* input, bool returnList)
+static StmtNode* dsqlNullifyReturning(DsqlCompilerScratch* dsqlScratch, StmtNode* input)
{
- thread_db* tdbb = JRD_get_thread_data();
- MemoryPool& pool = *tdbb->getDefaultPool();
+ if (dsqlScratch->isPsql())
+ return input;
- StmtNode* returning = NULL;
- EraseNode* eraseNode;
- ModifyNode* modifyNode;
- StoreNode* storeNode;
+ auto& pool = dsqlScratch->getPool();
+ ReturningClause* returning;
- if ((eraseNode = nodeAs(input)))
- returning = eraseNode->statement;
- else if ((modifyNode = nodeAs(input)))
- returning = modifyNode->statement2;
- else if ((storeNode = nodeAs(input)))
- returning = storeNode->statement2;
+ if (auto eraseNode = nodeAs(input))
+ returning = eraseNode->dsqlReturning;
+ else if (auto modifyNode = nodeAs(input))
+ returning = modifyNode->dsqlReturning;
+ else if (auto storeNode = nodeAs(input))
+ returning = storeNode->dsqlReturning;
else
{
fb_assert(false);
+ returning = nullptr;
}
- if (dsqlScratch->isPsql() || !returning)
- return returnList ? input : NULL;
+ if (!returning)
+ return input;
// If this is a RETURNING in DSQL, we need to initialize the output
- // parameters with NULL, to return in case of empty resultset.
- // Note: this may be changed in the future, i.e. return empty resultset
- // instead of NULLs. In this case, I suppose this function could be
- // completely removed.
+ // parameters with NULL, to return in case of empty resultset in some
+ // circumstances (for example WHERE CURRENT OF ... RETURNING).
- // nod_returning was already processed
- CompoundStmtNode* returningStmt = nodeAs(returning);
- fb_assert(returningStmt);
+ const auto nullAssign = FB_NEW_POOL(pool) CompoundStmtNode(pool);
+ auto nullPtr = nullAssign->statements.getBuffer(returning->first->items.getCount());
- CompoundStmtNode* nullAssign = FB_NEW_POOL(pool) CompoundStmtNode(pool);
-
- NestConst* ret_ptr = returningStmt->statements.begin();
- NestConst* null_ptr = nullAssign->statements.getBuffer(returningStmt->statements.getCount());
-
- for (const NestConst* const end = ret_ptr + returningStmt->statements.getCount();
- ret_ptr != end;
- ++ret_ptr, ++null_ptr)
+ for (auto& retPtr : returning->second->items)
{
AssignmentNode* assign = FB_NEW_POOL(pool) AssignmentNode(pool);
assign->asgnFrom = NullNode::instance();
- assign->asgnTo = nodeAs(*ret_ptr)->asgnTo;
- *null_ptr = assign;
+ assign->asgnTo = retPtr;
+ *nullPtr++ = assign;
}
- // If asked for, return a compound statement with the initialization and the
- // original statement.
- if (returnList)
- {
- CompoundStmtNode* list = FB_NEW_POOL(pool) CompoundStmtNode(pool);
- list->statements.add(nullAssign);
- list->statements.add(input);
- return list;
- }
-
- return nullAssign; // return the initialization statement.
+ // Return a compound statement with the initialization and the original statement.
+ const auto list = FB_NEW_POOL(pool) CompoundStmtNode(pool);
+ list->statements.add(nullAssign);
+ list->statements.add(input);
+ return list;
}
// Check that a field is named only once in INSERT or UPDATE statements.
@@ -9310,14 +9568,9 @@ static dsql_ctx* dsqlPassCursorContext(DsqlCompilerScratch* dsqlScratch, const M
NestConst temp = nodeRse->dsqlStreams;
dsql_ctx* context = NULL;
- NestConst* ptr = temp->items.begin();
-
- for (const NestConst* const end = temp->items.end(); ptr != end; ++ptr)
+ for (auto& recSource : temp->items)
{
- RecordSourceNode* r_node = *ptr;
- RelationSourceNode* relNode = nodeAs(r_node);
-
- if (relNode)
+ if (auto relNode = nodeAs(recSource))
{
dsql_ctx* candidate = relNode->dsqlContext;
DEV_BLKCHK(candidate, dsql_type_ctx);
@@ -9336,7 +9589,8 @@ static dsql_ctx* dsqlPassCursorContext(DsqlCompilerScratch* dsqlScratch, const M
context = candidate;
}
}
- else if (nodeAs(r_node))
+ //// TODO: LocalTableSourceNode
+ else if (nodeIs(recSource))
{
// cursor with aggregation is not updatable
ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-510) <<
@@ -9541,37 +9795,39 @@ static USHORT dsqlPassLabel(DsqlCompilerScratch* dsqlScratch, bool breakContinue
return number;
}
-// Compile a RETURNING clause (nod_returning or not).
-static StmtNode* dsqlProcessReturning(DsqlCompilerScratch* dsqlScratch, ReturningClause* input,
- StmtNode* stmt)
+// Compile a RETURNING clause.
+static ReturningClause* dsqlProcessReturning(DsqlCompilerScratch* dsqlScratch, dsql_rel* relation,
+ ReturningClause* input, bool singleton)
{
- thread_db* tdbb = JRD_get_thread_data();
+ if (!input)
+ return nullptr;
- if (stmt)
+ auto& pool = dsqlScratch->getPool();
+
+ auto inputFirst = input->first;
+
+ if (!inputFirst)
{
- const bool isPsql = dsqlScratch->isPsql();
-
- PsqlChanger changer(dsqlScratch, false);
- stmt = stmt->dsqlPass(dsqlScratch);
-
- if (!isPsql)
- dsqlScratch->getStatement()->setType(DsqlCompiledStatement::TYPE_EXEC_PROCEDURE);
-
- return stmt;
+ // Process RETURNING *
+ inputFirst = FB_NEW_POOL(pool) ValueListNode(pool, 0u);
+ dsqlExplodeFields(relation, inputFirst->items, true);
+ }
+ else
+ {
+ // Process alias.* items.
+ inputFirst = PASS1_expand_select_list(dsqlScratch, inputFirst, nullptr);
}
- if (!input)
- return NULL;
+ const auto node = FB_NEW_POOL(pool) ReturningClause(pool);
- MemoryPool& pool = *tdbb->getDefaultPool();
-
- ValueListNode* const source = Node::doDsqlPass(dsqlScratch, input->first, false);
+ node->first = Node::doDsqlPass(dsqlScratch, inputFirst, false);
dsqlScratch->flags |= DsqlCompilerScratch::FLAG_RETURNING_INTO;
- ValueListNode* target = dsqlPassArray(dsqlScratch, input->second);
+ node->second = dsqlPassArray(dsqlScratch,
+ dsqlScratch->returningClause ? dsqlScratch->returningClause->second : input->second);
dsqlScratch->flags &= ~DsqlCompilerScratch::FLAG_RETURNING_INTO;
- if (!dsqlScratch->isPsql() && target)
+ if (!dsqlScratch->isPsql() && input->second)
{
// RETURNING INTO is not allowed syntax for DSQL
ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-104) <<
@@ -9579,10 +9835,10 @@ static StmtNode* dsqlProcessReturning(DsqlCompilerScratch* dsqlScratch, Returnin
Arg::Gds(isc_token_err) <<
Arg::Gds(isc_random) << Arg::Str("INTO"));
}
- else if (dsqlScratch->isPsql() && !target)
+ else if (dsqlScratch->isPsql() && !input->second)
{
// This trick because we don't copy lexer positions when copying lists.
- const ValueListNode* errSrc = input->first;
+ const ValueListNode* errSrc = inputFirst;
// RETURNING without INTO is not allowed for PSQL
ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-104) <<
// Unexpected end of command
@@ -9590,68 +9846,148 @@ static StmtNode* dsqlProcessReturning(DsqlCompilerScratch* dsqlScratch, Returnin
Arg::Num(errSrc->column));
}
- const unsigned int count = source->items.getCount();
+ const unsigned count = node->first->items.getCount();
fb_assert(count);
- CompoundStmtNode* node = FB_NEW_POOL(pool) CompoundStmtNode(pool);
-
- if (target)
+ if (input->second)
{
- // PSQL case
fb_assert(dsqlScratch->isPsql());
- if (count != target->items.getCount())
+ if (count != node->second->items.getCount())
{
// count of column list and value list don't match
ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-804) <<
Arg::Gds(isc_dsql_var_count_err));
}
-
- NestConst* src = source->items.begin();
- NestConst* dst = target->items.begin();
-
- for (const NestConst* const end = source->items.end(); src != end; ++src, ++dst)
- {
- AssignmentNode* temp = FB_NEW_POOL(pool) AssignmentNode(pool);
- temp->asgnFrom = *src;
- temp->asgnTo = *dst;
-
- node->statements.add(temp);
- }
}
else
{
// DSQL case
fb_assert(!dsqlScratch->isPsql());
- NestConst* src = source->items.begin();
-
- for (const NestConst* const end = source->items.end(); src != end; ++src)
+ if (dsqlScratch->returningClause)
{
- dsql_par* parameter = MAKE_parameter(dsqlScratch->getStatement()->getReceiveMsg(),
- true, true, 0, *src);
- parameter->par_node = *src;
- DsqlDescMaker::fromNode(dsqlScratch, ¶meter->par_desc, *src, true);
+ fb_assert(node->first->items.getCount() == dsqlScratch->returningClause->first->items.getCount());
- ParameterNode* paramNode = FB_NEW_POOL(*tdbb->getDefaultPool()) ParameterNode(
- *tdbb->getDefaultPool());
- paramNode->dsqlParameterIndex = parameter->par_index;
- paramNode->dsqlParameter = parameter;
+ auto secondIt = node->second->items.begin();
- AssignmentNode* temp = FB_NEW_POOL(pool) AssignmentNode(pool);
- temp->asgnFrom = *src;
- temp->asgnTo = paramNode;
+ for (auto& src : node->first->items)
+ {
+ auto parameterNode = nodeAs(*secondIt++);
+ fb_assert(parameterNode);
- node->statements.add(temp);
+ // When RETURNING context marked with CTX_null is processed first, parameter
+ // node should be fixed when resolving parameters in the real context.
+ if (nodeIs(parameterNode->dsqlParameter->par_node))
+ {
+ parameterNode->dsqlParameter->par_node = src;
+ DsqlDescMaker::fromNode(dsqlScratch, ¶meterNode->dsqlParameter->par_desc, src, true);
+ }
+ }
+ }
+ else
+ {
+ node->second = FB_NEW_POOL(pool) ValueListNode(pool, count);
+ auto secondIt = node->second->items.begin();
+
+ for (auto& src : node->first->items)
+ {
+ auto parameter = MAKE_parameter(dsqlScratch->getStatement()->getReceiveMsg(),
+ true, true, 0, src);
+ parameter->par_node = src;
+ DsqlDescMaker::fromNode(dsqlScratch, ¶meter->par_desc, src, true);
+
+ auto paramNode = FB_NEW_POOL(pool) ParameterNode(pool);
+ paramNode->dsqlParameterIndex = parameter->par_index;
+ paramNode->dsqlParameter = parameter;
+
+ *secondIt++ = paramNode;
+ }
+
+ dsqlScratch->returningClause = node;
+
+ if (!singleton)
+ {
+ // Set up parameter to handle EOF
+ auto parameter = MAKE_parameter(dsqlScratch->getStatement()->getReceiveMsg(), false, false, 0, nullptr);
+ dsqlScratch->getStatement()->setEof(parameter);
+ parameter->par_desc.dsc_dtype = dtype_short;
+ parameter->par_desc.dsc_scale = 0;
+ parameter->par_desc.dsc_length = sizeof(SSHORT);
+ }
}
}
if (!dsqlScratch->isPsql())
- dsqlScratch->getStatement()->setType(DsqlCompiledStatement::TYPE_EXEC_PROCEDURE);
+ {
+ dsqlScratch->getStatement()->setType(singleton ?
+ DsqlCompiledStatement::TYPE_EXEC_PROCEDURE : DsqlCompiledStatement::TYPE_RETURNING_CURSOR);
+ }
return node;
}
+// Play with contexts for RETURNING purposes.
+// Its assumed that oldContext is already on the stack.
+// Changes oldContext name to "OLD".
+static ReturningClause* dsqlProcessReturning(DsqlCompilerScratch* dsqlScratch, dsql_ctx* oldContext,
+ dsql_ctx* modContext, ReturningClause* input, bool singleton)
+{
+ if (!input)
+ return nullptr;
+
+ AutoSaveRestore autoOldAlias(&oldContext->ctx_alias);
+ AutoSaveRestore autoOldInternalAlias(&oldContext->ctx_internal_alias);
+
+ AutoSetRestore autoFlags(&oldContext->ctx_flags, oldContext->ctx_flags | CTX_system | CTX_returning);
+ AutoSetRestore autoScopeLevel(&dsqlScratch->scopeLevel, dsqlScratch->scopeLevel + 1);
+
+ auto& pool = dsqlScratch->getPool();
+
+ // Clone the modify/old context and push with name "NEW" in a greater scope level.
+
+ const auto newContext = FB_NEW_POOL(pool) dsql_ctx(pool);
+
+ if (modContext)
+ {
+ // Push the modify context in the same scope level.
+ dsqlScratch->context->push(modContext);
+ *newContext = *modContext;
+ newContext->ctx_flags |= CTX_system;
+ }
+ else
+ {
+ // Create the target (= OLD) context and push it on the stack.
+ const auto targetContext = FB_NEW_POOL(pool) dsql_ctx(pool);
+ *targetContext = *oldContext;
+
+ // ASF: dsql_ctx::operator= do not copy ctx_internal_alias.
+ targetContext->ctx_internal_alias = oldContext->ctx_internal_alias;
+
+ targetContext->ctx_flags &= ~CTX_system; // resolve unqualified fields
+ dsqlScratch->context->push(targetContext);
+
+ // This is NEW in the context of a DELETE. Mark it as NULL.
+ *newContext = *oldContext;
+ newContext->ctx_flags |= CTX_null;
+ }
+
+ oldContext->ctx_alias = oldContext->ctx_internal_alias = OLD_CONTEXT_NAME;
+
+ newContext->ctx_alias = newContext->ctx_internal_alias = NEW_CONTEXT_NAME;
+ newContext->ctx_flags |= CTX_returning;
+ newContext->ctx_scope_level = dsqlScratch->scopeLevel;
+ dsqlScratch->context->push(newContext);
+
+ const auto ret = dsqlProcessReturning(dsqlScratch, oldContext->ctx_relation, input, singleton);
+
+ // Restore the context stack.
+ dsqlScratch->context->pop();
+ dsqlScratch->context->pop();
+
+ return ret;
+}
+
// Setup parameter name.
// This function was added as a part of array data type support for InterClient. It is called when
// either "insert" or "update" statements are parsed. If the statements have input parameters, then
@@ -9800,6 +10136,8 @@ static void makeValidation(thread_db* tdbb, CompilerScratch* csb, StreamType str
DEV_BLKCHK(csb, type_csb);
jrd_rel* relation = csb->csb_rpt[stream].csb_relation;
+ if (!relation) //// TODO: LocalTableSourceNode
+ return;
vec* vector = relation->rel_fields;
if (!vector)
@@ -10087,7 +10425,7 @@ static void preprocessAssignments(thread_db* tdbb, CompilerScratch* csb,
jrd_rel* relation = csb->csb_rpt[stream].csb_relation;
- fb_assert(relation);
+ //// TODO: LocalTableSourceNode
if (!relation)
return;
diff --git a/src/dsql/StmtNodes.h b/src/dsql/StmtNodes.h
index cb20d0727e..31b257e72c 100644
--- a/src/dsql/StmtNodes.h
+++ b/src/dsql/StmtNodes.h
@@ -38,13 +38,11 @@ class CompoundStmtNode;
class ExecBlockNode;
class ForNode;
class PlanNode;
+class RecordBuffer;
class RelationSourceNode;
class SelectNode;
class GeneratorItem;
-typedef Firebird::Pair<
- Firebird::NonPooled, NestConst > > ReturningClause;
-
class ExceptionItem : public Firebird::PermanentStorage, public Printable
{
@@ -414,6 +412,45 @@ public:
};
+class DeclareLocalTableNode : public TypedNode
+{
+public:
+ struct Impure
+ {
+ RecordBuffer* recordBuffer;
+ };
+
+public:
+ explicit DeclareLocalTableNode(MemoryPool& pool)
+ : TypedNode(pool)
+ {
+ }
+
+public:
+ static DmlNode* parse(thread_db* tdbb, MemoryPool& pool, CompilerScratch* csb, const UCHAR blrOp);
+
+ Firebird::string internalPrint(NodePrinter& printer) const override;
+ DeclareLocalTableNode* dsqlPass(DsqlCompilerScratch* dsqlScratch) override;
+ void genBlr(DsqlCompilerScratch* dsqlScratch) override;
+ DeclareLocalTableNode* copy(thread_db* tdbb, NodeCopier& copier) const override;
+
+ DeclareLocalTableNode* pass1(thread_db* tdbb, CompilerScratch* csb) override
+ {
+ return this;
+ }
+
+ DeclareLocalTableNode* pass2(thread_db* tdbb, CompilerScratch* csb) override;
+ const StmtNode* execute(thread_db* tdbb, jrd_req* request, ExeState* exeState) const override;
+
+public:
+ Impure* getImpure(thread_db* tdbb, jrd_req* request, bool createWhenDead = true) const;
+
+public:
+ NestConst format;
+ USHORT tableNumber = 0;
+};
+
+
class DeclareSubFuncNode : public TypedNode
{
public:
@@ -1075,38 +1112,42 @@ public:
struct Matched
{
explicit Matched(MemoryPool& pool)
- : assignments(NULL),
- condition(NULL)
+ : processedFields(pool),
+ processedValues(pool)
{
}
NestConst assignments;
NestConst condition;
+
+ NestConst modifyRelation;
+ NestValueArray processedFields;
+ NestValueArray processedValues;
+ NestConst processedReturning;
};
struct NotMatched
{
explicit NotMatched(MemoryPool& pool)
: fields(pool),
- values(NULL),
- condition(NULL)
+ processedFields(pool)
{
}
- Firebird::Array > fields;
+ Firebird::Array> fields;
NestConst values;
NestConst condition;
Nullable overrideClause;
+
+ NestConst storeRelation;
+ NestValueArray processedFields;
+ NestConst processedReturning;
};
explicit MergeNode(MemoryPool& pool)
: TypedNode(pool),
- relation(NULL),
- usingClause(NULL),
- condition(NULL),
whenMatched(pool),
- whenNotMatched(pool),
- returning(NULL)
+ whenNotMatched(pool)
{
}
@@ -1120,7 +1161,12 @@ public:
NestConst condition;
Firebird::ObjectsArray whenMatched;
Firebird::ObjectsArray whenNotMatched;
+ NestConst plan;
+ NestConst order;
NestConst returning;
+
+ NestConst rse;
+ dsql_ctx* targetContext = nullptr;
};
@@ -1163,24 +1209,8 @@ class ModifyNode : public TypedNode
public:
explicit ModifyNode(MemoryPool& pool)
: TypedNode(pool),
- dsqlRelation(NULL),
- dsqlBoolean(NULL),
- dsqlPlan(NULL),
- dsqlOrder(NULL),
- dsqlRows(NULL),
dsqlCursorName(pool),
- dsqlReturning(NULL),
- dsqlRse(NULL),
- dsqlContext(NULL),
- statement(NULL),
- statement2(NULL),
- subMod(NULL),
- validations(pool),
- mapView(NULL),
- orgStream(0),
- newStream(0),
- marks(0),
- dsqlRseFlags(0)
+ validations(pool)
{
}
@@ -1208,17 +1238,18 @@ public:
MetaName dsqlCursorName;
NestConst dsqlReturning;
NestConst dsqlRse;
- dsql_ctx* dsqlContext;
+ dsql_ctx* dsqlContext = nullptr;
NestConst statement;
NestConst statement2;
NestConst subMod;
Firebird::Array validations;
NestConst mapView;
NestConst forNode; // parent implicit cursor, if present
- StreamType orgStream;
- StreamType newStream;
- unsigned marks; // see StmtNode::IUD_MARK_xxx
- USHORT dsqlRseFlags;
+ StreamType orgStream = 0;
+ StreamType newStream = 0;
+ unsigned marks = 0; // see StmtNode::IUD_MARK_xxx
+ USHORT dsqlRseFlags = 0;
+ Nullable dsqlReturningLocalTableNumber;
};
@@ -1281,16 +1312,8 @@ class StoreNode : public TypedNode
public:
explicit StoreNode(MemoryPool& pool)
: TypedNode(pool),
- dsqlRelation(NULL),
dsqlFields(pool),
- dsqlValues(NULL),
- dsqlReturning(NULL),
- dsqlRse(NULL),
- statement(NULL),
- statement2(NULL),
- subStore(NULL),
- validations(pool),
- relationSource(NULL)
+ validations(pool)
{
}
@@ -1311,8 +1334,8 @@ private:
const StmtNode* store(thread_db* tdbb, jrd_req* request, WhichTrigger whichTrig) const;
public:
- NestConst dsqlRelation;
- Firebird::Array > dsqlFields;
+ NestConst target;
+ Firebird::Array> dsqlFields;
NestConst dsqlValues;
NestConst dsqlReturning;
NestConst dsqlRse;
@@ -1320,7 +1343,7 @@ public:
NestConst statement2;
NestConst subStore;
Firebird::Array validations;
- NestConst relationSource;
+ Nullable dsqlReturningLocalTableNumber;
Nullable overrideClause;
};
@@ -1865,16 +1888,52 @@ public:
};
+class TruncateLocalTableNode : public TypedNode
+{
+public:
+ explicit TruncateLocalTableNode(MemoryPool& pool)
+ : TypedNode(pool)
+ {
+ }
+
+public:
+ static DmlNode* parse(thread_db* tdbb, MemoryPool& pool, CompilerScratch* csb, const UCHAR blrOp);
+
+ Firebird::string internalPrint(NodePrinter& printer) const override;
+
+ TruncateLocalTableNode* dsqlPass(DsqlCompilerScratch* dsqlScratch) override
+ {
+ return this;
+ }
+
+ void genBlr(DsqlCompilerScratch* dsqlScratch) override;
+ TruncateLocalTableNode* copy(thread_db* tdbb, NodeCopier& copier) const override;
+
+ TruncateLocalTableNode* pass1(thread_db* tdbb, CompilerScratch* csb) override
+ {
+ return this;
+ }
+
+ TruncateLocalTableNode* pass2(thread_db* tdbb, CompilerScratch* csb) override
+ {
+ return this;
+ }
+
+ const StmtNode* execute(thread_db* tdbb, jrd_req* request, ExeState* exeState) const override;
+
+public:
+ USHORT tableNumber = 0;
+};
+
+
class UpdateOrInsertNode : public TypedNode
{
public:
explicit UpdateOrInsertNode(MemoryPool& pool)
: TypedNode(pool),
- relation(NULL),
fields(pool),
- values(NULL),
matching(pool),
- returning(NULL)
+ varAssignments(pool)
{
}
@@ -1884,11 +1943,17 @@ public:
public:
NestConst relation;
- Firebird::Array > fields;
+ Firebird::Array> fields;
NestConst values;
- Firebird::Array > matching;
+ Firebird::Array> matching;
+ NestConst plan;
+ NestConst order;
+ NestConst rows;
NestConst returning;
Nullable overrideClause;
+ NestConst storeNode;
+ NestConst modifyNode;
+ Firebird::Array> varAssignments;
};
diff --git a/src/dsql/dsql.cpp b/src/dsql/dsql.cpp
index c21e8b1924..cb7bad45e6 100644
--- a/src/dsql/dsql.cpp
+++ b/src/dsql/dsql.cpp
@@ -98,6 +98,7 @@ static inline bool reqTypeWithCursor(DsqlCompiledStatement::Type type)
case DsqlCompiledStatement::TYPE_SELECT:
case DsqlCompiledStatement::TYPE_SELECT_BLOCK:
case DsqlCompiledStatement::TYPE_SELECT_UPD:
+ case DsqlCompiledStatement::TYPE_RETURNING_CURSOR:
return true;
}
@@ -2062,6 +2063,7 @@ static void sql_info(thread_db* tdbb,
case DsqlCompiledStatement::TYPE_SELECT:
case DsqlCompiledStatement::TYPE_SELECT_UPD:
case DsqlCompiledStatement::TYPE_SELECT_BLOCK:
+ case DsqlCompiledStatement::TYPE_RETURNING_CURSOR:
value |= IStatement::FLAG_HAS_CURSOR;
break;
}
@@ -2075,6 +2077,7 @@ static void sql_info(thread_db* tdbb,
switch (statement->getType())
{
case DsqlCompiledStatement::TYPE_SELECT:
+ case DsqlCompiledStatement::TYPE_RETURNING_CURSOR:
number = isc_info_sql_stmt_select;
break;
case DsqlCompiledStatement::TYPE_SELECT_UPD:
diff --git a/src/dsql/dsql.h b/src/dsql/dsql.h
index 8c797edd0f..b3e1e7d6ff 100644
--- a/src/dsql/dsql.h
+++ b/src/dsql/dsql.h
@@ -461,7 +461,8 @@ public:
TYPE_SELECT, TYPE_SELECT_UPD, TYPE_INSERT, TYPE_DELETE, TYPE_UPDATE, TYPE_UPDATE_CURSOR,
TYPE_DELETE_CURSOR, TYPE_COMMIT, TYPE_ROLLBACK, TYPE_CREATE_DB, TYPE_DDL, TYPE_START_TRANS,
TYPE_EXEC_PROCEDURE, TYPE_COMMIT_RETAIN, TYPE_ROLLBACK_RETAIN, TYPE_SET_GENERATOR,
- TYPE_SAVEPOINT, TYPE_EXEC_BLOCK, TYPE_SELECT_BLOCK, TYPE_SESSION_MANAGEMENT
+ TYPE_SAVEPOINT, TYPE_EXEC_BLOCK, TYPE_SELECT_BLOCK, TYPE_SESSION_MANAGEMENT,
+ TYPE_RETURNING_CURSOR
};
// Statement flags.
diff --git a/src/dsql/gen.cpp b/src/dsql/gen.cpp
index 89cb813d5e..2bcb12ef50 100644
--- a/src/dsql/gen.cpp
+++ b/src/dsql/gen.cpp
@@ -267,6 +267,8 @@ void GEN_request(DsqlCompilerScratch* scratch, DmlNode* node)
case DsqlCompiledStatement::TYPE_SELECT_BLOCK:
node->genBlr(scratch);
break;
+
+ ///case DsqlCompiledStatement::TYPE_RETURNING_CURSOR:
default:
{
dsql_msg* message = statement->getSendMsg();
@@ -660,3 +662,13 @@ void GEN_stuff_context(DsqlCompilerScratch* dsqlScratch, const dsql_ctx* context
dsqlScratch->appendUChar(context->ctx_recursive);
}
}
+
+
+// Write a context number into the BLR buffer. Check for possible overflow.
+void GEN_stuff_context_number(DsqlCompilerScratch* dsqlScratch, USHORT contextNumber)
+{
+ if (contextNumber > MAX_UCHAR)
+ ERRD_post(Arg::Gds(isc_too_many_contexts));
+
+ dsqlScratch->appendUChar(contextNumber);
+}
diff --git a/src/dsql/gen_proto.h b/src/dsql/gen_proto.h
index 62c83e06d0..55cf5fb4fe 100644
--- a/src/dsql/gen_proto.h
+++ b/src/dsql/gen_proto.h
@@ -39,5 +39,6 @@ void GEN_request(Jrd::DsqlCompilerScratch*, Jrd::DmlNode*);
void GEN_rse(Jrd::DsqlCompilerScratch*, Jrd::RseNode*);
void GEN_sort(Jrd::DsqlCompilerScratch*, UCHAR, Jrd::ValueListNode*);
void GEN_stuff_context(Jrd::DsqlCompilerScratch*, const Jrd::dsql_ctx*);
+void GEN_stuff_context_number(Jrd::DsqlCompilerScratch*, USHORT);
#endif // DSQL_GEN_PROTO_H
diff --git a/src/dsql/parse.y b/src/dsql/parse.y
index 1c0bd576f0..2ec1a73ce5 100644
--- a/src/dsql/parse.y
+++ b/src/dsql/parse.y
@@ -6524,7 +6524,7 @@ insert_start
: INSERT INTO simple_table_name
{
StoreNode* node = newNode