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:
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_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)
|
||||||
|
@ -1296,6 +1296,11 @@ public:
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ensureCapacity(unsigned count)
|
||||||
|
{
|
||||||
|
items.ensureCapacity(count);
|
||||||
|
}
|
||||||
|
|
||||||
void clear()
|
void clear()
|
||||||
{
|
{
|
||||||
items.clear();
|
items.clear();
|
||||||
|
@ -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(¶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),
|
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);
|
node->outputTargets = dsqlPassArray(dsqlScratch, outputTargets);
|
||||||
}
|
}
|
||||||
else
|
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;
|
||||||
|
@ -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;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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);
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user