mirror of
https://github.com/FirebirdSQL/firebird.git
synced 2025-01-22 18:03:03 +01:00
Feature #7587 - CALL statement.
This commit is contained in:
parent
d5069c7464
commit
b6248f6453
103
doc/sql.extensions/README.call.md
Normal file
103
doc/sql.extensions/README.call.md
Normal 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
|
||||
```
|
@ -104,6 +104,7 @@ PARSER_TOKEN(TOK_BOOLEAN, "BOOLEAN", false)
|
||||
PARSER_TOKEN(TOK_BOTH, "BOTH", false)
|
||||
PARSER_TOKEN(TOK_BREAK, "BREAK", true)
|
||||
PARSER_TOKEN(TOK_BY, "BY", false)
|
||||
PARSER_TOKEN(TOK_CALL, "CALL", false)
|
||||
PARSER_TOKEN(TOK_CALLER, "CALLER", true)
|
||||
PARSER_TOKEN(TOK_CASCADE, "CASCADE", true)
|
||||
PARSER_TOKEN(TOK_CASE, "CASE", false)
|
||||
|
@ -1296,6 +1296,11 @@ public:
|
||||
return this;
|
||||
}
|
||||
|
||||
void ensureCapacity(unsigned count)
|
||||
{
|
||||
items.ensureCapacity(count);
|
||||
}
|
||||
|
||||
void clear()
|
||||
{
|
||||
items.clear();
|
||||
|
@ -2929,6 +2929,10 @@ DmlNode* ExecProcedureNode::parse(thread_db* tdbb, MemoryPool& pool, CompilerScr
|
||||
const UCHAR* outArgNamesPos = nullptr;
|
||||
ObjectsArray<MetaName>* outArgNames = nullptr;
|
||||
USHORT outArgCount = 0;
|
||||
const UCHAR* inOutArgNamesPos = nullptr;
|
||||
ObjectsArray<MetaName>* inOutArgNames = nullptr;
|
||||
USHORT inOutArgCount = 0;
|
||||
ValueListNode* inOutArgs = nullptr;
|
||||
QualifiedName name;
|
||||
|
||||
const auto node = FB_NEW_POOL(pool) ExecProcedureNode(pool);
|
||||
@ -3035,6 +3039,34 @@ DmlNode* ExecProcedureNode::parse(thread_db* tdbb, MemoryPool& pool, CompilerScr
|
||||
node->outputTargets = PAR_args(tdbb, csb, outArgCount, outArgCount);
|
||||
break;
|
||||
|
||||
case blr_invsel_procedure_inout_arg_names:
|
||||
{
|
||||
predateCheck(node->procedure,
|
||||
"blr_invsel_procedure_type", "blr_invsel_procedure_inout_arg_names");
|
||||
predateCheck(!inOutArgs,
|
||||
"blr_invsel_procedure_inout_arg_names", "blr_invsel_procedure_inout_args");
|
||||
|
||||
inOutArgNamesPos = blrReader.getPos();
|
||||
USHORT inOutArgNamesCount = blrReader.getWord();
|
||||
MetaName argName;
|
||||
|
||||
inOutArgNames = FB_NEW_POOL(pool) ObjectsArray<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:
|
||||
PAR_error(csb, Arg::Gds(isc_random) << "Invalid blr_invoke_procedure sub code");
|
||||
}
|
||||
@ -3071,6 +3103,81 @@ DmlNode* ExecProcedureNode::parse(thread_db* tdbb, MemoryPool& pool, CompilerScr
|
||||
break;
|
||||
}
|
||||
|
||||
if (!node->procedure)
|
||||
{
|
||||
blrReader.setPos(blrStartPos);
|
||||
PAR_error(csb, Arg::Gds(isc_prcnotdef) << name.toString());
|
||||
}
|
||||
|
||||
if ((inOutArgs || inOutArgNames) && (node->inputSources || inArgNames || node->outputTargets || outArgNames))
|
||||
{
|
||||
blrReader.setPos(inOutArgNamesPos);
|
||||
PAR_error(csb, Arg::Gds(isc_random) << "IN/OUT args are not allowed with IN or OUT args");
|
||||
}
|
||||
|
||||
if (inOutArgs)
|
||||
{
|
||||
node->outputTargets = FB_NEW_POOL(pool) ValueListNode(pool);
|
||||
|
||||
const auto prcInCount = node->procedure->getInputFields().getCount();
|
||||
const auto positionalArgCount = inOutArgs->items.getCount() -
|
||||
(inOutArgNames ? inOutArgNames->getCount() : 0);
|
||||
|
||||
if (positionalArgCount > prcInCount || inOutArgNames)
|
||||
{
|
||||
node->inputSources = FB_NEW_POOL(pool) ValueListNode(pool);
|
||||
inArgNames = FB_NEW_POOL(pool) ObjectsArray<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())
|
||||
{
|
||||
blrReader.setPos(inArgNamesPos);
|
||||
@ -3079,12 +3186,6 @@ DmlNode* ExecProcedureNode::parse(thread_db* tdbb, MemoryPool& pool, CompilerScr
|
||||
"blr_invsel_procedure_in_arg_names count cannot be greater than blr_invsel_procedure_in_args");
|
||||
}
|
||||
|
||||
if (!node->procedure)
|
||||
{
|
||||
blrReader.setPos(blrStartPos);
|
||||
PAR_error(csb, Arg::Gds(isc_prcnotdef) << name.toString());
|
||||
}
|
||||
|
||||
if (blrOp != blr_invoke_procedure)
|
||||
{
|
||||
inArgCount = blrReader.getWord();
|
||||
@ -3100,12 +3201,12 @@ DmlNode* ExecProcedureNode::parse(thread_db* tdbb, MemoryPool& pool, CompilerScr
|
||||
if (!node->outputTargets)
|
||||
node->outputTargets = FB_NEW_POOL(pool) ValueListNode(pool);
|
||||
|
||||
if (outArgNames && outArgNames->getCount() != node->outputTargets->items.getCount())
|
||||
if (outArgNames && outArgNames->getCount() > node->outputTargets->items.getCount())
|
||||
{
|
||||
blrReader.setPos(outArgNamesPos);
|
||||
PAR_error(csb,
|
||||
Arg::Gds(isc_random) <<
|
||||
"blr_invsel_procedure_out_arg_names count differs from blr_invsel_procedure_out_args");
|
||||
"blr_invsel_procedure_out_arg_names count cannot be greater than blr_invsel_procedure_out_args");
|
||||
}
|
||||
|
||||
if (node->procedure->isImplemented() && !node->procedure->isDefined())
|
||||
@ -3206,7 +3307,8 @@ DmlNode* ExecProcedureNode::parse(thread_db* tdbb, MemoryPool& pool, CompilerScr
|
||||
|
||||
ExecProcedureNode* ExecProcedureNode::dsqlPass(DsqlCompilerScratch* dsqlScratch)
|
||||
{
|
||||
dsql_prc* procedure = NULL;
|
||||
auto& pool = dsqlScratch->getPool();
|
||||
dsql_prc* procedure = nullptr;
|
||||
|
||||
if (dsqlName.package.isEmpty())
|
||||
{
|
||||
@ -3228,13 +3330,139 @@ ExecProcedureNode* ExecProcedureNode::dsqlPass(DsqlCompilerScratch* dsqlScratch)
|
||||
if (!dsqlScratch->isPsql())
|
||||
dsqlScratch->getDsqlStatement()->setType(DsqlStatement::TYPE_EXEC_PROCEDURE);
|
||||
|
||||
const auto node = FB_NEW_POOL(dsqlScratch->getPool()) ExecProcedureNode(dsqlScratch->getPool(), dsqlName,
|
||||
if (dsqlCallSyntax && !dsqlScratch->isPsql() && inputSources && inputSources->items.hasData())
|
||||
{
|
||||
const auto positionalArgCount = inputSources->items.getCount() -
|
||||
(dsqlInputArgNames ? dsqlInputArgNames->getCount() : 0);
|
||||
|
||||
if (positionalArgCount > procedure->prc_in_count || dsqlInputArgNames)
|
||||
{
|
||||
const auto newInputs = FB_NEW_POOL(pool) ValueListNode(pool);
|
||||
const auto newOutputs = FB_NEW_POOL(pool) ValueListNode(pool);
|
||||
const auto newInputArgNames = FB_NEW_POOL(pool) ObjectsArray<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(¶meter->par_desc, field);
|
||||
parameter->par_name = parameter->par_alias = field->fld_name.c_str();
|
||||
parameter->par_rel_name = procedure->prc_name.identifier.c_str();
|
||||
parameter->par_owner_name = procedure->prc_owner.c_str();
|
||||
}
|
||||
|
||||
field = field->fld_next;
|
||||
++pos;
|
||||
++targetArgIt;
|
||||
}
|
||||
|
||||
if (dsqlOutputArgNames)
|
||||
{
|
||||
fb_assert(dsqlOutputArgNames->getCount() <= outputTargets->items.getCount());
|
||||
|
||||
LeftPooledMap<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(¶meter->par_desc, *field);
|
||||
parameter->par_name = parameter->par_alias = (*field)->fld_name.c_str();
|
||||
parameter->par_rel_name = procedure->prc_name.identifier.c_str();
|
||||
parameter->par_owner_name = procedure->prc_owner.c_str();
|
||||
}
|
||||
}
|
||||
else
|
||||
mismatchStatus << Arg::Gds(isc_param_not_exist) << argName;
|
||||
|
||||
++targetArgIt;
|
||||
}
|
||||
|
||||
if (mismatchStatus.hasData())
|
||||
status_exception::raise(Arg::Gds(isc_prcmismat) << dsqlName.toString() << mismatchStatus);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const auto node = FB_NEW_POOL(pool) ExecProcedureNode(pool, dsqlName,
|
||||
doDsqlPass(dsqlScratch, inputSources),
|
||||
nullptr,
|
||||
dsqlInputArgNames ?
|
||||
FB_NEW_POOL(dsqlScratch->getPool()) ObjectsArray<MetaName>(dsqlScratch->getPool(), *dsqlInputArgNames) :
|
||||
FB_NEW_POOL(pool) ObjectsArray<MetaName>(pool, *dsqlInputArgNames) :
|
||||
nullptr);
|
||||
|
||||
node->dsqlCallSyntax = dsqlCallSyntax;
|
||||
node->dsqlProcedure = procedure;
|
||||
|
||||
if (node->dsqlName.package.isEmpty() && procedure->prc_name.package.hasData())
|
||||
@ -3242,7 +3470,7 @@ ExecProcedureNode* ExecProcedureNode::dsqlPass(DsqlCompilerScratch* dsqlScratch)
|
||||
|
||||
// Handle input parameters.
|
||||
|
||||
if (inputSources && inputSources->items.hasData())
|
||||
if (node->inputSources && node->inputSources->items.hasData())
|
||||
{
|
||||
auto sourceArgIt = node->inputSources->items.begin();
|
||||
const auto sourceArgEnd = node->inputSources->items.end();
|
||||
@ -3274,6 +3502,12 @@ ExecProcedureNode* ExecProcedureNode::dsqlPass(DsqlCompilerScratch* dsqlScratch)
|
||||
for (const auto* field = procedure->prc_inputs; field; field = field->fld_next)
|
||||
argsByName.put(field->fld_name, field);
|
||||
|
||||
if (dsqlCallSyntax && dsqlScratch->isPsql())
|
||||
{
|
||||
for (const auto* field = procedure->prc_outputs; field; field = field->fld_next)
|
||||
argsByName.put(field->fld_name, field);
|
||||
}
|
||||
|
||||
Arg::StatusVector mismatchStatus;
|
||||
|
||||
for (const auto& argName : *node->dsqlInputArgNames)
|
||||
@ -3302,16 +3536,19 @@ ExecProcedureNode* ExecProcedureNode::dsqlPass(DsqlCompilerScratch* dsqlScratch)
|
||||
|
||||
if (dsqlScratch->isPsql())
|
||||
{
|
||||
const USHORT outCount = outputSources ? outputSources->items.getCount() : 0;
|
||||
if (!dsqlCallSyntax)
|
||||
{
|
||||
const USHORT outCount = outputTargets ? outputTargets->items.getCount() : 0;
|
||||
|
||||
if (outCount != procedure->prc_out_count)
|
||||
ERRD_post(Arg::Gds(isc_prc_out_param_mismatch) << Arg::Str(dsqlName.toString()));
|
||||
if (outCount != procedure->prc_out_count)
|
||||
ERRD_post(Arg::Gds(isc_prc_out_param_mismatch) << Arg::Str(dsqlName.toString()));
|
||||
}
|
||||
|
||||
node->outputSources = dsqlPassArray(dsqlScratch, outputSources);
|
||||
node->outputTargets = dsqlPassArray(dsqlScratch, outputTargets);
|
||||
}
|
||||
else
|
||||
else if (!dsqlCallSyntax)
|
||||
{
|
||||
if (outputSources)
|
||||
if (outputTargets)
|
||||
{
|
||||
ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-104) <<
|
||||
// Token unknown
|
||||
@ -3319,18 +3556,19 @@ ExecProcedureNode* ExecProcedureNode::dsqlPass(DsqlCompilerScratch* dsqlScratch)
|
||||
Arg::Gds(isc_random) << Arg::Str("RETURNING_VALUES"));
|
||||
}
|
||||
|
||||
node->outputSources = explodeOutputs(dsqlScratch, procedure);
|
||||
node->outputTargets = explodeOutputs(dsqlScratch, procedure);
|
||||
}
|
||||
else
|
||||
node->outputTargets = dsqlPassArray(dsqlScratch, outputTargets);
|
||||
|
||||
if (node->outputTargets)
|
||||
{
|
||||
for (const auto target : node->outputTargets->items)
|
||||
AssignmentNode::dsqlValidateTarget(target);
|
||||
}
|
||||
|
||||
if (node->outputSources)
|
||||
{
|
||||
for (const NestConst<ValueExprNode>* i = node->outputSources->items.begin();
|
||||
i != node->outputSources->items.end();
|
||||
++i)
|
||||
{
|
||||
AssignmentNode::dsqlValidateTarget(*i);
|
||||
}
|
||||
}
|
||||
if (dsqlOutputArgNames)
|
||||
node->dsqlOutputArgNames = FB_NEW_POOL(pool) ObjectsArray<MetaName>(pool, *dsqlOutputArgNames);
|
||||
|
||||
return node;
|
||||
}
|
||||
@ -3350,10 +3588,10 @@ ValueListNode* ExecProcedureNode::explodeOutputs(DsqlCompilerScratch* dsqlScratc
|
||||
{
|
||||
DEV_BLKCHK(field, dsql_type_fld);
|
||||
|
||||
ParameterNode* paramNode = FB_NEW_POOL(dsqlScratch->getPool()) ParameterNode(dsqlScratch->getPool());
|
||||
const auto paramNode = FB_NEW_POOL(dsqlScratch->getPool()) ParameterNode(dsqlScratch->getPool());
|
||||
*ptr = paramNode;
|
||||
|
||||
dsql_par* parameter = paramNode->dsqlParameter = MAKE_parameter(
|
||||
const auto parameter = paramNode->dsqlParameter = MAKE_parameter(
|
||||
dsqlScratch->getDsqlStatement()->getReceiveMsg(), true, true, 0, NULL);
|
||||
paramNode->dsqlParameterIndex = parameter->par_index;
|
||||
|
||||
@ -3395,7 +3633,7 @@ void ExecProcedureNode::genBlr(DsqlCompilerScratch* dsqlScratch)
|
||||
}
|
||||
}
|
||||
|
||||
if (dsqlInputArgNames)
|
||||
if (dsqlInputArgNames || dsqlOutputArgNames || dsqlCallSyntax)
|
||||
{
|
||||
dsqlScratch->appendUChar(blr_invoke_procedure);
|
||||
|
||||
@ -3414,19 +3652,23 @@ void ExecProcedureNode::genBlr(DsqlCompilerScratch* dsqlScratch)
|
||||
|
||||
dsqlScratch->appendMetaString(dsqlName.identifier.c_str());
|
||||
|
||||
const bool useInOut = dsqlScratch->isPsql() && dsqlCallSyntax;
|
||||
|
||||
// Input parameters.
|
||||
if (inputSources)
|
||||
{
|
||||
if (dsqlInputArgNames && dsqlInputArgNames->hasData())
|
||||
{
|
||||
dsqlScratch->appendUChar(blr_invsel_procedure_in_arg_names);
|
||||
dsqlScratch->appendUChar(
|
||||
useInOut ? blr_invsel_procedure_inout_arg_names : blr_invsel_procedure_in_arg_names);
|
||||
dsqlScratch->appendUShort(dsqlInputArgNames->getCount());
|
||||
|
||||
for (auto& argName : *dsqlInputArgNames)
|
||||
dsqlScratch->appendMetaString(argName.c_str());
|
||||
}
|
||||
|
||||
dsqlScratch->appendUChar(blr_invsel_procedure_in_args);
|
||||
dsqlScratch->appendUChar(
|
||||
useInOut ? blr_invsel_procedure_inout_args : blr_invsel_procedure_in_args);
|
||||
dsqlScratch->appendUShort(inputSources->items.getCount());
|
||||
|
||||
for (auto& arg : inputSources->items)
|
||||
@ -3434,13 +3676,22 @@ void ExecProcedureNode::genBlr(DsqlCompilerScratch* dsqlScratch)
|
||||
}
|
||||
|
||||
// Output parameters.
|
||||
if (outputSources)
|
||||
if (!useInOut && outputTargets)
|
||||
{
|
||||
dsqlScratch->appendUChar(blr_invsel_procedure_out_args);
|
||||
dsqlScratch->appendUShort(outputSources->items.getCount());
|
||||
if (dsqlOutputArgNames && dsqlOutputArgNames->hasData())
|
||||
{
|
||||
dsqlScratch->appendUChar(blr_invsel_procedure_out_arg_names);
|
||||
dsqlScratch->appendUShort(dsqlOutputArgNames->getCount());
|
||||
|
||||
for (auto& arg : outputSources->items)
|
||||
GEN_expr(dsqlScratch, arg);
|
||||
for (auto& argName : *dsqlOutputArgNames)
|
||||
dsqlScratch->appendMetaString(argName.c_str());
|
||||
}
|
||||
|
||||
dsqlScratch->appendUChar(blr_invsel_procedure_out_args);
|
||||
dsqlScratch->appendUShort(outputTargets->items.getCount());
|
||||
|
||||
for (auto& arg : outputTargets->items)
|
||||
GEN_arg(dsqlScratch, arg);
|
||||
}
|
||||
|
||||
dsqlScratch->appendUChar(blr_end);
|
||||
@ -3469,11 +3720,11 @@ void ExecProcedureNode::genBlr(DsqlCompilerScratch* dsqlScratch)
|
||||
dsqlScratch->appendUShort(0);
|
||||
|
||||
// Output parameters.
|
||||
if (outputSources)
|
||||
if (outputTargets)
|
||||
{
|
||||
dsqlScratch->appendUShort(outputSources->items.getCount());
|
||||
dsqlScratch->appendUShort(outputTargets->items.getCount());
|
||||
|
||||
for (auto& arg : outputSources->items)
|
||||
for (auto& arg : outputTargets->items)
|
||||
GEN_expr(dsqlScratch, arg);
|
||||
}
|
||||
else
|
||||
@ -3514,12 +3765,8 @@ ExecProcedureNode* ExecProcedureNode::pass2(thread_db* tdbb, CompilerScratch* cs
|
||||
|
||||
if (outputTargets)
|
||||
{
|
||||
for (const NestConst<ValueExprNode>* i = outputTargets->items.begin();
|
||||
i != outputTargets->items.end();
|
||||
++i)
|
||||
{
|
||||
AssignmentNode::validateTarget(csb, *i);
|
||||
}
|
||||
for (const auto target : outputTargets->items)
|
||||
AssignmentNode::validateTarget(csb, target);
|
||||
}
|
||||
|
||||
return this;
|
||||
|
@ -594,7 +594,7 @@ public:
|
||||
: TypedNode<StmtNode, StmtNode::TYPE_EXEC_PROCEDURE>(pool),
|
||||
dsqlName(pool, aDsqlName),
|
||||
inputSources(aInputs),
|
||||
outputSources(aOutputs),
|
||||
outputTargets(aOutputs),
|
||||
dsqlInputArgNames(aDsqlInputArgNames)
|
||||
{
|
||||
}
|
||||
@ -625,6 +625,8 @@ public:
|
||||
NestConst<MessageNode> outputMessage;
|
||||
NestConst<jrd_prc> procedure;
|
||||
NestConst<Firebird::ObjectsArray<MetaName>> dsqlInputArgNames;
|
||||
NestConst<Firebird::ObjectsArray<MetaName>> dsqlOutputArgNames;
|
||||
bool dsqlCallSyntax = false;
|
||||
};
|
||||
|
||||
|
||||
|
@ -1127,9 +1127,8 @@ static UCHAR* var_info(const dsql_msg* message,
|
||||
for (FB_SIZE_T i = 0; i < parameters.getCount(); i++)
|
||||
{
|
||||
const dsql_par* param = parameters[i];
|
||||
fb_assert(param);
|
||||
|
||||
if (param->par_index >= first_index)
|
||||
if (param && param->par_index >= first_index)
|
||||
{
|
||||
dsc desc = param->par_desc;
|
||||
|
||||
|
@ -170,9 +170,24 @@ void GEN_port(DsqlCompilerScratch* dsqlScratch, dsql_msg* message)
|
||||
DSqlDataTypeUtil dataTypeUtil(dsqlScratch);
|
||||
ULONG offset = 0;
|
||||
|
||||
class ParamCmp
|
||||
{
|
||||
public:
|
||||
static int greaterThan(const Jrd::dsql_par* p1, const Jrd::dsql_par* p2)
|
||||
{
|
||||
return p1->par_index > p2->par_index;
|
||||
}
|
||||
};
|
||||
|
||||
SortedArray<dsql_par*, InlineStorage<dsql_par*, 16>, dsql_par*,
|
||||
DefaultKeyValue<dsql_par*>, ParamCmp> dsqlParams;
|
||||
|
||||
for (FB_SIZE_T i = 0; i < message->msg_parameters.getCount(); ++i)
|
||||
{
|
||||
dsql_par* parameter = message->msg_parameters[i];
|
||||
const auto parameter = message->msg_parameters[i];
|
||||
|
||||
if (parameter->par_index)
|
||||
dsqlParams.add(parameter);
|
||||
|
||||
parameter->par_parameter = (USHORT) i;
|
||||
|
||||
@ -232,6 +247,13 @@ void GEN_port(DsqlCompilerScratch* dsqlScratch, dsql_msg* message)
|
||||
message->msg_length = offset;
|
||||
|
||||
dsqlScratch->getDsqlStatement()->getPorts().add(message);
|
||||
|
||||
// Remove gaps in par_index due to output parameters using question-marks (CALL syntax).
|
||||
|
||||
USHORT parIndex = 0;
|
||||
|
||||
for (auto dsqlParam : dsqlParams)
|
||||
dsqlParam->par_index = ++parIndex;
|
||||
}
|
||||
|
||||
|
||||
|
@ -699,6 +699,7 @@ using namespace Firebird;
|
||||
// tokens added for Firebird 6.0
|
||||
|
||||
%token <metaNamePtr> ANY_VALUE
|
||||
%token <metaNamePtr> CALL
|
||||
%token <metaNamePtr> NAMED_ARG_ASSIGN
|
||||
|
||||
// precedence declarations for expression evaluation
|
||||
@ -890,6 +891,7 @@ dml_statement
|
||||
| insert { $$ = $1; }
|
||||
| merge { $$ = $1; }
|
||||
| exec_procedure { $$ = $1; }
|
||||
| call { $$ = $1; }
|
||||
| exec_block { $$ = $1; }
|
||||
| select { $$ = $1; }
|
||||
| update { $$ = $1; }
|
||||
@ -3278,6 +3280,7 @@ simple_proc_statement
|
||||
| delete
|
||||
| singleton_select
|
||||
| exec_procedure
|
||||
| call { $$ = $1; }
|
||||
| exec_sql { $$ = $1; }
|
||||
| exec_into { $$ = $1; }
|
||||
| exec_function
|
||||
@ -3787,6 +3790,31 @@ proc_outputs_opt
|
||||
| RETURNING_VALUES '(' variable_list ')' { $$ = $3; }
|
||||
;
|
||||
|
||||
// CALL
|
||||
|
||||
%type <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
|
||||
|
||||
%type <execBlockNode> exec_block
|
||||
@ -4376,6 +4404,7 @@ keyword_or_column
|
||||
| VARBINARY
|
||||
| WINDOW
|
||||
| WITHOUT
|
||||
| CALL // added in FB 6.0
|
||||
;
|
||||
|
||||
col_opt
|
||||
|
@ -489,8 +489,10 @@
|
||||
#define blr_invsel_procedure_in_args (unsigned char) 3
|
||||
#define blr_invsel_procedure_out_arg_names (unsigned char) 4
|
||||
#define blr_invsel_procedure_out_args (unsigned char) 5
|
||||
#define blr_invsel_procedure_context (unsigned char) 6
|
||||
#define blr_invsel_procedure_alias (unsigned char) 7
|
||||
#define blr_invsel_procedure_inout_arg_names (unsigned char) 6
|
||||
#define blr_invsel_procedure_inout_args (unsigned char) 7
|
||||
#define blr_invsel_procedure_context (unsigned char) 8
|
||||
#define blr_invsel_procedure_alias (unsigned char) 9
|
||||
|
||||
#define blr_default_arg (unsigned char) 227
|
||||
|
||||
|
@ -4056,6 +4056,8 @@ static void blr_print_verb(gds_ctl* control, SSHORT level)
|
||||
"in_args",
|
||||
"out_arg_names",
|
||||
"out_args",
|
||||
"inout_arg_names",
|
||||
"inout_args",
|
||||
"context",
|
||||
"alias"
|
||||
};
|
||||
@ -4103,6 +4105,7 @@ static void blr_print_verb(gds_ctl* control, SSHORT level)
|
||||
|
||||
case blr_invsel_procedure_in_arg_names:
|
||||
case blr_invsel_procedure_out_arg_names:
|
||||
case blr_invsel_procedure_inout_arg_names:
|
||||
n = blr_print_word(control);
|
||||
offset = blr_print_line(control, offset);
|
||||
|
||||
@ -4120,6 +4123,7 @@ static void blr_print_verb(gds_ctl* control, SSHORT level)
|
||||
|
||||
case blr_invsel_procedure_in_args:
|
||||
case blr_invsel_procedure_out_args:
|
||||
case blr_invsel_procedure_inout_args:
|
||||
n = blr_print_word(control);
|
||||
offset = blr_print_line(control, offset);
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user