From b6248f64535a767ecc473064b3fb94962c39dd1c Mon Sep 17 00:00:00 2001 From: Adriano dos Santos Fernandes Date: Tue, 19 Sep 2023 07:29:03 -0300 Subject: [PATCH] Feature #7587 - CALL statement. --- doc/sql.extensions/README.call.md | 103 +++++++++ src/common/ParserTokens.h | 1 + src/dsql/Nodes.h | 5 + src/dsql/StmtNodes.cpp | 341 ++++++++++++++++++++++++++---- src/dsql/StmtNodes.h | 4 +- src/dsql/dsql.cpp | 3 +- src/dsql/gen.cpp | 24 ++- src/dsql/parse.y | 29 +++ src/include/firebird/impl/blr.h | 6 +- src/yvalve/gds.cpp | 4 + 10 files changed, 467 insertions(+), 53 deletions(-) create mode 100644 doc/sql.extensions/README.call.md diff --git a/doc/sql.extensions/README.call.md b/doc/sql.extensions/README.call.md new file mode 100644 index 0000000000..ecbb7a53b9 --- /dev/null +++ b/doc/sql.extensions/README.call.md @@ -0,0 +1,103 @@ +# CALL statement (FB 6.0) + +`CALL` statement is similar to `EXECUTE PROCEDURE`, but allow the caller to get specific output parameters, or none. + +When using the positional or mixed parameter passing, output parameters follows the input ones. + +When passing `NULL` to output parameters, they are ignored, and in the case of DSQL, not even returned. + +In DSQL output parameters are specified using `?`, and in PSQL using the target variables or parameters. + +## Syntax + +``` + ::= + CALL [ .] () + + ::= + | + [ {,} ] + + ::= + [ {, }... ] + + ::= + [ {, }... ] + + ::= + => + + ::= + | + DEFAULT +``` + +## Examples + +``` +create or alter procedure insert_customer ( + last_name varchar(30), + first_name varchar(30) +) returns ( + id integer, + full_name varchar(62) +) +as +begin + insert into customers (last_name, first_name) + values (:last_name, :first_name) + returning id, last_name || ', ' || first_name + into :id, :full_name; +end +``` + +``` +-- Not all output parameters are necessary. +call insert_customer( + 'LECLERC', + 'CHARLES', + ?) +``` + +``` +-- Ignore first output parameter (using NULL) and get the second. +call insert_customer( + 'LECLERC', + 'CHARLES', + null, + ?) +``` + +``` +-- Ignore ID output parameter. +call insert_customer( + 'LECLERC', + 'CHARLES', + full_name => ?) +``` + +``` +-- Pass inputs and get outputs using named arguments. +call insert_customer( + last_name => 'LECLERC', + first_name => 'CHARLES', + last_name => ?, + id => ?) +``` + +``` +create or alter procedure do_something_and_insert_customer returns ( + out_id integer, + out_full_name varchar(62) +) +as + declare last_name varchar(30); + declare first_name varchar(30); +begin + call insert_customer( + last_name, + first_name, + out_id, + full_name => out_full_name); +end +``` diff --git a/src/common/ParserTokens.h b/src/common/ParserTokens.h index ae64e32bce..92f769f7fe 100644 --- a/src/common/ParserTokens.h +++ b/src/common/ParserTokens.h @@ -104,6 +104,7 @@ PARSER_TOKEN(TOK_BOOLEAN, "BOOLEAN", false) PARSER_TOKEN(TOK_BOTH, "BOTH", false) PARSER_TOKEN(TOK_BREAK, "BREAK", true) PARSER_TOKEN(TOK_BY, "BY", false) +PARSER_TOKEN(TOK_CALL, "CALL", false) PARSER_TOKEN(TOK_CALLER, "CALLER", true) PARSER_TOKEN(TOK_CASCADE, "CASCADE", true) PARSER_TOKEN(TOK_CASE, "CASE", false) diff --git a/src/dsql/Nodes.h b/src/dsql/Nodes.h index 6075ee0400..4849389876 100644 --- a/src/dsql/Nodes.h +++ b/src/dsql/Nodes.h @@ -1296,6 +1296,11 @@ public: return this; } + void ensureCapacity(unsigned count) + { + items.ensureCapacity(count); + } + void clear() { items.clear(); diff --git a/src/dsql/StmtNodes.cpp b/src/dsql/StmtNodes.cpp index b095a6e57b..b66509e780 100644 --- a/src/dsql/StmtNodes.cpp +++ b/src/dsql/StmtNodes.cpp @@ -2929,6 +2929,10 @@ DmlNode* ExecProcedureNode::parse(thread_db* tdbb, MemoryPool& pool, CompilerScr const UCHAR* outArgNamesPos = nullptr; ObjectsArray* outArgNames = nullptr; USHORT outArgCount = 0; + const UCHAR* inOutArgNamesPos = nullptr; + ObjectsArray* inOutArgNames = nullptr; + USHORT inOutArgCount = 0; + ValueListNode* inOutArgs = nullptr; QualifiedName name; const auto node = FB_NEW_POOL(pool) ExecProcedureNode(pool); @@ -3035,6 +3039,34 @@ DmlNode* ExecProcedureNode::parse(thread_db* tdbb, MemoryPool& pool, CompilerScr node->outputTargets = PAR_args(tdbb, csb, outArgCount, outArgCount); break; + case blr_invsel_procedure_inout_arg_names: + { + predateCheck(node->procedure, + "blr_invsel_procedure_type", "blr_invsel_procedure_inout_arg_names"); + predateCheck(!inOutArgs, + "blr_invsel_procedure_inout_arg_names", "blr_invsel_procedure_inout_args"); + + inOutArgNamesPos = blrReader.getPos(); + USHORT inOutArgNamesCount = blrReader.getWord(); + MetaName argName; + + inOutArgNames = FB_NEW_POOL(pool) ObjectsArray(pool); + + while (inOutArgNamesCount--) + { + blrReader.getMetaName(argName); + inOutArgNames->add(argName); + } + + break; + } + + case blr_invsel_procedure_inout_args: + predateCheck(node->procedure, "blr_invsel_procedure_type", "blr_invsel_procedure_inout_args"); + inOutArgCount = blrReader.getWord(); + inOutArgs = PAR_args(tdbb, csb, inOutArgCount, inOutArgCount); + break; + default: PAR_error(csb, Arg::Gds(isc_random) << "Invalid blr_invoke_procedure sub code"); } @@ -3071,6 +3103,81 @@ DmlNode* ExecProcedureNode::parse(thread_db* tdbb, MemoryPool& pool, CompilerScr break; } + if (!node->procedure) + { + blrReader.setPos(blrStartPos); + PAR_error(csb, Arg::Gds(isc_prcnotdef) << name.toString()); + } + + if ((inOutArgs || inOutArgNames) && (node->inputSources || inArgNames || node->outputTargets || outArgNames)) + { + blrReader.setPos(inOutArgNamesPos); + PAR_error(csb, Arg::Gds(isc_random) << "IN/OUT args are not allowed with IN or OUT args"); + } + + if (inOutArgs) + { + node->outputTargets = FB_NEW_POOL(pool) ValueListNode(pool); + + const auto prcInCount = node->procedure->getInputFields().getCount(); + const auto positionalArgCount = inOutArgs->items.getCount() - + (inOutArgNames ? inOutArgNames->getCount() : 0); + + if (positionalArgCount > prcInCount || inOutArgNames) + { + node->inputSources = FB_NEW_POOL(pool) ValueListNode(pool); + inArgNames = FB_NEW_POOL(pool) ObjectsArray(pool); + outArgNames = FB_NEW_POOL(pool) ObjectsArray(pool); + SortedObjectsArray outFields; + + for (const auto field : node->procedure->getOutputFields()) + outFields.add(field->prm_name); + + unsigned pos = 0; + + for (auto source : inOutArgs->items) + { + const bool isInput = (pos < positionalArgCount && pos < prcInCount) || + (pos >= positionalArgCount && + !outFields.exist((*inOutArgNames)[pos - positionalArgCount])); + + if (isInput) + { + node->inputSources->add(source); + + if (pos >= positionalArgCount) + inArgNames->add((*inOutArgNames)[pos - positionalArgCount]); + } + else + { + node->outputTargets->add(source); + + if (pos >= positionalArgCount) + outArgNames->add((*inOutArgNames)[pos - positionalArgCount]); + } + + ++pos; + } + + delete inOutArgs; + inOutArgs = nullptr; + + delete inOutArgNames; + inOutArgNames = nullptr; + } + else + { + node->inputSources = inOutArgs; + inArgNames = inOutArgNames; + } + + inArgCount = node->inputSources->items.getCount(); + outArgCount = node->outputTargets->items.getCount(); + + node->inputSources->ensureCapacity(node->procedure->getInputFields().getCount()); + node->outputTargets->ensureCapacity(node->procedure->getOutputFields().getCount()); + } + if (inArgNames && inArgNames->getCount() > node->inputSources->items.getCount()) { blrReader.setPos(inArgNamesPos); @@ -3079,12 +3186,6 @@ DmlNode* ExecProcedureNode::parse(thread_db* tdbb, MemoryPool& pool, CompilerScr "blr_invsel_procedure_in_arg_names count cannot be greater than blr_invsel_procedure_in_args"); } - if (!node->procedure) - { - blrReader.setPos(blrStartPos); - PAR_error(csb, Arg::Gds(isc_prcnotdef) << name.toString()); - } - if (blrOp != blr_invoke_procedure) { inArgCount = blrReader.getWord(); @@ -3100,12 +3201,12 @@ DmlNode* ExecProcedureNode::parse(thread_db* tdbb, MemoryPool& pool, CompilerScr if (!node->outputTargets) node->outputTargets = FB_NEW_POOL(pool) ValueListNode(pool); - if (outArgNames && outArgNames->getCount() != node->outputTargets->items.getCount()) + if (outArgNames && outArgNames->getCount() > node->outputTargets->items.getCount()) { blrReader.setPos(outArgNamesPos); PAR_error(csb, Arg::Gds(isc_random) << - "blr_invsel_procedure_out_arg_names count differs from blr_invsel_procedure_out_args"); + "blr_invsel_procedure_out_arg_names count cannot be greater than blr_invsel_procedure_out_args"); } if (node->procedure->isImplemented() && !node->procedure->isDefined()) @@ -3206,7 +3307,8 @@ DmlNode* ExecProcedureNode::parse(thread_db* tdbb, MemoryPool& pool, CompilerScr ExecProcedureNode* ExecProcedureNode::dsqlPass(DsqlCompilerScratch* dsqlScratch) { - dsql_prc* procedure = NULL; + auto& pool = dsqlScratch->getPool(); + dsql_prc* procedure = nullptr; if (dsqlName.package.isEmpty()) { @@ -3228,13 +3330,139 @@ ExecProcedureNode* ExecProcedureNode::dsqlPass(DsqlCompilerScratch* dsqlScratch) if (!dsqlScratch->isPsql()) dsqlScratch->getDsqlStatement()->setType(DsqlStatement::TYPE_EXEC_PROCEDURE); - const auto node = FB_NEW_POOL(dsqlScratch->getPool()) ExecProcedureNode(dsqlScratch->getPool(), dsqlName, + if (dsqlCallSyntax && !dsqlScratch->isPsql() && inputSources && inputSources->items.hasData()) + { + const auto positionalArgCount = inputSources->items.getCount() - + (dsqlInputArgNames ? dsqlInputArgNames->getCount() : 0); + + if (positionalArgCount > procedure->prc_in_count || dsqlInputArgNames) + { + const auto newInputs = FB_NEW_POOL(pool) ValueListNode(pool); + const auto newOutputs = FB_NEW_POOL(pool) ValueListNode(pool); + const auto newInputArgNames = FB_NEW_POOL(pool) ObjectsArray(pool); + const auto newOutputArgNames = FB_NEW_POOL(pool) ObjectsArray(pool); + SortedObjectsArray outFields; + + for (const auto* field = procedure->prc_outputs; field; field = field->fld_next) + outFields.add(field->fld_name); + + unsigned pos = 0; + + for (auto source : inputSources->items) + { + const bool isInput = (pos < positionalArgCount && pos < procedure->prc_in_count) || + (pos >= positionalArgCount && + !outFields.exist((*dsqlInputArgNames)[pos - positionalArgCount])); + + if (isInput) + { + newInputs->add(source); + + if (pos >= positionalArgCount) + newInputArgNames->add((*dsqlInputArgNames)[pos - positionalArgCount]); + } + else + { + newOutputs->add(source); + + if (pos >= positionalArgCount) + newOutputArgNames->add((*dsqlInputArgNames)[pos - positionalArgCount]); + } + + ++pos; + } + + if (newInputs->items.getCount() != inputSources->items.getCount()) + { + delete inputSources.getObject(); + inputSources = newInputs; + + delete dsqlInputArgNames.getObject(); + dsqlInputArgNames = newInputArgNames; + + delete outputTargets.getObject(); + outputTargets = newOutputs; + + delete dsqlOutputArgNames.getObject(); + dsqlOutputArgNames = newOutputArgNames; + } + + if (outputTargets && outputTargets->items.hasData()) + { + auto targetArgIt = outputTargets->items.begin(); + const auto targetArgEnd = outputTargets->items.end(); + const auto positionalArgCount = outputTargets->items.getCount() - + (dsqlOutputArgNames ? dsqlOutputArgNames->getCount() : 0); + const auto* field = procedure->prc_outputs; + unsigned pos = 0; + + while (pos < positionalArgCount && field && targetArgIt != targetArgEnd) + { + if (const auto paramNode = nodeAs(*targetArgIt)) + { + const auto parameter = paramNode->dsqlParameter = MAKE_parameter( + dsqlScratch->getDsqlStatement()->getReceiveMsg(), true, true, 0, NULL); + paramNode->dsqlParameterIndex = parameter->par_index; + + DsqlDescMaker::fromField(¶meter->par_desc, field); + parameter->par_name = parameter->par_alias = field->fld_name.c_str(); + parameter->par_rel_name = procedure->prc_name.identifier.c_str(); + parameter->par_owner_name = procedure->prc_owner.c_str(); + } + + field = field->fld_next; + ++pos; + ++targetArgIt; + } + + if (dsqlOutputArgNames) + { + fb_assert(dsqlOutputArgNames->getCount() <= outputTargets->items.getCount()); + + LeftPooledMap argsByName; + + for (const auto* field = procedure->prc_outputs; field; field = field->fld_next) + argsByName.put(field->fld_name, field); + + Arg::StatusVector mismatchStatus; + + for (const auto& argName : *dsqlOutputArgNames) + { + if (const auto field = argsByName.get(argName)) + { + if (const auto paramNode = nodeAs(*targetArgIt)) + { + const auto parameter = paramNode->dsqlParameter = MAKE_parameter( + dsqlScratch->getDsqlStatement()->getReceiveMsg(), true, true, 0, NULL); + paramNode->dsqlParameterIndex = parameter->par_index; + + DsqlDescMaker::fromField(¶meter->par_desc, *field); + parameter->par_name = parameter->par_alias = (*field)->fld_name.c_str(); + parameter->par_rel_name = procedure->prc_name.identifier.c_str(); + parameter->par_owner_name = procedure->prc_owner.c_str(); + } + } + else + mismatchStatus << Arg::Gds(isc_param_not_exist) << argName; + + ++targetArgIt; + } + + if (mismatchStatus.hasData()) + status_exception::raise(Arg::Gds(isc_prcmismat) << dsqlName.toString() << mismatchStatus); + } + } + } + } + + const auto node = FB_NEW_POOL(pool) ExecProcedureNode(pool, dsqlName, doDsqlPass(dsqlScratch, inputSources), nullptr, dsqlInputArgNames ? - FB_NEW_POOL(dsqlScratch->getPool()) ObjectsArray(dsqlScratch->getPool(), *dsqlInputArgNames) : + FB_NEW_POOL(pool) ObjectsArray(pool, *dsqlInputArgNames) : nullptr); + node->dsqlCallSyntax = dsqlCallSyntax; node->dsqlProcedure = procedure; if (node->dsqlName.package.isEmpty() && procedure->prc_name.package.hasData()) @@ -3242,7 +3470,7 @@ ExecProcedureNode* ExecProcedureNode::dsqlPass(DsqlCompilerScratch* dsqlScratch) // Handle input parameters. - if (inputSources && inputSources->items.hasData()) + if (node->inputSources && node->inputSources->items.hasData()) { auto sourceArgIt = node->inputSources->items.begin(); const auto sourceArgEnd = node->inputSources->items.end(); @@ -3274,6 +3502,12 @@ ExecProcedureNode* ExecProcedureNode::dsqlPass(DsqlCompilerScratch* dsqlScratch) for (const auto* field = procedure->prc_inputs; field; field = field->fld_next) argsByName.put(field->fld_name, field); + if (dsqlCallSyntax && dsqlScratch->isPsql()) + { + for (const auto* field = procedure->prc_outputs; field; field = field->fld_next) + argsByName.put(field->fld_name, field); + } + Arg::StatusVector mismatchStatus; for (const auto& argName : *node->dsqlInputArgNames) @@ -3302,16 +3536,19 @@ ExecProcedureNode* ExecProcedureNode::dsqlPass(DsqlCompilerScratch* dsqlScratch) if (dsqlScratch->isPsql()) { - const USHORT outCount = outputSources ? outputSources->items.getCount() : 0; + if (!dsqlCallSyntax) + { + const USHORT outCount = outputTargets ? outputTargets->items.getCount() : 0; - if (outCount != procedure->prc_out_count) - ERRD_post(Arg::Gds(isc_prc_out_param_mismatch) << Arg::Str(dsqlName.toString())); + if (outCount != procedure->prc_out_count) + ERRD_post(Arg::Gds(isc_prc_out_param_mismatch) << Arg::Str(dsqlName.toString())); + } - node->outputSources = dsqlPassArray(dsqlScratch, outputSources); + node->outputTargets = dsqlPassArray(dsqlScratch, outputTargets); } - else + else if (!dsqlCallSyntax) { - if (outputSources) + if (outputTargets) { ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-104) << // Token unknown @@ -3319,18 +3556,19 @@ ExecProcedureNode* ExecProcedureNode::dsqlPass(DsqlCompilerScratch* dsqlScratch) Arg::Gds(isc_random) << Arg::Str("RETURNING_VALUES")); } - node->outputSources = explodeOutputs(dsqlScratch, procedure); + node->outputTargets = explodeOutputs(dsqlScratch, procedure); + } + else + node->outputTargets = dsqlPassArray(dsqlScratch, outputTargets); + + if (node->outputTargets) + { + for (const auto target : node->outputTargets->items) + AssignmentNode::dsqlValidateTarget(target); } - if (node->outputSources) - { - for (const NestConst* i = node->outputSources->items.begin(); - i != node->outputSources->items.end(); - ++i) - { - AssignmentNode::dsqlValidateTarget(*i); - } - } + if (dsqlOutputArgNames) + node->dsqlOutputArgNames = FB_NEW_POOL(pool) ObjectsArray(pool, *dsqlOutputArgNames); return node; } @@ -3350,10 +3588,10 @@ ValueListNode* ExecProcedureNode::explodeOutputs(DsqlCompilerScratch* dsqlScratc { DEV_BLKCHK(field, dsql_type_fld); - ParameterNode* paramNode = FB_NEW_POOL(dsqlScratch->getPool()) ParameterNode(dsqlScratch->getPool()); + const auto paramNode = FB_NEW_POOL(dsqlScratch->getPool()) ParameterNode(dsqlScratch->getPool()); *ptr = paramNode; - dsql_par* parameter = paramNode->dsqlParameter = MAKE_parameter( + const auto parameter = paramNode->dsqlParameter = MAKE_parameter( dsqlScratch->getDsqlStatement()->getReceiveMsg(), true, true, 0, NULL); paramNode->dsqlParameterIndex = parameter->par_index; @@ -3395,7 +3633,7 @@ void ExecProcedureNode::genBlr(DsqlCompilerScratch* dsqlScratch) } } - if (dsqlInputArgNames) + if (dsqlInputArgNames || dsqlOutputArgNames || dsqlCallSyntax) { dsqlScratch->appendUChar(blr_invoke_procedure); @@ -3414,19 +3652,23 @@ void ExecProcedureNode::genBlr(DsqlCompilerScratch* dsqlScratch) dsqlScratch->appendMetaString(dsqlName.identifier.c_str()); + const bool useInOut = dsqlScratch->isPsql() && dsqlCallSyntax; + // Input parameters. if (inputSources) { if (dsqlInputArgNames && dsqlInputArgNames->hasData()) { - dsqlScratch->appendUChar(blr_invsel_procedure_in_arg_names); + dsqlScratch->appendUChar( + useInOut ? blr_invsel_procedure_inout_arg_names : blr_invsel_procedure_in_arg_names); dsqlScratch->appendUShort(dsqlInputArgNames->getCount()); for (auto& argName : *dsqlInputArgNames) dsqlScratch->appendMetaString(argName.c_str()); } - dsqlScratch->appendUChar(blr_invsel_procedure_in_args); + dsqlScratch->appendUChar( + useInOut ? blr_invsel_procedure_inout_args : blr_invsel_procedure_in_args); dsqlScratch->appendUShort(inputSources->items.getCount()); for (auto& arg : inputSources->items) @@ -3434,13 +3676,22 @@ void ExecProcedureNode::genBlr(DsqlCompilerScratch* dsqlScratch) } // Output parameters. - if (outputSources) + if (!useInOut && outputTargets) { - dsqlScratch->appendUChar(blr_invsel_procedure_out_args); - dsqlScratch->appendUShort(outputSources->items.getCount()); + if (dsqlOutputArgNames && dsqlOutputArgNames->hasData()) + { + dsqlScratch->appendUChar(blr_invsel_procedure_out_arg_names); + dsqlScratch->appendUShort(dsqlOutputArgNames->getCount()); - for (auto& arg : outputSources->items) - GEN_expr(dsqlScratch, arg); + for (auto& argName : *dsqlOutputArgNames) + dsqlScratch->appendMetaString(argName.c_str()); + } + + dsqlScratch->appendUChar(blr_invsel_procedure_out_args); + dsqlScratch->appendUShort(outputTargets->items.getCount()); + + for (auto& arg : outputTargets->items) + GEN_arg(dsqlScratch, arg); } dsqlScratch->appendUChar(blr_end); @@ -3469,11 +3720,11 @@ void ExecProcedureNode::genBlr(DsqlCompilerScratch* dsqlScratch) dsqlScratch->appendUShort(0); // Output parameters. - if (outputSources) + if (outputTargets) { - dsqlScratch->appendUShort(outputSources->items.getCount()); + dsqlScratch->appendUShort(outputTargets->items.getCount()); - for (auto& arg : outputSources->items) + for (auto& arg : outputTargets->items) GEN_expr(dsqlScratch, arg); } else @@ -3514,12 +3765,8 @@ ExecProcedureNode* ExecProcedureNode::pass2(thread_db* tdbb, CompilerScratch* cs if (outputTargets) { - for (const NestConst* i = outputTargets->items.begin(); - i != outputTargets->items.end(); - ++i) - { - AssignmentNode::validateTarget(csb, *i); - } + for (const auto target : outputTargets->items) + AssignmentNode::validateTarget(csb, target); } return this; diff --git a/src/dsql/StmtNodes.h b/src/dsql/StmtNodes.h index a9d3cad8e4..6976fd8103 100644 --- a/src/dsql/StmtNodes.h +++ b/src/dsql/StmtNodes.h @@ -594,7 +594,7 @@ public: : TypedNode(pool), dsqlName(pool, aDsqlName), inputSources(aInputs), - outputSources(aOutputs), + outputTargets(aOutputs), dsqlInputArgNames(aDsqlInputArgNames) { } @@ -625,6 +625,8 @@ public: NestConst outputMessage; NestConst procedure; NestConst> dsqlInputArgNames; + NestConst> dsqlOutputArgNames; + bool dsqlCallSyntax = false; }; diff --git a/src/dsql/dsql.cpp b/src/dsql/dsql.cpp index 3547627149..11717dfd98 100644 --- a/src/dsql/dsql.cpp +++ b/src/dsql/dsql.cpp @@ -1127,9 +1127,8 @@ static UCHAR* var_info(const dsql_msg* message, for (FB_SIZE_T i = 0; i < parameters.getCount(); i++) { const dsql_par* param = parameters[i]; - fb_assert(param); - if (param->par_index >= first_index) + if (param && param->par_index >= first_index) { dsc desc = param->par_desc; diff --git a/src/dsql/gen.cpp b/src/dsql/gen.cpp index ae55f6e6d4..ece4e4dc2d 100644 --- a/src/dsql/gen.cpp +++ b/src/dsql/gen.cpp @@ -170,9 +170,24 @@ void GEN_port(DsqlCompilerScratch* dsqlScratch, dsql_msg* message) DSqlDataTypeUtil dataTypeUtil(dsqlScratch); ULONG offset = 0; + class ParamCmp + { + public: + static int greaterThan(const Jrd::dsql_par* p1, const Jrd::dsql_par* p2) + { + return p1->par_index > p2->par_index; + } + }; + + SortedArray, dsql_par*, + DefaultKeyValue, ParamCmp> dsqlParams; + for (FB_SIZE_T i = 0; i < message->msg_parameters.getCount(); ++i) { - dsql_par* parameter = message->msg_parameters[i]; + const auto parameter = message->msg_parameters[i]; + + if (parameter->par_index) + dsqlParams.add(parameter); parameter->par_parameter = (USHORT) i; @@ -232,6 +247,13 @@ void GEN_port(DsqlCompilerScratch* dsqlScratch, dsql_msg* message) message->msg_length = offset; dsqlScratch->getDsqlStatement()->getPorts().add(message); + + // Remove gaps in par_index due to output parameters using question-marks (CALL syntax). + + USHORT parIndex = 0; + + for (auto dsqlParam : dsqlParams) + dsqlParam->par_index = ++parIndex; } diff --git a/src/dsql/parse.y b/src/dsql/parse.y index 1f4e85a811..c201fab22b 100644 --- a/src/dsql/parse.y +++ b/src/dsql/parse.y @@ -699,6 +699,7 @@ using namespace Firebird; // tokens added for Firebird 6.0 %token ANY_VALUE +%token CALL %token NAMED_ARG_ASSIGN // precedence declarations for expression evaluation @@ -890,6 +891,7 @@ dml_statement | insert { $$ = $1; } | merge { $$ = $1; } | exec_procedure { $$ = $1; } + | call { $$ = $1; } | exec_block { $$ = $1; } | select { $$ = $1; } | update { $$ = $1; } @@ -3278,6 +3280,7 @@ simple_proc_statement | delete | singleton_select | exec_procedure + | call { $$ = $1; } | exec_sql { $$ = $1; } | exec_into { $$ = $1; } | exec_function @@ -3787,6 +3790,31 @@ proc_outputs_opt | RETURNING_VALUES '(' variable_list ')' { $$ = $3; } ; +// CALL + +%type call +call + : CALL symbol_procedure_name '(' argument_list_opt ')' + { + auto node = newNode(QualifiedName(*$2), + ($4 ? $4->second : nullptr), + nullptr, + ($4 ? $4->first : nullptr)); + node->dsqlCallSyntax = true; + $$ = node; + } + | CALL symbol_package_name '.' symbol_procedure_name '(' argument_list_opt ')' + into_variable_list_opt + { + auto node = newNode(QualifiedName(*$4, *$2), + ($6 ? $6->second : nullptr), + nullptr, + ($6 ? $6->first : nullptr)); + node->dsqlCallSyntax = true; + $$ = node; + } + ; + // EXECUTE BLOCK %type exec_block @@ -4376,6 +4404,7 @@ keyword_or_column | VARBINARY | WINDOW | WITHOUT + | CALL // added in FB 6.0 ; col_opt diff --git a/src/include/firebird/impl/blr.h b/src/include/firebird/impl/blr.h index e65d1adadf..644c4ea165 100644 --- a/src/include/firebird/impl/blr.h +++ b/src/include/firebird/impl/blr.h @@ -489,8 +489,10 @@ #define blr_invsel_procedure_in_args (unsigned char) 3 #define blr_invsel_procedure_out_arg_names (unsigned char) 4 #define blr_invsel_procedure_out_args (unsigned char) 5 -#define blr_invsel_procedure_context (unsigned char) 6 -#define blr_invsel_procedure_alias (unsigned char) 7 +#define blr_invsel_procedure_inout_arg_names (unsigned char) 6 +#define blr_invsel_procedure_inout_args (unsigned char) 7 +#define blr_invsel_procedure_context (unsigned char) 8 +#define blr_invsel_procedure_alias (unsigned char) 9 #define blr_default_arg (unsigned char) 227 diff --git a/src/yvalve/gds.cpp b/src/yvalve/gds.cpp index 3e0f9c2f23..c62b1f3033 100644 --- a/src/yvalve/gds.cpp +++ b/src/yvalve/gds.cpp @@ -4056,6 +4056,8 @@ static void blr_print_verb(gds_ctl* control, SSHORT level) "in_args", "out_arg_names", "out_args", + "inout_arg_names", + "inout_args", "context", "alias" }; @@ -4103,6 +4105,7 @@ static void blr_print_verb(gds_ctl* control, SSHORT level) case blr_invsel_procedure_in_arg_names: case blr_invsel_procedure_out_arg_names: + case blr_invsel_procedure_inout_arg_names: n = blr_print_word(control); offset = blr_print_line(control, offset); @@ -4120,6 +4123,7 @@ static void blr_print_verb(gds_ctl* control, SSHORT level) case blr_invsel_procedure_in_args: case blr_invsel_procedure_out_args: + case blr_invsel_procedure_inout_args: n = blr_print_word(control); offset = blr_print_line(control, offset);