diff --git a/src/burp/BurpTasks.cpp b/src/burp/BurpTasks.cpp index 5489134c55f..32fbc2e78ab 100644 --- a/src/burp/BurpTasks.cpp +++ b/src/burp/BurpTasks.cpp @@ -531,6 +531,14 @@ void BackupRelationTask::initItem(BurpGlobals* tdgbl, Item& item) if (status->getState() & IStatus::STATE_ERRORS) BURP_abort(&status); + + if (m_masterGbl->setSearchPath.hasData()) + { + item.m_att->execute(&status, item.m_tra, 0, m_masterGbl->setSearchPath.c_str(), + SQL_DIALECT_CURRENT, nullptr, nullptr, nullptr, nullptr); + + fb_assert(status->isSuccess()); + } } tdgbl->db_handle = item.m_att; diff --git a/src/burp/OdsDetection.h b/src/burp/OdsDetection.h index 5abaf298520..61f99be03f4 100644 --- a/src/burp/OdsDetection.h +++ b/src/burp/OdsDetection.h @@ -69,6 +69,7 @@ const int DB_VERSION_DDL11_2 = 112; // ods11.2 db, FB2.5 const int DB_VERSION_DDL12 = 120; // ods12.0 db, FB3.0 const int DB_VERSION_DDL13 = 130; // ods13.0 db, FB4.0 const int DB_VERSION_DDL13_1 = 131; // ods13.1 db, FB5.0 +const int DB_VERSION_DDL14 = 140; // ods14.0 db, FB6.0 const int DB_VERSION_OLDEST_SUPPORTED = DB_VERSION_DDL8; // IB4.0 is ods8 diff --git a/src/burp/backup.epp b/src/burp/backup.epp index ebfe71f168d..cdc5f282851 100644 --- a/src/burp/backup.epp +++ b/src/burp/backup.epp @@ -45,6 +45,7 @@ #include "../burp/burp.h" #include "../jrd/ods.h" #include "../jrd/align.h" +#include "../jrd/intl.h" #include "../common/gdsassert.h" #include "../jrd/constants.h" #include "../common/stuff.h" @@ -286,6 +287,64 @@ int BACKUP_backup(const TEXT* dbb_file, const TEXT* file_name) tdgbl->gbl_sw_par_workers = 1; } + // decide what type of database we've got + + detectRuntimeODS(); + if (tdgbl->runtimeODS < DB_VERSION_OLDEST_SUPPORTED) + { + BURP_error(348, true, SafeArg() << tdgbl->runtimeODS); + // msg 348 database format @1 is too old to backup + } + else if (tdgbl->runtimeODS >= DB_VERSION_DDL13_1) + { + static constexpr UCHAR ODS_INFO[] = { + isc_info_ods_version, + isc_info_end + }; + UCHAR infoBuffer[16]; + USHORT odsMajorVersion; + + FbLocalStatus checkStatus; + + tdgbl->db_handle->getInfo(&checkStatus, sizeof(ODS_INFO), ODS_INFO, sizeof(infoBuffer), infoBuffer); + + if (checkStatus.isSuccess() && infoBuffer[0] == isc_info_ods_version) + { + USHORT len = isc_portable_integer(&infoBuffer[1], 2); + odsMajorVersion = isc_portable_integer(&infoBuffer[3], len); + + if (odsMajorVersion > ODS_VERSION13) + tdgbl->runtimeODS = DB_VERSION_DDL14; + } + else + fb_assert(false); + } + + // get schema list for v6 databases + if (tdgbl->runtimeODS >= DB_VERSION_DDL14) + { + const char* getSchemasSql = + R""(SELECT LIST('"' || REPLACE(TRIM(RDB$SCHEMA_NAME), '"', '""') || '"') FROM RDB$SCHEMAS)""; + BurpSql getSchemasQuery(tdgbl, getSchemasSql); + + FB_MESSAGE(GetSchemas, ThrowWrapper, + (FB_INTL_VARCHAR(4096 * METADATA_BYTES_PER_CHAR, CS_METADATA), schemaList) + ) getSchemas(&tdgbl->throwStatus, MasterInterfacePtr()); + + getSchemasQuery.singleSelect(tdgbl->tr_handle, &getSchemas); + + fb_assert(!getSchemas->schemaListNull); + + if (!getSchemas->schemaListNull) + { + const string schemaList(getSchemas->schemaList.str, getSchemas->schemaList.length); + tdgbl->setSearchPath = "SET SEARCH_PATH TO " + schemaList; + + BurpSql searchPathQuery(tdgbl, tdgbl->setSearchPath.c_str()); + searchPathQuery.execute(tdgbl->tr_handle); + } + } + // detect if MAKE_DBKEY is supported and decide kind of read relation query if (tdgbl->gbl_sw_par_workers > 1) { @@ -306,15 +365,6 @@ int BACKUP_backup(const TEXT* dbb_file, const TEXT* file_name) // msg 410 use up to @1 parallel workers } - // decide what type of database we've got - - detectRuntimeODS(); - if (tdgbl->runtimeODS < DB_VERSION_OLDEST_SUPPORTED) - { - BURP_error(348, true, SafeArg() << tdgbl->runtimeODS); - // msg 348 database format @1 is too old to backup - } - // Write burp record first with other valuable information // In case of split operation, write a 'split' header first to all the files diff --git a/src/burp/burp.h b/src/burp/burp.h index e980f4e6a5e..581f1598ebf 100644 --- a/src/burp/burp.h +++ b/src/burp/burp.h @@ -977,7 +977,8 @@ class BurpGlobals : public Firebird::ThreadData, public GblPool flag_on_line(true), firstMap(true), firstDbc(true), - stdIoMode(false) + stdIoMode(false), + setSearchPath(getPool()) { // this is VERY dirty hack to keep current (pre-FB2) behaviour memset (&gbl_database_file_name, 0, @@ -1220,6 +1221,8 @@ class BurpGlobals : public Firebird::ThreadData, public GblPool Firebird::AutoPtr skipDataMatcher; Firebird::AutoPtr includeDataMatcher; + Firebird::string setSearchPath; + public: Firebird::string toSystem(const Firebird::PathName& from); @@ -1293,7 +1296,7 @@ class BurpSql : public Firebird::AutoStorage : Firebird::AutoStorage(), tdgbl(g), stmt(nullptr) { - stmt = tdgbl->db_handle->prepare(&tdgbl->throwStatus, tdgbl->tr_handle, 0, sql, 3, 0); + stmt = tdgbl->db_handle->prepare(&tdgbl->throwStatus, tdgbl->tr_handle, 0, sql, SQL_DIALECT_CURRENT, 0); } template diff --git a/src/common/classes/BlrReader.h b/src/common/classes/BlrReader.h index 8a8f9b9eaca..22e8cb2b1cd 100644 --- a/src/common/classes/BlrReader.h +++ b/src/common/classes/BlrReader.h @@ -25,6 +25,7 @@ #include "iberror.h" #include "../common/classes/fb_string.h" +#include "../common/classes/MetaString.h" #include "../common/StatusArg.h" #include "../jrd/constants.h" @@ -121,6 +122,40 @@ class BlrReader return (b4 << 24) | (b3 << 16) | (b2 << 8) | b1; } + UCHAR parseHeader() + { + const auto version = getByte(); + + switch (version) + { + case blr_version4: + case blr_version5: + //case blr_version6: + break; + + default: + status_exception::raise( + Arg::Gds(isc_metadata_corrupt) << + Arg::Gds(isc_wroblrver2) << Arg::Num(blr_version4) << Arg::Num(blr_version5/*6*/) << + Arg::Num(version)); + } + + auto code = getByte(); + + if (code == blr_flags) + { + while ((code = getByte()) != blr_end) + { + const auto len = getWord(); + seekForward(len); + } + } + else + seekBackward(1); + + return version; + } + UCHAR checkByte(UCHAR expected) { UCHAR byte = getByte(); @@ -177,6 +212,12 @@ class BlrReader name.assign(str.c_str()); } + void skipMetaName() + { + MetaString name; + getMetaName(name); + } + private: const UCHAR* start; const UCHAR* end; diff --git a/src/common/classes/GenericMap.h b/src/common/classes/GenericMap.h index 25c6d8923ab..a590b8d9b42 100644 --- a/src/common/classes/GenericMap.h +++ b/src/common/classes/GenericMap.h @@ -299,6 +299,10 @@ class GenericMap : public AutoStorage size_t count() const { return mCount; } + bool hasData() const { return mCount != 0; } + + bool isEmpty() const { return mCount == 0; } + Accessor accessor() { return Accessor(this); diff --git a/src/dsql/ExprNodes.cpp b/src/dsql/ExprNodes.cpp index a66106b70e8..767155f7d7a 100644 --- a/src/dsql/ExprNodes.cpp +++ b/src/dsql/ExprNodes.cpp @@ -4944,7 +4944,7 @@ dsc* DecodeNode::execute(thread_db* tdbb, Request* request) const //-------------------- -static RegisterNode regDefaultNode({blr_default}); +static RegisterNode regDefaultNode({blr_default, blr_default2}); DefaultNode::DefaultNode(MemoryPool& pool, const MetaName& aRelationName, const MetaName& aFieldName) @@ -4955,8 +4955,11 @@ DefaultNode::DefaultNode(MemoryPool& pool, const MetaName& aRelationName, { } -DmlNode* DefaultNode::parse(thread_db* tdbb, MemoryPool& pool, CompilerScratch* csb, const UCHAR /*blrOp*/) +DmlNode* DefaultNode::parse(thread_db* tdbb, MemoryPool& pool, CompilerScratch* csb, const UCHAR blrOp) { + if (blrOp == blr_default2) + csb->csb_blr_reader.skipMetaName(); + MetaName relationName, fieldName; csb->csb_blr_reader.getMetaName(relationName); csb->csb_blr_reader.getMetaName(fieldName); @@ -7002,7 +7005,7 @@ dsc* FieldNode::execute(thread_db* tdbb, Request* request) const //-------------------- -static RegisterNode regGenIdNode({blr_gen_id, blr_gen_id2}); +static RegisterNode regGenIdNode({blr_gen_id, blr_gen_id2, blr_gen_id3}); GenIdNode::GenIdNode(MemoryPool& pool, bool aDialect1, const MetaName& name, @@ -7021,13 +7024,16 @@ GenIdNode::GenIdNode(MemoryPool& pool, bool aDialect1, DmlNode* GenIdNode::parse(thread_db* tdbb, MemoryPool& pool, CompilerScratch* csb, const UCHAR blrOp) { + if (blrOp == blr_gen_id3) + csb->csb_blr_reader.skipMetaName(); + MetaName name; csb->csb_blr_reader.getMetaName(name); - ValueExprNode* explicitStep = (blrOp == blr_gen_id2) ? NULL : PAR_parse_value(tdbb, csb); - GenIdNode* const node = - FB_NEW_POOL(pool) GenIdNode(pool, (csb->blrVersion == 4), name, explicitStep, - (blrOp == blr_gen_id2), false); + const bool useExplicitStep = blrOp == blr_gen_id || (blrOp == blr_gen_id3 && csb->csb_blr_reader.getByte() != 0); + const auto explicitStep = useExplicitStep ? PAR_parse_value(tdbb, csb) : nullptr; + const auto node = FB_NEW_POOL(pool) GenIdNode(pool, (csb->blrVersion == 4), name, explicitStep, + !useExplicitStep, false); // This check seems faster than ==, but assumes the special generator is named "" if (name.length() == 0) //(name == MASTER_GENERATOR) @@ -12301,7 +12307,7 @@ DmlNode* SysFuncCallNode::parse(thread_db* tdbb, MemoryPool& pool, CompilerScrat // Special handling for system function MAKE_DBKEY: // convert constant relation name into ID at the parsing time - auto literal = node->args->items.getCount() ? + auto literal = node->args->items.getCount() ? nodeAs(node->args->items[0]) : nullptr; if (literal && literal->litDesc.isText()) @@ -12842,7 +12848,7 @@ dsc* TrimNode::execute(thread_db* tdbb, Request* request) const //-------------------- -static RegisterNode regUdfCallNode({blr_function, blr_function2, blr_subfunc}); +static RegisterNode regUdfCallNode({blr_function, blr_function2, blr_subfunc, blr_invoke_function}); UdfCallNode::UdfCallNode(MemoryPool& pool, const QualifiedName& aName, ValueListNode* aArgs) : TypedNode(pool), @@ -12857,86 +12863,228 @@ UdfCallNode::UdfCallNode(MemoryPool& pool, const QualifiedName& aName, ValueList 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(); + + 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); + + if (blrOp == blr_invoke_function) + { + UCHAR subCode; + + while ((subCode = blrReader.getByte()) != blr_end) + { + switch (subCode) + { + case blr_invoke_function_id: + { + UCHAR functionIdCode; + + while ((functionIdCode = blrReader.getByte()) != blr_end) + { + switch (functionIdCode) + { + case blr_invoke_function_id_schema: + blrReader.skipMetaName(); + break; + + case blr_invoke_function_id_package: + blrReader.getMetaName(name.package); + break; + + case blr_invoke_function_id_name: + blrReader.getMetaName(name.identifier); + break; + + case blr_invoke_function_id_sub: + PAR_error(csb, Arg::Gds(isc_random) << + "blr_invoke_function_id_sub is not supported in this version"); + break; + + default: + PAR_error(csb, Arg::Gds(isc_random) << "Invalid blr_invoke_function_id"); + break; + } + } + + if (!node->function) + node->function = Function::lookup(tdbb, name, false); + + if (!node->function) + { + blrReader.setPos(startPos); + PAR_error(csb, Arg::Gds(isc_funnotdef) << name.toString()); + } - csb->csb_blr_reader.getMetaName(name.identifier); + break; + } - const USHORT count = name.package.length() + name.identifier.length(); + case blr_invoke_function_arg_names: + PAR_error(csb, Arg::Gds(isc_random) << + "blr_invoke_function_arg_names is not supported in this version"); + break; - UdfCallNode* node = FB_NEW_POOL(pool) UdfCallNode(pool, name); + case blr_invoke_function_args: + predateCheck(node->function, "blr_invoke_function_id", "blr_invoke_function_args"); - if (blrOp == blr_function && - (name.identifier == "RDB$GET_CONTEXT" || name.identifier == "RDB$SET_CONTEXT")) + 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.setPos(savePos); - return SysFuncCallNode::parse(tdbb, pool, csb, blr_sys_function); + 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); + + if (!node->function) + { + blrReader.setPos(startPos); + PAR_error(csb, Arg::Gds(isc_funnotdef) << name.toString()); + } + + argCount = blrReader.getByte(); + node->args = PAR_args(tdbb, csb, argCount, MAX(argCount, node->function->fun_inputs)); } - if (blrOp == blr_subfunc) - { - DeclareSubFuncNode* declareNode; + fb_assert(node->function); - for (auto curCsb = csb; curCsb && !node->function; curCsb = curCsb->mainCsb) + if (node->function->isImplemented() && !node->function->isDefined()) + { + if (tdbb->getAttachment()->isGbak() || (tdbb->tdbb_flags & TDBB_replicator)) { - if (curCsb->subFunctions.get(name.identifier, declareNode)) - node->function = declareNode->routine; + 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)); } } - Function* function = node->function; + node->name = name; + node->isSubRoutine = node->function->isSubRoutine(); - if (!function) - function = node->function = Function::lookup(tdbb, name, false); + Arg::StatusVector mismatchStatus; - if (function) + if (argCount > node->function->fun_inputs) + mismatchStatus << Arg::Gds(isc_wronumarg); + + if (!node->args) + node->args = FB_NEW_POOL(pool) ValueListNode(pool); + + auto argIt = node->args->items.begin(); + LeftPooledMap> argsByName; + + if (argCount) { - if (function->isImplemented() && !function->isDefined()) + if (argCount > node->function->fun_inputs) + mismatchStatus << Arg::Gds(isc_wronumarg); + + for (auto pos = 0u; pos < argCount; ++pos) { - if (tdbb->getAttachment()->isGbak() || (tdbb->tdbb_flags & TDBB_replicator)) + if (pos < node->function->fun_inputs) { - 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)); + const auto& parameter = node->function->getInputFields()[pos]; + + if (parameter->prm_name.hasData() && argsByName.put(parameter->prm_name, *argIt)) + { + fb_assert(false); + status_exception::raise( + Arg::Gds(isc_random) << + "Invalid BLR arguments for this version in UdfCallNode::parse"); + } } + + ++argIt; } } - else - { - csb->csb_blr_reader.seekBackward(count); - PAR_error(csb, Arg::Gds(isc_funnotdef) << Arg::Str(name.toString())); - } - node->isSubRoutine = function->isSubRoutine(); + node->args->items.resize(node->function->getInputFields().getCount()); + argIt = node->args->items.begin(); - const UCHAR argCount = csb->csb_blr_reader.getByte(); + for (auto& parameter : node->function->getInputFields()) + { + NestConst* argValue; + bool argExists = false; + + if (parameter->prm_name.hasData()) + { + argExists = argsByName.exist(parameter->prm_name); + argValue = argsByName.get(parameter->prm_name); - // 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()); + if (argValue) + { + *argIt = *argValue; + argsByName.remove(parameter->prm_name); + } + } + else // no parameter name in UDFs + argValue = argIt; - node->args = PAR_args(tdbb, csb, argCount, function->fun_inputs); + if (!argValue || !*argValue) + { + if (parameter->prm_default_value) + *argIt = CMP_clone_node(tdbb, csb, parameter->prm_default_value); + else + { + fb_assert(false); + status_exception::raise( + Arg::Gds(isc_random) << + "Invalid BLR arguments for this version in UdfCallNode::parse"); + } + } - for (USHORT i = argCount; i < function->fun_inputs; ++i) - { - Parameter* const parameter = function->getInputFields()[i]; - node->args->items[i] = CMP_clone_node(tdbb, csb, parameter->prm_default_value); + ++argIt; } + 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; + dependency.function = node->function; csb->addDependency(dependency); } diff --git a/src/dsql/ExprNodes.h b/src/dsql/ExprNodes.h index b8298980946..7b68da48c33 100644 --- a/src/dsql/ExprNodes.h +++ b/src/dsql/ExprNodes.h @@ -2151,8 +2151,8 @@ class UdfCallNode final : public TypedNode items.push(arg1); } + ValueListNode(MemoryPool& pool) + : TypedNode(pool), + items(pool, INITIAL_CAPACITY) + { + } + virtual void getChildren(NodeRefsHolder& holder, bool dsql) const { ListExprNode::getChildren(holder, dsql); diff --git a/src/dsql/StmtNodes.cpp b/src/dsql/StmtNodes.cpp index 6d38e5494d8..081aef6e928 100644 --- a/src/dsql/StmtNodes.cpp +++ b/src/dsql/StmtNodes.cpp @@ -2854,6 +2854,10 @@ DmlNode* ErrorHandlerNode::parse(thread_db* tdbb, MemoryPool& pool, CompilerScra PAR_error(csb, Arg::Gds(isc_codnotdef) << item.name); break; + case blr_exception2: + csb->csb_blr_reader.skipMetaName(); + [[fallthrough]]; + case blr_exception: { csb->csb_blr_reader.getString(item.name); @@ -2985,81 +2989,243 @@ const StmtNode* ErrorHandlerNode::execute(thread_db* /*tdbb*/, Request* request, static RegisterNode 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) { + 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); + } + }; + SET_TDBB(tdbb); - const auto blrStartPos = csb->csb_blr_reader.getPos(); - jrd_prc* procedure = NULL; - QualifiedName name; + auto& blrReader = csb->csb_blr_reader; + const auto blrStartPos = blrReader.getPos(); - 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); + USHORT inArgCount = 0; + USHORT outArgCount = 0; + QualifiedName name; - csb->csb_blr_reader.getMetaName(name.identifier); + const auto node = FB_NEW_POOL(pool) ExecProcedureNode(pool); - 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_id: + { + UCHAR procedureIdCode; + + while ((procedureIdCode = blrReader.getByte()) != blr_end) + { + switch (procedureIdCode) + { + case blr_invsel_procedure_id_schema: + blrReader.skipMetaName(); + break; + + case blr_invsel_procedure_id_package: + blrReader.getMetaName(name.package); + break; + + case blr_invsel_procedure_id_name: + blrReader.getMetaName(name.identifier); + break; + + case blr_invsel_procedure_id_sub: + PAR_error(csb, Arg::Gds(isc_random) << + "blr_invsel_procedure_id_sub is not supported in this version"); + break; + + default: + PAR_error(csb, Arg::Gds(isc_random) << "Invalid blr_invsel_procedure_id"); + break; + } + } + + if (!node->procedure) + node->procedure = MET_lookup_procedure(tdbb, name, false); + + break; + } + + case blr_invsel_procedure_in_arg_names: + PAR_error(csb, Arg::Gds(isc_random) << + "blr_invsel_procedure_in_arg_names is not supported in this version"); + break; + + case blr_invsel_procedure_in_args: + predateCheck(node->procedure, "blr_invsel_procedure_id", "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: + PAR_error(csb, Arg::Gds(isc_random) << + "blr_invsel_procedure_out_arg_names is not supported in this version"); + break; + + case blr_invsel_procedure_out_args: + predateCheck(node->procedure, "blr_invsel_procedure_id", "blr_invsel_procedure_out_args"); + outArgCount = blrReader.getWord(); + node->outputTargets = PAR_args(tdbb, csb, outArgCount, outArgCount); + break; + + case blr_invsel_procedure_inout_arg_names: + PAR_error(csb, Arg::Gds(isc_random) << + "blr_invsel_procedure_inout_arg_names is not supported in this version"); + break; + + case blr_invsel_procedure_inout_args: + PAR_error(csb, Arg::Gds(isc_random) << + "blr_invsel_procedure_inout_args is not supported in this version"); + break; + + default: + PAR_error(csb, Arg::Gds(isc_random) << "Invalid blr_invoke_procedure sub code"); + } } + + break; } - 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()) + 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 - { - csb->csb_blr_reader.setPos(blrStartPos); - PAR_error(csb, - Arg::Gds(isc_prcnotdef) << Arg::Str(name.toString()) << - Arg::Gds(isc_modnotfound)); - } + 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 (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 (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)); } } - ExecProcedureNode* node = FB_NEW_POOL(pool) ExecProcedureNode(pool); - node->procedure = procedure; + node->inputTargets = FB_NEW_POOL(pool) ValueListNode(pool, node->procedure->getInputFields().getCount()); + + Arg::StatusVector mismatchStatus; - 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); + CMP_procedure_arguments( + tdbb, + csb, + node->procedure, + true, + inArgCount, + node->inputSources, + node->inputTargets, + node->inputMessage, + mismatchStatus); - if (csb->collectingDependencies() && !procedure->isSubRoutine()) + CMP_procedure_arguments( + tdbb, + csb, + node->procedure, + false, + outArgCount, + node->outputTargets, + node->outputSources, + node->outputMessage, + mismatchStatus); + + if (mismatchStatus.hasData()) + { + status_exception::raise(Arg::Gds(isc_prcmismat) << + node->procedure->getName().toString() << mismatchStatus); + } + + if (csb->collectingDependencies() && !node->procedure->isSubRoutine()) { CompilerScratch::Dependency dependency(obj_procedure); - dependency.procedure = procedure; + dependency.procedure = node->procedure; csb->addDependency(dependency); } + if (node->inputSources && node->inputSources->items.isEmpty()) + { + 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; } @@ -4664,6 +4830,11 @@ DmlNode* ExceptionNode::parse(thread_db* tdbb, MemoryPool& pool, CompilerScratch PAR_error(csb, Arg::Gds(isc_codnotdef) << item->name); break; + case blr_exception2: + case blr_exception3: + csb->csb_blr_reader.skipMetaName(); + [[fallthrough]]; + case blr_exception: case blr_exception_msg: case blr_exception_params: @@ -4678,6 +4849,18 @@ DmlNode* ExceptionNode::parse(thread_db* tdbb, MemoryPool& pool, CompilerScratch dependency.number = item->code; csb->addDependency(dependency); } + + if (codeType == blr_exception_msg || + (codeType == blr_exception3 && csb->csb_blr_reader.getByte() != 0)) + { + node->messageExpr = PAR_parse_value(tdbb, csb); + } + + if (codeType == blr_exception_params || codeType == blr_exception3) + { + const USHORT count = csb->csb_blr_reader.getWord(); + node->parameters = PAR_args(tdbb, csb, count, count); + } } break; @@ -4689,14 +4872,6 @@ DmlNode* ExceptionNode::parse(thread_db* tdbb, MemoryPool& pool, CompilerScratch node->exception = item; } - if (type == blr_exception_params) - { - const USHORT count = csb->csb_blr_reader.getWord(); - node->parameters = PAR_args(tdbb, csb, count, count); - } - else if (type == blr_exception_msg) - node->messageExpr = PAR_parse_value(tdbb, csb); - return node; } diff --git a/src/dsql/StmtNodes.h b/src/dsql/StmtNodes.h index 60569094c32..cede8967bbb 100644 --- a/src/dsql/StmtNodes.h +++ b/src/dsql/StmtNodes.h @@ -1117,6 +1117,16 @@ class MessageNode : public TypedNode { } + // This constructor is temporary workaround for copying of existing format. + // For details look at comment in CMP_procedure_arguments() + explicit MessageNode(MemoryPool& pool, const Format& oldFormat) + : TypedNode(pool), + itemsUsedInSubroutines(pool) + { + format = Format::newFormat(pool, oldFormat.fmt_count); + *format = oldFormat; + } + public: static DmlNode* parse(thread_db* tdbb, MemoryPool& pool, CompilerScratch* csb, const UCHAR blrOp); diff --git a/src/include/firebird/impl/blr.h b/src/include/firebird/impl/blr.h index 0f666cc4e1c..992ed277a2d 100644 --- a/src/include/firebird/impl/blr.h +++ b/src/include/firebird/impl/blr.h @@ -97,6 +97,10 @@ #define blr_exception_params (unsigned char)7 #define blr_sql_state (unsigned char)8 +// v6 downgrade support +#define blr_exception2 (unsigned char)9 +#define blr_exception3 (unsigned char)10 + #define blr_version4 (unsigned char)4 #define blr_version5 (unsigned char)5 //#define blr_version6 (unsigned char)6 @@ -258,7 +262,10 @@ #define blr_relation2 (unsigned char)146 #define blr_rid2 (unsigned char)147 -// unused codes: 148..149 +// v6 downgrade support +#define blr_relation3 (unsigned char)148 + +// unused codes: 149 #define blr_set_generator (unsigned char)150 @@ -466,4 +473,37 @@ #define blr_skip_locked (unsigned char) 223 +// v6 downgrade support + +#define blr_invoke_function (unsigned char) 224 +#define blr_invoke_function_id (unsigned char) 1 +#define blr_invoke_function_id_schema (unsigned char) 1 +#define blr_invoke_function_id_package (unsigned char) 2 +#define blr_invoke_function_id_name (unsigned char) 3 +#define blr_invoke_function_id_sub (unsigned char) 4 +#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_id (unsigned char) 1 +#define blr_invsel_procedure_id_schema (unsigned char) 1 +#define blr_invsel_procedure_id_package (unsigned char) 2 +#define blr_invsel_procedure_id_name (unsigned char) 3 +#define blr_invsel_procedure_id_sub (unsigned char) 4 +#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_gen_id3 (unsigned char) 231 +#define blr_default2 (unsigned char) 232 +#define blr_flags (unsigned char) 234 + #endif // FIREBIRD_IMPL_BLR_H diff --git a/src/jrd/RecordSourceNodes.cpp b/src/jrd/RecordSourceNodes.cpp index 26f334f01bc..e45df87294d 100644 --- a/src/jrd/RecordSourceNodes.cpp +++ b/src/jrd/RecordSourceNodes.cpp @@ -780,12 +780,16 @@ RelationSourceNode* RelationSourceNode::parse(thread_db* tdbb, CompilerScratch* break; } + case blr_relation3: + csb->csb_blr_reader.skipMetaName(); + [[fallthrough]]; + case blr_relation: case blr_relation2: { csb->csb_blr_reader.getMetaName(name); - if (blrOp == blr_relation2) + if (blrOp == blr_relation2 || blrOp == blr_relation3) { aliasString = FB_NEW_POOL(csb->csb_pool) string(csb->csb_pool); csb->csb_blr_reader.getString(*aliasString); @@ -1085,27 +1089,131 @@ RecordSource* RelationSourceNode::compile(thread_db* tdbb, Optimizer* opt, bool ProcedureSourceNode* ProcedureSourceNode::parse(thread_db* tdbb, CompilerScratch* csb, const SSHORT blrOp, bool parseContext) { + 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); + } + }; + SET_TDBB(tdbb); - const auto blrStartPos = csb->csb_blr_reader.getPos(); - jrd_prc* procedure = nullptr; - AutoPtr aliasString; + auto& pool = *tdbb->getDefaultPool(); + auto& blrReader = csb->csb_blr_reader; + const auto blrStartPos = blrReader.getPos(); + 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_id: + { + UCHAR procedureIdCode; + + while ((procedureIdCode = blrReader.getByte()) != blr_end) + { + switch (procedureIdCode) + { + case blr_invsel_procedure_id_schema: + blrReader.skipMetaName(); + break; + + case blr_invsel_procedure_id_package: + blrReader.getMetaName(name.package); + break; + + case blr_invsel_procedure_id_name: + blrReader.getMetaName(name.identifier); + break; + + case blr_invsel_procedure_id_sub: + PAR_error(csb, Arg::Gds(isc_random) << + "blr_invsel_procedure_id_sub is not supported in this version"); + break; + + default: + PAR_error(csb, Arg::Gds(isc_random) << "Invalid blr_invsel_procedure_id"); + break; + } + } + + if (!node->procedure) + node->procedure = MET_lookup_procedure(tdbb, name, false); + + break; + } + + case blr_invsel_procedure_in_arg_names: + PAR_error(csb, Arg::Gds(isc_random) << + "blr_invsel_procedure_in_arg_names is not supported in this version"); + break; + + case blr_invsel_procedure_in_args: + predateCheck(node->procedure, "blr_invsel_procedure_id", "blr_invsel_procedure_in_args"); + + inArgCount = blrReader.getWord(); + node->sourceList = 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_id", "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 && node->alias.hasData()) + 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; @@ -1117,31 +1225,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; @@ -1149,60 +1249,91 @@ 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 (!node->procedure) { - if (procedure->isImplemented() && !procedure->isDefined()) - { - if (tdbb->getAttachment()->isGbak() || (tdbb->tdbb_flags & TDBB_replicator)) - { - PAR_warning( - Arg::Warning(isc_prcnotdef) << Arg::Str(name.toString()) << - Arg::Warning(isc_modnotfound)); - } - else - { - csb->csb_blr_reader.setPos(blrStartPos); - PAR_error(csb, - Arg::Gds(isc_prcnotdef) << Arg::Str(name.toString()) << - Arg::Gds(isc_modnotfound)); - } - } + blrReader.setPos(blrStartPos); + PAR_error(csb, Arg::Gds(isc_prcnotdef) << name.toString()); } - if (procedure->prc_type == prc_executable) + if (node->procedure->prc_type == prc_executable) { - const string name = procedure->getName().toString(); - if (tdbb->getAttachment()->isGbak()) - PAR_warning(Arg::Warning(isc_illegal_prc_type) << Arg::Str(name)); + PAR_warning(Arg::Warning(isc_illegal_prc_type) << node->procedure->getName().toString()); else - PAR_error(csb, Arg::Gds(isc_illegal_prc_type) << Arg::Str(name)); + PAR_error(csb, Arg::Gds(isc_illegal_prc_type) << node->procedure->getName().toString()); } - ProcedureSourceNode* node = FB_NEW_POOL(*tdbb->getDefaultPool()) ProcedureSourceNode( - *tdbb->getDefaultPool()); - - node->procedure = procedure; - node->isSubRoutine = procedure->isSubRoutine(); - node->procedureId = node->isSubRoutine ? 0 : procedure->getId(); + node->isSubRoutine = node->procedure->isSubRoutine(); + node->procedureId = node->isSubRoutine ? 0 : node->procedure->getId(); - if (aliasString) - node->alias = *aliasString; + 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 + { + blrReader.setPos(blrStartPos); + PAR_error(csb, + Arg::Gds(isc_prcnotdef) << name.toString() << + Arg::Gds(isc_modnotfound)); + } + } if (parseContext) { - node->stream = PAR_context(csb, &node->context); + if (blrOp != blr_select_procedure) + { + node->stream = PAR_context(csb, &node->context); - csb->csb_rpt[node->stream].csb_procedure = procedure; - csb->csb_rpt[node->stream].csb_alias = aliasString.release(); + csb->csb_rpt[node->stream].csb_procedure = node->procedure; + if (node->alias.hasData()) + csb->csb_rpt[node->stream].csb_alias = &node->alias; + + inArgCount = blrReader.getWord(); + node->sourceList = PAR_args(tdbb, csb, inArgCount, inArgCount); + } - PAR_procedure_parms(tdbb, csb, procedure, node->in_msg.getAddress(), - node->sourceList.getAddress(), node->targetList.getAddress(), true); + if (!node->sourceList) + node->sourceList = FB_NEW_POOL(pool) ValueListNode(pool); - if (csb->collectingDependencies()) - PAR_dependency(tdbb, csb, node->stream, (SSHORT) -1, ""); + node->targetList = FB_NEW_POOL(pool) ValueListNode(pool, node->procedure->getInputFields().getCount()); + + Arg::StatusVector mismatchStatus; + + if (!CMP_procedure_arguments( + tdbb, + csb, + node->procedure, + true, + inArgCount, + node->sourceList, + node->targetList, + node->in_msg, + mismatchStatus)) + { + status_exception::raise(Arg::Gds(isc_prcmismat) << + node->procedure->getName().toString() << mismatchStatus); + } + + if (csb->collectingDependencies() && !node->procedure->isSubRoutine()) + { + CompilerScratch::Dependency dependency(obj_procedure); + dependency.procedure = node->procedure; + csb->addDependency(dependency); + } + } + + if (node->sourceList && node->sourceList->items.isEmpty()) + { + delete node->sourceList.getObject(); + node->sourceList = nullptr; + + delete node->targetList.getObject(); + node->targetList = nullptr; } return node; diff --git a/src/jrd/Routine.cpp b/src/jrd/Routine.cpp index 4876f5d5f7c..fd915ce7801 100644 --- a/src/jrd/Routine.cpp +++ b/src/jrd/Routine.cpp @@ -190,21 +190,7 @@ void Routine::parseMessages(thread_db* tdbb, CompilerScratch* csb, BlrReader blr csb->csb_blr_reader = blrReader; - const SSHORT version = csb->csb_blr_reader.getByte(); - - switch (version) - { - case blr_version4: - case blr_version5: - //case blr_version6: - break; - - default: - status_exception::raise( - Arg::Gds(isc_metadata_corrupt) << - Arg::Gds(isc_wroblrver2) << Arg::Num(blr_version4) << Arg::Num(blr_version5/*6*/) << - Arg::Num(version)); - } + PAR_getBlrVersionAndFlags(csb); if (csb->csb_blr_reader.getByte() != blr_begin) status_exception::raise(Arg::Gds(isc_metadata_corrupt)); diff --git a/src/jrd/blp.h b/src/jrd/blp.h index 759b9206dd4..5d4815c3140 100644 --- a/src/jrd/blp.h +++ b/src/jrd/blp.h @@ -173,7 +173,7 @@ static const struct {"retrieve", two}, {"relation2", relation2}, {"rid2", rid2}, - {NULL, NULL}, + {"relation3", relation3}, {NULL, NULL}, {"set_generator", gen_id}, // 150 {"ansi_any", one}, @@ -255,5 +255,17 @@ static const struct {"outer_map", outer_map}, {NULL, NULL}, // blr_json_function {"skip_locked", zero}, + // New BLR in FB6.0 + {"invoke_function", invoke_function}, + {"invoke_procedure", invsel_procedure}, + {"select_procedure", invsel_procedure}, + {NULL, NULL}, // default_arg + {NULL, NULL}, // cast_format + {NULL, NULL}, // table_value_fun + {NULL, NULL}, // for_range + {"gen_id3", gen_id3}, + {"default2", default2}, + {NULL, NULL}, // current_schema + {NULL, NULL}, // flags - part of header {0, 0} }; diff --git a/src/jrd/cmp.cpp b/src/jrd/cmp.cpp index 05bb0b35dfc..45c1fd07951 100644 --- a/src/jrd/cmp.cpp +++ b/src/jrd/cmp.cpp @@ -459,6 +459,157 @@ ItemInfo* CMP_pass2_validation(thread_db* tdbb, CompilerScratch* csb, const Item } +bool CMP_procedure_arguments( + thread_db* tdbb, + CompilerScratch* csb, + Routine* routine, + bool isInput, + USHORT argCount, + NestConst& sources, + NestConst& targets, + NestConst& 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); + + /* 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; + */ + message = tail->csb_message = FB_NEW_POOL(pool) MessageNode(pool, *format); + // --- end of fix --- + message->messageNumber = n; + + auto sourceArgIt = sources->items.begin(); + LeftPooledMap> argsByName; + + if (argCount) + { + if (argCount > fields.getCount()) + mismatchStatus << Arg::Gds(isc_wronumarg); + + for (auto pos = 0u; pos < argCount; ++pos) + { + if (pos < fields.getCount()) + { + const auto& parameter = fields[pos]; + + if (argsByName.put(parameter->prm_name, *sourceArgIt)) + fb_assert(false); + } + + ++sourceArgIt; + } + } + + sourceArgIt = sources->items.begin(); + auto targetArgIt = targets->items.begin(); + + for (auto& parameter : fields) + { + const auto argValue = argsByName.get(parameter->prm_name); + const bool argExists = argsByName.exist(parameter->prm_name); + + if (argValue) + { + *sourceArgIt = *argValue; + argsByName.remove(parameter->prm_name); + } + + if (!argValue || !*argValue) + { + if (isInput) + { + if (parameter->prm_default_value) + *sourceArgIt = CMP_clone_node(tdbb, csb, parameter->prm_default_value); + else + { + fb_assert(false); + status_exception::raise( + Arg::Gds(isc_random) << + "Invalid BLR arguments for this version in CMP_procedure_arguments"); + } + } + else + continue; + } + + ++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; + } + + fb_assert(argsByName.isEmpty()); + } + else if (isInput) + { + 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) + { + fb_assert(false); + status_exception::raise( + Arg::Gds(isc_random) << + "Invalid BLR arguments for this version in CMP_procedure_arguments"); + } + } + } + } + + return mismatchStatus.isEmpty(); +} + + void CMP_post_procedure_access(thread_db* tdbb, CompilerScratch* csb, jrd_prc* procedure) { /************************************** diff --git a/src/jrd/cmp_proto.h b/src/jrd/cmp_proto.h index a8938a56659..d4a02a5cb92 100644 --- a/src/jrd/cmp_proto.h +++ b/src/jrd/cmp_proto.h @@ -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&); +bool CMP_procedure_arguments( + Jrd::thread_db* tdbb, + Jrd::CompilerScratch* csb, + Jrd::Routine* routine, + bool isInput, + USHORT argCount, + NestConst& sources, + NestConst& targets, + NestConst& message, + Firebird::Arg::StatusVector& mismatchStatus); + 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& = ""); diff --git a/src/jrd/constants.h b/src/jrd/constants.h index da2aa2f439c..2b999278de5 100644 --- a/src/jrd/constants.h +++ b/src/jrd/constants.h @@ -483,4 +483,7 @@ const int WITH_ADMIN_OPTION = 2; // Max length of the string returned by ERROR_TEXT context variable const USHORT MAX_ERROR_MSG_LENGTH = 1024 * METADATA_BYTES_PER_CHAR; // 1024 UTF-8 characters +// In v6 this is in consts_pub.h, but in v5 this should better be private +inline constexpr int isc_dpb_search_path = 105; + #endif // JRD_CONSTANTS_H diff --git a/src/jrd/par.cpp b/src/jrd/par.cpp index 314170a9478..b93e9ab8648 100644 --- a/src/jrd/par.cpp +++ b/src/jrd/par.cpp @@ -182,7 +182,7 @@ DmlNode* PAR_blr(thread_db* tdbb, jrd_rel* relation, const UCHAR* blr, ULONG blr csb->csb_blr_reader = BlrReader(blr, blr_length); - getBlrVersion(csb); + PAR_getBlrVersionAndFlags(csb); csb->csb_node = PAR_parse_node(tdbb, csb); @@ -231,7 +231,7 @@ BoolExprNode* PAR_validation_blr(thread_db* tdbb, jrd_rel* relation, const UCHAR csb->csb_blr_reader = BlrReader(blr, blr_length); - getBlrVersion(csb); + PAR_getBlrVersionAndFlags(csb); if (csb->csb_blr_reader.peekByte() == blr_stmt_expr) { @@ -693,7 +693,7 @@ CompilerScratch* PAR_parse(thread_db* tdbb, const UCHAR* blr, ULONG blr_length, if (internal_flag) csb->csb_g_flags |= csb_internal; - getBlrVersion(csb); + PAR_getBlrVersionAndFlags(csb); if (dbginfo_length > 0) DBG_parse_debug_info(dbginfo_length, dbginfo, *csb->csb_dbg_info); @@ -821,28 +821,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 +850,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) @@ -974,6 +979,7 @@ static PlanNode* par_plan(thread_db* tdbb, CompilerScratch* csb) case blr_relation: case blr_rid: case blr_relation2: + case blr_relation3: case blr_rid2: { const auto relationNode = RelationSourceNode::parse(tdbb, csb, blrOp, false); @@ -989,6 +995,7 @@ static PlanNode* par_plan(thread_db* tdbb, CompilerScratch* csb) case blr_procedure3: case blr_procedure4: case blr_subproc: + case blr_select_procedure: { const auto procedureNode = ProcedureSourceNode::parse(tdbb, csb, blrOp, false); plan->recordSourceNode = procedureNode; @@ -1281,6 +1288,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: @@ -1292,6 +1300,7 @@ RecordSourceNode* PAR_parseRecordSource(thread_db* tdbb, CompilerScratch* csb) case blr_rid: case blr_relation2: case blr_rid2: + case blr_relation3: return RelationSourceNode::parse(tdbb, csb, blrOp, true); case blr_local_table_id: @@ -1631,10 +1640,12 @@ 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: case blr_rid2: + case blr_relation3: case blr_local_table_id: case blr_union: case blr_recurse: @@ -1703,24 +1714,29 @@ void PAR_warning(const Arg::StatusVector& v) // Get the BLR version from the CSB stream and complain if it's unknown. -static void getBlrVersion(CompilerScratch* csb) +void PAR_getBlrVersionAndFlags(CompilerScratch* csb) { - const SSHORT version = csb->csb_blr_reader.getByte(); + const SSHORT version = csb->csb_blr_reader.parseHeader(); + switch (version) { - case blr_version4: - csb->blrVersion = 4; - break; - case blr_version5: - csb->blrVersion = 5; - break; - //case blr_version6: - // csb->blrVersion = 6; - // break; - default: - PAR_error(csb, Arg::Gds(isc_metadata_corrupt) << - Arg::Gds(isc_wroblrver2) << Arg::Num(blr_version4) << Arg::Num(blr_version5/*6*/) << - Arg::Num(version)); + case blr_version4: + csb->blrVersion = 4; + break; + + case blr_version5: + csb->blrVersion = 5; + break; + + //case blr_version6: + // csb->blrVersion = 6; + // break; + + default: + PAR_error(csb, + Arg::Gds(isc_metadata_corrupt) << + Arg::Gds(isc_wroblrver2) << + Arg::Num(blr_version4) << Arg::Num(blr_version5/*6*/) << Arg::Num(version)); } } diff --git a/src/jrd/par_proto.h b/src/jrd/par_proto.h index 77ea5dd2a08..81521ffc753 100644 --- a/src/jrd/par_proto.h +++ b/src/jrd/par_proto.h @@ -52,6 +52,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*); @@ -59,6 +60,7 @@ USHORT PAR_desc(Jrd::thread_db*, Jrd::CompilerScratch*, dsc*, Jrd::ItemInfo* = void PAR_error(Jrd::CompilerScratch*, const Firebird::Arg::StatusVector&, bool isSyntaxError = true); SSHORT PAR_find_proc_field(const Jrd::jrd_prc*, const Jrd::MetaName&); Jrd::ValueExprNode* PAR_gen_field(Jrd::thread_db* tdbb, StreamType stream, USHORT id, bool byId = false); +void PAR_getBlrVersionAndFlags(Jrd::CompilerScratch* csb); Jrd::ValueExprNode* PAR_make_field(Jrd::thread_db*, Jrd::CompilerScratch*, USHORT, const Jrd::MetaName&); Jrd::CompoundStmtNode* PAR_make_list(Jrd::thread_db*, Jrd::StmtNodeStack&); ULONG PAR_marks(Jrd::CompilerScratch*); diff --git a/src/yvalve/gds.cpp b/src/yvalve/gds.cpp index a1a6bb9d5a0..4be7985d659 100644 --- a/src/yvalve/gds.cpp +++ b/src/yvalve/gds.cpp @@ -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; +constexpr int op_invoke_function = 33; +constexpr int op_invsel_procedure = 34; static const UCHAR // generic print formats @@ -350,6 +353,10 @@ static const UCHAR relation[] = { op_byte, op_literal, op_pad, op_byte, op_line, 0}, relation2[] = { op_byte, op_literal, op_line, op_indent, op_byte, op_literal, op_pad, op_byte, op_line, 0}, + relation3[] = { op_line, op_indent, op_byte, op_literal, + op_line, op_indent, op_byte, op_literal, + op_line, op_indent, op_byte, op_literal, + op_line, op_indent, op_byte, op_line, 0}, aggregate[] = { op_byte, op_line, op_verb, op_verb, op_verb, 0}, rid[] = { op_word, op_byte, op_line, 0}, rid2[] = { op_word, op_byte, op_literal, op_pad, op_byte, op_line, 0}, @@ -360,6 +367,8 @@ static const UCHAR op_args, 0}, gen_id[] = { op_byte, op_literal, op_line, op_verb, 0}, gen_id2[] = { op_byte, op_literal, op_line, 0}, + gen_id3[] = { op_line, op_indent, op_byte, op_literal, op_line, op_indent, op_byte, op_literal, + op_line, op_indent, op_byte_opt_verb, 0}, declare[] = { op_word, op_dtype, op_line, 0}, one_word[] = { op_word, op_line, 0}, indx[] = { op_line, op_verb, op_indent, op_byte, op_line, op_args, 0}, @@ -414,7 +423,13 @@ static const UCHAR erase2[] = { op_erase, op_verb, 0}, local_table[] = { op_word, op_byte, op_literal, op_byte, op_line, 0}, outer_map[] = { op_outer_map, 0 }, - in_list[] = { op_line, op_verb, op_indent, op_word, op_line, op_args, 0}; + in_list[] = { op_line, op_verb, op_indent, op_word, op_line, op_args, 0}, + invoke_function[] = { op_invoke_function, 0 }, + invsel_procedure[] = { op_invsel_procedure, 0 }, + default2[] = { op_line, op_indent, op_byte, op_literal, + op_line, op_indent, op_byte, op_literal, + op_line, op_indent, op_byte, op_literal, + op_pad, op_line, 0}; #include "../jrd/blp.h" @@ -2162,6 +2177,57 @@ int API_ROUTINE fb_print_blr(const UCHAR* blr, ULONG blr_length, SSHORT level = 0; SLONG offset = 0; blr_print_line(control, (SSHORT) offset); + + if (control->ctl_blr_reader.getByte() == blr_flags) + { + blr_format(control, "blr_flags,"); + ++level; + + static const char* subCodes[] = + { + nullptr, + "search_system_schema" + }; + + UCHAR code; + + while ((code = control->ctl_blr_reader.getByte()) != blr_end) + { + offset = blr_print_line(control, offset); + blr_indent(control, level); + + if (code == 0 || code >= FB_NELEM(subCodes)) + { + control->ctl_blr_reader.seekBackward(1); + blr_print_byte(control); + } + else + blr_format(control, "blr_flags_%s, ", subCodes[code]); + + auto len = blr_print_word(control); + + if (len > 0) + { + offset = blr_print_line(control, offset); + blr_indent(control, level + 1); + + while (len > 0) + { + blr_print_byte(control); + --len; + } + } + } + + // print blr_end + offset = blr_print_line(control, offset); + control->ctl_blr_reader.seekBackward(1); + blr_print_verb(control, level); + --level; + } + else + control->ctl_blr_reader.seekBackward(1); + blr_print_verb(control, level); offset = control->ctl_blr_reader.getOffset(); @@ -3004,6 +3070,46 @@ static void blr_print_cond(gds_ctl* control, SSHORT level) blr_print_char(control); break; + case blr_exception2: + blr_format(control, "blr_exception2, "); + blr_print_line(control, (SSHORT) offset); + blr_indent(control, level); + n = blr_print_byte(control); + while (--n >= 0) + blr_print_char(control); + blr_print_line(control, (SSHORT) offset); + blr_indent(control, level); + n = blr_print_byte(control); + while (--n >= 0) + blr_print_char(control); + break; + + case blr_exception3: + blr_format(control, "blr_exception3, "); + blr_print_line(control, (SSHORT) offset); + blr_indent(control, level); + n = blr_print_byte(control); + while (--n >= 0) + blr_print_char(control); + blr_print_line(control, (SSHORT) offset); + blr_indent(control, level); + n = blr_print_byte(control); + while (--n >= 0) + blr_print_char(control); + blr_print_line(control, (SSHORT) offset); + blr_indent(control, level); + n = blr_print_byte(control); + if (n == 0) + blr_print_line(control, (SSHORT) offset); + else + blr_print_verb(control, 0); + blr_indent(control, level); + n = blr_print_word(control); + blr_print_line(control, (SSHORT) offset); + while (--n >= 0) + blr_print_verb(control, level); + break; + case blr_exception_msg: blr_format(control, "blr_exception_msg, "); n = blr_print_byte(control); @@ -3363,6 +3469,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) { /************************************** @@ -3976,6 +4091,216 @@ 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* idSubCodes[] = + { + nullptr, + "schema", + "package", + "name", + "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_id: + ++level; + + while ((n = control->ctl_blr_reader.getByte()) != blr_end) + { + if (n == 0 || n >= static_cast(FB_NELEM(idSubCodes))) + blr_error(control, "*** invalid blr_invoke_function_id sub code ***"); + + offset = blr_print_line(control, (SSHORT) offset); + blr_indent(control, level + 1); + blr_format(control, "blr_invoke_function_id_%s, ", idSubCodes[n]); + + if (n == blr_invoke_function_id_schema || + n == blr_invoke_function_id_package || + n == blr_invoke_function_id_name) + { + blr_print_name(control); + } + } + + offset = blr_print_line(control, (SSHORT) offset); + --level; + 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, + "id", + "in_arg_names", + "in_args", + "out_arg_names", + "out_args", + "inout_arg_names", + "inout_args", + "context", + "alias" + }; + + static const char* idSubCodes[] = + { + nullptr, + "schema", + "package", + "name", + "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_id: + ++level; + + while ((n = control->ctl_blr_reader.getByte()) != blr_end) + { + if (n == 0 || n >= static_cast(FB_NELEM(idSubCodes))) + blr_error(control, "*** invalid blr_invsel_procedure_id sub code ***"); + + offset = blr_print_line(control, (SSHORT) offset); + blr_indent(control, level + 1); + blr_format(control, "blr_invsel_procedure_id_%s, ", idSubCodes[n]); + + if (n == blr_invsel_procedure_id_schema || + n == blr_invsel_procedure_id_package || + n == blr_invsel_procedure_id_name) + { + blr_print_name(control); + } + } + + offset = blr_print_line(control, (SSHORT) offset); + --level; + 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;