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

Merge pull request #7557 from FirebirdSQL/work/named-arguments

Named arguments, DEFAULT in argument list and CALL statement
This commit is contained in:
Adriano dos Santos Fernandes 2023-09-19 10:41:57 +00:00 committed by GitHub
commit ffd31e3303
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
31 changed files with 2343 additions and 652 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

@ -0,0 +1,73 @@
# 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 those who have default values.
As the positional syntax, all arguments without default values are required to be present in the call.
A call can use positional, named or mixed arguments. In mixed syntax, positional arguments must appear before
named arguments.
## Syntax
```
<function call> ::=
[<package name> .] <function_name>( [<arguments>] )
<procedure selection> ::=
[<package name> .] <procedure_name> [( <arguments> )]
<execute procedure> ::=
EXECUTE PROCEDURE [<package name> .] <procedure name>
[{ (<arguments>) | <arguments> }]
[RETURNING_VALUES ...]
<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
```
select function_name(parameter2 => 'Two', parameter1 => 1)
from rdb$database
```
```
select function_name(1, parameter2 => 'Two')
from rdb$database
```
```
select function_name(default, parameter2 => 'Two')
from rdb$database
```
```
execute procedure insert_customer(
last_name => 'SCHUMACHER',
first_name => 'MICHAEL')
```
```
select *
from get_customers(city_id => 10, last_name => 'SCHUMACHER')
```

View File

@ -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)
@ -103,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)

View File

@ -299,6 +299,10 @@ public:
size_t count() const { return mCount; }
bool isEmpty() const { return mCount == 0; }
bool hasData() const { return mCount != 0; }
Accessor accessor()
{
return Accessor(this);

View File

@ -486,11 +486,16 @@ public:
}
}
void ensureCapacity(size_type newcapacity)
{
ensureCapacity(newcapacity, true);
}
protected:
size_type count, capacity;
T* data;
void ensureCapacity(size_type newcapacity, bool preserve = true)
void ensureCapacity(size_type newcapacity, bool preserve)
{
if (newcapacity > capacity)
{

View File

@ -12875,102 +12875,265 @@ dsc* TrimNode::execute(thread_db* tdbb, Request* request) const
//--------------------
static RegisterNode<UdfCallNode> regUdfCallNode({blr_function, blr_function2, blr_subfunc});
static RegisterNode<UdfCallNode> 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<MetaName>* aDsqlArgNames)
: TypedNode<ValueExprNode, ExprNode::TYPE_UDF_CALL>(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<MetaName>* 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;
}
}
Function* function = node->function;
if (!function)
function = node->function = Function::lookup(tdbb, name, false);
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<MetaName>(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, 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 (argNames && argNames->getCount() > argCount)
{
Parameter* const parameter = function->getInputFields()[i];
node->args->items[i] = CMP_clone_node(tdbb, csb, parameter->prm_default_value);
blrReader.setPos(argNamesPos);
PAR_error(csb,
Arg::Gds(isc_random) <<
"blr_invoke_function_arg_names count cannot be greater than blr_invoke_function_args");
}
if (!node->function)
{
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;
if (!node->args)
node->args = FB_NEW_POOL(pool) ValueListNode(pool);
const auto positionalArgCount = argNames ? argCount - argNames->getCount() : argCount;
auto argIt = node->args->items.begin();
LeftPooledMap<MetaName, NestConst<ValueExprNode>> argsByName;
if (positionalArgCount)
{
if (argCount > node->function->fun_inputs)
mismatchStatus << Arg::Gds(isc_wronumarg);
for (auto pos = 0; pos < positionalArgCount; ++pos)
{
if (pos < node->function->fun_inputs)
{
const auto& parameter = node->function->getInputFields()[pos];
if (argsByName.put(parameter->prm_name, *argIt))
mismatchStatus << Arg::Gds(isc_param_multiple_assignments) << parameter->prm_name;
}
++argIt;
}
}
if (argNames)
{
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())
{
const auto argValue = argsByName.get(parameter->prm_name);
if (argValue)
{
*argIt = *argValue;
argsByName.remove(parameter->prm_name);
}
if (!argValue || !*argValue)
{
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;
}
if (mismatchStatus.hasData())
status_exception::raise(Arg::Gds(isc_fun_param_mismatch) << name.toString() << 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;
@ -12993,6 +13156,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_arg(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
@ -13005,7 +13207,7 @@ void UdfCallNode::genBlr(DsqlCompilerScratch* dsqlScratch)
dsqlScratch->appendUChar(args->items.getCount());
for (auto& arg : args->items)
GEN_expr(dsqlScratch, arg);
GEN_arg(dsqlScratch, arg);
}
void UdfCallNode::make(DsqlCompilerScratch* /*dsqlScratch*/, dsc* desc)
@ -13356,8 +13558,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<MetaName>(dsqlScratch->getPool(), *dsqlArgNames) :
nullptr);
if (name.package.isEmpty())
{
@ -13375,30 +13580,50 @@ 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()));
auto argIt = node->args->items.begin();
unsigned pos = 0;
for (auto& arg : node->args->items)
while (pos < node->args->items.getCount() - (node->dsqlArgNames ? node->dsqlArgNames->getCount() : 0))
{
if (pos < node->dsqlFunction->udf_arguments.getCount())
{
PASS1_set_parameter_type(dsqlScratch, arg,
[&] (dsc* desc) { *desc = node->dsqlFunction->udf_arguments[pos]; },
PASS1_set_parameter_type(dsqlScratch, *argIt++,
[&] (dsc* desc) { *desc = node->dsqlFunction->udf_arguments[pos].desc; },
false);
}
else
{
// We should complain here in the future! The parameter is
// out of bounds or the function doesn't declare input params.
}
++pos;
}
if (node->dsqlArgNames)
{
fb_assert(node->dsqlArgNames->getCount() <= node->args->items.getCount());
LeftPooledMap<MetaName, const dsc*> argsByName;
for (const auto& arg : node->dsqlFunction->udf_arguments)
argsByName.put(arg.name, &arg.desc);
Arg::StatusVector mismatchStatus;
for (const auto& argName : *node->dsqlArgNames)
{
if (const auto argDescPtr = argsByName.get(argName))
{
PASS1_set_parameter_type(dsqlScratch, *argIt,
[&] (dsc* desc) { *desc = **argDescPtr; },
false);
}
else
mismatchStatus << Arg::Gds(isc_param_not_exist) << argName;
++argIt;
}
if (mismatchStatus.hasData())
status_exception::raise(Arg::Gds(isc_fun_param_mismatch) << name.toString() << mismatchStatus);
}
return node;
}

View File

@ -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<MetaName>* 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<ValueListNode> args;
NestConst<Firebird::ObjectsArray<MetaName>> dsqlArgNames;
NestConst<Function> function;
private:
dsql_udf* dsqlFunction;
bool isSubRoutine;
dsql_udf* dsqlFunction = nullptr;
bool isSubRoutine = false;
};

View File

@ -1270,6 +1270,12 @@ public:
items.push(arg1);
}
ValueListNode(MemoryPool& pool)
: TypedNode<ListExprNode, ExprNode::TYPE_VALUE_LIST>(pool),
items(pool, INITIAL_CAPACITY)
{
}
virtual void getChildren(NodeRefsHolder& holder, bool dsql) const
{
ListExprNode::getChildren(holder, dsql);
@ -1290,6 +1296,11 @@ public:
return this;
}
void ensureCapacity(unsigned count)
{
items.ensureCapacity(count);
}
void clear()
{
items.clear();

View File

@ -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,401 @@ const StmtNode* ErrorHandlerNode::execute(thread_db* /*tdbb*/, Request* request,
static RegisterNode<ExecProcedureNode> 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<MetaName>* inArgNames = nullptr;
USHORT inArgCount = 0;
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;
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<MetaName>(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,
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<MetaName>(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;
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");
}
}
}
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 ((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)
{
csb->csb_blr_reader.setPos(blrStartPos);
PAR_error(csb,
Arg::Gds(isc_prcnotdef) << Arg::Str(name.toString()) <<
Arg::Gds(isc_modnotfound));
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);
PAR_error(csb,
Arg::Gds(isc_random) <<
"blr_invsel_procedure_in_arg_names count cannot be greater than blr_invsel_procedure_in_args");
}
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 (outArgNames && outArgNames->getCount() > node->outputTargets->items.getCount())
{
blrReader.setPos(outArgNamesPos);
PAR_error(csb,
Arg::Gds(isc_random) <<
"blr_invsel_procedure_out_arg_names count cannot be greater than 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));
}
}
node->inputTargets = FB_NEW_POOL(pool) ValueListNode(pool, node->procedure->getInputFields().getCount());
Arg::StatusVector 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.hasData())
status_exception::raise(Arg::Gds(isc_prcmismat) << node->procedure->getName().toString() << 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)
{
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;
@ -2985,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())
{
@ -3001,13 +3324,145 @@ ExecProcedureNode* ExecProcedureNode::dsqlPass(DsqlCompilerScratch* dsqlScratch)
ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-204) <<
Arg::Gds(isc_dsql_procedure_err) <<
Arg::Gds(isc_random) <<
Arg::Str(dsqlName.toString()));
dsqlName.toString());
}
if (!dsqlScratch->isPsql())
dsqlScratch->getDsqlStatement()->setType(DsqlStatement::TYPE_EXEC_PROCEDURE);
ExecProcedureNode* 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),
nullptr,
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())
@ -3015,28 +3470,65 @@ 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->inputSources && node->inputSources->items.hasData())
{
// Initialize this stack variable, and make it look like a node.
dsc desc_node;
auto sourceArgIt = node->inputSources->items.begin();
const auto sourceArgEnd = node->inputSources->items.end();
const auto positionalArgCount = node->inputSources->items.getCount() -
(node->dsqlInputArgNames ? node->dsqlInputArgNames->getCount() : 0);
const auto* field = procedure->prc_inputs;
unsigned pos = 0;
NestConst<ValueExprNode>* ptr = node->inputSources->items.begin();
const NestConst<ValueExprNode>* end = node->inputSources->items.end();
for (const dsql_fld* field = procedure->prc_inputs; ptr != end; ++ptr, field = field->fld_next)
while (pos < positionalArgCount && field && sourceArgIt != sourceArgEnd)
{
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; },
dsc descNode;
DsqlDescMaker::fromField(&descNode, field);
PASS1_set_parameter_type(dsqlScratch, *sourceArgIt,
[&] (dsc* desc) { *desc = descNode; },
false);
field = field->fld_next;
++pos;
++sourceArgIt;
}
if (node->dsqlInputArgNames)
{
fb_assert(node->dsqlInputArgNames->getCount() <= node->inputSources->items.getCount());
LeftPooledMap<MetaName, const dsql_fld*> argsByName;
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)
{
if (const auto field = argsByName.get(argName))
{
dsc descNode;
DsqlDescMaker::fromField(&descNode, *field);
PASS1_set_parameter_type(dsqlScratch, *sourceArgIt,
[&] (dsc* desc) { *desc = descNode; },
false);
}
else
mismatchStatus << Arg::Gds(isc_param_not_exist) << argName;
++sourceArgIt;
}
if (mismatchStatus.hasData())
status_exception::raise(Arg::Gds(isc_prcmismat) << dsqlName.toString() << mismatchStatus);
}
}
@ -3044,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
@ -3061,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;
}
@ -3092,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;
@ -3125,7 +3621,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 +3633,104 @@ void ExecProcedureNode::genBlr(DsqlCompilerScratch* dsqlScratch)
}
}
if (dsqlName.package.hasData())
if (dsqlInputArgNames || dsqlOutputArgNames || dsqlCallSyntax)
{
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());
const bool useInOut = dsqlScratch->isPsql() && dsqlCallSyntax;
// Input parameters.
if (inputSources)
{
if (dsqlInputArgNames && dsqlInputArgNames->hasData())
{
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(
useInOut ? blr_invsel_procedure_inout_args : blr_invsel_procedure_in_args);
dsqlScratch->appendUShort(inputSources->items.getCount());
for (auto& arg : inputSources->items)
GEN_arg(dsqlScratch, arg);
}
// Output parameters.
if (!useInOut && outputTargets)
{
if (dsqlOutputArgNames && dsqlOutputArgNames->hasData())
{
dsqlScratch->appendUChar(blr_invsel_procedure_out_arg_names);
dsqlScratch->appendUShort(dsqlOutputArgNames->getCount());
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);
}
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());
for (auto& arg : inputSources->items)
GEN_arg(dsqlScratch, arg);
}
else
dsqlScratch->appendUShort(0);
// Output parameters.
if (outputTargets)
{
dsqlScratch->appendUShort(outputTargets->items.getCount());
for (auto& arg : outputTargets->items)
GEN_expr(dsqlScratch, arg);
}
else
dsqlScratch->appendUShort(0);
}
dsqlScratch->appendMetaString(dsqlName.identifier.c_str());
// Input parameters.
if (inputSources)
{
dsqlScratch->appendUShort(inputSources->items.getCount());
NestConst<ValueExprNode>* ptr = inputSources->items.begin();
const NestConst<ValueExprNode>* 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<ValueExprNode>* ptr = outputSources->items.begin();
for (const NestConst<ValueExprNode>* end = outputSources->items.end(); ptr != end; ++ptr)
GEN_expr(dsqlScratch, *ptr);
}
else
dsqlScratch->appendUShort(0);
if (message)
dsqlScratch->appendUChar(blr_end);
}
@ -3209,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;

View File

@ -589,23 +589,20 @@ class ExecProcedureNode final : public TypedNode<StmtNode, StmtNode::TYPE_EXEC_P
public:
explicit ExecProcedureNode(MemoryPool& pool,
const QualifiedName& aDsqlName = QualifiedName(),
ValueListNode* aInputs = NULL, ValueListNode* aOutputs = NULL)
ValueListNode* aInputs = nullptr, ValueListNode* aOutputs = nullptr,
Firebird::ObjectsArray<MetaName>* aDsqlInputArgNames = nullptr)
: TypedNode<StmtNode, StmtNode::TYPE_EXEC_PROCEDURE>(pool),
dsqlName(pool, aDsqlName),
dsqlProcedure(NULL),
inputSources(aInputs),
inputTargets(NULL),
inputMessage(NULL),
outputSources(aOutputs),
outputTargets(NULL),
outputMessage(NULL),
procedure(NULL)
outputTargets(aOutputs),
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<ValueListNode> inputSources;
NestConst<ValueListNode> inputTargets;
NestConst<MessageNode> inputMessage;
@ -627,6 +624,9 @@ public:
NestConst<ValueListNode> outputTargets;
NestConst<MessageNode> outputMessage;
NestConst<jrd_prc> procedure;
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++)
{
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;

View File

@ -318,6 +318,19 @@ enum prc_flags_vals {
//! User defined function block
class dsql_udf : public pool_alloc<dsql_type_udf>
{
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<dsc> udf_arguments;
Firebird::ObjectsArray<Argument> udf_arguments;
bool udf_private = false; // Packaged private function
SSHORT udf_def_count = 0; // number of inputs with default values
};

View File

@ -65,6 +65,17 @@ using namespace Firebird;
static void gen_plan(DsqlCompilerScratch*, const PlanNode*);
// Generate blr for an argument.
// When it is nullptr, generate blr_default_arg.
void GEN_arg(DsqlCompilerScratch* dsqlScratch, ExprNode* node)
{
if (node)
GEN_expr(dsqlScratch, node);
else
dsqlScratch->appendUChar(blr_default_arg);
}
void GEN_hidden_variables(DsqlCompilerScratch* dsqlScratch)
{
/**************************************
@ -159,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;
@ -221,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;
}

View File

@ -30,6 +30,7 @@ namespace Jrd
class ValueListNode;
}
void GEN_arg(Jrd::DsqlCompilerScratch*, Jrd::ExprNode*);
void GEN_descriptor(Jrd::DsqlCompilerScratch* dsqlScratch, const dsc* desc, bool texttype);
void GEN_expr(Jrd::DsqlCompilerScratch*, Jrd::ExprNode*);
void GEN_hidden_variables(Jrd::DsqlCompilerScratch* dsqlScratch);

View File

@ -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;
}
}
}

View File

@ -699,6 +699,8 @@ 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
@ -761,6 +763,8 @@ using namespace Firebird;
Jrd::DbFileClause* dbFileClause;
Firebird::Array<NestConst<Jrd::DbFileClause> >* dbFilesClause;
Jrd::ExternalClause* externalClause;
Firebird::NonPooledPair<Jrd::MetaName*, Jrd::ValueExprNode*>* namedArgument;
Firebird::NonPooledPair<Firebird::ObjectsArray<Jrd::MetaName>*, Jrd::ValueListNode*>* namedArguments;
Firebird::Array<NestConst<Jrd::ParameterClause> >* parametersClause;
Jrd::WindowClause* windowClause;
Jrd::WindowClause::FrameExtent* windowClauseFrameExtent;
@ -887,6 +891,7 @@ dml_statement
| insert { $$ = $1; }
| merge { $$ = $1; }
| exec_procedure { $$ = $1; }
| call { $$ = $1; }
| exec_block { $$ = $1; }
| select { $$ = $1; }
| update { $$ = $1; }
@ -3275,6 +3280,7 @@ simple_proc_statement
| delete
| singleton_select
| exec_procedure
| call { $$ = $1; }
| exec_sql { $$ = $1; }
| exec_into { $$ = $1; }
| exec_function
@ -3753,16 +3759,28 @@ fetch_scroll($cursorStmtNode)
%type <stmtNode> exec_procedure
exec_procedure
: EXECUTE PROCEDURE symbol_procedure_name proc_inputs proc_outputs_opt
{ $$ = newNode<ExecProcedureNode>(QualifiedName(*$3), $4, $5); }
{
$$ = newNode<ExecProcedureNode>(
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<ExecProcedureNode>(QualifiedName(*$5, *$3), $6, $7); }
{
$$ = newNode<ExecProcedureNode>(
QualifiedName(*$5, *$3),
($6 ? $6->second : nullptr),
$7,
($6 ? $6->first : nullptr));
}
;
%type <valueListNode> proc_inputs
%type <namedArguments> proc_inputs
proc_inputs
: /* nothing */ { $$ = NULL; }
| value_list { $$ = $1; }
| '(' value_list ')' { $$ = $2; }
: /* nothing */ { $$ = nullptr; }
| argument_list { $$ = $1; }
| '(' argument_list ')' { $$ = $2; }
;
%type <valueListNode> proc_outputs_opt
@ -3772,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
@ -4361,6 +4404,7 @@ keyword_or_column
| VARBINARY
| WINDOW
| WITHOUT
| CALL // added in FB 6.0
;
col_opt
@ -6248,38 +6292,42 @@ named_columns_join
table_proc
: symbol_procedure_name table_proc_inputs as_noise symbol_table_alias_name
{
ProcedureSourceNode* node = newNode<ProcedureSourceNode>(QualifiedName(*$1));
node->sourceList = $2;
const auto node = newNode<ProcedureSourceNode>(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<ProcedureSourceNode>(QualifiedName(*$1));
node->sourceList = $2;
const auto node = newNode<ProcedureSourceNode>(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<ProcedureSourceNode>(
const auto node = newNode<ProcedureSourceNode>(
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<ProcedureSourceNode>(
const auto node = newNode<ProcedureSourceNode>(
QualifiedName(*$3, *$1));
node->sourceList = $4;
node->inputSources = $4 ? $4->second : nullptr;
node->dsqlInputArgNames = $4 ? $4->first : nullptr;
$$ = node;
}
;
%type <valueListNode> table_proc_inputs
%type <namedArguments> table_proc_inputs
table_proc_inputs
: /* nothing */ { $$ = NULL; }
| '(' value_list ')' { $$ = $2; }
: /* nothing */ { $$ = nullptr; }
| '(' argument_list ')' { $$ = $2; }
;
%type <relSourceNode> table_name
@ -8580,14 +8628,64 @@ trim_specification
%type <valueExprNode> udf
udf
: symbol_UDF_call_name '(' value_list ')'
{ $$ = newNode<UdfCallNode>(QualifiedName(*$1, ""), $3); }
| symbol_UDF_call_name '(' ')'
{ $$ = newNode<UdfCallNode>(QualifiedName(*$1, ""), newNode<ValueListNode>(0)); }
| symbol_package_name '.' symbol_UDF_name '(' value_list ')'
{ $$ = newNode<UdfCallNode>(QualifiedName(*$3, *$1), $5); }
| symbol_package_name '.' symbol_UDF_name '(' ')'
{ $$ = newNode<UdfCallNode>(QualifiedName(*$3, *$1), newNode<ValueListNode>(0)); }
: symbol_UDF_call_name '(' argument_list_opt ')'
{ $$ = newNode<UdfCallNode>(QualifiedName(*$1, ""), $3->second, $3->first); }
| symbol_package_name '.' symbol_UDF_name '(' argument_list_opt ')'
{ $$ = newNode<UdfCallNode>(QualifiedName(*$3, *$1), $5->second, $5->first); }
;
%type <namedArguments> argument_list_opt
argument_list_opt
: // nothing
{
$$ = newNode<NonPooledPair<ObjectsArray<MetaName>*, ValueListNode*>>();
$$->second = newNode<ValueListNode>();
}
| argument_list
;
%type <namedArguments> argument_list
argument_list
: named_argument_list
| value_or_default_list
{
$$ = newNode<NonPooledPair<ObjectsArray<MetaName>*, ValueListNode*>>();
$$->second = $1;
}
| value_or_default_list ',' named_argument_list
{
$$ = $3;
for (auto item : $$->second->items)
$1->add(item);
delete $$->second;
$$->second = $1;
}
;
%type <namedArguments> named_argument_list
named_argument_list
: named_argument
{
$$ = newNode<NonPooledPair<ObjectsArray<MetaName>*, ValueListNode*>>();
$$->first = newNode<ObjectsArray<MetaName>>();
$$->first->add(*$1->first);
$$->second = newNode<ValueListNode>();
$$->second->add($1->second);
}
| named_argument_list ',' named_argument
{
$$ = $1;
$$->first->add(*$3->first);
$$->second->add($3->second);
}
;
%type <namedArgument> named_argument
named_argument
: symbol_column_name NAMED_ARG_ASSIGN value_or_default
{ $$ = newNode<NonPooledPair<MetaName*, ValueExprNode*>>($1, $3); }
;
%type <valueExprNode> cast_specification

View File

@ -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,34 +529,73 @@ 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();
}
if (count > procedure->prc_in_count ||
count < procedure->prc_in_count - procedure->prc_def_count)
{
ERRD_post(Arg::Gds(isc_prcmismat) << Arg::Str(procNode->dsqlName.toString()));
ERRD_post(Arg::Gds(isc_prcmismat) << procNode->dsqlName.toString());
}
if (count)
{
// Initialize this stack variable, and make it look like a node
dsc desc_node;
ValueListNode* inputList = context->ctx_proc_inputs;
NestConst<ValueExprNode>* input = inputList->items.begin();
auto inputList = context->ctx_proc_inputs;
auto argIt = inputList->items.begin();
const auto argEnd = inputList->items.end();
for (dsql_fld* field = procedure->prc_inputs;
input != inputList->items.end();
++input, field = field->fld_next)
const auto positionalArgCount = inputList->items.getCount() -
(procNode->dsqlInputArgNames ? procNode->dsqlInputArgNames->getCount() : 0);
const auto* field = procedure->prc_inputs;
unsigned pos = 0;
while (pos < positionalArgCount && field && argIt != argEnd)
{
DEV_BLKCHK(field, dsql_type_fld);
DsqlDescMaker::fromField(&desc_node, field);
PASS1_set_parameter_type(dsqlScratch, *input,
[&] (dsc* desc) { *desc = desc_node; },
dsc descNode;
DsqlDescMaker::fromField(&descNode, field);
PASS1_set_parameter_type(dsqlScratch, *argIt,
[&] (dsc* desc) { *desc = descNode; },
false);
field = field->fld_next;
++pos;
++argIt;
}
if (procNode->dsqlInputArgNames)
{
fb_assert(procNode->dsqlInputArgNames->getCount() <= inputList->items.getCount());
LeftPooledMap<MetaName, const dsql_fld*> argsByName;
for (const auto* field = procedure->prc_inputs; field; field = field->fld_next)
argsByName.put(field->fld_name, field);
Arg::StatusVector mismatchStatus;
for (const auto& argName : *procNode->dsqlInputArgNames)
{
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
mismatchStatus << Arg::Gds(isc_param_not_exist) << argName;
++argIt;
}
if (mismatchStatus.hasData())
status_exception::raise(Arg::Gds(isc_prcmismat) << procNode->dsqlName.toString() << mismatchStatus);
}
}
}
@ -1677,21 +1710,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<ProcedureSourceNode>(input)->dsqlInputArgNames;
return procNode;
}
//// TODO: LocalTableSourceNode

View File

@ -467,4 +467,33 @@
#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_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
#endif // FIREBIRD_IMPL_BLR_H

View File

@ -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 or was specified with DEFAULT")
FB_IMPL_MSG(JRD, 972, param_multiple_assignments, -170, "07", "001", "Parameter @1 has multiple assignments")

View File

@ -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;

View File

@ -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<string> aliasString;
auto& pool = *tdbb->getDefaultPool();
auto& blrReader = csb->csb_blr_reader;
const auto blrStartPos = blrReader.getPos();
const UCHAR* inArgNamesPos = nullptr;
ObjectsArray<MetaName>* 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<MetaName>(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,
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,108 @@ 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 (inArgNames && inArgNames->getCount() > node->inputSources->items.getCount())
{
if (procedure->isImplemented() && !procedure->isDefined())
blrReader.setPos(inArgNamesPos);
PAR_error(csb,
Arg::Gds(isc_random) <<
"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 (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);
node->inputTargets = FB_NEW_POOL(pool) ValueListNode(pool, node->procedure->getInputFields().getCount());
Arg::StatusVector mismatchStatus= CMP_procedure_arguments(
tdbb,
csb,
node->procedure,
true,
inArgCount,
inArgNames,
node->inputSources,
node->inputTargets,
node->inputMessage);
if (mismatchStatus.hasData())
status_exception::raise(Arg::Gds(isc_prcmismat) << node->procedure->getName().toString() << 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 +1180,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 +1197,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 +1206,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 +1215,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 +1224,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 +1233,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 +1242,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 +1257,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_arg(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 +1327,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 +1350,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<ValueExprNode>* ptr = inputs->items.begin();
ptr != inputs->items.end();
++ptr)
{
GEN_expr(dsqlScratch, *ptr);
}
for (auto& arg : inputSources->items)
GEN_arg(dsqlScratch, arg);
}
else
dsqlScratch->appendUShort(0);
@ -1180,12 +1386,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<MessageNode*> autoMessage(&copier.message, newSource->in_msg);
newSource->sourceList = copier.copy(tdbb, sourceList);
newSource->targetList = copier.copy(tdbb, targetList);
AutoSetRestore<MessageNode*> autoMessage(&copier.message, newSource->inputMessage);
newSource->inputSources = copier.copy(tdbb, inputSources);
newSource->inputTargets = copier.copy(tdbb, inputTargets);
}
newSource->stream = copier.csb->nextStream();
@ -1211,9 +1417,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 +1462,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 +1481,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 +1499,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 +1912,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 +3658,13 @@ static RecordSourceNode* dsqlPassRelProc(DsqlCompilerScratch* dsqlScratch, Recor
MetaName relName;
string relAlias;
if (auto procNode = nodeAs<ProcedureSourceNode>(source))
if (const auto procNode = nodeAs<ProcedureSourceNode>(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<RelationSourceNode>(source))
else if (const auto relNode = nodeAs<RelationSourceNode>(source))
{
relName = relNode->dsqlName;
relAlias = relNode->alias;

View File

@ -434,21 +434,14 @@ public:
const QualifiedName& aDsqlName = QualifiedName())
: TypedNode<RecordSourceNode, RecordSourceNode::TYPE_PROCEDURE>(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<ValueListNode> sourceList;
NestConst<ValueListNode> targetList;
jrd_prc* procedure = nullptr;
NestConst<ValueListNode> inputSources;
NestConst<ValueListNode> inputTargets;
NestConst<Firebird::ObjectsArray<MetaName>> dsqlInputArgNames;
private:
NestConst<MessageNode> in_msg;
NestConst<MessageNode> 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<RecordSourceNode, RecordSourceNode::TYPE_AGGREGATE_SOURCE>

View File

@ -255,5 +255,9 @@ 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},
{"blr_default_arg", zero},
{0, 0}
};

View File

@ -459,6 +459,161 @@ 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<MetaName>* argNames,
NestConst<ValueListNode>& sources,
NestConst<ValueListNode>& targets,
NestConst<MessageNode>& 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);
// 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 ---
const auto positionalArgCount = argNames ? argCount - argNames->getCount() : argCount;
auto sourceArgIt = sources->items.begin();
LeftPooledMap<MetaName, NestConst<ValueExprNode>> argsByName;
if (positionalArgCount)
{
if (argCount > fields.getCount())
mismatchStatus << Arg::Gds(isc_wronumarg);
for (auto pos = 0; pos < positionalArgCount; ++pos)
{
if (pos < fields.getCount())
{
const auto& parameter = fields[pos];
if (argsByName.put(parameter->prm_name, *sourceArgIt))
mismatchStatus << Arg::Gds(isc_param_multiple_assignments) << parameter->prm_name;
}
++sourceArgIt;
}
}
if (argNames)
{
for (const auto& argName : *argNames)
{
if (argsByName.put(argName, *sourceArgIt++))
mismatchStatus << Arg::Gds(isc_param_multiple_assignments) << argName;
}
}
sourceArgIt = sources->items.begin();
auto targetArgIt = targets->items.begin();
for (auto& parameter : fields)
{
const auto argValue = argsByName.get(parameter->prm_name);
if (argValue)
{
*sourceArgIt = *argValue;
argsByName.remove(parameter->prm_name);
}
if (isInput && (!argValue || !*argValue))
{
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 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)
{
/**************************************

View File

@ -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<Jrd::MetaName>* argNames,
NestConst<Jrd::ValueListNode>& sources,
NestConst<Jrd::ValueListNode>& targets,
NestConst<Jrd::MessageNode>& 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& = "");

View File

@ -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 RoutineManager<ProcedureManager, jrd_prc, obj_procedure,
@ -1096,7 +1096,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);
};
// These methods cannot be defined inline, because GPRE generates wrong code.
@ -1141,9 +1141,48 @@ namespace
END_FOR
}
void FunctionManager::checkOutParamDependencies(thread_db* tdbb, DeferredWork* work, jrd_tra* transaction)
void FunctionManager::checkParamDependencies(thread_db* tdbb, DeferredWork* work, jrd_tra* transaction)
{
// Do nothing, as function output is unnamed.
Jrd::Attachment* attachment = tdbb->getAttachment();
AutoCacheRequest handle(tdbb, irq_func_param_dep, IRQ_REQUESTS);
ObjectsArray<string> 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<string> 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

View File

@ -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

View File

@ -685,109 +685,66 @@ void MET_clear_cache(thread_db* tdbb)
}
}
const auto walkProcFunc = [&att](std::function<void (Routine*)> func)
{
for (const auto routine : att->att_procedures)
{
if (routine)
func(routine);
}
for (const auto routine : att->att_functions)
{
if (routine)
func(routine);
}
};
// Walk routines and calculate internal dependencies.
for (jrd_prc** iter = att->att_procedures.begin(); iter != att->att_procedures.end(); ++iter)
walkProcFunc([](Routine* routine)
{
Routine* const routine = *iter;
if (routine && routine->getStatement() &&
if (routine->getStatement() &&
!(routine->flags & Routine::FLAG_OBSOLETE) )
{
inc_int_use_count(routine->getStatement());
}
}
for (Function** iter = att->att_functions.begin(); iter != att->att_functions.end(); ++iter)
{
Function* const routine = *iter;
if (routine && routine->getStatement() &&
!(routine->flags & Routine::FLAG_OBSOLETE) )
{
inc_int_use_count(routine->getStatement());
}
}
});
// Walk routines again and adjust dependencies for routines which will not be removed.
for (jrd_prc** iter = att->att_procedures.begin(); iter != att->att_procedures.end(); ++iter)
walkProcFunc([](Routine* routine)
{
Routine* const routine = *iter;
if (routine && routine->getStatement() &&
if (routine->getStatement() &&
!(routine->flags & Routine::FLAG_OBSOLETE) &&
routine->useCount != routine->intUseCount )
{
adjust_dependencies(routine);
}
}
for (Function** iter = att->att_functions.begin(); iter != att->att_functions.end(); ++iter)
{
Function* const routine = *iter;
if (routine && routine->getStatement() &&
!(routine->flags & Routine::FLAG_OBSOLETE) &&
routine->useCount != routine->intUseCount )
{
adjust_dependencies(routine);
}
}
});
// Deallocate all used requests.
for (jrd_prc** iter = att->att_procedures.begin(); iter != att->att_procedures.end(); ++iter)
walkProcFunc([&tdbb](Routine* routine)
{
Routine* const routine = *iter;
if (routine)
if (routine->getStatement() && !(routine->flags & Routine::FLAG_OBSOLETE) &&
routine->intUseCount >= 0 &&
routine->useCount == routine->intUseCount)
{
if (routine->getStatement() && !(routine->flags & Routine::FLAG_OBSOLETE) &&
routine->intUseCount >= 0 &&
routine->useCount == routine->intUseCount)
{
routine->releaseStatement(tdbb);
routine->releaseStatement(tdbb);
if (routine->existenceLock)
LCK_release(tdbb, routine->existenceLock);
routine->existenceLock = NULL;
routine->flags |= Routine::FLAG_OBSOLETE;
}
// Leave it in state 0 to avoid extra pass next time to clear it
// Note: we need to adjust intUseCount for all routines
// in cache because any of them may have been affected from
// dependencies earlier. Even routines that were not scanned yet !
routine->intUseCount = 0;
if (routine->existenceLock)
LCK_release(tdbb, routine->existenceLock);
routine->existenceLock = NULL;
routine->flags |= Routine::FLAG_OBSOLETE;
}
}
for (Function** iter = att->att_functions.begin(); iter != att->att_functions.end(); ++iter)
{
Function* const routine = *iter;
if (routine)
{
if (routine->getStatement() && !(routine->flags & Routine::FLAG_OBSOLETE) &&
routine->intUseCount >= 0 &&
routine->useCount == routine->intUseCount)
{
routine->releaseStatement(tdbb);
if (routine->existenceLock)
LCK_release(tdbb, routine->existenceLock);
routine->existenceLock = NULL;
routine->flags |= Routine::FLAG_OBSOLETE;
}
// Leave it in state 0 to avoid extra pass next time to clear it
// Note: we need to adjust intUseCount for all routines
// in cache because any of them may have been affected from
// dependencies earlier. Even routines that were not scanned yet !
routine->intUseCount = 0;
}
}
// Leave it in state 0 to avoid extra pass next time to clear it
// Note: we need to adjust intUseCount for all routines
// in cache because any of them may have been affected from
// dependencies earlier. Even routines that were not scanned yet !
routine->intUseCount = 0;
});
#ifdef DEV_BUILD
MET_verify_cache(tdbb);

View File

@ -804,7 +804,12 @@ ValueListNode* PAR_args(thread_db* tdbb, CompilerScratch* csb, USHORT count, USH
{
do
{
*ptr++ = PAR_parse_value(tdbb, csb);
if (csb->csb_blr_reader.peekByte() == blr_default_arg)
csb->csb_blr_reader.getByte();
else
*ptr = PAR_parse_value(tdbb, csb);
++ptr;
} while (--count);
}
@ -821,28 +826,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 +855,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 +985,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 +999,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 +1021,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 +1178,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<ValueExprNode>* sourcePtr =
input_flag ? sourceValues->items.begin() : targetValues->items.begin();
NestConst<ValueExprNode>* 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 +1194,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 +1552,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:

View File

@ -39,6 +39,8 @@ namespace Jrd {
class StmtNode;
class ValueExprNode;
class ValueListNode;
using NodeParseFunc = DmlNode* (*)(thread_db* tdbb, MemoryPool& pool, CompilerScratch* csb, const UCHAR blrOp);
}
struct dsc;
@ -52,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*);
@ -66,22 +69,17 @@ 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);
Jrd::SortNode* PAR_sort_internal(Jrd::thread_db*, Jrd::CompilerScratch*, bool, USHORT);
SLONG PAR_symbol_to_gdscode(const Firebird::string&);
typedef Jrd::DmlNode* (*NodeParseFunc)(Jrd::thread_db* tdbb, MemoryPool& pool,
Jrd::CompilerScratch* csb, const UCHAR blrOp);
Jrd::BoolExprNode* PAR_parse_boolean(Jrd::thread_db* tdbb, Jrd::CompilerScratch* csb);
Jrd::ValueExprNode* PAR_parse_value(Jrd::thread_db* tdbb, Jrd::CompilerScratch* csb);
Jrd::StmtNode* PAR_parse_stmt(Jrd::thread_db* tdbb, Jrd::CompilerScratch* csb);
Jrd::DmlNode* PAR_parse_node(Jrd::thread_db* tdbb, Jrd::CompilerScratch* csb);
void PAR_register(UCHAR blr, NodeParseFunc parseFunc);
void PAR_register(UCHAR blr, Jrd::NodeParseFunc parseFunc);
void PAR_syntax_error(Jrd::CompilerScratch* csb, const TEXT* string);
void PAR_warning(const Firebird::Arg::StatusVector& v);

View File

@ -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,210 @@ 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",
"inout_arg_names",
"inout_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:
case blr_invsel_procedure_inout_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:
case blr_invsel_procedure_inout_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;