From 0167f8fbd08545a1223bb94533a9ff6bf6f4a359 Mon Sep 17 00:00:00 2001 From: Chudaykin Alex Date: Tue, 28 Jan 2025 10:36:23 +0300 Subject: [PATCH 1/9] Implementation of the new internal function UNLIST --- builds/win32/msvc15/engine_static.vcxproj | 1 + .../msvc15/engine_static.vcxproj.filters | 2 +- src/common/ParserTokens.h | 1 + src/dsql/DdlNodes.epp | 8 + src/dsql/DsqlCompilerScratch.cpp | 5 + src/dsql/ExprNodes.cpp | 39 +- src/dsql/Nodes.h | 1 + src/dsql/StmtNodes.cpp | 4 + src/dsql/dsql.h | 18 + src/dsql/make.cpp | 36 ++ src/dsql/make_proto.h | 1 + src/dsql/parse-conflicts.txt | 2 +- src/dsql/parse.y | 81 +++++ src/dsql/pass1.cpp | 56 ++- src/include/fb_blk.h | 1 + src/include/firebird/impl/blr.h | 4 + src/include/firebird/impl/msg/sqlerr.h | 1 + src/include/gen/Firebird.pas | 1 + src/jrd/RecordSourceNodes.cpp | 336 ++++++++++++++++++ src/jrd/RecordSourceNodes.h | 62 ++++ src/jrd/cmp.cpp | 2 + src/jrd/exe.h | 43 ++- src/jrd/optimizer/Optimizer.cpp | 14 +- src/jrd/par.cpp | 9 +- src/jrd/recsrc/RecordSource.h | 46 +++ src/jrd/recsrc/TableValueFunctionScan.cpp | 277 +++++++++++++++ 26 files changed, 1031 insertions(+), 20 deletions(-) create mode 100644 src/jrd/recsrc/TableValueFunctionScan.cpp diff --git a/builds/win32/msvc15/engine_static.vcxproj b/builds/win32/msvc15/engine_static.vcxproj index c2b465b4cce..7143acd7d41 100644 --- a/builds/win32/msvc15/engine_static.vcxproj +++ b/builds/win32/msvc15/engine_static.vcxproj @@ -140,6 +140,7 @@ + diff --git a/builds/win32/msvc15/engine_static.vcxproj.filters b/builds/win32/msvc15/engine_static.vcxproj.filters index 7aa65c72ea2..2d0070181f9 100644 --- a/builds/win32/msvc15/engine_static.vcxproj.filters +++ b/builds/win32/msvc15/engine_static.vcxproj.filters @@ -111,7 +111,7 @@ JRD files\Data Access - + JRD files\Data Access diff --git a/src/common/ParserTokens.h b/src/common/ParserTokens.h index a45da1c81aa..3f2e569e42b 100644 --- a/src/common/ParserTokens.h +++ b/src/common/ParserTokens.h @@ -524,6 +524,7 @@ PARSER_TOKEN(TOK_UNICODE_CHAR, "UNICODE_CHAR", true) PARSER_TOKEN(TOK_UNICODE_VAL, "UNICODE_VAL", true) PARSER_TOKEN(TOK_UNION, "UNION", false) PARSER_TOKEN(TOK_UNIQUE, "UNIQUE", false) +PARSER_TOKEN(TOK_UNLIST, "UNLIST", true) PARSER_TOKEN(TOK_UNKNOWN, "UNKNOWN", false) PARSER_TOKEN(TOK_UPDATE, "UPDATE", false) PARSER_TOKEN(TOK_UPDATING, "UPDATING", false) diff --git a/src/dsql/DdlNodes.epp b/src/dsql/DdlNodes.epp index 5a3cd5eed3a..d125bf67654 100644 --- a/src/dsql/DdlNodes.epp +++ b/src/dsql/DdlNodes.epp @@ -9119,6 +9119,14 @@ void CreateAlterViewNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScra else updatable = false; + + if (field && context && (context->ctx_flags & CTX_blr_fields)) + { + field = nullptr; + context = nullptr; + updatable = false; + } + // If this is an expression, check to make sure there is a name specified. if (!ptr && !fieldStr) diff --git a/src/dsql/DsqlCompilerScratch.cpp b/src/dsql/DsqlCompilerScratch.cpp index 586dfdb9eec..8fe3eee0f21 100644 --- a/src/dsql/DsqlCompilerScratch.cpp +++ b/src/dsql/DsqlCompilerScratch.cpp @@ -945,6 +945,11 @@ bool DsqlCompilerScratch::pass1RelProcIsRecursive(RecordSourceNode* input) relName = relNode->dsqlName; relAlias = relNode->alias; } + else if (auto tableValueFunctionNode = nodeAs(input)) + { + relName = tableValueFunctionNode->dsqlName; + relAlias = tableValueFunctionNode->alias.c_str(); + } //// TODO: LocalTableSourceNode else return false; diff --git a/src/dsql/ExprNodes.cpp b/src/dsql/ExprNodes.cpp index 8320ab355be..fed3bd74fb7 100644 --- a/src/dsql/ExprNodes.cpp +++ b/src/dsql/ExprNodes.cpp @@ -6027,6 +6027,11 @@ DmlNode* FieldNode::parse(thread_db* tdbb, MemoryPool& pool, CompilerScratch* cs Arg::Str(name) << Arg::Str(procedure->getName().toString())); } } + else if (tail->csb_table_value_fun) + { + csb->csb_blr_reader.getMetaName(name); + id = tail->csb_table_value_fun->getId(name); + } else { jrd_rel* relation = tail->csb_relation; @@ -6250,6 +6255,13 @@ ValueExprNode* FieldNode::internalDsqlPass(DsqlCompilerScratch* dsqlScratch, Rec procNode->dsqlContext = stackContext; *list = procNode; } + else if (context->ctx_table_value_fun) + { + auto tableValueFunctionNode = FB_NEW_POOL(*tdbb->getDefaultPool()) + TableValueFunctionSourceNode(*tdbb->getDefaultPool()); + tableValueFunctionNode->dsqlContext = stackContext; + *list = tableValueFunctionNode; + } //// TODO: LocalTableSourceNode fb_assert(*list); @@ -6446,9 +6458,28 @@ dsql_fld* FieldNode::resolveContext(DsqlCompilerScratch* dsqlScratch, const Meta return nullptr; } + const TEXT* dsqlName = NULL; + dsql_fld* outputField = NULL; dsql_rel* relation = context->ctx_relation; dsql_prc* procedure = context->ctx_procedure; - if (!relation && !procedure) + dsql_tab_func* tableValueFunctionContext = context->ctx_table_value_fun; + + if (relation) + { + dsqlName = relation->rel_name.c_str(); + outputField = relation->rel_fields; + } + else if (procedure) + { + dsqlName = procedure->prc_name.identifier.c_str(); + outputField = procedure->prc_outputs; + } + else if (tableValueFunctionContext) + { + dsqlName = tableValueFunctionContext->funName.c_str(); + outputField = tableValueFunctionContext->outputField; + } + else return nullptr; // if there is no qualifier, then we cannot match against @@ -6491,7 +6522,9 @@ dsql_fld* FieldNode::resolveContext(DsqlCompilerScratch* dsqlScratch, const Meta } if (aliasName.isEmpty()) - aliasName = relation ? relation->rel_name : procedure->prc_name.identifier; + { + aliasName = dsqlName; + } fb_assert(aliasName.hasData()); @@ -6501,7 +6534,7 @@ dsql_fld* FieldNode::resolveContext(DsqlCompilerScratch* dsqlScratch, const Meta // Lookup field in relation or procedure - return relation ? relation->rel_fields : procedure->prc_outputs; + return outputField; } bool FieldNode::dsqlAggregateFinder(AggregateFinder& visitor) diff --git a/src/dsql/Nodes.h b/src/dsql/Nodes.h index 4df8900d540..56bed32d86d 100644 --- a/src/dsql/Nodes.h +++ b/src/dsql/Nodes.h @@ -521,6 +521,7 @@ class ExprNode : public DmlNode TYPE_SELECT_EXPR, TYPE_UNION, TYPE_WINDOW, + TYPE_TABLE_VALUE_FUNCTION, // List types TYPE_REC_SOURCE_LIST, diff --git a/src/dsql/StmtNodes.cpp b/src/dsql/StmtNodes.cpp index f8a518da49c..f8174dbf352 100644 --- a/src/dsql/StmtNodes.cpp +++ b/src/dsql/StmtNodes.cpp @@ -10682,6 +10682,8 @@ static dsql_ctx* dsqlGetContext(const RecordSourceNode* node) return procNode->dsqlContext; else if (auto relNode = nodeAs(node)) return relNode->dsqlContext; + else if (auto tableValueFunctionNode = nodeAs(node)) + return tableValueFunctionNode->dsqlContext; //// TODO: LocalTableSourceNode else if (auto rseNode = nodeAs(node)) return rseNode->dsqlContext; @@ -10699,6 +10701,8 @@ static void dsqlGetContexts(DsqlContextStack& contexts, const RecordSourceNode* contexts.push(procNode->dsqlContext); else if (auto relNode = nodeAs(node)) contexts.push(relNode->dsqlContext); + else if (auto tableValueFunctionNode = nodeAs(node)) + contexts.push(tableValueFunctionNode->dsqlContext); //// TODO: LocalTableSourceNode else if (auto rseNode = nodeAs(node)) { diff --git a/src/dsql/dsql.h b/src/dsql/dsql.h index 422e36c3961..42ff206c6be 100644 --- a/src/dsql/dsql.h +++ b/src/dsql/dsql.h @@ -410,6 +410,21 @@ class dsql_intlsym : public pool_alloc USHORT intlsym_bytes_per_char = 0; }; + +// Table value function +class dsql_tab_func : public pool_alloc +{ +public: + explicit dsql_tab_func(MemoryPool& p) + : funName(p), + outputField(nullptr) + { + } + + MetaName funName; // Name of function + dsql_fld* outputField; // Output parameters +}; + // values used in intlsym_flags enum intlsym_flags_vals { @@ -454,6 +469,7 @@ class dsql_ctx : public pool_alloc dsql_rel* ctx_relation = nullptr; // Relation for context dsql_prc* ctx_procedure = nullptr; // Procedure for context + dsql_tab_func* ctx_table_value_fun = nullptr; // Table value function context NestConst ctx_proc_inputs; // Procedure input parameters dsql_map* ctx_map = nullptr; // Maps for aggregates and unions RseNode* ctx_rse = nullptr; // Sub-rse for aggregates @@ -475,6 +491,7 @@ class dsql_ctx : public pool_alloc { ctx_relation = v.ctx_relation; ctx_procedure = v.ctx_procedure; + ctx_table_value_fun = v.ctx_table_value_fun; ctx_proc_inputs = v.ctx_proc_inputs; ctx_map = v.ctx_map; ctx_rse = v.ctx_rse; @@ -518,6 +535,7 @@ const USHORT CTX_view_with_check_store = 0x20; // Context of WITH CHECK OPTION const USHORT CTX_view_with_check_modify = 0x40; // Context of WITH CHECK OPTION view's modify trigger const USHORT CTX_cursor = 0x80; // Context is a cursor const USHORT CTX_lateral = 0x100; // Context is a lateral derived table +const USHORT CTX_blr_fields = 0x200; // Fields of the context are defined inside BLR //! Aggregate/union map block to map virtual fields to their base //! TMN: NOTE! This datatype should definitely be renamed! diff --git a/src/dsql/make.cpp b/src/dsql/make.cpp index 0ee75a0428b..0d706dae630 100644 --- a/src/dsql/make.cpp +++ b/src/dsql/make.cpp @@ -423,6 +423,42 @@ FieldNode* MAKE_field(dsql_ctx* context, dsql_fld* field, ValueListNode* indices } +/** + + MAKE_field + + @brief Make up a dsql_fld from descriptor. + + + @param field + @param desc + + **/ +void MAKE_field(dsql_fld* field, const dsc* desc) +{ + DEV_BLKCHK(field, dsql_type_fld); + + field->dtype = desc->dsc_dtype; + field->scale = desc->dsc_scale; + field->subType = desc->dsc_sub_type; + field->length = desc->dsc_length; + + if (desc->dsc_dtype <= dtype_any_text) + { + field->collationId = DSC_GET_COLLATE(desc); + field->charSetId = DSC_GET_CHARSET(desc); + } + else if (desc->dsc_dtype == dtype_blob) + { + field->charSetId = desc->dsc_scale; + field->collationId = desc->dsc_flags >> 8; + } + + if (desc->dsc_flags & DSC_nullable) + field->flags |= FLD_nullable; +} + + /** MAKE_field_name diff --git a/src/dsql/make_proto.h b/src/dsql/make_proto.h index d68df999fb4..fd21fc4cf5d 100644 --- a/src/dsql/make_proto.h +++ b/src/dsql/make_proto.h @@ -84,6 +84,7 @@ Jrd::LiteralNode* MAKE_const_sint64(SINT64 value, SCHAR scale); Jrd::ValueExprNode* MAKE_constant(const char*, Jrd::dsql_constant_type, SSHORT = 0); Jrd::LiteralNode* MAKE_str_constant(const Jrd::IntlString*, SSHORT); Jrd::FieldNode* MAKE_field(Jrd::dsql_ctx*, Jrd::dsql_fld*, Jrd::ValueListNode*); +void MAKE_field(Jrd::dsql_fld*, const dsc*); Jrd::FieldNode* MAKE_field_name(const char*); Jrd::dsql_par* MAKE_parameter(Jrd::dsql_msg*, bool, bool, USHORT, const Jrd::ValueExprNode*); void MAKE_parameter_names(Jrd::dsql_par*, const Jrd::ValueExprNode*); diff --git a/src/dsql/parse-conflicts.txt b/src/dsql/parse-conflicts.txt index 612edc3bcf0..130f843da22 100644 --- a/src/dsql/parse-conflicts.txt +++ b/src/dsql/parse-conflicts.txt @@ -1 +1 @@ -117 shift/reduce conflicts, 22 reduce/reduce conflicts. +118 shift/reduce conflicts, 22 reduce/reduce conflicts. diff --git a/src/dsql/parse.y b/src/dsql/parse.y index c44adee3e88..f728d709b41 100644 --- a/src/dsql/parse.y +++ b/src/dsql/parse.y @@ -708,6 +708,7 @@ using namespace Firebird; %token LTRIM %token NAMED_ARG_ASSIGN %token RTRIM +%token UNLIST // precedence declarations for expression evaluation @@ -6397,6 +6398,7 @@ table_primary | derived_table { $$ = $1; } | lateral_derived_table { $$ = $1; } | parenthesized_joined_table { $$ = $1; } + | table_value_function { $$ = $1; } ; %type parenthesized_joined_table @@ -6595,6 +6597,84 @@ outer_noise | OUTER ; +%type table_value_function +table_value_function + : table_value_function_clause table_value_function_correlation_name table_value_function_columns_name + { + auto *node = nodeAs($1); + node->alias = *$2; + node->dsqlNameColumns = *$3; + $$ = node; + } + ; + +%type table_value_function_clause +table_value_function_clause + : table_value_function_unlist + { $$ = $1; } + ; + +%type table_value_function_unlist +table_value_function_unlist + : UNLIST '(' table_value_function_unlist_arg_list table_value_function_returning ')' + { + auto node = newNode(); + node->dsqlFlags |= RecordSourceNode::DFLAG_VALUE; + node->dsqlName = *$1; + node->inputList = $3; + node->dsqlField = $4; + $$ = node; + } + ; + +%type table_value_function_unlist_arg_list +table_value_function_unlist_arg_list + : value delimiter_opt + { + $$ = newNode($1); + $$->add($2); + } + ; + +%type table_value_function_returning +table_value_function_returning + : /*nothing*/ { $$ = NULL; } + | RETURNING data_type_descriptor { $$ = $2; } + ; + + +%type table_value_function_correlation_name +table_value_function_correlation_name + : as_noise symbol_table_alias_name { $$ = $2; } + ; + + +%type table_value_function_columns_name +table_value_function_columns_name + : /* nothing */ { $$ = newNode>(); } + | '(' table_value_function_columns ')' { $$ = $2; } + ; + +%type table_value_function_columns +table_value_function_columns + : table_value_function_column + { + ObjectsArray* node = newNode>(); + node->add(*$1); + $$ = node; + } + | table_value_function_columns ',' table_value_function_column + { + ObjectsArray* node = $1; + node->add(*$3); + $$ = node; + } + ; + +%type table_value_function_column +table_value_function_column + : symbol_column_name { $$ = $1; } + ; // other clauses in the select expression @@ -9690,6 +9770,7 @@ non_reserved_word | ANY_VALUE | FORMAT | OWNER + | UNLIST ; %% diff --git a/src/dsql/pass1.cpp b/src/dsql/pass1.cpp index ac7812eb4db..cfe48a840de 100644 --- a/src/dsql/pass1.cpp +++ b/src/dsql/pass1.cpp @@ -348,6 +348,7 @@ dsql_ctx* PASS1_make_context(DsqlCompilerScratch* dsqlScratch, RecordSourceNode* dsql_rel* relation = NULL; dsql_prc* procedure = NULL; + dsql_tab_func* tableValueFunctionContext = nullptr; // figure out whether this is a relation or a procedure // and give an error if it is neither @@ -356,11 +357,14 @@ dsql_ctx* PASS1_make_context(DsqlCompilerScratch* dsqlScratch, RecordSourceNode* ProcedureSourceNode* procNode = NULL; RelationSourceNode* relNode = NULL; SelectExprNode* selNode = NULL; + TableValueFunctionSourceNode* tableValueFunctionNode = nullptr; if ((procNode = nodeAs(relationNode))) relation_name = procNode->dsqlName.identifier; else if ((relNode = nodeAs(relationNode))) relation_name = relNode->dsqlName; + else if ((tableValueFunctionNode = nodeAs(relationNode))) + relation_name = tableValueFunctionNode->alias; //// TODO: LocalTableSourceNode else if ((selNode = nodeAs(relationNode))) relation_name = selNode->alias.c_str(); @@ -371,6 +375,11 @@ dsql_ctx* PASS1_make_context(DsqlCompilerScratch* dsqlScratch, RecordSourceNode* { // No processing needed here for derived tables. } + else if (tableValueFunctionNode) + { + tableValueFunctionContext = FB_NEW_POOL(*tdbb->getDefaultPool()) dsql_tab_func(*tdbb->getDefaultPool()); + tableValueFunctionContext->funName = tableValueFunctionNode->dsqlName; + } else if (procNode && (procNode->dsqlName.package.hasData() || procNode->inputSources)) { if (procNode->dsqlName.package.isEmpty()) @@ -430,6 +439,7 @@ dsql_ctx* PASS1_make_context(DsqlCompilerScratch* dsqlScratch, RecordSourceNode* const auto context = FB_NEW_POOL(*tdbb->getDefaultPool()) dsql_ctx(*tdbb->getDefaultPool()); context->ctx_relation = relation; context->ctx_procedure = procedure; + context->ctx_table_value_fun = tableValueFunctionContext; if (selNode) { @@ -467,6 +477,8 @@ dsql_ctx* PASS1_make_context(DsqlCompilerScratch* dsqlScratch, RecordSourceNode* // as one, I'll leave this assignment here. It will be set in PASS1_derived_table anyway. ///context->ctx_rse = selNode->querySpec; } + else if ((tableValueFunctionNode = nodeAs(relationNode))) + str = tableValueFunctionNode->alias.c_str(); if (str.hasData()) context->ctx_internal_alias = str; @@ -599,6 +611,15 @@ dsql_ctx* PASS1_make_context(DsqlCompilerScratch* dsqlScratch, RecordSourceNode* } } } + else if (tableValueFunctionNode) + { + context->ctx_flags |= CTX_blr_fields; + + fb_assert(tableValueFunctionContext); + tableValueFunctionContext->outputField = tableValueFunctionNode->makeField(dsqlScratch); + + context->ctx_proc_inputs = tableValueFunctionNode->inputList; + } // push the context onto the dsqlScratch context stack // for matching fields against @@ -959,7 +980,7 @@ void PASS1_expand_contexts(DsqlContextStack& contexts, dsql_ctx* context) { //// TODO: LocalTableSourceNode if (context->ctx_relation || context->ctx_procedure || - context->ctx_map || context->ctx_win_maps.hasData()) + context->ctx_map || context->ctx_win_maps.hasData() || context->ctx_table_value_fun) { if (context->ctx_parent) context = context->ctx_parent; @@ -1431,6 +1452,27 @@ void PASS1_expand_select_node(DsqlCompilerScratch* dsqlScratch, ExprNode* node, else list->add(value); } + else if (auto tableValueFunctionNode = nodeAs(node)) + { + auto context = tableValueFunctionNode->dsqlContext; + + if (const auto tableValueFunctionContext = context->ctx_table_value_fun) + { + tableValueFunctionNode->setDefaultNameField(dsqlScratch); + + for (dsql_fld* field = tableValueFunctionContext->outputField; field; field = field->fld_next) + { + DEV_BLKCHK(field, dsql_type_fld); + NestConst select_item = NULL; + if (!hide_using || context->getImplicitJoinField(field->fld_name, select_item)) + { + if (!select_item) + select_item = MAKE_field(context, field, NULL); + list->add(select_item); + } + } + } + } else { fb_assert(node->getKind() == DmlNode::KIND_VALUE); @@ -1728,6 +1770,13 @@ RecordSourceNode* PASS1_relation(DsqlCompilerScratch* dsqlScratch, RecordSourceN procNode->dsqlInputArgNames = nodeAs(input)->dsqlInputArgNames; return procNode; } + else if (context->ctx_table_value_fun) + { + const auto tableValueFunctionNode = FB_NEW_POOL(*tdbb->getDefaultPool()) + TableValueFunctionSourceNode(*tdbb->getDefaultPool()); + tableValueFunctionNode->dsqlContext = context; + return tableValueFunctionNode; + } //// TODO: LocalTableSourceNode fb_assert(false); @@ -2953,6 +3002,11 @@ static void remap_streams_to_parent_context(ExprNode* input, dsql_ctx* parent_co DEV_BLKCHK(relNode->dsqlContext, dsql_type_ctx); relNode->dsqlContext->ctx_parent = parent_context; } + else if (auto tableValueFunctionNode = nodeAs(input)) + { + DEV_BLKCHK(tableValueFunctionNode->dsqlContext, dsql_type_ctx); + tableValueFunctionNode->dsqlContext->ctx_parent = parent_context; + } //// TODO: LocalTableSourceNode else if (auto rseNode = nodeAs(input)) remap_streams_to_parent_context(rseNode->dsqlStreams, parent_context); diff --git a/src/include/fb_blk.h b/src/include/fb_blk.h index fec0ce090c8..852aa60316a 100644 --- a/src/include/fb_blk.h +++ b/src/include/fb_blk.h @@ -68,6 +68,7 @@ enum BlockType dsql_type_prc, dsql_type_intlsym, dsql_type_imp_join, + dsql_type_tab_func, alice_type_tdr, alice_type_str, diff --git a/src/include/firebird/impl/blr.h b/src/include/firebird/impl/blr.h index ecad7bb11a4..e190113f560 100644 --- a/src/include/firebird/impl/blr.h +++ b/src/include/firebird/impl/blr.h @@ -498,4 +498,8 @@ #define blr_cast_format (unsigned char) 228 +// Table value function +#define blr_table_value_fun (unsigned char) 229 +#define blr_table_value_fun_unlist (unsigned char) 1 + #endif // FIREBIRD_IMPL_BLR_H diff --git a/src/include/firebird/impl/msg/sqlerr.h b/src/include/firebird/impl/msg/sqlerr.h index 25a7014b0c4..63a2d897601 100644 --- a/src/include/firebird/impl/msg/sqlerr.h +++ b/src/include/firebird/impl/msg/sqlerr.h @@ -283,3 +283,4 @@ FB_IMPL_MSG(SQLERR, 1043, dsql_string_byte_length, -901, "42", "000", "String li FB_IMPL_MSG(SQLERR, 1044, dsql_string_char_length, -901, "42", "000", "String literal with @1 characters exceeds the maximum length of @2 characters for the @3 character set") FB_IMPL_MSG(SQLERR, 1045, dsql_max_nesting, -901, "07", "002", "Too many BEGIN...END nesting. Maximum level is @1") FB_IMPL_MSG(SQLERR, 1046, dsql_recreate_user_failed, -901, "42", "000", "RECREATE USER @1 failed") +FB_IMPL_MSG(SQLERR, 1047, dsql_table_value_many_columns, -104, "54", "001", "the number of fields exceeds the limit for the @1 operator. Expected @2, received @3") diff --git a/src/include/gen/Firebird.pas b/src/include/gen/Firebird.pas index e446fc37a00..8c59eb96f08 100644 --- a/src/include/gen/Firebird.pas +++ b/src/include/gen/Firebird.pas @@ -6125,6 +6125,7 @@ IProfilerStatsImpl = class(IProfilerStats) isc_dsql_string_char_length = 336397332; isc_dsql_max_nesting = 336397333; isc_dsql_recreate_user_failed = 336397334; + isc_dsql_table_value_many_columns = 336397335; isc_gsec_cant_open_db = 336723983; isc_gsec_switches_error = 336723984; isc_gsec_no_op_spec = 336723985; diff --git a/src/jrd/RecordSourceNodes.cpp b/src/jrd/RecordSourceNodes.cpp index 47367b41dce..22efc4663bc 100644 --- a/src/jrd/RecordSourceNodes.cpp +++ b/src/jrd/RecordSourceNodes.cpp @@ -210,6 +210,12 @@ PlanNode* PlanNode::dsqlPass(DsqlCompilerScratch* dsqlScratch) procNode->dsqlContext = context; node->recordSourceNode = procNode; } + else if (context->ctx_table_value_fun) + { + auto tableValueFunctionNode = FB_NEW_POOL(pool) TableValueFunctionSourceNode(pool); + tableValueFunctionNode->dsqlContext = context; + node->recordSourceNode = tableValueFunctionNode; + } //// TODO: LocalTableSourceNode // ASF: I think it's a error to let node->recordSourceNode be NULL here, but it happens @@ -3651,6 +3657,331 @@ RseNode* SelectExprNode::dsqlPass(DsqlCompilerScratch* dsqlScratch) return PASS1_derived_table(dsqlScratch, this, NULL); } +TableValueFunctionSourceNode* +TableValueFunctionSourceNode::parse(thread_db* tdbb, CompilerScratch* csb, const SSHORT blrOp) +{ + fb_assert(blrOp == blr_table_value_fun); + + MemoryPool& pool = *tdbb->getDefaultPool(); + + const auto funcId = csb->csb_blr_reader.getByte(); + auto node = TableValueFunctionSourceNode::parse2(tdbb, funcId); + + node->stream = PAR_context(csb, nullptr); + + CompilerScratch::csb_repeat& element = csb->csb_rpt[node->stream]; + auto tableValueFunctionCsb = node->m_csbTableValueFun = FB_NEW_POOL(pool) jrd_tab_func(pool); + + tableValueFunctionCsb->funcId = funcId; + + string aliasString; + csb->csb_blr_reader.getString(aliasString); + if (aliasString.hasData()) + node->alias = aliasString; + else + fb_assert(false); + + tableValueFunctionCsb->name = aliasString; + + auto count = csb->csb_blr_reader.getWord(); + node->inputList = FB_NEW_POOL(pool) ValueListNode(pool, 0U); + while (count--) + node->inputList->add(PAR_parse_value(tdbb, csb)); + + count = csb->csb_blr_reader.getWord(); + + auto recordFormat = Format::newFormat(pool, count); + ULONG& offset = recordFormat->fmt_length = FLAG_BYTES(recordFormat->fmt_count); + Format::fmt_desc_iterator descIt = recordFormat->fmt_desc.begin(); + SSHORT fieldId = 0; + while (count--) + { + PAR_desc(tdbb, csb, descIt, nullptr); + + const USHORT align = type_alignments[descIt->dsc_dtype]; + if (align) + offset = FB_ALIGN(offset, align); + descIt->dsc_address = (UCHAR*)(IPTR)offset; + offset += descIt->dsc_length; + descIt++; + + string columnString; + csb->csb_blr_reader.getString(columnString); + fb_assert(columnString.hasData()); + + tableValueFunctionCsb->fields.put(columnString, fieldId++); + } + + element.csb_format = tableValueFunctionCsb->recordFormat = recordFormat; + element.csb_table_value_fun = tableValueFunctionCsb; + + return node; +} + +TableValueFunctionSourceNode* TableValueFunctionSourceNode::parse2(thread_db* tdbb, + const SSHORT blrOp) +{ + MemoryPool& pool = *tdbb->getDefaultPool(); + if (blrOp == blr_table_value_fun_unlist) + return FB_NEW_POOL(pool) UnlistFunctionSourceNode(pool); + + fb_assert(false); + return nullptr; +} + +Firebird::string TableValueFunctionSourceNode::internalPrint(NodePrinter& printer) const +{ + RecordSourceNode::internalPrint(printer); + + NODE_PRINT(printer, dsqlName); + NODE_PRINT(printer, inputList); + NODE_PRINT(printer, dsqlField); + NODE_PRINT(printer, alias); + NODE_PRINT(printer, dsqlNameColumns); + + return "TableValueFunctionsSourceNode"; +} + +RecordSourceNode* TableValueFunctionSourceNode::dsqlPass(DsqlCompilerScratch* dsqlScratch) +{ + return dsqlPassRelProc(dsqlScratch, this); +} + +bool TableValueFunctionSourceNode::dsqlMatch(DsqlCompilerScratch* /*dsqlScratch*/, + const ExprNode* other, bool /*ignoreMapCast*/) const +{ + const auto o = nodeAs(other); + return o && dsqlContext == o->dsqlContext; +} + +RecordSourceNode* TableValueFunctionSourceNode::pass1(thread_db* tdbb, CompilerScratch* csb) +{ + doPass1(tdbb, csb, inputList.getAddress()); + return this; +} + +RecordSourceNode* TableValueFunctionSourceNode::pass2(thread_db* tdbb, CompilerScratch* csb) +{ + ExprNode::doPass2(tdbb, csb, inputList.getAddress()); + return this; +} + +void TableValueFunctionSourceNode::genBlr(DsqlCompilerScratch* dsqlScratch) +{ + dsqlScratch->appendUChar(blr_table_value_fun); + + auto tableValueFunctionContext = dsqlContext->ctx_table_value_fun; + + if (tableValueFunctionContext->funName == UnlistFunctionSourceNode::FUNC_NAME) + dsqlScratch->appendUChar(blr_table_value_fun_unlist); + else + fb_assert(false); + + GEN_stuff_context(dsqlScratch, dsqlContext); + + dsqlScratch->appendMetaString(dsqlContext->ctx_alias.c_str()); + + dsqlScratch->appendUShort(dsqlContext->ctx_proc_inputs->items.getCount()); + for (auto& arg : dsqlContext->ctx_proc_inputs->items) + GEN_expr(dsqlScratch, arg); + + Array arrayFld; + for (const auto* field = tableValueFunctionContext->outputField; field; + field = field->fld_next) + arrayFld.add(field); + + dsqlScratch->appendUShort(arrayFld.getCount()); + + for (const auto& fld : arrayFld) + { + dsqlScratch->putDtype(fld, true); + dsqlScratch->appendMetaString(fld->fld_name.c_str()); + } +} + +TableValueFunctionSourceNode* TableValueFunctionSourceNode::copy(thread_db* tdbb, + NodeCopier& copier) const +{ + if (!copier.remap) + BUGCHECK(221); // msg 221 (CMP) copy: cannot remap + + MemoryPool& pool = *tdbb->getDefaultPool(); + + auto newStream = copier.csb->nextStream(); + copier.remap[stream] = newStream; + + auto element = CMP_csb_element(copier.csb, newStream); + element->csb_view_stream = copier.remap[0]; + element->csb_format = m_csbTableValueFun->recordFormat; + element->csb_table_value_fun = m_csbTableValueFun; + if (alias.hasData()) + element->csb_alias = FB_NEW_POOL(pool) string(pool, alias.c_str()); + + auto newSource = TableValueFunctionSourceNode::parse2(tdbb, m_csbTableValueFun->funcId); + + newSource->inputList = copier.copy(tdbb, inputList); + newSource->m_csbTableValueFun = m_csbTableValueFun; + newSource->stream = newStream; + + return newSource; +} + +void TableValueFunctionSourceNode::pass1Source(thread_db* tdbb, CompilerScratch* csb, + RseNode* /*rse*/, BoolExprNode** /*boolean*/, + RecordSourceNodeStack& stack) +{ + stack.push(this); // Assume that the source will be used. Push it on the final stream stack. + + pass1(tdbb, csb); + + jrd_rel* const parentView = csb->csb_view; + const StreamType viewStream = csb->csb_view_stream; + + auto element = CMP_csb_element(csb, stream); + element->csb_view = parentView; + element->csb_view_stream = viewStream; + + // in the case where there is a parent view, find the context name + + if (parentView) + { + const ViewContexts& ctx = parentView->rel_view_contexts; + const USHORT key = stream; + FB_SIZE_T pos; + + if (ctx.find(key, pos)) + { + element->csb_alias = + FB_NEW_POOL(csb->csb_pool) string(csb->csb_pool, ctx[pos]->vcx_context_name); + } + } +} + +void TableValueFunctionSourceNode::pass2Rse(thread_db* tdbb, CompilerScratch* csb) +{ + csb->csb_rpt[stream].activate(); + + pass2(tdbb, csb); +} + +bool TableValueFunctionSourceNode::containsStream(StreamType checkStream) const +{ + return checkStream == stream; +} + +void TableValueFunctionSourceNode::computeDbKeyStreams(StreamList& /*streamList*/) const +{ +} + +RecordSource* TableValueFunctionSourceNode::compile(thread_db* /*tdbb*/, Optimizer* /*opt*/, + bool /*innerSubStream*/) +{ + fb_assert(false); // + return nullptr; +} + +bool TableValueFunctionSourceNode::computable(CompilerScratch* csb, StreamType stream, + bool allowOnlyCurrentStream, ValueExprNode* /*value*/) +{ + if (inputList && !inputList->computable(csb, stream, allowOnlyCurrentStream)) + return false; + + return true; +} + +void TableValueFunctionSourceNode::findDependentFromStreams(const CompilerScratch* csb, + StreamType currentStream, + SortedStreamList* streamList) +{ + if (inputList) + inputList->findDependentFromStreams(csb, currentStream, streamList); +} + +void TableValueFunctionSourceNode::collectStreams(SortedStreamList& streamList) const +{ + RecordSourceNode::collectStreams(streamList); + + if (inputList) + inputList->collectStreams(streamList); +} + +dsql_fld* TableValueFunctionSourceNode::makeField(DsqlCompilerScratch* /*dsqlScratch*/) +{ + fb_assert(false); + return nullptr; +} + +void TableValueFunctionSourceNode::setDefaultNameField(DsqlCompilerScratch* /*dsqlScratch*/) +{ + if (const auto tableValueFunctionContext = dsqlContext->ctx_table_value_fun) + { + MetaName nameFunc = tableValueFunctionContext->funName; + + auto i = 0U; + + if (nameFunc == UnlistFunctionSourceNode::FUNC_NAME) + { + dsql_fld* field = tableValueFunctionContext->outputField; + if (field->fld_name.isEmpty()) + field->fld_name = nameFunc; + } + else + fb_assert(false); + } + else + fb_assert(false); +} + +RecordSource* UnlistFunctionSourceNode::compile(thread_db* tdbb, Optimizer* opt, + bool /*innerSubStream*/) +{ + MemoryPool& pool = *tdbb->getDefaultPool(); + const auto csb = opt->getCompilerScratch(); + auto aliasOpt = opt->makeAlias(stream); + + return FB_NEW_POOL(pool) UnlistFunctionScan(csb, stream, aliasOpt, inputList); +} + +dsql_fld* UnlistFunctionSourceNode::makeField(DsqlCompilerScratch* dsqlScratch) +{ + if (inputList) + inputList = Node::doDsqlPass(dsqlScratch, inputList, false); + + dsql_fld* field = dsqlField; + + if (!field) + { + auto newField = FB_NEW_POOL(dsqlScratch->getPool()) dsql_fld(dsqlScratch->getPool()); + field = newField; + + auto inputItem = inputList->items.begin()->getObject(); + dsc desc; + DsqlDescMaker::fromNode(dsqlScratch, &desc, inputItem); + + USHORT ttype = desc.getCharSet(); + + if (ttype == CS_NONE) + ttype = CS_ASCII; + + desc.makeText(32, ttype); + MAKE_field(newField, &desc); + newField->fld_id = 0; + } + + if (dsqlNameColumns.hasData()) + { + if (dsqlNameColumns.getCount() > 1) + ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-104) << Arg::Gds(isc_dsql_command_err) + << Arg::Gds(isc_dsql_table_value_many_columns) + << Arg::Str(UnlistFunctionSourceNode::FUNC_NAME) + << Arg::Num(1) << Arg::Num(dsqlNameColumns.getCount())); + + field->fld_name = dsqlNameColumns[0]; + } + + field->resolve(dsqlScratch); + return field; +} //-------------------- @@ -3672,6 +4003,11 @@ static RecordSourceNode* dsqlPassRelProc(DsqlCompilerScratch* dsqlScratch, Recor relName = relNode->dsqlName; relAlias = relNode->alias; } + else if (const auto tblBasedFunNode = nodeAs(source)) + { + relName = tblBasedFunNode->dsqlName; + relAlias = tblBasedFunNode->alias.c_str(); + } //// TODO: LocalTableSourceNode else fb_assert(false); diff --git a/src/jrd/RecordSourceNodes.h b/src/jrd/RecordSourceNodes.h index ce4f7c0d51d..254302a6a05 100644 --- a/src/jrd/RecordSourceNodes.h +++ b/src/jrd/RecordSourceNodes.h @@ -963,6 +963,68 @@ class SelectExprNode final : public TypedNode* columns; }; +class TableValueFunctionSourceNode + : public TypedNode +{ +public: + explicit TableValueFunctionSourceNode(MemoryPool& pool) + : TypedNode(pool), + dsqlName(pool), alias(pool), dsqlField(nullptr), dsqlNameColumns(pool), + m_csbTableValueFun(nullptr) + { + } + static TableValueFunctionSourceNode* parse(thread_db* tdbb, CompilerScratch* csb, + const SSHORT blrOp); + static TableValueFunctionSourceNode* parse2(thread_db* tdbb, const SSHORT blrOp); + + Firebird::string internalPrint(NodePrinter& printer) const override; + RecordSourceNode* dsqlPass(DsqlCompilerScratch* dsqlScratch) override; + bool dsqlMatch(DsqlCompilerScratch* dsqlScratch, const ExprNode* other, + bool ignoreMapCast) const override; + RecordSourceNode* pass1(thread_db* tdbb, CompilerScratch* csb) override; + RecordSourceNode* pass2(thread_db* tdbb, CompilerScratch* csb) override; + TableValueFunctionSourceNode* copy(thread_db* tdbb, NodeCopier& copier) const override; + void genBlr(DsqlCompilerScratch* dsqlScratch) override; + void pass1Source(thread_db* tdbb, CompilerScratch* csb, RseNode* rse, BoolExprNode** boolean, + RecordSourceNodeStack& stack) override; + void pass2Rse(thread_db* tdbb, CompilerScratch* csb) override; + bool containsStream(StreamType checkStream) const override; + void computeDbKeyStreams(StreamList& streamList) const override; + RecordSource* compile(thread_db* tdbb, Optimizer* opt, bool innerSubStream) override; + + bool computable(CompilerScratch* csb, StreamType stream, bool allowOnlyCurrentStream, + ValueExprNode* value) override; + void findDependentFromStreams(const CompilerScratch* csb, StreamType currentStream, + SortedStreamList* streamList) override; + + void collectStreams(SortedStreamList& streamList) const override; + + virtual dsql_fld* makeField(DsqlCompilerScratch* dsqlScratch); + void setDefaultNameField(DsqlCompilerScratch* dsqlScratch); + +public: + MetaName dsqlName; + MetaName alias; + NestConst inputList; + dsql_fld* dsqlField; + Firebird::ObjectsArray dsqlNameColumns; + +private: + jrd_tab_func* m_csbTableValueFun; +}; + +class UnlistFunctionSourceNode : public TableValueFunctionSourceNode +{ +public: + explicit UnlistFunctionSourceNode(MemoryPool& pool) : TableValueFunctionSourceNode(pool) + { + } + + RecordSource* compile(thread_db* tdbb, Optimizer* opt, bool innerSubStream) final; + dsql_fld* makeField(DsqlCompilerScratch* dsqlScratch) final; + + static constexpr char const* FUNC_NAME = "UNLIST"; +}; } // namespace Jrd diff --git a/src/jrd/cmp.cpp b/src/jrd/cmp.cpp index 87930ad8b64..af1d010b2a9 100644 --- a/src/jrd/cmp.cpp +++ b/src/jrd/cmp.cpp @@ -266,6 +266,8 @@ const Format* CMP_format(thread_db* tdbb, CompilerScratch* csb, StreamType strea tail->csb_format = MET_current(tdbb, tail->csb_relation); else if (tail->csb_procedure) tail->csb_format = tail->csb_procedure->prc_record_format; + else if (tail->csb_table_value_fun) + tail->csb_format = tail->csb_table_value_fun->recordFormat; //// TODO: LocalTableSourceNode else IBERROR(222); // msg 222 bad blr - invalid stream diff --git a/src/jrd/exe.h b/src/jrd/exe.h index 3e348217024..1094be0e19c 100644 --- a/src/jrd/exe.h +++ b/src/jrd/exe.h @@ -431,6 +431,28 @@ class ItemInfo : public Printable typedef Firebird::LeftPooledMap MapFieldInfo; typedef Firebird::RightPooledMap MapItemInfo; +// Table value function block + +class jrd_tab_func +{ +public: + explicit jrd_tab_func(MemoryPool& p) : recordFormat(nullptr), fields(p), name(p), funcId(0) + { + } + + SSHORT getId(const MetaName& fieldName) const + { + SSHORT* id = fields.get(fieldName); + fb_assert(id); + return *id; + } + + const Format* recordFormat; + Firebird::LeftPooledMap fields; + Firebird::string name; + USHORT funcId; +}; + // Compile scratch block class CompilerScratch : public pool_alloc @@ -610,6 +632,7 @@ class CompilerScratch : public pool_alloc void activate(bool subStream = false); void deactivate(); + Firebird::string getName(bool allowEmpty = true) const; std::optional csb_cursor_number; // Cursor number for this stream StreamType csb_stream; // Map user context to internal stream @@ -630,6 +653,7 @@ class CompilerScratch : public pool_alloc PlanNode* csb_plan; // user-specified plan for this relation StreamType* csb_map; // Stream map for views RecordSource** csb_rsb_ptr; // point to rsb for nod_stream + jrd_tab_func* csb_table_value_fun; // Table value function }; typedef csb_repeat* rpt_itr; @@ -654,7 +678,8 @@ inline CompilerScratch::csb_repeat::csb_repeat() csb_cardinality(0.0), // TMN: Non-natural cardinality?! csb_plan(0), csb_map(0), - csb_rsb_ptr(0) + csb_rsb_ptr(0), + csb_table_value_fun(0) { } @@ -671,6 +696,22 @@ inline void CompilerScratch::csb_repeat::deactivate() csb_flags &= ~csb_active; } +inline Firebird::string CompilerScratch::csb_repeat::getName(bool allowEmpty) const +{ + if (csb_relation) + return csb_relation->rel_name.c_str(); + else if (csb_procedure) + return csb_procedure->getName().toString(); + else if (csb_table_value_fun) + return csb_table_value_fun->name.c_str(); + //// TODO: LocalTableSourceNode + else + { + fb_assert(allowEmpty); + return ""; + } +} + class AutoSetCurrentCursorId : private Firebird::AutoSetRestore { diff --git a/src/jrd/optimizer/Optimizer.cpp b/src/jrd/optimizer/Optimizer.cpp index 9d740423516..4566b5a3bf8 100644 --- a/src/jrd/optimizer/Optimizer.cpp +++ b/src/jrd/optimizer/Optimizer.cpp @@ -2887,12 +2887,7 @@ string Optimizer::getStreamName(StreamType stream) const auto procedure = tail->csb_procedure; const auto alias = tail->csb_alias; - string name; - - if (relation) - name = relation->rel_name.c_str(); - else if (procedure) - name = procedure->getName().toString(); + string name = tail->getName(); if (alias && alias->hasData()) { @@ -2941,13 +2936,8 @@ string Optimizer::makeAlias(StreamType stream) alias += ' '; } } - else if (csb_tail->csb_relation) - alias = csb_tail->csb_relation->rel_name.c_str(); - else if (csb_tail->csb_procedure) - alias = csb_tail->csb_procedure->getName().toString(); - //// TODO: LocalTableSourceNode else - fb_assert(false); + alias = csb_tail->getName(false); return alias; } diff --git a/src/jrd/par.cpp b/src/jrd/par.cpp index 3d7442d49d9..1ef131467c3 100644 --- a/src/jrd/par.cpp +++ b/src/jrd/par.cpp @@ -136,6 +136,7 @@ namespace CompilerScratch::csb_repeat* t2 = CMP_csb_element(m_csb, stream); t2->csb_relation = ptr->csb_relation; t2->csb_procedure = ptr->csb_procedure; + t2->csb_table_value_fun = ptr->csb_table_value_fun; t2->csb_stream = ptr->csb_stream; t2->csb_flags = ptr->csb_flags & csb_used; } @@ -616,10 +617,12 @@ ValueExprNode* PAR_make_field(thread_db* tdbb, CompilerScratch* csb, USHORT cont jrd_rel* const relation = csb->csb_rpt[stream].csb_relation; jrd_prc* const procedure = csb->csb_rpt[stream].csb_procedure; + jrd_tab_func* const table_value_function = csb->csb_rpt[stream].csb_table_value_fun; const SSHORT id = relation ? MET_lookup_field(tdbb, relation, base_field) : - procedure ? PAR_find_proc_field(procedure, base_field) : -1; + procedure ? PAR_find_proc_field(procedure, base_field) : + table_value_function ? table_value_function->getId(base_field) : -1; if (id < 0) return NULL; @@ -1221,6 +1224,9 @@ RecordSourceNode* PAR_parseRecordSource(thread_db* tdbb, CompilerScratch* csb) case blr_aggregate: return AggregateSourceNode::parse(tdbb, csb); + case blr_table_value_fun: + return TableValueFunctionSourceNode::parse(tdbb, csb, blrOp); + default: PAR_syntax_error(csb, "record source"); } @@ -1555,6 +1561,7 @@ DmlNode* PAR_parse_node(thread_db* tdbb, CompilerScratch* csb) case blr_recurse: case blr_window: case blr_aggregate: + case blr_table_value_fun: csb->csb_blr_reader.seekBackward(1); return PAR_parseRecordSource(tdbb, csb); } diff --git a/src/jrd/recsrc/RecordSource.h b/src/jrd/recsrc/RecordSource.h index 1fb0fc29748..3560714fcfe 100644 --- a/src/jrd/recsrc/RecordSource.h +++ b/src/jrd/recsrc/RecordSource.h @@ -1457,6 +1457,52 @@ namespace Jrd NestConst const m_boolean; }; + class TableValueFunctionScan : public RecordStream + { + protected: + struct Impure : public RecordSource::Impure + { + RecordBuffer* m_recordBuffer; + }; + + public: + TableValueFunctionScan(CompilerScratch* csb, StreamType stream, + const Firebird::string& alias); + void close(thread_db* tdbb) const override; + bool refetchRecord(thread_db* tdbb) const override; + WriteLockResult lockRecord(thread_db* tdbb) const override; + void getLegacyPlan(thread_db* tdbb, Firebird::string& plan, unsigned level) const override; + + protected: + bool internalGetRecord(thread_db* tdbb) const override; + void assignParameter(thread_db* tdbb, dsc* fromDesc, const dsc* toDesc, SSHORT toId, + Record* record) const; + + const Firebird::string m_alias; + }; + + class UnlistFunctionScan final : public TableValueFunctionScan + { + enum UnlistTypeItemIndex : unsigned + { + UNLIST_INDEX_STRING = 0, + UNLIST_INDEX_SEPARATOR = 1, + UNLIST_INDEX_LAST = 2 + }; + + public: + UnlistFunctionScan(CompilerScratch* csb, StreamType stream, const Firebird::string& alias, + ValueListNode* list); + + protected: + void internalOpen(thread_db* tdbb) const final; + void internalGetPlan(thread_db* tdbb, PlanEntry& planEntry, unsigned level, + bool recurse) const final; + + private: + NestConst m_inputList; + }; + } // namespace #endif // JRD_RECORD_SOURCE_H diff --git a/src/jrd/recsrc/TableValueFunctionScan.cpp b/src/jrd/recsrc/TableValueFunctionScan.cpp new file mode 100644 index 00000000000..bf819d53315 --- /dev/null +++ b/src/jrd/recsrc/TableValueFunctionScan.cpp @@ -0,0 +1,277 @@ +/* + * The contents of this file are subject to the Initial + * Developer's Public License Version 1.0 (the "License"); + * you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * http://www.ibphoenix.com/main.nfs?a=ibphoenix&page=ibp_idpl. + * + * Software distributed under the License is distributed AS IS, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. + * See the License for the specific language governing rights + * and limitations under the License. + * + * The Original Code was created by Chudaykin Alexey + * for Red Soft Corporation. + * + * Copyright (c) 2025 Red Soft Corporation + * and all contributors signed below. + * + * All Rights Reserved. + * Contributor(s): + */ +#include "firebird.h" +#include "../dsql/ExprNodes.h" +#include "../dsql/StmtNodes.h" +#include "../jrd/align.h" +#include "../jrd/jrd.h" +#include "../jrd/mov_proto.h" +#include "../jrd/optimizer/Optimizer.h" +#include "../jrd/req.h" +#include "../jrd/vio_proto.h" + +#include "RecordSource.h" + +using namespace Firebird; +using namespace Jrd; + +TableValueFunctionScan::TableValueFunctionScan(CompilerScratch* csb, StreamType stream, + const Firebird::string& alias) + : RecordStream(csb, stream), m_alias(csb->csb_pool, alias) +{ + m_impure = csb->allocImpure(); + m_cardinality = DEFAULT_CARDINALITY; +} + +void TableValueFunctionScan::close(thread_db* tdbb) const +{ + const auto request = tdbb->getRequest(); + + invalidateRecords(request); + + const auto impure = request->getImpure(m_impure); + + if (impure->irsb_flags & irsb_open) + { + impure->irsb_flags &= ~irsb_open; + + if (impure->m_recordBuffer) + { + delete impure->m_recordBuffer; + impure->m_recordBuffer = nullptr; + } + } +} + +bool TableValueFunctionScan::internalGetRecord(thread_db* tdbb) const +{ + JRD_reschedule(tdbb); + + const auto request = tdbb->getRequest(); + const auto impure = request->getImpure(m_impure); + const auto rpb = &request->req_rpb[m_stream]; + + if (!(impure->irsb_flags & irsb_open)) + { + rpb->rpb_number.setValid(false); + return false; + } + + rpb->rpb_number.increment(); + + if (!impure->m_recordBuffer->fetch(rpb->rpb_number.getValue(), rpb->rpb_record)) + { + rpb->rpb_number.setValid(false); + return false; + } + return true; +} + +bool TableValueFunctionScan::refetchRecord(thread_db* /*tdbb*/) const +{ + return true; +} + +WriteLockResult TableValueFunctionScan::lockRecord(thread_db* /*tdbb*/) const +{ + status_exception::raise(Arg::Gds(isc_record_lock_not_supp)); +} + +void TableValueFunctionScan::getLegacyPlan(thread_db* tdbb, Firebird::string& plan, + unsigned level) const +{ + if (!level) + plan += "("; + + plan += printName(tdbb, m_alias, false) + " NATURAL"; + + if (!level) + plan += ")"; +} + +void TableValueFunctionScan::assignParameter(thread_db* tdbb, dsc* fromDesc, const dsc* toDesc, + SSHORT toId, Record* record) const +{ + dsc toDescValue = *toDesc; + toDescValue.dsc_address = record->getData() + (IPTR)toDesc->dsc_address; + + if (fromDesc->isNull()) + { + record->setNull(toId); + return; + } + else + record->clearNull(toId); + + if (!DSC_EQUIV(fromDesc, &toDescValue, false)) + { + MOV_move(tdbb, fromDesc, &toDescValue); + return; + } + + memcpy(toDescValue.dsc_address, fromDesc->dsc_address, fromDesc->dsc_length); +} + +UnlistFunctionScan::UnlistFunctionScan(CompilerScratch* csb, StreamType stream, const string& alias, + ValueListNode* list) + : TableValueFunctionScan(csb, stream, alias), m_inputList(list) +{ +} + +void UnlistFunctionScan::internalOpen(thread_db* tdbb) const +{ + const auto request = tdbb->getRequest(); + const auto impure = request->getImpure(m_impure); + + impure->irsb_flags = irsb_open; + + const auto rpb = &request->req_rpb[m_stream]; + + MemoryPool& pool = *tdbb->getDefaultPool(); + impure->m_recordBuffer = FB_NEW_POOL(pool) RecordBuffer(pool, m_format); + + Record* const record = VIO_record(tdbb, rpb, m_format, &pool); + + rpb->rpb_number.setValue(BOF_NUMBER); + + fb_assert(m_inputList->items.getCount() >= UNLIST_INDEX_LAST); + + auto valueItem = m_inputList->items[UNLIST_INDEX_STRING]; + const auto valueDesc = EVL_expr(tdbb, request, valueItem); + if (valueDesc == nullptr) + { + rpb->rpb_number.setValid(true); + return; + } + + auto separatorItem = m_inputList->items[UNLIST_INDEX_SEPARATOR]; + const auto separatorDesc = EVL_expr(tdbb, request, separatorItem); + if (separatorDesc == nullptr) + { + rpb->rpb_number.setValid(false); + return; + } + + auto toDesc = m_format->fmt_desc.begin(); + fb_assert(toDesc); + + const auto textType = toDesc->getTextType(); + + auto setStringToRecord = [&] (string str, USHORT length = 0) + { + if (length == 0) + length = str.length(); + dsc fromDesc; + fromDesc.makeText(length, textType, (UCHAR*)str.c_str()); + assignParameter(tdbb, &fromDesc, toDesc, 0, record); + impure->m_recordBuffer->store(record); + }; + + string separatorStr(MOV_make_string2(tdbb, separatorDesc, textType, true)); + + if (separatorStr.isEmpty()) + { + string valueStr(MOV_make_string2(tdbb, valueDesc, textType, false)); + setStringToRecord(valueStr); + return; + } + + if (valueDesc->isBlob()) + { + blb* blob = blb::open(tdbb, request->req_transaction, + reinterpret_cast(valueDesc->dsc_address)); + + string resultStr; + + while (!(blob->blb_flags & BLB_eof)) + { + UCHAR buffer[BUFFER_LARGE]; + const auto length = blob->BLB_get_data(tdbb, buffer, sizeof(buffer), false); + if (length == 0) + continue; + + string valueStr(buffer, length); + + auto end = AbstractString::npos; + do + { + auto size = end = valueStr.find(separatorStr); + if(end == AbstractString::npos) + { + if (valueStr.hasData()) + resultStr.append(valueStr); + + break; + } + + if (size > 0) + resultStr.append(valueStr.substr(0, size)); + + valueStr.erase(valueStr.begin(), valueStr.begin() + end + separatorStr.length()); + + if (resultStr.hasData()) + { + setStringToRecord(resultStr); + resultStr.erase(); + } + } while (end != AbstractString::npos); + } + + // Tail + if (resultStr.hasData()) + setStringToRecord(resultStr); + } + else + { + string valueStr(MOV_make_string2(tdbb, valueDesc, textType, true)); + + auto end = AbstractString::npos; + do + { + auto size = end = valueStr.find(separatorStr); + if (end == AbstractString::npos) + { + if (valueStr.hasData()) + size = valueStr.size(); + else + break; + } + + if (size > 0) + setStringToRecord(valueStr, size); + + valueStr.erase(valueStr.begin(), valueStr.begin() + end + separatorStr.length()); + } while (end != AbstractString::npos); + } +} + +void UnlistFunctionScan::internalGetPlan(thread_db* tdbb, PlanEntry& planEntry, unsigned /*level*/, + bool /*recurse*/) const +{ + planEntry.className = "FunctionScan"; + + planEntry.lines.add().text = "Functions " + printName(tdbb, "Unlist", m_alias) + " Scan"; + printOptInfo(planEntry.lines); + + if (m_alias.hasData()) + planEntry.alias = m_alias; +} From 06536869e071b2b8681a321305eebb4d5d90b988 Mon Sep 17 00:00:00 2001 From: Chudaykin Alex Date: Wed, 29 Jan 2025 12:01:35 +0300 Subject: [PATCH 2/9] Added raedme file --- doc/sql.extensions/README.unlist | 69 ++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 doc/sql.extensions/README.unlist diff --git a/doc/sql.extensions/README.unlist b/doc/sql.extensions/README.unlist new file mode 100644 index 00000000000..8a1a4957e22 --- /dev/null +++ b/doc/sql.extensions/README.unlist @@ -0,0 +1,69 @@ +SQL Language Extension: UNLIST + +Function: + The function parses the input string using the specified delimiter (comma "," is implied by default) and returns the identified substrings as discrete records containing a single field. Additionally, the desired type of the returned field can be specified. If the specified data type conversion is impossible, an error is raised at runtime. + +Author: + Chudaykin Alex + +Format + ::= + UNLIST ( [, ] [, ] ) [AS] [ ( ) ] + + ::= + + ::= + + ::= RETURNING + +Syntax Rules: + +1) : any value expression that returns a string/blob of characters (or may be converted to a string), including string literals, table columns, constants, variables, expressions, etc. This parameter is mandatory. +2) : optional value expression that returns a string which is used as a delimiter (i.e. it separates one value from another inside the input string). It may also be a BLOB TEXT value, limited to 32KB. If an empty string is specified, the output will be just one record containing the input string. If omitted, the comma character is used as a delimiter. +2) : target data type to convert the output values into. Alternatively, a domain can be specified as the returned type. If omitted, VARCHAR(32) is implied. Feel free to suggest any better alternative default. +3) : alias of the record set returned by the UNLIST function. It is a mandatory parameter (per SQL standard). +4) : optional alias of the column returned by the UNLIST function. If omitted, UNLIST is used as an alias. + +Example: +A) + SELECT * FROM UNLIST('1,2,3,4,5') AS U; + +B) + SELECT * FROM UNLIST('100:200:300:400:500', ':' RETURNING INT) AS U; + +C) + SELECT U.* FROM UNLIST(‘text1, text2, text3’) AS U; + +D) + SELECT C0 FROM UNLIST(‘text1, text2, text3’) AS U(C0); + +E) + SELECT U.C0 FROM UNLIST(‘text1, text2, text3’) AS U(C0); + +F) + SET TERM ^ ; + RECREATE PROCEDURE TEST_PROC RETURNS (PROC_RETURN_INT INT) + AS + DECLARE VARIABLE text VARCHAR(11); + BEGIN + text = '123:123:123'; + FOR SELECT * FROM UNLIST( :text, ':' RETURNING INT) AS A INTO :PROC_RETURN_INT DO + SUSPEND; + END^ + SET TERM ; ^ + SELECT * FROM TEST_PROC; + +G) + CREATE DOMAIN D1 AS INT; + SELECT TEST_DOMAIN FROM UNLIST('1,2,3,4' RETURNING D1) AS A(TEST_DOMAIN); + +H) + CREATE VIEW TEST_VIEW AS SELECT * FROM UNLIST('1,2,3,4') AS A(B); + SELECT B FROM TEST_VIEW; + +Unacceptable behavior: + SELECT UNLIST FROM UNLIST('UNLIST,A,S,A') AS A; + + + + From c7c6429f4b55a249394aff22971d92fe12033d89 Mon Sep 17 00:00:00 2001 From: Chudaykin Alex Date: Wed, 29 Jan 2025 12:16:57 +0300 Subject: [PATCH 3/9] Fix typo --- builds/win32/msvc15/engine_static.vcxproj.filters | 3 +++ 1 file changed, 3 insertions(+) diff --git a/builds/win32/msvc15/engine_static.vcxproj.filters b/builds/win32/msvc15/engine_static.vcxproj.filters index 2d0070181f9..5a5f4d4f7ee 100644 --- a/builds/win32/msvc15/engine_static.vcxproj.filters +++ b/builds/win32/msvc15/engine_static.vcxproj.filters @@ -110,6 +110,9 @@ JRD files\Data Access + + + JRD files\Data Access JRD files\Data Access From be29efd1a64b661cbea1df6b838eb1d7cd8d5966 Mon Sep 17 00:00:00 2001 From: Chudaykin Alex Date: Mon, 3 Feb 2025 11:33:03 +0300 Subject: [PATCH 4/9] Correction of comments from the review. Added initialization of input parameter type. --- doc/sql.extensions/README.unlist | 2 ++ src/dsql/ExprNodes.cpp | 4 ++-- src/dsql/parse.y | 15 ++++----------- src/jrd/RecordSourceNodes.cpp | 19 ++++++++++++------- src/jrd/RecordSourceNodes.h | 2 +- src/jrd/exe.h | 6 +++--- src/jrd/par.cpp | 2 +- src/jrd/recsrc/TableValueFunctionScan.cpp | 4 ++-- 8 files changed, 27 insertions(+), 27 deletions(-) diff --git a/doc/sql.extensions/README.unlist b/doc/sql.extensions/README.unlist index 8a1a4957e22..7ca4c5fd680 100644 --- a/doc/sql.extensions/README.unlist +++ b/doc/sql.extensions/README.unlist @@ -57,6 +57,8 @@ G) CREATE DOMAIN D1 AS INT; SELECT TEST_DOMAIN FROM UNLIST('1,2,3,4' RETURNING D1) AS A(TEST_DOMAIN); + CREATE TABLE TABLE_TEST (COL1 INT); + SELECT TEST_TYPE_OF FROM UNLIST('1,2,3,4' RETURNING TYPE OF COLUMN TABLE_TEST.COL1) AS A(TEST_TYPE_OF); H) CREATE VIEW TEST_VIEW AS SELECT * FROM UNLIST('1,2,3,4') AS A(B); SELECT B FROM TEST_VIEW; diff --git a/src/dsql/ExprNodes.cpp b/src/dsql/ExprNodes.cpp index fed3bd74fb7..91ab2709735 100644 --- a/src/dsql/ExprNodes.cpp +++ b/src/dsql/ExprNodes.cpp @@ -6458,8 +6458,8 @@ dsql_fld* FieldNode::resolveContext(DsqlCompilerScratch* dsqlScratch, const Meta return nullptr; } - const TEXT* dsqlName = NULL; - dsql_fld* outputField = NULL; + const TEXT* dsqlName = nullptr; + dsql_fld* outputField = nullptr; dsql_rel* relation = context->ctx_relation; dsql_prc* procedure = context->ctx_procedure; dsql_tab_func* tableValueFunctionContext = context->ctx_table_value_fun; diff --git a/src/dsql/parse.y b/src/dsql/parse.y index f728d709b41..6dbda5f9461 100644 --- a/src/dsql/parse.y +++ b/src/dsql/parse.y @@ -6599,11 +6599,12 @@ outer_noise %type table_value_function table_value_function - : table_value_function_clause table_value_function_correlation_name table_value_function_columns_name + : table_value_function_clause table_value_function_correlation_name derived_column_list { - auto *node = nodeAs($1); + auto node = nodeAs($1); node->alias = *$2; - node->dsqlNameColumns = *$3; + if ($3) + node->dsqlNameColumns = *$3; $$ = node; } ; @@ -6642,19 +6643,11 @@ table_value_function_returning | RETURNING data_type_descriptor { $$ = $2; } ; - %type table_value_function_correlation_name table_value_function_correlation_name : as_noise symbol_table_alias_name { $$ = $2; } ; - -%type table_value_function_columns_name -table_value_function_columns_name - : /* nothing */ { $$ = newNode>(); } - | '(' table_value_function_columns ')' { $$ = $2; } - ; - %type table_value_function_columns table_value_function_columns : table_value_function_column diff --git a/src/jrd/RecordSourceNodes.cpp b/src/jrd/RecordSourceNodes.cpp index 22efc4663bc..dd001534d66 100644 --- a/src/jrd/RecordSourceNodes.cpp +++ b/src/jrd/RecordSourceNodes.cpp @@ -3657,8 +3657,9 @@ RseNode* SelectExprNode::dsqlPass(DsqlCompilerScratch* dsqlScratch) return PASS1_derived_table(dsqlScratch, this, NULL); } -TableValueFunctionSourceNode* -TableValueFunctionSourceNode::parse(thread_db* tdbb, CompilerScratch* csb, const SSHORT blrOp) +TableValueFunctionSourceNode* TableValueFunctionSourceNode::parse(thread_db* tdbb, + CompilerScratch* csb, + const SSHORT blrOp) { fb_assert(blrOp == blr_table_value_fun); @@ -3670,7 +3671,7 @@ TableValueFunctionSourceNode::parse(thread_db* tdbb, CompilerScratch* csb, const node->stream = PAR_context(csb, nullptr); CompilerScratch::csb_repeat& element = csb->csb_rpt[node->stream]; - auto tableValueFunctionCsb = node->m_csbTableValueFun = FB_NEW_POOL(pool) jrd_tab_func(pool); + auto tableValueFunctionCsb = node->m_csbTableValueFun = FB_NEW_POOL(pool) jrd_table_value_fun(pool); tableValueFunctionCsb->funcId = funcId; @@ -3786,8 +3787,7 @@ void TableValueFunctionSourceNode::genBlr(DsqlCompilerScratch* dsqlScratch) GEN_expr(dsqlScratch, arg); Array arrayFld; - for (const auto* field = tableValueFunctionContext->outputField; field; - field = field->fld_next) + for (const auto* field = tableValueFunctionContext->outputField; field; field = field->fld_next) arrayFld.add(field); dsqlScratch->appendUShort(arrayFld.getCount()); @@ -3947,6 +3947,11 @@ dsql_fld* UnlistFunctionSourceNode::makeField(DsqlCompilerScratch* dsqlScratch) if (inputList) inputList = Node::doDsqlPass(dsqlScratch, inputList, false); + dsc desc; + + auto inputItem = inputList->items.begin()->getObject(); + inputItem->setParameterType(dsqlScratch, [] (dsc* desc) { desc->makeVarying(1024, CS_dynamic); }, false); + dsql_fld* field = dsqlField; if (!field) @@ -3954,8 +3959,6 @@ dsql_fld* UnlistFunctionSourceNode::makeField(DsqlCompilerScratch* dsqlScratch) auto newField = FB_NEW_POOL(dsqlScratch->getPool()) dsql_fld(dsqlScratch->getPool()); field = newField; - auto inputItem = inputList->items.begin()->getObject(); - dsc desc; DsqlDescMaker::fromNode(dsqlScratch, &desc, inputItem); USHORT ttype = desc.getCharSet(); @@ -3971,10 +3974,12 @@ dsql_fld* UnlistFunctionSourceNode::makeField(DsqlCompilerScratch* dsqlScratch) if (dsqlNameColumns.hasData()) { if (dsqlNameColumns.getCount() > 1) + { ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-104) << Arg::Gds(isc_dsql_command_err) << Arg::Gds(isc_dsql_table_value_many_columns) << Arg::Str(UnlistFunctionSourceNode::FUNC_NAME) << Arg::Num(1) << Arg::Num(dsqlNameColumns.getCount())); + } field->fld_name = dsqlNameColumns[0]; } diff --git a/src/jrd/RecordSourceNodes.h b/src/jrd/RecordSourceNodes.h index 254302a6a05..97dcacac162 100644 --- a/src/jrd/RecordSourceNodes.h +++ b/src/jrd/RecordSourceNodes.h @@ -1010,7 +1010,7 @@ class TableValueFunctionSourceNode Firebird::ObjectsArray dsqlNameColumns; private: - jrd_tab_func* m_csbTableValueFun; + jrd_table_value_fun* m_csbTableValueFun; }; class UnlistFunctionSourceNode : public TableValueFunctionSourceNode diff --git a/src/jrd/exe.h b/src/jrd/exe.h index 1094be0e19c..ee46f2a5871 100644 --- a/src/jrd/exe.h +++ b/src/jrd/exe.h @@ -433,10 +433,10 @@ typedef Firebird::RightPooledMap MapItemInfo; // Table value function block -class jrd_tab_func +class jrd_table_value_fun { public: - explicit jrd_tab_func(MemoryPool& p) : recordFormat(nullptr), fields(p), name(p), funcId(0) + explicit jrd_table_value_fun(MemoryPool& p) : recordFormat(nullptr), fields(p), name(p), funcId(0) { } @@ -653,7 +653,7 @@ class CompilerScratch : public pool_alloc PlanNode* csb_plan; // user-specified plan for this relation StreamType* csb_map; // Stream map for views RecordSource** csb_rsb_ptr; // point to rsb for nod_stream - jrd_tab_func* csb_table_value_fun; // Table value function + jrd_table_value_fun* csb_table_value_fun; // Table value function }; typedef csb_repeat* rpt_itr; diff --git a/src/jrd/par.cpp b/src/jrd/par.cpp index 1ef131467c3..ef08ba112b2 100644 --- a/src/jrd/par.cpp +++ b/src/jrd/par.cpp @@ -617,7 +617,7 @@ ValueExprNode* PAR_make_field(thread_db* tdbb, CompilerScratch* csb, USHORT cont jrd_rel* const relation = csb->csb_rpt[stream].csb_relation; jrd_prc* const procedure = csb->csb_rpt[stream].csb_procedure; - jrd_tab_func* const table_value_function = csb->csb_rpt[stream].csb_table_value_fun; + jrd_table_value_fun* const table_value_function = csb->csb_rpt[stream].csb_table_value_fun; const SSHORT id = relation ? MET_lookup_field(tdbb, relation, base_field) : diff --git a/src/jrd/recsrc/TableValueFunctionScan.cpp b/src/jrd/recsrc/TableValueFunctionScan.cpp index bf819d53315..753118cb275 100644 --- a/src/jrd/recsrc/TableValueFunctionScan.cpp +++ b/src/jrd/recsrc/TableValueFunctionScan.cpp @@ -159,7 +159,7 @@ void UnlistFunctionScan::internalOpen(thread_db* tdbb) const const auto valueDesc = EVL_expr(tdbb, request, valueItem); if (valueDesc == nullptr) { - rpb->rpb_number.setValid(true); + rpb->rpb_number.setValid(false); return; } @@ -176,7 +176,7 @@ void UnlistFunctionScan::internalOpen(thread_db* tdbb) const const auto textType = toDesc->getTextType(); - auto setStringToRecord = [&] (string str, USHORT length = 0) + auto setStringToRecord = [&] (string& str, USHORT length = 0) { if (length == 0) length = str.length(); From dae3606631afe31ef31a1f13cbfceee21655c0a8 Mon Sep 17 00:00:00 2001 From: Chudaykin Alex Date: Wed, 5 Feb 2025 12:14:22 +0300 Subject: [PATCH 5/9] Improved names, added error output when blr code is incorrect --- src/jrd/RecordSourceNodes.cpp | 28 +++++++++++++++++++--------- src/jrd/RecordSourceNodes.h | 4 +++- 2 files changed, 22 insertions(+), 10 deletions(-) diff --git a/src/jrd/RecordSourceNodes.cpp b/src/jrd/RecordSourceNodes.cpp index dd001534d66..c6ec90b0c16 100644 --- a/src/jrd/RecordSourceNodes.cpp +++ b/src/jrd/RecordSourceNodes.cpp @@ -3666,7 +3666,7 @@ TableValueFunctionSourceNode* TableValueFunctionSourceNode::parse(thread_db* tdb MemoryPool& pool = *tdbb->getDefaultPool(); const auto funcId = csb->csb_blr_reader.getByte(); - auto node = TableValueFunctionSourceNode::parse2(tdbb, funcId); + auto node = TableValueFunctionSourceNode::parseTableValueFunctions(tdbb, csb, funcId); node->stream = PAR_context(csb, nullptr); @@ -3719,15 +3719,23 @@ TableValueFunctionSourceNode* TableValueFunctionSourceNode::parse(thread_db* tdb return node; } -TableValueFunctionSourceNode* TableValueFunctionSourceNode::parse2(thread_db* tdbb, - const SSHORT blrOp) +TableValueFunctionSourceNode* TableValueFunctionSourceNode::parseTableValueFunctions(thread_db* tdbb, + CompilerScratch* csb, + const SSHORT blrOp) { MemoryPool& pool = *tdbb->getDefaultPool(); - if (blrOp == blr_table_value_fun_unlist) - return FB_NEW_POOL(pool) UnlistFunctionSourceNode(pool); + TableValueFunctionSourceNode* node = nullptr; + switch (blrOp) + { + case blr_table_value_fun_unlist: + node = FB_NEW_POOL(pool) UnlistFunctionSourceNode(pool); + break; - fb_assert(false); - return nullptr; + default: + PAR_syntax_error(csb, "blr_table_value_fun"); + } + + return node; } Firebird::string TableValueFunctionSourceNode::internalPrint(NodePrinter& printer) const @@ -3817,7 +3825,8 @@ TableValueFunctionSourceNode* TableValueFunctionSourceNode::copy(thread_db* tdbb if (alias.hasData()) element->csb_alias = FB_NEW_POOL(pool) string(pool, alias.c_str()); - auto newSource = TableValueFunctionSourceNode::parse2(tdbb, m_csbTableValueFun->funcId); + auto newSource = TableValueFunctionSourceNode::parseTableValueFunctions( + tdbb, copier.csb, m_csbTableValueFun->funcId); newSource->inputList = copier.copy(tdbb, inputList); newSource->m_csbTableValueFun = m_csbTableValueFun; @@ -3950,7 +3959,8 @@ dsql_fld* UnlistFunctionSourceNode::makeField(DsqlCompilerScratch* dsqlScratch) dsc desc; auto inputItem = inputList->items.begin()->getObject(); - inputItem->setParameterType(dsqlScratch, [] (dsc* desc) { desc->makeVarying(1024, CS_dynamic); }, false); + inputItem->setParameterType( + dsqlScratch, [](dsc* desc) { desc->makeVarying(1024, CS_dynamic); }, false); dsql_fld* field = dsqlField; diff --git a/src/jrd/RecordSourceNodes.h b/src/jrd/RecordSourceNodes.h index 97dcacac162..d7405d8c837 100644 --- a/src/jrd/RecordSourceNodes.h +++ b/src/jrd/RecordSourceNodes.h @@ -975,7 +975,9 @@ class TableValueFunctionSourceNode } static TableValueFunctionSourceNode* parse(thread_db* tdbb, CompilerScratch* csb, const SSHORT blrOp); - static TableValueFunctionSourceNode* parse2(thread_db* tdbb, const SSHORT blrOp); + static TableValueFunctionSourceNode* parseTableValueFunctions(thread_db* tdbb, + CompilerScratch* csb, + const SSHORT blrOp); Firebird::string internalPrint(NodePrinter& printer) const override; RecordSourceNode* dsqlPass(DsqlCompilerScratch* dsqlScratch) override; From 4e4420c3967c4e75dc40b2de374af1ae9ab12538 Mon Sep 17 00:00:00 2001 From: Chudaykin Alex Date: Thu, 13 Feb 2025 11:32:00 +0300 Subject: [PATCH 6/9] Conversion to string_view --- src/jrd/recsrc/TableValueFunctionScan.cpp | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/src/jrd/recsrc/TableValueFunctionScan.cpp b/src/jrd/recsrc/TableValueFunctionScan.cpp index 753118cb275..40ad7ae46e3 100644 --- a/src/jrd/recsrc/TableValueFunctionScan.cpp +++ b/src/jrd/recsrc/TableValueFunctionScan.cpp @@ -30,6 +30,7 @@ #include "../jrd/vio_proto.h" #include "RecordSource.h" +#include using namespace Firebird; using namespace Jrd; @@ -209,33 +210,33 @@ void UnlistFunctionScan::internalOpen(thread_db* tdbb) const if (length == 0) continue; - string valueStr(buffer, length); + std::string_view separatorView(separatorStr.begin(), separatorStr.length()); + std::string_view valueView(reinterpret_cast(buffer), length); - auto end = AbstractString::npos; + auto end = std::string_view::npos; do { - auto size = end = valueStr.find(separatorStr); - if(end == AbstractString::npos) + auto size = end = valueView.find(separatorView); + if (end == std::string_view::npos) { - if (valueStr.hasData()) - resultStr.append(valueStr); + if (!valueView.empty()) + resultStr.append(valueView.data(), valueView.length()); break; } if (size > 0) - resultStr.append(valueStr.substr(0, size)); + resultStr.append(valueView.begin(), size); - valueStr.erase(valueStr.begin(), valueStr.begin() + end + separatorStr.length()); + valueView.remove_prefix(size + separatorView.length()); if (resultStr.hasData()) { setStringToRecord(resultStr); resultStr.erase(); } - } while (end != AbstractString::npos); + } while (end != std::string_view::npos); } - // Tail if (resultStr.hasData()) setStringToRecord(resultStr); @@ -275,3 +276,4 @@ void UnlistFunctionScan::internalGetPlan(thread_db* tdbb, PlanEntry& planEntry, if (m_alias.hasData()) planEntry.alias = m_alias; } + From c4928d57c70566b2f2ef4b4458444d5e815ddeac Mon Sep 17 00:00:00 2001 From: Chudaykin Alex Date: Wed, 19 Feb 2025 14:52:48 +0300 Subject: [PATCH 7/9] Fix windows build --- src/jrd/recsrc/TableValueFunctionScan.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/jrd/recsrc/TableValueFunctionScan.cpp b/src/jrd/recsrc/TableValueFunctionScan.cpp index 40ad7ae46e3..a796f8f20c3 100644 --- a/src/jrd/recsrc/TableValueFunctionScan.cpp +++ b/src/jrd/recsrc/TableValueFunctionScan.cpp @@ -226,7 +226,7 @@ void UnlistFunctionScan::internalOpen(thread_db* tdbb) const } if (size > 0) - resultStr.append(valueView.begin(), size); + resultStr.append(valueView.data(), size); valueView.remove_prefix(size + separatorView.length()); From f0a8d10e5d7a2f90e4eed10974ed0cca7245ab7f Mon Sep 17 00:00:00 2001 From: Chudaykin Alex Date: Tue, 11 Mar 2025 10:35:30 +0300 Subject: [PATCH 8/9] =?UTF-8?q?Changed=20processing=20of=20BLOB=20as=20inp?= =?UTF-8?q?ut=20parameter.=20The=20processing=20has=20been=20moved=20to=20?= =?UTF-8?q?the=20internalGetRecord=20method.=20If=20=E2=80=9CRecordBuffer?= =?UTF-8?q?=E2=80=9D=20is=20empty,=20it=20is=20tried=20to=20be=20filled=20?= =?UTF-8?q?in=20the=20=E2=80=9CnextBuffer=E2=80=9D=20method.=20Filling=20w?= =?UTF-8?q?ill=20be=20done=20in=20portions.=20A=20small=20refactoring.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/jrd/recsrc/RecordSource.h | 7 + src/jrd/recsrc/TableValueFunctionScan.cpp | 202 ++++++++++++++-------- 2 files changed, 134 insertions(+), 75 deletions(-) diff --git a/src/jrd/recsrc/RecordSource.h b/src/jrd/recsrc/RecordSource.h index 3560714fcfe..5da9341104c 100644 --- a/src/jrd/recsrc/RecordSource.h +++ b/src/jrd/recsrc/RecordSource.h @@ -1463,6 +1463,9 @@ namespace Jrd struct Impure : public RecordSource::Impure { RecordBuffer* m_recordBuffer; + blb* m_blob; + Firebird::string* m_separatorStr; + Firebird::string* m_resultStr; }; public: @@ -1478,6 +1481,8 @@ namespace Jrd void assignParameter(thread_db* tdbb, dsc* fromDesc, const dsc* toDesc, SSHORT toId, Record* record) const; + virtual bool nextBuffer(thread_db* tdbb) const; + const Firebird::string m_alias; }; @@ -1499,6 +1504,8 @@ namespace Jrd void internalGetPlan(thread_db* tdbb, PlanEntry& planEntry, unsigned level, bool recurse) const final; + bool nextBuffer(thread_db* tdbb) const final; + private: NestConst m_inputList; }; diff --git a/src/jrd/recsrc/TableValueFunctionScan.cpp b/src/jrd/recsrc/TableValueFunctionScan.cpp index a796f8f20c3..1d73df5dda5 100644 --- a/src/jrd/recsrc/TableValueFunctionScan.cpp +++ b/src/jrd/recsrc/TableValueFunctionScan.cpp @@ -22,7 +22,6 @@ #include "firebird.h" #include "../dsql/ExprNodes.h" #include "../dsql/StmtNodes.h" -#include "../jrd/align.h" #include "../jrd/jrd.h" #include "../jrd/mov_proto.h" #include "../jrd/optimizer/Optimizer.h" @@ -36,7 +35,7 @@ using namespace Firebird; using namespace Jrd; TableValueFunctionScan::TableValueFunctionScan(CompilerScratch* csb, StreamType stream, - const Firebird::string& alias) + const string& alias) : RecordStream(csb, stream), m_alias(csb->csb_pool, alias) { m_impure = csb->allocImpure(); @@ -60,6 +59,24 @@ void TableValueFunctionScan::close(thread_db* tdbb) const delete impure->m_recordBuffer; impure->m_recordBuffer = nullptr; } + + if (impure->m_blob) + { + impure->m_blob->BLB_close(tdbb); + impure->m_blob = nullptr; + } + + if (impure->m_separatorStr) + { + delete impure->m_separatorStr; + impure->m_separatorStr = nullptr; + } + + if (impure->m_resultStr) + { + delete impure->m_resultStr; + impure->m_resultStr = nullptr; + } } } @@ -78,13 +95,19 @@ bool TableValueFunctionScan::internalGetRecord(thread_db* tdbb) const } rpb->rpb_number.increment(); - - if (!impure->m_recordBuffer->fetch(rpb->rpb_number.getValue(), rpb->rpb_record)) + do { - rpb->rpb_number.setValid(false); - return false; - } - return true; + if (!impure->m_recordBuffer->fetch(rpb->rpb_number.getValue(), rpb->rpb_record)) + { + if (!nextBuffer(tdbb)) + { + rpb->rpb_number.setValid(false); + return false; + } + continue; + } + return true; + } while (1); } bool TableValueFunctionScan::refetchRecord(thread_db* /*tdbb*/) const @@ -97,8 +120,7 @@ WriteLockResult TableValueFunctionScan::lockRecord(thread_db* /*tdbb*/) const status_exception::raise(Arg::Gds(isc_record_lock_not_supp)); } -void TableValueFunctionScan::getLegacyPlan(thread_db* tdbb, Firebird::string& plan, - unsigned level) const +void TableValueFunctionScan::getLegacyPlan(thread_db* tdbb, string& plan, unsigned level) const { if (!level) plan += "("; @@ -132,6 +154,11 @@ void TableValueFunctionScan::assignParameter(thread_db* tdbb, dsc* fromDesc, con memcpy(toDescValue.dsc_address, fromDesc->dsc_address, fromDesc->dsc_length); } +bool TableValueFunctionScan::nextBuffer(thread_db* /*tdbb*/) const +{ + return false; +} + UnlistFunctionScan::UnlistFunctionScan(CompilerScratch* csb, StreamType stream, const string& alias, ValueListNode* list) : TableValueFunctionScan(csb, stream, alias), m_inputList(list) @@ -141,16 +168,8 @@ UnlistFunctionScan::UnlistFunctionScan(CompilerScratch* csb, StreamType stream, void UnlistFunctionScan::internalOpen(thread_db* tdbb) const { const auto request = tdbb->getRequest(); - const auto impure = request->getImpure(m_impure); - - impure->irsb_flags = irsb_open; - const auto rpb = &request->req_rpb[m_stream]; - MemoryPool& pool = *tdbb->getDefaultPool(); - impure->m_recordBuffer = FB_NEW_POOL(pool) RecordBuffer(pool, m_format); - - Record* const record = VIO_record(tdbb, rpb, m_format, &pool); rpb->rpb_number.setValue(BOF_NUMBER); @@ -172,79 +191,39 @@ void UnlistFunctionScan::internalOpen(thread_db* tdbb) const return; } + const auto impure = request->getImpure(m_impure); + impure->irsb_flags |= irsb_open; + impure->m_recordBuffer = FB_NEW_POOL(pool) RecordBuffer(pool, m_format); + + Record* const record = VIO_record(tdbb, rpb, m_format, &pool); + auto toDesc = m_format->fmt_desc.begin(); fb_assert(toDesc); - const auto textType = toDesc->getTextType(); - auto setStringToRecord = [&] (string& str, USHORT length = 0) + impure->m_separatorStr = FB_NEW_POOL(pool) + string(pool, MOV_make_string2(tdbb, separatorDesc, textType, true)); + + if (impure->m_separatorStr->isEmpty()) { - if (length == 0) - length = str.length(); + const string valueStr(MOV_make_string2(tdbb, valueDesc, textType, false)); dsc fromDesc; - fromDesc.makeText(length, textType, (UCHAR*)str.c_str()); + fromDesc.makeText(valueStr.size(), textType, (UCHAR*)(IPTR)(valueStr.c_str())); assignParameter(tdbb, &fromDesc, toDesc, 0, record); impure->m_recordBuffer->store(record); - }; - - string separatorStr(MOV_make_string2(tdbb, separatorDesc, textType, true)); - - if (separatorStr.isEmpty()) - { - string valueStr(MOV_make_string2(tdbb, valueDesc, textType, false)); - setStringToRecord(valueStr); return; } if (valueDesc->isBlob()) { - blb* blob = blb::open(tdbb, request->req_transaction, - reinterpret_cast(valueDesc->dsc_address)); - - string resultStr; - - while (!(blob->blb_flags & BLB_eof)) - { - UCHAR buffer[BUFFER_LARGE]; - const auto length = blob->BLB_get_data(tdbb, buffer, sizeof(buffer), false); - if (length == 0) - continue; - - std::string_view separatorView(separatorStr.begin(), separatorStr.length()); - std::string_view valueView(reinterpret_cast(buffer), length); - - auto end = std::string_view::npos; - do - { - auto size = end = valueView.find(separatorView); - if (end == std::string_view::npos) - { - if (!valueView.empty()) - resultStr.append(valueView.data(), valueView.length()); - - break; - } - - if (size > 0) - resultStr.append(valueView.data(), size); - - valueView.remove_prefix(size + separatorView.length()); - - if (resultStr.hasData()) - { - setStringToRecord(resultStr); - resultStr.erase(); - } - } while (end != std::string_view::npos); - } - // Tail - if (resultStr.hasData()) - setStringToRecord(resultStr); + impure->m_blob = blb::open(tdbb, request->req_transaction, + reinterpret_cast(valueDesc->dsc_address)); + impure->m_resultStr = FB_NEW_POOL(pool) string(pool); } else { + const string& separatorStr = *impure->m_separatorStr; string valueStr(MOV_make_string2(tdbb, valueDesc, textType, true)); - auto end = AbstractString::npos; do { @@ -258,7 +237,12 @@ void UnlistFunctionScan::internalOpen(thread_db* tdbb) const } if (size > 0) - setStringToRecord(valueStr, size); + { + dsc fromDesc; + fromDesc.makeText(size, textType, (UCHAR*)(IPTR)(valueStr.c_str())); + assignParameter(tdbb, &fromDesc, toDesc, 0, record); + impure->m_recordBuffer->store(record); + } valueStr.erase(valueStr.begin(), valueStr.begin() + end + separatorStr.length()); } while (end != AbstractString::npos); @@ -277,3 +261,71 @@ void UnlistFunctionScan::internalGetPlan(thread_db* tdbb, PlanEntry& planEntry, planEntry.alias = m_alias; } +bool UnlistFunctionScan::nextBuffer(thread_db* tdbb) const +{ + const auto request = tdbb->getRequest(); + const auto impure = request->getImpure(m_impure); + + if (impure->m_blob) + { + auto setStringToRecord = [&](const string& str) + { + Record* const record = request->req_rpb[m_stream].rpb_record; + + auto toDesc = m_format->fmt_desc.begin(); + const auto textType = toDesc->getTextType(); + + dsc fromDesc; + fromDesc.makeText(str.length(), textType, (UCHAR*)(IPTR)(str.c_str())); + assignParameter(tdbb, &fromDesc, toDesc, 0, record); + impure->m_recordBuffer->store(record); + }; + + MoveBuffer buffer; + const auto address = buffer.getBuffer(MAX_COLUMN_SIZE); + const auto length = impure->m_blob->BLB_get_data(tdbb, address, MAX_COLUMN_SIZE, false); + if (length > 0) + { + const std::string_view separatorView(impure->m_separatorStr->data(), + impure->m_separatorStr->length()); + std::string_view valueView(reinterpret_cast(address), length); + auto end = std::string_view::npos; + do + { + auto size = end = valueView.find(separatorView); + if (end == std::string_view::npos) + { + if (!valueView.empty()) + impure->m_resultStr->append(valueView.data(), valueView.length()); + + break; + } + + if (size > 0) + impure->m_resultStr->append(valueView.data(), size); + + valueView.remove_prefix(size + separatorView.length()); + + if (impure->m_resultStr->hasData()) + { + setStringToRecord(*impure->m_resultStr); + impure->m_resultStr->erase(); + } + } while (end != std::string_view::npos); + + return true; + } + + if (impure->m_blob->blb_flags & BLB_eof) + { + if (impure->m_resultStr->hasData()) + { + setStringToRecord(*impure->m_resultStr); + impure->m_resultStr->erase(); + return true; + } + } + } + + return false; +} From 42bbb27bd507d1d95750b3deba36a6aff10a76c0 Mon Sep 17 00:00:00 2001 From: Chudaykin Alex Date: Wed, 12 Mar 2025 11:09:56 +0300 Subject: [PATCH 9/9] Corrected remarks about variables belonging to the base class. corrected remarks on abstract class. --- src/jrd/recsrc/RecordSource.h | 15 ++-- src/jrd/recsrc/TableValueFunctionScan.cpp | 87 +++++++++++------------ 2 files changed, 52 insertions(+), 50 deletions(-) diff --git a/src/jrd/recsrc/RecordSource.h b/src/jrd/recsrc/RecordSource.h index 427291d1055..cfb4d685e2b 100644 --- a/src/jrd/recsrc/RecordSource.h +++ b/src/jrd/recsrc/RecordSource.h @@ -1468,15 +1468,12 @@ namespace Jrd struct Impure : public RecordSource::Impure { RecordBuffer* m_recordBuffer; - blb* m_blob; - Firebird::string* m_separatorStr; - Firebird::string* m_resultStr; }; public: TableValueFunctionScan(CompilerScratch* csb, StreamType stream, const Firebird::string& alias); - void close(thread_db* tdbb) const override; + bool refetchRecord(thread_db* tdbb) const override; WriteLockResult lockRecord(thread_db* tdbb) const override; void getLegacyPlan(thread_db* tdbb, Firebird::string& plan, unsigned level) const override; @@ -1486,7 +1483,7 @@ namespace Jrd void assignParameter(thread_db* tdbb, dsc* fromDesc, const dsc* toDesc, SSHORT toId, Record* record) const; - virtual bool nextBuffer(thread_db* tdbb) const; + virtual bool nextBuffer(thread_db* tdbb) const = 0; const Firebird::string m_alias; }; @@ -1500,11 +1497,19 @@ namespace Jrd UNLIST_INDEX_LAST = 2 }; + struct Impure : public TableValueFunctionScan::Impure + { + blb* m_blob; + Firebird::string* m_separatorStr; + Firebird::string* m_resultStr; + }; + public: UnlistFunctionScan(CompilerScratch* csb, StreamType stream, const Firebird::string& alias, ValueListNode* list); protected: + void close(thread_db* tdbb) const final; void internalOpen(thread_db* tdbb) const final; void internalGetPlan(thread_db* tdbb, PlanEntry& planEntry, unsigned level, bool recurse) const final; diff --git a/src/jrd/recsrc/TableValueFunctionScan.cpp b/src/jrd/recsrc/TableValueFunctionScan.cpp index 1d73df5dda5..ecf0354d2ad 100644 --- a/src/jrd/recsrc/TableValueFunctionScan.cpp +++ b/src/jrd/recsrc/TableValueFunctionScan.cpp @@ -38,48 +38,9 @@ TableValueFunctionScan::TableValueFunctionScan(CompilerScratch* csb, StreamType const string& alias) : RecordStream(csb, stream), m_alias(csb->csb_pool, alias) { - m_impure = csb->allocImpure(); m_cardinality = DEFAULT_CARDINALITY; } -void TableValueFunctionScan::close(thread_db* tdbb) const -{ - const auto request = tdbb->getRequest(); - - invalidateRecords(request); - - const auto impure = request->getImpure(m_impure); - - if (impure->irsb_flags & irsb_open) - { - impure->irsb_flags &= ~irsb_open; - - if (impure->m_recordBuffer) - { - delete impure->m_recordBuffer; - impure->m_recordBuffer = nullptr; - } - - if (impure->m_blob) - { - impure->m_blob->BLB_close(tdbb); - impure->m_blob = nullptr; - } - - if (impure->m_separatorStr) - { - delete impure->m_separatorStr; - impure->m_separatorStr = nullptr; - } - - if (impure->m_resultStr) - { - delete impure->m_resultStr; - impure->m_resultStr = nullptr; - } - } -} - bool TableValueFunctionScan::internalGetRecord(thread_db* tdbb) const { JRD_reschedule(tdbb); @@ -107,7 +68,7 @@ bool TableValueFunctionScan::internalGetRecord(thread_db* tdbb) const continue; } return true; - } while (1); + } while (true); } bool TableValueFunctionScan::refetchRecord(thread_db* /*tdbb*/) const @@ -154,15 +115,49 @@ void TableValueFunctionScan::assignParameter(thread_db* tdbb, dsc* fromDesc, con memcpy(toDescValue.dsc_address, fromDesc->dsc_address, fromDesc->dsc_length); } -bool TableValueFunctionScan::nextBuffer(thread_db* /*tdbb*/) const -{ - return false; -} - UnlistFunctionScan::UnlistFunctionScan(CompilerScratch* csb, StreamType stream, const string& alias, ValueListNode* list) : TableValueFunctionScan(csb, stream, alias), m_inputList(list) { + m_impure = csb->allocImpure(); +} + +void UnlistFunctionScan::close(thread_db* tdbb) const +{ + const auto request = tdbb->getRequest(); + + invalidateRecords(request); + + const auto impure = request->getImpure(m_impure); + + if (impure->irsb_flags & irsb_open) + { + impure->irsb_flags &= ~irsb_open; + + if (impure->m_recordBuffer) + { + delete impure->m_recordBuffer; + impure->m_recordBuffer = nullptr; + } + + if (impure->m_blob) + { + impure->m_blob->BLB_close(tdbb); + impure->m_blob = nullptr; + } + + if (impure->m_separatorStr) + { + delete impure->m_separatorStr; + impure->m_separatorStr = nullptr; + } + + if (impure->m_resultStr) + { + delete impure->m_resultStr; + impure->m_resultStr = nullptr; + } + } } void UnlistFunctionScan::internalOpen(thread_db* tdbb) const @@ -194,6 +189,8 @@ void UnlistFunctionScan::internalOpen(thread_db* tdbb) const const auto impure = request->getImpure(m_impure); impure->irsb_flags |= irsb_open; impure->m_recordBuffer = FB_NEW_POOL(pool) RecordBuffer(pool, m_format); + impure->m_blob = nullptr; + impure->m_resultStr = nullptr; Record* const record = VIO_record(tdbb, rpb, m_format, &pool);