8
0
mirror of https://github.com/FirebirdSQL/firebird.git synced 2025-01-22 20:43:02 +01:00

Feature #7587 - CALL statement.

This commit is contained in:
Adriano dos Santos Fernandes 2023-09-19 07:29:03 -03:00
parent d5069c7464
commit b6248f6453
10 changed files with 467 additions and 53 deletions

View File

@ -0,0 +1,103 @@
# CALL statement (FB 6.0)
`CALL` statement is similar to `EXECUTE PROCEDURE`, but allow the caller to get specific output parameters, or none.
When using the positional or mixed parameter passing, output parameters follows the input ones.
When passing `NULL` to output parameters, they are ignored, and in the case of DSQL, not even returned.
In DSQL output parameters are specified using `?`, and in PSQL using the target variables or parameters.
## Syntax
```
<call statement> ::=
CALL [<package name> .] <procedure name> (<arguments>)
<arguments> ::=
<positional arguments> |
[ {<positional arguments>,} ] <named arguments>
<positional arguments> ::=
<value or default> [ {, <value or default>}... ]
<named arguments> ::=
<named argument> [ {, <named argument>}... ]
<named argument> ::=
<argument name> => <value or default>
<value or default> ::=
<value> |
DEFAULT
```
## Examples
```
create or alter procedure insert_customer (
last_name varchar(30),
first_name varchar(30)
) returns (
id integer,
full_name varchar(62)
)
as
begin
insert into customers (last_name, first_name)
values (:last_name, :first_name)
returning id, last_name || ', ' || first_name
into :id, :full_name;
end
```
```
-- Not all output parameters are necessary.
call insert_customer(
'LECLERC',
'CHARLES',
?)
```
```
-- Ignore first output parameter (using NULL) and get the second.
call insert_customer(
'LECLERC',
'CHARLES',
null,
?)
```
```
-- Ignore ID output parameter.
call insert_customer(
'LECLERC',
'CHARLES',
full_name => ?)
```
```
-- Pass inputs and get outputs using named arguments.
call insert_customer(
last_name => 'LECLERC',
first_name => 'CHARLES',
last_name => ?,
id => ?)
```
```
create or alter procedure do_something_and_insert_customer returns (
out_id integer,
out_full_name varchar(62)
)
as
declare last_name varchar(30);
declare first_name varchar(30);
begin
call insert_customer(
last_name,
first_name,
out_id,
full_name => out_full_name);
end
```

View File

@ -104,6 +104,7 @@ PARSER_TOKEN(TOK_BOOLEAN, "BOOLEAN", false)
PARSER_TOKEN(TOK_BOTH, "BOTH", false) PARSER_TOKEN(TOK_BOTH, "BOTH", false)
PARSER_TOKEN(TOK_BREAK, "BREAK", true) PARSER_TOKEN(TOK_BREAK, "BREAK", true)
PARSER_TOKEN(TOK_BY, "BY", false) PARSER_TOKEN(TOK_BY, "BY", false)
PARSER_TOKEN(TOK_CALL, "CALL", false)
PARSER_TOKEN(TOK_CALLER, "CALLER", true) PARSER_TOKEN(TOK_CALLER, "CALLER", true)
PARSER_TOKEN(TOK_CASCADE, "CASCADE", true) PARSER_TOKEN(TOK_CASCADE, "CASCADE", true)
PARSER_TOKEN(TOK_CASE, "CASE", false) PARSER_TOKEN(TOK_CASE, "CASE", false)

View File

@ -1296,6 +1296,11 @@ public:
return this; return this;
} }
void ensureCapacity(unsigned count)
{
items.ensureCapacity(count);
}
void clear() void clear()
{ {
items.clear(); items.clear();

View File

@ -2929,6 +2929,10 @@ DmlNode* ExecProcedureNode::parse(thread_db* tdbb, MemoryPool& pool, CompilerScr
const UCHAR* outArgNamesPos = nullptr; const UCHAR* outArgNamesPos = nullptr;
ObjectsArray<MetaName>* outArgNames = nullptr; ObjectsArray<MetaName>* outArgNames = nullptr;
USHORT outArgCount = 0; USHORT outArgCount = 0;
const UCHAR* inOutArgNamesPos = nullptr;
ObjectsArray<MetaName>* inOutArgNames = nullptr;
USHORT inOutArgCount = 0;
ValueListNode* inOutArgs = nullptr;
QualifiedName name; QualifiedName name;
const auto node = FB_NEW_POOL(pool) ExecProcedureNode(pool); const auto node = FB_NEW_POOL(pool) ExecProcedureNode(pool);
@ -3035,6 +3039,34 @@ DmlNode* ExecProcedureNode::parse(thread_db* tdbb, MemoryPool& pool, CompilerScr
node->outputTargets = PAR_args(tdbb, csb, outArgCount, outArgCount); node->outputTargets = PAR_args(tdbb, csb, outArgCount, outArgCount);
break; break;
case blr_invsel_procedure_inout_arg_names:
{
predateCheck(node->procedure,
"blr_invsel_procedure_type", "blr_invsel_procedure_inout_arg_names");
predateCheck(!inOutArgs,
"blr_invsel_procedure_inout_arg_names", "blr_invsel_procedure_inout_args");
inOutArgNamesPos = blrReader.getPos();
USHORT inOutArgNamesCount = blrReader.getWord();
MetaName argName;
inOutArgNames = FB_NEW_POOL(pool) ObjectsArray<MetaName>(pool);
while (inOutArgNamesCount--)
{
blrReader.getMetaName(argName);
inOutArgNames->add(argName);
}
break;
}
case blr_invsel_procedure_inout_args:
predateCheck(node->procedure, "blr_invsel_procedure_type", "blr_invsel_procedure_inout_args");
inOutArgCount = blrReader.getWord();
inOutArgs = PAR_args(tdbb, csb, inOutArgCount, inOutArgCount);
break;
default: default:
PAR_error(csb, Arg::Gds(isc_random) << "Invalid blr_invoke_procedure sub code"); PAR_error(csb, Arg::Gds(isc_random) << "Invalid blr_invoke_procedure sub code");
} }
@ -3071,6 +3103,81 @@ DmlNode* ExecProcedureNode::parse(thread_db* tdbb, MemoryPool& pool, CompilerScr
break; break;
} }
if (!node->procedure)
{
blrReader.setPos(blrStartPos);
PAR_error(csb, Arg::Gds(isc_prcnotdef) << name.toString());
}
if ((inOutArgs || inOutArgNames) && (node->inputSources || inArgNames || node->outputTargets || outArgNames))
{
blrReader.setPos(inOutArgNamesPos);
PAR_error(csb, Arg::Gds(isc_random) << "IN/OUT args are not allowed with IN or OUT args");
}
if (inOutArgs)
{
node->outputTargets = FB_NEW_POOL(pool) ValueListNode(pool);
const auto prcInCount = node->procedure->getInputFields().getCount();
const auto positionalArgCount = inOutArgs->items.getCount() -
(inOutArgNames ? inOutArgNames->getCount() : 0);
if (positionalArgCount > prcInCount || inOutArgNames)
{
node->inputSources = FB_NEW_POOL(pool) ValueListNode(pool);
inArgNames = FB_NEW_POOL(pool) ObjectsArray<MetaName>(pool);
outArgNames = FB_NEW_POOL(pool) ObjectsArray<MetaName>(pool);
SortedObjectsArray<MetaName> outFields;
for (const auto field : node->procedure->getOutputFields())
outFields.add(field->prm_name);
unsigned pos = 0;
for (auto source : inOutArgs->items)
{
const bool isInput = (pos < positionalArgCount && pos < prcInCount) ||
(pos >= positionalArgCount &&
!outFields.exist((*inOutArgNames)[pos - positionalArgCount]));
if (isInput)
{
node->inputSources->add(source);
if (pos >= positionalArgCount)
inArgNames->add((*inOutArgNames)[pos - positionalArgCount]);
}
else
{
node->outputTargets->add(source);
if (pos >= positionalArgCount)
outArgNames->add((*inOutArgNames)[pos - positionalArgCount]);
}
++pos;
}
delete inOutArgs;
inOutArgs = nullptr;
delete inOutArgNames;
inOutArgNames = nullptr;
}
else
{
node->inputSources = inOutArgs;
inArgNames = inOutArgNames;
}
inArgCount = node->inputSources->items.getCount();
outArgCount = node->outputTargets->items.getCount();
node->inputSources->ensureCapacity(node->procedure->getInputFields().getCount());
node->outputTargets->ensureCapacity(node->procedure->getOutputFields().getCount());
}
if (inArgNames && inArgNames->getCount() > node->inputSources->items.getCount()) if (inArgNames && inArgNames->getCount() > node->inputSources->items.getCount())
{ {
blrReader.setPos(inArgNamesPos); blrReader.setPos(inArgNamesPos);
@ -3079,12 +3186,6 @@ DmlNode* ExecProcedureNode::parse(thread_db* tdbb, MemoryPool& pool, CompilerScr
"blr_invsel_procedure_in_arg_names count cannot be greater than blr_invsel_procedure_in_args"); "blr_invsel_procedure_in_arg_names count cannot be greater than blr_invsel_procedure_in_args");
} }
if (!node->procedure)
{
blrReader.setPos(blrStartPos);
PAR_error(csb, Arg::Gds(isc_prcnotdef) << name.toString());
}
if (blrOp != blr_invoke_procedure) if (blrOp != blr_invoke_procedure)
{ {
inArgCount = blrReader.getWord(); inArgCount = blrReader.getWord();
@ -3100,12 +3201,12 @@ DmlNode* ExecProcedureNode::parse(thread_db* tdbb, MemoryPool& pool, CompilerScr
if (!node->outputTargets) if (!node->outputTargets)
node->outputTargets = FB_NEW_POOL(pool) ValueListNode(pool); node->outputTargets = FB_NEW_POOL(pool) ValueListNode(pool);
if (outArgNames && outArgNames->getCount() != node->outputTargets->items.getCount()) if (outArgNames && outArgNames->getCount() > node->outputTargets->items.getCount())
{ {
blrReader.setPos(outArgNamesPos); blrReader.setPos(outArgNamesPos);
PAR_error(csb, PAR_error(csb,
Arg::Gds(isc_random) << Arg::Gds(isc_random) <<
"blr_invsel_procedure_out_arg_names count differs from blr_invsel_procedure_out_args"); "blr_invsel_procedure_out_arg_names count cannot be greater than blr_invsel_procedure_out_args");
} }
if (node->procedure->isImplemented() && !node->procedure->isDefined()) if (node->procedure->isImplemented() && !node->procedure->isDefined())
@ -3206,7 +3307,8 @@ DmlNode* ExecProcedureNode::parse(thread_db* tdbb, MemoryPool& pool, CompilerScr
ExecProcedureNode* ExecProcedureNode::dsqlPass(DsqlCompilerScratch* dsqlScratch) ExecProcedureNode* ExecProcedureNode::dsqlPass(DsqlCompilerScratch* dsqlScratch)
{ {
dsql_prc* procedure = NULL; auto& pool = dsqlScratch->getPool();
dsql_prc* procedure = nullptr;
if (dsqlName.package.isEmpty()) if (dsqlName.package.isEmpty())
{ {
@ -3228,13 +3330,139 @@ ExecProcedureNode* ExecProcedureNode::dsqlPass(DsqlCompilerScratch* dsqlScratch)
if (!dsqlScratch->isPsql()) if (!dsqlScratch->isPsql())
dsqlScratch->getDsqlStatement()->setType(DsqlStatement::TYPE_EXEC_PROCEDURE); dsqlScratch->getDsqlStatement()->setType(DsqlStatement::TYPE_EXEC_PROCEDURE);
const auto node = FB_NEW_POOL(dsqlScratch->getPool()) ExecProcedureNode(dsqlScratch->getPool(), dsqlName, if (dsqlCallSyntax && !dsqlScratch->isPsql() && inputSources && inputSources->items.hasData())
{
const auto positionalArgCount = inputSources->items.getCount() -
(dsqlInputArgNames ? dsqlInputArgNames->getCount() : 0);
if (positionalArgCount > procedure->prc_in_count || dsqlInputArgNames)
{
const auto newInputs = FB_NEW_POOL(pool) ValueListNode(pool);
const auto newOutputs = FB_NEW_POOL(pool) ValueListNode(pool);
const auto newInputArgNames = FB_NEW_POOL(pool) ObjectsArray<MetaName>(pool);
const auto newOutputArgNames = FB_NEW_POOL(pool) ObjectsArray<MetaName>(pool);
SortedObjectsArray<MetaName> outFields;
for (const auto* field = procedure->prc_outputs; field; field = field->fld_next)
outFields.add(field->fld_name);
unsigned pos = 0;
for (auto source : inputSources->items)
{
const bool isInput = (pos < positionalArgCount && pos < procedure->prc_in_count) ||
(pos >= positionalArgCount &&
!outFields.exist((*dsqlInputArgNames)[pos - positionalArgCount]));
if (isInput)
{
newInputs->add(source);
if (pos >= positionalArgCount)
newInputArgNames->add((*dsqlInputArgNames)[pos - positionalArgCount]);
}
else
{
newOutputs->add(source);
if (pos >= positionalArgCount)
newOutputArgNames->add((*dsqlInputArgNames)[pos - positionalArgCount]);
}
++pos;
}
if (newInputs->items.getCount() != inputSources->items.getCount())
{
delete inputSources.getObject();
inputSources = newInputs;
delete dsqlInputArgNames.getObject();
dsqlInputArgNames = newInputArgNames;
delete outputTargets.getObject();
outputTargets = newOutputs;
delete dsqlOutputArgNames.getObject();
dsqlOutputArgNames = newOutputArgNames;
}
if (outputTargets && outputTargets->items.hasData())
{
auto targetArgIt = outputTargets->items.begin();
const auto targetArgEnd = outputTargets->items.end();
const auto positionalArgCount = outputTargets->items.getCount() -
(dsqlOutputArgNames ? dsqlOutputArgNames->getCount() : 0);
const auto* field = procedure->prc_outputs;
unsigned pos = 0;
while (pos < positionalArgCount && field && targetArgIt != targetArgEnd)
{
if (const auto paramNode = nodeAs<ParameterNode>(*targetArgIt))
{
const auto parameter = paramNode->dsqlParameter = MAKE_parameter(
dsqlScratch->getDsqlStatement()->getReceiveMsg(), true, true, 0, NULL);
paramNode->dsqlParameterIndex = parameter->par_index;
DsqlDescMaker::fromField(&parameter->par_desc, field);
parameter->par_name = parameter->par_alias = field->fld_name.c_str();
parameter->par_rel_name = procedure->prc_name.identifier.c_str();
parameter->par_owner_name = procedure->prc_owner.c_str();
}
field = field->fld_next;
++pos;
++targetArgIt;
}
if (dsqlOutputArgNames)
{
fb_assert(dsqlOutputArgNames->getCount() <= outputTargets->items.getCount());
LeftPooledMap<MetaName, const dsql_fld*> argsByName;
for (const auto* field = procedure->prc_outputs; field; field = field->fld_next)
argsByName.put(field->fld_name, field);
Arg::StatusVector mismatchStatus;
for (const auto& argName : *dsqlOutputArgNames)
{
if (const auto field = argsByName.get(argName))
{
if (const auto paramNode = nodeAs<ParameterNode>(*targetArgIt))
{
const auto parameter = paramNode->dsqlParameter = MAKE_parameter(
dsqlScratch->getDsqlStatement()->getReceiveMsg(), true, true, 0, NULL);
paramNode->dsqlParameterIndex = parameter->par_index;
DsqlDescMaker::fromField(&parameter->par_desc, *field);
parameter->par_name = parameter->par_alias = (*field)->fld_name.c_str();
parameter->par_rel_name = procedure->prc_name.identifier.c_str();
parameter->par_owner_name = procedure->prc_owner.c_str();
}
}
else
mismatchStatus << Arg::Gds(isc_param_not_exist) << argName;
++targetArgIt;
}
if (mismatchStatus.hasData())
status_exception::raise(Arg::Gds(isc_prcmismat) << dsqlName.toString() << mismatchStatus);
}
}
}
}
const auto node = FB_NEW_POOL(pool) ExecProcedureNode(pool, dsqlName,
doDsqlPass(dsqlScratch, inputSources), doDsqlPass(dsqlScratch, inputSources),
nullptr, nullptr,
dsqlInputArgNames ? dsqlInputArgNames ?
FB_NEW_POOL(dsqlScratch->getPool()) ObjectsArray<MetaName>(dsqlScratch->getPool(), *dsqlInputArgNames) : FB_NEW_POOL(pool) ObjectsArray<MetaName>(pool, *dsqlInputArgNames) :
nullptr); nullptr);
node->dsqlCallSyntax = dsqlCallSyntax;
node->dsqlProcedure = procedure; node->dsqlProcedure = procedure;
if (node->dsqlName.package.isEmpty() && procedure->prc_name.package.hasData()) if (node->dsqlName.package.isEmpty() && procedure->prc_name.package.hasData())
@ -3242,7 +3470,7 @@ ExecProcedureNode* ExecProcedureNode::dsqlPass(DsqlCompilerScratch* dsqlScratch)
// Handle input parameters. // Handle input parameters.
if (inputSources && inputSources->items.hasData()) if (node->inputSources && node->inputSources->items.hasData())
{ {
auto sourceArgIt = node->inputSources->items.begin(); auto sourceArgIt = node->inputSources->items.begin();
const auto sourceArgEnd = node->inputSources->items.end(); const auto sourceArgEnd = node->inputSources->items.end();
@ -3274,6 +3502,12 @@ ExecProcedureNode* ExecProcedureNode::dsqlPass(DsqlCompilerScratch* dsqlScratch)
for (const auto* field = procedure->prc_inputs; field; field = field->fld_next) for (const auto* field = procedure->prc_inputs; field; field = field->fld_next)
argsByName.put(field->fld_name, field); argsByName.put(field->fld_name, field);
if (dsqlCallSyntax && dsqlScratch->isPsql())
{
for (const auto* field = procedure->prc_outputs; field; field = field->fld_next)
argsByName.put(field->fld_name, field);
}
Arg::StatusVector mismatchStatus; Arg::StatusVector mismatchStatus;
for (const auto& argName : *node->dsqlInputArgNames) for (const auto& argName : *node->dsqlInputArgNames)
@ -3302,16 +3536,19 @@ ExecProcedureNode* ExecProcedureNode::dsqlPass(DsqlCompilerScratch* dsqlScratch)
if (dsqlScratch->isPsql()) if (dsqlScratch->isPsql())
{ {
const USHORT outCount = outputSources ? outputSources->items.getCount() : 0; if (!dsqlCallSyntax)
{
const USHORT outCount = outputTargets ? outputTargets->items.getCount() : 0;
if (outCount != procedure->prc_out_count) if (outCount != procedure->prc_out_count)
ERRD_post(Arg::Gds(isc_prc_out_param_mismatch) << Arg::Str(dsqlName.toString())); ERRD_post(Arg::Gds(isc_prc_out_param_mismatch) << Arg::Str(dsqlName.toString()));
node->outputSources = dsqlPassArray(dsqlScratch, outputSources);
} }
else
node->outputTargets = dsqlPassArray(dsqlScratch, outputTargets);
}
else if (!dsqlCallSyntax)
{ {
if (outputSources) if (outputTargets)
{ {
ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-104) << ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-104) <<
// Token unknown // Token unknown
@ -3319,18 +3556,19 @@ ExecProcedureNode* ExecProcedureNode::dsqlPass(DsqlCompilerScratch* dsqlScratch)
Arg::Gds(isc_random) << Arg::Str("RETURNING_VALUES")); Arg::Gds(isc_random) << Arg::Str("RETURNING_VALUES"));
} }
node->outputSources = explodeOutputs(dsqlScratch, procedure); node->outputTargets = explodeOutputs(dsqlScratch, procedure);
}
else
node->outputTargets = dsqlPassArray(dsqlScratch, outputTargets);
if (node->outputTargets)
{
for (const auto target : node->outputTargets->items)
AssignmentNode::dsqlValidateTarget(target);
} }
if (node->outputSources) if (dsqlOutputArgNames)
{ node->dsqlOutputArgNames = FB_NEW_POOL(pool) ObjectsArray<MetaName>(pool, *dsqlOutputArgNames);
for (const NestConst<ValueExprNode>* i = node->outputSources->items.begin();
i != node->outputSources->items.end();
++i)
{
AssignmentNode::dsqlValidateTarget(*i);
}
}
return node; return node;
} }
@ -3350,10 +3588,10 @@ ValueListNode* ExecProcedureNode::explodeOutputs(DsqlCompilerScratch* dsqlScratc
{ {
DEV_BLKCHK(field, dsql_type_fld); DEV_BLKCHK(field, dsql_type_fld);
ParameterNode* paramNode = FB_NEW_POOL(dsqlScratch->getPool()) ParameterNode(dsqlScratch->getPool()); const auto paramNode = FB_NEW_POOL(dsqlScratch->getPool()) ParameterNode(dsqlScratch->getPool());
*ptr = paramNode; *ptr = paramNode;
dsql_par* parameter = paramNode->dsqlParameter = MAKE_parameter( const auto parameter = paramNode->dsqlParameter = MAKE_parameter(
dsqlScratch->getDsqlStatement()->getReceiveMsg(), true, true, 0, NULL); dsqlScratch->getDsqlStatement()->getReceiveMsg(), true, true, 0, NULL);
paramNode->dsqlParameterIndex = parameter->par_index; paramNode->dsqlParameterIndex = parameter->par_index;
@ -3395,7 +3633,7 @@ void ExecProcedureNode::genBlr(DsqlCompilerScratch* dsqlScratch)
} }
} }
if (dsqlInputArgNames) if (dsqlInputArgNames || dsqlOutputArgNames || dsqlCallSyntax)
{ {
dsqlScratch->appendUChar(blr_invoke_procedure); dsqlScratch->appendUChar(blr_invoke_procedure);
@ -3414,19 +3652,23 @@ void ExecProcedureNode::genBlr(DsqlCompilerScratch* dsqlScratch)
dsqlScratch->appendMetaString(dsqlName.identifier.c_str()); dsqlScratch->appendMetaString(dsqlName.identifier.c_str());
const bool useInOut = dsqlScratch->isPsql() && dsqlCallSyntax;
// Input parameters. // Input parameters.
if (inputSources) if (inputSources)
{ {
if (dsqlInputArgNames && dsqlInputArgNames->hasData()) if (dsqlInputArgNames && dsqlInputArgNames->hasData())
{ {
dsqlScratch->appendUChar(blr_invsel_procedure_in_arg_names); dsqlScratch->appendUChar(
useInOut ? blr_invsel_procedure_inout_arg_names : blr_invsel_procedure_in_arg_names);
dsqlScratch->appendUShort(dsqlInputArgNames->getCount()); dsqlScratch->appendUShort(dsqlInputArgNames->getCount());
for (auto& argName : *dsqlInputArgNames) for (auto& argName : *dsqlInputArgNames)
dsqlScratch->appendMetaString(argName.c_str()); dsqlScratch->appendMetaString(argName.c_str());
} }
dsqlScratch->appendUChar(blr_invsel_procedure_in_args); dsqlScratch->appendUChar(
useInOut ? blr_invsel_procedure_inout_args : blr_invsel_procedure_in_args);
dsqlScratch->appendUShort(inputSources->items.getCount()); dsqlScratch->appendUShort(inputSources->items.getCount());
for (auto& arg : inputSources->items) for (auto& arg : inputSources->items)
@ -3434,13 +3676,22 @@ void ExecProcedureNode::genBlr(DsqlCompilerScratch* dsqlScratch)
} }
// Output parameters. // Output parameters.
if (outputSources) if (!useInOut && outputTargets)
{ {
dsqlScratch->appendUChar(blr_invsel_procedure_out_args); if (dsqlOutputArgNames && dsqlOutputArgNames->hasData())
dsqlScratch->appendUShort(outputSources->items.getCount()); {
dsqlScratch->appendUChar(blr_invsel_procedure_out_arg_names);
dsqlScratch->appendUShort(dsqlOutputArgNames->getCount());
for (auto& arg : outputSources->items) for (auto& argName : *dsqlOutputArgNames)
GEN_expr(dsqlScratch, arg); dsqlScratch->appendMetaString(argName.c_str());
}
dsqlScratch->appendUChar(blr_invsel_procedure_out_args);
dsqlScratch->appendUShort(outputTargets->items.getCount());
for (auto& arg : outputTargets->items)
GEN_arg(dsqlScratch, arg);
} }
dsqlScratch->appendUChar(blr_end); dsqlScratch->appendUChar(blr_end);
@ -3469,11 +3720,11 @@ void ExecProcedureNode::genBlr(DsqlCompilerScratch* dsqlScratch)
dsqlScratch->appendUShort(0); dsqlScratch->appendUShort(0);
// Output parameters. // Output parameters.
if (outputSources) if (outputTargets)
{ {
dsqlScratch->appendUShort(outputSources->items.getCount()); dsqlScratch->appendUShort(outputTargets->items.getCount());
for (auto& arg : outputSources->items) for (auto& arg : outputTargets->items)
GEN_expr(dsqlScratch, arg); GEN_expr(dsqlScratch, arg);
} }
else else
@ -3514,12 +3765,8 @@ ExecProcedureNode* ExecProcedureNode::pass2(thread_db* tdbb, CompilerScratch* cs
if (outputTargets) if (outputTargets)
{ {
for (const NestConst<ValueExprNode>* i = outputTargets->items.begin(); for (const auto target : outputTargets->items)
i != outputTargets->items.end(); AssignmentNode::validateTarget(csb, target);
++i)
{
AssignmentNode::validateTarget(csb, *i);
}
} }
return this; return this;

View File

@ -594,7 +594,7 @@ public:
: TypedNode<StmtNode, StmtNode::TYPE_EXEC_PROCEDURE>(pool), : TypedNode<StmtNode, StmtNode::TYPE_EXEC_PROCEDURE>(pool),
dsqlName(pool, aDsqlName), dsqlName(pool, aDsqlName),
inputSources(aInputs), inputSources(aInputs),
outputSources(aOutputs), outputTargets(aOutputs),
dsqlInputArgNames(aDsqlInputArgNames) dsqlInputArgNames(aDsqlInputArgNames)
{ {
} }
@ -625,6 +625,8 @@ public:
NestConst<MessageNode> outputMessage; NestConst<MessageNode> outputMessage;
NestConst<jrd_prc> procedure; NestConst<jrd_prc> procedure;
NestConst<Firebird::ObjectsArray<MetaName>> dsqlInputArgNames; NestConst<Firebird::ObjectsArray<MetaName>> dsqlInputArgNames;
NestConst<Firebird::ObjectsArray<MetaName>> dsqlOutputArgNames;
bool dsqlCallSyntax = false;
}; };

View File

@ -1127,9 +1127,8 @@ static UCHAR* var_info(const dsql_msg* message,
for (FB_SIZE_T i = 0; i < parameters.getCount(); i++) for (FB_SIZE_T i = 0; i < parameters.getCount(); i++)
{ {
const dsql_par* param = parameters[i]; const dsql_par* param = parameters[i];
fb_assert(param);
if (param->par_index >= first_index) if (param && param->par_index >= first_index)
{ {
dsc desc = param->par_desc; dsc desc = param->par_desc;

View File

@ -170,9 +170,24 @@ void GEN_port(DsqlCompilerScratch* dsqlScratch, dsql_msg* message)
DSqlDataTypeUtil dataTypeUtil(dsqlScratch); DSqlDataTypeUtil dataTypeUtil(dsqlScratch);
ULONG offset = 0; ULONG offset = 0;
class ParamCmp
{
public:
static int greaterThan(const Jrd::dsql_par* p1, const Jrd::dsql_par* p2)
{
return p1->par_index > p2->par_index;
}
};
SortedArray<dsql_par*, InlineStorage<dsql_par*, 16>, dsql_par*,
DefaultKeyValue<dsql_par*>, ParamCmp> dsqlParams;
for (FB_SIZE_T i = 0; i < message->msg_parameters.getCount(); ++i) for (FB_SIZE_T i = 0; i < message->msg_parameters.getCount(); ++i)
{ {
dsql_par* parameter = message->msg_parameters[i]; const auto parameter = message->msg_parameters[i];
if (parameter->par_index)
dsqlParams.add(parameter);
parameter->par_parameter = (USHORT) i; parameter->par_parameter = (USHORT) i;
@ -232,6 +247,13 @@ void GEN_port(DsqlCompilerScratch* dsqlScratch, dsql_msg* message)
message->msg_length = offset; message->msg_length = offset;
dsqlScratch->getDsqlStatement()->getPorts().add(message); dsqlScratch->getDsqlStatement()->getPorts().add(message);
// Remove gaps in par_index due to output parameters using question-marks (CALL syntax).
USHORT parIndex = 0;
for (auto dsqlParam : dsqlParams)
dsqlParam->par_index = ++parIndex;
} }

View File

@ -699,6 +699,7 @@ using namespace Firebird;
// tokens added for Firebird 6.0 // tokens added for Firebird 6.0
%token <metaNamePtr> ANY_VALUE %token <metaNamePtr> ANY_VALUE
%token <metaNamePtr> CALL
%token <metaNamePtr> NAMED_ARG_ASSIGN %token <metaNamePtr> NAMED_ARG_ASSIGN
// precedence declarations for expression evaluation // precedence declarations for expression evaluation
@ -890,6 +891,7 @@ dml_statement
| insert { $$ = $1; } | insert { $$ = $1; }
| merge { $$ = $1; } | merge { $$ = $1; }
| exec_procedure { $$ = $1; } | exec_procedure { $$ = $1; }
| call { $$ = $1; }
| exec_block { $$ = $1; } | exec_block { $$ = $1; }
| select { $$ = $1; } | select { $$ = $1; }
| update { $$ = $1; } | update { $$ = $1; }
@ -3278,6 +3280,7 @@ simple_proc_statement
| delete | delete
| singleton_select | singleton_select
| exec_procedure | exec_procedure
| call { $$ = $1; }
| exec_sql { $$ = $1; } | exec_sql { $$ = $1; }
| exec_into { $$ = $1; } | exec_into { $$ = $1; }
| exec_function | exec_function
@ -3787,6 +3790,31 @@ proc_outputs_opt
| RETURNING_VALUES '(' variable_list ')' { $$ = $3; } | RETURNING_VALUES '(' variable_list ')' { $$ = $3; }
; ;
// CALL
%type <stmtNode> call
call
: CALL symbol_procedure_name '(' argument_list_opt ')'
{
auto node = newNode<ExecProcedureNode>(QualifiedName(*$2),
($4 ? $4->second : nullptr),
nullptr,
($4 ? $4->first : nullptr));
node->dsqlCallSyntax = true;
$$ = node;
}
| CALL symbol_package_name '.' symbol_procedure_name '(' argument_list_opt ')'
into_variable_list_opt
{
auto node = newNode<ExecProcedureNode>(QualifiedName(*$4, *$2),
($6 ? $6->second : nullptr),
nullptr,
($6 ? $6->first : nullptr));
node->dsqlCallSyntax = true;
$$ = node;
}
;
// EXECUTE BLOCK // EXECUTE BLOCK
%type <execBlockNode> exec_block %type <execBlockNode> exec_block
@ -4376,6 +4404,7 @@ keyword_or_column
| VARBINARY | VARBINARY
| WINDOW | WINDOW
| WITHOUT | WITHOUT
| CALL // added in FB 6.0
; ;
col_opt col_opt

View File

@ -489,8 +489,10 @@
#define blr_invsel_procedure_in_args (unsigned char) 3 #define blr_invsel_procedure_in_args (unsigned char) 3
#define blr_invsel_procedure_out_arg_names (unsigned char) 4 #define blr_invsel_procedure_out_arg_names (unsigned char) 4
#define blr_invsel_procedure_out_args (unsigned char) 5 #define blr_invsel_procedure_out_args (unsigned char) 5
#define blr_invsel_procedure_context (unsigned char) 6 #define blr_invsel_procedure_inout_arg_names (unsigned char) 6
#define blr_invsel_procedure_alias (unsigned char) 7 #define blr_invsel_procedure_inout_args (unsigned char) 7
#define blr_invsel_procedure_context (unsigned char) 8
#define blr_invsel_procedure_alias (unsigned char) 9
#define blr_default_arg (unsigned char) 227 #define blr_default_arg (unsigned char) 227

View File

@ -4056,6 +4056,8 @@ static void blr_print_verb(gds_ctl* control, SSHORT level)
"in_args", "in_args",
"out_arg_names", "out_arg_names",
"out_args", "out_args",
"inout_arg_names",
"inout_args",
"context", "context",
"alias" "alias"
}; };
@ -4103,6 +4105,7 @@ static void blr_print_verb(gds_ctl* control, SSHORT level)
case blr_invsel_procedure_in_arg_names: case blr_invsel_procedure_in_arg_names:
case blr_invsel_procedure_out_arg_names: case blr_invsel_procedure_out_arg_names:
case blr_invsel_procedure_inout_arg_names:
n = blr_print_word(control); n = blr_print_word(control);
offset = blr_print_line(control, offset); offset = blr_print_line(control, offset);
@ -4120,6 +4123,7 @@ static void blr_print_verb(gds_ctl* control, SSHORT level)
case blr_invsel_procedure_in_args: case blr_invsel_procedure_in_args:
case blr_invsel_procedure_out_args: case blr_invsel_procedure_out_args:
case blr_invsel_procedure_inout_args:
n = blr_print_word(control); n = blr_print_word(control);
offset = blr_print_line(control, offset); offset = blr_print_line(control, offset);