Skip to content

Commit

Permalink
HHH-18731 Add generate_series() set-returning function
Browse files Browse the repository at this point in the history
  • Loading branch information
beikov committed Oct 23, 2024
1 parent 5bd244d commit 82b20a0
Show file tree
Hide file tree
Showing 75 changed files with 3,974 additions and 208 deletions.
5 changes: 3 additions & 2 deletions ci/build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -60,10 +60,11 @@ elif [ "$RDBMS" == "db2_10_5" ]; then
goal="-Pdb=db2"
elif [ "$RDBMS" == "mssql" ] || [ "$RDBMS" == "mssql_2017" ]; then
goal="-Pdb=mssql_ci"
# Exclude some Sybase tests on CI because they use `xmltable` function which has a memory leak on the DB version in CI
elif [ "$RDBMS" == "sybase" ]; then
goal="-Pdb=sybase_ci"
goal="-Pdb=sybase_ci -PexcludeTests=**.GenerateSeriesTest*"
elif [ "$RDBMS" == "sybase_jconn" ]; then
goal="-Pdb=sybase_jconn_ci"
goal="-Pdb=sybase_jconn_ci -PexcludeTests=**.GenerateSeriesTest*"
elif [ "$RDBMS" == "tidb" ]; then
goal="-Pdb=tidb"
elif [ "$RDBMS" == "hana_cloud" ]; then
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2958,7 +2958,7 @@ The following set-returning functions are available on many platforms:
| Function | purpose
| <<hql-array-unnest,`unnest()`>> | Turns an array into rows
//| `generate_series()` | Creates a series of values as rows
| <<hql-from-set-returning-functions-generate-series,`generate_series()`>> | Creates a series of values as rows
|===
To use set returning functions defined in the database, it is required to register them in a `FunctionContributor`:
Expand Down Expand Up @@ -2986,6 +2986,43 @@ which is not supported on some databases for user defined functions.
Hibernate ORM tries to emulate this feature by wrapping invocations as lateral subqueries and using `row_number()`,
which may lead to worse performance.
[[hql-from-set-returning-functions-generate-series]]
==== `generate_series` set-returning function
A <<hql-from-set-returning-functions,set-returning function>>, which generates rows from a given start value (inclusive)
up to a given stop value (inclusive). The function has 2 variants:
* `generate_series(numeric, numeric [,numeric])` - Arguments are `start`, `stop` and `step` with a default of `1` for the optional `step` argument
* `generate_series(temporal, temporal, duration)` - Like the numeric variant, but for temporal types and `step` is required
[[hql-generate-series-example]]
====
[source, java, indent=0]
----
include::{srf-example-dir-hql}/GenerateSeriesTest.java[tags=hql-set-returning-function-generate-series-example]
----
====
To obtain the "row number" of a generated value i.e. ordinality, it is possible to use the `index()` function.
[[hql-generate-series-ordinality-example]]
====
[source, java, indent=0]
----
include::{srf-example-dir-hql}/GenerateSeriesTest.java[tags=hql-set-returning-function-generate-series-ordinality-example]
----
====
The `step` argument can be a negative value and progress from a higher `start` value to a lower `stop` value.
[[hql-generate-series-temporal-example]]
====
[source, java, indent=0]
----
include::{srf-example-dir-hql}/GenerateSeriesTest.java[tags=hql-set-returning-function-generate-series-temporal-example]
----
====
[[hql-join]]
=== Declaring joined entities
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -518,6 +518,7 @@ public void initializeFunctionRegistry(FunctionContributions functionContributio
functionFactory.jsonArrayInsert_postgresql();

functionFactory.unnest_postgresql();
functionFactory.generateSeries( null, "ordinality", true );

// Postgres uses # instead of ^ for XOR
functionContributions.getFunctionRegistry().patternDescriptorBuilder( "bitxor", "(?1#?2)" )
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -458,6 +458,18 @@ public void initializeFunctionRegistry(FunctionContributions functionContributio
functionFactory.xmlagg();

functionFactory.unnest_emulated();
if ( supportsRecursiveCTE() ) {
functionFactory.generateSeries_recursive( getMaximumSeriesSize(), false, true );
}
}

/**
* DB2 doesn't support the {@code generate_series} function or {@code lateral} recursive CTEs,
* so it has to be emulated with a top level recursive CTE which requires an upper bound on the amount
* of elements that the series can return.
*/
protected int getMaximumSeriesSize() {
return 10000;
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -428,6 +428,7 @@ public void initializeFunctionRegistry(FunctionContributions functionContributio
}

functionFactory.unnest_h2( getMaximumArraySize() );
functionFactory.generateSeries_h2( getMaximumSeriesSize() );
}

/**
Expand All @@ -440,6 +441,16 @@ protected int getMaximumArraySize() {
return 1000;
}

/**
* Since H2 doesn't support ordinality for the {@code system_range} function or {@code lateral},
* it's impossible to use {@code system_range} for non-constant cases.
* Luckily, correlation can be emulated, but requires that there is an upper bound on the amount
* of elements that the series can return.
*/
protected int getMaximumSeriesSize() {
return 10000;
}

@Override
public @Nullable String getDefaultOrdinalityColumnName() {
return "nord";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -497,6 +497,8 @@ public void initializeFunctionRegistry(FunctionContributions functionContributio
functionFactory.unnest_hana();
// functionFactory.json_table();

functionFactory.generateSeries_hana( getMaximumSeriesSize() );

if ( getVersion().isSameOrAfter(2, 0, 20 ) ) {
if ( getVersion().isSameOrAfter( 2, 0, 40 ) ) {
// Introduced in 2.0 SPS 04
Expand All @@ -513,6 +515,14 @@ public void initializeFunctionRegistry(FunctionContributions functionContributio
}
}

/**
* HANA doesn't support the {@code generate_series} function or {@code lateral} recursive CTEs,
* so it has to be emulated with the {@code xmltable} and {@code lpad} functions.
*/
protected int getMaximumSeriesSize() {
return 10000;
}

@Override
public SqlAstTranslatorFactory getSqlAstTranslatorFactory() {
return new StandardSqlAstTranslatorFactory() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,7 @@ public void initializeFunctionRegistry(FunctionContributions functionContributio
}

functionFactory.unnest( "c1", "c2" );
functionFactory.generateSeries_recursive( getMaximumSeriesSize(), true, false );

//trim() requires parameters to be cast when used as trim character
functionContributions.getFunctionRegistry().register( "trim", new TrimFunction(
Expand All @@ -288,6 +289,16 @@ public void initializeFunctionRegistry(FunctionContributions functionContributio
) );
}

/**
* HSQLDB doesn't support the {@code generate_series} function or {@code lateral} recursive CTEs,
* so it has to be emulated with a top level recursive CTE which requires an upper bound on the amount
* of elements that the series can return.
*/
protected int getMaximumSeriesSize() {
// The maximum recursion depth of HSQLDB
return 258;
}

@Override
public @Nullable String getDefaultOrdinalityColumnName() {
return "c2";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -675,9 +675,22 @@ public void initializeFunctionRegistry(FunctionContributions functionContributio
if ( getMySQLVersion().isSameOrAfter( 8 ) ) {
functionFactory.unnest_emulated();
}
if ( supportsRecursiveCTE() ) {
functionFactory.generateSeries_recursive( getMaximumSeriesSize(), false, false );
}
}
}

/**
* MySQL doesn't support the {@code generate_series} function or {@code lateral} recursive CTEs,
* so it has to be emulated with a top level recursive CTE which requires an upper bound on the amount
* of elements that the series can return.
*/
protected int getMaximumSeriesSize() {
// The maximum recursion depth of MySQL
return 1000;
}

@Override
public void contributeTypes(TypeContributions typeContributions, ServiceRegistry serviceRegistry) {
super.contributeTypes( typeContributions, serviceRegistry );
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,16 @@ public void initializeFunctionRegistry(FunctionContributions functionContributio
functionFactory.xmlagg();

functionFactory.unnest_oracle();
functionFactory.generateSeries_recursive( getMaximumSeriesSize(), true, false );
}

/**
* Oracle doesn't support the {@code generate_series} function or {@code lateral} recursive CTEs,
* so it has to be emulated with a top level recursive CTE which requires an upper bound on the amount
* of elements that the series can return.
*/
protected int getMaximumSeriesSize() {
return 10000;
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -711,6 +711,7 @@ public void initializeFunctionRegistry(FunctionContributions functionContributio
else {
functionFactory.unnest_postgresql();
}
functionFactory.generateSeries( null, "ordinality", false );
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -440,13 +440,32 @@ public void initializeFunctionRegistry(FunctionContributions functionContributio
functionFactory.leastGreatest();
functionFactory.dateTrunc_datetrunc();
functionFactory.trunc_round_datetrunc();
functionFactory.generateSeries_sqlserver( getMaximumSeriesSize() );
}
else {
functionContributions.getFunctionRegistry().register(
"trunc",
new SqlServerConvertTruncFunction( functionContributions.getTypeConfiguration() )
);
functionContributions.getFunctionRegistry().registerAlternateKey( "truncate", "trunc" );
if ( supportsRecursiveCTE() ) {
functionFactory.generateSeries_recursive( getMaximumSeriesSize(), false, false );
}
}
}

/**
* SQL Server doesn't support the {@code generate_series} function or {@code lateral} recursive CTEs,
* so it has to be emulated with a top level recursive CTE which requires an upper bound on the amount
* of elements that the series can return.
*/
protected int getMaximumSeriesSize() {
if ( getVersion().isSameOrAfter( 16 ) ) {
return 10000;
}
else {
// The maximum recursion depth of SQL Server
return 100;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,17 @@ public void initializeFunctionRegistry(FunctionContributions functionContributio
CommonFunctionFactory functionFactory = new CommonFunctionFactory( functionContributions);

functionFactory.unnest_sybasease();
functionFactory.generateSeries_sybasease( getMaximumSeriesSize() );
}

/**
* Sybase ASE doesn't support the {@code generate_series} function or {@code lateral} recursive CTEs,
* so it has to be emulated with the {@code xmltable} and {@code replicate} functions.
*/
protected int getMaximumSeriesSize() {
// The maximum possible value for replicating an XML tag, so that the resulting string stays below the 16K limit
// https://infocenter.sybase.com/help/index.jsp?topic=/com.sybase.infocenter.dc32300.1570/html/sqlug/sqlug31.htm
return 4094;
}

private static boolean isAnsiNull(DatabaseMetaData databaseMetaData) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html.
* SPDX-License-Identifier: LGPL-2.1-or-later
* Copyright Red Hat Inc. and Hibernate Authors
*/
package org.hibernate.boot.models.annotations.internal;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -486,6 +486,7 @@ public void initializeFunctionRegistry(FunctionContributions functionContributio
functionFactory.jsonArrayInsert_postgresql();

functionFactory.unnest_postgresql();
functionFactory.generateSeries( null, "ordinality", true );

// Postgres uses # instead of ^ for XOR
functionContributions.getFunctionRegistry().patternDescriptorBuilder( "bitxor", "(?1#?2)" )
Expand Down
10 changes: 10 additions & 0 deletions hibernate-core/src/main/java/org/hibernate/dialect/DB2Dialect.java
Original file line number Diff line number Diff line change
Expand Up @@ -443,6 +443,16 @@ public void initializeFunctionRegistry(FunctionContributions functionContributio
functionFactory.xmlagg();

functionFactory.unnest_emulated();
functionFactory.generateSeries_recursive( getMaximumSeriesSize(), false, true );
}

/**
* DB2 doesn't support the {@code generate_series} function or {@code lateral} recursive CTEs,
* so it has to be emulated with a top level recursive CTE which requires an upper bound on the amount
* of elements that the series can return.
*/
protected int getMaximumSeriesSize() {
return 10000;
}

@Override
Expand Down
Loading

0 comments on commit 82b20a0

Please sign in to comment.