Skip to content

Commit f0ac23e

Browse files
authored
GENERATE_SERIES function (#8795)
1 parent 53b218f commit f0ac23e

File tree

11 files changed

+462
-2
lines changed

11 files changed

+462
-2
lines changed
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
# GENERATE_SERIES function
2+
3+
The `GENERATE_SERIES` function creates a series of numbers within a specified interval.
4+
The interval and the step between series values ​​are defined by the user.
5+
6+
## Syntax
7+
8+
```
9+
<generate_series_function> ::=
10+
GENERATE_SERIES(<start>, <finish> [, <step>]) [AS] <correlation name> [ ( <derived column name> ) ]
11+
```
12+
13+
## Arguments
14+
15+
* `start` - The first value in the interval. `start` is specified as a variable, a literal, or a scalar expression of type
16+
`SMALLINT`, `INTEGER`, `BIGINT`, `INT128` or `NUMERIC/DECIMAL`.
17+
18+
* `finish` - The last value in the interval. `finish` is specified as a variable, a literal, or a scalar expression of
19+
type `SMALLINT`, `INTEGER`, `BIGINT`, `INT128` or `NUMERIC/DECIMAL`. The series stops once the last generated step value
20+
exceeds the `finish` value.
21+
22+
* `step` - Indicates the number of values to increment or decrement between steps in the series. `step` is an expression
23+
of type `SMALLINT`, `INTEGER`, `BIGINT`, `INT128` or `NUMERIC/DECIMAL`.
24+
`step` can be either negative or positive, but can't be zero (0). This argument is optional. The default value for `step` is 1.
25+
26+
## Returning type
27+
28+
The function `GENERATE_SERIES` returns a set with `BIGINT`, `INT128` or `NUMERIC(18, x)/NUMERIC(38, x)` column,
29+
where the scale is determined by the maximum of the scales of the function arguments.
30+
31+
## Rules
32+
33+
* If `start > finish` and a negative `step` value is specified, an empty set is returned.
34+
35+
* If `start < finish` and a positive `step` value is specified, an empty set is returned.
36+
37+
* If the `step` argument is zero, an error is thrown.
38+
39+
## Examples
40+
41+
```
42+
SELECT n
43+
FROM GENERATE_SERIES(1, 3) AS S(n);
44+
45+
SELECT n
46+
FROM GENERATE_SERIES(3, 1, -1) AS S(n);
47+
48+
SELECT n
49+
FROM GENERATE_SERIES(0, 9.9, 0.1) AS S(n);
50+
51+
SELECT
52+
DATEADD(n MINUTE TO timestamp '2025-01-01 12:00') AS START_TIME,
53+
DATEADD(n MINUTE TO timestamp '2025-01-01 12:00:59.9999') AS FINISH_TIME
54+
FROM GENERATE_SERIES(0, 59) AS S(n);
55+
```
56+

src/common/ParserTokens.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,7 @@ PARSER_TOKEN(TOK_FROM, "FROM", false)
236236
PARSER_TOKEN(TOK_FULL, "FULL", false)
237237
PARSER_TOKEN(TOK_FUNCTION, "FUNCTION", false)
238238
PARSER_TOKEN(TOK_GDSCODE, "GDSCODE", false)
239+
PARSER_TOKEN(TOK_GENERATE_SERIES, "GENERATE_SERIES", true)
239240
PARSER_TOKEN(TOK_GENERATED, "GENERATED", true)
240241
PARSER_TOKEN(TOK_GENERATOR, "GENERATOR", true)
241242
PARSER_TOKEN(TOK_GEN_ID, "GEN_ID", true)

src/dsql/parse-conflicts.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
133 shift/reduce conflicts, 13 reduce/reduce conflicts.
1+
134 shift/reduce conflicts, 13 reduce/reduce conflicts.

src/dsql/parse.y

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -710,6 +710,7 @@ using namespace Firebird;
710710
%token <metaNamePtr> CURRENT_SCHEMA
711711
%token <metaNamePtr> DOWNTO
712712
%token <metaNamePtr> FORMAT
713+
%token <metaNamePtr> GENERATE_SERIES
713714
%token <metaNamePtr> GREATEST
714715
%token <metaNamePtr> LEAST
715716
%token <metaNamePtr> LTRIM
@@ -6792,6 +6793,8 @@ table_value_function
67926793
table_value_function_clause
67936794
: table_value_function_unlist
67946795
{ $$ = $1; }
6796+
| table_value_function_gen_series
6797+
{ $$ = $1; }
67956798
;
67966799

67976800
%type <recSourceNode> table_value_function_unlist
@@ -6827,6 +6830,34 @@ table_value_function_correlation_name
68276830
: as_noise symbol_item_alias_name { $$ = $2; }
68286831
;
68296832

6833+
%type <recSourceNode> table_value_function_gen_series
6834+
table_value_function_gen_series
6835+
: GENERATE_SERIES '(' table_value_function_gen_series_arg_list ')'
6836+
{
6837+
auto node = newNode<GenSeriesFunctionSourceNode>();
6838+
node->dsqlFlags |= RecordSourceNode::DFLAG_VALUE;
6839+
node->dsqlName = *$1;
6840+
node->inputList = $3;
6841+
node->dsqlField = nullptr;
6842+
$$ = node;
6843+
}
6844+
;
6845+
6846+
%type <valueListNode> table_value_function_gen_series_arg_list
6847+
table_value_function_gen_series_arg_list
6848+
: value ',' value gen_series_step_opt
6849+
{
6850+
$$ = newNode<ValueListNode>($1);
6851+
$$->add($3);
6852+
$$->add($4);
6853+
}
6854+
;
6855+
6856+
%type <valueExprNode> gen_series_step_opt
6857+
gen_series_step_opt
6858+
: /* nothing */ { $$ = MAKE_const_sint64(1, 0); }
6859+
| ',' value { $$ = $2; }
6860+
;
68306861

68316862
// other clauses in the select expression
68326863

@@ -10031,6 +10062,7 @@ non_reserved_word
1003110062
| BIN_XOR_AGG
1003210063
| DOWNTO
1003310064
| FORMAT
10065+
| GENERATE_SERIES
1003410066
| OWNER
1003510067
| SEARCH_PATH
1003610068
| SCHEMA

src/include/firebird/impl/blr.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -508,6 +508,7 @@
508508
// Table value function
509509
#define blr_table_value_fun (unsigned char) 229
510510
#define blr_table_value_fun_unlist (unsigned char) 1
511+
#define blr_table_value_fun_gen_series (unsigned char) 2
511512

512513
#define blr_for_range (unsigned char) 230
513514
#define blr_for_range_variable (unsigned char) 1

src/include/firebird/impl/msg/jrd.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -998,3 +998,5 @@ FB_IMPL_MSG(JRD, 995, missing_value_for_format_pattern, -901, "HY", "000", "Cann
998998
FB_IMPL_MSG(JRD, 996, invalid_name, -901, "HY", "000", "Invalid name: @1")
999999
FB_IMPL_MSG(JRD, 997, invalid_unqualified_name_list, -901, "HY", "000", "Invalid list of unqualified names: @1")
10001000
FB_IMPL_MSG(JRD, 998, no_user_att_while_restore, -901, "HY", "000", "User attachments are not allowed for the database being restored")
1001+
FB_IMPL_MSG(JRD, 999, genseq_stepmustbe_nonzero, -833, "42", "000", "Argument STEP must be different than zero for function @1")
1002+
FB_IMPL_MSG(JRD, 1000, argmustbe_exact_function, -833, "42", "000", "Arguments for @1 function must be exact numeric types")

src/include/gen/Firebird.pas

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5846,6 +5846,8 @@ IProfilerStatsImpl = class(IProfilerStats)
58465846
isc_invalid_name = 335545316;
58475847
isc_invalid_unqualified_name_list = 335545317;
58485848
isc_no_user_att_while_restore = 335545318;
5849+
isc_genseq_stepmustbe_nonzero = 335545319;
5850+
isc_argmustbe_exact_function = 335545320;
58495851
isc_gfix_db_name = 335740929;
58505852
isc_gfix_invalid_sw = 335740930;
58515853
isc_gfix_incmp_sw = 335740932;

src/jrd/RecordSourceNodes.cpp

Lines changed: 92 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4109,6 +4109,10 @@ TableValueFunctionSourceNode* TableValueFunctionSourceNode::parseFunction(thread
41094109
node = FB_NEW_POOL(pool) UnlistFunctionSourceNode(pool);
41104110
break;
41114111

4112+
case blr_table_value_fun_gen_series:
4113+
node = FB_NEW_POOL(pool) GenSeriesFunctionSourceNode(pool);
4114+
break;
4115+
41124116
default:
41134117
PAR_syntax_error(csb, "blr_table_value_fun");
41144118
}
@@ -4161,6 +4165,8 @@ void TableValueFunctionSourceNode::genBlr(DsqlCompilerScratch* dsqlScratch)
41614165

41624166
if (tableValueFunctionContext->funName == UnlistFunctionSourceNode::FUNC_NAME)
41634167
dsqlScratch->appendUChar(blr_table_value_fun_unlist);
4168+
else if (tableValueFunctionContext->funName == GenSeriesFunctionSourceNode::FUNC_NAME)
4169+
dsqlScratch->appendUChar(blr_table_value_fun_gen_series);
41644170
else
41654171
fb_assert(false);
41664172

@@ -4307,7 +4313,8 @@ void TableValueFunctionSourceNode::setDefaultNameField(DsqlCompilerScratch* /*ds
43074313

43084314
auto i = 0U;
43094315

4310-
if (nameFunc == UnlistFunctionSourceNode::FUNC_NAME)
4316+
if ((nameFunc == UnlistFunctionSourceNode::FUNC_NAME) ||
4317+
(nameFunc == GenSeriesFunctionSourceNode::FUNC_NAME))
43114318
{
43124319
dsql_fld* field = tableValueFunctionContext->outputField;
43134320
if (field->fld_name.isEmpty())
@@ -4320,6 +4327,8 @@ void TableValueFunctionSourceNode::setDefaultNameField(DsqlCompilerScratch* /*ds
43204327
fb_assert(false);
43214328
}
43224329

4330+
//--------------------
4331+
43234332
RecordSource* UnlistFunctionSourceNode::compile(thread_db* tdbb, Optimizer* opt,
43244333
bool /*innerSubStream*/)
43254334
{
@@ -4378,6 +4387,88 @@ dsql_fld* UnlistFunctionSourceNode::makeField(DsqlCompilerScratch* dsqlScratch)
43784387

43794388
//--------------------
43804389

4390+
RecordSource* GenSeriesFunctionSourceNode::compile(thread_db* tdbb, Optimizer* opt,
4391+
bool /*innerSubStream*/)
4392+
{
4393+
MemoryPool& pool = *tdbb->getDefaultPool();
4394+
const auto csb = opt->getCompilerScratch();
4395+
const auto alias = opt->makeAlias(stream);
4396+
4397+
return FB_NEW_POOL(pool) GenSeriesFunctionScan(csb, stream, alias, inputList);
4398+
}
4399+
4400+
dsql_fld* GenSeriesFunctionSourceNode::makeField(DsqlCompilerScratch* dsqlScratch)
4401+
{
4402+
if (inputList)
4403+
inputList = Node::doDsqlPass(dsqlScratch, inputList, false);
4404+
4405+
const auto startItem = inputList->items[0].getObject();
4406+
startItem->setParameterType(
4407+
dsqlScratch, [](dsc* desc) { desc->makeInt64(0); }, false);
4408+
4409+
const auto finishItem = inputList->items[1].getObject();
4410+
finishItem->setParameterType(
4411+
dsqlScratch, [](dsc* desc) { desc->makeInt64(0); }, false);
4412+
4413+
const auto stepItem = inputList->items[2].getObject();
4414+
stepItem->setParameterType(
4415+
dsqlScratch, [](dsc* desc) { desc->makeInt64(0); }, false);
4416+
4417+
dsc startDesc;
4418+
DsqlDescMaker::fromNode(dsqlScratch, &startDesc, startItem, true);
4419+
if (!startDesc.isExact() && !startDesc.isNull())
4420+
status_exception::raise(Arg::Gds(isc_argmustbe_exact_function) << Arg::Str(getName()));
4421+
4422+
dsc finishDesc;
4423+
DsqlDescMaker::fromNode(dsqlScratch, &finishDesc, finishItem, true);
4424+
if (!finishDesc.isExact() && !finishDesc.isNull())
4425+
status_exception::raise(Arg::Gds(isc_argmustbe_exact_function) << Arg::Str(getName()));
4426+
4427+
dsc stepDesc;
4428+
DsqlDescMaker::fromNode(dsqlScratch, &stepDesc, stepItem, true);
4429+
if (!stepDesc.isExact() && !stepDesc.isNull())
4430+
status_exception::raise(Arg::Gds(isc_argmustbe_exact_function) << Arg::Str(getName()));
4431+
4432+
// common scale
4433+
const auto scale = MIN(MIN(startDesc.dsc_scale, finishDesc.dsc_scale), stepDesc.dsc_scale);
4434+
// common type
4435+
const auto dtype = MAX(MAX(startDesc.dsc_dtype, finishDesc.dsc_dtype), stepDesc.dsc_dtype);
4436+
4437+
dsql_fld* field = dsqlField;
4438+
4439+
if (!field)
4440+
{
4441+
field = FB_NEW_POOL(dsqlScratch->getPool()) dsql_fld(dsqlScratch->getPool());
4442+
4443+
dsc desc;
4444+
if (dtype == dtype_int128)
4445+
desc.makeInt128(scale);
4446+
else
4447+
desc.makeInt64(scale);
4448+
4449+
MAKE_field(field, &desc);
4450+
field->fld_id = 0;
4451+
}
4452+
4453+
if (dsqlNameColumns.hasData())
4454+
{
4455+
if (dsqlNameColumns.getCount() > 1)
4456+
{
4457+
ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-104) << Arg::Gds(isc_dsql_command_err)
4458+
<< Arg::Gds(isc_dsql_table_value_many_columns)
4459+
<< Arg::Str(getName())
4460+
<< Arg::Num(1) << Arg::Num(dsqlNameColumns.getCount()));
4461+
}
4462+
4463+
field->fld_name = dsqlNameColumns[0];
4464+
}
4465+
4466+
field->resolve(dsqlScratch);
4467+
return field;
4468+
}
4469+
4470+
//--------------------
4471+
43814472

43824473
static RecordSourceNode* dsqlPassRelProc(DsqlCompilerScratch* dsqlScratch, RecordSourceNode* source)
43834474
{

src/jrd/RecordSourceNodes.h

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1081,6 +1081,25 @@ class UnlistFunctionSourceNode : public TableValueFunctionSourceNode
10811081
}
10821082
};
10831083

1084+
class GenSeriesFunctionSourceNode final : public TableValueFunctionSourceNode
1085+
{
1086+
public:
1087+
explicit GenSeriesFunctionSourceNode(MemoryPool& pool)
1088+
: TableValueFunctionSourceNode(pool)
1089+
{
1090+
}
1091+
1092+
RecordSource* compile(thread_db* tdbb, Optimizer* opt, bool innerSubStream) override;
1093+
dsql_fld* makeField(DsqlCompilerScratch* dsqlScratch) override;
1094+
1095+
static constexpr char const* FUNC_NAME = "GENERATE_SERIES";
1096+
1097+
const char* getName() const override
1098+
{
1099+
return FUNC_NAME;
1100+
}
1101+
};
1102+
10841103
} // namespace Jrd
10851104

10861105
#endif // JRD_RECORD_SOURCE_NODES_H

src/jrd/recsrc/RecordSource.h

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1615,6 +1615,63 @@ namespace Jrd
16151615
NestConst<ValueListNode> m_inputList;
16161616
};
16171617

1618+
class GenSeriesFunctionScan final : public TableValueFunctionScan
1619+
{
1620+
enum GenSeriesTypeItemIndex : UCHAR
1621+
{
1622+
GEN_SERIES_INDEX_START = 0,
1623+
GEN_SERIES_INDEX_FINISH = 1,
1624+
GEN_SERIES_INDEX_STEP = 2,
1625+
GEN_SERIES_INDEX_LAST = 3
1626+
};
1627+
1628+
struct Impure : public TableValueFunctionScan::Impure
1629+
{
1630+
union
1631+
{
1632+
SINT64 vlu_int64;
1633+
Firebird::Int128 vlu_int128;
1634+
} m_start;
1635+
1636+
union
1637+
{
1638+
SINT64 vlu_int64;
1639+
Firebird::Int128 vlu_int128;
1640+
} m_finish;
1641+
1642+
union
1643+
{
1644+
SINT64 vlu_int64;
1645+
Firebird::Int128 vlu_int128;
1646+
} m_step;
1647+
1648+
union
1649+
{
1650+
SINT64 vlu_int64;
1651+
Firebird::Int128 vlu_int128;
1652+
} m_result;
1653+
1654+
UCHAR m_dtype;
1655+
SCHAR m_scale;
1656+
};
1657+
1658+
public:
1659+
GenSeriesFunctionScan(CompilerScratch* csb, StreamType stream, const Firebird::string& alias,
1660+
ValueListNode* list);
1661+
1662+
protected:
1663+
void close(thread_db* tdbb) const override;
1664+
void internalOpen(thread_db* tdbb) const override;
1665+
void internalGetPlan(thread_db* tdbb, PlanEntry& planEntry, unsigned level,
1666+
bool recurse) const override;
1667+
bool internalGetRecord(thread_db* tdbb) const override;
1668+
1669+
bool nextBuffer(thread_db* tdbb) const override;
1670+
1671+
private:
1672+
NestConst<ValueListNode> m_inputList;
1673+
};
1674+
16181675
} // namespace
16191676

16201677
#endif // JRD_RECORD_SOURCE_H

0 commit comments

Comments
 (0)