diff --git a/doc/sql.extensions/README.named_arguments.md b/doc/sql.extensions/README.named_arguments.md new file mode 100644 index 0000000000..bbcc27b119 --- /dev/null +++ b/doc/sql.extensions/README.named_arguments.md @@ -0,0 +1,52 @@ +# Named arguments for function and procedure calling (FB 6.0) + +Named arguments allows you to specify function and procedure arguments by their names, rather than only by their positions. + +It is especially useful when the routine have a lot of parameters and you want to specify them in arbitrary order or not specify some of them who have default values. + +As the positional syntax, all arguments without default values are required to be present in the call. + +It's currently not possible to mix positional and named arguments in the same call. + +## Syntax + +``` + ::= + [ .] ( [] ) + + ::= + [ .] [( )] + + ::= + EXECUTE PROCEDURE [ .] + [{ () | }] + [RETURNING_VALUES ...] + + ::= + + + + ::= + [ {, }... ] + + ::= + => +``` + +## Examples + +``` +select function_name(parameter2 => 'Two', parameter1 => 1) + from rdb$database +``` + +``` +execute procedure insert_customer( + last_name => 'SCHUMACHER', + first_name => 'MICHAEL') +``` + +``` +select * + from get_customers(city_id => 10, last_name => 'SCHUMACHER') +``` diff --git a/src/common/ParserTokens.h b/src/common/ParserTokens.h index b4b8ba17da..ae64e32bce 100644 --- a/src/common/ParserTokens.h +++ b/src/common/ParserTokens.h @@ -46,6 +46,7 @@ PARSER_TOKEN('<', "<", false) PARSER_TOKEN(TOK_LEQ, "<=", false) PARSER_TOKEN(TOK_NEQ, "<>", false) // Alias of != PARSER_TOKEN('=', "=", false) +PARSER_TOKEN(TOK_NAMED_ARG_ASSIGN, "=>", false) PARSER_TOKEN('>', ">", false) PARSER_TOKEN(TOK_GEQ, ">=", false) PARSER_TOKEN(TOK_BIND_PARAM, ":=", false) diff --git a/src/dsql/ExprNodes.cpp b/src/dsql/ExprNodes.cpp index 3c15a55a6c..abb1f33896 100644 --- a/src/dsql/ExprNodes.cpp +++ b/src/dsql/ExprNodes.cpp @@ -12875,101 +12875,274 @@ dsc* TrimNode::execute(thread_db* tdbb, Request* request) const //-------------------- -static RegisterNode regUdfCallNode({blr_function, blr_function2, blr_subfunc}); +static RegisterNode regUdfCallNode({blr_function, blr_function2, blr_subfunc, blr_invoke_function}); -UdfCallNode::UdfCallNode(MemoryPool& pool, const QualifiedName& aName, ValueListNode* aArgs) +UdfCallNode::UdfCallNode(MemoryPool& pool, const QualifiedName& aName, + ValueListNode* aArgs, ObjectsArray* aDsqlArgNames) : TypedNode(pool), name(pool, aName), args(aArgs), - function(NULL), - dsqlFunction(NULL), - isSubRoutine(false) + dsqlArgNames(aDsqlArgNames) { } DmlNode* UdfCallNode::parse(thread_db* tdbb, MemoryPool& pool, CompilerScratch* csb, const UCHAR blrOp) { - const UCHAR* savePos = csb->csb_blr_reader.getPos(); + const auto predateCheck = [&](bool condition, const char* preVerb, const char* postVerb) + { + if (!condition) + { + string str; + str.printf("%s should predate %s", preVerb, postVerb); + PAR_error(csb, Arg::Gds(isc_random) << str); + } + }; + auto& blrReader = csb->csb_blr_reader; + const UCHAR* startPos = csb->csb_blr_reader.getPos(); + + const UCHAR* argNamesPos = nullptr; + ObjectsArray* argNames = nullptr; + USHORT argCount = 0; QualifiedName name; - if (blrOp == blr_function2) - csb->csb_blr_reader.getMetaName(name.package); + const auto node = FB_NEW_POOL(pool) UdfCallNode(pool); - csb->csb_blr_reader.getMetaName(name.identifier); - - const USHORT count = name.package.length() + name.identifier.length(); - - UdfCallNode* node = FB_NEW_POOL(pool) UdfCallNode(pool, name); - - if (blrOp == blr_function && - (name.identifier == "RDB$GET_CONTEXT" || name.identifier == "RDB$SET_CONTEXT")) + if (blrOp == blr_invoke_function) { - csb->csb_blr_reader.setPos(savePos); - return SysFuncCallNode::parse(tdbb, pool, csb, blr_sys_function); - } + UCHAR subCode; - if (blrOp == blr_subfunc) - { - DeclareSubFuncNode* declareNode; - - for (auto curCsb = csb; curCsb && !node->function; curCsb = curCsb->mainCsb) + while ((subCode = blrReader.getByte()) != blr_end) { - if (curCsb->subFunctions.get(name.identifier, declareNode)) - node->function = declareNode->routine; - } - } - else if (!node->function) - node->function = Function::lookup(tdbb, name, false); - - Function* function = node->function; - - if (function) - { - if (function->isImplemented() && !function->isDefined()) - { - if (tdbb->getAttachment()->isGbak() || (tdbb->tdbb_flags & TDBB_replicator)) + switch (subCode) { - PAR_warning(Arg::Warning(isc_funnotdef) << Arg::Str(name.toString()) << - Arg::Warning(isc_modnotfound)); - } - else - { - csb->csb_blr_reader.seekBackward(count); - PAR_error(csb, Arg::Gds(isc_funnotdef) << Arg::Str(name.toString()) << - Arg::Gds(isc_modnotfound)); + case blr_invoke_function_type: + { + UCHAR functionType = blrReader.getByte(); + + switch (functionType) + { + case blr_invoke_function_type_packaged: + blrReader.getMetaName(name.package); + break; + + case blr_invoke_function_type_standalone: + case blr_invoke_function_type_sub: + break; + + default: + PAR_error(csb, Arg::Gds(isc_random) << "Invalid blr_invoke_function_type"); + break; + } + + blrReader.getMetaName(name.identifier); + + if (functionType == blr_invoke_function_type_sub) + { + for (auto curCsb = csb; curCsb && !node->function; curCsb = curCsb->mainCsb) + { + if (DeclareSubFuncNode* declareNode; curCsb->subFunctions.get(name.identifier, declareNode)) + node->function = declareNode->routine; + } + } + else if (!node->function) + node->function = Function::lookup(tdbb, name, false); + + break; + } + + case blr_invoke_function_arg_names: + { + predateCheck(node->function, "blr_invoke_function_type", "blr_invoke_function_arg_names"); + predateCheck(!node->args, "blr_invoke_function_arg_names", "blr_invoke_function_arg_names"); + + argNamesPos = blrReader.getPos(); + USHORT argNamesCount = blrReader.getWord(); + MetaName argName; + + argNames = FB_NEW_POOL(pool) ObjectsArray(pool); + + while (argNamesCount--) + { + blrReader.getMetaName(argName); + argNames->add(argName); + } + + break; + } + + case blr_invoke_function_args: + predateCheck(node->function, "blr_invoke_function_type", "blr_invoke_function_args"); + + argCount = blrReader.getWord(); + node->args = PAR_args(tdbb, csb, argCount, (argNames ? argCount : MAX(argCount, node->function->fun_inputs))); + break; + + default: + PAR_error(csb, Arg::Gds(isc_random) << "Invalid blr_invoke_function sub code"); } } } else { - csb->csb_blr_reader.seekBackward(count); - PAR_error(csb, Arg::Gds(isc_funnotdef) << Arg::Str(name.toString())); + if (blrOp == blr_function2) + blrReader.getMetaName(name.package); + + blrReader.getMetaName(name.identifier); + + if (blrOp == blr_function && + (name.identifier == "RDB$GET_CONTEXT" || name.identifier == "RDB$SET_CONTEXT")) + { + blrReader.setPos(startPos); + return SysFuncCallNode::parse(tdbb, pool, csb, blr_sys_function); + } + + if (blrOp == blr_subfunc) + { + for (auto curCsb = csb; curCsb && !node->function; curCsb = curCsb->mainCsb) + { + if (DeclareSubFuncNode* declareNode; curCsb->subFunctions.get(name.identifier, declareNode)) + node->function = declareNode->routine; + } + } + else if (!node->function) + node->function = Function::lookup(tdbb, name, false); + + argCount = blrReader.getByte(); + node->args = PAR_args(tdbb, csb, argCount, node->function->fun_inputs); } - node->isSubRoutine = function->isSubRoutine(); - - const UCHAR argCount = csb->csb_blr_reader.getByte(); - - // Check to see if the argument count matches. - if (argCount < function->fun_inputs - function->getDefaultCount() || argCount > function->fun_inputs) - PAR_error(csb, Arg::Gds(isc_funmismat) << name.toString()); - - node->args = PAR_args(tdbb, csb, argCount, function->fun_inputs); - - for (USHORT i = argCount; i < function->fun_inputs; ++i) + if (!node->function) { - Parameter* const parameter = function->getInputFields()[i]; - node->args->items[i] = CMP_clone_node(tdbb, csb, parameter->prm_default_value); + blrReader.setPos(startPos); + PAR_error(csb, Arg::Gds(isc_funnotdef) << name.toString()); } + if (node->function->isImplemented() && !node->function->isDefined()) + { + if (tdbb->getAttachment()->isGbak() || (tdbb->tdbb_flags & TDBB_replicator)) + { + PAR_warning(Arg::Warning(isc_funnotdef) << name.toString() << + Arg::Warning(isc_modnotfound)); + } + else + { + blrReader.setPos(startPos); + PAR_error(csb, Arg::Gds(isc_funnotdef) << name.toString() << + Arg::Gds(isc_modnotfound)); + } + } + + node->name = name; + node->isSubRoutine = node->function->isSubRoutine(); + + Arg::StatusVector mismatchStatus; + mismatchStatus << Arg::Gds(isc_fun_param_mismatch) << name.toString(); + const auto mismatchInitialLength = mismatchStatus.length(); + + if (!node->args) + node->args = FB_NEW_POOL(pool) ValueListNode(pool); + + if (argNames && argNames->getCount() != node->args->items.getCount()) + { + blrReader.setPos(argNamesPos); + PAR_error(csb, + Arg::Gds(isc_random) << "blr_invoke_function_arg_names count differs from blr_invoke_function_args"); + } + + if (argNames) + { + LeftPooledMap> argsByName; + auto argIt = node->args->items.begin(); + + for (const auto& argName : *argNames) + { + if (argsByName.put(argName, *argIt++)) + mismatchStatus << Arg::Gds(isc_param_multiple_assignments) << argName; + } + + node->args->items.resize(node->function->getInputFields().getCount()); + argIt = node->args->items.begin(); + + for (auto& parameter : node->function->getInputFields()) + { + if (const auto argValue = argsByName.get(parameter->prm_name)) + { + *argIt = *argValue; + argsByName.remove(parameter->prm_name); + } + else + { + if (parameter->prm_default_value) + *argIt = CMP_clone_node(tdbb, csb, parameter->prm_default_value); + else + mismatchStatus << Arg::Gds(isc_param_no_default_not_specified) << parameter->prm_name; + } + + ++argIt; + } + + if (argsByName.hasData()) + { + for (const auto& argPair : argsByName) + mismatchStatus << Arg::Gds(isc_param_not_exist) << argPair.first; + } + } + else + { + // Check to see if the argument count matches. + + if (argCount > node->function->fun_inputs) + mismatchStatus << Arg::Gds(isc_wronumarg); + else if (argCount < node->function->fun_inputs - node->function->getDefaultCount()) + { + unsigned pos = 0; + + for (auto& parameter : node->function->getInputFields()) + { + if (++pos <= argCount) + continue; + + mismatchStatus << Arg::Gds(isc_param_no_default_not_specified) << parameter->prm_name; + + if (pos >= node->function->fun_inputs - node->function->getDefaultCount()) + break; + } + } + else + { + for (unsigned pos = argCount; pos < node->function->getInputFields().getCount(); ++pos) + { + auto parameter = node->function->getInputFields()[pos]; + fb_assert(parameter->prm_default_value); + node->args->items[pos] = CMP_clone_node(tdbb, csb, parameter->prm_default_value); + } + } + } + + if (mismatchStatus.length() > mismatchInitialLength) + status_exception::raise(mismatchStatus); + // CVC: I will track ufds only if a function is not being dropped. - if (!function->isSubRoutine() && csb->collectingDependencies()) + if (!node->function->isSubRoutine() && csb->collectingDependencies()) { - CompilerScratch::Dependency dependency(obj_udf); - dependency.function = function; - csb->addDependency(dependency); + { // scope + CompilerScratch::Dependency dependency(obj_udf); + dependency.function = node->function; + csb->addDependency(dependency); + } + + if (argNames) + { + for (const auto& argName : *argNames) + { + CompilerScratch::Dependency dependency(obj_udf); + dependency.function = node->function; + dependency.subName = &argName; + csb->addDependency(dependency); + } + } } return node; @@ -12992,6 +13165,45 @@ void UdfCallNode::setParameterName(dsql_par* parameter) const void UdfCallNode::genBlr(DsqlCompilerScratch* dsqlScratch) { + if (dsqlArgNames || args->items.getCount() >= UCHAR_MAX) + { + dsqlScratch->appendUChar(blr_invoke_function); + + dsqlScratch->appendUChar(blr_invoke_function_type); + + if (dsqlFunction->udf_name.package.hasData()) + { + dsqlScratch->appendUChar(blr_invoke_function_type_packaged); + dsqlScratch->appendMetaString(dsqlFunction->udf_name.package.c_str()); + } + else + { + dsqlScratch->appendUChar((dsqlFunction->udf_flags & UDF_subfunc) ? + blr_invoke_function_type_sub : blr_invoke_function_type_standalone); + } + + dsqlScratch->appendMetaString(dsqlFunction->udf_name.identifier.c_str()); + + if (dsqlArgNames && dsqlArgNames->hasData()) + { + dsqlScratch->appendUChar(blr_invoke_function_arg_names); + dsqlScratch->appendUShort(dsqlArgNames->getCount()); + + for (auto& argName : *dsqlArgNames) + dsqlScratch->appendMetaString(argName.c_str()); + } + + dsqlScratch->appendUChar(blr_invoke_function_args); + dsqlScratch->appendUShort(args->items.getCount()); + + for (auto& arg : args->items) + GEN_expr(dsqlScratch, arg); + + dsqlScratch->appendUChar(blr_end); + + return; + } + if (dsqlFunction->udf_name.package.isEmpty()) dsqlScratch->appendUChar((dsqlFunction->udf_flags & UDF_subfunc) ? blr_subfunc : blr_function); else @@ -13355,8 +13567,11 @@ dsc* UdfCallNode::execute(thread_db* tdbb, Request* request) const ValueExprNode* UdfCallNode::dsqlPass(DsqlCompilerScratch* dsqlScratch) { - UdfCallNode* node = FB_NEW_POOL(dsqlScratch->getPool()) UdfCallNode(dsqlScratch->getPool(), name, - doDsqlPass(dsqlScratch, args)); + const auto node = FB_NEW_POOL(dsqlScratch->getPool()) UdfCallNode(dsqlScratch->getPool(), name, + doDsqlPass(dsqlScratch, args), + dsqlArgNames ? + FB_NEW_POOL(dsqlScratch->getPool()) ObjectsArray(dsqlScratch->getPool(), *dsqlArgNames) : + nullptr); if (name.package.isEmpty()) { @@ -13374,28 +13589,56 @@ ValueExprNode* UdfCallNode::dsqlPass(DsqlCompilerScratch* dsqlScratch) Arg::Gds(isc_random) << Arg::Str(name.toString())); } - const USHORT arg_count = node->dsqlFunction->udf_arguments.getCount(); - const USHORT count = node->args->items.getCount(); - if (count > arg_count || count < arg_count - node->dsqlFunction->udf_def_count) - ERRD_post(Arg::Gds(isc_fun_param_mismatch) << Arg::Str(name.toString())); - - unsigned pos = 0; - - for (auto& arg : node->args->items) + if (node->dsqlArgNames) { - if (pos < node->dsqlFunction->udf_arguments.getCount()) + fb_assert(node->dsqlArgNames->getCount() == node->args->items.getCount()); + + LeftPooledMap argsByName; + + for (const auto& arg : node->dsqlFunction->udf_arguments) + argsByName.put(arg.name, &arg.desc); + + bool mismatchError = false; + Arg::StatusVector mismatchStatus; + mismatchStatus << Arg::Gds(isc_fun_param_mismatch) << Arg::Str(name.toString()); + + auto argIt = node->args->items.begin(); + + for (const auto& argName : *node->dsqlArgNames) { - PASS1_set_parameter_type(dsqlScratch, arg, - [&] (dsc* desc) { *desc = node->dsqlFunction->udf_arguments[pos]; }, - false); - } - else - { - // We should complain here in the future! The parameter is - // out of bounds or the function doesn't declare input params. + if (const auto argDescPtr = argsByName.get(argName)) + { + PASS1_set_parameter_type(dsqlScratch, *argIt, + [&] (dsc* desc) { *desc = **argDescPtr; }, + false); + } + else + { + mismatchError = true; + mismatchStatus << Arg::Gds(isc_param_not_exist) << argName; + } + + ++argIt; } - ++pos; + if (mismatchError) + status_exception::raise(mismatchStatus); + } + else + { + unsigned pos = 0; + + for (auto& arg : node->args->items) + { + if (pos < node->dsqlFunction->udf_arguments.getCount()) + { + PASS1_set_parameter_type(dsqlScratch, arg, + [&] (dsc* desc) { *desc = node->dsqlFunction->udf_arguments[pos].desc; }, + false); + } + + ++pos; + } } return node; diff --git a/src/dsql/ExprNodes.h b/src/dsql/ExprNodes.h index b20d61c7cd..a7ae73afc1 100644 --- a/src/dsql/ExprNodes.h +++ b/src/dsql/ExprNodes.h @@ -2134,11 +2134,12 @@ private: }; public: - explicit UdfCallNode(MemoryPool& pool, const QualifiedName& aName, - ValueListNode* aArgs = NULL); + UdfCallNode(MemoryPool& pool, const QualifiedName& aName = {}, + ValueListNode* aArgs = nullptr, Firebird::ObjectsArray* aDsqlArgNames = nullptr); static DmlNode* parse(thread_db* tdbb, MemoryPool& pool, CompilerScratch* csb, const UCHAR blrOp); +public: virtual void getChildren(NodeRefsHolder& holder, bool dsql) const { ValueExprNode::getChildren(holder, dsql); @@ -2172,11 +2173,12 @@ public: public: QualifiedName name; NestConst args; + NestConst> dsqlArgNames; NestConst function; private: - dsql_udf* dsqlFunction; - bool isSubRoutine; + dsql_udf* dsqlFunction = nullptr; + bool isSubRoutine = false; }; diff --git a/src/dsql/Nodes.h b/src/dsql/Nodes.h index ce98db0f2f..6075ee0400 100644 --- a/src/dsql/Nodes.h +++ b/src/dsql/Nodes.h @@ -1270,6 +1270,12 @@ public: items.push(arg1); } + ValueListNode(MemoryPool& pool) + : TypedNode(pool), + items(pool, INITIAL_CAPACITY) + { + } + virtual void getChildren(NodeRefsHolder& holder, bool dsql) const { ListExprNode::getChildren(holder, dsql); diff --git a/src/dsql/StmtNodes.cpp b/src/dsql/StmtNodes.cpp index 3d92cfbfbd..8bc3df0eed 100644 --- a/src/dsql/StmtNodes.cpp +++ b/src/dsql/StmtNodes.cpp @@ -1664,8 +1664,8 @@ DeclareSubFuncNode* DeclareSubFuncNode::dsqlPass(DsqlCompilerScratch* dsqlScratc if (!implemetingForward) { - // ASF: dsqlFunction->udf_arguments is only checked for its count for now. - dsqlFunction->udf_arguments.add(dsc()); + // ASF: dsqlFunction->udf_arguments types (desc) are not checked for now. + dsqlFunction->udf_arguments.add().name = param->name; } if (param->defaultClause) @@ -2905,79 +2905,304 @@ const StmtNode* ErrorHandlerNode::execute(thread_db* /*tdbb*/, Request* request, static RegisterNode regExecProcedureNode( - {blr_exec_proc, blr_exec_proc2, blr_exec_pid, blr_exec_subproc}); + {blr_exec_proc, blr_exec_proc2, blr_exec_pid, blr_exec_subproc, blr_invoke_procedure}); // Parse an execute procedure reference. DmlNode* ExecProcedureNode::parse(thread_db* tdbb, MemoryPool& pool, CompilerScratch* csb, const UCHAR blrOp) { - SET_TDBB(tdbb); + const auto predateCheck = [&](bool condition, const char* preVerb, const char* postVerb) + { + if (!condition) + { + string str; + str.printf("%s should predate %s", preVerb, postVerb); + PAR_error(csb, Arg::Gds(isc_random) << str); + } + }; - const auto blrStartPos = csb->csb_blr_reader.getPos(); - jrd_prc* procedure = NULL; + auto& blrReader = csb->csb_blr_reader; + const auto blrStartPos = blrReader.getPos(); + + const UCHAR* inArgNamesPos = nullptr; + ObjectsArray* inArgNames = nullptr; + USHORT inArgCount = 0; + const UCHAR* outArgNamesPos = nullptr; + ObjectsArray* outArgNames = nullptr; + USHORT outArgCount = 0; QualifiedName name; - if (blrOp == blr_exec_pid) - { - const USHORT pid = csb->csb_blr_reader.getWord(); - if (!(procedure = MET_lookup_procedure_id(tdbb, pid, false, false, 0))) - name.identifier.printf("id %d", pid); - } - else - { - if (blrOp == blr_exec_proc2) - csb->csb_blr_reader.getMetaName(name.package); + const auto node = FB_NEW_POOL(pool) ExecProcedureNode(pool); - csb->csb_blr_reader.getMetaName(name.identifier); - - if (blrOp == blr_exec_subproc) + switch (blrOp) + { + case blr_invoke_procedure: { - DeclareSubProcNode* declareNode; + UCHAR subCode; - for (auto curCsb = csb; curCsb && !procedure; curCsb = curCsb->mainCsb) + while ((subCode = blrReader.getByte()) != blr_end) { - if (curCsb->subProcedures.get(name.identifier, declareNode)) - procedure = declareNode->routine; + switch (subCode) + { + case blr_invsel_procedure_type: + { + UCHAR procedureType = blrReader.getByte(); + + switch (procedureType) + { + case blr_invsel_procedure_type_packaged: + blrReader.getMetaName(name.package); + break; + + case blr_invsel_procedure_type_standalone: + case blr_invsel_procedure_type_sub: + break; + + default: + PAR_error(csb, Arg::Gds(isc_random) << "Invalid blr_invsel_procedure_type"); + break; + } + + blrReader.getMetaName(name.identifier); + + if (procedureType == blr_invsel_procedure_type_sub) + { + for (auto curCsb = csb; curCsb && !node->procedure; curCsb = curCsb->mainCsb) + { + if (const auto declareNode = curCsb->subProcedures.get(name.identifier)) + node->procedure = (*declareNode)->routine; + } + } + else if (!node->procedure) + node->procedure = MET_lookup_procedure(tdbb, name, false); + + break; + } + + case blr_invsel_procedure_in_arg_names: + { + predateCheck(node->procedure, "blr_invsel_procedure_type", "blr_invsel_procedure_in_arg_names"); + predateCheck(!node->inputSources, + "blr_invsel_procedure_in_arg_names", "blr_invsel_procedure_in_args"); + + inArgNamesPos = blrReader.getPos(); + USHORT inArgNamesCount = blrReader.getWord(); + MetaName argName; + + inArgNames = FB_NEW_POOL(pool) ObjectsArray(pool); + + while (inArgNamesCount--) + { + blrReader.getMetaName(argName); + inArgNames->add(argName); + } + + break; + } + + case blr_invsel_procedure_in_args: + predateCheck(node->procedure, "blr_invsel_procedure_type", "blr_invsel_procedure_in_args"); + inArgCount = blrReader.getWord(); + node->inputSources = PAR_args(tdbb, csb, inArgCount, + (inArgNames ? inArgCount : MAX(inArgCount, node->procedure->getInputFields().getCount()))); + break; + + case blr_invsel_procedure_out_arg_names: + { + predateCheck(node->procedure, + "blr_invsel_procedure_type", "blr_invsel_procedure_out_arg_names"); + + predateCheck(!node->outputTargets, + "blr_invsel_procedure_out_arg_names", "blr_invsel_procedure_out_args"); + + outArgNamesPos = blrReader.getPos(); + USHORT outArgNamesCount = blrReader.getWord(); + MetaName argName; + + outArgNames = FB_NEW_POOL(pool) ObjectsArray(pool); + + while (outArgNamesCount--) + { + blrReader.getMetaName(argName); + outArgNames->add(argName); + } + + break; + } + + case blr_invsel_procedure_out_args: + predateCheck(node->procedure, "blr_invsel_procedure_type", "blr_invsel_procedure_out_args"); + outArgCount = blrReader.getWord(); + node->outputTargets = PAR_args(tdbb, csb, outArgCount, outArgCount); + break; + + default: + PAR_error(csb, Arg::Gds(isc_random) << "Invalid blr_invoke_procedure sub code"); + } } - } - else - procedure = MET_lookup_procedure(tdbb, name, false); - } - if (!procedure) - PAR_error(csb, Arg::Gds(isc_prcnotdef) << Arg::Str(name.toString())); - else - { - if (procedure->isImplemented() && !procedure->isDefined()) + break; + } + + case blr_exec_pid: { - if (tdbb->getAttachment()->isGbak() || (tdbb->tdbb_flags & TDBB_replicator)) + const USHORT pid = blrReader.getWord(); + if (!(node->procedure = MET_lookup_procedure_id(tdbb, pid, false, false, 0))) + name.identifier.printf("id %d", pid); + break; + } + + default: + if (blrOp == blr_exec_proc2) + blrReader.getMetaName(name.package); + + blrReader.getMetaName(name.identifier); + + if (blrOp == blr_exec_subproc) { - PAR_warning( - Arg::Warning(isc_prcnotdef) << Arg::Str(name.toString()) << - Arg::Warning(isc_modnotfound)); + for (auto curCsb = csb; curCsb && !node->procedure; curCsb = curCsb->mainCsb) + { + if (const auto declareNode = curCsb->subProcedures.get(name.identifier)) + node->procedure = (*declareNode)->routine; + } } else + node->procedure = MET_lookup_procedure(tdbb, name, false); + + break; + } + + if (!node->procedure) + { + blrReader.setPos(blrStartPos); + PAR_error(csb, Arg::Gds(isc_prcnotdef) << name.toString()); + } + + if (blrOp != blr_invoke_procedure) + { + inArgCount = blrReader.getWord(); + node->inputSources = PAR_args(tdbb, csb, inArgCount, inArgCount); + + outArgCount = blrReader.getWord(); + node->outputTargets = PAR_args(tdbb, csb, outArgCount, outArgCount); + } + + if (!node->inputSources) + node->inputSources = FB_NEW_POOL(pool) ValueListNode(pool); + + if (!node->outputTargets) + node->outputTargets = FB_NEW_POOL(pool) ValueListNode(pool); + + if (inArgNames && inArgNames->getCount() != node->inputSources->items.getCount()) + { + blrReader.setPos(inArgNamesPos); + PAR_error(csb, + Arg::Gds(isc_random) << + "blr_invsel_procedure_in_arg_names count differs from blr_invsel_procedure_in_args"); + } + + 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"); + } + + if (node->procedure->isImplemented() && !node->procedure->isDefined()) + { + if (tdbb->getAttachment()->isGbak() || (tdbb->tdbb_flags & TDBB_replicator)) + { + PAR_warning( + Arg::Warning(isc_prcnotdef) << name.toString() << + Arg::Warning(isc_modnotfound)); + } + else + { + csb->csb_blr_reader.setPos(blrStartPos); + PAR_error(csb, + Arg::Gds(isc_prcnotdef) << name.toString() << + Arg::Gds(isc_modnotfound)); + } + } + + Arg::StatusVector mismatchStatus; + mismatchStatus << Arg::Gds(isc_prcmismat) << node->procedure->getName().toString(); + const auto mismatchInitialLength = mismatchStatus.length(); + + node->inputTargets = FB_NEW_POOL(pool) ValueListNode(pool, node->procedure->getInputFields().getCount()); + + mismatchStatus << CMP_procedure_arguments( + tdbb, + csb, + node->procedure, + true, + inArgCount, + inArgNames, + node->inputSources, + node->inputTargets, + node->inputMessage); + + mismatchStatus << CMP_procedure_arguments( + tdbb, + csb, + node->procedure, + false, + outArgCount, + outArgNames, + node->outputTargets, + node->outputSources, + node->outputMessage); + + if (mismatchStatus.length() > mismatchInitialLength) + status_exception::raise(mismatchStatus); + + if (csb->collectingDependencies() && !node->procedure->isSubRoutine()) + { + { // scope + CompilerScratch::Dependency dependency(obj_procedure); + dependency.procedure = node->procedure; + csb->addDependency(dependency); + } + + if (inArgNames) + { + for (const auto& argName : *inArgNames) { - csb->csb_blr_reader.setPos(blrStartPos); - PAR_error(csb, - Arg::Gds(isc_prcnotdef) << Arg::Str(name.toString()) << - Arg::Gds(isc_modnotfound)); + CompilerScratch::Dependency dependency(obj_procedure); + dependency.procedure = node->procedure; + dependency.subName = &argName; + csb->addDependency(dependency); + } + } + + if (outArgNames) + { + for (const auto& argName : *outArgNames) + { + CompilerScratch::Dependency dependency(obj_procedure); + dependency.procedure = node->procedure; + dependency.subName = &argName; + csb->addDependency(dependency); } } } - ExecProcedureNode* node = FB_NEW_POOL(pool) ExecProcedureNode(pool); - node->procedure = procedure; - - PAR_procedure_parms(tdbb, csb, procedure, node->inputMessage.getAddress(), - node->inputSources.getAddress(), node->inputTargets.getAddress(), true); - PAR_procedure_parms(tdbb, csb, procedure, node->outputMessage.getAddress(), - node->outputSources.getAddress(), node->outputTargets.getAddress(), false); - - if (csb->collectingDependencies() && !procedure->isSubRoutine()) + if (node->inputSources && node->inputSources->items.isEmpty()) { - CompilerScratch::Dependency dependency(obj_procedure); - dependency.procedure = procedure; - csb->addDependency(dependency); + delete node->inputSources.getObject(); + node->inputSources = nullptr; + + delete node->inputTargets.getObject(); + node->inputTargets = nullptr; + } + + if (node->outputSources && node->outputSources->items.isEmpty()) + { + delete node->outputSources.getObject(); + node->outputSources = nullptr; + + delete node->outputTargets.getObject(); + node->outputTargets = nullptr; } return node; @@ -3007,7 +3232,13 @@ ExecProcedureNode* ExecProcedureNode::dsqlPass(DsqlCompilerScratch* dsqlScratch) if (!dsqlScratch->isPsql()) dsqlScratch->getDsqlStatement()->setType(DsqlStatement::TYPE_EXEC_PROCEDURE); - ExecProcedureNode* node = FB_NEW_POOL(dsqlScratch->getPool()) ExecProcedureNode(dsqlScratch->getPool(), dsqlName); + const auto node = FB_NEW_POOL(dsqlScratch->getPool()) ExecProcedureNode(dsqlScratch->getPool(), dsqlName, + doDsqlPass(dsqlScratch, inputSources), + nullptr, + dsqlInputArgNames ? + FB_NEW_POOL(dsqlScratch->getPool()) ObjectsArray(dsqlScratch->getPool(), *dsqlInputArgNames) : + nullptr); + node->dsqlProcedure = procedure; if (node->dsqlName.package.isEmpty() && procedure->prc_name.package.hasData()) @@ -3015,28 +3246,63 @@ ExecProcedureNode* ExecProcedureNode::dsqlPass(DsqlCompilerScratch* dsqlScratch) // Handle input parameters. - const USHORT count = inputSources ? inputSources->items.getCount() : 0; - if (count > procedure->prc_in_count || count < procedure->prc_in_count - procedure->prc_def_count) - ERRD_post(Arg::Gds(isc_prcmismat) << Arg::Str(dsqlName.toString())); - - node->inputSources = doDsqlPass(dsqlScratch, inputSources); - - if (count) + if (node->dsqlInputArgNames) { - // Initialize this stack variable, and make it look like a node. - dsc desc_node; + fb_assert(node->dsqlInputArgNames->getCount() == node->inputSources->items.getCount()); - NestConst* ptr = node->inputSources->items.begin(); - const NestConst* end = node->inputSources->items.end(); + LeftPooledMap argsByName; - for (const dsql_fld* field = procedure->prc_inputs; ptr != end; ++ptr, field = field->fld_next) + for (const auto* field = procedure->prc_inputs; field; field = field->fld_next) + argsByName.put(field->fld_name, field); + + bool mismatchError = false; + Arg::StatusVector mismatchStatus; + mismatchStatus << Arg::Gds(isc_prcmismat) << Arg::Str(dsqlName.toString()); + + auto argIt = node->inputSources->items.begin(); + + for (const auto& argName : *node->dsqlInputArgNames) { - DEV_BLKCHK(field, dsql_type_fld); - DEV_BLKCHK(*ptr, dsql_type_nod); - DsqlDescMaker::fromField(&desc_node, field); - PASS1_set_parameter_type(dsqlScratch, *ptr, - [&] (dsc* desc) { *desc = desc_node; }, - false); + if (const auto field = argsByName.get(argName)) + { + dsc descNode; + DsqlDescMaker::fromField(&descNode, *field); + + PASS1_set_parameter_type(dsqlScratch, *argIt, + [&] (dsc* desc) { *desc = descNode; }, + false); + } + else + { + mismatchError = true; + mismatchStatus << Arg::Gds(isc_param_not_exist) << argName; + } + + ++argIt; + } + + if (mismatchError) + status_exception::raise(mismatchStatus); + } + else + { + if (inputSources && inputSources->items.hasData()) + { + // Initialize this stack variable, and make it look like a node. + dsc descNode; + + auto ptr = node->inputSources->items.begin(); + const auto end = node->inputSources->items.end(); + + for (const dsql_fld* field = procedure->prc_inputs; field && ptr != end; ++ptr, field = field->fld_next) + { + DEV_BLKCHK(field, dsql_type_fld); + DEV_BLKCHK(*ptr, dsql_type_nod); + DsqlDescMaker::fromField(&descNode, field); + PASS1_set_parameter_type(dsqlScratch, *ptr, + [&] (dsc* desc) { *desc = descNode; }, + false); + } } } @@ -3125,7 +3391,7 @@ string ExecProcedureNode::internalPrint(NodePrinter& printer) const void ExecProcedureNode::genBlr(DsqlCompilerScratch* dsqlScratch) { - const dsql_msg* message = NULL; + const dsql_msg* message = nullptr; if (dsqlScratch->getDsqlStatement()->getType() == DsqlStatement::TYPE_EXEC_PROCEDURE) { @@ -3137,44 +3403,94 @@ void ExecProcedureNode::genBlr(DsqlCompilerScratch* dsqlScratch) } } - if (dsqlName.package.hasData()) + if (dsqlInputArgNames) { - dsqlScratch->appendUChar(blr_exec_proc2); - dsqlScratch->appendMetaString(dsqlName.package.c_str()); + dsqlScratch->appendUChar(blr_invoke_procedure); + + dsqlScratch->appendUChar(blr_invsel_procedure_type); + + if (dsqlName.package.hasData()) + { + dsqlScratch->appendUChar(blr_invsel_procedure_type_packaged); + dsqlScratch->appendMetaString(dsqlName.package.c_str()); + } + else + { + dsqlScratch->appendUChar((dsqlProcedure->prc_flags & PRC_subproc) ? + blr_invsel_procedure_type_sub : blr_invsel_procedure_type_standalone); + } + + dsqlScratch->appendMetaString(dsqlName.identifier.c_str()); + + // Input parameters. + if (inputSources) + { + if (dsqlInputArgNames && dsqlInputArgNames->hasData()) + { + dsqlScratch->appendUChar(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->appendUShort(inputSources->items.getCount()); + + for (auto& arg : inputSources->items) + GEN_expr(dsqlScratch, arg); + } + + // Output parameters. + if (outputSources) + { + dsqlScratch->appendUChar(blr_invsel_procedure_out_args); + dsqlScratch->appendUShort(outputSources->items.getCount()); + + for (auto& arg : outputSources->items) + GEN_expr(dsqlScratch, arg); + } + + dsqlScratch->appendUChar(blr_end); } else { - dsqlScratch->appendUChar( - (dsqlProcedure->prc_flags & PRC_subproc) ? blr_exec_subproc : blr_exec_proc); + if (dsqlName.package.hasData()) + { + dsqlScratch->appendUChar(blr_exec_proc2); + dsqlScratch->appendMetaString(dsqlName.package.c_str()); + } + else + dsqlScratch->appendUChar((dsqlProcedure->prc_flags & PRC_subproc) ? blr_exec_subproc : blr_exec_proc); + + dsqlScratch->appendMetaString(dsqlName.identifier.c_str()); + + // Input parameters. + if (inputSources) + { + dsqlScratch->appendUShort(inputSources->items.getCount()); + auto ptr = inputSources->items.begin(); + const auto end = inputSources->items.end(); + + while (ptr < end) + GEN_expr(dsqlScratch, *ptr++); + } + else + dsqlScratch->appendUShort(0); + + // Output parameters. + if (outputSources) + { + dsqlScratch->appendUShort(outputSources->items.getCount()); + auto ptr = outputSources->items.begin(); + + for (const auto end = outputSources->items.end(); ptr != end; ++ptr) + GEN_expr(dsqlScratch, *ptr); + } + else + dsqlScratch->appendUShort(0); } - dsqlScratch->appendMetaString(dsqlName.identifier.c_str()); - - // Input parameters. - if (inputSources) - { - dsqlScratch->appendUShort(inputSources->items.getCount()); - NestConst* ptr = inputSources->items.begin(); - const NestConst* end = inputSources->items.end(); - - while (ptr < end) - GEN_expr(dsqlScratch, *ptr++); - } - else - dsqlScratch->appendUShort(0); - - // Output parameters. - if (outputSources) - { - dsqlScratch->appendUShort(outputSources->items.getCount()); - NestConst* ptr = outputSources->items.begin(); - - for (const NestConst* end = outputSources->items.end(); ptr != end; ++ptr) - GEN_expr(dsqlScratch, *ptr); - } - else - dsqlScratch->appendUShort(0); - if (message) dsqlScratch->appendUChar(blr_end); } diff --git a/src/dsql/StmtNodes.h b/src/dsql/StmtNodes.h index b62c27f8af..a9d3cad8e4 100644 --- a/src/dsql/StmtNodes.h +++ b/src/dsql/StmtNodes.h @@ -589,23 +589,20 @@ class ExecProcedureNode final : public TypedNode* aDsqlInputArgNames = nullptr) : TypedNode(pool), dsqlName(pool, aDsqlName), - dsqlProcedure(NULL), inputSources(aInputs), - inputTargets(NULL), - inputMessage(NULL), outputSources(aOutputs), - outputTargets(NULL), - outputMessage(NULL), - procedure(NULL) + dsqlInputArgNames(aDsqlInputArgNames) { } public: static DmlNode* parse(thread_db* tdbb, MemoryPool& pool, CompilerScratch* csb, const UCHAR blrOp); +public: virtual Firebird::string internalPrint(NodePrinter& printer) const; virtual ExecProcedureNode* dsqlPass(DsqlCompilerScratch* dsqlScratch); virtual void genBlr(DsqlCompilerScratch* dsqlScratch); @@ -619,7 +616,7 @@ private: public: QualifiedName dsqlName; - dsql_prc* dsqlProcedure; + dsql_prc* dsqlProcedure = nullptr; NestConst inputSources; NestConst inputTargets; NestConst inputMessage; @@ -627,6 +624,7 @@ public: NestConst outputTargets; NestConst outputMessage; NestConst procedure; + NestConst> dsqlInputArgNames; }; diff --git a/src/dsql/dsql.h b/src/dsql/dsql.h index 3dc968c481..422e36c396 100644 --- a/src/dsql/dsql.h +++ b/src/dsql/dsql.h @@ -318,6 +318,19 @@ enum prc_flags_vals { //! User defined function block class dsql_udf : public pool_alloc { +public: + class Argument + { + public: + Argument(MemoryPool& p) + : name(p) + {} + + public: + MetaName name; + dsc desc; + }; + public: explicit dsql_udf(MemoryPool& p) : udf_name(p), @@ -332,7 +345,7 @@ public: SSHORT udf_character_set_id = 0; USHORT udf_flags = 0; QualifiedName udf_name; - Firebird::Array udf_arguments; + Firebird::ObjectsArray udf_arguments; bool udf_private = false; // Packaged private function SSHORT udf_def_count = 0; // number of inputs with default values }; diff --git a/src/dsql/metd.epp b/src/dsql/metd.epp index 1348e774d2..e9d23ec088 100644 --- a/src/dsql/metd.epp +++ b/src/dsql/metd.epp @@ -770,7 +770,9 @@ dsql_udf* METD_get_function(jrd_tra* transaction, DsqlCompilerScratch* dsqlScrat defaults++; } - userFunc->udf_arguments.add(d); + auto& argument = userFunc->udf_arguments.add(); + argument.name = X.RDB$ARGUMENT_NAME; + argument.desc = d; } } END_FOR @@ -842,7 +844,9 @@ dsql_udf* METD_get_function(jrd_tra* transaction, DsqlCompilerScratch* dsqlScrat defaults++; } - userFunc->udf_arguments.add(d); + auto& argument = userFunc->udf_arguments.add(); + argument.name = X.RDB$ARGUMENT_NAME; + argument.desc = d; } } } diff --git a/src/dsql/parse.y b/src/dsql/parse.y index 3d087ae1e4..8bc45d0f38 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 NAMED_ARG_ASSIGN // precedence declarations for expression evaluation @@ -761,6 +762,8 @@ using namespace Firebird; Jrd::DbFileClause* dbFileClause; Firebird::Array >* dbFilesClause; Jrd::ExternalClause* externalClause; + Firebird::NonPooledPair* namedArgument; + Firebird::NonPooledPair*, Jrd::ValueListNode*>* namedArguments; Firebird::Array >* parametersClause; Jrd::WindowClause* windowClause; Jrd::WindowClause::FrameExtent* windowClauseFrameExtent; @@ -3753,16 +3756,28 @@ fetch_scroll($cursorStmtNode) %type exec_procedure exec_procedure : EXECUTE PROCEDURE symbol_procedure_name proc_inputs proc_outputs_opt - { $$ = newNode(QualifiedName(*$3), $4, $5); } + { + $$ = newNode( + QualifiedName(*$3), + ($4 ? $4->second : nullptr), + $5, + ($4 ? $4->first : nullptr)); + } | EXECUTE PROCEDURE symbol_package_name '.' symbol_procedure_name proc_inputs proc_outputs_opt - { $$ = newNode(QualifiedName(*$5, *$3), $6, $7); } + { + $$ = newNode( + QualifiedName(*$5, *$3), + ($6 ? $6->second : nullptr), + $7, + ($6 ? $6->first : nullptr)); + } ; -%type proc_inputs +%type proc_inputs proc_inputs - : /* nothing */ { $$ = NULL; } - | value_list { $$ = $1; } - | '(' value_list ')' { $$ = $2; } + : /* nothing */ { $$ = nullptr; } + | argument_list { $$ = $1; } + | '(' argument_list ')' { $$ = $2; } ; %type proc_outputs_opt @@ -6248,38 +6263,42 @@ named_columns_join table_proc : symbol_procedure_name table_proc_inputs as_noise symbol_table_alias_name { - ProcedureSourceNode* node = newNode(QualifiedName(*$1)); - node->sourceList = $2; + const auto node = newNode(QualifiedName(*$1)); + node->inputSources = $2 ? $2->second : nullptr; + node->dsqlInputArgNames = $2 ? $2->first : nullptr; node->alias = $4->c_str(); $$ = node; } | symbol_procedure_name table_proc_inputs { - ProcedureSourceNode* node = newNode(QualifiedName(*$1)); - node->sourceList = $2; + const auto node = newNode(QualifiedName(*$1)); + node->inputSources = $2 ? $2->second : nullptr; + node->dsqlInputArgNames = $2 ? $2->first : nullptr; $$ = node; } | symbol_package_name '.' symbol_procedure_name table_proc_inputs as_noise symbol_table_alias_name { - ProcedureSourceNode* node = newNode( + const auto node = newNode( QualifiedName(*$3, *$1)); - node->sourceList = $4; + node->inputSources = $4 ? $4->second : nullptr; + node->dsqlInputArgNames = $4 ? $4->first : nullptr; node->alias = $6->c_str(); $$ = node; } | symbol_package_name '.' symbol_procedure_name table_proc_inputs { - ProcedureSourceNode* node = newNode( + const auto node = newNode( QualifiedName(*$3, *$1)); - node->sourceList = $4; + node->inputSources = $4 ? $4->second : nullptr; + node->dsqlInputArgNames = $4 ? $4->first : nullptr; $$ = node; } ; -%type table_proc_inputs +%type table_proc_inputs table_proc_inputs - : /* nothing */ { $$ = NULL; } - | '(' value_list ')' { $$ = $2; } + : /* nothing */ { $$ = nullptr; } + | '(' argument_list ')' { $$ = $2; } ; %type table_name @@ -8580,14 +8599,55 @@ trim_specification %type udf udf - : symbol_UDF_call_name '(' value_list ')' - { $$ = newNode(QualifiedName(*$1, ""), $3); } - | symbol_UDF_call_name '(' ')' - { $$ = newNode(QualifiedName(*$1, ""), newNode(0)); } - | symbol_package_name '.' symbol_UDF_name '(' value_list ')' - { $$ = newNode(QualifiedName(*$3, *$1), $5); } - | symbol_package_name '.' symbol_UDF_name '(' ')' - { $$ = newNode(QualifiedName(*$3, *$1), newNode(0)); } + : symbol_UDF_call_name '(' argument_list_opt ')' + { $$ = newNode(QualifiedName(*$1, ""), $3->second, $3->first); } + | symbol_package_name '.' symbol_UDF_name '(' argument_list_opt ')' + { $$ = newNode(QualifiedName(*$3, *$1), $5->second, $5->first); } + ; + +%type argument_list_opt +argument_list_opt + : // nothing + { + $$ = newNode*, ValueListNode*>>(); + $$->second = newNode(); + } + | argument_list + ; + +%type argument_list +argument_list + : named_argument_list + { $$ = $1; } + | value_list + { + $$ = newNode*, ValueListNode*>>(); + $$->second = $1; + } + ; + +%type named_argument_list +named_argument_list + : named_argument + { + $$ = newNode*, ValueListNode*>>(); + $$->first = newNode>(); + $$->first->add(*$1->first); + $$->second = newNode(); + $$->second->add($1->second); + } + | named_argument_list ',' named_argument + { + $$ = $1; + $$->first->add(*$3->first); + $$->second->add($3->second); + } + ; + +%type named_argument +named_argument + : symbol_column_name NAMED_ARG_ASSIGN value + { $$ = newNode>($1, $3); } ; %type cast_specification diff --git a/src/dsql/pass1.cpp b/src/dsql/pass1.cpp index 4917083274..67d4087641 100644 --- a/src/dsql/pass1.cpp +++ b/src/dsql/pass1.cpp @@ -371,19 +371,16 @@ dsql_ctx* PASS1_make_context(DsqlCompilerScratch* dsqlScratch, RecordSourceNode* { // No processing needed here for derived tables. } - else if (procNode && (procNode->dsqlName.package.hasData() || procNode->sourceList)) + else if (procNode && (procNode->dsqlName.package.hasData() || procNode->inputSources)) { if (procNode->dsqlName.package.isEmpty()) { - DeclareSubProcNode* subProcedure = dsqlScratch->getSubProcedure(procNode->dsqlName.identifier); + const auto subProcedure = dsqlScratch->getSubProcedure(procNode->dsqlName.identifier); procedure = subProcedure ? subProcedure->dsqlProcedure : NULL; } if (!procedure) - { - procedure = METD_get_procedure(dsqlScratch->getTransaction(), dsqlScratch, - procNode->dsqlName); - } + procedure = METD_get_procedure(dsqlScratch->getTransaction(), dsqlScratch, procNode->dsqlName); if (!procedure) { @@ -401,7 +398,7 @@ dsql_ctx* PASS1_make_context(DsqlCompilerScratch* dsqlScratch, RecordSourceNode* { if (procNode && procNode->dsqlName.package.isEmpty()) { - DeclareSubProcNode* subProcedure = dsqlScratch->getSubProcedure(procNode->dsqlName.identifier); + const auto subProcedure = dsqlScratch->getSubProcedure(procNode->dsqlName.identifier); procedure = subProcedure ? subProcedure->dsqlProcedure : NULL; } @@ -409,10 +406,7 @@ dsql_ctx* PASS1_make_context(DsqlCompilerScratch* dsqlScratch, RecordSourceNode* relation = METD_get_relation(dsqlScratch->getTransaction(), dsqlScratch, relation_name); if (!relation && !procedure && procNode) - { - procedure = METD_get_procedure(dsqlScratch->getTransaction(), - dsqlScratch, procNode->dsqlName); - } + procedure = METD_get_procedure(dsqlScratch->getTransaction(), dsqlScratch, procNode->dsqlName); if (!relation && !procedure) { @@ -433,7 +427,7 @@ dsql_ctx* PASS1_make_context(DsqlCompilerScratch* dsqlScratch, RecordSourceNode* } // Set up context block. - dsql_ctx* context = FB_NEW_POOL(*tdbb->getDefaultPool()) dsql_ctx(*tdbb->getDefaultPool()); + const auto context = FB_NEW_POOL(*tdbb->getDefaultPool()) dsql_ctx(*tdbb->getDefaultPool()); context->ctx_relation = relation; context->ctx_procedure = procedure; @@ -535,9 +529,9 @@ dsql_ctx* PASS1_make_context(DsqlCompilerScratch* dsqlScratch, RecordSourceNode* { USHORT count = 0; - if (procNode->sourceList) + if (procNode->inputSources) { - context->ctx_proc_inputs = Node::doDsqlPass(dsqlScratch, procNode->sourceList, false); + context->ctx_proc_inputs = Node::doDsqlPass(dsqlScratch, procNode->inputSources, false); count = context->ctx_proc_inputs->items.getCount(); } @@ -551,8 +545,8 @@ dsql_ctx* PASS1_make_context(DsqlCompilerScratch* dsqlScratch, RecordSourceNode* { // Initialize this stack variable, and make it look like a node dsc desc_node; - ValueListNode* inputList = context->ctx_proc_inputs; - NestConst* input = inputList->items.begin(); + auto inputList = context->ctx_proc_inputs; + auto input = inputList->items.begin(); for (dsql_fld* field = procedure->prc_inputs; input != inputList->items.end(); @@ -1677,21 +1671,22 @@ RecordSourceNode* PASS1_relation(DsqlCompilerScratch* dsqlScratch, RecordSourceN DEV_BLKCHK(dsqlScratch, dsql_type_req); - dsql_ctx* context = PASS1_make_context(dsqlScratch, input); - RecordSourceNode* node = NULL; + const auto context = PASS1_make_context(dsqlScratch, input); if (context->ctx_relation) { - RelationSourceNode* relNode = FB_NEW_POOL(*tdbb->getDefaultPool()) RelationSourceNode( + const auto relNode = FB_NEW_POOL(*tdbb->getDefaultPool()) RelationSourceNode( *tdbb->getDefaultPool(), context->ctx_relation->rel_name); relNode->dsqlContext = context; return relNode; } else if (context->ctx_procedure) { - ProcedureSourceNode* procNode = FB_NEW_POOL(*tdbb->getDefaultPool()) ProcedureSourceNode( + const auto procNode = FB_NEW_POOL(*tdbb->getDefaultPool()) ProcedureSourceNode( *tdbb->getDefaultPool(), context->ctx_procedure->prc_name); procNode->dsqlContext = context; + procNode->inputSources = context->ctx_proc_inputs; + procNode->dsqlInputArgNames = nodeAs(input)->dsqlInputArgNames; return procNode; } //// TODO: LocalTableSourceNode diff --git a/src/include/firebird/impl/blr.h b/src/include/firebird/impl/blr.h index ad2d56b190..20197e135a 100644 --- a/src/include/firebird/impl/blr.h +++ b/src/include/firebird/impl/blr.h @@ -467,4 +467,29 @@ #define blr_skip_locked (unsigned char) 223 +// FB 6.0 specific BLR + +#define blr_invoke_function (unsigned char) 224 +#define blr_invoke_function_type (unsigned char) 1 +#define blr_invoke_function_type_standalone (unsigned char) 1 +#define blr_invoke_function_type_packaged (unsigned char) 2 +#define blr_invoke_function_type_sub (unsigned char) 3 +#define blr_invoke_function_arg_names (unsigned char) 2 +#define blr_invoke_function_args (unsigned char) 3 + +#define blr_invoke_procedure (unsigned char) 225 +#define blr_select_procedure (unsigned char) 226 + +// subcodes of blr_invoke_procedure and blr_select_procedure +#define blr_invsel_procedure_type (unsigned char) 1 +#define blr_invsel_procedure_type_standalone (unsigned char) 1 +#define blr_invsel_procedure_type_packaged (unsigned char) 2 +#define blr_invsel_procedure_type_sub (unsigned char) 3 +#define blr_invsel_procedure_in_arg_names (unsigned char) 2 +#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 + #endif // FIREBIRD_IMPL_BLR_H diff --git a/src/include/firebird/impl/msg/jrd.h b/src/include/firebird/impl/msg/jrd.h index 0da6706bbe..2a78d1c81e 100644 --- a/src/include/firebird/impl/msg/jrd.h +++ b/src/include/firebird/impl/msg/jrd.h @@ -190,7 +190,7 @@ FB_IMPL_MSG(JRD, 188, range_not_found, -834, "42", "000", "refresh range number FB_IMPL_MSG(JRD, 189, charset_not_found, -204, "2C", "000", "CHARACTER SET @1 is not defined") FB_IMPL_MSG(JRD, 190, lock_timeout, -901, "40", "001", "lock time-out on wait transaction") FB_IMPL_MSG(JRD, 191, prcnotdef, -204, "42", "000", "procedure @1 is not defined") -FB_IMPL_MSG(JRD, 192, prcmismat, -170, "07", "001", "Input parameter mismatch for procedure @1") +FB_IMPL_MSG(JRD, 192, prcmismat, -170, "07", "001", "Parameter mismatch for procedure @1") FB_IMPL_MSG(JRD, 193, wal_bugcheck, -246, "XX", "000", "Database @1: WAL subsystem bug for pid @2\n@3") FB_IMPL_MSG(JRD, 194, wal_cant_expand, -247, "HY", "000", "Could not expand the WAL segment for database @1") FB_IMPL_MSG(JRD, 195, codnotdef, -204, "HY", "000", "status code @1 unknown") @@ -779,7 +779,7 @@ FB_IMPL_MSG(JRD, 777, crdb_load, -901, "08", "004", "@1 failed when working with FB_IMPL_MSG(JRD, 778, crdb_nodb, -901, "0A", "000", "CREATE DATABASE grants check is not possible when database @1 is not present") FB_IMPL_MSG(JRD, 779, crdb_notable, -901, "0A", "000", "CREATE DATABASE grants check is not possible when table RDB$DB_CREATORS is not present in database @1") FB_IMPL_MSG(JRD, 780, interface_version_too_old, -804, "HY", "000", "Interface @3 version too old: expected @1, found @2") -FB_IMPL_MSG(JRD, 781, fun_param_mismatch, -170, "07", "001", "Input parameter mismatch for function @1") +FB_IMPL_MSG(JRD, 781, fun_param_mismatch, -170, "07", "001", "Parameter mismatch for function @1") FB_IMPL_MSG(JRD, 782, savepoint_backout_err, -901, "HY", "000", "Error during savepoint backout - transaction invalidated") FB_IMPL_MSG(JRD, 783, domain_primary_key_notnull, -291, "42", "000", "Domain used in the PRIMARY KEY constraint of table @1 must be NOT NULL") FB_IMPL_MSG(JRD, 784, invalid_attachment_charset, -204, "2C", "000", "CHARACTER SET @1 cannot be used as a attachment character set") @@ -969,3 +969,6 @@ FB_IMPL_MSG(JRD, 966, bad_par_workers, -924, "HY", "000", "Wrong parallel worker FB_IMPL_MSG(JRD, 967, idx_expr_not_found, -902, "42", "000", "Definition of index expression is not found for index @1") FB_IMPL_MSG(JRD, 968, idx_cond_not_found, -902, "42", "000", "Definition of index condition is not found for index @1") FB_IMPL_MSG(JRD, 969, uninitialized_var, -625, "42", "000", "Variable @1 is not initialized") +FB_IMPL_MSG(JRD, 970, param_not_exist, -170, "07", "001", "Parameter @1 does not exist") +FB_IMPL_MSG(JRD, 971, param_no_default_not_specified, -170, "07", "001", "Parameter @1 has no default value and was not specified") +FB_IMPL_MSG(JRD, 972, param_multiple_assignments, -170, "07", "001", "Parameter @1 has multiple assignments") diff --git a/src/include/gen/Firebird.pas b/src/include/gen/Firebird.pas index b38073a954..0496fc4693 100644 --- a/src/include/gen/Firebird.pas +++ b/src/include/gen/Firebird.pas @@ -5701,6 +5701,9 @@ const isc_idx_expr_not_found = 335545287; isc_idx_cond_not_found = 335545288; isc_uninitialized_var = 335545289; + isc_param_not_exist = 335545290; + isc_param_no_default_not_specified = 335545291; + isc_param_multiple_assignments = 335545292; isc_gfix_db_name = 335740929; isc_gfix_invalid_sw = 335740930; isc_gfix_incmp_sw = 335740932; diff --git a/src/jrd/RecordSourceNodes.cpp b/src/jrd/RecordSourceNodes.cpp index 8643480930..4e5d010671 100644 --- a/src/jrd/RecordSourceNodes.cpp +++ b/src/jrd/RecordSourceNodes.cpp @@ -896,27 +896,144 @@ RecordSource* RelationSourceNode::compile(thread_db* tdbb, Optimizer* opt, bool ProcedureSourceNode* ProcedureSourceNode::parse(thread_db* tdbb, CompilerScratch* csb, const SSHORT blrOp, bool parseContext) { - SET_TDBB(tdbb); + const auto predateCheck = [&](bool condition, const char* preVerb, const char* postVerb) + { + if (!condition) + { + string str; + str.printf("%s should predate %s", preVerb, postVerb); + PAR_error(csb, Arg::Gds(isc_random) << str); + } + }; - const auto blrStartPos = csb->csb_blr_reader.getPos(); - jrd_prc* procedure = nullptr; - AutoPtr aliasString; + auto& pool = *tdbb->getDefaultPool(); + auto& blrReader = csb->csb_blr_reader; + const auto blrStartPos = blrReader.getPos(); + const UCHAR* inArgNamesPos = nullptr; + ObjectsArray* inArgNames = nullptr; + USHORT inArgCount = 0; QualifiedName name; + const auto node = FB_NEW_POOL(pool) ProcedureSourceNode(pool); + switch (blrOp) { + case blr_select_procedure: + { + CompilerScratch::csb_repeat* csbTail = nullptr; + UCHAR subCode; + + while ((subCode = blrReader.getByte()) != blr_end) + { + switch (subCode) + { + case blr_invsel_procedure_type: + { + UCHAR procedureType = blrReader.getByte(); + + switch (procedureType) + { + case blr_invsel_procedure_type_packaged: + blrReader.getMetaName(name.package); + break; + + case blr_invsel_procedure_type_standalone: + case blr_invsel_procedure_type_sub: + break; + + default: + PAR_error(csb, Arg::Gds(isc_random) << "Invalid blr_invsel_procedure_type"); + break; + } + + blrReader.getMetaName(name.identifier); + + if (procedureType == blr_invsel_procedure_type_sub) + { + for (auto curCsb = csb; curCsb && !node->procedure; curCsb = curCsb->mainCsb) + { + if (const auto declareNode = curCsb->subProcedures.get(name.identifier)) + node->procedure = (*declareNode)->routine; + } + } + else if (!node->procedure) + node->procedure = MET_lookup_procedure(tdbb, name, false); + + break; + } + + case blr_invsel_procedure_in_arg_names: + { + predateCheck(node->procedure, "blr_invsel_procedure_type", "blr_invsel_procedure_in_arg_names"); + predateCheck(!node->inputSources, "blr_invsel_procedure_in_arg_names", "blr_invsel_procedure_in_args"); + + inArgNamesPos = blrReader.getPos(); + USHORT inArgNamesCount = blrReader.getWord(); + MetaName argName; + + inArgNames = FB_NEW_POOL(pool) ObjectsArray(pool); + + while (inArgNamesCount--) + { + blrReader.getMetaName(argName); + inArgNames->add(argName); + } + + break; + } + + case blr_invsel_procedure_in_args: + predateCheck(node->procedure, "blr_invsel_procedure_type", "blr_invsel_procedure_in_args"); + + inArgCount = blrReader.getWord(); + node->inputSources = PAR_args(tdbb, csb, inArgCount, + (inArgNames ? inArgCount : MAX(inArgCount, node->procedure->getInputFields().getCount()))); + break; + + case blr_invsel_procedure_context: + if (!parseContext) + { + PAR_error(csb, + Arg::Gds(isc_random) << + "blr_invsel_procedure_context not expected inside plan clauses"); + } + + predateCheck(node->procedure, "blr_invsel_procedure_type", "blr_invsel_procedure_context"); + node->stream = PAR_context2(csb, &node->context); + csbTail = &csb->csb_rpt[node->stream]; + csbTail->csb_procedure = node->procedure; + + if (node->alias.hasData()) + csbTail->csb_alias = &node->alias; + + if (csb->collectingDependencies()) + PAR_dependency(tdbb, csb, node->stream, (SSHORT) -1, ""); + + break; + + case blr_invsel_procedure_alias: + blrReader.getString(node->alias); + if (csbTail) + csbTail->csb_alias = &node->alias; + break; + + default: + PAR_error(csb, Arg::Gds(isc_random) << "Invalid blr_select_procedure sub code"); + } + } + + break; + } + case blr_pid: case blr_pid2: { - const SSHORT pid = csb->csb_blr_reader.getWord(); + const SSHORT pid = blrReader.getWord(); if (blrOp == blr_pid2) - { - aliasString = FB_NEW_POOL(csb->csb_pool) string(csb->csb_pool); - csb->csb_blr_reader.getString(*aliasString); - } + blrReader.getString(node->alias); - if (!(procedure = MET_lookup_procedure_id(tdbb, pid, false, false, 0))) + if (!(node->procedure = MET_lookup_procedure_id(tdbb, pid, false, false, 0))) name.identifier.printf("id %d", pid); break; @@ -928,31 +1045,23 @@ ProcedureSourceNode* ProcedureSourceNode::parse(thread_db* tdbb, CompilerScratch case blr_procedure4: case blr_subproc: if (blrOp == blr_procedure3 || blrOp == blr_procedure4) - csb->csb_blr_reader.getMetaName(name.package); + blrReader.getMetaName(name.package); - csb->csb_blr_reader.getMetaName(name.identifier); + blrReader.getMetaName(name.identifier); if (blrOp == blr_procedure2 || blrOp == blr_procedure4 || blrOp == blr_subproc) - { - aliasString = FB_NEW_POOL(csb->csb_pool) string(csb->csb_pool); - csb->csb_blr_reader.getString(*aliasString); - - if (blrOp == blr_subproc && aliasString->isEmpty()) - aliasString.reset(); - } + blrReader.getString(node->alias); if (blrOp == blr_subproc) { - DeclareSubProcNode* declareNode; - - for (auto curCsb = csb; curCsb && !procedure; curCsb = curCsb->mainCsb) + for (auto curCsb = csb; curCsb && !node->procedure; curCsb = curCsb->mainCsb) { - if (curCsb->subProcedures.get(name.identifier, declareNode)) - procedure = declareNode->routine; + if (const auto declareNode = curCsb->subProcedures.get(name.identifier)) + node->procedure = (*declareNode)->routine; } } else - procedure = MET_lookup_procedure(tdbb, name, false); + node->procedure = MET_lookup_procedure(tdbb, name, false); break; @@ -960,60 +1069,112 @@ ProcedureSourceNode* ProcedureSourceNode::parse(thread_db* tdbb, CompilerScratch fb_assert(false); } - if (!procedure) - PAR_error(csb, Arg::Gds(isc_prcnotdef) << Arg::Str(name.toString())); - else + if (!node->procedure) { - if (procedure->isImplemented() && !procedure->isDefined()) + blrReader.setPos(blrStartPos); + PAR_error(csb, Arg::Gds(isc_prcnotdef) << name.toString()); + } + + if (node->procedure->prc_type == prc_executable) + { + if (tdbb->getAttachment()->isGbak()) + PAR_warning(Arg::Warning(isc_illegal_prc_type) << node->procedure->getName().toString()); + else + PAR_error(csb, Arg::Gds(isc_illegal_prc_type) << node->procedure->getName().toString()); + } + + node->isSubRoutine = node->procedure->isSubRoutine(); + node->procedureId = node->isSubRoutine ? 0 : node->procedure->getId(); + + if (node->procedure->isImplemented() && !node->procedure->isDefined()) + { + if (tdbb->getAttachment()->isGbak() || (tdbb->tdbb_flags & TDBB_replicator)) { - if (tdbb->getAttachment()->isGbak() || (tdbb->tdbb_flags & TDBB_replicator)) - { - PAR_warning( - Arg::Warning(isc_prcnotdef) << Arg::Str(name.toString()) << - Arg::Warning(isc_modnotfound)); + PAR_warning( + Arg::Warning(isc_prcnotdef) << name.toString() << + Arg::Warning(isc_modnotfound)); + } + else + { + blrReader.setPos(blrStartPos); + PAR_error(csb, + Arg::Gds(isc_prcnotdef) << name.toString() << + Arg::Gds(isc_modnotfound)); + } + } + + if (parseContext) + { + if (blrOp != blr_select_procedure) + { + node->stream = PAR_context(csb, &node->context); + + csb->csb_rpt[node->stream].csb_procedure = node->procedure; + csb->csb_rpt[node->stream].csb_alias = &node->alias; + + inArgCount = blrReader.getWord(); + node->inputSources = PAR_args(tdbb, csb, inArgCount, inArgCount); + } + + if (!node->inputSources) + node->inputSources = FB_NEW_POOL(pool) ValueListNode(pool); + + if (inArgNames && inArgNames->getCount() != node->inputSources->items.getCount()) + { + blrReader.setPos(inArgNamesPos); + PAR_error(csb, + Arg::Gds(isc_random) << + "blr_invsel_procedure_in_arg_names count differs from blr_invsel_procedure_in_args"); + } + + Arg::StatusVector mismatchStatus; + mismatchStatus << Arg::Gds(isc_prcmismat) << node->procedure->getName().toString(); + const auto mismatchInitialLength = mismatchStatus.length(); + + node->inputTargets = FB_NEW_POOL(pool) ValueListNode(pool, node->procedure->getInputFields().getCount()); + + mismatchStatus << CMP_procedure_arguments( + tdbb, + csb, + node->procedure, + true, + inArgCount, + inArgNames, + node->inputSources, + node->inputTargets, + node->inputMessage); + + if (mismatchStatus.length() > mismatchInitialLength) + status_exception::raise(mismatchStatus); + + if (csb->collectingDependencies() && !node->procedure->isSubRoutine()) + { + { // scope + CompilerScratch::Dependency dependency(obj_procedure); + dependency.procedure = node->procedure; + csb->addDependency(dependency); } - else + + if (inArgNames) { - csb->csb_blr_reader.setPos(blrStartPos); - PAR_error(csb, - Arg::Gds(isc_prcnotdef) << Arg::Str(name.toString()) << - Arg::Gds(isc_modnotfound)); + for (const auto& argName : *inArgNames) + { + CompilerScratch::Dependency dependency(obj_procedure); + dependency.procedure = node->procedure; + dependency.subName = &argName; + csb->addDependency(dependency); + } } } } - if (procedure->prc_type == prc_executable) + if (node->inputSources && node->inputSources->items.isEmpty()) { - const string name = procedure->getName().toString(); + delete node->inputSources.getObject(); + node->inputSources = nullptr; - if (tdbb->getAttachment()->isGbak()) - PAR_warning(Arg::Warning(isc_illegal_prc_type) << Arg::Str(name)); - else - PAR_error(csb, Arg::Gds(isc_illegal_prc_type) << Arg::Str(name)); - } - - ProcedureSourceNode* node = FB_NEW_POOL(*tdbb->getDefaultPool()) ProcedureSourceNode( - *tdbb->getDefaultPool()); - - node->procedure = procedure; - node->isSubRoutine = procedure->isSubRoutine(); - node->procedureId = node->isSubRoutine ? 0 : procedure->getId(); - - if (aliasString) - node->alias = *aliasString; - - if (parseContext) - { - node->stream = PAR_context(csb, &node->context); - - csb->csb_rpt[node->stream].csb_procedure = procedure; - csb->csb_rpt[node->stream].csb_alias = aliasString.release(); - - PAR_procedure_parms(tdbb, csb, procedure, node->in_msg.getAddress(), - node->sourceList.getAddress(), node->targetList.getAddress(), true); - - if (csb->collectingDependencies()) - PAR_dependency(tdbb, csb, node->stream, (SSHORT) -1, ""); + delete node->inputTargets.getObject(); + node->inputTargets = nullptr; } return node; @@ -1023,7 +1184,7 @@ string ProcedureSourceNode::internalPrint(NodePrinter& printer) const { RecordSourceNode::internalPrint(printer); - NODE_PRINT(printer, in_msg); + NODE_PRINT(printer, inputMessage); NODE_PRINT(printer, context); return "ProcedureSourceNode"; @@ -1040,7 +1201,7 @@ bool ProcedureSourceNode::dsqlAggregateFinder(AggregateFinder& visitor) if (dsqlContext->ctx_procedure) { // Check if an aggregate is buried inside the input parameters. - return visitor.visit(dsqlContext->ctx_proc_inputs); + return visitor.visit(inputSources); } return false; @@ -1049,7 +1210,7 @@ bool ProcedureSourceNode::dsqlAggregateFinder(AggregateFinder& visitor) bool ProcedureSourceNode::dsqlAggregate2Finder(Aggregate2Finder& visitor) { if (dsqlContext->ctx_procedure) - return visitor.visit(dsqlContext->ctx_proc_inputs); + return visitor.visit(inputSources); return false; } @@ -1058,7 +1219,7 @@ bool ProcedureSourceNode::dsqlInvalidReferenceFinder(InvalidReferenceFinder& vis { // If relation is a procedure, check if the parameters are valid. if (dsqlContext->ctx_procedure) - return visitor.visit(dsqlContext->ctx_proc_inputs); + return visitor.visit(inputSources); return false; } @@ -1067,7 +1228,7 @@ bool ProcedureSourceNode::dsqlSubSelectFinder(SubSelectFinder& visitor) { // If relation is a procedure, check if the parameters are valid. if (dsqlContext->ctx_procedure) - return visitor.visit(dsqlContext->ctx_proc_inputs); + return visitor.visit(inputSources); return false; } @@ -1076,7 +1237,7 @@ bool ProcedureSourceNode::dsqlFieldFinder(FieldFinder& visitor) { // If relation is a procedure, check if the parameters are valid. if (dsqlContext->ctx_procedure) - return visitor.visit(dsqlContext->ctx_proc_inputs); + return visitor.visit(inputSources); return false; } @@ -1085,7 +1246,7 @@ RecordSourceNode* ProcedureSourceNode::dsqlFieldRemapper(FieldRemapper& visitor) { // Check if relation is a procedure. if (dsqlContext->ctx_procedure) - doDsqlFieldRemapper(visitor, dsqlContext->ctx_proc_inputs); // Remap the input parameters. + doDsqlFieldRemapper(visitor, inputSources); // Remap the input parameters. return this; } @@ -1100,12 +1261,67 @@ bool ProcedureSourceNode::dsqlMatch(DsqlCompilerScratch* dsqlScratch, const Expr // Generate blr for a procedure reference. void ProcedureSourceNode::genBlr(DsqlCompilerScratch* dsqlScratch) { - const dsql_prc* procedure = dsqlContext->ctx_procedure; + const dsql_prc* dsqlProcedure = dsqlContext->ctx_procedure; - if (procedure->prc_flags & PRC_subproc) + if (dsqlInputArgNames) + { + dsqlScratch->appendUChar(blr_select_procedure); + + dsqlScratch->appendUChar(blr_invsel_procedure_type); + + if (dsqlName.package.hasData()) + { + dsqlScratch->appendUChar(blr_invsel_procedure_type_packaged); + dsqlScratch->appendMetaString(dsqlName.package.c_str()); + } + else + { + dsqlScratch->appendUChar((dsqlProcedure->prc_flags & PRC_subproc) ? + blr_invsel_procedure_type_sub : blr_invsel_procedure_type_standalone); + } + + dsqlScratch->appendMetaString(dsqlName.identifier.c_str()); + + // Input parameters. + if (inputSources) + { + if (dsqlInputArgNames && dsqlInputArgNames->hasData()) + { + dsqlScratch->appendUChar(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->appendUShort(inputSources->items.getCount()); + + for (auto& arg : inputSources->items) + GEN_expr(dsqlScratch, arg); + } + + if (dsqlContext->ctx_context > MAX_UCHAR) + ERRD_post(Arg::Gds(isc_too_many_contexts)); + + dsqlScratch->appendUChar(blr_invsel_procedure_context); + dsqlScratch->appendUShort(dsqlContext->ctx_context); + + if (dsqlContext->ctx_alias.hasData()) + { + dsqlScratch->appendUChar(blr_invsel_procedure_alias); + dsqlScratch->appendMetaString(dsqlContext->ctx_alias.c_str()); + } + + dsqlScratch->appendUChar(blr_end); + + return; + } + + if (dsqlProcedure->prc_flags & PRC_subproc) { dsqlScratch->appendUChar(blr_subproc); - dsqlScratch->appendMetaString(procedure->prc_name.identifier.c_str()); + dsqlScratch->appendMetaString(dsqlProcedure->prc_name.identifier.c_str()); dsqlScratch->appendMetaString(dsqlContext->ctx_alias.c_str()); } else @@ -1115,20 +1331,20 @@ void ProcedureSourceNode::genBlr(DsqlCompilerScratch* dsqlScratch) if (DDL_ids(dsqlScratch)) { dsqlScratch->appendUChar(dsqlContext->ctx_alias.hasData() ? blr_pid2 : blr_pid); - dsqlScratch->appendUShort(procedure->prc_id); + dsqlScratch->appendUShort(dsqlProcedure->prc_id); } else { - if (procedure->prc_name.package.hasData()) + if (dsqlProcedure->prc_name.package.hasData()) { dsqlScratch->appendUChar(dsqlContext->ctx_alias.hasData() ? blr_procedure4 : blr_procedure3); - dsqlScratch->appendMetaString(procedure->prc_name.package.c_str()); - dsqlScratch->appendMetaString(procedure->prc_name.identifier.c_str()); + dsqlScratch->appendMetaString(dsqlProcedure->prc_name.package.c_str()); + dsqlScratch->appendMetaString(dsqlProcedure->prc_name.identifier.c_str()); } else { dsqlScratch->appendUChar(dsqlContext->ctx_alias.hasData() ? blr_procedure2 : blr_procedure); - dsqlScratch->appendMetaString(procedure->prc_name.identifier.c_str()); + dsqlScratch->appendMetaString(dsqlProcedure->prc_name.identifier.c_str()); } } @@ -1138,18 +1354,12 @@ void ProcedureSourceNode::genBlr(DsqlCompilerScratch* dsqlScratch) GEN_stuff_context(dsqlScratch, dsqlContext); - ValueListNode* inputs = dsqlContext->ctx_proc_inputs; - - if (inputs && !(dsqlFlags & DFLAG_PLAN_ITEM)) + if (inputSources && !(dsqlFlags & DFLAG_PLAN_ITEM)) { - dsqlScratch->appendUShort(inputs->items.getCount()); + dsqlScratch->appendUShort(inputSources->items.getCount()); - for (NestConst* ptr = inputs->items.begin(); - ptr != inputs->items.end(); - ++ptr) - { - GEN_expr(dsqlScratch, *ptr); - } + for (auto& arg : inputSources->items) + GEN_expr(dsqlScratch, arg); } else dsqlScratch->appendUShort(0); @@ -1180,12 +1390,12 @@ ProcedureSourceNode* ProcedureSourceNode::copy(thread_db* tdbb, NodeCopier& copi // dimitr: See the appropriate code and comment in NodeCopier (in nod_argument). // We must copy the message first and only then use the new pointer to // copy the inputs properly. - newSource->in_msg = copier.copy(tdbb, in_msg); + newSource->inputMessage = copier.copy(tdbb, inputMessage); { // scope - AutoSetRestore autoMessage(&copier.message, newSource->in_msg); - newSource->sourceList = copier.copy(tdbb, sourceList); - newSource->targetList = copier.copy(tdbb, targetList); + AutoSetRestore autoMessage(&copier.message, newSource->inputMessage); + newSource->inputSources = copier.copy(tdbb, inputSources); + newSource->inputTargets = copier.copy(tdbb, inputTargets); } newSource->stream = copier.csb->nextStream(); @@ -1211,9 +1421,9 @@ ProcedureSourceNode* ProcedureSourceNode::copy(thread_db* tdbb, NodeCopier& copi RecordSourceNode* ProcedureSourceNode::pass1(thread_db* tdbb, CompilerScratch* csb) { - doPass1(tdbb, csb, sourceList.getAddress()); - doPass1(tdbb, csb, targetList.getAddress()); - doPass1(tdbb, csb, in_msg.getAddress()); + doPass1(tdbb, csb, inputSources.getAddress()); + doPass1(tdbb, csb, inputTargets.getAddress()); + doPass1(tdbb, csb, inputMessage.getAddress()); return this; } @@ -1256,9 +1466,9 @@ void ProcedureSourceNode::pass1Source(thread_db* tdbb, CompilerScratch* csb, Rse RecordSourceNode* ProcedureSourceNode::pass2(thread_db* tdbb, CompilerScratch* csb) { - ExprNode::doPass2(tdbb, csb, sourceList.getAddress()); - ExprNode::doPass2(tdbb, csb, targetList.getAddress()); - ExprNode::doPass2(tdbb, csb, in_msg.getAddress()); + ExprNode::doPass2(tdbb, csb, inputSources.getAddress()); + ExprNode::doPass2(tdbb, csb, inputTargets.getAddress()); + ExprNode::doPass2(tdbb, csb, inputMessage.getAddress()); return this; } @@ -1275,16 +1485,16 @@ RecordSource* ProcedureSourceNode::compile(thread_db* tdbb, Optimizer* opt, bool const string alias = opt->makeAlias(stream); return FB_NEW_POOL(*tdbb->getDefaultPool()) ProcedureScan(csb, alias, stream, procedure, - sourceList, targetList, in_msg); + inputSources, inputTargets, inputMessage); } bool ProcedureSourceNode::computable(CompilerScratch* csb, StreamType stream, bool allowOnlyCurrentStream, ValueExprNode* /*value*/) { - if (sourceList && !sourceList->computable(csb, stream, allowOnlyCurrentStream)) + if (inputSources && !inputSources->computable(csb, stream, allowOnlyCurrentStream)) return false; - if (targetList && !targetList->computable(csb, stream, allowOnlyCurrentStream)) + if (inputTargets && !inputTargets->computable(csb, stream, allowOnlyCurrentStream)) return false; return true; @@ -1293,22 +1503,22 @@ bool ProcedureSourceNode::computable(CompilerScratch* csb, StreamType stream, void ProcedureSourceNode::findDependentFromStreams(const CompilerScratch* csb, StreamType currentStream, SortedStreamList* streamList) { - if (sourceList) - sourceList->findDependentFromStreams(csb, currentStream, streamList); + if (inputSources) + inputSources->findDependentFromStreams(csb, currentStream, streamList); - if (targetList) - targetList->findDependentFromStreams(csb, currentStream, streamList); + if (inputTargets) + inputTargets->findDependentFromStreams(csb, currentStream, streamList); } void ProcedureSourceNode::collectStreams(SortedStreamList& streamList) const { RecordSourceNode::collectStreams(streamList); - if (sourceList) - sourceList->collectStreams(streamList); + if (inputSources) + inputSources->collectStreams(streamList); - if (targetList) - targetList->collectStreams(streamList); + if (inputTargets) + inputTargets->collectStreams(streamList); } @@ -1706,7 +1916,7 @@ UnionSourceNode* UnionSourceNode::parse(thread_db* tdbb, CompilerScratch* csb, c if (node->recursive) { - stream2 = PAR_context(csb, 0); + stream2 = PAR_context(csb, nullptr); node->mapStream = stream2; } @@ -3452,13 +3662,13 @@ static RecordSourceNode* dsqlPassRelProc(DsqlCompilerScratch* dsqlScratch, Recor MetaName relName; string relAlias; - if (auto procNode = nodeAs(source)) + if (const auto procNode = nodeAs(source)) { relName = procNode->dsqlName.identifier; relAlias = procNode->alias; - couldBeCte = !procNode->sourceList && procNode->dsqlName.package.isEmpty(); + couldBeCte = !procNode->inputSources && procNode->dsqlName.package.isEmpty(); } - else if (auto relNode = nodeAs(source)) + else if (const auto relNode = nodeAs(source)) { relName = relNode->dsqlName; relAlias = relNode->alias; diff --git a/src/jrd/RecordSourceNodes.h b/src/jrd/RecordSourceNodes.h index 9594d63f1e..b64b3afcbb 100644 --- a/src/jrd/RecordSourceNodes.h +++ b/src/jrd/RecordSourceNodes.h @@ -434,21 +434,14 @@ public: const QualifiedName& aDsqlName = QualifiedName()) : TypedNode(pool), dsqlName(pool, aDsqlName), - alias(pool), - procedure(NULL), - sourceList(NULL), - targetList(NULL), - in_msg(NULL), - view(NULL), - procedureId(0), - context(0), - isSubRoutine(false) + alias(pool) { } static ProcedureSourceNode* parse(thread_db* tdbb, CompilerScratch* csb, const SSHORT blrOp, bool parseContext); +public: virtual Firebird::string internalPrint(NodePrinter& printer) const; virtual RecordSourceNode* dsqlPass(DsqlCompilerScratch* dsqlScratch); @@ -510,17 +503,18 @@ public: cache management policies yet, so I leave it for the other day. ***/ - jrd_prc* procedure; - NestConst sourceList; - NestConst targetList; + jrd_prc* procedure = nullptr; + NestConst inputSources; + NestConst inputTargets; + NestConst> dsqlInputArgNames; private: - NestConst in_msg; + NestConst inputMessage; - jrd_rel* view; - USHORT procedureId; - SSHORT context; - bool isSubRoutine; + jrd_rel* view = nullptr; + USHORT procedureId = 0; + SSHORT context = 0; + bool isSubRoutine = false; }; class AggregateSourceNode final : public TypedNode diff --git a/src/jrd/blp.h b/src/jrd/blp.h index cda079e900..738de16f50 100644 --- a/src/jrd/blp.h +++ b/src/jrd/blp.h @@ -255,5 +255,8 @@ static const struct {"outer_map", outer_map}, {NULL, NULL}, // blr_json_function {"skip_locked", zero}, + {"invoke_function", invoke_function}, + {"invoke_procedure", invsel_procedure}, + {"select_procedure", invsel_procedure}, {0, 0} }; diff --git a/src/jrd/cmp.cpp b/src/jrd/cmp.cpp index 05bb0b35df..98b6060fd8 100644 --- a/src/jrd/cmp.cpp +++ b/src/jrd/cmp.cpp @@ -459,6 +459,173 @@ ItemInfo* CMP_pass2_validation(thread_db* tdbb, CompilerScratch* csb, const Item } +Arg::StatusVector CMP_procedure_arguments( + thread_db* tdbb, + CompilerScratch* csb, + Routine* routine, + bool isInput, + USHORT argCount, + ObjectsArray* argNames, + NestConst& sources, + NestConst& targets, + NestConst& message) +{ + Arg::StatusVector mismatchStatus; + + auto& pool = *tdbb->getDefaultPool(); + auto& fields = isInput ? routine->getInputFields() : routine->getOutputFields(); + const auto format = isInput ? routine->getInputFormat() : routine->getOutputFormat(); + + if ((isInput && fields.hasData() && (argCount || routine->getDefaultCount())) || + (!isInput && argCount)) + { + if (isInput) + sources->items.resize(fields.getCount()); + else + targets = FB_NEW_POOL(pool) ValueListNode(pool, argCount); + + auto sourceArgIt = sources->items.begin(); + auto targetArgIt = targets->items.begin(); + + // We have a few parameters. Get on with creating the message block + // Outer messages map may start with 2, but they are always in the routine start. + USHORT n = ++csb->csb_msg_number; + if (n < 2) + csb->csb_msg_number = n = 2; + const auto tail = CMP_csb_element(csb, n); + + message = tail->csb_message = FB_NEW_POOL(pool) MessageNode(pool); + message->messageNumber = n; + + /* dimitr: procedure (with its parameter formats) is allocated out of + its own pool (prc_request->req_pool) and can be freed during + the cache cleanup (MET_clear_cache). Since the current + tdbb default pool is different from the procedure's one, + it's dangerous to copy a pointer from one request to another. + As an experiment, I've decided to copy format by value + instead of copying the reference. Since Format structure + doesn't contain any pointers, it should be safe to use a + default assignment operator which does a simple byte copy. + This change fixes one serious bug in the current codebase. + I think that this situation can (and probably should) be + handled by the metadata cache (via incrementing prc_use_count) + to avoid unexpected cache cleanups, but that area is out of my + knowledge. So this fix should be considered a temporary solution. + + message->format = format; + */ + const auto fmtCopy = Format::newFormat(pool, format->fmt_count); + *fmtCopy = *format; + message->format = fmtCopy; + // --- end of fix --- + + if (argNames) + { + LeftPooledMap> argsByName; + + for (const auto& argName : *argNames) + { + if (argsByName.put(argName, *sourceArgIt++)) + mismatchStatus << Arg::Gds(isc_param_multiple_assignments) << argName; + } + + sourceArgIt = sources->items.begin(); + + for (auto& parameter : fields) + { + if (const auto argValue = argsByName.get(parameter->prm_name)) + { + *sourceArgIt = *argValue; + argsByName.remove(parameter->prm_name); + } + else if (isInput) + { + if (parameter->prm_default_value) + *sourceArgIt = CMP_clone_node(tdbb, csb, parameter->prm_default_value); + else + mismatchStatus << Arg::Gds(isc_param_no_default_not_specified) << parameter->prm_name; + } + + ++sourceArgIt; + + const auto paramNode = FB_NEW_POOL(csb->csb_pool) ParameterNode(csb->csb_pool); + paramNode->messageNumber = message->messageNumber; + paramNode->message = message; + paramNode->argNumber = parameter->prm_number * 2; + + const auto paramFlagNode = FB_NEW_POOL(csb->csb_pool) ParameterNode(csb->csb_pool); + paramFlagNode->messageNumber = message->messageNumber; + paramFlagNode->message = message; + paramFlagNode->argNumber = parameter->prm_number * 2 + 1; + + paramNode->argFlag = paramFlagNode; + + *targetArgIt++ = paramNode; + } + + if (argsByName.hasData()) + { + for (const auto& argPair : argsByName) + mismatchStatus << Arg::Gds(isc_param_not_exist) << argPair.first; + } + } + else + { + for (unsigned i = 0; i < (isInput ? fields.getCount() : argCount); ++i) + { + if (isInput) + { + // default value for parameter + if (i >= argCount) + { + auto parameter = fields[i]; + + if (parameter->prm_default_value) + *sourceArgIt = CMP_clone_node(tdbb, csb, parameter->prm_default_value); + } + + ++sourceArgIt; + } + + const auto paramNode = FB_NEW_POOL(csb->csb_pool) ParameterNode(csb->csb_pool); + paramNode->messageNumber = message->messageNumber; + paramNode->message = message; + paramNode->argNumber = i * 2; + + const auto paramFlagNode = FB_NEW_POOL(csb->csb_pool) ParameterNode(csb->csb_pool); + paramFlagNode->messageNumber = message->messageNumber; + paramFlagNode->message = message; + paramFlagNode->argNumber = i * 2 + 1; + + paramNode->argFlag = paramFlagNode; + + *targetArgIt++ = paramNode; + } + } + } + + if (isInput && !argNames) + { + if (argCount > fields.getCount()) + mismatchStatus << Arg::Gds(isc_wronumarg); + + for (unsigned i = 0; i < fields.getCount(); ++i) + { + // default value for parameter + if (i >= argCount) + { + auto parameter = fields[i]; + + if (!parameter->prm_default_value) + mismatchStatus << Arg::Gds(isc_param_no_default_not_specified) << parameter->prm_name; + } + } + } + + return mismatchStatus; +} + + void CMP_post_procedure_access(thread_db* tdbb, CompilerScratch* csb, jrd_prc* procedure) { /************************************** diff --git a/src/jrd/cmp_proto.h b/src/jrd/cmp_proto.h index a8938a5665..b50c9224b2 100644 --- a/src/jrd/cmp_proto.h +++ b/src/jrd/cmp_proto.h @@ -46,6 +46,17 @@ Jrd::IndexLock* CMP_get_index_lock(Jrd::thread_db*, Jrd::jrd_rel*, USHORT); Jrd::Request* CMP_make_request(Jrd::thread_db*, Jrd::CompilerScratch*, bool); Jrd::ItemInfo* CMP_pass2_validation(Jrd::thread_db*, Jrd::CompilerScratch*, const Jrd::Item&); +Firebird::Arg::StatusVector CMP_procedure_arguments( + Jrd::thread_db* tdbb, + Jrd::CompilerScratch* csb, + Jrd::Routine* routine, + bool isInput, + USHORT argCount, + Firebird::ObjectsArray* argNames, + NestConst& sources, + NestConst& targets, + NestConst& message); + void CMP_post_access(Jrd::thread_db*, Jrd::CompilerScratch*, const Jrd::MetaName&, SLONG ssRelationId, Jrd::SecurityClass::flags_t, ObjectType obj_type, const Jrd::MetaName&, const Jrd::MetaName& = ""); diff --git a/src/jrd/dfw.epp b/src/jrd/dfw.epp index 3396c47f90..02f645b9d1 100644 --- a/src/jrd/dfw.epp +++ b/src/jrd/dfw.epp @@ -859,7 +859,7 @@ namespace return true; case 6: - Self::checkOutParamDependencies(tdbb, work, transaction); + Self::checkParamDependencies(tdbb, work, transaction); break; } @@ -1076,7 +1076,7 @@ namespace static Routine* lookupBlobId(thread_db* tdbb, DeferredWork* work, bid& blobId, bool compile); static void validate(thread_db* tdbb, jrd_tra* transaction, DeferredWork* work, SSHORT validBlr); - static void checkOutParamDependencies(thread_db* tdbb, DeferredWork* work, jrd_tra* transaction); + static void checkParamDependencies(thread_db* tdbb, DeferredWork* work, jrd_tra* transaction); }; class ProcedureManager : public RoutineManagergetAttachment(); + AutoCacheRequest handle(tdbb, irq_func_param_dep, IRQ_REQUESTS); + ObjectsArray names; + int depCount = 0; + + FOR (REQUEST_HANDLE handle) + DEP IN RDB$DEPENDENCIES + WITH DEP.RDB$DEPENDED_ON_NAME EQ work->dfw_name.c_str() AND + DEP.RDB$PACKAGE_NAME EQUIV NULLIF(work->dfw_package.c_str(), '') AND + DEP.RDB$DEPENDED_ON_TYPE = obj_udf AND + NOT DEP.RDB$FIELD_NAME MISSING AND + NOT ANY ARG IN RDB$FUNCTION_ARGUMENTS + WITH ARG.RDB$FUNCTION_NAME EQ DEP.RDB$DEPENDED_ON_NAME AND + ARG.RDB$PACKAGE_NAME EQUIV DEP.RDB$PACKAGE_NAME AND + ARG.RDB$ARGUMENT_NAME EQ DEP.RDB$FIELD_NAME + { + // If the found object is also being deleted, there's no dependency + + if (!find_depend_in_dfw(tdbb, DEP.RDB$DEPENDENT_NAME, DEP.RDB$DEPENDENT_TYPE, 0, transaction)) + { + string& name = names.add(); + name.printf("%s.%s", work->dfw_name.c_str(), DEP.RDB$FIELD_NAME); + + ++depCount; + } + } + END_FOR + + if (names.hasData()) + { + Arg::StatusVector status; + status << Arg::Gds(isc_no_meta_update) << Arg::Gds(isc_no_delete); + + for (auto& name : names) + status << Arg::Gds(isc_parameter_name) << Arg::Str(name); + + status << Arg::Gds(isc_dependency) << Arg::Num(depCount); + + ERR_post(status); + } } Routine* ProcedureManager::lookupBlobId(thread_db* tdbb, DeferredWork* work, bid& blobId, @@ -1186,10 +1225,10 @@ namespace END_FOR } - void ProcedureManager::checkOutParamDependencies(thread_db* tdbb, DeferredWork* work, jrd_tra* transaction) + void ProcedureManager::checkParamDependencies(thread_db* tdbb, DeferredWork* work, jrd_tra* transaction) { Jrd::Attachment* attachment = tdbb->getAttachment(); - AutoCacheRequest handle(tdbb, irq_out_proc_param_dep, IRQ_REQUESTS); + AutoCacheRequest handle(tdbb, irq_proc_param_dep, IRQ_REQUESTS); ObjectsArray names; int depCount = 0; @@ -1202,8 +1241,7 @@ namespace NOT ANY PP IN RDB$PROCEDURE_PARAMETERS WITH PP.RDB$PROCEDURE_NAME EQ DEP.RDB$DEPENDED_ON_NAME AND PP.RDB$PACKAGE_NAME EQUIV DEP.RDB$PACKAGE_NAME AND - PP.RDB$PARAMETER_NAME EQ DEP.RDB$FIELD_NAME AND - PP.RDB$PARAMETER_TYPE EQ 1 + PP.RDB$PARAMETER_NAME EQ DEP.RDB$FIELD_NAME { // If the found object is also being deleted, there's no dependency diff --git a/src/jrd/irq.h b/src/jrd/irq.h index 26efe28f8d..7e41cc7042 100644 --- a/src/jrd/irq.h +++ b/src/jrd/irq.h @@ -182,7 +182,8 @@ enum irq_type_t irq_c_relation3, // lookup relation in phase 0 to cleanup irq_linger, // get database linger value irq_dbb_ss_definer, // get database sql security value - irq_out_proc_param_dep, // check output procedure parameter dependency + irq_proc_param_dep, // check procedure parameter dependency + irq_func_param_dep, // check function parameter dependency irq_l_pub_tab_state, // lookup publication state for a table irq_MAX diff --git a/src/jrd/par.cpp b/src/jrd/par.cpp index d959bb675c..a8837c2260 100644 --- a/src/jrd/par.cpp +++ b/src/jrd/par.cpp @@ -821,28 +821,11 @@ ValueListNode* PAR_args(thread_db* tdbb, CompilerScratch* csb) } -StreamType PAR_context(CompilerScratch* csb, SSHORT* context_ptr) +// Introduce a new context into the system. +// This involves assigning a stream and possibly extending the compile scratch block. +StreamType par_context(CompilerScratch* csb, USHORT context) { -/************************************** - * - * P A R _ c o n t e x t - * - ************************************** - * - * Functional description - * Introduce a new context into the system. This involves - * assigning a stream and possibly extending the compile - * scratch block. - * - **************************************/ - - // CVC: Bottleneck - const SSHORT context = (unsigned int) csb->csb_blr_reader.getByte(); - - if (context_ptr) - *context_ptr = context; - - CompilerScratch::csb_repeat* tail = CMP_csb_element(csb, context); + const auto tail = CMP_csb_element(csb, context); if (tail->csb_flags & csb_used) { @@ -867,6 +850,28 @@ StreamType PAR_context(CompilerScratch* csb, SSHORT* context_ptr) return stream; } +// Introduce a new context into the system - byte version. +StreamType PAR_context(CompilerScratch* csb, SSHORT* context_ptr) +{ + const USHORT context = csb->csb_blr_reader.getByte(); + + if (context_ptr) + *context_ptr = (SSHORT) context; + + return par_context(csb, context); +} + +// Introduce a new context into the system - word version. +StreamType PAR_context2(CompilerScratch* csb, SSHORT* context_ptr) +{ + const USHORT context = csb->csb_blr_reader.getWord(); + + if (context_ptr) + *context_ptr = (SSHORT) context; + + return par_context(csb, context); +} + void PAR_dependency(thread_db* tdbb, CompilerScratch* csb, StreamType stream, SSHORT id, const MetaName& field_name) @@ -975,12 +980,12 @@ static PlanNode* par_plan(thread_db* tdbb, CompilerScratch* csb) case blr_rid: case blr_relation2: case blr_rid2: - { - const auto relationNode = RelationSourceNode::parse(tdbb, csb, blrOp, false); - plan->recordSourceNode = relationNode; - relation = relationNode->relation; - } + { + const auto relationNode = RelationSourceNode::parse(tdbb, csb, blrOp, false); + plan->recordSourceNode = relationNode; + relation = relationNode->relation; break; + } case blr_pid: case blr_pid2: @@ -989,12 +994,13 @@ static PlanNode* par_plan(thread_db* tdbb, CompilerScratch* csb) case blr_procedure3: case blr_procedure4: case blr_subproc: - { - const auto procedureNode = ProcedureSourceNode::parse(tdbb, csb, blrOp, false); - plan->recordSourceNode = procedureNode; - procedure = procedureNode->procedure; - } + case blr_select_procedure: + { + const auto procedureNode = ProcedureSourceNode::parse(tdbb, csb, blrOp, false); + plan->recordSourceNode = procedureNode; + procedure = procedureNode->procedure; break; + } case blr_local_table_id: // TODO @@ -1010,7 +1016,7 @@ static PlanNode* par_plan(thread_db* tdbb, CompilerScratch* csb) const StreamType stream = csb->csb_rpt[context].csb_stream; plan->recordSourceNode->setStream(stream); -// plan->recordSourceNode->context = context; not needed ??? + /// plan->recordSourceNode->context = context; not needed ??? if (procedure) { @@ -1167,104 +1173,6 @@ static PlanNode* par_plan(thread_db* tdbb, CompilerScratch* csb) } -// Parse some procedure parameters. -void PAR_procedure_parms(thread_db* tdbb, CompilerScratch* csb, jrd_prc* procedure, - MessageNode** message_ptr, ValueListNode** sourceList, ValueListNode** targetList, bool input_flag) -{ - SET_TDBB(tdbb); - SLONG count = csb->csb_blr_reader.getWord(); - const SLONG inputCount = procedure->getInputFields().getCount(); - - // Check to see if the parameter count matches - if (input_flag ? - (count < (inputCount - procedure->getDefaultCount()) || (count > inputCount) ) : - (count != SLONG(procedure->getOutputFields().getCount()))) - { - PAR_error(csb, Arg::Gds(input_flag ? isc_prcmismat : isc_prc_out_param_mismatch) << - Arg::Str(procedure->getName().toString())); - } - - if (count || input_flag && procedure->getDefaultCount()) - { - MemoryPool& pool = *tdbb->getDefaultPool(); - - // We have a few parameters. Get on with creating the message block - // Outer messages map may start with 2, but they are always in the routine start. - USHORT n = ++csb->csb_msg_number; - if (n < 2) - csb->csb_msg_number = n = 2; - CompilerScratch::csb_repeat* tail = CMP_csb_element(csb, n); - - MessageNode* message = tail->csb_message = *message_ptr = FB_NEW_POOL(pool) MessageNode(pool); - message->messageNumber = n; - - const Format* format = input_flag ? procedure->getInputFormat() : procedure->getOutputFormat(); - /* dimitr: procedure (with its parameter formats) is allocated out of - its own pool (prc_request->req_pool) and can be freed during - the cache cleanup (MET_clear_cache). Since the current - tdbb default pool is different from the procedure's one, - it's dangerous to copy a pointer from one request to another. - As an experiment, I've decided to copy format by value - instead of copying the reference. Since Format structure - doesn't contain any pointers, it should be safe to use a - default assignment operator which does a simple byte copy. - This change fixes one serious bug in the current codebase. - I think that this situation can (and probably should) be - handled by the metadata cache (via incrementing prc_use_count) - to avoid unexpected cache cleanups, but that area is out of my - knowledge. So this fix should be considered a temporary solution. - - message->format = format; - */ - Format* fmt_copy = Format::newFormat(pool, format->fmt_count); - *fmt_copy = *format; - message->format = fmt_copy; - // --- end of fix --- - - n = format->fmt_count / 2; - - ValueListNode* sourceValues = *sourceList = FB_NEW_POOL(pool) ValueListNode(pool, n); - ValueListNode* targetValues = *targetList = FB_NEW_POOL(pool) ValueListNode(pool, n); - - NestConst* sourcePtr = - input_flag ? sourceValues->items.begin() : targetValues->items.begin(); - NestConst* targetPtr = - input_flag ? targetValues->items.begin() : sourceValues->items.begin(); - - for (USHORT i = 0; n; count--, n--) - { - // default value for parameter - if (count <= 0 && input_flag) - { - Parameter* parameter = procedure->getInputFields()[inputCount - n]; - *sourcePtr++ = CMP_clone_node(tdbb, csb, parameter->prm_default_value); - } - else - *sourcePtr++ = PAR_parse_value(tdbb, csb); - - ParameterNode* paramNode = FB_NEW_POOL(csb->csb_pool) ParameterNode(csb->csb_pool); - paramNode->messageNumber = message->messageNumber; - paramNode->message = message; - paramNode->argNumber = i++; - - ParameterNode* paramFlagNode = FB_NEW_POOL(csb->csb_pool) ParameterNode(csb->csb_pool); - paramFlagNode->messageNumber = message->messageNumber; - paramFlagNode->message = message; - paramFlagNode->argNumber = i++; - - paramNode->argFlag = paramFlagNode; - - *targetPtr++ = paramNode; - } - } - else if (input_flag ? inputCount : procedure->getOutputFields().getCount()) - { - PAR_error(csb, Arg::Gds(input_flag ? isc_prcmismat : isc_prc_out_param_mismatch) << - Arg::Str(procedure->getName().toString())); - } -} - - // Parse a RecordSourceNode. RecordSourceNode* PAR_parseRecordSource(thread_db* tdbb, CompilerScratch* csb) { @@ -1281,6 +1189,7 @@ RecordSourceNode* PAR_parseRecordSource(thread_db* tdbb, CompilerScratch* csb) case blr_procedure3: case blr_procedure4: case blr_subproc: + case blr_select_procedure: return ProcedureSourceNode::parse(tdbb, csb, blrOp, true); case blr_rse: @@ -1638,6 +1547,7 @@ DmlNode* PAR_parse_node(thread_db* tdbb, CompilerScratch* csb) case blr_procedure3: case blr_procedure4: case blr_subproc: + case blr_select_procedure: case blr_relation: case blr_rid: case blr_relation2: diff --git a/src/jrd/par_proto.h b/src/jrd/par_proto.h index cb3292bd4e..c72d71861c 100644 --- a/src/jrd/par_proto.h +++ b/src/jrd/par_proto.h @@ -54,6 +54,7 @@ void PAR_preparsed_node(Jrd::thread_db*, Jrd::jrd_rel*, Jrd::DmlNode*, Jrd::BoolExprNode* PAR_validation_blr(Jrd::thread_db*, Jrd::jrd_rel*, const UCHAR* blr, ULONG blr_length, Jrd::CompilerScratch*, Jrd::CompilerScratch**, USHORT); StreamType PAR_context(Jrd::CompilerScratch*, SSHORT*); +StreamType PAR_context2(Jrd::CompilerScratch*, SSHORT*); void PAR_dependency(Jrd::thread_db* tdbb, Jrd::CompilerScratch* csb, StreamType stream, SSHORT id, const Jrd::MetaName& field_name); USHORT PAR_datatype(Firebird::BlrReader&, dsc*); @@ -68,8 +69,6 @@ Jrd::CompilerScratch* PAR_parse(Jrd::thread_db*, const UCHAR* blr, ULONG blr_len bool internal_flag, ULONG = 0, const UCHAR* = NULL); Jrd::RecordSourceNode* PAR_parseRecordSource(Jrd::thread_db* tdbb, Jrd::CompilerScratch* csb); -void PAR_procedure_parms(Jrd::thread_db*, Jrd::CompilerScratch*, Jrd::jrd_prc*, - Jrd::MessageNode**, Jrd::ValueListNode**, Jrd::ValueListNode**, bool input_flag); Jrd::RseNode* PAR_rse(Jrd::thread_db*, Jrd::CompilerScratch*, SSHORT); Jrd::RseNode* PAR_rse(Jrd::thread_db*, Jrd::CompilerScratch*); Jrd::SortNode* PAR_sort(Jrd::thread_db*, Jrd::CompilerScratch*, UCHAR, bool); diff --git a/src/yvalve/gds.cpp b/src/yvalve/gds.cpp index d043688935..3e0f9c2f23 100644 --- a/src/yvalve/gds.cpp +++ b/src/yvalve/gds.cpp @@ -235,6 +235,7 @@ static void blr_print_cond(gds_ctl*, SSHORT); static int blr_print_dtype(gds_ctl*); static void blr_print_join(gds_ctl*); static SLONG blr_print_line(gds_ctl*, SSHORT); +static void blr_print_name(gds_ctl*); static void blr_print_verb(gds_ctl*, SSHORT); static int blr_print_word(gds_ctl*); @@ -323,6 +324,8 @@ 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; const int op_outer_map = 32; +const int op_invoke_function = 33; +const int op_invsel_procedure = 34; static const UCHAR // generic print formats @@ -413,7 +416,9 @@ static const UCHAR erase[] = { op_erase, 0}, local_table[] = { op_word, op_byte, op_literal, op_byte, op_line, 0}, outer_map[] = { op_outer_map, 0 }, - in_list[] = { op_verb, op_line, op_word, op_line, op_args, 0}; + in_list[] = { op_verb, op_line, op_word, op_line, op_args, 0}, + invoke_function[] = { op_invoke_function, 0 }, + invsel_procedure[] = { op_invsel_procedure, 0 }; #include "../jrd/blp.h" @@ -3325,6 +3330,15 @@ static SLONG blr_print_line(gds_ctl* control, SSHORT offset) } +static void blr_print_name(gds_ctl* control) +{ + auto len = blr_print_byte(control); + + while (len-- > 0) + blr_print_char(control); +} + + static void blr_print_verb(gds_ctl* control, SSHORT level) { /************************************** @@ -3938,6 +3952,206 @@ static void blr_print_verb(gds_ctl* control, SSHORT level) break; } + case op_invoke_function: + { + offset = blr_print_line(control, offset); + + static const char* subCodes[] = + { + nullptr, + "type", + "arg_names", + "args" + }; + + static const char* typeSubCodes[] = + { + nullptr, + "standalone", + "packaged", + "sub" + }; + + 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_invoke_function sub code ***"); + + blr_format(control, "blr_invoke_function_%s, ", subCodes[blr_operator]); + + switch (blr_operator) + { + case blr_invoke_function_type: + n = control->ctl_blr_reader.getByte(); + + if (n == 0 || n >= FB_NELEM(typeSubCodes)) + blr_error(control, "*** invalid blr_invoke_function_type sub code ***"); + + blr_format(control, "blr_invoke_function_type_%s,", typeSubCodes[n]); + offset = blr_print_line(control, (SSHORT) offset); + + blr_indent(control, level + 1); + blr_print_name(control); + offset = blr_print_line(control, (SSHORT) offset); + + if (n == blr_invoke_function_type_packaged) + { + blr_indent(control, level + 1); + blr_print_name(control); + offset = blr_print_line(control, (SSHORT) offset); + } + + break; + + case blr_invoke_function_arg_names: + n = blr_print_word(control); + offset = blr_print_line(control, offset); + + ++level; + + while (n-- > 0) + { + blr_indent(control, level); + blr_print_name(control); + offset = blr_print_line(control, (SSHORT) offset); + } + + --level; + break; + + case blr_invoke_function_args: + n = blr_print_word(control); + offset = blr_print_line(control, offset); + + ++level; + + while (n-- > 0) + blr_print_verb(control, level); + + --level; + break; + + default: + fb_assert(false); + } + } + + // print blr_end + control->ctl_blr_reader.seekBackward(1); + blr_print_verb(control, level); + break; + } + + case op_invsel_procedure: + { + offset = blr_print_line(control, offset); + + static const char* subCodes[] = + { + nullptr, + "type", + "in_arg_names", + "in_args", + "out_arg_names", + "out_args", + "context", + "alias" + }; + + static const char* typeSubCodes[] = + { + nullptr, + "standalone", + "packaged", + "sub" + }; + + 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_invsel_procedure sub code ***"); + + blr_format(control, "blr_invsel_procedure_%s, ", subCodes[blr_operator]); + + switch (blr_operator) + { + case blr_invsel_procedure_type: + n = control->ctl_blr_reader.getByte(); + + if (n == 0 || n >= FB_NELEM(typeSubCodes)) + blr_error(control, "*** invalid blr_invsel_procedure_type sub code ***"); + + blr_format(control, "blr_invsel_procedure_type_%s,", typeSubCodes[n]); + offset = blr_print_line(control, (SSHORT) offset); + + blr_indent(control, level + 1); + blr_print_name(control); + offset = blr_print_line(control, (SSHORT) offset); + + if (n == blr_invsel_procedure_type_packaged) + { + blr_indent(control, level + 1); + blr_print_name(control); + offset = blr_print_line(control, (SSHORT) offset); + } + + break; + + case blr_invsel_procedure_in_arg_names: + case blr_invsel_procedure_out_arg_names: + n = blr_print_word(control); + offset = blr_print_line(control, offset); + + ++level; + + while (n-- > 0) + { + blr_indent(control, level); + blr_print_name(control); + offset = blr_print_line(control, (SSHORT) offset); + } + + --level; + break; + + case blr_invsel_procedure_in_args: + case blr_invsel_procedure_out_args: + n = blr_print_word(control); + offset = blr_print_line(control, offset); + + ++level; + + while (n-- > 0) + blr_print_verb(control, level); + + --level; + break; + + case blr_invsel_procedure_context: + blr_print_word(control); + offset = blr_print_line(control, offset); + break; + + case blr_invsel_procedure_alias: + blr_print_name(control); + offset = blr_print_line(control, offset); + 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;