diff --git a/doc/README.using_statement.md b/doc/README.using_statement.md new file mode 100644 index 00000000000..37d73a3cf66 --- /dev/null +++ b/doc/README.using_statement.md @@ -0,0 +1,110 @@ +# The `USING` Statement + +## Overview + +The `USING` statement is a new DSQL extension designed to bridge the gap between standard DSQL statements and the +powerful but verbose `EXECUTE BLOCK`. + +When adapting a standard DSQL command to use `EXECUTE BLOCK` (for instance, to utilize sub-routines or reuse a single +input parameter in multiple places), the developer is currently forced to explicitly declare all input parameters and, +more tediously, all output fields. + +The `USING` statement simplifies this workflow. It provides the ability to declare parameters and sub-routines while +allowing the engine to infer outputs automatically from the contained SQL command. + +## Syntax + +```sql +USING [ ( ) ] + [ ] +DO +``` + +**Note:** At least one of `` or `` must be present. A `USING ... DO` statement +without parameters and without subroutines is invalid. + +### Components + +* **``**: A strictly typed list of parameters. These can be bound to values using the `?` + placeholder. +* **``**: Standard PSQL function or procedure declarations. +* **``**: The DSQL statement to execute. Supported statements include: + * `SELECT` + * `INSERT` (with or without `RETURNING`) + * `UPDATE` (with or without `RETURNING`) + * `UPDATE OR INSERT` (with or without `RETURNING`) + * `DELETE` (with or without `RETURNING`) + * `MERGE` (with or without `RETURNING`) + * `CALL` + * `EXECUTE PROCEDURE` + +## Key Features + +1. **Inferred Outputs**: Unlike `EXECUTE BLOCK`, you do not need to explicitly declare a `RETURNS (...)` clause. The + output columns are automatically inferred from the `` in the `DO` clause. +2. **Statement Type Transparency**: The API returns the statement type of the inner `` (e.g., if the + inner command is a `SELECT`, the client sees a `SELECT` statement). +3. **Parameter Reuse**: Input parameters declared in the `USING` clause can be used multiple times within the script + using named references (e.g., `:p1`), while only requiring a single bind from the client application. +4. **Mixed Parameter Binding**: You can mix declared parameters (bound via `?` in the declaration) and direct + positional parameters (using `?` inside the `DO` command). + +## Examples + +### 1. Basic Parameter Reuse and Subroutines + +This example demonstrates declaring typed parameters, defining local functions/procedures, and using them in a query. + +```sql +using (p1 integer = ?, p2 integer = ?) + -- Declare a local function + declare function subfunc (i1 integer) returns integer + as + begin + return i1; + end + + -- Declare a local procedure + declare procedure subproc (i1 integer) returns (o1 integer) + as + begin + o1 = i1; + suspend; + end +do +-- The main query +select subfunc(:p1) + o1 + from subproc(:p2 + ?) +``` + +In this scenario: +1. The client binds values to `p1` and `p2`. +2. The client binds a third value to the `?` inside the `DO` clause. +3. The result set structure is inferred from the `SELECT` statement. + +### 2. Simplifying Parameter Reuse + +Without `USING`, inserting the same bind value into multiple columns requires sending the data twice. + +**Standard DSQL:** +```sql +insert into generic_table (col_a, col_b) values (?, ?); +-- Client must bind: [100, 100] +``` + +**With `USING`:** +```sql +using (val integer = ?) +do insert into generic_table (col_a, col_b) values (:val, :val); +-- Client binds: [100] +``` + +## Comparison + +| Feature | Standard DSQL | `EXECUTE BLOCK` | `USING` | +| :---------------------- | :------------------------------ | :-------------------------------------------- | :-------------------------------------------- | +| **Subroutines** | No | Yes | Yes | +| **Input Declarations** | Implicit (Positional) | Explicit | Hybrid (implicit and explicit) | +| **Output Declarations** | Inferred | Explicit (`RETURNS`) | Inferred | +| **Verbosity** | Low | High | Medium | +| **Use Case** | Simple queries | Complex logic, loops, no result set inference | Reusing params, subroutines, standard queries | diff --git a/src/dsql/DdlNodes.epp b/src/dsql/DdlNodes.epp index 9097c30658e..f6fdbbe5c31 100644 --- a/src/dsql/DdlNodes.epp +++ b/src/dsql/DdlNodes.epp @@ -2465,7 +2465,10 @@ void CreateAlterFunctionNode::compile(thread_db* /*tdbb*/, DsqlCompilerScratch* dsqlScratch->setPsql(true); if (localDeclList) + { + localDeclList = localDeclList->dsqlPass(dsqlScratch); localDeclList->genBlr(dsqlScratch); + } dsqlScratch->loopLevel = 0; dsqlScratch->cursorNumber = 0; @@ -3381,7 +3384,10 @@ void CreateAlterProcedureNode::compile(thread_db* /*tdbb*/, DsqlCompilerScratch* dsqlScratch->setPsql(true); if (localDeclList) + { + localDeclList = localDeclList->dsqlPass(dsqlScratch); localDeclList->genBlr(dsqlScratch); + } dsqlScratch->loopLevel = 0; dsqlScratch->cursorNumber = 0; @@ -3936,7 +3942,10 @@ void CreateAlterTriggerNode::compile(thread_db* /*tdbb*/, DsqlCompilerScratch* d dsqlScratch->setPsql(true); if (localDeclList) + { + localDeclList = localDeclList->dsqlPass(dsqlScratch); localDeclList->genBlr(dsqlScratch); + } dsqlScratch->loopLevel = 0; dsqlScratch->cursorNumber = 0; diff --git a/src/dsql/DsqlCompilerScratch.h b/src/dsql/DsqlCompilerScratch.h index 68854524f46..93e03b93f52 100644 --- a/src/dsql/DsqlCompilerScratch.h +++ b/src/dsql/DsqlCompilerScratch.h @@ -76,6 +76,7 @@ class DsqlCompilerScratch : public BlrDebugWriter static const unsigned FLAG_FETCH = 0x4000; static const unsigned FLAG_VIEW_WITH_CHECK = 0x8000; static const unsigned FLAG_EXEC_BLOCK = 0x010000; + static const unsigned FLAG_USING_STATEMENT = 0x020000; static const unsigned MAX_NESTING = 512; diff --git a/src/dsql/ExprNodes.cpp b/src/dsql/ExprNodes.cpp index 7d74b33b55f..da2e423be07 100644 --- a/src/dsql/ExprNodes.cpp +++ b/src/dsql/ExprNodes.cpp @@ -14176,9 +14176,10 @@ void VariableNode::genBlr(DsqlCompilerScratch* dsqlScratch) { auto varScratch = outerDecl ? dsqlScratch->mainScratch : dsqlScratch; - const bool execBlock = (varScratch->flags & DsqlCompilerScratch::FLAG_EXEC_BLOCK); + const bool execBlockOrUsing = (varScratch->flags & + (DsqlCompilerScratch::FLAG_EXEC_BLOCK | DsqlCompilerScratch::FLAG_USING_STATEMENT)); - if (dsqlVar->type == dsql_var::TYPE_INPUT && !execBlock) + if (dsqlVar->type == dsql_var::TYPE_INPUT && !execBlockOrUsing) { dsqlScratch->appendUChar(blr_parameter2); @@ -14196,7 +14197,7 @@ void VariableNode::genBlr(DsqlCompilerScratch* dsqlScratch) } else { - // If this is an EXECUTE BLOCK input parameter, use the internal variable. + // If this is an EXECUTE BLOCK or USING input parameter, use the internal variable. dsqlScratch->appendUChar(blr_variable); if (outerDecl) diff --git a/src/dsql/Nodes.h b/src/dsql/Nodes.h index f769b4b33ed..831598b1ad3 100644 --- a/src/dsql/Nodes.h +++ b/src/dsql/Nodes.h @@ -1490,6 +1490,7 @@ class StmtNode : public DmlNode TYPE_SUSPEND, TYPE_TRUNCATE_LOCAL_TABLE, TYPE_UPDATE_OR_INSERT, + TYPE_USING, TYPE_EXT_INIT_PARAMETERS, TYPE_EXT_TRIGGER diff --git a/src/dsql/StmtNodes.cpp b/src/dsql/StmtNodes.cpp index 7f084ad3d05..0d7447bdf1e 100644 --- a/src/dsql/StmtNodes.cpp +++ b/src/dsql/StmtNodes.cpp @@ -110,6 +110,7 @@ static void preModifyEraseTriggers(thread_db* tdbb, TrigVector** trigs, static void preprocessAssignments(thread_db* tdbb, CompilerScratch* csb, StreamType stream, CompoundStmtNode* compoundNode, const std::optional* insertOverride); static void restartRequest(const Request* request, jrd_tra* transaction); +static void revertParametersOrder(Array& parameters); static void validateExpressions(thread_db* tdbb, const Array& validations); } // namespace Jrd @@ -1279,8 +1280,6 @@ DeclareCursorNode* DeclareCursorNode::dsqlPass(DsqlCompilerScratch* dsqlScratch) cursorNumber = dsqlScratch->cursorNumber++; dsqlScratch->cursors.push(this); - dsqlScratch->putDebugDeclaredCursor(cursorNumber, dsqlName); - ++dsqlScratch->scopeLevel; return this; @@ -1304,6 +1303,8 @@ string DeclareCursorNode::internalPrint(NodePrinter& printer) const void DeclareCursorNode::genBlr(DsqlCompilerScratch* dsqlScratch) { + dsqlScratch->putDebugDeclaredCursor(cursorNumber, dsqlName); + dsqlScratch->appendUChar(blr_dcl_cursor); dsqlScratch->appendUShort(cursorNumber); @@ -2190,8 +2191,15 @@ DmlNode* DeclareVariableNode::parse(thread_db* tdbb, MemoryPool& pool, CompilerS return node; } -DeclareVariableNode* DeclareVariableNode::dsqlPass(DsqlCompilerScratch* /*dsqlScratch*/) +DeclareVariableNode* DeclareVariableNode::dsqlPass(DsqlCompilerScratch* dsqlScratch) { + if (dsqlDef->defaultClause) + dsqlDef->defaultClause->value = doDsqlPass(dsqlScratch, dsqlDef->defaultClause->value); + + dsql_fld* field = dsqlDef->type; + dsqlVar = dsqlScratch->makeVariable(field, field->fld_name.c_str(), dsql_var::TYPE_LOCAL, 0, 0); + dsqlVar->initialized = true; + return this; } @@ -4999,20 +5007,29 @@ ExecBlockNode* ExecBlockNode::dsqlPass(DsqlCompilerScratch* dsqlScratch) ExecBlockNode* node = FB_NEW_POOL(dsqlScratch->getPool()) ExecBlockNode(dsqlScratch->getPool()); - for (NestConst* param = parameters.begin(); param != parameters.end(); ++param) + node->parameters = parameters; + + for (auto paramIt = node->parameters.begin(); paramIt != node->parameters.end(); ++paramIt) { + const USHORT index = static_cast(paramIt - node->parameters.begin()); + auto newParam = *paramIt; + PsqlChanger changer(dsqlScratch, false); - node->parameters.add(*param); - ParameterClause* newParam = node->parameters.back(); + newParam->type->resolve(dsqlScratch); + newParam->type->fld_id = paramIt - node->parameters.begin(); + + if (!(dsqlScratch->flags & DsqlCompilerScratch::FLAG_SUB_ROUTINE)) + { + dsqlScratch->makeVariable(newParam->type, newParam->name.c_str(), + dsql_var::TYPE_INPUT, 0, (USHORT) (2 * index), index); + } newParam->parameterExpr = doDsqlPass(dsqlScratch, newParam->parameterExpr); if (newParam->defaultClause) newParam->defaultClause->value = doDsqlPass(dsqlScratch, newParam->defaultClause->value); - newParam->type->resolve(dsqlScratch); - newParam->type->fld_id = param - parameters.begin(); { // scope ValueExprNode* temp = newParam->parameterExpr; @@ -5027,25 +5044,36 @@ ExecBlockNode* ExecBlockNode::dsqlPass(DsqlCompilerScratch* dsqlScratch) false); } // end scope - if (param != parameters.begin()) + if (paramIt != node->parameters.begin()) node->parameters.end()[-2]->type->fld_next = newParam->type; } node->returns = returns; - for (FB_SIZE_T i = 0; i < node->returns.getCount(); ++i) + for (auto retIt = node->returns.begin(); retIt != node->returns.end(); ++retIt) { - node->returns[i]->type->resolve(dsqlScratch); - node->returns[i]->type->fld_id = i; + const USHORT index = static_cast(retIt - node->returns.begin()); + auto newRet = *retIt; - if (i != 0) - node->returns[i - 1]->type->fld_next = node->returns[i]->type; + newRet->type->resolve(dsqlScratch); + newRet->type->fld_id = index; + + if (!(dsqlScratch->flags & DsqlCompilerScratch::FLAG_SUB_ROUTINE)) + { + dsqlScratch->makeVariable(newRet->type, newRet->name.c_str(), + dsql_var::TYPE_OUTPUT, 1, (USHORT) (2 * index), parameters.getCount() + index); + } + + if (index != 0) + node->returns[index - 1]->type->fld_next = newRet->type; } - node->localDeclList = localDeclList; - node->body = body; + LocalDeclarationsNode::checkUniqueFieldsNames(localDeclList, ¶meters, &returns); + + if (localDeclList) + node->localDeclList = localDeclList->dsqlPass(dsqlScratch); - LocalDeclarationsNode::checkUniqueFieldsNames(node->localDeclList, ¶meters, &returns); + node->body = body; return node; } @@ -5073,31 +5101,6 @@ void ExecBlockNode::genBlr(DsqlCompilerScratch* dsqlScratch) // Sub routine doesn't need ports and should generate BLR as declared in its metadata. const bool subRoutine = dsqlScratch->flags & DsqlCompilerScratch::FLAG_SUB_ROUTINE; - unsigned returnsPos; - - if (!subRoutine) - { - // Now do the input parameters. - for (FB_SIZE_T i = 0; i < parameters.getCount(); ++i) - { - ParameterClause* parameter = parameters[i]; - - dsqlScratch->makeVariable(parameter->type, parameter->name.c_str(), - dsql_var::TYPE_INPUT, 0, (USHORT) (2 * i), i); - } - - returnsPos = dsqlScratch->variables.getCount(); - - // Now do the output parameters. - for (FB_SIZE_T i = 0; i < returns.getCount(); ++i) - { - ParameterClause* parameter = returns[i]; - - dsqlScratch->makeVariable(parameter->type, parameter->name.c_str(), - dsql_var::TYPE_OUTPUT, 1, (USHORT) (2 * i), parameters.getCount() + i); - } - } - DsqlStatement* const statement = dsqlScratch->getDsqlStatement(); dsqlScratch->appendUChar(blr_begin); @@ -5133,8 +5136,12 @@ void ExecBlockNode::genBlr(DsqlCompilerScratch* dsqlScratch) else statement->setReceiveMsg(nullptr); + unsigned returnsPos; + unsigned inputStart = 0; + if (subRoutine) { + inputStart = dsqlScratch->variables.getCount(); dsqlScratch->genParameters(parameters, returns); returnsPos = dsqlScratch->variables.getCount() - dsqlScratch->outputVariables.getCount(); } @@ -5152,7 +5159,7 @@ void ExecBlockNode::genBlr(DsqlCompilerScratch* dsqlScratch) // This validation is needed only for subroutines. Standard EXECUTE BLOCK moves input // parameters to variables and are then validated. - for (unsigned i = 0; i < returnsPos; ++i) + for (unsigned i = inputStart; i < returnsPos; ++i) { const dsql_var* variable = dsqlScratch->variables[i]; const TypeClause* field = variable->field; @@ -5172,7 +5179,10 @@ void ExecBlockNode::genBlr(DsqlCompilerScratch* dsqlScratch) const auto& variables = subRoutine ? dsqlScratch->outputVariables : dsqlScratch->variables; for (const auto variable : variables) - dsqlScratch->putLocalVariable(variable); + { + if (variable->type != dsql_var::TYPE_LOCAL) + dsqlScratch->putLocalVariable(variable); + } dsqlScratch->setPsql(true); @@ -5211,22 +5221,6 @@ void ExecBlockNode::genBlr(DsqlCompilerScratch* dsqlScratch) dsqlScratch->endDebug(); } -// Revert parameters order for EXECUTE BLOCK statement -void ExecBlockNode::revertParametersOrder(Array& parameters) -{ - int start = 0; - int end = int(parameters.getCount()) - 1; - - while (start < end) - { - dsql_par* temp = parameters[start]; - parameters[start] = parameters[end]; - parameters[end] = temp; - ++start; - --end; - } -} - //-------------------- @@ -6521,24 +6515,18 @@ void LocalDeclarationsNode::checkUniqueFieldsNames(const LocalDeclarationsNode* } } -void LocalDeclarationsNode::genBlr(DsqlCompilerScratch* dsqlScratch) +LocalDeclarationsNode* LocalDeclarationsNode::dsqlPass(DsqlCompilerScratch* dsqlScratch) { - // Sub routine needs a different approach from EXECUTE BLOCK. - // EXECUTE BLOCK needs "ports", which creates DSQL messages using the client charset. - // Sub routine doesn't need ports and should generate BLR as declared in its metadata. - const bool isSubRoutine = dsqlScratch->flags & DsqlCompilerScratch::FLAG_SUB_ROUTINE; - - Array declaredVariables; + PsqlChanger changer(dsqlScratch, true); + const auto node = FB_NEW_POOL(dsqlScratch->getPool()) LocalDeclarationsNode(dsqlScratch->getPool()); const auto end = statements.end(); for (auto ptr = statements.begin(); ptr != end; ++ptr) { - auto parameter = *ptr; - - if (const auto varNode = nodeAs(parameter)) + if (const auto varNode = nodeAs(*ptr)) { - dsql_fld* field = varNode->dsqlDef->type; + const dsql_fld* field = varNode->dsqlDef->type; const NestConst* rest = ptr; while (++rest != end) @@ -6554,37 +6542,55 @@ void LocalDeclarationsNode::genBlr(DsqlCompilerScratch* dsqlScratch) } } } + } + } - const auto variable = dsqlScratch->makeVariable(field, field->fld_name.c_str(), - dsql_var::TYPE_LOCAL, 0, 0); - declaredVariables.add(variable); + for (auto stmt : statements) + node->statements.add(stmt->dsqlPass(dsqlScratch)); + + return node; +} + +void LocalDeclarationsNode::genBlr(DsqlCompilerScratch* dsqlScratch) +{ + // Sub routine needs a different approach from EXECUTE BLOCK. + // EXECUTE BLOCK needs "ports", which creates DSQL messages using the client charset. + // Sub routine doesn't need ports and should generate BLR as declared in its metadata. + const bool isSubRoutine = dsqlScratch->flags & DsqlCompilerScratch::FLAG_SUB_ROUTINE; + + for (auto parameter : statements) + { + if (const auto varNode = nodeAs(parameter)) + { + dsql_var* variable = varNode->dsqlVar; + fb_assert(variable); dsqlScratch->putLocalVariableDecl(variable, varNode, varNode->dsqlDef->type->collate); // Some field attributes are calculated inside putLocalVariable(), so we reinitialize // the descriptor. - DsqlDescMaker::fromField(&variable->desc, field); + DsqlDescMaker::fromField(&variable->desc, variable->field); } else if (nodeIs(parameter) || nodeIs(parameter) || nodeIs(parameter)) { dsqlScratch->putDebugSrcInfo(parameter->line, parameter->column); - parameter->dsqlPass(dsqlScratch); parameter->genBlr(dsqlScratch); } else fb_assert(false); } - auto declVarIt = declaredVariables.begin(); - for (const auto parameter : statements) { if (const auto varNode = nodeAs(parameter)) { + const auto variable = varNode->dsqlVar; + fb_assert(variable); + dsqlScratch->putDebugSrcInfo(parameter->line, parameter->column); - dsqlScratch->putLocalVariableInit(*declVarIt++, varNode); + dsqlScratch->putLocalVariableInit(variable, varNode); } } @@ -9513,7 +9519,7 @@ void SelectNode::genBlr(DsqlCompilerScratch* dsqlScratch) auto message = statement->getSendMsg(); - if (message->msg_parameter) + if (message && message->msg_parameter) GEN_port(dsqlScratch, message); else statement->setSendMsg(nullptr); @@ -10910,6 +10916,171 @@ void UserSavepointNode::execute(thread_db* tdbb, DsqlRequest* request, jrd_tra** //-------------------- +StmtNode* UsingNode::dsqlPass(DsqlCompilerScratch* dsqlScratch) +{ + // USING without parameters and subroutines is useless + if (parameters.isEmpty() && !localDeclList) + { + ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-104) << + Arg::Gds(isc_dsql_using_requires_params_subroutines)); + } + + dsqlScratch->flags |= DsqlCompilerScratch::FLAG_USING_STATEMENT; + + const auto statement = dsqlScratch->getDsqlStatement(); + unsigned index = 0; + + const auto node = FB_NEW_POOL(dsqlScratch->getPool()) UsingNode(dsqlScratch->getPool()); + + node->parameters = parameters; + + for (auto newParam : node->parameters) + { + PsqlChanger changer(dsqlScratch, false); + + newParam->parameterExpr = doDsqlPass(dsqlScratch, newParam->parameterExpr); + + if (newParam->defaultClause) + newParam->defaultClause->value = doDsqlPass(dsqlScratch, newParam->defaultClause->value); + + newParam->type->resolve(dsqlScratch); + newParam->type->fld_id = index; + + { // scope + auto temp = newParam->parameterExpr; + + // Initialize this stack variable, and make it look like a node + dsc descNode; + + newParam->type->flags |= FLD_nullable; + DsqlDescMaker::fromField(&descNode, newParam->type); + PASS1_set_parameter_type(dsqlScratch, temp, + [&] (dsc* desc) { *desc = descNode; }, + false); + + if (const auto paramNode = nodeAs(temp)) + { + if (paramNode->dsqlParameter) + { + paramNode->dsqlParameter->par_name = newParam->name; + paramNode->dsqlParameter->par_alias = newParam->name; + } + } + } + + dsqlScratch->makeVariable(newParam->type, newParam->name.c_str(), + dsql_var::TYPE_INPUT, 0, (USHORT) (2 * index), index); + + ++index; + } + + LocalDeclarationsNode::checkUniqueFieldsNames(localDeclList, ¶meters, nullptr); + + if (localDeclList) + node->localDeclList = localDeclList->dsqlPass(dsqlScratch); + + if (body) + node->body = body->dsqlPass(dsqlScratch); + + if (statement->getSendMsg()) + revertParametersOrder(statement->getSendMsg()->msg_parameters); + + return node; +} + +string UsingNode::internalPrint(NodePrinter& printer) const +{ + StmtNode::internalPrint(printer); + + NODE_PRINT(printer, parameters); + NODE_PRINT(printer, localDeclList); + NODE_PRINT(printer, body); + + return "UsingNode"; +} + +void UsingNode::genBlr(DsqlCompilerScratch* dsqlScratch) +{ + const auto statement = dsqlScratch->getDsqlStatement(); + + // For SELECT-based statement types, GEN_statement does NOT call GEN_port or generate + // blr_receive_batch, so we must handle it here. For other types (INSERT, UPDATE, DELETE), + // GEN_statement already handles the message port generation. + const bool isSelect = (statement->getType() == DsqlStatement::TYPE_SELECT || + statement->getType() == DsqlStatement::TYPE_SELECT_UPD); + + // For SELECT types, check if we have actual parameters. Clear sendMsg if not + // (similar to what GEN_statement does for non-SELECT types). + auto sendMsg = statement->getSendMsg(); + if (isSelect && sendMsg && !sendMsg->msg_parameter) + { + statement->setSendMsg(nullptr); + sendMsg = nullptr; + } + + // For SELECT types with parameters, we need to wrap everything in blr_begin + // and generate our own message/receive + const bool genSelectWrapper = isSelect && sendMsg; + + if (genSelectWrapper) + dsqlScratch->appendUChar(blr_begin); + + // Generate the port (message) BLR for input parameters - only for SELECT statements + if (genSelectWrapper) + GEN_port(dsqlScratch, sendMsg); + + if (dsqlScratch->variables.hasData()) + { + // Generate receive statement to get parameter values - only for SELECT statements + if (genSelectWrapper) + { + dsqlScratch->appendUChar(blr_receive); + dsqlScratch->appendUChar(0); + } + + dsqlScratch->appendUChar(blr_begin); + + for (const auto var : dsqlScratch->variables) + dsqlScratch->putLocalVariable(var); + } + + dsqlScratch->appendUChar(blr_begin); + + if (localDeclList) + localDeclList->genBlr(dsqlScratch); + + dsqlScratch->loopLevel = 0; + + // Temporarily hide SendMsg so the body (SelectNode) doesn't generate duplicate port/receive + if (sendMsg) + statement->setSendMsg(nullptr); + + // The body should be generated in DSQL mode (isPsql=false) so that RETURNING + // generates the correct local table declarations and cursor handling. + PsqlChanger changer(dsqlScratch, false); + + if (body) + body->genBlr(dsqlScratch); + + if (sendMsg) + statement->setSendMsg(sendMsg); + + dsqlScratch->putOuterMaps(); + GEN_hidden_variables(dsqlScratch); + + dsqlScratch->appendUChar(blr_end); + + if (dsqlScratch->variables.hasData()) + dsqlScratch->appendUChar(blr_end); + + if (genSelectWrapper) + dsqlScratch->appendUChar(blr_end); +} + + +//-------------------- + + // Generate a field list that correspond to table fields. template static void dsqlExplodeFields(dsql_rel* relation, Array >& fields, bool includeComputed) @@ -12184,6 +12355,22 @@ static void restartRequest(const Request* request, jrd_tra* transaction) Arg::Gds(isc_concurrent_transaction) << Arg::Int64(top_request->req_conflict_txn)); } +// Revert parameters order for EXECUTE BLOCK and USING statements. +static void revertParametersOrder(Array& parameters) +{ + int start = 0; + int end = int(parameters.getCount()) - 1; + + while (start < end) + { + const auto temp = parameters[start]; + parameters[start] = parameters[end]; + parameters[end] = temp; + ++start; + --end; + } +} + // Execute a list of validation expressions. static void validateExpressions(thread_db* tdbb, const Array& validations) { diff --git a/src/dsql/StmtNodes.h b/src/dsql/StmtNodes.h index 4d1d4936a02..8845194c098 100644 --- a/src/dsql/StmtNodes.h +++ b/src/dsql/StmtNodes.h @@ -508,6 +508,7 @@ class DeclareVariableNode final : public TypedNode dsqlDef; dsc varDesc; + dsql_var* dsqlVar = nullptr; USHORT varId = 0; bool usedInSubRoutines = false; }; @@ -785,9 +786,6 @@ class ExecBlockNode final : public TypedNode& parameters); - public: Firebird::Array> parameters; Firebird::Array> returns; @@ -1049,6 +1047,7 @@ class LocalDeclarationsNode final : public TypedNode>* outputParameters); public: + LocalDeclarationsNode* dsqlPass(DsqlCompilerScratch* dsqlScratch) override; void genBlr(DsqlCompilerScratch* dsqlScratch) override; public: @@ -2100,6 +2099,26 @@ class UpdateOrInsertNode final : public TypedNode +{ +public: + explicit UsingNode(MemoryPool& pool) + : TypedNode(pool), + parameters(pool) + { + } + + Firebird::string internalPrint(NodePrinter& printer) const override; + StmtNode* dsqlPass(DsqlCompilerScratch* dsqlScratch) override; + void genBlr(DsqlCompilerScratch* dsqlScratch) override; + +public: + Firebird::Array> parameters; + NestConst localDeclList; + NestConst body; +}; + + } // namespace #endif // DSQL_STMT_NODES_H diff --git a/src/dsql/gen.cpp b/src/dsql/gen.cpp index d51a51c70d4..7a89d2236c4 100644 --- a/src/dsql/gen.cpp +++ b/src/dsql/gen.cpp @@ -290,7 +290,7 @@ void GEN_statement(DsqlCompilerScratch* scratch, DmlNode* node) default: { dsql_msg* message = statement->getSendMsg(); - if (!message->msg_parameter) + if (!message || !message->msg_parameter) statement->setSendMsg(NULL); else { diff --git a/src/dsql/parse-conflicts.txt b/src/dsql/parse-conflicts.txt index 08c7529f156..41dbe10bf44 100644 --- a/src/dsql/parse-conflicts.txt +++ b/src/dsql/parse-conflicts.txt @@ -1 +1 @@ -134 shift/reduce conflicts, 7 reduce/reduce conflicts. +135 shift/reduce conflicts, 7 reduce/reduce conflicts. diff --git a/src/dsql/parse.y b/src/dsql/parse.y index 98625c8b02c..5aa0fbab48b 100644 --- a/src/dsql/parse.y +++ b/src/dsql/parse.y @@ -852,6 +852,7 @@ using namespace Firebird; Jrd::ExecBlockNode* execBlockNode; Jrd::StoreNode* storeNode; Jrd::UpdateOrInsertNode* updInsNode; + Jrd::UsingNode* usingNode; Jrd::AggNode* aggNode; Jrd::SysFuncCallNode* sysFuncCallNode; Jrd::ValueIfNode* valueIfNode; @@ -928,6 +929,7 @@ dml_statement | select { $$ = $1; } | update { $$ = $1; } | update_or_insert { $$ = $1; } + | using { $$ = $1; } ; %type ddl_statement @@ -4145,6 +4147,36 @@ block_parameter($parameters) } ; +// USING + +%type using +using + : USING + { $$ = newNode(); } + block_input_params(NOTRIAL(&$2->parameters)) + local_declarations_opt + DO + using_dml_statement + { + const auto node = $2; + node->localDeclList = $4; + node->body = $6; + $$ = node; + } + ; + +%type using_dml_statement +using_dml_statement + : call { $$ = $1; } + | delete { $$ = $1; } + | insert { $$ = $1; } + | merge { $$ = $1; } + | exec_procedure { $$ = $1; } + | select { $$ = $1; } + | update { $$ = $1; } + | update_or_insert { $$ = $1; } + ; + // CREATE VIEW %type view_clause diff --git a/src/include/firebird/impl/msg/dsql.h b/src/include/firebird/impl/msg/dsql.h index 21ac4ecd321..309ddf744c8 100644 --- a/src/include/firebird/impl/msg/dsql.h +++ b/src/include/firebird/impl/msg/dsql.h @@ -38,3 +38,4 @@ FB_IMPL_MSG(DSQL, 38, dsql_no_output_sqlda, -802, "07", "002", "No SQLDA for out FB_IMPL_MSG(DSQL, 39, dsql_wrong_param_num, -313, "07", "001", "Wrong number of parameters (expected @1, got @2)") FB_IMPL_MSG(DSQL, 40, dsql_invalid_drop_ss_clause, -817, "42", "000", "Invalid DROP SQL SECURITY clause") FB_IMPL_MSG(DSQL, 41, upd_ins_cannot_default, -313, "42", "000", "UPDATE OR INSERT value for field @1, part of the implicit or explicit MATCHING clause, cannot be DEFAULT") +FB_IMPL_MSG(DSQL, 42, dsql_using_requires_params_subroutines, -104, "42", "000", "USING requires parameters or subroutines") diff --git a/src/include/gen/Firebird.pas b/src/include/gen/Firebird.pas index eb0cfeae5a0..d830f214c9b 100644 --- a/src/include/gen/Firebird.pas +++ b/src/include/gen/Firebird.pas @@ -6022,6 +6022,7 @@ IPerformanceStatsImpl = class(IPerformanceStats) isc_dsql_wrong_param_num = 336003111; isc_dsql_invalid_drop_ss_clause = 336003112; isc_upd_ins_cannot_default = 336003113; + isc_dsql_using_requires_params_subroutines = 336003114; isc_dyn_filter_not_found = 336068645; isc_dyn_func_not_found = 336068649; isc_dyn_index_not_found = 336068656;