Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 56 additions & 0 deletions doc/sql.extensions/README.generate_series.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# 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_function> ::=
GENERATE_SERIES(<start>, <finish> [, <step>]) [AS] <correlation name> [ ( <derived column name> ) ]
```

## 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`, `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`, `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`, `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`, `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

* 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 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,
DATEADD(n MINUTE TO timestamp '2025-01-01 12:00:59.9999') AS FINISH_TIME
FROM GENERATE_SERIES(0, 59) AS S(n);
```

1 change: 1 addition & 0 deletions src/common/ParserTokens.h
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion src/dsql/parse-conflicts.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
133 shift/reduce conflicts, 13 reduce/reduce conflicts.
134 shift/reduce conflicts, 13 reduce/reduce conflicts.
32 changes: 32 additions & 0 deletions src/dsql/parse.y
Original file line number Diff line number Diff line change
Expand Up @@ -710,6 +710,7 @@ using namespace Firebird;
%token <metaNamePtr> CURRENT_SCHEMA
%token <metaNamePtr> DOWNTO
%token <metaNamePtr> FORMAT
%token <metaNamePtr> GENERATE_SERIES
%token <metaNamePtr> GREATEST
%token <metaNamePtr> LEAST
%token <metaNamePtr> LTRIM
Expand Down Expand Up @@ -6798,6 +6799,8 @@ table_value_function
table_value_function_clause
: table_value_function_unlist
{ $$ = $1; }
| table_value_function_gen_series
{ $$ = $1; }
;

%type <recSourceNode> table_value_function_unlist
Expand Down Expand Up @@ -6833,6 +6836,34 @@ table_value_function_correlation_name
: as_noise symbol_item_alias_name { $$ = $2; }
;

%type <recSourceNode> table_value_function_gen_series
table_value_function_gen_series
: GENERATE_SERIES '(' table_value_function_gen_series_arg_list ')'
{
auto node = newNode<GenSeriesFunctionSourceNode>();
node->dsqlFlags |= RecordSourceNode::DFLAG_VALUE;
node->dsqlName = *$1;
node->inputList = $3;
node->dsqlField = nullptr;
$$ = node;
}
;

%type <valueListNode> table_value_function_gen_series_arg_list
table_value_function_gen_series_arg_list
: value ',' value gen_series_step_opt
{
$$ = newNode<ValueListNode>($1);
$$->add($3);
$$->add($4);
}
;

%type <valueExprNode> gen_series_step_opt
gen_series_step_opt
: /* nothing */ { $$ = MAKE_const_sint64(1, 0); }
| ',' value { $$ = $2; }
;

// other clauses in the select expression

Expand Down Expand Up @@ -9967,6 +9998,7 @@ non_reserved_word
| BIN_XOR_AGG
| DOWNTO
| FORMAT
| GENERATE_SERIES
| OWNER
| SEARCH_PATH
| SCHEMA
Expand Down
1 change: 1 addition & 0 deletions src/include/firebird/impl/blr.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions src/include/firebird/impl/msg/jrd.h
Original file line number Diff line number Diff line change
Expand Up @@ -998,3 +998,5 @@ 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")
FB_IMPL_MSG(JRD, 1000, argmustbe_exact_function, -833, "42", "000", "Arguments for @1 function must be exact numeric types")
2 changes: 2 additions & 0 deletions src/include/gen/Firebird.pas
Original file line number Diff line number Diff line change
Expand Up @@ -5844,6 +5844,8 @@ 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_argmustbe_exact_function = 335545320;
isc_gfix_db_name = 335740929;
isc_gfix_invalid_sw = 335740930;
isc_gfix_incmp_sw = 335740932;
Expand Down
90 changes: 90 additions & 0 deletions src/jrd/RecordSourceNodes.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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");
}
Expand Down Expand Up @@ -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);

Expand Down Expand Up @@ -4320,6 +4326,8 @@ void TableValueFunctionSourceNode::setDefaultNameField(DsqlCompilerScratch* /*ds
fb_assert(false);
}

//--------------------

RecordSource* UnlistFunctionSourceNode::compile(thread_db* tdbb, Optimizer* opt,
bool /*innerSubStream*/)
{
Expand Down Expand Up @@ -4378,6 +4386,88 @@ 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);
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;

if (!field)
{
field = FB_NEW_POOL(dsqlScratch->getPool()) dsql_fld(dsqlScratch->getPool());

dsc desc;
if (dtype == dtype_int128)
desc.makeInt128(scale);
else
desc.makeInt64(scale);

MAKE_field(field, &desc);
field->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(getName())
<< Arg::Num(1) << Arg::Num(dsqlNameColumns.getCount()));
}

field->fld_name = dsqlNameColumns[0];
}

field->resolve(dsqlScratch);
return field;
}

//--------------------


static RecordSourceNode* dsqlPassRelProc(DsqlCompilerScratch* dsqlScratch, RecordSourceNode* source)
{
Expand Down
19 changes: 19 additions & 0 deletions src/jrd/RecordSourceNodes.h
Original file line number Diff line number Diff line change
Expand Up @@ -1081,6 +1081,25 @@ class UnlistFunctionSourceNode : public TableValueFunctionSourceNode
}
};

class GenSeriesFunctionSourceNode final : public TableValueFunctionSourceNode
{
public:
explicit GenSeriesFunctionSourceNode(MemoryPool& pool)
: TableValueFunctionSourceNode(pool)
{
}

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 override
{
return FUNC_NAME;
}
};

} // namespace Jrd

#endif // JRD_RECORD_SOURCE_NODES_H
57 changes: 57 additions & 0 deletions src/jrd/recsrc/RecordSource.h
Original file line number Diff line number Diff line change
Expand Up @@ -1615,6 +1615,63 @@ namespace Jrd
NestConst<ValueListNode> m_inputList;
};

class GenSeriesFunctionScan final : public TableValueFunctionScan
{
enum GenSeriesTypeItemIndex : UCHAR
{
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
{
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;
};

public:
GenSeriesFunctionScan(CompilerScratch* csb, StreamType stream, const Firebird::string& alias,
ValueListNode* list);

protected:
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 override;
bool internalGetRecord(thread_db* tdbb) const override;

bool nextBuffer(thread_db* tdbb) const override;

private:
NestConst<ValueListNode> m_inputList;
};

} // namespace

#endif // JRD_RECORD_SOURCE_H
Loading
Loading