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

Improvement #7590 - Improve DECLARE VARIABLE to accept complete value expressions. (#7608)

This commit is contained in:
Adriano dos Santos Fernandes 2023-09-18 08:02:05 -03:00 committed by GitHub
parent 986e96fac8
commit a71ec888e4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 219 additions and 85 deletions

View File

@ -0,0 +1,68 @@
# DECLARE VARIABLE initializer enhancements (FB 6.0)
Up to Firebird 5.0, variables could be declared and initialized in the same statement, however only simple
expressions (the same as allowed in a DOMAIN's DEFAULT clause) were allowed as initializers.
This limitation has been removed in Firebird 6.0, allowing the use of any value expression as initializer.
## Syntax
```
DECLARE [VARIABLE] <varname> <type> [{ = | DEFAULT } <value>];
```
## Notes
Previously declared variables can be used in the initializer expression of following variables.
A variable initializer may call subroutines, and these subroutines may read and write previously
declared variables.
A subroutine used in an initializer may also write to variables declared after the one being
initialized, but in this case their values will be overwritten when their initializers are
executed, even if they don't have explicit initializers.
```
-- This block will return (<null>, 2) as values of v1 and v2 assigned in sf1 will
-- be overwritten by their initializer after sf1 is called.
execute block returns (o1 integer, o2 integer)
as
declare function sf1 returns integer;
declare v0 integer = sf1();
declare v1 integer;
declare v2 integer = 2;
declare function sf1 returns integer
as
begin
v1 = 10;
v2 = 20;
return 0;
end
begin
o1 = v1;
o2 = v2;
suspend;
end
```
It's an error if a subroutine reads a variable declared after the one being initialized like in the
following example.
```
-- When sf1 is called, v1 is not yet initialized.
execute block
as
declare function sf1 returns integer;
declare v0 integer = sf1();
declare v1 integer;
declare function sf1 returns integer
as
begin
return v1;
end
begin
end
```

View File

@ -2323,7 +2323,7 @@ void CreateAlterFunctionNode::compile(thread_db* /*tdbb*/, DsqlCompilerScratch*
}
dsql_var* const variable = dsqlScratch->outputVariables[0];
dsqlScratch->putLocalVariable(variable, 0, NULL);
dsqlScratch->putLocalVariable(variable, nullptr, {});
// ASF: This is here to not change the old logic (proc_flag)
// of previous calls to PASS1_node and PASS1_statement.
@ -3193,7 +3193,7 @@ void CreateAlterProcedureNode::compile(thread_db* /*tdbb*/, DsqlCompilerScratch*
i != dsqlScratch->outputVariables.end();
++i)
{
dsqlScratch->putLocalVariable(*i, 0, NULL);
dsqlScratch->putLocalVariable(*i, nullptr, {});
}
// ASF: This is here to not change the old logic (proc_flag)

View File

@ -289,26 +289,24 @@ void DsqlCompilerScratch::putLocalVariables(CompoundStmtNode* parameters, USHORT
if (!parameters)
return;
NestConst<StmtNode>* ptr = parameters->statements.begin();
Array<dsql_var*> declaredVariables;
for (const NestConst<StmtNode>* const end = parameters->statements.end(); ptr != end; ++ptr)
const auto end = parameters->statements.end();
for (auto ptr = parameters->statements.begin(); ptr != end; ++ptr)
{
StmtNode* parameter = *ptr;
auto parameter = *ptr;
putDebugSrcInfo(parameter->line, parameter->column);
DeclareVariableNode* varNode;
if ((varNode = nodeAs<DeclareVariableNode>(parameter)))
if (const auto varNode = nodeAs<DeclareVariableNode>(parameter))
{
dsql_fld* field = varNode->dsqlDef->type;
const NestConst<StmtNode>* rest = ptr;
while (++rest != end)
{
const DeclareVariableNode* varNode2;
if ((varNode2 = nodeAs<DeclareVariableNode>(*rest)))
if (const auto varNode2 = nodeAs<DeclareVariableNode>(*rest))
{
const dsql_fld* rest_field = varNode2->dsqlDef->type;
@ -320,10 +318,10 @@ void DsqlCompilerScratch::putLocalVariables(CompoundStmtNode* parameters, USHORT
}
}
dsql_var* variable = makeVariable(field, field->fld_name.c_str(), dsql_var::TYPE_LOCAL,
0, 0, locals);
const auto variable = makeVariable(field, field->fld_name.c_str(), dsql_var::TYPE_LOCAL, 0, 0, locals);
declaredVariables.add(variable);
putLocalVariable(variable, varNode, varNode->dsqlDef->type->collate);
putLocalVariableDecl(variable, varNode, varNode->dsqlDef->type->collate);
// Some field attributes are calculated inside putLocalVariable(), so we reinitialize
// the descriptor.
@ -342,6 +340,14 @@ void DsqlCompilerScratch::putLocalVariables(CompoundStmtNode* parameters, USHORT
fb_assert(false);
}
auto declVarIt = declaredVariables.begin();
for (const auto parameter : parameters->statements)
{
if (const auto varNode = nodeAs<DeclareVariableNode>(parameter))
putLocalVariableInit(*declVarIt++, varNode);
}
if (!(flags & DsqlCompilerScratch::FLAG_SUB_ROUTINE))
{
// Check not implemented sub-functions.
@ -369,22 +375,38 @@ void DsqlCompilerScratch::putLocalVariables(CompoundStmtNode* parameters, USHORT
}
// Write out local variable field data type.
void DsqlCompilerScratch::putLocalVariable(dsql_var* variable, const DeclareVariableNode* hostParam,
void DsqlCompilerScratch::putLocalVariableDecl(dsql_var* variable, DeclareVariableNode* hostParam,
const MetaName& collationName)
{
dsql_fld* field = variable->field;
const auto field = variable->field;
appendUChar(blr_dcl_variable);
appendUShort(variable->number);
DDL_resolve_intl_type(this, field, collationName);
//const USHORT dtype = field->dtype;
putDtype(field, true);
//field->dtype = dtype;
if (variable->field->fld_name.hasData()) // Not a function return value
putDebugVariable(variable->number, variable->field->fld_name);
++hiddenVarsNumber;
if (variable->type != dsql_var::TYPE_INPUT && hostParam && hostParam->dsqlDef->defaultClause)
{
hostParam->dsqlDef->defaultClause->value =
Node::doDsqlPass(this, hostParam->dsqlDef->defaultClause->value, true);
}
variable->initialized = true;
}
// Write out local variable initialization.
void DsqlCompilerScratch::putLocalVariableInit(dsql_var* variable, const DeclareVariableNode* hostParam)
{
const dsql_fld* field = variable->field;
// Check for a default value, borrowed from define_domain
NestConst<ValueSourceClause> node = hostParam ? hostParam->dsqlDef->defaultClause : NULL;
NestConst<ValueSourceClause> node = hostParam ? hostParam->dsqlDef->defaultClause : nullptr;
if (variable->type == dsql_var::TYPE_INPUT)
{
@ -405,7 +427,7 @@ void DsqlCompilerScratch::putLocalVariable(dsql_var* variable, const DeclareVari
appendUChar(blr_assignment);
if (node)
GEN_expr(this, Node::doDsqlPass(this, node->value, false));
GEN_expr(this, node->value);
else
appendUChar(blr_null); // Initialize variable to NULL
@ -417,11 +439,6 @@ void DsqlCompilerScratch::putLocalVariable(dsql_var* variable, const DeclareVari
appendUChar(blr_init_variable);
appendUShort(variable->number);
}
if (variable->field->fld_name.hasData()) // Not a function return value
putDebugVariable(variable->number, variable->field->fld_name);
++hiddenVarsNumber;
}
// Put maps in subroutines for outer variables/parameters usage.

View File

@ -181,8 +181,15 @@ public:
void putDtype(const TypeClause* field, bool useSubType);
void putType(const TypeClause* type, bool useSubType);
void putLocalVariables(CompoundStmtNode* parameters, USHORT locals);
void putLocalVariable(dsql_var* variable, const DeclareVariableNode* hostParam,
const MetaName& collationName);
void putLocalVariableDecl(dsql_var* variable, DeclareVariableNode* hostParam, const MetaName& collationName);
void putLocalVariableInit(dsql_var* variable, const DeclareVariableNode* hostParam);
void putLocalVariable(dsql_var* variable, DeclareVariableNode* hostParam, const MetaName& collationName)
{
putLocalVariableDecl(variable, hostParam, collationName);
putLocalVariableInit(variable, hostParam);
}
void putOuterMaps();
dsql_var* makeVariable(dsql_fld*, const char*, const dsql_var::Type type, USHORT,
USHORT, USHORT);

View File

@ -13703,8 +13703,11 @@ ValueExprNode* VariableNode::dsqlPass(DsqlCompilerScratch* dsqlScratch)
}
}
if (!node->dsqlVar)
if (!node->dsqlVar ||
(node->dsqlVar->type == dsql_var::TYPE_LOCAL && !node->dsqlVar->initialized && !dsqlScratch->mainScratch))
{
PASS1_field_unknown(NULL, dsqlName.c_str(), this);
}
return node;
}
@ -13844,6 +13847,15 @@ dsc* VariableNode::execute(thread_db* tdbb, Request* request) const
const auto varRequest = getVarRequest(request);
const auto varImpure = varRequest->getImpure<impure_value>(varDecl->impureOffset);
if (!(varImpure->vlu_flags & VLU_initialized))
{
const Item item(Item::TYPE_VARIABLE, varId);
//// FIXME: Variable with simple type has no varInfo.
const auto s = item.getDescription(request, varInfo);
ERR_post(Arg::Gds(isc_uninitialized_var) << s);
}
request->req_flags &= ~req_null;
dsc* desc;

View File

@ -4254,9 +4254,11 @@ const StmtNode* InitVariableNode::execute(thread_db* tdbb, Request* request, Exe
{
if (request->req_operation == Request::req_evaluate)
{
const auto varImpure = request->getImpure<impure_value>(varDecl->impureOffset);
if (varInfo)
{
dsc* toDesc = &request->getImpure<impure_value>(varDecl->impureOffset)->vlu_desc;
dsc* toDesc = &varImpure->vlu_desc;
toDesc->dsc_flags |= DSC_null;
MapFieldInfo::ValueType fieldInfo;
@ -4275,6 +4277,8 @@ const StmtNode* InitVariableNode::execute(thread_db* tdbb, Request* request, Exe
}
}
varImpure->vlu_flags |= VLU_initialized;
request->req_operation = Request::req_return;
}
@ -4497,10 +4501,10 @@ void ExecBlockNode::genBlr(DsqlCompilerScratch* dsqlScratch)
}
}
Array<dsql_var*>& variables = subRoutine ? dsqlScratch->outputVariables : dsqlScratch->variables;
const Array<dsql_var*>& variables = subRoutine ? dsqlScratch->outputVariables : dsqlScratch->variables;
for (Array<dsql_var*>::const_iterator i = variables.begin(); i != variables.end(); ++i)
dsqlScratch->putLocalVariable(*i, 0, NULL);
for (const auto variable : variables)
dsqlScratch->putLocalVariable(variable, nullptr, {});
dsqlScratch->setPsql(true);

View File

@ -386,21 +386,17 @@ public:
public:
explicit dsql_var(MemoryPool& p)
: PermanentStorage(p),
field(NULL),
type(TYPE_INPUT),
msgNumber(0),
msgItem(0),
number(0)
: PermanentStorage(p)
{
desc.clear();
}
dsql_fld* field; // Field on which variable is based
Type type; // Input, output, local or hidden variable
USHORT msgNumber; // Message number containing variable
USHORT msgItem; // Item number in message
USHORT number; // Local variable number
dsql_fld* field = nullptr; // Field on which variable is based
Type type = TYPE_INPUT; // Input, output, local or hidden variable
USHORT msgNumber = 0; // Message number containing variable
USHORT msgItem = 0; // Item number in message
USHORT number = 0; // Local variable number
bool initialized = false; // Is variable initialized?
dsc desc;
};

View File

@ -3158,7 +3158,7 @@ local_declaration_item
%type <stmtNode> var_declaration_item
var_declaration_item
: column_domain_or_non_array_type collate_clause default_par_opt
: column_domain_or_non_array_type collate_clause var_declaration_initializer
{
DeclareVariableNode* node = newNode<DeclareVariableNode>();
node->dsqlDef = newNode<ParameterClause>($1, optName($2), $3);
@ -3166,6 +3166,26 @@ var_declaration_item
}
;
%type <valueSourceClause> var_declaration_initializer
var_declaration_initializer
: // nothing
{ $$ = nullptr; }
| DEFAULT value
{
const auto clause = newNode<ValueSourceClause>();
clause->value = $2;
clause->source = makeParseStr(YYPOSNARG(1), YYPOSNARG(2));
$$ = clause;
}
| '=' value
{
const auto clause = newNode<ValueSourceClause>();
clause->value = $2;
clause->source = makeParseStr(YYPOSNARG(1), YYPOSNARG(2));
$$ = clause;
}
;
var_decl_opt
: // nothing
| VARIABLE

View File

@ -968,3 +968,4 @@ FB_IMPL_MSG(JRD, 965, ods_upgrade_err, -901, "HY", "000", "ODS upgrade failed wh
FB_IMPL_MSG(JRD, 966, bad_par_workers, -924, "HY", "000", "Wrong parallel workers value @1, valid range are from 1 to @2")
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")

View File

@ -677,41 +677,8 @@ void EVL_validate(thread_db* tdbb, const Item& item, const ItemInfo* itemInfo, d
}
else
{
if (itemInfo->name.isEmpty())
{
int index = item.index + 1;
status = isc_not_valid_for;
if (item.type == Item::TYPE_VARIABLE)
{
const jrd_prc* procedure = request->getStatement()->procedure;
if (procedure)
{
if (index <= int(procedure->getOutputFields().getCount()))
s.printf("output parameter number %d", index);
else
{
s.printf("variable number %d",
index - int(procedure->getOutputFields().getCount()));
}
}
else
s.printf("variable number %d", index);
}
else if (item.type == Item::TYPE_PARAMETER && item.subType == 0)
s.printf("input parameter number %d", (index - 1) / 2 + 1);
else if (item.type == Item::TYPE_PARAMETER && item.subType == 1)
s.printf("output parameter number %d", index);
if (s.isEmpty())
arg = UNKNOWN_STRING_MARK;
else
arg = s.c_str();
}
else
arg = itemInfo->name.c_str();
s = item.getDescription(request, itemInfo);
arg = s.c_str();
}
ERR_post(Arg::Gds(status) << Arg::Str(arg) << Arg::Str(value));

View File

@ -115,6 +115,44 @@
using namespace Jrd;
using namespace Firebird;
// Item class implementation
string Item::getDescription(Request* request, const ItemInfo* itemInfo) const
{
if (itemInfo && itemInfo->name.hasData())
return itemInfo->name.c_str();
int oneBasedIndex = index + 1;
string s;
if (type == Item::TYPE_VARIABLE)
{
const auto* const procedure = request->getStatement()->procedure;
if (procedure)
{
if (oneBasedIndex <= int(procedure->getOutputFields().getCount()))
s.printf("[output parameter number %d]", oneBasedIndex);
else
{
s.printf("[number %d]",
oneBasedIndex - int(procedure->getOutputFields().getCount()));
}
}
else
s.printf("[number %d]", oneBasedIndex);
}
else if (type == Item::TYPE_PARAMETER && subType == 0)
s.printf("[input parameter number %d]", (oneBasedIndex - 1) / 2 + 1);
else if (type == Item::TYPE_PARAMETER && subType == 1)
s.printf("[output parameter number %d]", oneBasedIndex);
if (s.isEmpty())
s = UNKNOWN_STRING_MARK;
return s;
}
// AffectedRows class implementation
AffectedRows::AffectedRows()
@ -327,8 +365,8 @@ void EXE_assignment(thread_db* tdbb, const ValueExprNode* to, dsc* from_desc, bo
toVar->varDecl->impureOffset)->vlu_flags;
}
if (impure_flags != NULL)
*impure_flags |= VLU_checked;
if (impure_flags)
*impure_flags |= VLU_initialized | VLU_checked;
// If the value is non-missing, move/convert it. Otherwise fill the
// field with appropriate nulls.

View File

@ -81,6 +81,7 @@ class Cursor;
class DeclareSubFuncNode;
class DeclareSubProcNode;
class DeclareVariableNode;
class ItemInfo;
class MessageNode;
class PlanNode;
class RecordSource;
@ -357,6 +358,8 @@ struct Item
return type > x.type;
}
Firebird::string getDescription(Request* request, const ItemInfo* itemInfo) const;
};
struct FieldInfo

View File

@ -229,9 +229,10 @@ struct impure_value_ex : public impure_value
blb* vlu_blob;
};
const int VLU_computed = 1; // An invariant sub-query has been computed
const int VLU_null = 2; // An invariant sub-query computed to null
const int VLU_checked = 4; // Constraint already checked in first read or assignment to argument/variable
const int VLU_computed = 1; // An invariant sub-query has been computed
const int VLU_null = 2; // An invariant sub-query computed to null
const int VLU_checked = 4; // Constraint already checked in first read or assignment to argument/variable
const int VLU_initialized = 8; // Variable initialized
class Format : public pool_alloc<type_fmt>