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(); - node->dsqlRelation = $3; + node->target = $3; $$ = node; } ; @@ -6559,10 +6559,13 @@ merge node->usingClause = $5; node->condition = $7; } - merge_when_clause($8) returning_clause + merge_when_clause($8) + plan_clause order_clause_opt returning_clause { MergeNode* node = $$ = $8; - node->returning = $10; + node->plan = $10; + node->order = $11; + node->returning = $12; } ; @@ -6705,12 +6708,16 @@ update_or_insert node->relation = $5; } ins_column_parens_opt(NOTRIAL(&$6->fields)) override_opt VALUES '(' value_or_default_list ')' - update_or_insert_matching_opt(NOTRIAL(&$6->matching)) returning_clause + update_or_insert_matching_opt(NOTRIAL(&$6->matching)) + plan_clause order_clause_opt rows_clause_optional returning_clause { UpdateOrInsertNode* node = $$ = $6; node->overrideClause = $8; node->values = $11; - node->returning = $14; + node->plan = $14; + node->order = $15; + node->rows = $16; + node->returning = $17; } ; diff --git a/src/dsql/pass1.cpp b/src/dsql/pass1.cpp index eb01c9a419..442c5a8802 100644 --- a/src/dsql/pass1.cpp +++ b/src/dsql/pass1.cpp @@ -362,6 +362,7 @@ dsql_ctx* PASS1_make_context(DsqlCompilerScratch* dsqlScratch, RecordSourceNode* relation_name = procNode->dsqlName.identifier; else if ((relNode = nodeAs(relationNode))) relation_name = relNode->dsqlName; + //// TODO: LocalTableSourceNode else if ((selNode = nodeAs(relationNode))) relation_name = selNode->alias.c_str(); @@ -683,12 +684,9 @@ void PASS1_check_unique_fields_names(StrArray& names, const CompoundStmtNode* fi { const char* name = NULL; - const DeclareVariableNode* varNode; - const DeclareCursorNode* cursorNode; - - if ((varNode = nodeAs(*ptr))) + if (auto varNode = nodeAs(*ptr)) name = varNode->dsqlDef->name.c_str(); - else if ((cursorNode = nodeAs(*ptr))) + else if (auto cursorNode = nodeAs(*ptr)) name = cursorNode->dsqlName.c_str(); else if (nodeAs(*ptr) || nodeAs(*ptr)) continue; @@ -1326,12 +1324,10 @@ ValueListNode* PASS1_expand_select_list(DsqlCompilerScratch* dsqlScratch, ValueL void PASS1_expand_select_node(DsqlCompilerScratch* dsqlScratch, ExprNode* node, ValueListNode* list, bool hide_using) { - RseNode* rseNode; - ProcedureSourceNode* procNode; - RelationSourceNode* relNode; FieldNode* fieldNode; - if ((rseNode = nodeAs(node))) + //// TODO: LocalTableSourceNode + if (auto rseNode = nodeAs(node)) { ValueListNode* sub_items = rseNode->dsqlSelectList; @@ -1375,7 +1371,7 @@ void PASS1_expand_select_node(DsqlCompilerScratch* dsqlScratch, ExprNode* node, } } } - else if ((procNode = nodeAs(node))) + else if (auto procNode = nodeAs(node)) { dsql_ctx* context = procNode->dsqlContext; @@ -1395,7 +1391,7 @@ void PASS1_expand_select_node(DsqlCompilerScratch* dsqlScratch, ExprNode* node, } } } - else if ((relNode = nodeAs(node))) + else if (auto relNode = nodeAs(node)) { dsql_ctx* context = relNode->dsqlContext; @@ -1730,6 +1726,7 @@ RecordSourceNode* PASS1_relation(DsqlCompilerScratch* dsqlScratch, RecordSourceN procNode->dsqlContext = context; return procNode; } + //// TODO: LocalTableSourceNode fb_assert(false); return NULL; @@ -2928,31 +2925,26 @@ static void remap_streams_to_parent_context(ExprNode* input, dsql_ctx* parent_co { DEV_BLKCHK(parent_context, dsql_type_ctx); - RecSourceListNode* listNode; - ProcedureSourceNode* procNode; - RelationSourceNode* relNode; - RseNode* rseNode; - UnionSourceNode* unionNode; - - if ((listNode = nodeAs(input))) + if (auto listNode = nodeAs(input)) { NestConst* ptr = listNode->items.begin(); for (const NestConst* const end = listNode->items.end(); ptr != end; ++ptr) remap_streams_to_parent_context(*ptr, parent_context); } - else if ((procNode = nodeAs(input))) + else if (auto procNode = nodeAs(input)) { DEV_BLKCHK(procNode->dsqlContext, dsql_type_ctx); procNode->dsqlContext->ctx_parent = parent_context; } - else if ((relNode = nodeAs(input))) + else if (auto relNode = nodeAs(input)) { DEV_BLKCHK(relNode->dsqlContext, dsql_type_ctx); relNode->dsqlContext->ctx_parent = parent_context; } - else if ((rseNode = nodeAs(input))) + //// TODO: LocalTableSourceNode + else if (auto rseNode = nodeAs(input)) remap_streams_to_parent_context(rseNode->dsqlStreams, parent_context); - else if ((unionNode = nodeAs(input))) + else if (auto unionNode = nodeAs(input)) remap_streams_to_parent_context(unionNode->dsqlClauses, parent_context); else fb_assert(nodeAs(input)); diff --git a/src/include/firebird/impl/blr.h b/src/include/firebird/impl/blr.h index 2479990c42..045fec9347 100644 --- a/src/include/firebird/impl/blr.h +++ b/src/include/firebird/impl/blr.h @@ -451,4 +451,12 @@ #define blr_marks (unsigned char) 217 // mark some blr code with specific flags +#define blr_dcl_local_table (unsigned char) 218 + +// subcodes of blr_dcl_local_table +#define blr_dcl_local_table_format (unsigned char) 1 + +#define blr_local_table_truncate (unsigned char) 219 +#define blr_local_table_id (unsigned char) 220 + #endif // FIREBIRD_IMPL_BLR_H diff --git a/src/include/firebird/impl/consts_pub.h b/src/include/firebird/impl/consts_pub.h index 75c5089755..2802d23c08 100644 --- a/src/include/firebird/impl/consts_pub.h +++ b/src/include/firebird/impl/consts_pub.h @@ -769,6 +769,7 @@ #define fb_dbg_subproc 5 #define fb_dbg_subfunc 6 #define fb_dbg_map_curname 7 +//// TODO: LocalTable name. // sub code for fb_dbg_map_argument #define fb_dbg_arg_input 0 diff --git a/src/include/gen/Firebird.pas b/src/include/gen/Firebird.pas index f98fbb9295..8feec76ad5 100644 --- a/src/include/gen/Firebird.pas +++ b/src/include/gen/Firebird.pas @@ -5135,6 +5135,7 @@ const isc_tom_key_length = 335545274; isc_inf_invalid_args = 335545275; isc_sysf_invalid_null_empty = 335545276; + isc_bad_loctab_num = 335545276; isc_gfix_db_name = 335740929; isc_gfix_invalid_sw = 335740930; isc_gfix_incmp_sw = 335740932; diff --git a/src/include/gen/codetext.h b/src/include/gen/codetext.h index e122c9399e..e2d3b0a9e4 100644 --- a/src/include/gen/codetext.h +++ b/src/include/gen/codetext.h @@ -978,6 +978,7 @@ static const struct { {"tom_key_length", 335545274}, {"inf_invalid_args", 335545275}, {"sysf_invalid_null_empty", 335545276}, + {"bad_loctab_num", 335545277}, {"gfix_db_name", 335740929}, {"gfix_invalid_sw", 335740930}, {"gfix_incmp_sw", 335740932}, diff --git a/src/include/gen/iberror.h b/src/include/gen/iberror.h index dea0e45fc7..a77c2a5079 100644 --- a/src/include/gen/iberror.h +++ b/src/include/gen/iberror.h @@ -1012,6 +1012,7 @@ const ISC_STATUS isc_block_size = 335545273L; const ISC_STATUS isc_tom_key_length = 335545274L; const ISC_STATUS isc_inf_invalid_args = 335545275L; const ISC_STATUS isc_sysf_invalid_null_empty = 335545276L; +const ISC_STATUS isc_bad_loctab_num = 335545277L; const ISC_STATUS isc_gfix_db_name = 335740929L; const ISC_STATUS isc_gfix_invalid_sw = 335740930L; const ISC_STATUS isc_gfix_incmp_sw = 335740932L; @@ -1504,7 +1505,7 @@ const ISC_STATUS isc_trace_switch_user_only = 337182757L; const ISC_STATUS isc_trace_switch_param_miss = 337182758L; const ISC_STATUS isc_trace_param_act_notcompat = 337182759L; const ISC_STATUS isc_trace_mandatory_switch_miss = 337182760L; -const ISC_STATUS isc_err_max = 1448; +const ISC_STATUS isc_err_max = 1449; #else /* c definitions */ @@ -2486,6 +2487,7 @@ const ISC_STATUS isc_err_max = 1448; #define isc_tom_key_length 335545274L #define isc_inf_invalid_args 335545275L #define isc_sysf_invalid_null_empty 335545276L +#define isc_bad_loctab_num 335545277L #define isc_gfix_db_name 335740929L #define isc_gfix_invalid_sw 335740930L #define isc_gfix_incmp_sw 335740932L @@ -2978,7 +2980,7 @@ const ISC_STATUS isc_err_max = 1448; #define isc_trace_switch_param_miss 337182758L #define isc_trace_param_act_notcompat 337182759L #define isc_trace_mandatory_switch_miss 337182760L -#define isc_err_max 1448 +#define isc_err_max 1449 #endif diff --git a/src/include/gen/msgs.h b/src/include/gen/msgs.h index 77d85f4b3d..6fe682d9fc 100644 --- a/src/include/gen/msgs.h +++ b/src/include/gen/msgs.h @@ -981,6 +981,7 @@ Data source : @4"}, /* eds_statement */ {335545274, "Invalid key length @1, need >@2"}, /* tom_key_length */ {335545275, "Invalid information arguments"}, /* inf_invalid_args */ {335545276, "Empty or NULL parameter @1 is not accepted"}, /* sysf_invalid_null_empty */ + {335545277, "Undefined local table number @1"}, /* bad_loctab_num */ {335740929, "data base file name (@1) already given"}, /* gfix_db_name */ {335740930, "invalid switch @1"}, /* gfix_invalid_sw */ {335740932, "incompatible switch combination"}, /* gfix_incmp_sw */ diff --git a/src/include/gen/sql_code.h b/src/include/gen/sql_code.h index 50cf84e837..7236ebf25f 100644 --- a/src/include/gen/sql_code.h +++ b/src/include/gen/sql_code.h @@ -977,6 +977,7 @@ static const struct { {335545274, -901}, /* 954 tom_key_length */ {335545275, -901}, /* 955 inf_invalid_args */ {335545276, -901}, /* 956 sysf_invalid_null_empty */ + {335545277, -901}, /* 957 bad_loctab_num */ {335740929, -901}, /* 1 gfix_db_name */ {335740930, -901}, /* 2 gfix_invalid_sw */ {335740932, -901}, /* 4 gfix_incmp_sw */ diff --git a/src/include/gen/sql_state.h b/src/include/gen/sql_state.h index 35152cda10..62f2765caf 100644 --- a/src/include/gen/sql_state.h +++ b/src/include/gen/sql_state.h @@ -977,6 +977,7 @@ static const struct { {335545274, "22023"}, // 954 tom_key_length {335545275, "HY000"}, // 955 inf_invalid_args {335545276, "22023"}, // 956 sysf_invalid_null_empty + {335545277, "HY000"}, // 957 bad_loctab_num {335740929, "00000"}, // 1 gfix_db_name {335740930, "00000"}, // 2 gfix_invalid_sw {335740932, "00000"}, // 4 gfix_incmp_sw diff --git a/src/jrd/JrdStatement.cpp b/src/jrd/JrdStatement.cpp index 9887c66c6c..2af48dcb89 100644 --- a/src/jrd/JrdStatement.cpp +++ b/src/jrd/JrdStatement.cpp @@ -71,6 +71,7 @@ JrdStatement::JrdStatement(thread_db* tdbb, MemoryPool* p, CompilerScratch* csb) parentStatement(NULL), subStatements(*p), fors(*p), + localTables(*p), invariants(*p), blr(*p), mapFieldInfo(*p), @@ -155,6 +156,8 @@ JrdStatement::JrdStatement(thread_db* tdbb, MemoryPool* p, CompilerScratch* csb) // make a vector of all used RSEs fors = csb->csb_fors; + localTables = csb->csb_localTables; + // make a vector of all invariant-type nodes, so that we will // be able to easily reinitialize them when we restart the request invariants.join(csb->csb_invariants); diff --git a/src/jrd/JrdStatement.h b/src/jrd/JrdStatement.h index f20c9b9f14..ed67f30dfa 100644 --- a/src/jrd/JrdStatement.h +++ b/src/jrd/JrdStatement.h @@ -81,6 +81,7 @@ public: Firebird::Array subStatements; // Array of subroutines' statements const StmtNode* topNode; // top of execution tree Firebird::Array fors; // record sources + Firebird::Array localTables; // local tables Firebird::Array invariants; // pointer to nodes invariant offsets Firebird::RefStrPtr sqlText; // SQL text (encoded in the metadata charset) Firebird::Array blr; // BLR for non-SQL query diff --git a/src/jrd/Optimizer.cpp b/src/jrd/Optimizer.cpp index 99f895ab6e..c280374979 100644 --- a/src/jrd/Optimizer.cpp +++ b/src/jrd/Optimizer.cpp @@ -187,6 +187,7 @@ namespace Jrd alias = csb_tail->csb_relation->rel_name.c_str(); else if (csb_tail->csb_procedure) alias = csb_tail->csb_procedure->getName().toString(); + //// TODO: LocalTableSourceNode else fb_assert(false); diff --git a/src/jrd/RecordSourceNodes.cpp b/src/jrd/RecordSourceNodes.cpp index 6f288304ff..6b911d360f 100644 --- a/src/jrd/RecordSourceNodes.cpp +++ b/src/jrd/RecordSourceNodes.cpp @@ -256,6 +256,7 @@ PlanNode* PlanNode::dsqlPass(DsqlCompilerScratch* dsqlScratch) procNode->dsqlContext = context; node->dsqlRecordSourceNode = procNode; } + //// TODO: LocalTableSourceNode // ASF: I think it's a error to let node->dsqlRecordSourceNode be NULL here, but it happens // at least since v2.5. See gen.cpp/gen_plan for more information. @@ -453,6 +454,144 @@ RecSourceListNode* RecSourceListNode::dsqlPass(DsqlCompilerScratch* dsqlScratch) //-------------------- +// Parse a local table reference. +LocalTableSourceNode* LocalTableSourceNode::parse(thread_db* tdbb, CompilerScratch* csb, + const SSHORT blrOp, bool parseContext) +{ + const USHORT tableNumber = csb->csb_blr_reader.getWord(); + + if (tableNumber >= csb->csb_localTables.getCount()) + PAR_error(csb, Arg::Gds(isc_bad_loctab_num) << Arg::Num(tableNumber)); + + // Make a relation reference node + + const auto node = FB_NEW_POOL(*tdbb->getDefaultPool()) LocalTableSourceNode( + *tdbb->getDefaultPool()); + + node->tableNumber = tableNumber; + + AutoPtr aliasString(FB_NEW_POOL(csb->csb_pool) string(csb->csb_pool)); + csb->csb_blr_reader.getString(*aliasString); + + if (aliasString->hasData()) + node->alias = *aliasString; + else + aliasString.reset(); + + // generate a stream for the relation reference, assuming it is a real reference + + if (parseContext) + { + node->stream = PAR_context(csb, &node->context); + + if (tableNumber >= csb->csb_localTables.getCount() || !csb->csb_localTables[tableNumber]) + PAR_error(csb, Arg::Gds(isc_bad_loctab_num) << Arg::Num(tableNumber)); + + csb->csb_rpt[node->stream].csb_format = csb->csb_localTables[tableNumber]->format; + csb->csb_rpt[node->stream].csb_alias = aliasString.release(); + } + + return node; +} + +string LocalTableSourceNode::internalPrint(NodePrinter& printer) const +{ + RecordSourceNode::internalPrint(printer); + + NODE_PRINT(printer, alias); + NODE_PRINT(printer, tableNumber); + NODE_PRINT(printer, context); + + return "LocalTableSourceNode"; +} + +RecordSourceNode* LocalTableSourceNode::dsqlPass(DsqlCompilerScratch* dsqlScratch) +{ + return dsqlPassRelProc(dsqlScratch, this); +} + +bool LocalTableSourceNode::dsqlMatch(DsqlCompilerScratch* /*dsqlScratch*/, const ExprNode* other, + bool /*ignoreMapCast*/) const +{ + const auto o = nodeAs(other); + return o && dsqlContext == o->dsqlContext; +} + +void LocalTableSourceNode::genBlr(DsqlCompilerScratch* dsqlScratch) +{ + dsqlScratch->appendUChar(blr_local_table_id); + dsqlScratch->appendUShort(tableNumber); + dsqlScratch->appendMetaString(alias.c_str()); // dsqlContext->ctx_alias? + + GEN_stuff_context(dsqlScratch, dsqlContext); +} + +LocalTableSourceNode* LocalTableSourceNode::copy(thread_db* tdbb, NodeCopier& copier) const +{ + if (!copier.remap) + BUGCHECK(221); // msg 221 (CMP) copy: cannot remap + + const auto newSource = FB_NEW_POOL(*tdbb->getDefaultPool()) LocalTableSourceNode( + *tdbb->getDefaultPool()); + + newSource->stream = copier.csb->nextStream(); + copier.remap[stream] = newSource->stream; + + newSource->context = context; + + if (tableNumber >= copier.csb->csb_localTables.getCount() || !copier.csb->csb_localTables[tableNumber]) + ERR_post(Arg::Gds(isc_bad_loctab_num) << Arg::Num(tableNumber)); + + const auto element = CMP_csb_element(copier.csb, newSource->stream); + + element->csb_format = copier.csb->csb_localTables[tableNumber]->format; + element->csb_view_stream = copier.remap[0]; + + if (alias.hasData()) + { + element->csb_alias = FB_NEW_POOL(*tdbb->getDefaultPool()) + string(*tdbb->getDefaultPool(), alias); + } + + return newSource; +} + +void LocalTableSourceNode::pass1Source(thread_db* tdbb, CompilerScratch* csb, RseNode* /*rse*/, + BoolExprNode** /*boolean*/, RecordSourceNodeStack& stack) +{ + fb_assert(!csb->csb_view); // local tables cannot be inside a view + + stack.push(this); // Assume that the source will be used. Push it on the final stream stack. + + pass1(tdbb, csb); +} + +void LocalTableSourceNode::pass2Rse(thread_db* tdbb, CompilerScratch* csb) +{ + csb->csb_rpt[stream].activate(); + + pass2(tdbb, csb); +} + +RecordSource* LocalTableSourceNode::compile(thread_db* tdbb, OptimizerBlk* opt, bool /*innerSubStream*/) +{ + opt->beds.add(stream); + opt->localStreams.add(stream); + + const auto csb = opt->opt_csb; + + if (tableNumber >= opt->opt_csb->csb_localTables.getCount() || !opt->opt_csb->csb_localTables[tableNumber]) + ERR_post(Arg::Gds(isc_bad_loctab_num) << Arg::Num(tableNumber)); + + auto localTable = csb->csb_localTables[tableNumber]; + + return FB_NEW_POOL(*tdbb->getDefaultPool()) LocalTableStream(csb, stream, localTable); +} + + +//-------------------- + + // Parse a relation reference. RelationSourceNode* RelationSourceNode::parse(thread_db* tdbb, CompilerScratch* csb, const SSHORT blrOp, bool parseContext) @@ -3345,26 +3484,24 @@ RseNode* SelectExprNode::dsqlPass(DsqlCompilerScratch* dsqlScratch) static RecordSourceNode* dsqlPassRelProc(DsqlCompilerScratch* dsqlScratch, RecordSourceNode* source) { - ProcedureSourceNode* procNode = nodeAs(source); - RelationSourceNode* relNode = nodeAs(source); - - fb_assert(procNode || relNode); - bool couldBeCte = true; MetaName relName; string relAlias; - if (procNode) + if (auto procNode = nodeAs(source)) { relName = procNode->dsqlName.identifier; relAlias = procNode->alias; couldBeCte = !procNode->sourceList && procNode->dsqlName.package.isEmpty(); } - else if (relNode) + else if (auto relNode = nodeAs(source)) { relName = relNode->dsqlName; relAlias = relNode->alias; } + //// TODO: LocalTableSourceNode + else + fb_assert(false); if (relAlias.isEmpty()) relAlias = relName.c_str(); diff --git a/src/jrd/RecordSourceNodes.h b/src/jrd/RecordSourceNodes.h index dabbb9fcfa..a7643545c9 100644 --- a/src/jrd/RecordSourceNodes.h +++ b/src/jrd/RecordSourceNodes.h @@ -283,6 +283,75 @@ public: }; +class LocalTableSourceNode : public TypedNode +{ +public: + explicit LocalTableSourceNode(MemoryPool& pool, const MetaName& aDsqlName = NULL) + : TypedNode(pool), + alias(pool) + { + } + + static LocalTableSourceNode* parse(thread_db* tdbb, CompilerScratch* csb, const SSHORT blrOp, + bool parseContext); + + Firebird::string internalPrint(NodePrinter& printer) const override; + RecordSourceNode* dsqlPass(DsqlCompilerScratch* dsqlScratch) override; + + bool dsqlSubSelectFinder(SubSelectFinder& /*visitor*/) override + { + return false; + } + + bool dsqlMatch(DsqlCompilerScratch* dsqlScratch, const ExprNode* other, bool ignoreMapCast) const override; + void genBlr(DsqlCompilerScratch* dsqlScratch) override; + + LocalTableSourceNode* copy(thread_db* tdbb, NodeCopier& copier) const override; + + RecordSourceNode* pass1(thread_db* tdbb, CompilerScratch* csb) override + { + return this; + } + + void pass1Source(thread_db* tdbb, CompilerScratch* csb, RseNode* rse, + BoolExprNode** boolean, RecordSourceNodeStack& stack) override; + + RecordSourceNode* pass2(thread_db* /*tdbb*/, CompilerScratch* /*csb*/) override + { + return this; + } + + void pass2Rse(thread_db* tdbb, CompilerScratch* csb) override; + + bool containsStream(StreamType checkStream) const override + { + return checkStream == stream; + } + + void computeDbKeyStreams(StreamList& streamList) const override + { + streamList.add(getStream()); + } + + bool computable(CompilerScratch* /*csb*/, StreamType /*stream*/, + bool /*allowOnlyCurrentStream*/, ValueExprNode* /*value*/) override + { + return true; + } + + void findDependentFromStreams(const OptimizerRetrieval* /*optRet*/, + SortedStreamList* /*streamList*/) override + { + } + + RecordSource* compile(thread_db* tdbb, OptimizerBlk* opt, bool innerSubStream) override; + +public: + Firebird::string alias; + USHORT tableNumber = 0; + SSHORT context = 0; // user-specified context number for the local table reference +}; + class RelationSourceNode : public TypedNode { public: diff --git a/src/jrd/blp.h b/src/jrd/blp.h index e8b17425b0..b6ed5eacee 100644 --- a/src/jrd/blp.h +++ b/src/jrd/blp.h @@ -51,7 +51,7 @@ static const struct {"field", field}, {"fid", parm}, {"parameter", parm}, - {"variable", variable}, + {"variable", one_word}, {"average", two}, {"count", one}, {"maximum", two}, @@ -213,7 +213,7 @@ static const struct {"strlen", strlength}, {"trim", trim}, // New BLR in FB2.1 - {"init_variable", variable}, + {"init_variable", one_word}, {"recurse", union_ops}, {"sys_function", function}, // New BLR in FB2.5 @@ -248,5 +248,9 @@ static const struct {"local_time", byte_line}, {"at", verb_byte_verb}, {"marks", marks}, + // New BLR in FB5.0 + {"dcl_local_table", dcl_local_table}, + {"local_table_truncate", one_word}, + {"local_table_id", local_table}, {0, 0} }; diff --git a/src/jrd/cmp.cpp b/src/jrd/cmp.cpp index cd58d0ee65..c891e8a54f 100644 --- a/src/jrd/cmp.cpp +++ b/src/jrd/cmp.cpp @@ -257,17 +257,12 @@ const Format* CMP_format(thread_db* tdbb, CompilerScratch* csb, StreamType strea if (!tail->csb_format) { if (tail->csb_relation) - { tail->csb_format = MET_current(tdbb, tail->csb_relation); - } else if (tail->csb_procedure) - { tail->csb_format = tail->csb_procedure->prc_record_format; - } + //// TODO: LocalTableSourceNode else - { IBERROR(222); // msg 222 bad blr - invalid stream - } } fb_assert(tail->csb_format); diff --git a/src/jrd/exe.cpp b/src/jrd/exe.cpp index 41bc9d0989..9e2c9ea3ba 100644 --- a/src/jrd/exe.cpp +++ b/src/jrd/exe.cpp @@ -982,6 +982,17 @@ void EXE_unwind(thread_db* tdbb, jrd_req* request) tdbb->setRequest(old_request); tdbb->setTransaction(old_transaction); } + + for (auto localTable : statement->localTables) + { + if (!localTable) + continue; + + auto impure = localTable->getImpure(tdbb, request, false); + delete impure->recordBuffer; + impure->recordBuffer = nullptr; + } + release_blobs(tdbb, request); } diff --git a/src/jrd/exe.h b/src/jrd/exe.h index 26adac8ab1..5260f9168d 100644 --- a/src/jrd/exe.h +++ b/src/jrd/exe.h @@ -448,7 +448,7 @@ public: csb_resources(p), csb_dependencies(p), csb_fors(p), - csb_cursors(p), + csb_localTables(p), csb_invariants(p), csb_current_nodes(p), csb_current_for_nodes(p), @@ -511,7 +511,7 @@ public: ResourceList csb_resources; // Resources (relations and indexes) Firebird::Array csb_dependencies; // objects that this statement depends upon Firebird::Array csb_fors; // record sources - Firebird::Array csb_cursors; // named cursors + Firebird::Array csb_localTables; // local tables Firebird::Array csb_invariants; // stack of pointer to nodes invariant offsets Firebird::Array csb_current_nodes; // RseNode's and other invariant // candidates within whose scope we are diff --git a/src/jrd/extds/InternalDS.cpp b/src/jrd/extds/InternalDS.cpp index 53f975e6ba..c978bd82e7 100644 --- a/src/jrd/extds/InternalDS.cpp +++ b/src/jrd/extds/InternalDS.cpp @@ -548,6 +548,7 @@ void InternalStatement::doPrepare(thread_db* tdbb, const string& sql) switch (statement->getType()) { case DsqlCompiledStatement::TYPE_SELECT: + case DsqlCompiledStatement::TYPE_RETURNING_CURSOR: case DsqlCompiledStatement::TYPE_SELECT_UPD: case DsqlCompiledStatement::TYPE_SELECT_BLOCK: m_stmt_selectable = true; diff --git a/src/jrd/opt.cpp b/src/jrd/opt.cpp index ca193d59ba..72acb6439d 100644 --- a/src/jrd/opt.cpp +++ b/src/jrd/opt.cpp @@ -1281,7 +1281,7 @@ static void check_sorts(CompilerScratch* csb, RseNode* rse) { RecordSourceNode* subNode = new_rse->rse_relations[i]; - if (nodeIs(subNode) && + if ((nodeIs(subNode) || nodeIs(subNode)) && subNode->getStream() == sort_stream && new_rse != rse) { @@ -1289,7 +1289,6 @@ static void check_sorts(CompilerScratch* csb, RseNode* rse) sortStreamFound = true; break; } - } if (sortStreamFound) @@ -1309,7 +1308,7 @@ static void check_sorts(CompilerScratch* csb, RseNode* rse) } else { - if (nodeIs(node) && + if ((nodeIs(node) || nodeIs(node)) && node->getStream() == sort_stream && new_rse && new_rse != rse) { @@ -2090,7 +2089,7 @@ static RecordSource* gen_outer(thread_db* tdbb, OptimizerBlk* opt, RseNode* rse, { const RecordSourceNode* node = rse->rse_relations[i]; - if (nodeIs(node)) + if (nodeIs(node) || nodeIs(node)) { stream_ptr[i]->stream_rsb = NULL; stream_ptr[i]->stream_num = node->getStream(); diff --git a/src/jrd/par.cpp b/src/jrd/par.cpp index c0b47d81d9..a66fe8e252 100644 --- a/src/jrd/par.cpp +++ b/src/jrd/par.cpp @@ -180,7 +180,8 @@ namespace if (relationNode->getKind() != DmlNode::KIND_REC_SOURCE) PAR_syntax_error(csb, "TABLE"); - RelationSourceNode* relationSource = nodeAs( + //// TODO: LocalTableSourceNode + auto relationSource = nodeAs( static_cast(relationNode)); if (!relationSource) @@ -1011,6 +1012,7 @@ static PlanNode* par_plan(thread_db* tdbb, CompilerScratch* csb) // in which case the base relation (and alias) must be specified USHORT n = (unsigned int) csb->csb_blr_reader.getByte(); + //// TODO: LocalTableSourceNode (blr_local_table_id) if (n != blr_relation && n != blr_relation2 && n != blr_rid && n != blr_rid2) PAR_syntax_error(csb, "TABLE"); @@ -1018,6 +1020,7 @@ static PlanNode* par_plan(thread_db* tdbb, CompilerScratch* csb) // this would add a new context; while this is a reference to // an existing context + //// TODO: LocalTableSourceNode plan->relationNode = RelationSourceNode::parse(tdbb, csb, n, false); jrd_rel* relation = plan->relationNode->relation; @@ -1297,6 +1300,9 @@ RecordSourceNode* PAR_parseRecordSource(thread_db* tdbb, CompilerScratch* csb) case blr_rid2: return RelationSourceNode::parse(tdbb, csb, blrOp, true); + case blr_local_table_id: + return LocalTableSourceNode::parse(tdbb, csb, blrOp, true); + case blr_union: case blr_recurse: return UnionSourceNode::parse(tdbb, csb, blrOp); @@ -1622,6 +1628,7 @@ DmlNode* PAR_parse_node(thread_db* tdbb, CompilerScratch* csb) case blr_rid: case blr_relation2: case blr_rid2: + case blr_local_table_id: case blr_union: case blr_recurse: case blr_window: diff --git a/src/jrd/recsrc/Cursor.h b/src/jrd/recsrc/Cursor.h index 4d521731e9..6883a610ca 100644 --- a/src/jrd/recsrc/Cursor.h +++ b/src/jrd/recsrc/Cursor.h @@ -59,7 +59,6 @@ namespace Jrd bool irsb_active; State irsb_state; FB_UINT64 irsb_position; - RecordBuffer* irsb_buffer; }; public: diff --git a/src/jrd/recsrc/LocalTableStream.cpp b/src/jrd/recsrc/LocalTableStream.cpp new file mode 100644 index 0000000000..727a3934d9 --- /dev/null +++ b/src/jrd/recsrc/LocalTableStream.cpp @@ -0,0 +1,128 @@ +/* + * The contents of this file are subject to the Initial + * Developer's Public License Version 1.0 (the "License"); + * you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * http://www.ibphoenix.com/main.nfs?a=ibphoenix&page=ibp_idpl. + * + * Software distributed under the License is distributed AS IS, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. + * See the License for the specific language governing rights + * and limitations under the License. + * + * The Original Code was created by Adriano dos Santos Fernandes + * for the Firebird Open Source RDBMS project. + * + * Copyright (c) 2021 Adriano dos Santos Fernandes + * and all contributors signed below. + * + * All Rights Reserved. + * Contributor(s): ______________________________________. + */ + +#include "firebird.h" +#include "../jrd/align.h" +#include "../jrd/jrd.h" +#include "../jrd/req.h" +#include "../dsql/StmtNodes.h" + +#include "RecordSource.h" + +using namespace Firebird; +using namespace Jrd; + +// ------------------------ +// Data access: local table +// ------------------------ + +LocalTableStream::LocalTableStream(CompilerScratch* csb, StreamType stream, const DeclareLocalTableNode* table) + : RecordStream(csb, stream), + m_table(table) +{ + fb_assert(m_table); + + m_impure = csb->allocImpure(); +} + +void LocalTableStream::open(thread_db* tdbb) const +{ + const auto request = tdbb->getRequest(); + const auto impure = request->getImpure(m_impure); + + impure->irsb_flags = irsb_open; + + const auto rpb = &request->req_rpb[m_stream]; + rpb->getWindow(tdbb).win_flags = 0; + + rpb->rpb_number.setValue(BOF_NUMBER); +} + +void LocalTableStream::close(thread_db* tdbb) const +{ + const auto request = tdbb->getRequest(); + + invalidateRecords(request); + + const auto impure = request->getImpure(m_impure); + + if (impure->irsb_flags & irsb_open) + impure->irsb_flags &= ~irsb_open; +} + +bool LocalTableStream::getRecord(thread_db* tdbb) const +{ + JRD_reschedule(tdbb); + + const auto request = tdbb->getRequest(); + const auto rpb = &request->req_rpb[m_stream]; + const auto impure = request->getImpure(m_impure); + + if (!(impure->irsb_flags & irsb_open)) + { + rpb->rpb_number.setValid(false); + return false; + } + + if (!rpb->rpb_record) + rpb->rpb_record = FB_NEW_POOL(*tdbb->getDefaultPool()) Record(*tdbb->getDefaultPool(), m_format); + + rpb->rpb_number.increment(); + + if (!m_table->getImpure(tdbb, request)->recordBuffer->fetch(rpb->rpb_number.getValue(), rpb->rpb_record)) + { + rpb->rpb_number.setValid(false); + return false; + } + + return true; +} + +bool LocalTableStream::refetchRecord(thread_db* tdbb) const +{ + return true; +} + +bool LocalTableStream::lockRecord(thread_db* tdbb) const +{ + status_exception::raise(Arg::Gds(isc_record_lock_not_supp)); + return false; // compiler silencer +} + +void LocalTableStream::print(thread_db* tdbb, string& plan, bool detailed, unsigned level) const +{ + //// TODO: Use Local Table name/alias. + + if (detailed) + plan += printIndent(++level) + "Local Table Full Scan"; + else + { + if (!level) + plan += "("; + + plan += "Local_Table"; + plan += " NATURAL"; + + if (!level) + plan += ")"; + } +} diff --git a/src/jrd/recsrc/RecordSource.h b/src/jrd/recsrc/RecordSource.h index 7c4897822b..210028bf92 100644 --- a/src/jrd/recsrc/RecordSource.h +++ b/src/jrd/recsrc/RecordSource.h @@ -39,6 +39,7 @@ namespace Jrd class jrd_prc; class AggNode; class BoolExprNode; + class DeclareLocalTableNode; class Sort; class CompilerScratch; class RecordBuffer; @@ -1145,6 +1146,24 @@ namespace Jrd Firebird::Array m_keys; }; + class LocalTableStream : public RecordStream + { + public: + LocalTableStream(CompilerScratch* csb, StreamType stream, const DeclareLocalTableNode* table); + + void open(thread_db* tdbb) const override; + void close(thread_db* tdbb) const override; + + bool getRecord(thread_db* tdbb) const override; + bool refetchRecord(thread_db* tdbb) const override; + bool lockRecord(thread_db* tdbb) const override; + + void print(thread_db* tdbb, Firebird::string& plan, bool detailed, unsigned level) const override; + + private: + const DeclareLocalTableNode* m_table; + }; + class Union : public RecordStream { struct Impure : public RecordSource::Impure diff --git a/src/msgs/facilities2.sql b/src/msgs/facilities2.sql index d6da6ca0c9..72c47a94ef 100644 --- a/src/msgs/facilities2.sql +++ b/src/msgs/facilities2.sql @@ -1,7 +1,7 @@ /* MAX_NUMBER is the next number to be used, always one more than the highest message number. */ set bulk_insert INSERT INTO FACILITIES (LAST_CHANGE, FACILITY, FAC_CODE, MAX_NUMBER) VALUES (?, ?, ?, ?); -- -('2021-05-11 14:10:00', 'JRD', 0, 957) +('2021-08-25 07:16:00', 'JRD', 0, 958) --('2015-03-17 18:33:00', 'QLI', 1, 533) ('2018-03-17 12:00:00', 'GFIX', 3, 136) ('1996-11-07 13:39:40', 'GPRE', 4, 1) diff --git a/src/msgs/messages2.sql b/src/msgs/messages2.sql index 3ed42bab2c..d4f145d39f 100644 --- a/src/msgs/messages2.sql +++ b/src/msgs/messages2.sql @@ -1064,6 +1064,7 @@ Data source : @4', NULL, NULL) ('tom_key_length', NULL, 'SysFunction.cpp', NULL, 0, 954, NULL, 'Invalid key length @1, need >@2', NULL, NULL); ('inf_invalid_args', NULL, NULL, NULL, 0, 955, NULL, 'Invalid information arguments', NULL, NULL); ('sysf_invalid_null_empty', NULL, 'SysFunction.cpp', NULL, 0, 956, NULL, 'Empty or NULL parameter @1 is not accepted', NULL, NULL); +('bad_loctab_num', NULL, NULL, NULL, 0, 957, NULL, 'Undefined local table number @1', NULL, NULL); -- GFIX ('gfix_db_name', 'ALICE_gfix', 'alice.c', NULL, 3, 1, NULL, 'data base file name (@1) already given', NULL, NULL); ('gfix_invalid_sw', 'ALICE_gfix', 'alice.c', NULL, 3, 2, NULL, 'invalid switch @1', NULL, NULL); diff --git a/src/msgs/system_errors2.sql b/src/msgs/system_errors2.sql index 502d7759b9..58f0a734fe 100644 --- a/src/msgs/system_errors2.sql +++ b/src/msgs/system_errors2.sql @@ -963,6 +963,7 @@ set bulk_insert INSERT INTO SYSTEM_ERRORS (SQL_CODE, SQL_CLASS, SQL_SUBCLASS, FA (-901, '22', '023', 0, 954, 'tom_key_length', NULL, NULL) (-901, 'HY', '000', 0, 955, 'inf_invalid_args', NULL, 'WARNING') (-901, '22', '023', 0, 956, 'sysf_invalid_null_empty', NULL, NULL) +(-901, 'HY', '000', 0, 957, 'bad_loctab_num', NULL, NULL) -- GFIX (-901, '00', '000', 3, 1, 'gfix_db_name', NULL, NULL) (-901, '00', '000', 3, 2, 'gfix_invalid_sw', NULL, NULL) diff --git a/src/yvalve/gds.cpp b/src/yvalve/gds.cpp index f39c056cc3..d714b0639c 100644 --- a/src/yvalve/gds.cpp +++ b/src/yvalve/gds.cpp @@ -263,6 +263,7 @@ const int op_subproc_decl = 27; const int op_subfunc_decl = 28; const int op_window_win = 29; const int op_erase = 30; // special due to optional blr_marks after blr_erase +const int op_dcl_local_table = 31; static const UCHAR // generic print formats @@ -302,7 +303,7 @@ static const UCHAR gen_id[] = { op_byte, op_literal, op_line, op_verb, 0}, gen_id2[] = { op_byte, op_literal, op_line, 0}, declare[] = { op_word, op_dtype, op_line, 0}, - variable[] = { op_word, op_line, 0}, + one_word[] = { op_word, op_line, 0}, indx[] = { op_line, op_verb, op_indent, op_byte, op_line, op_args, 0}, seek[] = { op_line, op_verb, op_verb, 0}, join[] = { op_join, op_line, 0}, @@ -332,6 +333,7 @@ static const UCHAR user_savepoint[] = { op_byte, op_byte, op_literal, op_line, 0}, exec_into[] = { op_word, op_line, op_indent, op_exec_into, 0}, dcl_cursor[] = { op_word, op_line, op_verb, op_indent, op_word, op_line, op_args, 0}, + dcl_local_table[] = { op_dcl_local_table, 0 }, cursor_stmt[] = { op_cursor_stmt, 0 }, strlength[] = { op_byte, op_line, op_verb, 0}, trim[] = { op_byte, op_byte_opt_verb, op_verb, 0}, @@ -350,7 +352,8 @@ static const UCHAR op_line, op_indent, op_byte, op_literal, op_pad, op_line, 0}, store3[] = { op_line, op_byte, op_line, op_verb, op_verb, op_verb, 0}, marks[] = { op_byte, op_literal, op_line, op_verb, 0}, - erase[] = { op_erase, 0}; + erase[] = { op_erase, 0}, + local_table[] = { op_word, op_byte, op_literal, op_byte, op_line, 0}; #include "../jrd/blp.h" @@ -3779,6 +3782,56 @@ static void blr_print_verb(gds_ctl* control, SSHORT level) offset = blr_print_line(control, offset); break; + case op_dcl_local_table: + { + offset = blr_print_line(control, offset); + blr_indent(control, level); + blr_print_word(control); + offset = blr_print_line(control, offset); + + static const char* subCodes[] = + { + nullptr, + "format" + }; + + while ((blr_operator = control->ctl_blr_reader.getByte()) != blr_end) + { + blr_indent(control, level); + + if (blr_operator == 0 || blr_operator >= FB_NELEM(subCodes)) + blr_error(control, "*** invalid blr_dcl_local_table sub code ***"); + + blr_format(control, "blr_dcl_local_table_%s, ", subCodes[blr_operator]); + + switch (blr_operator) + { + case blr_dcl_local_table_format: + n = blr_print_word(control); + offset = blr_print_line(control, offset); + ++level; + + while (--n >= 0) + { + blr_indent(control, level); + blr_print_dtype(control); + offset = blr_print_line(control, offset); + } + + --level; + break; + + default: + fb_assert(false); + } + } + + // print blr_end + control->ctl_blr_reader.seekBackward(1); + blr_print_verb(control, level); + break; + } + default: fb_assert(false); break;