diff --git a/doc/sql.extensions/README.declare_var_initializer.md b/doc/sql.extensions/README.declare_var_initializer.md new file mode 100644 index 0000000000..4ae8db45d8 --- /dev/null +++ b/doc/sql.extensions/README.declare_var_initializer.md @@ -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] [{ = | DEFAULT } ]; +``` + +## 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 (, 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 +``` diff --git a/src/dsql/DdlNodes.epp b/src/dsql/DdlNodes.epp index eded6073de..79a4cda447 100644 --- a/src/dsql/DdlNodes.epp +++ b/src/dsql/DdlNodes.epp @@ -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) diff --git a/src/dsql/DsqlCompilerScratch.cpp b/src/dsql/DsqlCompilerScratch.cpp index f3c21dbc06..7a23d9ea22 100644 --- a/src/dsql/DsqlCompilerScratch.cpp +++ b/src/dsql/DsqlCompilerScratch.cpp @@ -289,26 +289,24 @@ void DsqlCompilerScratch::putLocalVariables(CompoundStmtNode* parameters, USHORT if (!parameters) return; - NestConst* ptr = parameters->statements.begin(); + Array declaredVariables; - for (const NestConst* 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(parameter))) + if (const auto varNode = nodeAs(parameter)) { dsql_fld* field = varNode->dsqlDef->type; const NestConst* rest = ptr; while (++rest != end) { - const DeclareVariableNode* varNode2; - - if ((varNode2 = nodeAs(*rest))) + if (const auto varNode2 = nodeAs(*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(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 node = hostParam ? hostParam->dsqlDef->defaultClause : NULL; + NestConst 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. diff --git a/src/dsql/DsqlCompilerScratch.h b/src/dsql/DsqlCompilerScratch.h index 12e4fd76b3..9743e3a284 100644 --- a/src/dsql/DsqlCompilerScratch.h +++ b/src/dsql/DsqlCompilerScratch.h @@ -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); diff --git a/src/dsql/ExprNodes.cpp b/src/dsql/ExprNodes.cpp index 29834c220b..d485bf3019 100644 --- a/src/dsql/ExprNodes.cpp +++ b/src/dsql/ExprNodes.cpp @@ -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(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; diff --git a/src/dsql/StmtNodes.cpp b/src/dsql/StmtNodes.cpp index f3dd8c31a1..8cf75a3052 100644 --- a/src/dsql/StmtNodes.cpp +++ b/src/dsql/StmtNodes.cpp @@ -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(varDecl->impureOffset); + if (varInfo) { - dsc* toDesc = &request->getImpure(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& variables = subRoutine ? dsqlScratch->outputVariables : dsqlScratch->variables; + const Array& variables = subRoutine ? dsqlScratch->outputVariables : dsqlScratch->variables; - for (Array::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); diff --git a/src/dsql/dsql.h b/src/dsql/dsql.h index 067ae93c61..efce3ac4ba 100644 --- a/src/dsql/dsql.h +++ b/src/dsql/dsql.h @@ -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; }; diff --git a/src/dsql/parse.y b/src/dsql/parse.y index a10ed10b9c..ee8aa0976d 100644 --- a/src/dsql/parse.y +++ b/src/dsql/parse.y @@ -3158,7 +3158,7 @@ local_declaration_item %type 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(); node->dsqlDef = newNode($1, optName($2), $3); @@ -3166,6 +3166,26 @@ var_declaration_item } ; +%type var_declaration_initializer +var_declaration_initializer + : // nothing + { $$ = nullptr; } + | DEFAULT value + { + const auto clause = newNode(); + clause->value = $2; + clause->source = makeParseStr(YYPOSNARG(1), YYPOSNARG(2)); + $$ = clause; + } + | '=' value + { + const auto clause = newNode(); + clause->value = $2; + clause->source = makeParseStr(YYPOSNARG(1), YYPOSNARG(2)); + $$ = clause; + } + ; + var_decl_opt : // nothing | VARIABLE diff --git a/src/include/firebird/impl/msg/jrd.h b/src/include/firebird/impl/msg/jrd.h index 535d4e4f43..0da6706bbe 100644 --- a/src/include/firebird/impl/msg/jrd.h +++ b/src/include/firebird/impl/msg/jrd.h @@ -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") diff --git a/src/jrd/evl.cpp b/src/jrd/evl.cpp index 5116c08806..9eaa3f643f 100644 --- a/src/jrd/evl.cpp +++ b/src/jrd/evl.cpp @@ -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)); diff --git a/src/jrd/exe.cpp b/src/jrd/exe.cpp index 463e4f8f65..76ade31923 100644 --- a/src/jrd/exe.cpp +++ b/src/jrd/exe.cpp @@ -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. diff --git a/src/jrd/exe.h b/src/jrd/exe.h index 4782fbfff7..25cf4fffb2 100644 --- a/src/jrd/exe.h +++ b/src/jrd/exe.h @@ -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 diff --git a/src/jrd/val.h b/src/jrd/val.h index fe727a4d6a..b564d57b16 100644 --- a/src/jrd/val.h +++ b/src/jrd/val.h @@ -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