Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

HHH-18731 Add generate_series() set-returning function #9110

Merged
merged 1 commit into from
Oct 23, 2024
Merged
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
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
Loading