mirror of
https://github.com/FirebirdSQL/firebird.git
synced 2025-01-22 17:23:03 +01:00
This commit is contained in:
parent
986e96fac8
commit
a71ec888e4
68
doc/sql.extensions/README.declare_var_initializer.md
Normal file
68
doc/sql.extensions/README.declare_var_initializer.md
Normal 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
|
||||
```
|
@ -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)
|
||||
|
@ -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.
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
|
@ -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
|
||||
|
@ -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")
|
||||
|
@ -677,42 +677,9 @@ 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
|
||||
s = item.getDescription(request, itemInfo);
|
||||
arg = s.c_str();
|
||||
}
|
||||
else
|
||||
arg = itemInfo->name.c_str();
|
||||
}
|
||||
|
||||
ERR_post(Arg::Gds(status) << Arg::Str(arg) << Arg::Str(value));
|
||||
}
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
@ -232,6 +232,7 @@ struct impure_value_ex : public impure_value
|
||||
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>
|
||||
|
Loading…
Reference in New Issue
Block a user