From 46c836bea0b2fa3695c6c2171af88b22b561cc23 Mon Sep 17 00:00:00 2001 From: Simonov Denis Date: Wed, 29 Oct 2025 19:34:15 +0300 Subject: [PATCH 1/9] GENERATE_SERIES function --- doc/sql.extensions/README.generate_series.md | 51 +++++++ src/common/ParserTokens.h | 1 + src/dsql/parse-conflicts.txt | 2 +- src/dsql/parse.y | 32 ++++ src/include/firebird/impl/blr.h | 1 + src/include/firebird/impl/msg/jrd.h | 1 + src/include/gen/Firebird.pas | 1 + src/jrd/RecordSourceNodes.cpp | 79 ++++++++++ src/jrd/RecordSourceNodes.h | 18 +++ src/jrd/recsrc/RecordSource.h | 35 +++++ src/jrd/recsrc/TableValueFunctionScan.cpp | 149 +++++++++++++++++++ 11 files changed, 369 insertions(+), 1 deletion(-) create mode 100644 doc/sql.extensions/README.generate_series.md diff --git a/doc/sql.extensions/README.generate_series.md b/doc/sql.extensions/README.generate_series.md new file mode 100644 index 00000000000..f80d0b1fa45 --- /dev/null +++ b/doc/sql.extensions/README.generate_series.md @@ -0,0 +1,51 @@ +# GENERATE_SERIES function + +The `GENERATE_SERIES` function creates a series of numbers within a specified interval. +The interval and the step between series values ​​are defined by the user. + +## Syntax + +``` + ::= + GENERATE_SERIES(, [, ]) [AS] [ ( ) ] +``` + +## Arguments + +* `start` - The first value in the interval. `start` is specified as a variable, a literal, or a scalar expression of type +`smallint`, `integer` or `bigint`. + +* `finish` - The last value in the interval. `finish` is specified as a variable, a literal, or a scalar expression of +type `smallint`, `integer` or `bigint`. The series stops once the last generated step value exceeds the `finish` value. + +* `step` - Indicates the number of values to increment or decrement between steps in the series. `step` is an expression +of type `smallint`, `integer` or `bigint`. `step` can be either negative or positive, but can't be zero (0). This +argument is optional. The default value for `step` is 1. + +## Returning type + +The function `GENERATE_SERIES` returns a set with a `BIGINT` column. + +## Rules + +* If `start > finish` and a negative `step` value is specified, an empty set is returned. + +* If `start < finish` and a positive `step` value is specified, an empty set is returned. + +* If the `step` argument is zero, an error is thrown. + +## Examples + +``` +SELECT n +FROM GENERATE_SERIES(1, 3) AS S(n); + +SELECT n +FROM GENERATE_SERIES(3, 1, 1) AS S(n); + +SELECT + DATEADD(n MINUTE TO timestamp '2025-01-01 12:00') AS START_TIME, + DATEADD(n MINUTE TO timestamp '2025-01-01 12:00:59.9999') AS FINISH_TIME +FROM GENERATE_SERIES(0, 59) AS S(n); +``` + diff --git a/src/common/ParserTokens.h b/src/common/ParserTokens.h index 8a44da58a38..a74b37d5e6d 100644 --- a/src/common/ParserTokens.h +++ b/src/common/ParserTokens.h @@ -235,6 +235,7 @@ PARSER_TOKEN(TOK_FROM, "FROM", false) PARSER_TOKEN(TOK_FULL, "FULL", false) PARSER_TOKEN(TOK_FUNCTION, "FUNCTION", false) PARSER_TOKEN(TOK_GDSCODE, "GDSCODE", false) +PARSER_TOKEN(TOK_GENERATE_SERIES, "GENERATE_SERIES", true) PARSER_TOKEN(TOK_GENERATED, "GENERATED", true) PARSER_TOKEN(TOK_GENERATOR, "GENERATOR", true) PARSER_TOKEN(TOK_GEN_ID, "GEN_ID", true) diff --git a/src/dsql/parse-conflicts.txt b/src/dsql/parse-conflicts.txt index dea420bc00c..d18086526cc 100644 --- a/src/dsql/parse-conflicts.txt +++ b/src/dsql/parse-conflicts.txt @@ -1 +1 @@ -133 shift/reduce conflicts, 13 reduce/reduce conflicts. +134 shift/reduce conflicts, 13 reduce/reduce conflicts. diff --git a/src/dsql/parse.y b/src/dsql/parse.y index c8f2204af17..47e6bf608a7 100644 --- a/src/dsql/parse.y +++ b/src/dsql/parse.y @@ -710,6 +710,7 @@ using namespace Firebird; %token CURRENT_SCHEMA %token DOWNTO %token FORMAT +%token GENERATE_SERIES %token GREATEST %token LEAST %token LTRIM @@ -6798,6 +6799,8 @@ table_value_function table_value_function_clause : table_value_function_unlist { $$ = $1; } + | table_value_function_gen_series + { $$ = $1; } ; %type table_value_function_unlist @@ -6833,6 +6836,34 @@ table_value_function_correlation_name : as_noise symbol_item_alias_name { $$ = $2; } ; +%type table_value_function_gen_series +table_value_function_gen_series + : GENERATE_SERIES '(' table_value_function_gen_series_arg_list ')' + { + auto node = newNode(); + node->dsqlFlags |= RecordSourceNode::DFLAG_VALUE; + node->dsqlName = *$1; + node->inputList = $3; + node->dsqlField = nullptr; + $$ = node; + } + ; + +%type table_value_function_gen_series_arg_list +table_value_function_gen_series_arg_list + : value ',' value step_opt + { + $$ = newNode($1); + $$->add($3); + $$->add($4); + } + ; + +%type step_opt +step_opt + : /* nothing */ { $$ = MAKE_const_sint64(1, 0); } + | ',' value { $$ = $2; } + ; // other clauses in the select expression @@ -9967,6 +9998,7 @@ non_reserved_word | BIN_XOR_AGG | DOWNTO | FORMAT + | GENERATE_SERIES | OWNER | SEARCH_PATH | SCHEMA diff --git a/src/include/firebird/impl/blr.h b/src/include/firebird/impl/blr.h index 6c4c9573aae..bb7825e3d4a 100644 --- a/src/include/firebird/impl/blr.h +++ b/src/include/firebird/impl/blr.h @@ -508,6 +508,7 @@ // Table value function #define blr_table_value_fun (unsigned char) 229 #define blr_table_value_fun_unlist (unsigned char) 1 +#define blr_table_value_fun_gen_series (unsigned char) 2 #define blr_for_range (unsigned char) 230 #define blr_for_range_variable (unsigned char) 1 diff --git a/src/include/firebird/impl/msg/jrd.h b/src/include/firebird/impl/msg/jrd.h index cef871febd6..8c68ff17c3b 100644 --- a/src/include/firebird/impl/msg/jrd.h +++ b/src/include/firebird/impl/msg/jrd.h @@ -998,3 +998,4 @@ FB_IMPL_MSG(JRD, 995, missing_value_for_format_pattern, -901, "HY", "000", "Cann FB_IMPL_MSG(JRD, 996, invalid_name, -901, "HY", "000", "Invalid name: @1") FB_IMPL_MSG(JRD, 997, invalid_unqualified_name_list, -901, "HY", "000", "Invalid list of unqualified names: @1") FB_IMPL_MSG(JRD, 998, no_user_att_while_restore, -901, "HY", "000", "User attachments are not allowed for the database being restored") +FB_IMPL_MSG(JRD, 999, genseq_stepmustbe_nonzero, -833, "42", "000", "Argument STEP must be different than zero for function @1") diff --git a/src/include/gen/Firebird.pas b/src/include/gen/Firebird.pas index 15fb8074f2c..696ed6bc9cd 100644 --- a/src/include/gen/Firebird.pas +++ b/src/include/gen/Firebird.pas @@ -5844,6 +5844,7 @@ IProfilerStatsImpl = class(IProfilerStats) isc_invalid_name = 335545316; isc_invalid_unqualified_name_list = 335545317; isc_no_user_att_while_restore = 335545318; + isc_genseq_stepmustbe_nonzero = 335545319; isc_gfix_db_name = 335740929; isc_gfix_invalid_sw = 335740930; isc_gfix_incmp_sw = 335740932; diff --git a/src/jrd/RecordSourceNodes.cpp b/src/jrd/RecordSourceNodes.cpp index cd72088503e..f037ca5b48a 100644 --- a/src/jrd/RecordSourceNodes.cpp +++ b/src/jrd/RecordSourceNodes.cpp @@ -4109,6 +4109,10 @@ TableValueFunctionSourceNode* TableValueFunctionSourceNode::parseFunction(thread node = FB_NEW_POOL(pool) UnlistFunctionSourceNode(pool); break; + case blr_table_value_fun_gen_series: + node = FB_NEW_POOL(pool) GenSeriesFunctionSourceNode(pool); + break; + default: PAR_syntax_error(csb, "blr_table_value_fun"); } @@ -4161,6 +4165,8 @@ void TableValueFunctionSourceNode::genBlr(DsqlCompilerScratch* dsqlScratch) if (tableValueFunctionContext->funName == UnlistFunctionSourceNode::FUNC_NAME) dsqlScratch->appendUChar(blr_table_value_fun_unlist); + else if (tableValueFunctionContext->funName == GenSeriesFunctionSourceNode::FUNC_NAME) + dsqlScratch->appendUChar(blr_table_value_fun_gen_series); else fb_assert(false); @@ -4320,6 +4326,8 @@ void TableValueFunctionSourceNode::setDefaultNameField(DsqlCompilerScratch* /*ds fb_assert(false); } +//-------------------- + RecordSource* UnlistFunctionSourceNode::compile(thread_db* tdbb, Optimizer* opt, bool /*innerSubStream*/) { @@ -4378,6 +4386,77 @@ dsql_fld* UnlistFunctionSourceNode::makeField(DsqlCompilerScratch* dsqlScratch) //-------------------- +RecordSource* GenSeriesFunctionSourceNode::compile(thread_db* tdbb, Optimizer* opt, + bool /*innerSubStream*/) +{ + MemoryPool& pool = *tdbb->getDefaultPool(); + const auto csb = opt->getCompilerScratch(); + const auto alias = opt->makeAlias(stream); + + return FB_NEW_POOL(pool) GenSeriesFunctionScan(csb, stream, alias, inputList); +} + +dsql_fld* GenSeriesFunctionSourceNode::makeField(DsqlCompilerScratch* dsqlScratch) +{ + if (inputList) + inputList = Node::doDsqlPass(dsqlScratch, inputList, false); + + const auto startItem = inputList->items[0].getObject(); + startItem->setParameterType( + dsqlScratch, [](dsc* desc) { desc->makeInt64(0); }, false); + + const auto finishItem = inputList->items[1].getObject(); + finishItem->setParameterType( + dsqlScratch, [](dsc* desc) { desc->makeInt64(0); }, false); + + const auto stepItem = inputList->items[2].getObject(); + stepItem->setParameterType( + dsqlScratch, [](dsc* desc) { desc->makeInt64(0); }, false); + + dsc startDesc; + DsqlDescMaker::fromNode(dsqlScratch, &startDesc, startItem, true); + + dsc finishDesc; + DsqlDescMaker::fromNode(dsqlScratch, &finishDesc, finishItem, true); + + dsc stepDesc; + DsqlDescMaker::fromNode(dsqlScratch, &stepDesc, stepItem, true); + + // common scale + const auto scale = MIN(MIN(startDesc.dsc_scale, finishDesc.dsc_scale), stepDesc.dsc_scale); + + dsql_fld* field = dsqlField; + + if (!field) + { + const auto newField = FB_NEW_POOL(dsqlScratch->getPool()) dsql_fld(dsqlScratch->getPool()); + field = newField; + + dsc desc; + desc.makeInt64(scale); + 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(GenSeriesFunctionSourceNode::FUNC_NAME) + << Arg::Num(1) << Arg::Num(dsqlNameColumns.getCount())); + } + + field->fld_name = dsqlNameColumns[0]; + } + + field->resolve(dsqlScratch); + return field; +} + +//-------------------- + static RecordSourceNode* dsqlPassRelProc(DsqlCompilerScratch* dsqlScratch, RecordSourceNode* source) { diff --git a/src/jrd/RecordSourceNodes.h b/src/jrd/RecordSourceNodes.h index 7630539a20c..cd0978d900b 100644 --- a/src/jrd/RecordSourceNodes.h +++ b/src/jrd/RecordSourceNodes.h @@ -1081,6 +1081,24 @@ class UnlistFunctionSourceNode : public TableValueFunctionSourceNode } }; +class GenSeriesFunctionSourceNode : public TableValueFunctionSourceNode +{ +public: + explicit GenSeriesFunctionSourceNode(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 = "GENERATE_SERIES"; + + const char* getName() const override + { + return FUNC_NAME; + } +}; + } // namespace Jrd #endif // JRD_RECORD_SOURCE_NODES_H diff --git a/src/jrd/recsrc/RecordSource.h b/src/jrd/recsrc/RecordSource.h index bc5df3dfed2..47a35aed8a3 100644 --- a/src/jrd/recsrc/RecordSource.h +++ b/src/jrd/recsrc/RecordSource.h @@ -1615,6 +1615,41 @@ namespace Jrd NestConst m_inputList; }; + class GenSeriesFunctionScan final : public TableValueFunctionScan + { + enum GenSeriesTypeItemIndex : unsigned + { + GEN_SERIES_INDEX_START = 0, + GEN_SERIES_INDEX_FINISH = 1, + GEN_SERIES_INDEX_STEP = 2, + GEN_SERIES_INDEX_LAST = 3 + }; + + struct Impure : public TableValueFunctionScan::Impure + { + SINT64 m_start; + SINT64 m_finish; + SINT64 m_step; + SINT64 m_result; + SCHAR m_scale; + }; + + public: + GenSeriesFunctionScan(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; + + bool nextBuffer(thread_db* tdbb) 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 index 326caf89b6e..4e18cd151a1 100644 --- a/src/jrd/recsrc/TableValueFunctionScan.cpp +++ b/src/jrd/recsrc/TableValueFunctionScan.cpp @@ -118,6 +118,8 @@ void TableValueFunctionScan::assignParameter(thread_db* tdbb, dsc* fromDesc, con 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) @@ -346,3 +348,150 @@ bool UnlistFunctionScan::nextBuffer(thread_db* tdbb) const return false; } + +//-------------------- + +GenSeriesFunctionScan::GenSeriesFunctionScan(CompilerScratch* csb, StreamType stream, const string& alias, + ValueListNode* list) + : TableValueFunctionScan(csb, stream, alias), m_inputList(list) +{ + m_impure = csb->allocImpure(); +} + +void GenSeriesFunctionScan::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; + } + } +} + + +void GenSeriesFunctionScan::internalOpen(thread_db* tdbb) const +{ + const auto request = tdbb->getRequest(); + const auto rpb = &request->req_rpb[m_stream]; + MemoryPool& pool = *tdbb->getDefaultPool(); + + rpb->rpb_number.setValue(BOF_NUMBER); + + fb_assert(m_inputList->items.getCount() >= GEN_SERIES_INDEX_LAST); + + auto startItem = m_inputList->items[GEN_SERIES_INDEX_START]; + const auto startDesc = EVL_expr(tdbb, request, startItem); + if (startDesc == nullptr) + { + rpb->rpb_number.setValid(false); + return; + } + + auto finishItem = m_inputList->items[GEN_SERIES_INDEX_FINISH]; + const auto finishDesc = EVL_expr(tdbb, request, finishItem); + if (finishDesc == nullptr) + { + rpb->rpb_number.setValid(false); + return; + } + + auto stepItem = m_inputList->items[GEN_SERIES_INDEX_STEP]; + const auto stepDesc = EVL_expr(tdbb, request, stepItem); + if (stepDesc == nullptr) + { + rpb->rpb_number.setValid(false); + return; + } + + // common scale + const auto scale = MIN(MIN(startDesc->dsc_scale, finishDesc->dsc_scale), stepDesc->dsc_scale); + + const auto start = MOV_get_int64(tdbb, startDesc, scale); + const auto finish = MOV_get_int64(tdbb, finishDesc, scale); + const auto step = MOV_get_int64(tdbb, stepDesc, scale); + + // validate parameter value + if (((step > 0) && (start > finish)) || + ((step < 0) && (start < finish))) + { + rpb->rpb_number.setValid(false); + return; + } + + if (step == 0) + status_exception::raise(Arg::Gds(isc_genseq_stepmustbe_nonzero) << Arg::Str(m_name)); + + + 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_start = start; + impure->m_finish = finish; + impure->m_step = step; + impure->m_result = start; + impure->m_scale = scale; + + + Record* const record = VIO_record(tdbb, rpb, m_format, &pool); + + auto toDesc = m_format->fmt_desc.begin(); + + dsc fromDesc; + fromDesc.makeInt64(scale, &impure->m_result); + + assignParameter(tdbb, &fromDesc, toDesc, 0, record); + impure->m_recordBuffer->store(record); +} + +void GenSeriesFunctionScan::internalGetPlan(thread_db* tdbb, PlanEntry& planEntry, unsigned /*level*/, + bool /*recurse*/) const +{ + planEntry.className = "FunctionScan"; + + planEntry.lines.add().text = "Function " + + printName(tdbb, m_name.toQuotedString(), m_alias) + " Scan"; + + printOptInfo(planEntry.lines); + + if (m_alias.hasData()) + planEntry.alias = m_alias; +} + +bool GenSeriesFunctionScan::nextBuffer(thread_db* tdbb) const +{ + const auto request = tdbb->getRequest(); + const auto impure = request->getImpure(m_impure); + + auto setIntToRecord = [&](SINT64 i) + { + Record* const record = request->req_rpb[m_stream].rpb_record; + + auto toDesc = m_format->fmt_desc.begin(); + + dsc fromDesc; + fromDesc.makeInt64(impure->m_scale, &i); + assignParameter(tdbb, &fromDesc, toDesc, 0, record); + impure->m_recordBuffer->store(record); + }; + + impure->m_result += impure->m_step; + + if (((impure->m_step > 0) && (impure->m_result <= impure->m_finish)) || + ((impure->m_step < 0) && (impure->m_result >= impure->m_finish))) + { + setIntToRecord(impure->m_result); + return true; + } + + return false; +} From e2c86bd005dc272f7353df9bcb0b8ecde85ae12d Mon Sep 17 00:00:00 2001 From: Simonov Denis Date: Thu, 30 Oct 2025 20:02:59 +0300 Subject: [PATCH 2/9] don't use temp space --- doc/sql.extensions/README.generate_series.md | 15 ++++--- src/jrd/recsrc/RecordSource.h | 2 + src/jrd/recsrc/TableValueFunctionScan.cpp | 46 ++++++++++++++------ 3 files changed, 45 insertions(+), 18 deletions(-) diff --git a/doc/sql.extensions/README.generate_series.md b/doc/sql.extensions/README.generate_series.md index f80d0b1fa45..e7e9edd96b8 100644 --- a/doc/sql.extensions/README.generate_series.md +++ b/doc/sql.extensions/README.generate_series.md @@ -13,18 +13,20 @@ The interval and the step between series values ​​are defined by the user. ## Arguments * `start` - The first value in the interval. `start` is specified as a variable, a literal, or a scalar expression of type -`smallint`, `integer` or `bigint`. +`smallint`, `integer`, `bigint` or `numeric(18, x)`. * `finish` - The last value in the interval. `finish` is specified as a variable, a literal, or a scalar expression of -type `smallint`, `integer` or `bigint`. The series stops once the last generated step value exceeds the `finish` value. +type `smallint`, `integer`, `bigint` or `numeric(18, x)`. The series stops once the last generated step value exceeds +the `finish` value. * `step` - Indicates the number of values to increment or decrement between steps in the series. `step` is an expression -of type `smallint`, `integer` or `bigint`. `step` can be either negative or positive, but can't be zero (0). This +of type `smallint`, `integer`, `bigint` or `numeric(18, x)`. `step` can be either negative or positive, but can't be zero (0). This argument is optional. The default value for `step` is 1. ## Returning type -The function `GENERATE_SERIES` returns a set with a `BIGINT` column. +The function `GENERATE_SERIES` returns a set with `BIGINT` or `NUMERIC(18, x)` column, where the scale is +determined by the maximum of the scales of the function arguments. ## Rules @@ -41,7 +43,10 @@ SELECT n FROM GENERATE_SERIES(1, 3) AS S(n); SELECT n -FROM GENERATE_SERIES(3, 1, 1) AS S(n); +FROM GENERATE_SERIES(3, 1, -1) AS S(n); + +SELECT n +FROM GENERATE_SERIES(0, 9.9, 0.1) AS S(n); SELECT DATEADD(n MINUTE TO timestamp '2025-01-01 12:00') AS START_TIME, diff --git a/src/jrd/recsrc/RecordSource.h b/src/jrd/recsrc/RecordSource.h index 47a35aed8a3..6dec0c3283a 100644 --- a/src/jrd/recsrc/RecordSource.h +++ b/src/jrd/recsrc/RecordSource.h @@ -1632,6 +1632,7 @@ namespace Jrd SINT64 m_step; SINT64 m_result; SCHAR m_scale; + bool m_recordExists; }; public: @@ -1643,6 +1644,7 @@ namespace Jrd void internalOpen(thread_db* tdbb) const final; void internalGetPlan(thread_db* tdbb, PlanEntry& planEntry, unsigned level, bool recurse) const final; + bool internalGetRecord(thread_db* tdbb) const final; bool nextBuffer(thread_db* tdbb) const final; diff --git a/src/jrd/recsrc/TableValueFunctionScan.cpp b/src/jrd/recsrc/TableValueFunctionScan.cpp index 4e18cd151a1..e8867c8f277 100644 --- a/src/jrd/recsrc/TableValueFunctionScan.cpp +++ b/src/jrd/recsrc/TableValueFunctionScan.cpp @@ -367,15 +367,7 @@ void GenSeriesFunctionScan::close(thread_db* tdbb) const 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; - } - } } @@ -431,16 +423,15 @@ void GenSeriesFunctionScan::internalOpen(thread_db* tdbb) const if (step == 0) status_exception::raise(Arg::Gds(isc_genseq_stepmustbe_nonzero) << Arg::Str(m_name)); - 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_recordBuffer = nullptr; impure->m_start = start; impure->m_finish = finish; impure->m_step = step; impure->m_result = start; impure->m_scale = scale; - + impure->m_recordExists = true; Record* const record = VIO_record(tdbb, rpb, m_format, &pool); @@ -450,7 +441,6 @@ void GenSeriesFunctionScan::internalOpen(thread_db* tdbb) const fromDesc.makeInt64(scale, &impure->m_result); assignParameter(tdbb, &fromDesc, toDesc, 0, record); - impure->m_recordBuffer->store(record); } void GenSeriesFunctionScan::internalGetPlan(thread_db* tdbb, PlanEntry& planEntry, unsigned /*level*/, @@ -467,6 +457,36 @@ void GenSeriesFunctionScan::internalGetPlan(thread_db* tdbb, PlanEntry& planEntr planEntry.alias = m_alias; } +bool GenSeriesFunctionScan::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(); + + do + { + if (impure->m_recordExists) + { + impure->m_recordExists = false; + rpb->rpb_number.setValid(true); + return true; + } + } while (nextBuffer(tdbb)); + + rpb->rpb_number.setValid(false); + return false; +} + bool GenSeriesFunctionScan::nextBuffer(thread_db* tdbb) const { const auto request = tdbb->getRequest(); @@ -481,7 +501,7 @@ bool GenSeriesFunctionScan::nextBuffer(thread_db* tdbb) const dsc fromDesc; fromDesc.makeInt64(impure->m_scale, &i); assignParameter(tdbb, &fromDesc, toDesc, 0, record); - impure->m_recordBuffer->store(record); + impure->m_recordExists = true; }; impure->m_result += impure->m_step; From 8f660b8bacd1fc47110343270f4a4c1dcfe8c3e2 Mon Sep 17 00:00:00 2001 From: Simonov Denis Date: Thu, 30 Oct 2025 21:16:21 +0300 Subject: [PATCH 3/9] fixed after hvlad review --- src/jrd/RecordSourceNodes.cpp | 2 +- src/jrd/RecordSourceNodes.h | 2 +- src/jrd/recsrc/TableValueFunctionScan.cpp | 14 +++++--------- 3 files changed, 7 insertions(+), 11 deletions(-) diff --git a/src/jrd/RecordSourceNodes.cpp b/src/jrd/RecordSourceNodes.cpp index f037ca5b48a..de8d94eb911 100644 --- a/src/jrd/RecordSourceNodes.cpp +++ b/src/jrd/RecordSourceNodes.cpp @@ -4444,7 +4444,7 @@ dsql_fld* GenSeriesFunctionSourceNode::makeField(DsqlCompilerScratch* dsqlScratc { 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(GenSeriesFunctionSourceNode::FUNC_NAME) + << Arg::Str(getName()) << Arg::Num(1) << Arg::Num(dsqlNameColumns.getCount())); } diff --git a/src/jrd/RecordSourceNodes.h b/src/jrd/RecordSourceNodes.h index cd0978d900b..b26ba3467fa 100644 --- a/src/jrd/RecordSourceNodes.h +++ b/src/jrd/RecordSourceNodes.h @@ -1093,7 +1093,7 @@ class GenSeriesFunctionSourceNode : public TableValueFunctionSourceNode static constexpr char const* FUNC_NAME = "GENERATE_SERIES"; - const char* getName() const override + const char* getName() const final { return FUNC_NAME; } diff --git a/src/jrd/recsrc/TableValueFunctionScan.cpp b/src/jrd/recsrc/TableValueFunctionScan.cpp index e8867c8f277..f31c1c7d18b 100644 --- a/src/jrd/recsrc/TableValueFunctionScan.cpp +++ b/src/jrd/recsrc/TableValueFunctionScan.cpp @@ -492,24 +492,20 @@ bool GenSeriesFunctionScan::nextBuffer(thread_db* tdbb) const const auto request = tdbb->getRequest(); const auto impure = request->getImpure(m_impure); - auto setIntToRecord = [&](SINT64 i) + impure->m_result += impure->m_step; + + if (((impure->m_step > 0) && (impure->m_result <= impure->m_finish)) || + ((impure->m_step < 0) && (impure->m_result >= impure->m_finish))) { Record* const record = request->req_rpb[m_stream].rpb_record; auto toDesc = m_format->fmt_desc.begin(); dsc fromDesc; - fromDesc.makeInt64(impure->m_scale, &i); + fromDesc.makeInt64(impure->m_scale, &impure->m_result); assignParameter(tdbb, &fromDesc, toDesc, 0, record); impure->m_recordExists = true; - }; - impure->m_result += impure->m_step; - - if (((impure->m_step > 0) && (impure->m_result <= impure->m_finish)) || - ((impure->m_step < 0) && (impure->m_result >= impure->m_finish))) - { - setIntToRecord(impure->m_result); return true; } From 328eb398d6d61e385ffe2495def04d872294be4d Mon Sep 17 00:00:00 2001 From: Simonov Denis Date: Fri, 31 Oct 2025 17:26:48 +0300 Subject: [PATCH 4/9] More transparent code and other fixes according to @asfernandes. --- src/jrd/RecordSourceNodes.cpp | 7 +++--- src/jrd/RecordSourceNodes.h | 11 +++++---- src/jrd/recsrc/RecordSource.h | 13 +++++------ src/jrd/recsrc/TableValueFunctionScan.cpp | 28 ++++++----------------- 4 files changed, 22 insertions(+), 37 deletions(-) diff --git a/src/jrd/RecordSourceNodes.cpp b/src/jrd/RecordSourceNodes.cpp index de8d94eb911..8457b3b1317 100644 --- a/src/jrd/RecordSourceNodes.cpp +++ b/src/jrd/RecordSourceNodes.cpp @@ -4429,13 +4429,12 @@ dsql_fld* GenSeriesFunctionSourceNode::makeField(DsqlCompilerScratch* dsqlScratc if (!field) { - const auto newField = FB_NEW_POOL(dsqlScratch->getPool()) dsql_fld(dsqlScratch->getPool()); - field = newField; + field = FB_NEW_POOL(dsqlScratch->getPool()) dsql_fld(dsqlScratch->getPool()); dsc desc; desc.makeInt64(scale); - MAKE_field(newField, &desc); - newField->fld_id = 0; + MAKE_field(field, &desc); + field->fld_id = 0; } if (dsqlNameColumns.hasData()) diff --git a/src/jrd/RecordSourceNodes.h b/src/jrd/RecordSourceNodes.h index b26ba3467fa..9e78e5415c0 100644 --- a/src/jrd/RecordSourceNodes.h +++ b/src/jrd/RecordSourceNodes.h @@ -1081,19 +1081,20 @@ class UnlistFunctionSourceNode : public TableValueFunctionSourceNode } }; -class GenSeriesFunctionSourceNode : public TableValueFunctionSourceNode +class GenSeriesFunctionSourceNode final : public TableValueFunctionSourceNode { public: - explicit GenSeriesFunctionSourceNode(MemoryPool& pool) : TableValueFunctionSourceNode(pool) + explicit GenSeriesFunctionSourceNode(MemoryPool& pool) + : TableValueFunctionSourceNode(pool) { } - RecordSource* compile(thread_db* tdbb, Optimizer* opt, bool innerSubStream) final; - dsql_fld* makeField(DsqlCompilerScratch* dsqlScratch) final; + RecordSource* compile(thread_db* tdbb, Optimizer* opt, bool innerSubStream) override; + dsql_fld* makeField(DsqlCompilerScratch* dsqlScratch) override; static constexpr char const* FUNC_NAME = "GENERATE_SERIES"; - const char* getName() const final + const char* getName() const override { return FUNC_NAME; } diff --git a/src/jrd/recsrc/RecordSource.h b/src/jrd/recsrc/RecordSource.h index 6dec0c3283a..69b4c93a5af 100644 --- a/src/jrd/recsrc/RecordSource.h +++ b/src/jrd/recsrc/RecordSource.h @@ -1617,7 +1617,7 @@ namespace Jrd class GenSeriesFunctionScan final : public TableValueFunctionScan { - enum GenSeriesTypeItemIndex : unsigned + enum GenSeriesTypeItemIndex : UCHAR { GEN_SERIES_INDEX_START = 0, GEN_SERIES_INDEX_FINISH = 1, @@ -1632,7 +1632,6 @@ namespace Jrd SINT64 m_step; SINT64 m_result; SCHAR m_scale; - bool m_recordExists; }; public: @@ -1640,13 +1639,13 @@ namespace Jrd ValueListNode* list); protected: - void close(thread_db* tdbb) const final; - void internalOpen(thread_db* tdbb) const final; + void close(thread_db* tdbb) const override; + void internalOpen(thread_db* tdbb) const override; void internalGetPlan(thread_db* tdbb, PlanEntry& planEntry, unsigned level, - bool recurse) const final; - bool internalGetRecord(thread_db* tdbb) const final; + bool recurse) const override; + bool internalGetRecord(thread_db* tdbb) const override; - bool nextBuffer(thread_db* tdbb) const final; + bool nextBuffer(thread_db* tdbb) const override; private: NestConst m_inputList; diff --git a/src/jrd/recsrc/TableValueFunctionScan.cpp b/src/jrd/recsrc/TableValueFunctionScan.cpp index f31c1c7d18b..ab391730abb 100644 --- a/src/jrd/recsrc/TableValueFunctionScan.cpp +++ b/src/jrd/recsrc/TableValueFunctionScan.cpp @@ -431,16 +431,8 @@ void GenSeriesFunctionScan::internalOpen(thread_db* tdbb) const impure->m_step = step; impure->m_result = start; impure->m_scale = scale; - impure->m_recordExists = true; - Record* const record = VIO_record(tdbb, rpb, m_format, &pool); - - auto toDesc = m_format->fmt_desc.begin(); - - dsc fromDesc; - fromDesc.makeInt64(scale, &impure->m_result); - - assignParameter(tdbb, &fromDesc, toDesc, 0, record); + VIO_record(tdbb, rpb, m_format, &pool); } void GenSeriesFunctionScan::internalGetPlan(thread_db* tdbb, PlanEntry& planEntry, unsigned /*level*/, @@ -473,15 +465,10 @@ bool GenSeriesFunctionScan::internalGetRecord(thread_db* tdbb) const rpb->rpb_number.increment(); - do - { - if (impure->m_recordExists) - { - impure->m_recordExists = false; - rpb->rpb_number.setValid(true); - return true; - } - } while (nextBuffer(tdbb)); + if (nextBuffer(tdbb)) { + rpb->rpb_number.setValid(true); + return true; + } rpb->rpb_number.setValid(false); return false; @@ -492,8 +479,6 @@ bool GenSeriesFunctionScan::nextBuffer(thread_db* tdbb) const const auto request = tdbb->getRequest(); const auto impure = request->getImpure(m_impure); - impure->m_result += impure->m_step; - if (((impure->m_step > 0) && (impure->m_result <= impure->m_finish)) || ((impure->m_step < 0) && (impure->m_result >= impure->m_finish))) { @@ -504,7 +489,8 @@ bool GenSeriesFunctionScan::nextBuffer(thread_db* tdbb) const dsc fromDesc; fromDesc.makeInt64(impure->m_scale, &impure->m_result); assignParameter(tdbb, &fromDesc, toDesc, 0, record); - impure->m_recordExists = true; + + impure->m_result += impure->m_step; return true; } From 84d9f20be330a169daac5c29bc1544e88979cdfd Mon Sep 17 00:00:00 2001 From: Simonov Denis Date: Fri, 31 Oct 2025 19:27:00 +0300 Subject: [PATCH 5/9] support INT128 datatype --- doc/sql.extensions/README.generate_series.md | 14 +-- src/include/firebird/impl/msg/jrd.h | 1 + src/include/gen/Firebird.pas | 1 + src/jrd/RecordSourceNodes.cpp | 18 ++- src/jrd/recsrc/RecordSource.h | 10 +- src/jrd/recsrc/TableValueFunctionScan.cpp | 117 ++++++++++++++----- 6 files changed, 120 insertions(+), 41 deletions(-) diff --git a/doc/sql.extensions/README.generate_series.md b/doc/sql.extensions/README.generate_series.md index e7e9edd96b8..01db7638d53 100644 --- a/doc/sql.extensions/README.generate_series.md +++ b/doc/sql.extensions/README.generate_series.md @@ -13,20 +13,20 @@ The interval and the step between series values ​​are defined by the user. ## Arguments * `start` - The first value in the interval. `start` is specified as a variable, a literal, or a scalar expression of type -`smallint`, `integer`, `bigint` or `numeric(18, x)`. +`SMALLINT`, `INTEGER`, `BIGINT`, `INT128` or `NUMERIC/DECIMAL`. * `finish` - The last value in the interval. `finish` is specified as a variable, a literal, or a scalar expression of -type `smallint`, `integer`, `bigint` or `numeric(18, x)`. The series stops once the last generated step value exceeds -the `finish` value. +type `SMALLINT`, `INTEGER`, `BIGINT`, `INT128` or `NUMERIC/DECIMAL`. The series stops once the last generated step value +exceeds the `finish` value. * `step` - Indicates the number of values to increment or decrement between steps in the series. `step` is an expression -of type `smallint`, `integer`, `bigint` or `numeric(18, x)`. `step` can be either negative or positive, but can't be zero (0). This -argument is optional. The default value for `step` is 1. +of type `SMALLINT`, `INTEGER`, `BIGINT`, `INT128` or `NUMERIC/DECIMAL`. +`step` can be either negative or positive, but can't be zero (0). This argument is optional. The default value for `step` is 1. ## Returning type -The function `GENERATE_SERIES` returns a set with `BIGINT` or `NUMERIC(18, x)` column, where the scale is -determined by the maximum of the scales of the function arguments. +The function `GENERATE_SERIES` returns a set with `BIGINT`, `INT128` or `NUMERIC(18, x)/NUMERIC(38, x)` column, +where the scale is determined by the maximum of the scales of the function arguments. ## Rules diff --git a/src/include/firebird/impl/msg/jrd.h b/src/include/firebird/impl/msg/jrd.h index 8c68ff17c3b..7ba26ca0ac2 100644 --- a/src/include/firebird/impl/msg/jrd.h +++ b/src/include/firebird/impl/msg/jrd.h @@ -999,3 +999,4 @@ FB_IMPL_MSG(JRD, 996, invalid_name, -901, "HY", "000", "Invalid name: @1") FB_IMPL_MSG(JRD, 997, invalid_unqualified_name_list, -901, "HY", "000", "Invalid list of unqualified names: @1") FB_IMPL_MSG(JRD, 998, no_user_att_while_restore, -901, "HY", "000", "User attachments are not allowed for the database being restored") FB_IMPL_MSG(JRD, 999, genseq_stepmustbe_nonzero, -833, "42", "000", "Argument STEP must be different than zero for function @1") +FB_IMPL_MSG(JRD, 1000, argmustbe_exact_function, -833, "42", "000", "Arguments for @1 function must be exact numeric types") diff --git a/src/include/gen/Firebird.pas b/src/include/gen/Firebird.pas index 696ed6bc9cd..0ff161ae362 100644 --- a/src/include/gen/Firebird.pas +++ b/src/include/gen/Firebird.pas @@ -5845,6 +5845,7 @@ IProfilerStatsImpl = class(IProfilerStats) isc_invalid_unqualified_name_list = 335545317; isc_no_user_att_while_restore = 335545318; isc_genseq_stepmustbe_nonzero = 335545319; + isc_argmustbe_exact_function = 335545320; isc_gfix_db_name = 335740929; isc_gfix_invalid_sw = 335740930; isc_gfix_incmp_sw = 335740932; diff --git a/src/jrd/RecordSourceNodes.cpp b/src/jrd/RecordSourceNodes.cpp index 8457b3b1317..83584f3f7e4 100644 --- a/src/jrd/RecordSourceNodes.cpp +++ b/src/jrd/RecordSourceNodes.cpp @@ -4415,15 +4415,26 @@ dsql_fld* GenSeriesFunctionSourceNode::makeField(DsqlCompilerScratch* dsqlScratc dsc startDesc; DsqlDescMaker::fromNode(dsqlScratch, &startDesc, startItem, true); + if (!startDesc.isExact() && !startDesc.isNull()) { + status_exception::raise(Arg::Gds(isc_argmustbe_exact_function) << Arg::Str(getName())); + } dsc finishDesc; DsqlDescMaker::fromNode(dsqlScratch, &finishDesc, finishItem, true); + if (!finishDesc.isExact() && !finishDesc.isNull()) { + status_exception::raise(Arg::Gds(isc_argmustbe_exact_function) << Arg::Str(getName())); + } dsc stepDesc; DsqlDescMaker::fromNode(dsqlScratch, &stepDesc, stepItem, true); + if (!stepDesc.isExact() && !stepDesc.isNull()) { + status_exception::raise(Arg::Gds(isc_argmustbe_exact_function) << Arg::Str(getName())); + } // common scale const auto scale = MIN(MIN(startDesc.dsc_scale, finishDesc.dsc_scale), stepDesc.dsc_scale); + // common type + const auto dtype = MAX(MAX(startDesc.dsc_dtype, finishDesc.dsc_dtype), stepDesc.dsc_dtype); dsql_fld* field = dsqlField; @@ -4432,7 +4443,12 @@ dsql_fld* GenSeriesFunctionSourceNode::makeField(DsqlCompilerScratch* dsqlScratc field = FB_NEW_POOL(dsqlScratch->getPool()) dsql_fld(dsqlScratch->getPool()); dsc desc; - desc.makeInt64(scale); + if (dtype == dtype_int128) { + desc.makeInt128(scale); + } + else { + desc.makeInt64(scale); + } MAKE_field(field, &desc); field->fld_id = 0; } diff --git a/src/jrd/recsrc/RecordSource.h b/src/jrd/recsrc/RecordSource.h index 69b4c93a5af..028480fd54f 100644 --- a/src/jrd/recsrc/RecordSource.h +++ b/src/jrd/recsrc/RecordSource.h @@ -24,6 +24,7 @@ #define JRD_RECORD_SOURCE_H #include +#include #include "../common/classes/array.h" #include "../common/classes/objects_array.h" #include "../common/classes/NestConst.h" @@ -1627,10 +1628,11 @@ namespace Jrd struct Impure : public TableValueFunctionScan::Impure { - SINT64 m_start; - SINT64 m_finish; - SINT64 m_step; - SINT64 m_result; + std::variant m_start; + std::variant m_finish; + std::variant m_step; + std::variant m_result; + UCHAR m_dtype; SCHAR m_scale; }; diff --git a/src/jrd/recsrc/TableValueFunctionScan.cpp b/src/jrd/recsrc/TableValueFunctionScan.cpp index ab391730abb..4664879c0d4 100644 --- a/src/jrd/recsrc/TableValueFunctionScan.cpp +++ b/src/jrd/recsrc/TableValueFunctionScan.cpp @@ -405,32 +405,60 @@ void GenSeriesFunctionScan::internalOpen(thread_db* tdbb) const return; } - // common scale - const auto scale = MIN(MIN(startDesc->dsc_scale, finishDesc->dsc_scale), stepDesc->dsc_scale); + const auto impure = request->getImpure(m_impure); + impure->m_recordBuffer = nullptr; - const auto start = MOV_get_int64(tdbb, startDesc, scale); - const auto finish = MOV_get_int64(tdbb, finishDesc, scale); - const auto step = MOV_get_int64(tdbb, stepDesc, scale); + // common scale + impure->m_scale = MIN(MIN(startDesc->dsc_scale, finishDesc->dsc_scale), stepDesc->dsc_scale); + // common type + impure->m_dtype = MAX(MAX(startDesc->dsc_dtype, finishDesc->dsc_dtype), stepDesc->dsc_dtype); - // validate parameter value - if (((step > 0) && (start > finish)) || - ((step < 0) && (start < finish))) + if (impure->m_dtype != dtype_int128) { - rpb->rpb_number.setValid(false); - return; + const auto start = MOV_get_int64(tdbb, startDesc, impure->m_scale); + const auto finish = MOV_get_int64(tdbb, finishDesc, impure->m_scale); + const auto step = MOV_get_int64(tdbb, stepDesc, impure->m_scale); + + if (step == 0) + status_exception::raise(Arg::Gds(isc_genseq_stepmustbe_nonzero) << Arg::Str(m_name)); + + // validate parameter value + if (((step > 0) && (start > finish)) || + ((step < 0) && (start < finish))) + { + rpb->rpb_number.setValid(false); + return; + } + + impure->m_start = start; + impure->m_finish = finish; + impure->m_step = step; } + else { + const auto start = MOV_get_int128(tdbb, startDesc, impure->m_scale); + const auto finish = MOV_get_int128(tdbb, finishDesc, impure->m_scale); + const auto step = MOV_get_int128(tdbb, stepDesc, impure->m_scale); - if (step == 0) - status_exception::raise(Arg::Gds(isc_genseq_stepmustbe_nonzero) << Arg::Str(m_name)); + if (step.sign() == 0) + status_exception::raise(Arg::Gds(isc_genseq_stepmustbe_nonzero) << Arg::Str(m_name)); - const auto impure = request->getImpure(m_impure); + // validate parameter value + if (((step.sign() > 0) && (start.compare(finish) > 0)) || + ((step.sign() < 0) && (start.compare(finish) < 0))) + { + rpb->rpb_number.setValid(false); + return; + } + + impure->m_start = start; + impure->m_finish = finish; + impure->m_step = step; + } + + impure->irsb_flags |= irsb_open; - impure->m_recordBuffer = nullptr; - impure->m_start = start; - impure->m_finish = finish; - impure->m_step = step; - impure->m_result = start; - impure->m_scale = scale; + impure->m_result = impure->m_start; + VIO_record(tdbb, rpb, m_format, &pool); } @@ -465,7 +493,8 @@ bool GenSeriesFunctionScan::internalGetRecord(thread_db* tdbb) const rpb->rpb_number.increment(); - if (nextBuffer(tdbb)) { + if (nextBuffer(tdbb)) + { rpb->rpb_number.setValid(true); return true; } @@ -479,20 +508,50 @@ bool GenSeriesFunctionScan::nextBuffer(thread_db* tdbb) const const auto request = tdbb->getRequest(); const auto impure = request->getImpure(m_impure); - if (((impure->m_step > 0) && (impure->m_result <= impure->m_finish)) || - ((impure->m_step < 0) && (impure->m_result >= impure->m_finish))) + if (impure->m_dtype != dtype_int128) { - Record* const record = request->req_rpb[m_stream].rpb_record; + auto result = std::get(impure->m_result); + const auto finish = std::get(impure->m_finish); + const auto step = std::get(impure->m_step); + + if (((step > 0) && (result <= finish)) || + ((step < 0) && (result >= finish))) + { + Record* const record = request->req_rpb[m_stream].rpb_record; - auto toDesc = m_format->fmt_desc.begin(); + auto toDesc = m_format->fmt_desc.begin(); - dsc fromDesc; - fromDesc.makeInt64(impure->m_scale, &impure->m_result); - assignParameter(tdbb, &fromDesc, toDesc, 0, record); + dsc fromDesc; + fromDesc.makeInt64(impure->m_scale, &result); + assignParameter(tdbb, &fromDesc, toDesc, 0, record); - impure->m_result += impure->m_step; + result += step; + impure->m_result = result; - return true; + return true; + } + } + else { + auto result = std::get(impure->m_result); + const auto finish = std::get(impure->m_finish); + const auto step = std::get(impure->m_step); + + if (((step.sign() > 0) && (result.compare(finish) <= 0)) || + ((step.sign() < 0) && (result.compare(finish) >= 0))) + { + Record* const record = request->req_rpb[m_stream].rpb_record; + + auto toDesc = m_format->fmt_desc.begin(); + + dsc fromDesc; + fromDesc.makeInt128(impure->m_scale, &result); + assignParameter(tdbb, &fromDesc, toDesc, 0, record); + + result = result.add(step); + impure->m_result = result; + + return true; + } } return false; From 232c0b1160af4d67994f2dbfdc9a678d95f6cc1c Mon Sep 17 00:00:00 2001 From: Simonov Denis Date: Mon, 3 Nov 2025 10:03:37 +0300 Subject: [PATCH 6/9] Use union instead sd::variant --- src/jrd/RecordSourceNodes.cpp | 16 ++++------ src/jrd/recsrc/RecordSource.h | 29 ++++++++++++++--- src/jrd/recsrc/TableValueFunctionScan.cpp | 38 +++++++++++------------ 3 files changed, 49 insertions(+), 34 deletions(-) diff --git a/src/jrd/RecordSourceNodes.cpp b/src/jrd/RecordSourceNodes.cpp index 83584f3f7e4..9eee2d4fd34 100644 --- a/src/jrd/RecordSourceNodes.cpp +++ b/src/jrd/RecordSourceNodes.cpp @@ -4415,21 +4415,18 @@ dsql_fld* GenSeriesFunctionSourceNode::makeField(DsqlCompilerScratch* dsqlScratc dsc startDesc; DsqlDescMaker::fromNode(dsqlScratch, &startDesc, startItem, true); - if (!startDesc.isExact() && !startDesc.isNull()) { + if (!startDesc.isExact() && !startDesc.isNull()) status_exception::raise(Arg::Gds(isc_argmustbe_exact_function) << Arg::Str(getName())); - } dsc finishDesc; DsqlDescMaker::fromNode(dsqlScratch, &finishDesc, finishItem, true); - if (!finishDesc.isExact() && !finishDesc.isNull()) { + if (!finishDesc.isExact() && !finishDesc.isNull()) status_exception::raise(Arg::Gds(isc_argmustbe_exact_function) << Arg::Str(getName())); - } dsc stepDesc; DsqlDescMaker::fromNode(dsqlScratch, &stepDesc, stepItem, true); - if (!stepDesc.isExact() && !stepDesc.isNull()) { + if (!stepDesc.isExact() && !stepDesc.isNull()) status_exception::raise(Arg::Gds(isc_argmustbe_exact_function) << Arg::Str(getName())); - } // common scale const auto scale = MIN(MIN(startDesc.dsc_scale, finishDesc.dsc_scale), stepDesc.dsc_scale); @@ -4443,12 +4440,11 @@ dsql_fld* GenSeriesFunctionSourceNode::makeField(DsqlCompilerScratch* dsqlScratc field = FB_NEW_POOL(dsqlScratch->getPool()) dsql_fld(dsqlScratch->getPool()); dsc desc; - if (dtype == dtype_int128) { + if (dtype == dtype_int128) desc.makeInt128(scale); - } - else { + else desc.makeInt64(scale); - } + MAKE_field(field, &desc); field->fld_id = 0; } diff --git a/src/jrd/recsrc/RecordSource.h b/src/jrd/recsrc/RecordSource.h index 028480fd54f..ad8756327b2 100644 --- a/src/jrd/recsrc/RecordSource.h +++ b/src/jrd/recsrc/RecordSource.h @@ -24,7 +24,6 @@ #define JRD_RECORD_SOURCE_H #include -#include #include "../common/classes/array.h" #include "../common/classes/objects_array.h" #include "../common/classes/NestConst.h" @@ -1628,10 +1627,30 @@ namespace Jrd struct Impure : public TableValueFunctionScan::Impure { - std::variant m_start; - std::variant m_finish; - std::variant m_step; - std::variant m_result; + union + { + SINT64 vlu_int64; + Firebird::Int128 vlu_int128; + } m_start; + + union + { + SINT64 vlu_int64; + Firebird::Int128 vlu_int128; + } m_finish; + + union + { + SINT64 vlu_int64; + Firebird::Int128 vlu_int128; + } m_step; + + union + { + SINT64 vlu_int64; + Firebird::Int128 vlu_int128; + } m_result; + UCHAR m_dtype; SCHAR m_scale; }; diff --git a/src/jrd/recsrc/TableValueFunctionScan.cpp b/src/jrd/recsrc/TableValueFunctionScan.cpp index 4664879c0d4..b346fe99adf 100644 --- a/src/jrd/recsrc/TableValueFunctionScan.cpp +++ b/src/jrd/recsrc/TableValueFunctionScan.cpp @@ -430,11 +430,13 @@ void GenSeriesFunctionScan::internalOpen(thread_db* tdbb) const return; } - impure->m_start = start; - impure->m_finish = finish; - impure->m_step = step; + impure->m_start.vlu_int64 = start; + impure->m_finish.vlu_int64 = finish; + impure->m_step.vlu_int64 = step; + impure->m_result.vlu_int64 = start; } - else { + else + { const auto start = MOV_get_int128(tdbb, startDesc, impure->m_scale); const auto finish = MOV_get_int128(tdbb, finishDesc, impure->m_scale); const auto step = MOV_get_int128(tdbb, stepDesc, impure->m_scale); @@ -450,16 +452,14 @@ void GenSeriesFunctionScan::internalOpen(thread_db* tdbb) const return; } - impure->m_start = start; - impure->m_finish = finish; - impure->m_step = step; + impure->m_start.vlu_int128 = start; + impure->m_finish.vlu_int128 = finish; + impure->m_step.vlu_int128 = step; + impure->m_result.vlu_int128 = start; } - impure->irsb_flags |= irsb_open; - impure->m_result = impure->m_start; - - + VIO_record(tdbb, rpb, m_format, &pool); } @@ -510,9 +510,9 @@ bool GenSeriesFunctionScan::nextBuffer(thread_db* tdbb) const if (impure->m_dtype != dtype_int128) { - auto result = std::get(impure->m_result); - const auto finish = std::get(impure->m_finish); - const auto step = std::get(impure->m_step); + auto result = impure->m_result.vlu_int64; + const auto finish = impure->m_finish.vlu_int64; + const auto step = impure->m_step.vlu_int64; if (((step > 0) && (result <= finish)) || ((step < 0) && (result >= finish))) @@ -526,15 +526,15 @@ bool GenSeriesFunctionScan::nextBuffer(thread_db* tdbb) const assignParameter(tdbb, &fromDesc, toDesc, 0, record); result += step; - impure->m_result = result; + impure->m_result.vlu_int64 = result; return true; } } else { - auto result = std::get(impure->m_result); - const auto finish = std::get(impure->m_finish); - const auto step = std::get(impure->m_step); + auto result = impure->m_result.vlu_int128; + const auto finish = impure->m_finish.vlu_int128; + const auto step = impure->m_step.vlu_int128; if (((step.sign() > 0) && (result.compare(finish) <= 0)) || ((step.sign() < 0) && (result.compare(finish) >= 0))) @@ -548,7 +548,7 @@ bool GenSeriesFunctionScan::nextBuffer(thread_db* tdbb) const assignParameter(tdbb, &fromDesc, toDesc, 0, record); result = result.add(step); - impure->m_result = result; + impure->m_result.vlu_int128 = result; return true; } From e5e04da063df3b37cb851e19f4ed017b43da756e Mon Sep 17 00:00:00 2001 From: Simonov Denis Date: Mon, 3 Nov 2025 14:23:41 +0300 Subject: [PATCH 7/9] Corrected according to @dyemanov --- builds/win32/y.txt | 2 ++ builds/win32/y_tab.c | 26 +++++++++++++++++++++++ builds/win32/y_tab.h | 0 src/dsql/parse.y | 4 ++-- src/jrd/recsrc/TableValueFunctionScan.cpp | 15 ------------- 5 files changed, 30 insertions(+), 17 deletions(-) create mode 100644 builds/win32/y.txt create mode 100644 builds/win32/y_tab.c create mode 100644 builds/win32/y_tab.h diff --git a/builds/win32/y.txt b/builds/win32/y.txt new file mode 100644 index 00000000000..8dc3bd68e0b --- /dev/null +++ b/builds/win32/y.txt @@ -0,0 +1,2 @@ +y.y:6864: $$ is untyped +y.y:6865: $$ is untyped diff --git a/builds/win32/y_tab.c b/builds/win32/y_tab.c new file mode 100644 index 00000000000..41ea1ff7c07 --- /dev/null +++ b/builds/win32/y_tab.c @@ -0,0 +1,26 @@ + +// +// @(#)btyaccpar, based on byacc 1.8 (Berkeley) +// Parser skeleton modified for use in the Firebird project by Nickolay Samofatov +// +#define YYBTYACC 1 + +#include "firebird.h" + +#include +#include +#include +#include +#include "../dsql/Nodes.h" +#include "../dsql/AggNodes.h" +#include "../dsql/DdlNodes.h" +#include "../dsql/BoolNodes.h" +#include "../dsql/ExprNodes.h" +#include "../dsql/PackageNodes.h" +#include "../dsql/StmtNodes.h" +#include "../dsql/WinNodes.h" +#include "../jrd/RecordSourceNodes.h" +#include "../common/classes/TriState.h" +#include "gen/parse.h" +#include "../dsql/Parser.h" + diff --git a/builds/win32/y_tab.h b/builds/win32/y_tab.h new file mode 100644 index 00000000000..e69de29bb2d diff --git a/src/dsql/parse.y b/src/dsql/parse.y index 47e6bf608a7..3257602d818 100644 --- a/src/dsql/parse.y +++ b/src/dsql/parse.y @@ -6851,7 +6851,7 @@ table_value_function_gen_series %type table_value_function_gen_series_arg_list table_value_function_gen_series_arg_list - : value ',' value step_opt + : value ',' value gen_series_step_opt { $$ = newNode($1); $$->add($3); @@ -6859,7 +6859,7 @@ table_value_function_gen_series_arg_list } ; -%type step_opt +%type gen_series_step_opt step_opt : /* nothing */ { $$ = MAKE_const_sint64(1, 0); } | ',' value { $$ = $2; } diff --git a/src/jrd/recsrc/TableValueFunctionScan.cpp b/src/jrd/recsrc/TableValueFunctionScan.cpp index b346fe99adf..c6cc4a36982 100644 --- a/src/jrd/recsrc/TableValueFunctionScan.cpp +++ b/src/jrd/recsrc/TableValueFunctionScan.cpp @@ -384,26 +384,17 @@ void GenSeriesFunctionScan::internalOpen(thread_db* tdbb) const auto startItem = m_inputList->items[GEN_SERIES_INDEX_START]; const auto startDesc = EVL_expr(tdbb, request, startItem); if (startDesc == nullptr) - { - rpb->rpb_number.setValid(false); return; - } auto finishItem = m_inputList->items[GEN_SERIES_INDEX_FINISH]; const auto finishDesc = EVL_expr(tdbb, request, finishItem); if (finishDesc == nullptr) - { - rpb->rpb_number.setValid(false); return; - } auto stepItem = m_inputList->items[GEN_SERIES_INDEX_STEP]; const auto stepDesc = EVL_expr(tdbb, request, stepItem); if (stepDesc == nullptr) - { - rpb->rpb_number.setValid(false); return; - } const auto impure = request->getImpure(m_impure); impure->m_recordBuffer = nullptr; @@ -425,10 +416,7 @@ void GenSeriesFunctionScan::internalOpen(thread_db* tdbb) const // validate parameter value if (((step > 0) && (start > finish)) || ((step < 0) && (start < finish))) - { - rpb->rpb_number.setValid(false); return; - } impure->m_start.vlu_int64 = start; impure->m_finish.vlu_int64 = finish; @@ -447,10 +435,7 @@ void GenSeriesFunctionScan::internalOpen(thread_db* tdbb) const // validate parameter value if (((step.sign() > 0) && (start.compare(finish) > 0)) || ((step.sign() < 0) && (start.compare(finish) < 0))) - { - rpb->rpb_number.setValid(false); return; - } impure->m_start.vlu_int128 = start; impure->m_finish.vlu_int128 = finish; From d5e333cf388c7e40ec7076d34cedf92091757b29 Mon Sep 17 00:00:00 2001 From: Simonov Denis Date: Mon, 3 Nov 2025 14:47:52 +0300 Subject: [PATCH 8/9] correction parse.y --- builds/win32/y.txt | 2 -- builds/win32/y_tab.c | 26 -------------------------- builds/win32/y_tab.h | 0 src/dsql/parse.y | 2 +- 4 files changed, 1 insertion(+), 29 deletions(-) delete mode 100644 builds/win32/y.txt delete mode 100644 builds/win32/y_tab.c delete mode 100644 builds/win32/y_tab.h diff --git a/builds/win32/y.txt b/builds/win32/y.txt deleted file mode 100644 index 8dc3bd68e0b..00000000000 --- a/builds/win32/y.txt +++ /dev/null @@ -1,2 +0,0 @@ -y.y:6864: $$ is untyped -y.y:6865: $$ is untyped diff --git a/builds/win32/y_tab.c b/builds/win32/y_tab.c deleted file mode 100644 index 41ea1ff7c07..00000000000 --- a/builds/win32/y_tab.c +++ /dev/null @@ -1,26 +0,0 @@ - -// -// @(#)btyaccpar, based on byacc 1.8 (Berkeley) -// Parser skeleton modified for use in the Firebird project by Nickolay Samofatov -// -#define YYBTYACC 1 - -#include "firebird.h" - -#include -#include -#include -#include -#include "../dsql/Nodes.h" -#include "../dsql/AggNodes.h" -#include "../dsql/DdlNodes.h" -#include "../dsql/BoolNodes.h" -#include "../dsql/ExprNodes.h" -#include "../dsql/PackageNodes.h" -#include "../dsql/StmtNodes.h" -#include "../dsql/WinNodes.h" -#include "../jrd/RecordSourceNodes.h" -#include "../common/classes/TriState.h" -#include "gen/parse.h" -#include "../dsql/Parser.h" - diff --git a/builds/win32/y_tab.h b/builds/win32/y_tab.h deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/src/dsql/parse.y b/src/dsql/parse.y index 3257602d818..b385ac94550 100644 --- a/src/dsql/parse.y +++ b/src/dsql/parse.y @@ -6860,7 +6860,7 @@ table_value_function_gen_series_arg_list ; %type gen_series_step_opt -step_opt +gen_series_step_opt : /* nothing */ { $$ = MAKE_const_sint64(1, 0); } | ',' value { $$ = $2; } ; From 68672ea790216fee9c88ea1a707a9eb215f88b3d Mon Sep 17 00:00:00 2001 From: Simonov Denis Date: Tue, 4 Nov 2025 09:27:50 +0300 Subject: [PATCH 9/9] Use braces if condition has more than one line. --- src/jrd/recsrc/TableValueFunctionScan.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/jrd/recsrc/TableValueFunctionScan.cpp b/src/jrd/recsrc/TableValueFunctionScan.cpp index c6cc4a36982..51e44fed7ef 100644 --- a/src/jrd/recsrc/TableValueFunctionScan.cpp +++ b/src/jrd/recsrc/TableValueFunctionScan.cpp @@ -416,7 +416,9 @@ void GenSeriesFunctionScan::internalOpen(thread_db* tdbb) const // validate parameter value if (((step > 0) && (start > finish)) || ((step < 0) && (start < finish))) + { return; + } impure->m_start.vlu_int64 = start; impure->m_finish.vlu_int64 = finish; @@ -435,7 +437,9 @@ void GenSeriesFunctionScan::internalOpen(thread_db* tdbb) const // validate parameter value if (((step.sign() > 0) && (start.compare(finish) > 0)) || ((step.sign() < 0) && (start.compare(finish) < 0))) + { return; + } impure->m_start.vlu_int128 = start; impure->m_finish.vlu_int128 = finish;