HHH-18731 Add generate_series() set-returning function

This commit is contained in:
Christian Beikov 2024-10-18 15:04:39 +02:00
parent 5bd244dd20
commit 82b20a0e90
75 changed files with 3975 additions and 209 deletions

View File

@ -60,10 +60,11 @@ elif [ "$RDBMS" == "db2_10_5" ]; then
goal="-Pdb=db2" goal="-Pdb=db2"
elif [ "$RDBMS" == "mssql" ] || [ "$RDBMS" == "mssql_2017" ]; then elif [ "$RDBMS" == "mssql" ] || [ "$RDBMS" == "mssql_2017" ]; then
goal="-Pdb=mssql_ci" 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 elif [ "$RDBMS" == "sybase" ]; then
goal="-Pdb=sybase_ci" goal="-Pdb=sybase_ci -PexcludeTests=**.GenerateSeriesTest*"
elif [ "$RDBMS" == "sybase_jconn" ]; then elif [ "$RDBMS" == "sybase_jconn" ]; then
goal="-Pdb=sybase_jconn_ci" goal="-Pdb=sybase_jconn_ci -PexcludeTests=**.GenerateSeriesTest*"
elif [ "$RDBMS" == "tidb" ]; then elif [ "$RDBMS" == "tidb" ]; then
goal="-Pdb=tidb" goal="-Pdb=tidb"
elif [ "$RDBMS" == "hana_cloud" ]; then elif [ "$RDBMS" == "hana_cloud" ]; then

View File

@ -2958,7 +2958,7 @@ The following set-returning functions are available on many platforms:
| Function | purpose | Function | purpose
| <<hql-array-unnest,`unnest()`>> | Turns an array into rows | <<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`: To use set returning functions defined in the database, it is required to register them in a `FunctionContributor`:
@ -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()`, Hibernate ORM tries to emulate this feature by wrapping invocations as lateral subqueries and using `row_number()`,
which may lead to worse performance. 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]] [[hql-join]]
=== Declaring joined entities === Declaring joined entities

View File

@ -518,6 +518,7 @@ public class CockroachLegacyDialect extends Dialect {
functionFactory.jsonArrayInsert_postgresql(); functionFactory.jsonArrayInsert_postgresql();
functionFactory.unnest_postgresql(); functionFactory.unnest_postgresql();
functionFactory.generateSeries( null, "ordinality", true );
// Postgres uses # instead of ^ for XOR // Postgres uses # instead of ^ for XOR
functionContributions.getFunctionRegistry().patternDescriptorBuilder( "bitxor", "(?1#?2)" ) functionContributions.getFunctionRegistry().patternDescriptorBuilder( "bitxor", "(?1#?2)" )

View File

@ -458,6 +458,18 @@ public class DB2LegacyDialect extends Dialect {
functionFactory.xmlagg(); functionFactory.xmlagg();
functionFactory.unnest_emulated(); 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 @Override

View File

@ -428,6 +428,7 @@ public class H2LegacyDialect extends Dialect {
} }
functionFactory.unnest_h2( getMaximumArraySize() ); functionFactory.unnest_h2( getMaximumArraySize() );
functionFactory.generateSeries_h2( getMaximumSeriesSize() );
} }
/** /**
@ -440,6 +441,16 @@ public class H2LegacyDialect extends Dialect {
return 1000; 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 @Override
public @Nullable String getDefaultOrdinalityColumnName() { public @Nullable String getDefaultOrdinalityColumnName() {
return "nord"; return "nord";

View File

@ -497,6 +497,8 @@ public class HANALegacyDialect extends Dialect {
functionFactory.unnest_hana(); functionFactory.unnest_hana();
// functionFactory.json_table(); // functionFactory.json_table();
functionFactory.generateSeries_hana( getMaximumSeriesSize() );
if ( getVersion().isSameOrAfter(2, 0, 20 ) ) { if ( getVersion().isSameOrAfter(2, 0, 20 ) ) {
if ( getVersion().isSameOrAfter( 2, 0, 40 ) ) { if ( getVersion().isSameOrAfter( 2, 0, 40 ) ) {
// Introduced in 2.0 SPS 04 // Introduced in 2.0 SPS 04
@ -513,6 +515,14 @@ public class HANALegacyDialect extends Dialect {
} }
} }
/**
* 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 @Override
public SqlAstTranslatorFactory getSqlAstTranslatorFactory() { public SqlAstTranslatorFactory getSqlAstTranslatorFactory() {
return new StandardSqlAstTranslatorFactory() { return new StandardSqlAstTranslatorFactory() {

View File

@ -279,6 +279,7 @@ public class HSQLLegacyDialect extends Dialect {
} }
functionFactory.unnest( "c1", "c2" ); functionFactory.unnest( "c1", "c2" );
functionFactory.generateSeries_recursive( getMaximumSeriesSize(), true, false );
//trim() requires parameters to be cast when used as trim character //trim() requires parameters to be cast when used as trim character
functionContributions.getFunctionRegistry().register( "trim", new TrimFunction( functionContributions.getFunctionRegistry().register( "trim", new TrimFunction(
@ -288,6 +289,16 @@ public class HSQLLegacyDialect extends Dialect {
) ); ) );
} }
/**
* 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 @Override
public @Nullable String getDefaultOrdinalityColumnName() { public @Nullable String getDefaultOrdinalityColumnName() {
return "c2"; return "c2";

View File

@ -675,9 +675,22 @@ public class MySQLLegacyDialect extends Dialect {
if ( getMySQLVersion().isSameOrAfter( 8 ) ) { if ( getMySQLVersion().isSameOrAfter( 8 ) ) {
functionFactory.unnest_emulated(); 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 @Override
public void contributeTypes(TypeContributions typeContributions, ServiceRegistry serviceRegistry) { public void contributeTypes(TypeContributions typeContributions, ServiceRegistry serviceRegistry) {
super.contributeTypes( typeContributions, serviceRegistry ); super.contributeTypes( typeContributions, serviceRegistry );

View File

@ -335,6 +335,16 @@ public class OracleLegacyDialect extends Dialect {
functionFactory.xmlagg(); functionFactory.xmlagg();
functionFactory.unnest_oracle(); 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 @Override

View File

@ -711,6 +711,7 @@ public class PostgreSQLLegacyDialect extends Dialect {
else { else {
functionFactory.unnest_postgresql(); functionFactory.unnest_postgresql();
} }
functionFactory.generateSeries( null, "ordinality", false );
} }
@Override @Override

View File

@ -440,6 +440,7 @@ public class SQLServerLegacyDialect extends AbstractTransactSQLDialect {
functionFactory.leastGreatest(); functionFactory.leastGreatest();
functionFactory.dateTrunc_datetrunc(); functionFactory.dateTrunc_datetrunc();
functionFactory.trunc_round_datetrunc(); functionFactory.trunc_round_datetrunc();
functionFactory.generateSeries_sqlserver( getMaximumSeriesSize() );
} }
else { else {
functionContributions.getFunctionRegistry().register( functionContributions.getFunctionRegistry().register(
@ -447,6 +448,24 @@ public class SQLServerLegacyDialect extends AbstractTransactSQLDialect {
new SqlServerConvertTruncFunction( functionContributions.getTypeConfiguration() ) new SqlServerConvertTruncFunction( functionContributions.getTypeConfiguration() )
); );
functionContributions.getFunctionRegistry().registerAlternateKey( "truncate", "trunc" ); 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;
} }
} }

View File

@ -166,6 +166,17 @@ public class SybaseASELegacyDialect extends SybaseLegacyDialect {
CommonFunctionFactory functionFactory = new CommonFunctionFactory( functionContributions); CommonFunctionFactory functionFactory = new CommonFunctionFactory( functionContributions);
functionFactory.unnest_sybasease(); 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) { private static boolean isAnsiNull(DatabaseMetaData databaseMetaData) {

View File

@ -1,8 +1,6 @@
/* /*
* Hibernate, Relational Persistence for Idiomatic Java * SPDX-License-Identifier: LGPL-2.1-or-later
* * Copyright Red Hat Inc. and Hibernate Authors
* 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.
*/ */
package org.hibernate.boot.models.annotations.internal; package org.hibernate.boot.models.annotations.internal;

View File

@ -486,6 +486,7 @@ public class CockroachDialect extends Dialect {
functionFactory.jsonArrayInsert_postgresql(); functionFactory.jsonArrayInsert_postgresql();
functionFactory.unnest_postgresql(); functionFactory.unnest_postgresql();
functionFactory.generateSeries( null, "ordinality", true );
// Postgres uses # instead of ^ for XOR // Postgres uses # instead of ^ for XOR
functionContributions.getFunctionRegistry().patternDescriptorBuilder( "bitxor", "(?1#?2)" ) functionContributions.getFunctionRegistry().patternDescriptorBuilder( "bitxor", "(?1#?2)" )

View File

@ -443,6 +443,16 @@ public class DB2Dialect extends Dialect {
functionFactory.xmlagg(); functionFactory.xmlagg();
functionFactory.unnest_emulated(); 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 @Override

View File

@ -185,7 +185,10 @@ import java.time.LocalDate;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.time.LocalTime; import java.time.LocalTime;
import java.time.OffsetDateTime; import java.time.OffsetDateTime;
import java.time.Period;
import java.time.temporal.ChronoUnit;
import java.time.temporal.TemporalAccessor; import java.time.temporal.TemporalAccessor;
import java.time.temporal.TemporalAmount;
import java.util.Calendar; import java.util.Calendar;
import java.util.Date; import java.util.Date;
import java.util.HashSet; import java.util.HashSet;
@ -5618,11 +5621,102 @@ public abstract class Dialect implements ConversionContext, TypeContributor, Fun
* {@link Duration}. * {@link Duration}.
*/ */
public void appendIntervalLiteral(SqlAppender appender, Duration literal) { public void appendIntervalLiteral(SqlAppender appender, Duration literal) {
final int nano = literal.getNano();
final int secondsPart = literal.toSecondsPart();
final int minutesPart = literal.toMinutesPart();
final int hoursPart = literal.toHoursPart();
final long daysPart = literal.toDaysPart();
enum Unit { day, hour, minute }
final Unit unit;
if ( daysPart != 0 ) {
unit = hoursPart == 0 && minutesPart == 0 && secondsPart == 0 && nano == 0
? Unit.day
: null;
}
else if ( hoursPart != 0 ) {
unit = minutesPart == 0 && secondsPart == 0 && nano == 0
? Unit.hour
: null;
}
else if ( minutesPart != 0 ) {
unit = secondsPart == 0 && nano == 0
? Unit.minute
: null;
}
else {
unit = null;
}
appender.appendSql( "interval '" ); appender.appendSql( "interval '" );
appender.appendSql( literal.getSeconds() ); if ( unit != null ) {
appender.appendSql( '.' ); appender.appendSql( switch( unit ) {
appender.appendSql( literal.getNano() ); case day -> daysPart;
appender.appendSql( "' second" ); case hour -> hoursPart;
case minute -> minutesPart;
});
appender.appendSql( "' " );
appender.appendSql( unit.toString() );
}
else {
appender.appendSql( "interval '" );
appender.appendSql( literal.getSeconds() );
if ( nano > 0 ) {
appender.appendSql( '.' );
appender.appendSql( nano );
}
appender.appendSql( "' second" );
}
}
/**
* Append a literal SQL {@code interval} representing the given Java
* {@link TemporalAmount}.
*/
public void appendIntervalLiteral(SqlAppender appender, TemporalAmount literal) {
if ( literal instanceof Duration duration ) {
appendIntervalLiteral( appender, duration );
}
else if ( literal instanceof Period period ) {
final int years = period.getYears();
final int months = period.getMonths();
final int days = period.getDays();
final boolean parenthesis = years != 0 && months != 0
|| years != 0 && days != 0
|| months != 0 && days != 0;
if ( parenthesis ) {
appender.appendSql( '(' );
}
boolean first = true;
for ( java.time.temporal.TemporalUnit unit : literal.getUnits() ) {
final long value = literal.get( unit );
if ( value != 0 ) {
if ( first ) {
first = false;
}
else {
appender.appendSql( "+" );
}
appender.appendSql( "interval '" );
appender.appendSql( value );
appender.appendSql( "' " );
if ( unit == ChronoUnit.YEARS ) {
appender.appendSql( "year" );
}
else if ( unit == ChronoUnit.MONTHS ) {
appender.appendSql( "month" );
}
else {
assert unit == ChronoUnit.DAYS;
appender.appendSql( "day" );
}
}
}
if ( parenthesis ) {
appender.appendSql( ')' );
}
}
else {
throw new IllegalArgumentException( "Unsupported temporal amount type: " + literal );
}
} }
/** /**

View File

@ -10,6 +10,7 @@ import java.sql.ResultSet;
import java.sql.SQLException; import java.sql.SQLException;
import java.time.Duration; import java.time.Duration;
import java.time.temporal.TemporalAccessor; import java.time.temporal.TemporalAccessor;
import java.time.temporal.TemporalAmount;
import java.util.Calendar; import java.util.Calendar;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
@ -20,6 +21,7 @@ import java.util.Set;
import java.util.TimeZone; import java.util.TimeZone;
import java.util.UUID; import java.util.UUID;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.hibernate.Incubating; import org.hibernate.Incubating;
import org.hibernate.LockMode; import org.hibernate.LockMode;
import org.hibernate.LockOptions; import org.hibernate.LockOptions;
@ -46,6 +48,7 @@ import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.exception.spi.SQLExceptionConversionDelegate; import org.hibernate.exception.spi.SQLExceptionConversionDelegate;
import org.hibernate.exception.spi.ViolatedConstraintNameExtractor; import org.hibernate.exception.spi.ViolatedConstraintNameExtractor;
import org.hibernate.loader.ast.spi.MultiKeyLoadSizingStrategy; import org.hibernate.loader.ast.spi.MultiKeyLoadSizingStrategy;
import org.hibernate.mapping.CheckConstraint;
import org.hibernate.mapping.Column; import org.hibernate.mapping.Column;
import org.hibernate.mapping.ForeignKey; import org.hibernate.mapping.ForeignKey;
import org.hibernate.mapping.Index; import org.hibernate.mapping.Index;
@ -1596,6 +1599,11 @@ public class DialectDelegateWrapper extends Dialect {
wrapped.appendIntervalLiteral( appender, literal ); wrapped.appendIntervalLiteral( appender, literal );
} }
@Override
public void appendIntervalLiteral(SqlAppender appender, TemporalAmount literal) {
wrapped.appendIntervalLiteral( appender, literal );
}
@Override @Override
public void appendUUIDLiteral(SqlAppender appender, UUID literal) { public void appendUUIDLiteral(SqlAppender appender, UUID literal) {
wrapped.appendUUIDLiteral( appender, literal ); wrapped.appendUUIDLiteral( appender, literal );
@ -1625,4 +1633,216 @@ public class DialectDelegateWrapper extends Dialect {
public String getRowIdColumnString(String rowId) { public String getRowIdColumnString(String rowId) {
return wrapped.getRowIdColumnString( rowId ); return wrapped.getRowIdColumnString( rowId );
} }
@Override
public DatabaseVersion determineDatabaseVersion(DialectResolutionInfo info) {
return wrapped.determineDatabaseVersion( info );
}
@Override
public boolean isLob(int sqlTypeCode) {
return wrapped.isLob( sqlTypeCode );
}
@Override
public String getEnumTypeDeclaration(Class<? extends Enum<?>> enumType) {
return wrapped.getEnumTypeDeclaration( enumType );
}
@Override
public String[] getCreateEnumTypeCommand(String name, String[] values) {
return wrapped.getCreateEnumTypeCommand( name, values );
}
@Override
public String[] getCreateEnumTypeCommand(Class<? extends Enum<?>> enumType) {
return wrapped.getCreateEnumTypeCommand( enumType );
}
@Override
public String[] getDropEnumTypeCommand(String name) {
return wrapped.getDropEnumTypeCommand( name );
}
@Override
public String[] getDropEnumTypeCommand(Class<? extends Enum<?>> enumType) {
return wrapped.getDropEnumTypeCommand( enumType );
}
@Override
public String getCheckCondition(String columnName, Class<? extends Enum<?>> enumType) {
return wrapped.getCheckCondition( columnName, enumType );
}
@Deprecated(since = "6.5", forRemoval = true)
@Override
public String getCheckCondition(String columnName, long[] values) {
return wrapped.getCheckCondition( columnName, values );
}
@Override
public String getCheckCondition(String columnName, Long[] values) {
return wrapped.getCheckCondition( columnName, values );
}
@Override
public String getCheckCondition(String columnName, Set<?> valueSet, JdbcType jdbcType) {
return wrapped.getCheckCondition( columnName, valueSet, jdbcType );
}
@Override
public String buildStringToBooleanCast(String trueValue, String falseValue) {
return wrapped.buildStringToBooleanCast( trueValue, falseValue );
}
@Override
public String buildStringToBooleanCastDecode(String trueValue, String falseValue) {
return wrapped.buildStringToBooleanCastDecode( trueValue, falseValue );
}
@Override
public String buildStringToBooleanDecode(String trueValue, String falseValue) {
return wrapped.buildStringToBooleanDecode( trueValue, falseValue );
}
@Override
public String getDual() {
return wrapped.getDual();
}
@Override
public String getFromDualForSelectOnly() {
return wrapped.getFromDualForSelectOnly();
}
@Deprecated(since = "7.0", forRemoval = true)
@Override
public String getNativeIdentifierGeneratorStrategy() {
return wrapped.getNativeIdentifierGeneratorStrategy();
}
@Override
public int getTimeoutInSeconds(int millis) {
return wrapped.getTimeoutInSeconds( millis );
}
@Override
public String getBeforeDropStatement() {
return wrapped.getBeforeDropStatement();
}
@Override
public boolean useCrossReferenceForeignKeys() {
return wrapped.useCrossReferenceForeignKeys();
}
@Override
public String getCrossReferenceParentTableFilter() {
return wrapped.getCrossReferenceParentTableFilter();
}
@Override
public boolean supportsIsTrue() {
return wrapped.supportsIsTrue();
}
@Override
public String quoteCollation(String collation) {
return wrapped.quoteCollation( collation );
}
@Override
public boolean supportsInsertReturningRowId() {
return wrapped.supportsInsertReturningRowId();
}
@Override
public boolean supportsUpdateReturning() {
return wrapped.supportsUpdateReturning();
}
@Override
public boolean unquoteGetGeneratedKeys() {
return wrapped.unquoteGetGeneratedKeys();
}
@Override
public boolean supportsNationalizedMethods() {
return wrapped.supportsNationalizedMethods();
}
@Override
public boolean useArrayForMultiValuedParameters() {
return wrapped.useArrayForMultiValuedParameters();
}
@Override
public boolean supportsConflictClauseForInsertCTE() {
return wrapped.supportsConflictClauseForInsertCTE();
}
@Override
public boolean supportsFromClauseInUpdate() {
return wrapped.supportsFromClauseInUpdate();
}
@Override
public int getDefaultIntervalSecondScale() {
return wrapped.getDefaultIntervalSecondScale();
}
@Override
public boolean doesRoundTemporalOnOverflow() {
return wrapped.doesRoundTemporalOnOverflow();
}
@Override
public Boolean supportsBatchUpdates() {
return wrapped.supportsBatchUpdates();
}
@Override
public Boolean supportsRefCursors() {
return wrapped.supportsRefCursors();
}
@Override
public @Nullable String getDefaultOrdinalityColumnName() {
return wrapped.getDefaultOrdinalityColumnName();
}
@Override
public DmlTargetColumnQualifierSupport getDmlTargetColumnQualifierSupport() {
return wrapped.getDmlTargetColumnQualifierSupport();
}
@Override
public FunctionalDependencyAnalysisSupport getFunctionalDependencyAnalysisSupport() {
return wrapped.getFunctionalDependencyAnalysisSupport();
}
@Override
public String getCheckConstraintString(CheckConstraint checkConstraint) {
return wrapped.getCheckConstraintString( checkConstraint );
}
@Override
public String appendCheckConstraintOptions(CheckConstraint checkConstraint, String sqlCheckConstraint) {
return wrapped.appendCheckConstraintOptions( checkConstraint, sqlCheckConstraint );
}
@Override
public boolean supportsTableOptions() {
return wrapped.supportsTableOptions();
}
@Override
public boolean supportsBindingNullSqlTypeForSetNull() {
return wrapped.supportsBindingNullSqlTypeForSetNull();
}
@Override
public boolean supportsBindingNullForSetObject() {
return wrapped.supportsBindingNullForSetObject();
}
} }

View File

@ -359,6 +359,7 @@ public class H2Dialect extends Dialect {
functionFactory.xmlpi_h2(); functionFactory.xmlpi_h2();
functionFactory.unnest_h2( getMaximumArraySize() ); functionFactory.unnest_h2( getMaximumArraySize() );
functionFactory.generateSeries_h2( getMaximumSeriesSize() );
} }
/** /**
@ -371,6 +372,16 @@ public class H2Dialect extends Dialect {
return 1000; 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 @Override
public @Nullable String getDefaultOrdinalityColumnName() { public @Nullable String getDefaultOrdinalityColumnName() {
return "nord"; return "nord";

View File

@ -497,23 +497,32 @@ public class HANADialect extends Dialect {
typeConfiguration typeConfiguration
); );
// Introduced in 2.0 SPS 00 // Introduced in 2.0 SPS 00
functionFactory.jsonValue_no_passing(); functionFactory.jsonValue_no_passing();
functionFactory.jsonQuery_no_passing(); functionFactory.jsonQuery_no_passing();
functionFactory.jsonExists_hana(); functionFactory.jsonExists_hana();
functionFactory.unnest_hana(); functionFactory.unnest_hana();
// functionFactory.json_table(); // functionFactory.json_table();
// Introduced in 2.0 SPS 04 // Introduced in 2.0 SPS 04
functionFactory.jsonObject_hana(); functionFactory.jsonObject_hana();
functionFactory.jsonArray_hana(); functionFactory.jsonArray_hana();
functionFactory.jsonArrayAgg_hana(); functionFactory.jsonArrayAgg_hana();
functionFactory.jsonObjectAgg_hana(); functionFactory.jsonObjectAgg_hana();
// functionFactory.xmltable(); // functionFactory.xmltable();
// functionFactory.xmlextract(); // functionFactory.xmlextract();
functionFactory.generateSeries_hana( getMaximumSeriesSize() );
}
/**
* 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 @Override

View File

@ -214,6 +214,7 @@ public class HSQLDialect extends Dialect {
} }
functionFactory.unnest( "c1", "c2" ); functionFactory.unnest( "c1", "c2" );
functionFactory.generateSeries_recursive( getMaximumSeriesSize(), true, false );
//trim() requires parameters to be cast when used as trim character //trim() requires parameters to be cast when used as trim character
functionContributions.getFunctionRegistry().register( "trim", new TrimFunction( functionContributions.getFunctionRegistry().register( "trim", new TrimFunction(
@ -223,6 +224,16 @@ public class HSQLDialect extends Dialect {
) ); ) );
} }
/**
* 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 @Override
public @Nullable String getDefaultOrdinalityColumnName() { public @Nullable String getDefaultOrdinalityColumnName() {
return "c2"; return "c2";

View File

@ -28,6 +28,7 @@ import org.hibernate.sql.ast.tree.from.NamedTableReference;
import org.hibernate.sql.ast.tree.insert.ConflictClause; import org.hibernate.sql.ast.tree.insert.ConflictClause;
import org.hibernate.sql.ast.tree.insert.InsertSelectStatement; import org.hibernate.sql.ast.tree.insert.InsertSelectStatement;
import org.hibernate.sql.ast.tree.predicate.BooleanExpressionPredicate; import org.hibernate.sql.ast.tree.predicate.BooleanExpressionPredicate;
import org.hibernate.sql.ast.tree.predicate.ComparisonPredicate;
import org.hibernate.sql.ast.tree.predicate.InArrayPredicate; import org.hibernate.sql.ast.tree.predicate.InArrayPredicate;
import org.hibernate.sql.ast.tree.select.QueryPart; import org.hibernate.sql.ast.tree.select.QueryPart;
import org.hibernate.sql.ast.tree.update.UpdateStatement; import org.hibernate.sql.ast.tree.update.UpdateStatement;
@ -288,6 +289,22 @@ public class HSQLSqlAstTranslator<T extends JdbcOperation> extends AbstractSqlAs
emulateSelectTupleComparison( lhsExpressions, tuple.getExpressions(), operator, true ); emulateSelectTupleComparison( lhsExpressions, tuple.getExpressions(), operator, true );
} }
@Override
public void visitRelationalPredicate(ComparisonPredicate comparisonPredicate) {
if ( isParameter( comparisonPredicate.getLeftHandExpression() )
&& isParameter( comparisonPredicate.getRightHandExpression() ) ) {
// HSQLDB doesn't like comparing two parameters with each other
withParameterRenderingMode(
SqlAstNodeRenderingMode.NO_PLAIN_PARAMETER,
() -> super.visitRelationalPredicate( comparisonPredicate )
);
}
else {
super.visitRelationalPredicate( comparisonPredicate );
}
}
@Override
protected void renderComparison(Expression lhs, ComparisonOperator operator, Expression rhs) { protected void renderComparison(Expression lhs, ComparisonOperator operator, Expression rhs) {
final JdbcMappingContainer lhsExpressionType = lhs.getExpressionType(); final JdbcMappingContainer lhsExpressionType = lhs.getExpressionType();
if ( lhsExpressionType == null || lhsExpressionType.getJdbcTypeCount() != 1 ) { if ( lhsExpressionType == null || lhsExpressionType.getJdbcTypeCount() != 1 ) {

View File

@ -660,6 +660,19 @@ public class MySQLDialect extends Dialect {
if ( getMySQLVersion().isSameOrAfter( 8 ) ) { if ( getMySQLVersion().isSameOrAfter( 8 ) ) {
functionFactory.unnest_emulated(); 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 @Override

View File

@ -423,6 +423,16 @@ public class OracleDialect extends Dialect {
functionFactory.xmlagg(); functionFactory.xmlagg();
functionFactory.unnest_oracle(); 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 @Override

View File

@ -671,6 +671,7 @@ public class PostgreSQLDialect extends Dialect {
else { else {
functionFactory.unnest_postgresql(); functionFactory.unnest_postgresql();
} }
functionFactory.generateSeries( null, "ordinality", false );
} }
@Override @Override

View File

@ -457,6 +457,7 @@ public class SQLServerDialect extends AbstractTransactSQLDialect {
functionFactory.leastGreatest(); functionFactory.leastGreatest();
functionFactory.dateTrunc_datetrunc(); functionFactory.dateTrunc_datetrunc();
functionFactory.trunc_round_datetrunc(); functionFactory.trunc_round_datetrunc();
functionFactory.generateSeries_sqlserver( getMaximumSeriesSize() );
} }
else { else {
functionContributions.getFunctionRegistry().register( functionContributions.getFunctionRegistry().register(
@ -464,6 +465,24 @@ public class SQLServerDialect extends AbstractTransactSQLDialect {
new SqlServerConvertTruncFunction( functionContributions.getTypeConfiguration() ) new SqlServerConvertTruncFunction( functionContributions.getTypeConfiguration() )
); );
functionContributions.getFunctionRegistry().registerAlternateKey( "truncate", "trunc" ); 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;
} }
} }

View File

@ -183,6 +183,17 @@ public class SybaseASEDialect extends SybaseDialect {
CommonFunctionFactory functionFactory = new CommonFunctionFactory( functionContributions); CommonFunctionFactory functionFactory = new CommonFunctionFactory( functionContributions);
functionFactory.unnest_sybasease(); 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;
} }
@Override @Override

View File

@ -4287,4 +4287,46 @@ public class CommonFunctionFactory {
public void unnest_hana() { public void unnest_hana() {
functionRegistry.register( "unnest", new HANAUnnestFunction() ); functionRegistry.register( "unnest", new HANAUnnestFunction() );
} }
/**
* Standard generate_series() function
*/
public void generateSeries(@Nullable String defaultValueColumnName, String defaultIndexSelectionExpression, boolean coerceToTimestamp) {
functionRegistry.register( "generate_series", new GenerateSeriesFunction( defaultValueColumnName, defaultIndexSelectionExpression, coerceToTimestamp, typeConfiguration ) );
}
/**
* Recursive CTE generate_series() function
*/
public void generateSeries_recursive(int maxSeriesSize, boolean supportsInterval, boolean coerceToTimestamp) {
functionRegistry.register( "generate_series", new CteGenerateSeriesFunction( maxSeriesSize, supportsInterval, coerceToTimestamp, typeConfiguration ) );
}
/**
* H2 generate_series() function
*/
public void generateSeries_h2(int maxSeriesSize) {
functionRegistry.register( "generate_series", new H2GenerateSeriesFunction( maxSeriesSize, typeConfiguration ) );
}
/**
* SQL Server generate_series() function
*/
public void generateSeries_sqlserver(int maxSeriesSize) {
functionRegistry.register( "generate_series", new SQLServerGenerateSeriesFunction( maxSeriesSize, typeConfiguration ) );
}
/**
* Sybase ASE generate_series() function
*/
public void generateSeries_sybasease(int maxSeriesSize) {
functionRegistry.register( "generate_series", new SybaseASEGenerateSeriesFunction( maxSeriesSize, typeConfiguration ) );
}
/**
* HANA generate_series() function
*/
public void generateSeries_hana(int maxSeriesSize) {
functionRegistry.register( "generate_series", new HANAGenerateSeriesFunction( maxSeriesSize, typeConfiguration ) );
}
} }

View File

@ -0,0 +1,441 @@
/*
* SPDX-License-Identifier: LGPL-2.1-or-later
* Copyright Red Hat Inc. and Hibernate Authors
*/
package org.hibernate.dialect.function;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.hibernate.metamodel.mapping.BasicValuedMapping;
import org.hibernate.metamodel.mapping.CollectionPart;
import org.hibernate.metamodel.mapping.ModelPart;
import org.hibernate.metamodel.mapping.SelectableMapping;
import org.hibernate.query.derived.AnonymousTupleTableGroupProducer;
import org.hibernate.query.derived.AnonymousTupleType;
import org.hibernate.query.spi.QueryEngine;
import org.hibernate.query.sqm.BinaryArithmeticOperator;
import org.hibernate.query.sqm.ComparisonOperator;
import org.hibernate.query.sqm.SetOperator;
import org.hibernate.query.sqm.function.SelfRenderingSqmSetReturningFunction;
import org.hibernate.query.sqm.sql.SqmToSqlAstConverter;
import org.hibernate.query.sqm.tree.SqmTypedNode;
import org.hibernate.query.sqm.tree.expression.NumericTypeCategory;
import org.hibernate.spi.NavigablePath;
import org.hibernate.sql.ast.SqlAstTranslator;
import org.hibernate.sql.ast.spi.SqlAppender;
import org.hibernate.sql.ast.tree.SqlAstNode;
import org.hibernate.sql.ast.tree.cte.CteColumn;
import org.hibernate.sql.ast.tree.cte.CteContainer;
import org.hibernate.sql.ast.tree.cte.CteStatement;
import org.hibernate.sql.ast.tree.cte.CteTable;
import org.hibernate.sql.ast.tree.cte.CteTableGroup;
import org.hibernate.sql.ast.tree.expression.BinaryArithmeticExpression;
import org.hibernate.sql.ast.tree.expression.ColumnReference;
import org.hibernate.sql.ast.tree.expression.Expression;
import org.hibernate.sql.ast.tree.expression.UnparsedNumericLiteral;
import org.hibernate.sql.ast.tree.from.FunctionTableGroup;
import org.hibernate.sql.ast.tree.from.NamedTableReference;
import org.hibernate.sql.ast.tree.from.QueryPartTableGroup;
import org.hibernate.sql.ast.tree.from.TableGroup;
import org.hibernate.sql.ast.tree.predicate.ComparisonPredicate;
import org.hibernate.sql.ast.tree.predicate.Junction;
import org.hibernate.sql.ast.tree.predicate.Predicate;
import org.hibernate.sql.ast.tree.select.QueryGroup;
import org.hibernate.sql.ast.tree.select.QuerySpec;
import org.hibernate.sql.ast.tree.select.SelectStatement;
import org.hibernate.sql.results.internal.SqlSelectionImpl;
import org.hibernate.type.BasicType;
import org.hibernate.type.SqlTypes;
import org.hibernate.type.descriptor.jdbc.JdbcType;
import org.hibernate.type.spi.TypeConfiguration;
import java.util.List;
/**
* Recursive CTE based generate_series function.
*/
public class CteGenerateSeriesFunction extends NumberSeriesGenerateSeriesFunction {
public CteGenerateSeriesFunction(int maxSeriesSize, boolean supportsIntervals, boolean coerceToTimestamp, TypeConfiguration typeConfiguration) {
super(
new CteGenerateSeriesSetReturningFunctionTypeResolver(),
// Treat durations like intervals to avoid conversions
typeConfiguration.getBasicTypeRegistry().resolve(
java.time.Duration.class,
supportsIntervals ? SqlTypes.INTERVAL_SECOND : SqlTypes.DURATION
),
coerceToTimestamp,
maxSeriesSize
);
}
@Override
protected <T> SelfRenderingSqmSetReturningFunction<T> generateSqmSetReturningFunctionExpression(List<? extends SqmTypedNode<?>> arguments, QueryEngine queryEngine) {
//noinspection unchecked
return new SelfRenderingSqmSetReturningFunction<>(
this,
this,
arguments,
getArgumentsValidator(),
getSetReturningTypeResolver(),
(AnonymousTupleType<T>) getSetReturningTypeResolver().resolveTupleType( arguments, queryEngine.getTypeConfiguration() ),
queryEngine.getCriteriaBuilder(),
getName()
) {
@Override
public TableGroup convertToSqlAst(NavigablePath navigablePath, String identifierVariable, boolean lateral, boolean canUseInnerJoins, boolean withOrdinality, SqmToSqlAstConverter walker) {
final FunctionTableGroup tableGroup = (FunctionTableGroup) super.convertToSqlAst(
navigablePath,
identifierVariable,
lateral,
canUseInnerJoins,
withOrdinality,
walker
);
final AnonymousTupleTableGroupProducer tableGroupProducer = (AnonymousTupleTableGroupProducer) tableGroup.getModelPart();
if ( !lateral ) {
return new QueryPartTableGroup(
navigablePath,
tableGroupProducer,
createCteSubquery( tableGroup, walker ),
identifierVariable,
tableGroupProducer.getColumnNames(),
tableGroup.getPrimaryTableReference().getCompatibleTableExpressions(),
lateral,
canUseInnerJoins,
walker.getCreationContext().getSessionFactory()
);
}
else {
final CteTableGroup cteTableGroup = new CteTableGroup(
canUseInnerJoins,
navigablePath,
null,
tableGroupProducer,
new NamedTableReference( CteGenerateSeriesQueryTransformer.NAME, identifierVariable ),
tableGroupProducer.getCompatibleTableExpressions()
);
walker.registerQueryTransformer( new CteGenerateSeriesQueryTransformer(
tableGroup,
cteTableGroup,
maxSeriesSize,
"i",
coerceToTimestamp
) );
return cteTableGroup;
}
}
};
}
protected static class CteGenerateSeriesQueryTransformer extends NumberSeriesQueryTransformer {
public static final String NAME = "max_series";
protected final int maxSeriesSize;
public CteGenerateSeriesQueryTransformer(FunctionTableGroup functionTableGroup, TableGroup targetTableGroup, int maxSeriesSize, String positionColumnName, boolean coerceToTimestamp) {
super( functionTableGroup, targetTableGroup, positionColumnName, coerceToTimestamp );
this.maxSeriesSize = maxSeriesSize;
}
@Override
public QuerySpec transform(CteContainer cteContainer, QuerySpec querySpec, SqmToSqlAstConverter converter) {
// First add the CTE that creates the series
if ( cteContainer.getCteStatement( CteGenerateSeriesQueryTransformer.NAME ) == null ) {
cteContainer.addCteStatement( createSeriesCte( converter ) );
}
return super.transform( cteContainer, querySpec, converter );
}
protected CteStatement createSeriesCte(SqmToSqlAstConverter converter) {
final BasicType<Long> longType = converter.getCreationContext().getTypeConfiguration()
.getBasicTypeForJavaType( Long.class );
final Expression one = new UnparsedNumericLiteral<>( "1", NumericTypeCategory.LONG, longType );
final List<CteColumn> cteColumns = List.of( new CteColumn( "i", longType ) );
final QuerySpec cteStart = new QuerySpec( false );
cteStart.getSelectClause().addSqlSelection( new SqlSelectionImpl( one ) );
final QuerySpec cteUnion = new QuerySpec( false );
final CteTableGroup cteTableGroup = new CteTableGroup( new NamedTableReference( CteGenerateSeriesQueryTransformer.NAME, "t" ) );
cteUnion.getFromClause().addRoot( cteTableGroup );
final ColumnReference tIndex = new ColumnReference( cteTableGroup.getPrimaryTableReference(), "i", longType );
final Expression nextValue = new BinaryArithmeticExpression(
tIndex,
BinaryArithmeticOperator.ADD,
one,
longType
);
cteUnion.getSelectClause().addSqlSelection( new SqlSelectionImpl( nextValue ) );
cteUnion.applyPredicate(
new ComparisonPredicate(
nextValue,
ComparisonOperator.LESS_THAN_OR_EQUAL,
new UnparsedNumericLiteral<>(
Integer.toString( maxSeriesSize ),
NumericTypeCategory.LONG,
longType
)
)
);
final QueryGroup cteContent = new QueryGroup( false, SetOperator.UNION_ALL, List.of( cteStart, cteUnion ) );
final CteStatement cteStatement = new CteStatement(
new CteTable( CteGenerateSeriesQueryTransformer.NAME, cteColumns ),
new SelectStatement( cteContent )
);
cteStatement.setRecursive();
return cteStatement;
}
}
private SelectStatement createCteSubquery(FunctionTableGroup tableGroup, SqmToSqlAstConverter walker) {
final AnonymousTupleTableGroupProducer tableGroupProducer = (AnonymousTupleTableGroupProducer) tableGroup.getModelPart();
final ModelPart indexPart = tableGroupProducer.findSubPart( CollectionPart.Nature.INDEX.getName(), null );
final ModelPart elementPart = tableGroupProducer.findSubPart( CollectionPart.Nature.ELEMENT.getName(), null );
final NumericTypeCategory numericTypeCategory = NumericTypeCategory.BIG_DECIMAL;
final BasicType<?> resultType = (BasicType<?>) elementPart.getSingleJdbcMapping();
final BasicType<Integer> integerType = walker.getCreationContext().getTypeConfiguration()
.getBasicTypeForJavaType( Integer.class );
final BasicType<Boolean> booleanType = walker.getCreationContext().getTypeConfiguration()
.getBasicTypeForJavaType( Boolean.class );
final JdbcType boundType = resultType.getJdbcType();
final boolean castTimestamp = coerceToTimestamp
&& (boundType.getDdlTypeCode() == SqlTypes.DATE || boundType.getDdlTypeCode() == SqlTypes.TIME);
final List<? extends SqlAstNode> arguments = tableGroup.getPrimaryTableReference().getFunctionExpression()
.getArguments();
final Expression start = castTimestamp
? castToTimestamp( arguments.get( 0 ), walker )
: (Expression) arguments.get( 0 );
final Expression stop = castTimestamp
? castToTimestamp( arguments.get( 1 ), walker )
: (Expression) arguments.get( 1 );
final Expression explicitStep = arguments.size() > 2 ? (Expression) arguments.get( 2 ) : null;
final Expression step = explicitStep != null
? explicitStep
: new UnparsedNumericLiteral<>( "1", numericTypeCategory, resultType );
final String cteName = "generate_series";
final List<CteColumn> cteColumns;
if ( indexPart == null ) {
cteColumns = List.of( new CteColumn( "v", resultType ) );
}
else {
cteColumns = List.of(
new CteColumn( "v", resultType ),
new CteColumn( "i", indexPart.getSingleJdbcMapping() )
);
}
// Select the start value and check if the step can progress towards the stop value
final QuerySpec cteStart = new QuerySpec( false );
if ( explicitStep == null ) {
cteStart.getSelectClause().addSqlSelection( new SqlSelectionImpl( start ) );
}
else {
// For explicit steps, we need to add the step 0 times in the initial part of the recursive CTE,
// in order for the database to recognize the correct result type of the CTE column
cteStart.getSelectClause().addSqlSelection( new SqlSelectionImpl( add(
start,
multiply( step, 0, integerType ),
walker
) ) );
}
if ( indexPart != null ) {
// ordinal is 1 based
cteStart.getSelectClause().addSqlSelection( new SqlSelectionImpl(
new UnparsedNumericLiteral<>( "1", NumericTypeCategory.INTEGER, integerType )
) );
}
// Add a predicate to ensure the start value is valid
if ( explicitStep == null ) {
// The default step is 1, so just check if start <= stop
cteStart.applyPredicate(
new ComparisonPredicate(
start,
ComparisonOperator.LESS_THAN_OR_EQUAL,
stop
)
);
}
else {
// When start <= stop, only produce an initial result if the step is positive i.e. step > step*-1
final Predicate positiveProgress = new Junction(
Junction.Nature.CONJUNCTION,
List.of(
new ComparisonPredicate(
start,
ComparisonOperator.LESS_THAN_OR_EQUAL,
stop
),
new ComparisonPredicate(
step,
ComparisonOperator.GREATER_THAN,
multiply( step, -1, integerType )
)
),
booleanType
);
// When start >= stop, only produce an initial result if the step is negative i.e. step > step*-1
final Predicate negativeProgress = new Junction(
Junction.Nature.CONJUNCTION,
List.of(
new ComparisonPredicate(
start,
ComparisonOperator.GREATER_THAN_OR_EQUAL,
stop
),
new ComparisonPredicate(
step,
ComparisonOperator.LESS_THAN,
multiply( step, -1, integerType )
)
),
booleanType
);
cteStart.applyPredicate(
new Junction(
Junction.Nature.DISJUNCTION,
List.of( positiveProgress, negativeProgress ),
booleanType
)
);
}
// The union part just adds the step to the previous value as long as the stop value is not reached
final QuerySpec cteUnion = new QuerySpec( false );
final CteTableGroup cteTableGroup = new CteTableGroup( new NamedTableReference( cteName, "t" ) );
cteUnion.getFromClause().addRoot( cteTableGroup );
final ColumnReference tValue = new ColumnReference( cteTableGroup.getPrimaryTableReference(), "v", resultType );
final ColumnReference tIndex = indexPart == null
? null
: new ColumnReference(
cteTableGroup.getPrimaryTableReference(),
"i",
indexPart.getSingleJdbcMapping()
);
final Expression nextValue = add( tValue, step, walker );
cteUnion.getSelectClause().addSqlSelection( new SqlSelectionImpl( nextValue ) );
if ( tIndex != null ) {
cteUnion.getSelectClause().addSqlSelection( new SqlSelectionImpl( new BinaryArithmeticExpression(
tIndex,
BinaryArithmeticOperator.ADD,
new UnparsedNumericLiteral<>( "1", NumericTypeCategory.INTEGER, integerType ),
(BasicValuedMapping) indexPart.getSingleJdbcMapping()
) ) );
}
// Add a predicate to ensure the current value is valid
if ( explicitStep == null ) {
// The default step is 1, so just check if value <= stop
cteUnion.applyPredicate(
new ComparisonPredicate(
nextValue,
ComparisonOperator.LESS_THAN_OR_EQUAL,
stop
)
);
}
else {
// When start < stop, value is only valid if it's less than or equal to stop
final Predicate positiveProgress = new Junction(
Junction.Nature.CONJUNCTION,
List.of(
new ComparisonPredicate(
start,
ComparisonOperator.LESS_THAN,
stop
),
new ComparisonPredicate(
nextValue,
ComparisonOperator.LESS_THAN_OR_EQUAL,
stop
)
),
booleanType
);
// When start > stop, value is only valid if it's greater than or equal to stop
final Predicate negativeProgress = new Junction(
Junction.Nature.CONJUNCTION,
List.of(
new ComparisonPredicate(
start,
ComparisonOperator.GREATER_THAN,
stop
),
new ComparisonPredicate(
nextValue,
ComparisonOperator.GREATER_THAN_OR_EQUAL,
stop
)
),
booleanType
);
cteUnion.applyPredicate(
new Junction(
Junction.Nature.DISJUNCTION,
List.of( positiveProgress, negativeProgress ),
booleanType
)
);
}
// Main query selects the columns from the CTE
final QueryGroup cteContent = new QueryGroup( false, SetOperator.UNION_ALL, List.of( cteStart, cteUnion ) );
final QuerySpec mainQuery = new QuerySpec( false );
final SelectStatement selectStatement = new SelectStatement( mainQuery );
final CteStatement cteStatement = new CteStatement(
new CteTable( cteName, cteColumns ),
new SelectStatement( cteContent )
);
cteStatement.setRecursive();
selectStatement.addCteStatement( cteStatement );
mainQuery.getFromClause().addRoot( cteTableGroup );
mainQuery.getSelectClause().addSqlSelection( new SqlSelectionImpl( tValue ) );
if ( indexPart != null ) {
mainQuery.getSelectClause().addSqlSelection( new SqlSelectionImpl( tIndex ) );
}
return selectStatement;
}
static class CteGenerateSeriesSetReturningFunctionTypeResolver extends NumberSeriesGenerateSeriesSetReturningFunctionTypeResolver {
public CteGenerateSeriesSetReturningFunctionTypeResolver() {
super( "v", "i" );
}
public CteGenerateSeriesSetReturningFunctionTypeResolver(@Nullable String defaultValueColumnName, String defaultIndexSelectionExpression) {
super( defaultValueColumnName, defaultIndexSelectionExpression );
}
@Override
public SelectableMapping[] resolveFunctionReturnType(
List<? extends SqlAstNode> arguments,
String tableIdentifierVariable,
boolean lateral,
boolean withOrdinality,
SqmToSqlAstConverter converter) {
if ( !lateral ) {
return super.resolveFunctionReturnType( arguments, tableIdentifierVariable, lateral, withOrdinality, converter );
}
else {
return resolveIterationVariableBasedFunctionReturnType( arguments, tableIdentifierVariable, lateral, withOrdinality, converter );
}
}
}
@Override
protected void renderGenerateSeries(
SqlAppender sqlAppender,
Expression start,
Expression stop,
@Nullable Expression step,
AnonymousTupleTableGroupProducer tupleType,
String tableIdentifierVariable,
SqlAstTranslator<?> walker) {
throw new UnsupportedOperationException( "Function expands to custom SQL AST" );
}
}

View File

@ -0,0 +1,68 @@
/*
* SPDX-License-Identifier: LGPL-2.1-or-later
* Copyright Red Hat Inc. and Hibernate Authors
*/
package org.hibernate.dialect.function;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.hibernate.metamodel.mapping.MappingModelExpressible;
import org.hibernate.query.sqm.produce.function.ArgumentsValidator;
import org.hibernate.query.sqm.produce.function.internal.AbstractFunctionArgumentTypeResolver;
import org.hibernate.query.sqm.sql.SqmToSqlAstConverter;
import org.hibernate.query.sqm.tree.SqmTypedNode;
import org.hibernate.query.sqm.tree.expression.SqmExpression;
import org.hibernate.type.BasicType;
import java.time.Duration;
import java.util.List;
/**
* A {@link ArgumentsValidator} that validates the array type is compatible with the element type.
*/
public class GenerateSeriesArgumentTypeResolver extends AbstractFunctionArgumentTypeResolver {
private final BasicType<Duration> durationType;
public GenerateSeriesArgumentTypeResolver(BasicType<Duration> durationType) {
this.durationType = durationType;
}
@Override
public @Nullable MappingModelExpressible<?> resolveFunctionArgumentType(List<? extends SqmTypedNode<?>> arguments, int argumentIndex, SqmToSqlAstConverter converter) {
if ( argumentIndex == 0 ) {
final MappingModelExpressible<?> mappingModelExpressible = converter.resolveFunctionImpliedReturnType();
return mappingModelExpressible != null
? mappingModelExpressible
: converter.determineValueMapping( (SqmExpression<?>) arguments.get( 1 ) );
}
else if ( argumentIndex == 1 ) {
final MappingModelExpressible<?> mappingModelExpressible = converter.resolveFunctionImpliedReturnType();
return mappingModelExpressible != null
? mappingModelExpressible
: converter.determineValueMapping( (SqmExpression<?>) arguments.get( 0 ) );
}
else {
assert argumentIndex == 2;
final MappingModelExpressible<?> implied = converter.resolveFunctionImpliedReturnType();
final MappingModelExpressible<?> firstType;
final MappingModelExpressible<?> resultType;
if ( implied != null ) {
resultType = implied;
}
else if ( (firstType = converter.determineValueMapping( (SqmExpression<?>) arguments.get( 0 ) )) != null ) {
resultType = firstType;
}
else {
resultType = converter.determineValueMapping( (SqmExpression<?>) arguments.get( 1 ) );
}
assert resultType != null;
if ( resultType.getSingleJdbcMapping().getJdbcType().isTemporal() ) {
return durationType;
}
else {
return resultType;
}
}
}
}

View File

@ -0,0 +1,133 @@
/*
* SPDX-License-Identifier: LGPL-2.1-or-later
* Copyright Red Hat Inc. and Hibernate Authors
*/
package org.hibernate.dialect.function;
import org.hibernate.metamodel.mapping.JdbcMapping;
import org.hibernate.metamodel.model.domain.DomainType;
import org.hibernate.query.sqm.SqmExpressible;
import org.hibernate.query.sqm.produce.function.ArgumentsValidator;
import org.hibernate.query.sqm.produce.function.FunctionArgumentException;
import org.hibernate.query.sqm.produce.function.StandardArgumentsValidators;
import org.hibernate.query.sqm.tree.SqmTypedNode;
import org.hibernate.type.descriptor.jdbc.JdbcType;
import org.hibernate.type.spi.TypeConfiguration;
import java.util.List;
import java.util.Locale;
/**
* A {@link ArgumentsValidator} that validates the array type is compatible with the element type.
*/
public class GenerateSeriesArgumentValidator implements ArgumentsValidator {
private final ArgumentsValidator delegate;
public GenerateSeriesArgumentValidator() {
this.delegate = StandardArgumentsValidators.between( 2, 3 );
}
@Override
public void validate(
List<? extends SqmTypedNode<?>> arguments,
String functionName,
TypeConfiguration typeConfiguration) {
delegate.validate( arguments, functionName, typeConfiguration );
final SqmTypedNode<?> start = arguments.get( 0 );
final SqmTypedNode<?> stop = arguments.get( 1 );
final SqmTypedNode<?> step = arguments.size() > 2 ? arguments.get( 2 ) : null;
final SqmExpressible<?> startExpressible = start.getExpressible();
final SqmExpressible<?> stopExpressible = stop.getExpressible();
final SqmExpressible<?> stepExpressible = step == null ? null : step.getExpressible();
final DomainType<?> startType = startExpressible == null ? null : startExpressible.getSqmType();
final DomainType<?> stopType = stopExpressible == null ? null : stopExpressible.getSqmType();
final DomainType<?> stepType = stepExpressible == null ? null : stepExpressible.getSqmType();
if ( startType == null ) {
throw unknownType( functionName, arguments, 0 );
}
if ( stopType == null ) {
throw unknownType( functionName, arguments, 1 );
}
if ( startType != stopType ) {
throw new FunctionArgumentException(
String.format(
"Start and stop parameters of function '%s()' must be of the same type, but found [%s,%s]",
functionName,
startType.getTypeName(),
stopType.getTypeName()
)
);
}
final JdbcMapping type = (JdbcMapping) startType;
final JdbcType jdbcType = type.getJdbcType();
if ( jdbcType.isInteger() || jdbcType.isDecimal() ) {
if ( step != null ) {
if ( stepType == null ) {
throw unknownType( functionName, arguments, 2 );
}
if ( stepType != startType ) {
throw new FunctionArgumentException(
String.format(
"Step parameter of function '%s()' is of type '%s', but must be of the same type as start and stop [%s,%s]",
functionName,
stepType.getTypeName(),
startType.getTypeName(),
stopType.getTypeName()
)
);
}
}
}
else if ( jdbcType.isTemporal() ) {
if ( step == null ) {
throw new FunctionArgumentException(
String.format(
Locale.ROOT,
"Function %s() requires exactly 3 arguments when invoked with a temporal argument, but %d arguments given",
functionName,
arguments.size()
)
);
}
if ( stepType == null ) {
throw unknownType( functionName, arguments, 2 );
}
final JdbcType stepJdbcType = ((JdbcMapping) stepType).getJdbcType();
if ( !stepJdbcType.isInterval() && !stepJdbcType.isDuration() ) {
throw new FunctionArgumentException(
String.format(
"Step parameter of function '%s()' is of type '%s', but must be of type interval",
functionName,
stepType.getTypeName()
)
);
}
}
else {
throw new FunctionArgumentException(
String.format(
"Unsupported type '%s' for function '%s()'. Only integral, decimal and timestamp types are supported.",
startType.getTypeName(),
functionName
)
);
}
}
private FunctionArgumentException unknownType(String functionName, List<? extends SqmTypedNode<?>> arguments, int parameterIndex) {
return new FunctionArgumentException(
String.format(
"Couldn't determine type of parameter %d of function '%s()'. Argument is '%s'",
parameterIndex,
functionName,
arguments.get( parameterIndex )
)
);
}
}

View File

@ -0,0 +1,103 @@
/*
* SPDX-License-Identifier: LGPL-2.1-or-later
* Copyright Red Hat Inc. and Hibernate Authors
*/
package org.hibernate.dialect.function;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.hibernate.metamodel.mapping.CollectionPart;
import org.hibernate.query.derived.AnonymousTupleTableGroupProducer;
import org.hibernate.query.sqm.function.AbstractSqmSelfRenderingSetReturningFunctionDescriptor;
import org.hibernate.query.sqm.produce.function.SetReturningFunctionTypeResolver;
import org.hibernate.sql.ast.SqlAstTranslator;
import org.hibernate.sql.ast.spi.SqlAppender;
import org.hibernate.sql.ast.tree.SqlAstNode;
import org.hibernate.sql.ast.tree.expression.Expression;
import org.hibernate.type.BasicType;
import org.hibernate.type.SqlTypes;
import org.hibernate.type.descriptor.jdbc.JdbcType;
import org.hibernate.type.spi.TypeConfiguration;
import java.time.Duration;
import java.util.List;
/**
* Standard generate_series function.
*/
public class GenerateSeriesFunction extends AbstractSqmSelfRenderingSetReturningFunctionDescriptor {
protected final boolean coerceToTimestamp;
public GenerateSeriesFunction(@Nullable String defaultValueColumnName, String defaultIndexSelectionExpression, boolean coerceToTimestamp, TypeConfiguration typeConfiguration) {
this(
new GenerateSeriesSetReturningFunctionTypeResolver(
defaultValueColumnName,
defaultIndexSelectionExpression
),
// Treat durations like intervals to avoid conversions
typeConfiguration.getBasicTypeRegistry().resolve( java.time.Duration.class, SqlTypes.INTERVAL_SECOND ),
coerceToTimestamp
);
}
protected GenerateSeriesFunction(SetReturningFunctionTypeResolver setReturningFunctionTypeResolver, BasicType<Duration> durationType) {
this( setReturningFunctionTypeResolver, durationType, false );
}
protected GenerateSeriesFunction(SetReturningFunctionTypeResolver setReturningFunctionTypeResolver, BasicType<Duration> durationType, boolean coerceToTimestamp) {
super(
"generate_series",
new GenerateSeriesArgumentValidator(),
setReturningFunctionTypeResolver,
new GenerateSeriesArgumentTypeResolver( durationType )
);
this.coerceToTimestamp = coerceToTimestamp;
}
@Override
public void render(
SqlAppender sqlAppender,
List<? extends SqlAstNode> sqlAstArguments,
AnonymousTupleTableGroupProducer tupleType,
String tableIdentifierVariable,
SqlAstTranslator<?> walker) {
final Expression start = (Expression) sqlAstArguments.get( 0 );
final Expression stop = (Expression) sqlAstArguments.get( 1 );
final Expression step = sqlAstArguments.size() > 2 ? (Expression) sqlAstArguments.get( 2 ) : null;
renderGenerateSeries( sqlAppender, start, stop, step, tupleType, tableIdentifierVariable, walker );
}
protected void renderGenerateSeries(
SqlAppender sqlAppender,
Expression start,
Expression stop,
@Nullable Expression step,
AnonymousTupleTableGroupProducer tupleType,
String tableIdentifierVariable,
SqlAstTranslator<?> walker) {
final JdbcType boundType = start.getExpressionType().getSingleJdbcMapping().getJdbcType();
final boolean castTimestamp = coerceToTimestamp
&& (boundType.getDdlTypeCode() == SqlTypes.DATE || boundType.getDdlTypeCode() == SqlTypes.TIME);
sqlAppender.appendSql( "generate_series(" );
if ( castTimestamp ) {
sqlAppender.appendSql( "cast(" );
start.accept( walker );
sqlAppender.appendSql( " as timestamp),cast(" );
stop.accept( walker );
sqlAppender.appendSql( " as timestamp)" );
}
else {
start.accept( walker );
sqlAppender.appendSql( ',' );
stop.accept( walker );
}
if ( step != null ) {
sqlAppender.appendSql( ',' );
step.accept( walker );
}
sqlAppender.appendSql( ')' );
if ( tupleType.findSubPart( CollectionPart.Nature.INDEX.getName(), null ) != null ) {
sqlAppender.append( " with ordinality" );
}
}
}

View File

@ -0,0 +1,154 @@
/*
* SPDX-License-Identifier: LGPL-2.1-or-later
* Copyright Red Hat Inc. and Hibernate Authors
*/
package org.hibernate.dialect.function;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.hibernate.internal.util.NullnessHelper;
import org.hibernate.metamodel.mapping.CollectionPart;
import org.hibernate.metamodel.mapping.JdbcMapping;
import org.hibernate.metamodel.mapping.JdbcMappingContainer;
import org.hibernate.metamodel.mapping.SelectableMapping;
import org.hibernate.metamodel.mapping.SelectablePath;
import org.hibernate.metamodel.mapping.SqlTypedMapping;
import org.hibernate.metamodel.mapping.internal.SelectableMappingImpl;
import org.hibernate.metamodel.model.domain.DomainType;
import org.hibernate.query.derived.AnonymousTupleType;
import org.hibernate.query.sqm.SqmExpressible;
import org.hibernate.query.sqm.produce.function.SetReturningFunctionTypeResolver;
import org.hibernate.query.sqm.sql.SqmToSqlAstConverter;
import org.hibernate.query.sqm.tree.SqmTypedNode;
import org.hibernate.sql.ast.tree.SqlAstNode;
import org.hibernate.sql.ast.tree.expression.Expression;
import org.hibernate.type.spi.TypeConfiguration;
import java.util.List;
/**
*
* @since 7.0
*/
public class GenerateSeriesSetReturningFunctionTypeResolver implements SetReturningFunctionTypeResolver {
protected final @Nullable String defaultValueColumnName;
protected final String defaultIndexSelectionExpression;
public GenerateSeriesSetReturningFunctionTypeResolver(@Nullable String defaultValueColumnName, String defaultIndexSelectionExpression) {
this.defaultValueColumnName = defaultValueColumnName;
this.defaultIndexSelectionExpression = defaultIndexSelectionExpression;
}
@Override
public AnonymousTupleType<?> resolveTupleType(List<? extends SqmTypedNode<?>> arguments, TypeConfiguration typeConfiguration) {
final SqmTypedNode<?> start = arguments.get( 0 );
final SqmTypedNode<?> stop = arguments.get( 1 );
final SqmExpressible<?> startExpressible = start.getExpressible();
final SqmExpressible<?> stopExpressible = stop.getExpressible();
final DomainType<?> type = NullnessHelper.coalesce(
startExpressible == null ? null : startExpressible.getSqmType(),
stopExpressible == null ? null : stopExpressible.getSqmType()
);
if ( type == null ) {
throw new IllegalArgumentException( "Couldn't determine types of arguments to function 'generate_series'" );
}
final SqmExpressible<?>[] componentTypes = new SqmExpressible<?>[]{ type, typeConfiguration.getBasicTypeForJavaType( Long.class ) };
final String[] componentNames = new String[]{ CollectionPart.Nature.ELEMENT.getName(), CollectionPart.Nature.INDEX.getName() };
return new AnonymousTupleType<>( componentTypes, componentNames );
}
@Override
public SelectableMapping[] resolveFunctionReturnType(
List<? extends SqlAstNode> arguments,
String tableIdentifierVariable,
boolean lateral,
boolean withOrdinality,
SqmToSqlAstConverter converter) {
final Expression start = (Expression) arguments.get( 0 );
final Expression stop = (Expression) arguments.get( 0 );
final JdbcMappingContainer expressionType = NullnessHelper.coalesce(
start.getExpressionType(),
stop.getExpressionType()
);
final JdbcMapping type = expressionType.getSingleJdbcMapping();
if ( type == null ) {
throw new IllegalArgumentException( "Couldn't determine types of arguments to function 'generate_series'" );
}
final SelectableMapping indexMapping = withOrdinality ? new SelectableMappingImpl(
"",
defaultIndexSelectionExpression,
new SelectablePath( CollectionPart.Nature.INDEX.getName() ),
null,
null,
null,
null,
null,
null,
null,
false,
false,
false,
false,
false,
false,
converter.getCreationContext().getTypeConfiguration().getBasicTypeForJavaType( Long.class )
) : null;
final String elementSelectionExpression = defaultValueColumnName == null
? tableIdentifierVariable
: defaultValueColumnName;
final SelectableMapping elementMapping;
if ( expressionType instanceof SqlTypedMapping typedMapping ) {
elementMapping = new SelectableMappingImpl(
"",
elementSelectionExpression,
new SelectablePath( CollectionPart.Nature.ELEMENT.getName() ),
null,
null,
typedMapping.getColumnDefinition(),
typedMapping.getLength(),
typedMapping.getPrecision(),
typedMapping.getScale(),
typedMapping.getTemporalPrecision(),
typedMapping.isLob(),
true,
false,
false,
false,
false,
type
);
}
else {
elementMapping = new SelectableMappingImpl(
"",
elementSelectionExpression,
new SelectablePath( CollectionPart.Nature.ELEMENT.getName() ),
null,
null,
null,
null,
null,
null,
null,
false,
true,
false,
false,
false,
false,
type
);
}
final SelectableMapping[] returnType;
if ( indexMapping == null ) {
returnType = new SelectableMapping[]{ elementMapping };
}
else {
returnType = new SelectableMapping[] {elementMapping, indexMapping};
}
return returnType;
}
}

View File

@ -0,0 +1,239 @@
/*
* SPDX-License-Identifier: LGPL-2.1-or-later
* Copyright Red Hat Inc. and Hibernate Authors
*/
package org.hibernate.dialect.function;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.internal.util.NullnessHelper;
import org.hibernate.metamodel.mapping.CollectionPart;
import org.hibernate.metamodel.mapping.JdbcMapping;
import org.hibernate.metamodel.mapping.JdbcMappingContainer;
import org.hibernate.metamodel.mapping.ModelPart;
import org.hibernate.metamodel.mapping.SelectableMapping;
import org.hibernate.query.derived.AnonymousTupleTableGroupProducer;
import org.hibernate.query.derived.AnonymousTupleType;
import org.hibernate.query.spi.QueryEngine;
import org.hibernate.query.sqm.function.SelfRenderingSqmSetReturningFunction;
import org.hibernate.query.sqm.sql.SqmToSqlAstConverter;
import org.hibernate.query.sqm.tree.SqmTypedNode;
import org.hibernate.spi.NavigablePath;
import org.hibernate.sql.ast.SqlAstTranslator;
import org.hibernate.sql.ast.spi.SqlAppender;
import org.hibernate.sql.ast.tree.SqlAstNode;
import org.hibernate.sql.ast.tree.expression.ColumnReference;
import org.hibernate.sql.ast.tree.expression.Expression;
import org.hibernate.sql.ast.tree.expression.JdbcParameter;
import org.hibernate.sql.ast.tree.expression.Literal;
import org.hibernate.sql.ast.tree.from.FunctionTableGroup;
import org.hibernate.sql.ast.tree.from.TableGroup;
import org.hibernate.type.SqlTypes;
import org.hibernate.type.spi.TypeConfiguration;
import java.util.List;
/**
* H2 generate_series function.
*
* When possible, the {@code system_range} function is used directly.
* If ordinality is requested, the arguments are temporals or anything other than literals,
* this emulation comes into play.
* It essentially renders a {@code system_range} with a specified maximum size that serves as "iteration variable".
* References to the value are replaced with expressions of the form {@code start + step * iterationVariable}
* and a condition is added either to the query or join where the function is used to ensure that the value is
* less than or equal to the stop value.
*/
public class H2GenerateSeriesFunction extends NumberSeriesGenerateSeriesFunction {
public H2GenerateSeriesFunction(int maxSeriesSize, TypeConfiguration typeConfiguration) {
super(
new H2GenerateSeriesSetReturningFunctionTypeResolver(),
// Treat durations like intervals to avoid conversions
typeConfiguration.getBasicTypeRegistry().resolve( java.time.Duration.class, SqlTypes.INTERVAL_SECOND ),
maxSeriesSize
);
}
@Override
public boolean rendersIdentifierVariable(List<SqlAstNode> arguments, SessionFactoryImplementor sessionFactory) {
// To make our lives simpler during emulation
return true;
}
@Override
protected <T> SelfRenderingSqmSetReturningFunction<T> generateSqmSetReturningFunctionExpression(List<? extends SqmTypedNode<?>> arguments, QueryEngine queryEngine) {
//noinspection unchecked
return new SelfRenderingSqmSetReturningFunction<>(
this,
this,
arguments,
getArgumentsValidator(),
getSetReturningTypeResolver(),
(AnonymousTupleType<T>) getSetReturningTypeResolver().resolveTupleType( arguments, queryEngine.getTypeConfiguration() ),
queryEngine.getCriteriaBuilder(),
getName()
) {
@Override
public TableGroup convertToSqlAst(
NavigablePath navigablePath,
String identifierVariable,
boolean lateral,
boolean canUseInnerJoins,
boolean withOrdinality,
SqmToSqlAstConverter walker) {
// Register a transformer that adds a join predicate "start+(step*(ordinal-1))<=stop"
final FunctionTableGroup functionTableGroup = (FunctionTableGroup) super.convertToSqlAst(
navigablePath,
identifierVariable,
lateral,
canUseInnerJoins,
withOrdinality,
walker
);
//noinspection unchecked
final List<SqlAstNode> sqlArguments = (List<SqlAstNode>) functionTableGroup.getPrimaryTableReference()
.getFunctionExpression()
.getArguments();
final Expression startExpression = (Expression) sqlArguments.get( 0 );
final Expression stopExpression = (Expression) sqlArguments.get( 1 );
final Expression explicitStepExpression = sqlArguments.size() > 2
? (Expression) sqlArguments.get( 2 )
: null;
final boolean needsEmulation = needsEmulation( startExpression )
|| needsEmulation( stopExpression )
|| explicitStepExpression != null && needsEmulation( explicitStepExpression );
final ModelPart elementPart = functionTableGroup.getModelPart()
.findSubPart( CollectionPart.Nature.ELEMENT.getName(), null );
final boolean isTemporal = elementPart.getSingleJdbcMapping().getJdbcType().isTemporal();
// Only do this transformation if one of the arguments is anything but a literal or parameter,
// ordinality is requested or the result is a temporal (H2 only supports numerics in system_range)
if ( needsEmulation || withOrdinality || isTemporal ) {
// Register a query transformer to register a join predicate
walker.registerQueryTransformer( new NumberSeriesQueryTransformer(
functionTableGroup,
functionTableGroup,
"x",
coerceToTimestamp
) );
}
return functionTableGroup;
}
};
}
private static class H2GenerateSeriesSetReturningFunctionTypeResolver extends NumberSeriesGenerateSeriesSetReturningFunctionTypeResolver {
public H2GenerateSeriesSetReturningFunctionTypeResolver() {
super( "x", "x" );
}
@Override
public SelectableMapping[] resolveFunctionReturnType(
List<? extends SqlAstNode> arguments,
String tableIdentifierVariable,
boolean lateral,
boolean withOrdinality,
SqmToSqlAstConverter converter) {
final Expression start = (Expression) arguments.get( 0 );
final Expression stop = (Expression) arguments.get( 1 );
final JdbcMappingContainer expressionType = NullnessHelper.coalesce(
start.getExpressionType(),
stop.getExpressionType()
);
final Expression explicitStep = arguments.size() > 2
? (Expression) arguments.get( 2 )
: null;
final ColumnReference joinBaseColumnReference = NullnessHelper.coalesce(
start.getColumnReference(),
stop.getColumnReference(),
explicitStep != null
? explicitStep.getColumnReference()
: null
);
final JdbcMapping type = expressionType.getSingleJdbcMapping();
if ( type == null ) {
throw new IllegalArgumentException( "Couldn't determine types of arguments to function 'generate_series'" );
}
if ( joinBaseColumnReference != null || withOrdinality || type.getJdbcType().isTemporal() ) {
return resolveIterationVariableBasedFunctionReturnType( arguments, tableIdentifierVariable, lateral, withOrdinality, converter );
}
else {
return super.resolveFunctionReturnType( arguments, tableIdentifierVariable, lateral, withOrdinality, converter );
}
}
}
@Override
protected void renderGenerateSeries(
SqlAppender sqlAppender,
Expression start,
Expression stop,
@Nullable Expression step,
AnonymousTupleTableGroupProducer tupleType,
String tableIdentifierVariable,
SqlAstTranslator<?> walker) {
final boolean needsEmulation = needsEmulation( start )
|| needsEmulation( stop )
|| step != null && needsEmulation( step );
final ModelPart elementPart = tupleType.findSubPart( CollectionPart.Nature.ELEMENT.getName(), null );
final ModelPart ordinalityPart = tupleType.findSubPart( CollectionPart.Nature.INDEX.getName(), null );
final boolean isTemporal = elementPart.getSingleJdbcMapping().getJdbcType().isTemporal();
if ( needsEmulation || ordinalityPart != null || isTemporal ) {
final boolean startNeedsVariable = needsVariable( start );
final boolean stepNeedsVariable = step != null && needsVariable( step );
if ( startNeedsVariable || stepNeedsVariable ) {
sqlAppender.appendSql( "((values " );
char separator = '(';
if ( startNeedsVariable ) {
sqlAppender.appendSql( separator );
start.accept( walker );
separator = ',';
}
if ( stepNeedsVariable ) {
sqlAppender.appendSql( separator );
step.accept( walker );
}
sqlAppender.appendSql( ")) " );
sqlAppender.appendSql( tableIdentifierVariable );
sqlAppender.appendSql( "_" );
separator = '(';
if ( startNeedsVariable ) {
sqlAppender.appendSql( separator );
sqlAppender.appendSql( "b" );
separator = ',';
}
if ( stepNeedsVariable ) {
sqlAppender.appendSql( separator );
sqlAppender.appendSql( "s" );
}
sqlAppender.appendSql( ") join " );
}
sqlAppender.appendSql( "system_range(1," );
sqlAppender.appendSql( maxSeriesSize );
sqlAppender.appendSql( ") " );
sqlAppender.appendSql( tableIdentifierVariable );
if ( startNeedsVariable || stepNeedsVariable ) {
sqlAppender.appendSql( " on true)" );
}
}
else {
sqlAppender.appendSql( "system_range(" );
start.accept( walker );
sqlAppender.appendSql( ',' );
stop.accept( walker );
if ( step != null ) {
sqlAppender.appendSql( ',' );
step.accept( walker );
}
sqlAppender.appendSql( ") " );
sqlAppender.appendSql( tableIdentifierVariable );
}
}
private static boolean needsEmulation(Expression expression) {
return !( expression instanceof Literal || expression instanceof JdbcParameter);
}
}

View File

@ -0,0 +1,188 @@
/*
* SPDX-License-Identifier: LGPL-2.1-or-later
* Copyright Red Hat Inc. and Hibernate Authors
*/
package org.hibernate.dialect.function;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.metamodel.mapping.JdbcMappingContainer;
import org.hibernate.metamodel.mapping.SelectableMapping;
import org.hibernate.query.derived.AnonymousTupleTableGroupProducer;
import org.hibernate.query.derived.AnonymousTupleType;
import org.hibernate.query.spi.QueryEngine;
import org.hibernate.query.sqm.function.SelfRenderingSqmSetReturningFunction;
import org.hibernate.query.sqm.sql.SqmToSqlAstConverter;
import org.hibernate.query.sqm.tree.SqmTypedNode;
import org.hibernate.spi.NavigablePath;
import org.hibernate.sql.ast.SqlAstTranslator;
import org.hibernate.sql.ast.spi.SqlAppender;
import org.hibernate.sql.ast.tree.SqlAstNode;
import org.hibernate.sql.ast.tree.cte.CteColumn;
import org.hibernate.sql.ast.tree.cte.CteStatement;
import org.hibernate.sql.ast.tree.cte.CteTable;
import org.hibernate.sql.ast.tree.expression.Duration;
import org.hibernate.sql.ast.tree.expression.Expression;
import org.hibernate.sql.ast.tree.expression.SelfRenderingExpression;
import org.hibernate.sql.ast.tree.from.FunctionTableGroup;
import org.hibernate.sql.ast.tree.from.TableGroup;
import org.hibernate.sql.ast.tree.select.QuerySpec;
import org.hibernate.sql.ast.tree.select.SelectStatement;
import org.hibernate.sql.results.internal.SqlSelectionImpl;
import org.hibernate.type.BasicType;
import org.hibernate.type.SqlTypes;
import org.hibernate.type.spi.TypeConfiguration;
import java.util.List;
/**
* HANA generate_series function.
*/
public class HANAGenerateSeriesFunction extends NumberSeriesGenerateSeriesFunction {
public HANAGenerateSeriesFunction(int maxSeriesSize, TypeConfiguration typeConfiguration) {
super(
new CteGenerateSeriesSetReturningFunctionTypeResolver(),
// Treat durations like intervals to avoid conversions
typeConfiguration.getBasicTypeRegistry().resolve(
java.time.Duration.class,
SqlTypes.DURATION
),
false,
maxSeriesSize
);
}
@Override
public boolean rendersIdentifierVariable(List<SqlAstNode> arguments, SessionFactoryImplementor sessionFactory) {
// To make our lives simpler during emulation
return true;
}
@Override
protected <T> SelfRenderingSqmSetReturningFunction<T> generateSqmSetReturningFunctionExpression(List<? extends SqmTypedNode<?>> arguments, QueryEngine queryEngine) {
//noinspection unchecked
return new SelfRenderingSqmSetReturningFunction<>(
this,
this,
arguments,
getArgumentsValidator(),
getSetReturningTypeResolver(),
(AnonymousTupleType<T>) getSetReturningTypeResolver().resolveTupleType( arguments, queryEngine.getTypeConfiguration() ),
queryEngine.getCriteriaBuilder(),
getName()
) {
@Override
public TableGroup convertToSqlAst(NavigablePath navigablePath, String identifierVariable, boolean lateral, boolean canUseInnerJoins, boolean withOrdinality, SqmToSqlAstConverter walker) {
final FunctionTableGroup tableGroup = (FunctionTableGroup) super.convertToSqlAst(
navigablePath,
identifierVariable,
lateral,
canUseInnerJoins,
withOrdinality,
walker
);
walker.registerQueryTransformer( new HANAGenerateSeriesQueryTransformer(
tableGroup,
tableGroup,
maxSeriesSize,
"i",
coerceToTimestamp
) );
return tableGroup;
}
};
}
protected static class HANAGenerateSeriesQueryTransformer extends CteGenerateSeriesFunction.CteGenerateSeriesQueryTransformer {
public HANAGenerateSeriesQueryTransformer(FunctionTableGroup functionTableGroup, TableGroup targetTableGroup, int maxSeriesSize, String positionColumnName, boolean coerceToTimestamp) {
super( functionTableGroup, targetTableGroup, maxSeriesSize, positionColumnName, coerceToTimestamp );
}
@Override
protected CteStatement createSeriesCte(SqmToSqlAstConverter converter) {
final BasicType<String> stringType = converter.getCreationContext().getTypeConfiguration()
.getBasicTypeForJavaType( String.class );
final List<CteColumn> cteColumns = List.of( new CteColumn( "v", stringType ) );
final QuerySpec query = new QuerySpec( false );
query.getSelectClause().addSqlSelection( new SqlSelectionImpl( new SelfRenderingExpression() {
@Override
public void renderToSql(SqlAppender sqlAppender, SqlAstTranslator<?> walker, SessionFactoryImplementor sessionFactory) {
sqlAppender.appendSql( "'<r>'||lpad(''," );
sqlAppender.appendSql( maxSeriesSize * 4 );
sqlAppender.appendSql( ",'<a/>')||'</r>'" );
}
@Override
public JdbcMappingContainer getExpressionType() {
return stringType;
}
} ) );
return new CteStatement( new CteTable( CteGenerateSeriesFunction.CteGenerateSeriesQueryTransformer.NAME, cteColumns ), new SelectStatement( query ) );
}
}
static class CteGenerateSeriesSetReturningFunctionTypeResolver extends NumberSeriesGenerateSeriesSetReturningFunctionTypeResolver {
public CteGenerateSeriesSetReturningFunctionTypeResolver() {
super( "v", "i" );
}
@Override
public SelectableMapping[] resolveFunctionReturnType(
List<? extends SqlAstNode> arguments,
String tableIdentifierVariable,
boolean lateral,
boolean withOrdinality,
SqmToSqlAstConverter converter) {
return resolveIterationVariableBasedFunctionReturnType( arguments, tableIdentifierVariable, lateral, withOrdinality, converter );
}
}
@Override
protected void renderGenerateSeries(
SqlAppender sqlAppender,
Expression start,
Expression stop,
@Nullable Expression step,
AnonymousTupleTableGroupProducer tupleType,
String tableIdentifierVariable,
SqlAstTranslator<?> walker) {
final boolean startNeedsVariable = needsVariable( start );
final boolean stepNeedsVariable = step != null && needsVariable( step );
if ( startNeedsVariable || stepNeedsVariable ) {
sqlAppender.appendSql( "((select" );
char separator = ' ';
if ( startNeedsVariable ) {
sqlAppender.appendSql( separator );
start.accept( walker );
sqlAppender.appendSql( " b" );
separator = ',';
}
if ( stepNeedsVariable ) {
sqlAppender.appendSql( separator );
if ( step instanceof Duration duration ) {
duration.getMagnitude().accept( walker );
}
else {
step.accept( walker );
}
sqlAppender.appendSql( " s" );
}
sqlAppender.appendSql( " from sys.dummy) " );
sqlAppender.appendSql( tableIdentifierVariable );
sqlAppender.appendSql( "_" );
sqlAppender.appendSql( " join " );
}
sqlAppender.appendSql( "xmltable('/r/a' passing " );
sqlAppender.appendSql( CteGenerateSeriesFunction.CteGenerateSeriesQueryTransformer.NAME );
sqlAppender.appendSql( ".v columns i for ordinality) " );
sqlAppender.appendSql( tableIdentifierVariable );
if ( startNeedsVariable || stepNeedsVariable ) {
sqlAppender.appendSql( " on 1=1)" );
}
}
}

View File

@ -0,0 +1,509 @@
/*
* SPDX-License-Identifier: LGPL-2.1-or-later
* Copyright Red Hat Inc. and Hibernate Authors
*/
package org.hibernate.dialect.function;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.hibernate.engine.spi.LazySessionWrapperOptions;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.internal.util.NullnessHelper;
import org.hibernate.metamodel.mapping.BasicValuedMapping;
import org.hibernate.metamodel.mapping.CollectionPart;
import org.hibernate.metamodel.mapping.JdbcMapping;
import org.hibernate.metamodel.mapping.JdbcMappingContainer;
import org.hibernate.metamodel.mapping.SelectableMapping;
import org.hibernate.metamodel.mapping.SelectablePath;
import org.hibernate.metamodel.mapping.SqlTypedMapping;
import org.hibernate.metamodel.mapping.internal.SelectableMappingImpl;
import org.hibernate.query.ReturnableType;
import org.hibernate.query.derived.AnonymousTupleTableGroupProducer;
import org.hibernate.query.spi.QueryOptions;
import org.hibernate.query.sqm.BinaryArithmeticOperator;
import org.hibernate.query.sqm.ComparisonOperator;
import org.hibernate.query.sqm.function.FunctionRenderer;
import org.hibernate.query.sqm.function.SelfRenderingFunctionSqlAstExpression;
import org.hibernate.query.sqm.produce.function.SetReturningFunctionTypeResolver;
import org.hibernate.query.sqm.sql.SqmToSqlAstConverter;
import org.hibernate.query.sqm.tree.expression.NumericTypeCategory;
import org.hibernate.sql.Template;
import org.hibernate.sql.ast.SqlAstTranslator;
import org.hibernate.sql.ast.spi.SqlAppender;
import org.hibernate.sql.ast.tree.SqlAstNode;
import org.hibernate.sql.ast.tree.cte.CteContainer;
import org.hibernate.sql.ast.tree.expression.BinaryArithmeticExpression;
import org.hibernate.sql.ast.tree.expression.CastTarget;
import org.hibernate.sql.ast.tree.expression.ColumnReference;
import org.hibernate.sql.ast.tree.expression.DurationUnit;
import org.hibernate.sql.ast.tree.expression.Expression;
import org.hibernate.sql.ast.tree.expression.Literal;
import org.hibernate.sql.ast.tree.expression.QueryLiteral;
import org.hibernate.sql.ast.tree.expression.QueryTransformer;
import org.hibernate.sql.ast.tree.expression.SelfRenderingSqlFragmentExpression;
import org.hibernate.sql.ast.tree.expression.UnparsedNumericLiteral;
import org.hibernate.sql.ast.tree.from.FunctionTableGroup;
import org.hibernate.sql.ast.tree.from.TableGroup;
import org.hibernate.sql.ast.tree.predicate.ComparisonPredicate;
import org.hibernate.sql.ast.tree.predicate.Junction;
import org.hibernate.sql.ast.tree.predicate.Predicate;
import org.hibernate.sql.ast.tree.predicate.PredicateContainer;
import org.hibernate.sql.ast.tree.select.QuerySpec;
import org.hibernate.sql.ast.tree.select.SelectStatement;
import org.hibernate.sql.exec.spi.JdbcOperationQuerySelect;
import org.hibernate.sql.results.internal.SqlSelectionImpl;
import org.hibernate.type.BasicType;
import org.hibernate.type.SqlTypes;
import org.hibernate.type.descriptor.jdbc.JdbcType;
import org.hibernate.type.spi.TypeConfiguration;
import java.sql.Timestamp;
import java.time.Duration;
import java.util.List;
/**
* The base for generate_series function implementations that use a static number source.
*/
public abstract class NumberSeriesGenerateSeriesFunction extends GenerateSeriesFunction {
protected final int maxSeriesSize;
public NumberSeriesGenerateSeriesFunction(@Nullable String defaultValueColumnName, String defaultIndexSelectionExpression, boolean coerceToTimestamp, TypeConfiguration typeConfiguration, int maxSeriesSize) {
super( defaultValueColumnName, defaultIndexSelectionExpression, coerceToTimestamp, typeConfiguration );
this.maxSeriesSize = maxSeriesSize;
}
public NumberSeriesGenerateSeriesFunction(SetReturningFunctionTypeResolver setReturningFunctionTypeResolver, BasicType<Duration> durationType, int maxSeriesSize) {
super( setReturningFunctionTypeResolver, durationType );
this.maxSeriesSize = maxSeriesSize;
}
public NumberSeriesGenerateSeriesFunction(SetReturningFunctionTypeResolver setReturningFunctionTypeResolver, BasicType<Duration> durationType, boolean coerceToTimestamp, int maxSeriesSize) {
super( setReturningFunctionTypeResolver, durationType, coerceToTimestamp );
this.maxSeriesSize = maxSeriesSize;
}
@Override
protected abstract void renderGenerateSeries(
SqlAppender sqlAppender,
Expression start,
Expression stop,
@Nullable Expression step,
AnonymousTupleTableGroupProducer tupleType,
String tableIdentifierVariable,
SqlAstTranslator<?> walker);
/**
* Returns whether a variable (e.g. through values clause) shall be introduced for an expression,
* which is passed as argument to the {@code generate_series} function.
* Since the selection expression of the value column that this function returns must be transformed
* to the form {@code start + step * ( iterationVariable - 1 )}, it is vital that {@code start} and {@code step}
* can be rendered to a {@code String} during SQL AST build time for {@link SelectableMapping#getSelectionExpression()}.
* If that isn't possible because the expression is too complex, a variable needs to be introduced which is then used
* instead of the original expression.
*/
protected static boolean needsVariable(Expression expression) {
return !( expression instanceof Literal || expression instanceof ColumnReference );
}
public static Expression add(Expression left, Expression right, SqmToSqlAstConverter converter) {
if ( right instanceof org.hibernate.sql.ast.tree.expression.Duration duration ) {
final BasicType<?> nodeType = (BasicType<?>) left.getExpressionType().getSingleJdbcMapping();
final FunctionRenderer timestampadd = (FunctionRenderer) converter.getCreationContext().getSessionFactory()
.getQueryEngine().getSqmFunctionRegistry().findFunctionDescriptor( "timestampadd" );
return new SelfRenderingFunctionSqlAstExpression(
"timestampadd",
timestampadd,
List.of(
new DurationUnit( duration.getUnit(), duration.getExpressionType() ),
duration.getMagnitude(),
left
),
nodeType,
nodeType
);
}
else {
return new BinaryArithmeticExpression(
left,
BinaryArithmeticOperator.ADD,
right,
(BasicValuedMapping) left.getExpressionType()
);
}
}
public static Expression multiply(Expression left, int multiplier, BasicType<Integer> integerType) {
return multiply( left, new UnparsedNumericLiteral<>( Integer.toString( multiplier ), NumericTypeCategory.INTEGER, integerType ) );
}
public static Expression multiply(Expression left, Expression multiplier) {
if ( left instanceof org.hibernate.sql.ast.tree.expression.Duration duration ) {
return new org.hibernate.sql.ast.tree.expression.Duration(
multiply( duration.getMagnitude(), multiplier ),
duration.getUnit(),
duration.getExpressionType()
);
}
else {
return new BinaryArithmeticExpression(
left,
BinaryArithmeticOperator.MULTIPLY,
multiplier,
(BasicValuedMapping) left.getExpressionType()
);
}
}
static Expression castToTimestamp(SqlAstNode node, SqmToSqlAstConverter converter) {
final BasicType<?> nodeType = (BasicType<?>) ((Expression) node).getExpressionType().getSingleJdbcMapping();
final FunctionRenderer cast = (FunctionRenderer) converter.getCreationContext().getSessionFactory().getQueryEngine()
.getSqmFunctionRegistry().findFunctionDescriptor( "cast" );
final BasicType<?> timestampType = converter.getCreationContext().getTypeConfiguration()
.getBasicTypeForJavaType( Timestamp.class );
return new SelfRenderingFunctionSqlAstExpression(
"cast",
cast,
List.of( node, new CastTarget( timestampType ) ),
nodeType,
nodeType
);
}
protected static class NumberSeriesQueryTransformer implements QueryTransformer {
protected final FunctionTableGroup functionTableGroup;
protected final TableGroup targetTableGroup;
protected final String positionColumnName;
protected final boolean coerceToTimestamp;
public NumberSeriesQueryTransformer(FunctionTableGroup functionTableGroup, TableGroup targetTableGroup, String positionColumnName, boolean coerceToTimestamp) {
this.functionTableGroup = functionTableGroup;
this.targetTableGroup = targetTableGroup;
this.positionColumnName = positionColumnName;
this.coerceToTimestamp = coerceToTimestamp;
}
@Override
public QuerySpec transform(CteContainer cteContainer, QuerySpec querySpec, SqmToSqlAstConverter converter) {
//noinspection unchecked
final List<SqlAstNode> arguments = (List<SqlAstNode>) functionTableGroup.getPrimaryTableReference()
.getFunctionExpression()
.getArguments();
final JdbcType boundType = ((Expression) arguments.get( 0 )).getExpressionType().getSingleJdbcMapping().getJdbcType();
final boolean castTimestamp = coerceToTimestamp
&& (boundType.getDdlTypeCode() == SqlTypes.DATE || boundType.getDdlTypeCode() == SqlTypes.TIME);
final Expression start = castTimestamp
? castToTimestamp( arguments.get( 0 ), converter )
: (Expression) arguments.get( 0 );
final Expression stop = castTimestamp
? castToTimestamp( arguments.get( 1 ), converter )
: (Expression) arguments.get( 1 );
final Expression explicitStep = arguments.size() > 2
? (Expression) arguments.get( 2 )
: null;
final TableGroup parentTableGroup = querySpec.getFromClause().queryTableGroups(
tg -> tg.findTableGroupJoin( targetTableGroup ) == null ? null : tg
);
final PredicateContainer predicateContainer;
if ( parentTableGroup != null ) {
predicateContainer = parentTableGroup.findTableGroupJoin( targetTableGroup );
}
else {
predicateContainer = querySpec;
}
final BasicType<Integer> integerType = converter.getCreationContext()
.getSessionFactory()
.getNodeBuilder()
.getIntegerType();
final Expression oneBasedOrdinal = new ColumnReference(
functionTableGroup.getPrimaryTableReference().getIdentificationVariable(),
positionColumnName,
false,
null,
integerType
);
final Expression one = new QueryLiteral<>( 1, integerType );
final Expression zeroBasedOrdinal = new BinaryArithmeticExpression(
oneBasedOrdinal,
BinaryArithmeticOperator.SUBTRACT,
one,
integerType
);
final Expression stepExpression = explicitStep != null
? multiply( explicitStep, zeroBasedOrdinal )
: zeroBasedOrdinal;
final Expression nextValue = add( start, stepExpression, converter );
// Add a predicate to ensure the current value is valid
if ( explicitStep == null ) {
// The default step is 1, so just check if value <= stop
predicateContainer.applyPredicate(
new ComparisonPredicate(
nextValue,
ComparisonOperator.LESS_THAN_OR_EQUAL,
stop
)
);
}
else {
// When start < stop, step must be positive and value is only valid if it's less than or equal to stop
final BasicType<Boolean> booleanType = converter.getCreationContext()
.getSessionFactory()
.getNodeBuilder()
.getBooleanType();
final Predicate positiveProgress = new Junction(
Junction.Nature.CONJUNCTION,
List.of(
new ComparisonPredicate(
start,
ComparisonOperator.LESS_THAN,
stop
),
new ComparisonPredicate(
explicitStep,
ComparisonOperator.GREATER_THAN,
multiply( explicitStep, -1, integerType )
),
new ComparisonPredicate(
nextValue,
ComparisonOperator.LESS_THAN_OR_EQUAL,
stop
)
),
booleanType
);
// When start > stop, step must be negative and value is only valid if it's greater than or equal to stop
final Predicate negativeProgress = new Junction(
Junction.Nature.CONJUNCTION,
List.of(
new ComparisonPredicate(
start,
ComparisonOperator.GREATER_THAN,
stop
),
new ComparisonPredicate(
explicitStep,
ComparisonOperator.LESS_THAN,
multiply( explicitStep, -1, integerType )
),
new ComparisonPredicate(
nextValue,
ComparisonOperator.GREATER_THAN_OR_EQUAL,
stop
)
),
booleanType
);
final Predicate initialValue = new Junction(
Junction.Nature.CONJUNCTION,
List.of(
new ComparisonPredicate(
start,
ComparisonOperator.EQUAL,
stop
),
new ComparisonPredicate(
oneBasedOrdinal,
ComparisonOperator.EQUAL,
one
)
),
booleanType
);
predicateContainer.applyPredicate(
new Junction(
Junction.Nature.DISJUNCTION,
List.of( positiveProgress, negativeProgress, initialValue ),
booleanType
)
);
}
return querySpec;
}
}
protected static class NumberSeriesGenerateSeriesSetReturningFunctionTypeResolver extends GenerateSeriesSetReturningFunctionTypeResolver {
public NumberSeriesGenerateSeriesSetReturningFunctionTypeResolver(@Nullable String defaultValueColumnName, String defaultIndexSelectionExpression) {
super( defaultValueColumnName, defaultIndexSelectionExpression );
}
protected SelectableMapping[] resolveIterationVariableBasedFunctionReturnType(
List<? extends SqlAstNode> arguments,
String tableIdentifierVariable,
boolean lateral,
boolean withOrdinality,
SqmToSqlAstConverter converter) {
final Expression start = (Expression) arguments.get( 0 );
final Expression stop = (Expression) arguments.get( 0 );
final JdbcMappingContainer expressionType = NullnessHelper.coalesce(
start.getExpressionType(),
stop.getExpressionType()
);
final Expression explicitStep = arguments.size() > 2
? (Expression) arguments.get( 2 )
: null;
final JdbcMapping type = expressionType.getSingleJdbcMapping();
if ( type == null ) {
throw new IllegalArgumentException(
"Couldn't determine types of arguments to function 'generate_series'" );
}
final SelectableMapping indexMapping = withOrdinality ? new SelectableMappingImpl(
"",
defaultIndexSelectionExpression,
new SelectablePath( CollectionPart.Nature.INDEX.getName() ),
null,
null,
null,
null,
null,
null,
null,
false,
false,
false,
false,
false,
false,
converter.getCreationContext().getTypeConfiguration().getBasicTypeForJavaType( Long.class )
) : null;
//t.x+(1*(s.x-1))
final String startExpression = getStartExpression( start, tableIdentifierVariable, converter );
final String stepExpression = getStepExpression( explicitStep, tableIdentifierVariable, converter );
final String customReadExpression;
if ( type.getJdbcType().isTemporal() ) {
final org.hibernate.sql.ast.tree.expression.Duration step = (org.hibernate.sql.ast.tree.expression.Duration) explicitStep;
customReadExpression = timestampadd( startExpression, stepExpression, type, step, converter );
}
else {
customReadExpression = startExpression + "+" + stepExpression;
}
final String elementSelectionExpression = defaultValueColumnName == null
? tableIdentifierVariable
: defaultValueColumnName;
final SelectableMapping elementMapping;
if ( expressionType instanceof SqlTypedMapping typedMapping ) {
elementMapping = new SelectableMappingImpl(
"",
elementSelectionExpression,
new SelectablePath( CollectionPart.Nature.ELEMENT.getName() ),
customReadExpression,
null,
typedMapping.getColumnDefinition(),
typedMapping.getLength(),
typedMapping.getPrecision(),
typedMapping.getScale(),
typedMapping.getTemporalPrecision(),
typedMapping.isLob(),
true,
false,
false,
false,
false,
type
);
}
else {
elementMapping = new SelectableMappingImpl(
"",
elementSelectionExpression,
new SelectablePath( CollectionPart.Nature.ELEMENT.getName() ),
customReadExpression,
null,
null,
null,
null,
null,
null,
false,
true,
false,
false,
false,
false,
type
);
}
final SelectableMapping[] returnType;
if ( indexMapping == null ) {
returnType = new SelectableMapping[] {elementMapping};
}
else {
returnType = new SelectableMapping[] {elementMapping, indexMapping};
}
return returnType;
}
private static String timestampadd(String startExpression, String stepExpression, JdbcMapping type, org.hibernate.sql.ast.tree.expression.Duration duration, SqmToSqlAstConverter converter) {
final FunctionRenderer renderer = (FunctionRenderer) converter.getCreationContext().getSessionFactory()
.getQueryEngine().getSqmFunctionRegistry().findFunctionDescriptor( "timestampadd" );
final QuerySpec fakeQuery = new QuerySpec( true );
fakeQuery.getSelectClause().addSqlSelection( new SqlSelectionImpl(
new SelfRenderingFunctionSqlAstExpression(
"timestampadd",
renderer,
List.of(
new DurationUnit( duration.getUnit(), duration.getExpressionType() ),
new SelfRenderingSqlFragmentExpression( stepExpression, duration.getExpressionType() ),
new SelfRenderingSqlFragmentExpression( startExpression, type )
),
(ReturnableType<?>) type,
type
)
) );
final SqlAstTranslator<JdbcOperationQuerySelect> translator = converter.getCreationContext()
.getSessionFactory().getJdbcServices().getDialect().getSqlAstTranslatorFactory()
.buildSelectTranslator( converter.getCreationContext().getSessionFactory(), new SelectStatement( fakeQuery ) );
final JdbcOperationQuerySelect operation = translator.translate( null, QueryOptions.NONE );
final String sqlString = operation.getSqlString();
assert sqlString.startsWith( "select " );
final int startIndex = "select ".length();
final int fromIndex = sqlString.lastIndexOf( " from" );
return fromIndex == -1
? sqlString.substring( startIndex )
: sqlString.substring( startIndex, fromIndex );
}
private String getStartExpression(Expression expression, String tableIdentifierVariable, SqmToSqlAstConverter walker) {
return getExpression( expression, tableIdentifierVariable, "b", walker );
}
private String getStepExpression(@Nullable Expression explicitStep, String tableIdentifierVariable, SqmToSqlAstConverter walker) {
if ( explicitStep == null ) {
return "(" + Template.TEMPLATE + "." + defaultIndexSelectionExpression + "-1)";
}
else {
return "(" + getExpression( explicitStep, tableIdentifierVariable, "s", walker ) + "*(" + Template.TEMPLATE + "." + defaultIndexSelectionExpression + "-1))";
}
}
private String getExpression(Expression expression, String tableIdentifierVariable, String syntheticColumnName, SqmToSqlAstConverter walker) {
if ( expression instanceof Literal literal ) {
final SessionFactoryImplementor sessionFactory = walker.getCreationContext().getSessionFactory();
final LazySessionWrapperOptions wrapperOptions = new LazySessionWrapperOptions( sessionFactory );
try {
//noinspection unchecked
return literal.getJdbcMapping().getJdbcLiteralFormatter().toJdbcLiteral(
literal.getLiteralValue(),
sessionFactory.getJdbcServices().getDialect(),
wrapperOptions
);
}
finally {
wrapperOptions.cleanup();
}
}
else if ( expression instanceof ColumnReference columnReference ) {
return columnReference.getExpressionText();
}
else {
return tableIdentifierVariable + "_." + syntheticColumnName;
}
}
}
}

View File

@ -0,0 +1,213 @@
/*
* SPDX-License-Identifier: LGPL-2.1-or-later
* Copyright Red Hat Inc. and Hibernate Authors
*/
package org.hibernate.dialect.function;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.internal.util.NullnessHelper;
import org.hibernate.metamodel.mapping.CollectionPart;
import org.hibernate.metamodel.mapping.JdbcMapping;
import org.hibernate.metamodel.mapping.JdbcMappingContainer;
import org.hibernate.metamodel.mapping.ModelPart;
import org.hibernate.metamodel.mapping.SelectableMapping;
import org.hibernate.query.derived.AnonymousTupleTableGroupProducer;
import org.hibernate.query.derived.AnonymousTupleType;
import org.hibernate.query.spi.QueryEngine;
import org.hibernate.query.sqm.function.SelfRenderingSqmSetReturningFunction;
import org.hibernate.query.sqm.sql.SqmToSqlAstConverter;
import org.hibernate.query.sqm.tree.SqmTypedNode;
import org.hibernate.spi.NavigablePath;
import org.hibernate.sql.ast.SqlAstTranslator;
import org.hibernate.sql.ast.spi.SqlAppender;
import org.hibernate.sql.ast.tree.SqlAstNode;
import org.hibernate.sql.ast.tree.expression.Duration;
import org.hibernate.sql.ast.tree.expression.Expression;
import org.hibernate.sql.ast.tree.from.FunctionTableGroup;
import org.hibernate.sql.ast.tree.from.TableGroup;
import org.hibernate.type.SqlTypes;
import org.hibernate.type.spi.TypeConfiguration;
import java.util.List;
/**
* SQL Server generate_series function.
*
* When possible, the {@code generate_series} function is used directly.
* If ordinality is requested or the arguments are temporals, this emulation comes into play.
* It essentially renders a {@code generate_series} with a specified maximum size that serves as "iteration variable".
* References to the value are replaced with expressions of the form {@code start + step * iterationVariable}
* and a condition is added either to the query or join where the function is used to ensure that the value is
* less than or equal to the stop value.
*/
public class SQLServerGenerateSeriesFunction extends NumberSeriesGenerateSeriesFunction {
public SQLServerGenerateSeriesFunction(int maxSeriesSize, TypeConfiguration typeConfiguration) {
super(
new SQLServerGenerateSeriesSetReturningFunctionTypeResolver(),
// Treat durations like intervals to avoid conversions
typeConfiguration.getBasicTypeRegistry().resolve( java.time.Duration.class, SqlTypes.DURATION ),
maxSeriesSize
);
}
@Override
public boolean rendersIdentifierVariable(List<SqlAstNode> arguments, SessionFactoryImplementor sessionFactory) {
// To make our lives simpler during emulation
return true;
}
@Override
protected <T> SelfRenderingSqmSetReturningFunction<T> generateSqmSetReturningFunctionExpression(List<? extends SqmTypedNode<?>> arguments, QueryEngine queryEngine) {
//noinspection unchecked
return new SelfRenderingSqmSetReturningFunction<>(
this,
this,
arguments,
getArgumentsValidator(),
getSetReturningTypeResolver(),
(AnonymousTupleType<T>) getSetReturningTypeResolver().resolveTupleType( arguments, queryEngine.getTypeConfiguration() ),
queryEngine.getCriteriaBuilder(),
getName()
) {
@Override
public TableGroup convertToSqlAst(
NavigablePath navigablePath,
String identifierVariable,
boolean lateral,
boolean canUseInnerJoins,
boolean withOrdinality,
SqmToSqlAstConverter walker) {
// Register a transformer that adds a join predicate "start+(step*(ordinal-1))<=stop"
final FunctionTableGroup functionTableGroup = (FunctionTableGroup) super.convertToSqlAst(
navigablePath,
identifierVariable,
lateral,
canUseInnerJoins,
withOrdinality,
walker
);
final ModelPart elementPart = functionTableGroup.getModelPart()
.findSubPart( CollectionPart.Nature.ELEMENT.getName(), null );
final boolean isTemporal = elementPart.getSingleJdbcMapping().getJdbcType().isTemporal();
// Only do this transformation if ordinality is requested
// or the result is a temporal (SQL Server only supports numerics in system_range)
if ( withOrdinality || isTemporal ) {
// Register a query transformer to register a join predicate
walker.registerQueryTransformer( new NumberSeriesQueryTransformer(
functionTableGroup,
functionTableGroup,
"value",
coerceToTimestamp
) );
}
return functionTableGroup;
}
};
}
private static class SQLServerGenerateSeriesSetReturningFunctionTypeResolver extends NumberSeriesGenerateSeriesSetReturningFunctionTypeResolver {
public SQLServerGenerateSeriesSetReturningFunctionTypeResolver() {
super( "value", "value" );
}
@Override
public SelectableMapping[] resolveFunctionReturnType(
List<? extends SqlAstNode> arguments,
String tableIdentifierVariable,
boolean lateral,
boolean withOrdinality,
SqmToSqlAstConverter converter) {
final Expression start = (Expression) arguments.get( 0 );
final Expression stop = (Expression) arguments.get( 1 );
final JdbcMappingContainer expressionType = NullnessHelper.coalesce(
start.getExpressionType(),
stop.getExpressionType()
);
final JdbcMapping type = expressionType.getSingleJdbcMapping();
if ( type == null ) {
throw new IllegalArgumentException( "Couldn't determine types of arguments to function 'generate_series'" );
}
if ( withOrdinality || type.getJdbcType().isTemporal() ) {
return resolveIterationVariableBasedFunctionReturnType( arguments, tableIdentifierVariable, lateral, withOrdinality, converter );
}
else {
return super.resolveFunctionReturnType( arguments, tableIdentifierVariable, lateral, withOrdinality, converter );
}
}
}
@Override
protected void renderGenerateSeries(
SqlAppender sqlAppender,
Expression start,
Expression stop,
@Nullable Expression step,
AnonymousTupleTableGroupProducer tupleType,
String tableIdentifierVariable,
SqlAstTranslator<?> walker) {
final ModelPart elementPart = tupleType.findSubPart( CollectionPart.Nature.ELEMENT.getName(), null );
final ModelPart ordinalityPart = tupleType.findSubPart( CollectionPart.Nature.INDEX.getName(), null );
final boolean isTemporal = elementPart.getSingleJdbcMapping().getJdbcType().isTemporal();
if ( ordinalityPart != null || isTemporal ) {
final boolean startNeedsEmulation = needsVariable( start );
final boolean stepNeedsEmulation = step != null && needsVariable( step );
if ( startNeedsEmulation || stepNeedsEmulation ) {
sqlAppender.appendSql( "((values " );
char separator = '(';
if ( startNeedsEmulation ) {
sqlAppender.appendSql( separator );
start.accept( walker );
separator = ',';
}
if ( stepNeedsEmulation ) {
sqlAppender.appendSql( separator );
if ( step instanceof Duration duration ) {
duration.getMagnitude().accept( walker );
}
else {
step.accept( walker );
}
}
sqlAppender.appendSql( ")) " );
sqlAppender.appendSql( tableIdentifierVariable );
sqlAppender.appendSql( "_" );
separator = '(';
if ( startNeedsEmulation ) {
sqlAppender.appendSql( separator );
sqlAppender.appendSql( "b" );
separator = ',';
}
if ( stepNeedsEmulation ) {
sqlAppender.appendSql( separator );
sqlAppender.appendSql( "s" );
}
sqlAppender.appendSql( ") join " );
}
sqlAppender.appendSql( "generate_series(1," );
sqlAppender.appendSql( maxSeriesSize );
sqlAppender.appendSql( ") " );
sqlAppender.appendSql( tableIdentifierVariable );
if ( startNeedsEmulation || stepNeedsEmulation ) {
sqlAppender.appendSql( " on 1=1)" );
}
}
else {
sqlAppender.appendSql( "generate_series(" );
start.accept( walker );
sqlAppender.appendSql( ',' );
stop.accept( walker );
if ( step != null ) {
sqlAppender.appendSql( ',' );
step.accept( walker );
}
sqlAppender.appendSql( ") " );
sqlAppender.appendSql( tableIdentifierVariable );
}
}
}

View File

@ -0,0 +1,166 @@
/*
* SPDX-License-Identifier: LGPL-2.1-or-later
* Copyright Red Hat Inc. and Hibernate Authors
*/
package org.hibernate.dialect.function;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.metamodel.mapping.SelectableMapping;
import org.hibernate.query.derived.AnonymousTupleTableGroupProducer;
import org.hibernate.query.derived.AnonymousTupleType;
import org.hibernate.query.spi.QueryEngine;
import org.hibernate.query.sqm.function.SelfRenderingSqmSetReturningFunction;
import org.hibernate.query.sqm.sql.SqmToSqlAstConverter;
import org.hibernate.query.sqm.tree.SqmTypedNode;
import org.hibernate.spi.NavigablePath;
import org.hibernate.sql.ast.SqlAstTranslator;
import org.hibernate.sql.ast.spi.SqlAppender;
import org.hibernate.sql.ast.tree.SqlAstNode;
import org.hibernate.sql.ast.tree.expression.Duration;
import org.hibernate.sql.ast.tree.expression.Expression;
import org.hibernate.sql.ast.tree.from.FunctionTableGroup;
import org.hibernate.sql.ast.tree.from.TableGroup;
import org.hibernate.type.SqlTypes;
import org.hibernate.type.spi.TypeConfiguration;
import java.util.List;
/**
* Sybase ASE generate_series function.
*
* This implementation first replicates an XML tag with a specified maximum size with the {@code replicate} function
* and then uses {@code xmltable} to produce rows for every generated element.
* References to the value are replaced with expressions of the form {@code start + step * iterationVariable}
* and a condition is added either to the query or join where the function is used to ensure that the value is
* less than or equal to the stop value.
*/
public class SybaseASEGenerateSeriesFunction extends NumberSeriesGenerateSeriesFunction {
public SybaseASEGenerateSeriesFunction(int maxSeriesSize, TypeConfiguration typeConfiguration) {
super(
new SybaseASEGenerateSeriesSetReturningFunctionTypeResolver(),
// Treat durations like intervals to avoid conversions
typeConfiguration.getBasicTypeRegistry().resolve( java.time.Duration.class, SqlTypes.DURATION ),
maxSeriesSize
);
}
@Override
public boolean rendersIdentifierVariable(List<SqlAstNode> arguments, SessionFactoryImplementor sessionFactory) {
// To make our lives simpler during emulation
return true;
}
@Override
protected <T> SelfRenderingSqmSetReturningFunction<T> generateSqmSetReturningFunctionExpression(List<? extends SqmTypedNode<?>> arguments, QueryEngine queryEngine) {
//noinspection unchecked
return new SelfRenderingSqmSetReturningFunction<>(
this,
this,
arguments,
getArgumentsValidator(),
getSetReturningTypeResolver(),
(AnonymousTupleType<T>) getSetReturningTypeResolver().resolveTupleType( arguments, queryEngine.getTypeConfiguration() ),
queryEngine.getCriteriaBuilder(),
getName()
) {
@Override
public TableGroup convertToSqlAst(
NavigablePath navigablePath,
String identifierVariable,
boolean lateral,
boolean canUseInnerJoins,
boolean withOrdinality,
SqmToSqlAstConverter walker) {
// Register a transformer that adds a join predicate "start+(step*(ordinal-1))<=stop"
final FunctionTableGroup functionTableGroup = (FunctionTableGroup) super.convertToSqlAst(
navigablePath,
identifierVariable,
lateral,
canUseInnerJoins,
withOrdinality,
walker
);
// Register a query transformer to register a join predicate
walker.registerQueryTransformer( new NumberSeriesQueryTransformer(
functionTableGroup,
functionTableGroup,
"i",
coerceToTimestamp
) );
return functionTableGroup;
}
};
}
private static class SybaseASEGenerateSeriesSetReturningFunctionTypeResolver extends NumberSeriesGenerateSeriesSetReturningFunctionTypeResolver {
public SybaseASEGenerateSeriesSetReturningFunctionTypeResolver() {
super( "v", "i" );
}
@Override
public SelectableMapping[] resolveFunctionReturnType(
List<? extends SqlAstNode> arguments,
String tableIdentifierVariable,
boolean lateral,
boolean withOrdinality,
SqmToSqlAstConverter converter) {
return resolveIterationVariableBasedFunctionReturnType( arguments, tableIdentifierVariable, lateral, withOrdinality, converter );
}
}
@Override
protected void renderGenerateSeries(
SqlAppender sqlAppender,
Expression start,
Expression stop,
@Nullable Expression step,
AnonymousTupleTableGroupProducer tupleType,
String tableIdentifierVariable,
SqlAstTranslator<?> walker) {
final boolean startNeedsEmulation = needsVariable( start );
final boolean stepNeedsEmulation = step != null && needsVariable( step );
if ( startNeedsEmulation || stepNeedsEmulation ) {
sqlAppender.appendSql( "((select" );
char separator = ' ';
if ( startNeedsEmulation ) {
sqlAppender.appendSql( separator );
start.accept( walker );
separator = ',';
}
if ( stepNeedsEmulation ) {
sqlAppender.appendSql( separator );
if ( step instanceof Duration duration ) {
duration.getMagnitude().accept( walker );
}
else {
step.accept( walker );
}
}
sqlAppender.appendSql( ") " );
sqlAppender.appendSql( tableIdentifierVariable );
sqlAppender.appendSql( "_" );
separator = '(';
if ( startNeedsEmulation ) {
sqlAppender.appendSql( separator );
sqlAppender.appendSql( "b" );
separator = ',';
}
if ( stepNeedsEmulation ) {
sqlAppender.appendSql( separator );
sqlAppender.appendSql( "s" );
}
sqlAppender.appendSql( ") join " );
}
sqlAppender.appendSql( "xmltable('/r/a' passing '<r>'+replicate('<a/>'," );
sqlAppender.appendSql( maxSeriesSize );
sqlAppender.appendSql( ")+'</r>' columns i bigint for ordinality, v varchar(255) path '.') " );
sqlAppender.appendSql( tableIdentifierVariable );
if ( startNeedsEmulation || stepNeedsEmulation ) {
sqlAppender.appendSql( " on 1=1)" );
}
}
}

View File

@ -20,6 +20,7 @@ import org.hibernate.metamodel.mapping.internal.SelectableMappingImpl;
import org.hibernate.query.derived.AnonymousTupleType; import org.hibernate.query.derived.AnonymousTupleType;
import org.hibernate.query.sqm.SqmExpressible; import org.hibernate.query.sqm.SqmExpressible;
import org.hibernate.query.sqm.produce.function.SetReturningFunctionTypeResolver; import org.hibernate.query.sqm.produce.function.SetReturningFunctionTypeResolver;
import org.hibernate.query.sqm.sql.SqmToSqlAstConverter;
import org.hibernate.query.sqm.tree.SqmTypedNode; import org.hibernate.query.sqm.tree.SqmTypedNode;
import org.hibernate.sql.ast.tree.SqlAstNode; import org.hibernate.sql.ast.tree.SqlAstNode;
import org.hibernate.sql.ast.tree.expression.Expression; import org.hibernate.sql.ast.tree.expression.Expression;
@ -84,8 +85,9 @@ public class UnnestSetReturningFunctionTypeResolver implements SetReturningFunct
public SelectableMapping[] resolveFunctionReturnType( public SelectableMapping[] resolveFunctionReturnType(
List<? extends SqlAstNode> arguments, List<? extends SqlAstNode> arguments,
String tableIdentifierVariable, String tableIdentifierVariable,
boolean lateral,
boolean withOrdinality, boolean withOrdinality,
TypeConfiguration typeConfiguration) { SqmToSqlAstConverter converter) {
final Expression expression = (Expression) arguments.get( 0 ); final Expression expression = (Expression) arguments.get( 0 );
final JdbcMappingContainer expressionType = expression.getExpressionType(); final JdbcMappingContainer expressionType = expression.getExpressionType();
if ( expressionType == null ) { if ( expressionType == null ) {
@ -112,7 +114,7 @@ public class UnnestSetReturningFunctionTypeResolver implements SetReturningFunct
false, false,
false, false,
false, false,
typeConfiguration.getBasicTypeForJavaType( Long.class ) converter.getCreationContext().getTypeConfiguration().getBasicTypeForJavaType( Long.class )
) : null; ) : null;
final BasicType<?> elementType = pluralType.getElementType(); final BasicType<?> elementType = pluralType.getElementType();

View File

@ -4,15 +4,19 @@
*/ */
package org.hibernate.dialect.function.array; package org.hibernate.dialect.function.array;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.hibernate.metamodel.mapping.MappingModelExpressible; import org.hibernate.metamodel.mapping.MappingModelExpressible;
import org.hibernate.query.sqm.function.AbstractSqmSelfRenderingFunctionDescriptor; import org.hibernate.query.sqm.function.AbstractSqmSelfRenderingFunctionDescriptor;
import org.hibernate.query.sqm.produce.function.ArgumentTypesValidator; import org.hibernate.query.sqm.produce.function.ArgumentTypesValidator;
import org.hibernate.query.sqm.produce.function.FunctionArgumentTypeResolver; import org.hibernate.query.sqm.produce.function.FunctionArgumentTypeResolver;
import org.hibernate.query.sqm.produce.function.FunctionParameterType; import org.hibernate.query.sqm.produce.function.FunctionParameterType;
import org.hibernate.query.sqm.produce.function.internal.AbstractFunctionArgumentTypeResolver;
import org.hibernate.query.sqm.sql.SqmToSqlAstConverter; import org.hibernate.query.sqm.sql.SqmToSqlAstConverter;
import org.hibernate.query.sqm.tree.expression.SqmFunction; import org.hibernate.query.sqm.tree.SqmTypedNode;
import org.hibernate.type.BasicPluralType; import org.hibernate.type.BasicPluralType;
import java.util.List;
/** /**
* Encapsulates the validator, return type and argument type resolvers for the array_contains function. * Encapsulates the validator, return type and argument type resolvers for the array_contains function.
* Subclasses only have to implement the rendering. * Subclasses only have to implement the rendering.
@ -35,7 +39,7 @@ public abstract class AbstractArrayFillFunction extends AbstractSqmSelfRendering
return "(OBJECT element, INTEGER elementCount)"; return "(OBJECT element, INTEGER elementCount)";
} }
private static class ArrayFillArgumentsValidator implements FunctionArgumentTypeResolver { private static class ArrayFillArgumentsValidator extends AbstractFunctionArgumentTypeResolver {
public static final FunctionArgumentTypeResolver INSTANCE = new ArrayFillArgumentsValidator(); public static final FunctionArgumentTypeResolver INSTANCE = new ArrayFillArgumentsValidator();
@ -43,10 +47,7 @@ public abstract class AbstractArrayFillFunction extends AbstractSqmSelfRendering
} }
@Override @Override
public MappingModelExpressible<?> resolveFunctionArgumentType( public @Nullable MappingModelExpressible<?> resolveFunctionArgumentType(List<? extends SqmTypedNode<?>> arguments, int argumentIndex, SqmToSqlAstConverter converter) {
SqmFunction<?> function,
int argumentIndex,
SqmToSqlAstConverter converter) {
if ( argumentIndex == 0 ) { if ( argumentIndex == 0 ) {
final MappingModelExpressible<?> impliedReturnType = converter.resolveFunctionImpliedReturnType(); final MappingModelExpressible<?> impliedReturnType = converter.resolveFunctionImpliedReturnType();
return impliedReturnType instanceof BasicPluralType<?, ?> return impliedReturnType instanceof BasicPluralType<?, ?>

View File

@ -4,13 +4,20 @@
*/ */
package org.hibernate.dialect.function.array; package org.hibernate.dialect.function.array;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.hibernate.metamodel.mapping.MappingModelExpressible;
import org.hibernate.query.sqm.function.AbstractSqmSelfRenderingFunctionDescriptor; import org.hibernate.query.sqm.function.AbstractSqmSelfRenderingFunctionDescriptor;
import org.hibernate.query.sqm.produce.function.ArgumentTypesValidator; import org.hibernate.query.sqm.produce.function.ArgumentTypesValidator;
import org.hibernate.query.sqm.produce.function.FunctionParameterType; import org.hibernate.query.sqm.produce.function.FunctionParameterType;
import org.hibernate.query.sqm.produce.function.StandardArgumentsValidators; import org.hibernate.query.sqm.produce.function.StandardArgumentsValidators;
import org.hibernate.query.sqm.produce.function.StandardFunctionReturnTypeResolvers; import org.hibernate.query.sqm.produce.function.StandardFunctionReturnTypeResolvers;
import org.hibernate.query.sqm.produce.function.internal.AbstractFunctionArgumentTypeResolver;
import org.hibernate.query.sqm.sql.SqmToSqlAstConverter;
import org.hibernate.query.sqm.tree.SqmTypedNode;
import org.hibernate.type.spi.TypeConfiguration; import org.hibernate.type.spi.TypeConfiguration;
import java.util.List;
/** /**
* Encapsulates the validator, return type and argument type resolvers for the array_position functions. * Encapsulates the validator, return type and argument type resolvers for the array_position functions.
* Subclasses only have to implement the rendering. * Subclasses only have to implement the rendering.
@ -30,19 +37,22 @@ public abstract class AbstractArrayPositionFunction extends AbstractSqmSelfRende
FunctionParameterType.INTEGER FunctionParameterType.INTEGER
), ),
StandardFunctionReturnTypeResolvers.invariant( typeConfiguration.standardBasicTypeForJavaType( Integer.class ) ), StandardFunctionReturnTypeResolvers.invariant( typeConfiguration.standardBasicTypeForJavaType( Integer.class ) ),
(function, argumentIndex, converter) -> { new AbstractFunctionArgumentTypeResolver() {
if ( argumentIndex == 2 ) { @Override
return converter.getCreationContext() public @Nullable MappingModelExpressible<?> resolveFunctionArgumentType(List<? extends SqmTypedNode<?>> arguments, int argumentIndex, SqmToSqlAstConverter converter) {
.getSessionFactory() if ( argumentIndex == 2 ) {
.getTypeConfiguration() return converter.getCreationContext()
.standardBasicTypeForJavaType( Integer.class ); .getSessionFactory()
} .getTypeConfiguration()
else { .standardBasicTypeForJavaType( Integer.class );
return ArrayAndElementArgumentTypeResolver.DEFAULT_INSTANCE.resolveFunctionArgumentType( }
function, else {
argumentIndex, return ArrayAndElementArgumentTypeResolver.DEFAULT_INSTANCE.resolveFunctionArgumentType(
converter arguments,
); argumentIndex,
converter
);
}
} }
} }
); );

View File

@ -4,21 +4,24 @@
*/ */
package org.hibernate.dialect.function.array; package org.hibernate.dialect.function.array;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.hibernate.internal.util.collections.ArrayHelper; import org.hibernate.internal.util.collections.ArrayHelper;
import org.hibernate.metamodel.mapping.MappingModelExpressible; import org.hibernate.metamodel.mapping.MappingModelExpressible;
import org.hibernate.metamodel.model.domain.DomainType; import org.hibernate.metamodel.model.domain.DomainType;
import org.hibernate.query.sqm.produce.function.FunctionArgumentTypeResolver; import org.hibernate.query.sqm.produce.function.FunctionArgumentTypeResolver;
import org.hibernate.query.sqm.produce.function.internal.AbstractFunctionArgumentTypeResolver;
import org.hibernate.query.sqm.sql.SqmToSqlAstConverter; import org.hibernate.query.sqm.sql.SqmToSqlAstConverter;
import org.hibernate.query.sqm.tree.SqmTypedNode; import org.hibernate.query.sqm.tree.SqmTypedNode;
import org.hibernate.query.sqm.tree.expression.SqmExpression; import org.hibernate.query.sqm.tree.expression.SqmExpression;
import org.hibernate.query.sqm.tree.expression.SqmFunction;
import org.hibernate.type.BasicPluralType; import org.hibernate.type.BasicPluralType;
import java.util.List;
/** /**
* A {@link FunctionArgumentTypeResolver} that resolves the array argument type based on the element argument type * A {@link FunctionArgumentTypeResolver} that resolves the array argument type based on the element argument type
* or the element argument type based on the array argument type. * or the element argument type based on the array argument type.
*/ */
public class ArrayAndElementArgumentTypeResolver implements FunctionArgumentTypeResolver { public class ArrayAndElementArgumentTypeResolver extends AbstractFunctionArgumentTypeResolver {
public static final FunctionArgumentTypeResolver DEFAULT_INSTANCE = new ArrayAndElementArgumentTypeResolver( 0, 1 ); public static final FunctionArgumentTypeResolver DEFAULT_INSTANCE = new ArrayAndElementArgumentTypeResolver( 0, 1 );
@ -31,13 +34,10 @@ public class ArrayAndElementArgumentTypeResolver implements FunctionArgumentType
} }
@Override @Override
public MappingModelExpressible<?> resolveFunctionArgumentType( public @Nullable MappingModelExpressible<?> resolveFunctionArgumentType(List<? extends SqmTypedNode<?>> arguments, int argumentIndex, SqmToSqlAstConverter converter) {
SqmFunction<?> function,
int argumentIndex,
SqmToSqlAstConverter converter) {
if ( argumentIndex == arrayIndex ) { if ( argumentIndex == arrayIndex ) {
for ( int elementIndex : elementIndexes ) { for ( int elementIndex : elementIndexes ) {
final SqmTypedNode<?> node = function.getArguments().get( elementIndex ); final SqmTypedNode<?> node = arguments.get( elementIndex );
if ( node instanceof SqmExpression<?> ) { if ( node instanceof SqmExpression<?> ) {
final MappingModelExpressible<?> expressible = converter.determineValueMapping( (SqmExpression<?>) node ); final MappingModelExpressible<?> expressible = converter.determineValueMapping( (SqmExpression<?>) node );
if ( expressible != null ) { if ( expressible != null ) {
@ -50,7 +50,7 @@ public class ArrayAndElementArgumentTypeResolver implements FunctionArgumentType
} }
} }
else if ( ArrayHelper.contains( elementIndexes, argumentIndex ) ) { else if ( ArrayHelper.contains( elementIndexes, argumentIndex ) ) {
final SqmTypedNode<?> node = function.getArguments().get( arrayIndex ); final SqmTypedNode<?> node = arguments.get( arrayIndex );
if ( node instanceof SqmExpression<?> ) { if ( node instanceof SqmExpression<?> ) {
final MappingModelExpressible<?> expressible = converter.determineValueMapping( (SqmExpression<?>) node ); final MappingModelExpressible<?> expressible = converter.determineValueMapping( (SqmExpression<?>) node );
if ( expressible != null ) { if ( expressible != null ) {

View File

@ -4,29 +4,29 @@
*/ */
package org.hibernate.dialect.function.array; package org.hibernate.dialect.function.array;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.hibernate.metamodel.mapping.MappingModelExpressible; import org.hibernate.metamodel.mapping.MappingModelExpressible;
import org.hibernate.metamodel.model.domain.DomainType; import org.hibernate.metamodel.model.domain.DomainType;
import org.hibernate.query.sqm.produce.function.FunctionArgumentTypeResolver; import org.hibernate.query.sqm.produce.function.FunctionArgumentTypeResolver;
import org.hibernate.query.sqm.produce.function.internal.AbstractFunctionArgumentTypeResolver;
import org.hibernate.query.sqm.sql.SqmToSqlAstConverter; import org.hibernate.query.sqm.sql.SqmToSqlAstConverter;
import org.hibernate.query.sqm.tree.SqmTypedNode; import org.hibernate.query.sqm.tree.SqmTypedNode;
import org.hibernate.query.sqm.tree.expression.SqmExpression; import org.hibernate.query.sqm.tree.expression.SqmExpression;
import org.hibernate.query.sqm.tree.expression.SqmFunction;
import org.hibernate.type.BasicPluralType; import org.hibernate.type.BasicPluralType;
import java.util.List;
/** /**
* A {@link FunctionArgumentTypeResolver} that resolves the argument types for the {@code array_contains} function. * A {@link FunctionArgumentTypeResolver} that resolves the argument types for the {@code array_contains} function.
*/ */
public class ArrayContainsArgumentTypeResolver implements FunctionArgumentTypeResolver { public class ArrayContainsArgumentTypeResolver extends AbstractFunctionArgumentTypeResolver {
public static final FunctionArgumentTypeResolver INSTANCE = new ArrayContainsArgumentTypeResolver(); public static final FunctionArgumentTypeResolver INSTANCE = new ArrayContainsArgumentTypeResolver();
@Override @Override
public MappingModelExpressible<?> resolveFunctionArgumentType( public @Nullable MappingModelExpressible<?> resolveFunctionArgumentType(List<? extends SqmTypedNode<?>> arguments, int argumentIndex, SqmToSqlAstConverter converter) {
SqmFunction<?> function,
int argumentIndex,
SqmToSqlAstConverter converter) {
if ( argumentIndex == 0 ) { if ( argumentIndex == 0 ) {
final SqmTypedNode<?> node = function.getArguments().get( 1 ); final SqmTypedNode<?> node = arguments.get( 1 );
if ( node instanceof SqmExpression<?> ) { if ( node instanceof SqmExpression<?> ) {
final MappingModelExpressible<?> expressible = converter.determineValueMapping( (SqmExpression<?>) node ); final MappingModelExpressible<?> expressible = converter.determineValueMapping( (SqmExpression<?>) node );
if ( expressible != null ) { if ( expressible != null ) {
@ -43,7 +43,7 @@ public class ArrayContainsArgumentTypeResolver implements FunctionArgumentTypeRe
} }
} }
else if ( argumentIndex == 1 ) { else if ( argumentIndex == 1 ) {
final SqmTypedNode<?> node = function.getArguments().get( 0 ); final SqmTypedNode<?> node = arguments.get( 0 );
if ( node instanceof SqmExpression<?> ) { if ( node instanceof SqmExpression<?> ) {
final MappingModelExpressible<?> expressible = converter.determineValueMapping( (SqmExpression<?>) node ); final MappingModelExpressible<?> expressible = converter.determineValueMapping( (SqmExpression<?>) node );
if ( expressible != null ) { if ( expressible != null ) {

View File

@ -4,33 +4,33 @@
*/ */
package org.hibernate.dialect.function.array; package org.hibernate.dialect.function.array;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.hibernate.metamodel.mapping.MappingModelExpressible; import org.hibernate.metamodel.mapping.MappingModelExpressible;
import org.hibernate.query.sqm.produce.function.FunctionArgumentTypeResolver; import org.hibernate.query.sqm.produce.function.FunctionArgumentTypeResolver;
import org.hibernate.query.sqm.produce.function.internal.AbstractFunctionArgumentTypeResolver;
import org.hibernate.query.sqm.sql.SqmToSqlAstConverter; import org.hibernate.query.sqm.sql.SqmToSqlAstConverter;
import org.hibernate.query.sqm.tree.SqmTypedNode; import org.hibernate.query.sqm.tree.SqmTypedNode;
import org.hibernate.query.sqm.tree.expression.SqmExpression; import org.hibernate.query.sqm.tree.expression.SqmExpression;
import org.hibernate.query.sqm.tree.expression.SqmFunction;
import java.util.List;
/** /**
* A {@link FunctionArgumentTypeResolver} that resolves the argument types for the {@code array_includes} function. * A {@link FunctionArgumentTypeResolver} that resolves the argument types for the {@code array_includes} function.
*/ */
public class ArrayIncludesArgumentTypeResolver implements FunctionArgumentTypeResolver { public class ArrayIncludesArgumentTypeResolver extends AbstractFunctionArgumentTypeResolver {
public static final FunctionArgumentTypeResolver INSTANCE = new ArrayIncludesArgumentTypeResolver(); public static final FunctionArgumentTypeResolver INSTANCE = new ArrayIncludesArgumentTypeResolver();
@Override @Override
public MappingModelExpressible<?> resolveFunctionArgumentType( public @Nullable MappingModelExpressible<?> resolveFunctionArgumentType(List<? extends SqmTypedNode<?>> arguments, int argumentIndex, SqmToSqlAstConverter converter) {
SqmFunction<?> function,
int argumentIndex,
SqmToSqlAstConverter converter) {
if ( argumentIndex == 0 ) { if ( argumentIndex == 0 ) {
final SqmTypedNode<?> node = function.getArguments().get( 1 ); final SqmTypedNode<?> node = arguments.get( 1 );
if ( node instanceof SqmExpression<?> ) { if ( node instanceof SqmExpression<?> ) {
return converter.determineValueMapping( (SqmExpression<?>) node ); return converter.determineValueMapping( (SqmExpression<?>) node );
} }
} }
else if ( argumentIndex == 1 ) { else if ( argumentIndex == 1 ) {
final SqmTypedNode<?> node = function.getArguments().get( 0 ); final SqmTypedNode<?> node = arguments.get( 0 );
if ( node instanceof SqmExpression<?> ) { if ( node instanceof SqmExpression<?> ) {
return converter.determineValueMapping( (SqmExpression<?>) node ); return converter.determineValueMapping( (SqmExpression<?>) node );
} }

View File

@ -41,7 +41,6 @@ import org.hibernate.type.BasicType;
import org.hibernate.type.descriptor.jdbc.AggregateJdbcType; import org.hibernate.type.descriptor.jdbc.AggregateJdbcType;
import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.nullness.qual.Nullable;
import org.hibernate.type.spi.TypeConfiguration;
/** /**
* H2 unnest function. * H2 unnest function.
@ -193,8 +192,9 @@ public class H2UnnestFunction extends UnnestFunction {
public SelectableMapping[] resolveFunctionReturnType( public SelectableMapping[] resolveFunctionReturnType(
List<? extends SqlAstNode> arguments, List<? extends SqlAstNode> arguments,
String tableIdentifierVariable, String tableIdentifierVariable,
boolean lateral,
boolean withOrdinality, boolean withOrdinality,
TypeConfiguration typeConfiguration) { SqmToSqlAstConverter converter) {
final Expression expression = (Expression) arguments.get( 0 ); final Expression expression = (Expression) arguments.get( 0 );
final JdbcMappingContainer expressionType = expression.getExpressionType(); final JdbcMappingContainer expressionType = expression.getExpressionType();
if ( expressionType == null ) { if ( expressionType == null ) {
@ -221,7 +221,7 @@ public class H2UnnestFunction extends UnnestFunction {
false, false,
false, false,
false, false,
typeConfiguration.getBasicTypeForJavaType( Long.class ) converter.getCreationContext().getTypeConfiguration().getBasicTypeForJavaType( Long.class )
) : null; ) : null;
final BasicType<?> elementType = pluralType.getElementType(); final BasicType<?> elementType = pluralType.getElementType();

View File

@ -36,7 +36,7 @@ public class UnnestFunction extends AbstractSqmSelfRenderingSetReturningFunction
protected UnnestFunction(SetReturningFunctionTypeResolver setReturningFunctionTypeResolver) { protected UnnestFunction(SetReturningFunctionTypeResolver setReturningFunctionTypeResolver) {
super( super(
"unnest", "unnest",
null, ArrayArgumentValidator.DEFAULT_INSTANCE,
setReturningFunctionTypeResolver, setReturningFunctionTypeResolver,
null null
); );

View File

@ -13,6 +13,7 @@ import java.time.Duration;
import java.time.Instant; import java.time.Instant;
import java.time.temporal.Temporal; import java.time.temporal.Temporal;
import java.time.temporal.TemporalAccessor; import java.time.temporal.TemporalAccessor;
import java.time.temporal.TemporalAmount;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -4224,6 +4225,206 @@ public interface HibernateCriteriaBuilder extends CriteriaBuilder {
@Incubating @Incubating
<E> JpaSetReturningFunction<E> unnestCollection(Expression<? extends Collection<E>> collection); <E> JpaSetReturningFunction<E> unnestCollection(Expression<? extends Collection<E>> collection);
/**
* Creates a {@code generate_series} function expression to generate a set of values as rows.
*
* @since 7.0
* @see JpaSelectCriteria#from(JpaSetReturningFunction)
* @see JpaFrom#join(JpaSetReturningFunction)
*/
@Incubating
<E extends Number> JpaSetReturningFunction<E> generateSeries(E start, E stop);
/**
* Creates a {@code generate_series} function expression to generate a set of values as rows.
*
* @since 7.0
* @see JpaSelectCriteria#from(JpaSetReturningFunction)
* @see JpaFrom#join(JpaSetReturningFunction)
*/
@Incubating
<E extends Number> JpaSetReturningFunction<E> generateSeries(E start, Expression<E> stop);
/**
* Creates a {@code generate_series} function expression to generate a set of values as rows.
*
* @since 7.0
* @see JpaSelectCriteria#from(JpaSetReturningFunction)
* @see JpaFrom#join(JpaSetReturningFunction)
*/
@Incubating
<E extends Number> JpaSetReturningFunction<E> generateSeries(Expression<E> start, E stop);
/**
* Creates a {@code generate_series} function expression to generate a set of values as rows.
*
* @since 7.0
* @see JpaSelectCriteria#from(JpaSetReturningFunction)
* @see JpaFrom#join(JpaSetReturningFunction)
*/
@Incubating
<E extends Number> JpaSetReturningFunction<E> generateSeries(Expression<E> start, Expression<E> stop);
/**
* Creates a {@code generate_series} function expression to generate a set of values as rows.
*
* @since 7.0
* @see JpaSelectCriteria#from(JpaSetReturningFunction)
* @see JpaFrom#join(JpaSetReturningFunction)
*/
@Incubating
<E extends Number> JpaSetReturningFunction<E> generateSeries(E start, Expression<E> stop, Expression<E> step);
/**
* Creates a {@code generate_series} function expression to generate a set of values as rows.
*
* @since 7.0
* @see JpaSelectCriteria#from(JpaSetReturningFunction)
* @see JpaFrom#join(JpaSetReturningFunction)
*/
@Incubating
<E extends Number> JpaSetReturningFunction<E> generateSeries(Expression<E> start, E stop, Expression<E> step);
/**
* Creates a {@code generate_series} function expression to generate a set of values as rows.
*
* @since 7.0
* @see JpaSelectCriteria#from(JpaSetReturningFunction)
* @see JpaFrom#join(JpaSetReturningFunction)
*/
@Incubating
<E extends Number> JpaSetReturningFunction<E> generateSeries(Expression<E> start, Expression<E> stop, E step);
/**
* Creates a {@code generate_series} function expression to generate a set of values as rows.
*
* @since 7.0
* @see JpaSelectCriteria#from(JpaSetReturningFunction)
* @see JpaFrom#join(JpaSetReturningFunction)
*/
@Incubating
<E extends Number> JpaSetReturningFunction<E> generateSeries(E start, Expression<E> stop, E step);
/**
* Creates a {@code generate_series} function expression to generate a set of values as rows.
*
* @since 7.0
* @see JpaSelectCriteria#from(JpaSetReturningFunction)
* @see JpaFrom#join(JpaSetReturningFunction)
*/
@Incubating
<E extends Number> JpaSetReturningFunction<E> generateSeries(Expression<E> start, E stop, E step);
/**
* Creates a {@code generate_series} function expression to generate a set of values as rows.
*
* @since 7.0
* @see JpaSelectCriteria#from(JpaSetReturningFunction)
* @see JpaFrom#join(JpaSetReturningFunction)
*/
@Incubating
<E extends Number> JpaSetReturningFunction<E> generateSeries(E start, E stop, Expression<E> step);
/**
* Creates a {@code generate_series} function expression to generate a set of values as rows.
*
* @since 7.0
* @see JpaSelectCriteria#from(JpaSetReturningFunction)
* @see JpaFrom#join(JpaSetReturningFunction)
*/
@Incubating
<E extends Number> JpaSetReturningFunction<E> generateSeries(E start, E stop, E step);
/**
* Creates a {@code generate_series} function expression to generate a set of values as rows.
*
* @since 7.0
* @see JpaSelectCriteria#from(JpaSetReturningFunction)
* @see JpaFrom#join(JpaSetReturningFunction)
*/
@Incubating
<E extends Number> JpaSetReturningFunction<E> generateSeries(Expression<E> start, Expression<E> stop, Expression<E> step);
/**
* Creates a {@code generate_series} function expression to generate a set of values as rows.
*
* @since 7.0
* @see JpaSelectCriteria#from(JpaSetReturningFunction)
* @see JpaFrom#join(JpaSetReturningFunction)
*/
@Incubating
<E extends Temporal> JpaSetReturningFunction<E> generateTimeSeries(E start, Expression<E> stop, Expression<? extends TemporalAmount> step);
/**
* Creates a {@code generate_series} function expression to generate a set of values as rows.
*
* @since 7.0
* @see JpaSelectCriteria#from(JpaSetReturningFunction)
* @see JpaFrom#join(JpaSetReturningFunction)
*/
@Incubating
<E extends Temporal> JpaSetReturningFunction<E> generateTimeSeries(Expression<E> start, E stop, Expression<? extends TemporalAmount> step);
/**
* Creates a {@code generate_series} function expression to generate a set of values as rows.
*
* @since 7.0
* @see JpaSelectCriteria#from(JpaSetReturningFunction)
* @see JpaFrom#join(JpaSetReturningFunction)
*/
@Incubating
<E extends Temporal> JpaSetReturningFunction<E> generateTimeSeries(E start, E stop, Expression<? extends TemporalAmount> step);
/**
* Creates a {@code generate_series} function expression to generate a set of values as rows.
*
* @since 7.0
* @see JpaSelectCriteria#from(JpaSetReturningFunction)
* @see JpaFrom#join(JpaSetReturningFunction)
*/
@Incubating
<E extends Temporal> JpaSetReturningFunction<E> generateTimeSeries(Expression<E> start, Expression<E> stop, TemporalAmount step);
/**
* Creates a {@code generate_series} function expression to generate a set of values as rows.
*
* @since 7.0
* @see JpaSelectCriteria#from(JpaSetReturningFunction)
* @see JpaFrom#join(JpaSetReturningFunction)
*/
@Incubating
<E extends Temporal> JpaSetReturningFunction<E> generateTimeSeries(Expression<E> start, E stop, TemporalAmount step);
/**
* Creates a {@code generate_series} function expression to generate a set of values as rows.
*
* @since 7.0
* @see JpaSelectCriteria#from(JpaSetReturningFunction)
* @see JpaFrom#join(JpaSetReturningFunction)
*/
@Incubating
<E extends Temporal> JpaSetReturningFunction<E> generateTimeSeries(E start, Expression<E> stop, TemporalAmount step);
/**
* Creates a {@code generate_series} function expression to generate a set of values as rows.
*
* @since 7.0
* @see JpaSelectCriteria#from(JpaSetReturningFunction)
* @see JpaFrom#join(JpaSetReturningFunction)
*/
@Incubating
<E extends Temporal> JpaSetReturningFunction<E> generateTimeSeries(E start, E stop, TemporalAmount step);
/**
* Creates a {@code generate_series} function expression to generate a set of values as rows.
*
* @since 7.0
* @see JpaSelectCriteria#from(JpaSetReturningFunction)
* @see JpaFrom#join(JpaSetReturningFunction)
*/
@Incubating
<E extends Temporal> JpaSetReturningFunction<E> generateTimeSeries(Expression<E> start, Expression<E> stop, Expression<? extends TemporalAmount> step);
@Override @Override
JpaPredicate and(List<Predicate> restrictions); JpaPredicate and(List<Predicate> restrictions);

View File

@ -16,6 +16,7 @@ import java.time.LocalDateTime;
import java.time.LocalTime; import java.time.LocalTime;
import java.time.temporal.Temporal; import java.time.temporal.Temporal;
import java.time.temporal.TemporalAccessor; import java.time.temporal.TemporalAccessor;
import java.time.temporal.TemporalAmount;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -3753,4 +3754,124 @@ public class HibernateCriteriaBuilderDelegate implements HibernateCriteriaBuilde
public <E> JpaSetReturningFunction<E> unnestCollection(Expression<? extends Collection<E>> collection) { public <E> JpaSetReturningFunction<E> unnestCollection(Expression<? extends Collection<E>> collection) {
return criteriaBuilder.unnestCollection( collection ); return criteriaBuilder.unnestCollection( collection );
} }
@Incubating
@Override
public <E extends Number> JpaSetReturningFunction<E> generateSeries(E start, E stop) {
return criteriaBuilder.generateSeries( start, stop );
}
@Incubating
@Override
public <E extends Number> JpaSetReturningFunction<E> generateSeries(E start, Expression<E> stop) {
return criteriaBuilder.generateSeries( start, stop );
}
@Incubating
@Override
public <E extends Number> JpaSetReturningFunction<E> generateSeries(Expression<E> start, E stop) {
return criteriaBuilder.generateSeries( start, stop );
}
@Incubating
@Override
public <E extends Number> JpaSetReturningFunction<E> generateSeries(Expression<E> start, Expression<E> stop) {
return criteriaBuilder.generateSeries( start, stop );
}
@Incubating
@Override
public <E extends Number> JpaSetReturningFunction<E> generateSeries(E start, Expression<E> stop, Expression<E> step) {
return criteriaBuilder.generateSeries( start, stop, step );
}
@Incubating
@Override
public <E extends Number> JpaSetReturningFunction<E> generateSeries(Expression<E> start, E stop, Expression<E> step) {
return criteriaBuilder.generateSeries( start, stop, step );
}
@Incubating
@Override
public <E extends Number> JpaSetReturningFunction<E> generateSeries(Expression<E> start, Expression<E> stop, E step) {
return criteriaBuilder.generateSeries( start, stop, step );
}
@Incubating
@Override
public <E extends Number> JpaSetReturningFunction<E> generateSeries(E start, Expression<E> stop, E step) {
return criteriaBuilder.generateSeries( start, stop, step );
}
@Incubating
@Override
public <E extends Number> JpaSetReturningFunction<E> generateSeries(Expression<E> start, E stop, E step) {
return criteriaBuilder.generateSeries( start, stop, step );
}
@Incubating
@Override
public <E extends Number> JpaSetReturningFunction<E> generateSeries(E start, E stop, Expression<E> step) {
return criteriaBuilder.generateSeries( start, stop, step );
}
@Incubating
@Override
public <E extends Number> JpaSetReturningFunction<E> generateSeries(E start, E stop, E step) {
return criteriaBuilder.generateSeries( start, stop, step );
}
@Incubating
@Override
public <E extends Number> JpaSetReturningFunction<E> generateSeries(Expression<E> start, Expression<E> stop, Expression<E> step) {
return criteriaBuilder.generateSeries( start, stop, step );
}
@Incubating
@Override
public <E extends Temporal> JpaSetReturningFunction<E> generateTimeSeries(E start, Expression<E> stop, Expression<? extends TemporalAmount> step) {
return criteriaBuilder.generateTimeSeries( start, stop, step );
}
@Incubating
@Override
public <E extends Temporal> JpaSetReturningFunction<E> generateTimeSeries(Expression<E> start, E stop, Expression<? extends TemporalAmount> step) {
return criteriaBuilder.generateTimeSeries( start, stop, step );
}
@Incubating
@Override
public <E extends Temporal> JpaSetReturningFunction<E> generateTimeSeries(E start, E stop, Expression<? extends TemporalAmount> step) {
return criteriaBuilder.generateTimeSeries( start, stop, step );
}
@Incubating
@Override
public <E extends Temporal> JpaSetReturningFunction<E> generateTimeSeries(Expression<E> start, Expression<E> stop, TemporalAmount step) {
return criteriaBuilder.generateTimeSeries( start, stop, step );
}
@Incubating
@Override
public <E extends Temporal> JpaSetReturningFunction<E> generateTimeSeries(Expression<E> start, E stop, TemporalAmount step) {
return criteriaBuilder.generateTimeSeries( start, stop, step );
}
@Incubating
@Override
public <E extends Temporal> JpaSetReturningFunction<E> generateTimeSeries(E start, Expression<E> stop, TemporalAmount step) {
return criteriaBuilder.generateTimeSeries( start, stop, step );
}
@Incubating
@Override
public <E extends Temporal> JpaSetReturningFunction<E> generateTimeSeries(E start, E stop, TemporalAmount step) {
return criteriaBuilder.generateTimeSeries( start, stop, step );
}
@Incubating
@Override
public <E extends Temporal> JpaSetReturningFunction<E> generateTimeSeries(Expression<E> start, Expression<E> stop, Expression<? extends TemporalAmount> step) {
return criteriaBuilder.generateTimeSeries( start, stop, step );
}
} }

View File

@ -12,6 +12,7 @@ import java.util.Map;
import org.hibernate.Incubating; import org.hibernate.Incubating;
import org.hibernate.internal.util.collections.CollectionHelper; import org.hibernate.internal.util.collections.CollectionHelper;
import org.hibernate.metamodel.UnsupportedMappingException; import org.hibernate.metamodel.UnsupportedMappingException;
import org.hibernate.metamodel.mapping.CollectionPart;
import org.hibernate.metamodel.mapping.JdbcMappingContainer; import org.hibernate.metamodel.mapping.JdbcMappingContainer;
import org.hibernate.metamodel.mapping.SqlTypedMapping; import org.hibernate.metamodel.mapping.SqlTypedMapping;
import org.hibernate.metamodel.mapping.internal.SqlTypedMappingImpl; import org.hibernate.metamodel.mapping.internal.SqlTypedMappingImpl;
@ -43,7 +44,7 @@ import org.checkerframework.checker.nullness.qual.Nullable;
@Incubating @Incubating
public class AnonymousTupleType<T> implements TupleType<T>, DomainType<T>, ReturnableType<T>, SqmPathSource<T> { public class AnonymousTupleType<T> implements TupleType<T>, DomainType<T>, ReturnableType<T>, SqmPathSource<T> {
private final ObjectArrayJavaType javaTypeDescriptor; private final JavaType<T> javaTypeDescriptor;
private final @Nullable NavigablePath[] componentSourcePaths; private final @Nullable NavigablePath[] componentSourcePaths;
private final SqmExpressible<?>[] expressibles; private final SqmExpressible<?>[] expressibles;
private final String[] componentNames; private final String[] componentNames;
@ -65,7 +66,8 @@ public class AnonymousTupleType<T> implements TupleType<T>, DomainType<T>, Retur
this.expressibles = expressibles; this.expressibles = expressibles;
this.componentSourcePaths = componentSourcePaths; this.componentSourcePaths = componentSourcePaths;
this.componentNames = new String[components.length]; this.componentNames = new String[components.length];
this.javaTypeDescriptor = new ObjectArrayJavaType( getTypeDescriptors( components ) ); //noinspection unchecked
this.javaTypeDescriptor = (JavaType<T>) new ObjectArrayJavaType( getTypeDescriptors( components ) );
final Map<String, Integer> map = CollectionHelper.linkedMapOfSize( components.length ); final Map<String, Integer> map = CollectionHelper.linkedMapOfSize( components.length );
for ( int i = 0; i < components.length; i++ ) { for ( int i = 0; i < components.length; i++ ) {
final SqmSelectableNode<?> component = components[i]; final SqmSelectableNode<?> component = components[i];
@ -84,11 +86,23 @@ public class AnonymousTupleType<T> implements TupleType<T>, DomainType<T>, Retur
this.componentSourcePaths = new NavigablePath[componentNames.length]; this.componentSourcePaths = new NavigablePath[componentNames.length];
this.expressibles = expressibles; this.expressibles = expressibles;
this.componentNames = componentNames; this.componentNames = componentNames;
this.javaTypeDescriptor = new ObjectArrayJavaType( getTypeDescriptors( expressibles ) );
final Map<String, Integer> map = CollectionHelper.linkedMapOfSize( expressibles.length ); final Map<String, Integer> map = CollectionHelper.linkedMapOfSize( expressibles.length );
int elementIndex = -1;
for ( int i = 0; i < componentNames.length; i++ ) { for ( int i = 0; i < componentNames.length; i++ ) {
if ( CollectionPart.Nature.ELEMENT.getName().equals( componentNames[i] ) ) {
elementIndex = i;
}
map.put( componentNames[i], i ); map.put( componentNames[i], i );
} }
// The expressible java type of this tuple type must be equal to the element type if it exists
if ( elementIndex == -1 ) {
//noinspection unchecked
this.javaTypeDescriptor = (JavaType<T>) new ObjectArrayJavaType( getTypeDescriptors( expressibles ) );
}
else {
//noinspection unchecked
this.javaTypeDescriptor = (JavaType<T>) expressibles[elementIndex].getExpressibleJavaType();
}
this.componentIndexMap = map; this.componentIndexMap = map;
} }

View File

@ -10,6 +10,8 @@ import java.sql.Date;
import java.sql.Time; import java.sql.Time;
import java.sql.Timestamp; import java.sql.Timestamp;
import java.time.Instant; import java.time.Instant;
import java.time.temporal.Temporal;
import java.time.temporal.TemporalAmount;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -811,6 +813,66 @@ public interface NodeBuilder extends HibernateCriteriaBuilder, BindingContext {
@Override @Override
<E> SqmSetReturningFunction<E> unnestCollection(Expression<? extends Collection<E>> collection); <E> SqmSetReturningFunction<E> unnestCollection(Expression<? extends Collection<E>> collection);
@Override
<E extends Temporal> SqmSetReturningFunction<E> generateTimeSeries(Expression<E> start, Expression<E> stop, Expression<? extends TemporalAmount> step);
@Override
<E extends Temporal> SqmSetReturningFunction<E> generateTimeSeries(E start, E stop, TemporalAmount step);
@Override
<E extends Temporal> SqmSetReturningFunction<E> generateTimeSeries(E start, Expression<E> stop, TemporalAmount step);
@Override
<E extends Temporal> SqmSetReturningFunction<E> generateTimeSeries(Expression<E> start, E stop, TemporalAmount step);
@Override
<E extends Temporal> SqmSetReturningFunction<E> generateTimeSeries(Expression<E> start, Expression<E> stop, TemporalAmount step);
@Override
<E extends Temporal> SqmSetReturningFunction<E> generateTimeSeries(E start, E stop, Expression<? extends TemporalAmount> step);
@Override
<E extends Temporal> SqmSetReturningFunction<E> generateTimeSeries(Expression<E> start, E stop, Expression<? extends TemporalAmount> step);
@Override
<E extends Temporal> SqmSetReturningFunction<E> generateTimeSeries(E start, Expression<E> stop, Expression<? extends TemporalAmount> step);
@Override
<E extends Number> SqmSetReturningFunction<E> generateSeries(Expression<E> start, Expression<E> stop, Expression<E> step);
@Override
<E extends Number> SqmSetReturningFunction<E> generateSeries(E start, E stop, E step);
@Override
<E extends Number> SqmSetReturningFunction<E> generateSeries(E start, E stop, Expression<E> step);
@Override
<E extends Number> SqmSetReturningFunction<E> generateSeries(Expression<E> start, E stop, E step);
@Override
<E extends Number> SqmSetReturningFunction<E> generateSeries(E start, Expression<E> stop, E step);
@Override
<E extends Number> SqmSetReturningFunction<E> generateSeries(Expression<E> start, Expression<E> stop, E step);
@Override
<E extends Number> SqmSetReturningFunction<E> generateSeries(Expression<E> start, E stop, Expression<E> step);
@Override
<E extends Number> SqmSetReturningFunction<E> generateSeries(E start, Expression<E> stop, Expression<E> step);
@Override
<E extends Number> SqmSetReturningFunction<E> generateSeries(Expression<E> start, Expression<E> stop);
@Override
<E extends Number> SqmSetReturningFunction<E> generateSeries(Expression<E> start, E stop);
@Override
<E extends Number> SqmSetReturningFunction<E> generateSeries(E start, Expression<E> stop);
@Override
<E extends Number> SqmSetReturningFunction<E> generateSeries(E start, E stop);
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Covariant overrides // Covariant overrides

View File

@ -6,6 +6,7 @@ package org.hibernate.query.sqm.function;
import java.util.List; import java.util.List;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.hibernate.query.ReturnableType; import org.hibernate.query.ReturnableType;
import org.hibernate.query.spi.QueryEngine; import org.hibernate.query.spi.QueryEngine;
import org.hibernate.query.sqm.produce.function.ArgumentsValidator; import org.hibernate.query.sqm.produce.function.ArgumentsValidator;
@ -33,22 +34,22 @@ public abstract class AbstractSqmFunctionDescriptor implements SqmFunctionDescri
public AbstractSqmFunctionDescriptor( public AbstractSqmFunctionDescriptor(
String name, String name,
ArgumentsValidator argumentsValidator) { @Nullable ArgumentsValidator argumentsValidator) {
this( name, argumentsValidator, null, null ); this( name, argumentsValidator, null, null );
} }
public AbstractSqmFunctionDescriptor( public AbstractSqmFunctionDescriptor(
String name, String name,
ArgumentsValidator argumentsValidator, @Nullable ArgumentsValidator argumentsValidator,
FunctionArgumentTypeResolver argumentTypeResolver) { @Nullable FunctionArgumentTypeResolver argumentTypeResolver) {
this( name, argumentsValidator, null, argumentTypeResolver ); this( name, argumentsValidator, null, argumentTypeResolver );
} }
public AbstractSqmFunctionDescriptor( public AbstractSqmFunctionDescriptor(
String name, String name,
ArgumentsValidator argumentsValidator, @Nullable ArgumentsValidator argumentsValidator,
FunctionReturnTypeResolver returnTypeResolver, @Nullable FunctionReturnTypeResolver returnTypeResolver,
FunctionArgumentTypeResolver argumentTypeResolver) { @Nullable FunctionArgumentTypeResolver argumentTypeResolver) {
this.name = name; this.name = name;
this.argumentsValidator = argumentsValidator == null this.argumentsValidator = argumentsValidator == null
? StandardArgumentsValidators.NONE ? StandardArgumentsValidators.NONE

View File

@ -4,6 +4,7 @@
*/ */
package org.hibernate.query.sqm.function; package org.hibernate.query.sqm.function;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.hibernate.query.ReturnableType; import org.hibernate.query.ReturnableType;
import org.hibernate.query.spi.QueryEngine; import org.hibernate.query.spi.QueryEngine;
import org.hibernate.query.sqm.produce.function.ArgumentsValidator; import org.hibernate.query.sqm.produce.function.ArgumentsValidator;
@ -28,9 +29,9 @@ public abstract class AbstractSqmSelfRenderingFunctionDescriptor
public AbstractSqmSelfRenderingFunctionDescriptor( public AbstractSqmSelfRenderingFunctionDescriptor(
String name, String name,
ArgumentsValidator argumentsValidator, @Nullable ArgumentsValidator argumentsValidator,
FunctionReturnTypeResolver returnTypeResolver, @Nullable FunctionReturnTypeResolver returnTypeResolver,
FunctionArgumentTypeResolver argumentTypeResolver) { @Nullable FunctionArgumentTypeResolver argumentTypeResolver) {
super( name, argumentsValidator, returnTypeResolver, argumentTypeResolver ); super( name, argumentsValidator, returnTypeResolver, argumentTypeResolver );
this.functionKind = FunctionKind.NORMAL; this.functionKind = FunctionKind.NORMAL;
} }
@ -38,9 +39,9 @@ public abstract class AbstractSqmSelfRenderingFunctionDescriptor
public AbstractSqmSelfRenderingFunctionDescriptor( public AbstractSqmSelfRenderingFunctionDescriptor(
String name, String name,
FunctionKind functionKind, FunctionKind functionKind,
ArgumentsValidator argumentsValidator, @Nullable ArgumentsValidator argumentsValidator,
FunctionReturnTypeResolver returnTypeResolver, @Nullable FunctionReturnTypeResolver returnTypeResolver,
FunctionArgumentTypeResolver argumentTypeResolver) { @Nullable FunctionArgumentTypeResolver argumentTypeResolver) {
super( name, argumentsValidator, returnTypeResolver, argumentTypeResolver ); super( name, argumentsValidator, returnTypeResolver, argumentTypeResolver );
this.functionKind = functionKind; this.functionKind = functionKind;
} }

View File

@ -4,6 +4,7 @@
*/ */
package org.hibernate.query.sqm.function; package org.hibernate.query.sqm.function;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.hibernate.query.ReturnableType; import org.hibernate.query.ReturnableType;
import org.hibernate.query.sqm.produce.function.ArgumentsValidator; import org.hibernate.query.sqm.produce.function.ArgumentsValidator;
import org.hibernate.query.sqm.produce.function.FunctionArgumentTypeResolver; import org.hibernate.query.sqm.produce.function.FunctionArgumentTypeResolver;
@ -41,8 +42,8 @@ public class NamedSqmFunctionDescriptor
public NamedSqmFunctionDescriptor( public NamedSqmFunctionDescriptor(
String functionName, String functionName,
boolean useParenthesesWhenNoArgs, boolean useParenthesesWhenNoArgs,
ArgumentsValidator argumentsValidator, @Nullable ArgumentsValidator argumentsValidator,
FunctionReturnTypeResolver returnTypeResolver) { @Nullable FunctionReturnTypeResolver returnTypeResolver) {
this( this(
functionName, functionName,
useParenthesesWhenNoArgs, useParenthesesWhenNoArgs,
@ -59,9 +60,9 @@ public class NamedSqmFunctionDescriptor
public NamedSqmFunctionDescriptor( public NamedSqmFunctionDescriptor(
String functionName, String functionName,
boolean useParenthesesWhenNoArgs, boolean useParenthesesWhenNoArgs,
ArgumentsValidator argumentsValidator, @Nullable ArgumentsValidator argumentsValidator,
FunctionReturnTypeResolver returnTypeResolver, @Nullable FunctionReturnTypeResolver returnTypeResolver,
FunctionArgumentTypeResolver argumentTypeResolver) { @Nullable FunctionArgumentTypeResolver argumentTypeResolver) {
this( this(
functionName, functionName,
useParenthesesWhenNoArgs, useParenthesesWhenNoArgs,
@ -78,9 +79,9 @@ public class NamedSqmFunctionDescriptor
public NamedSqmFunctionDescriptor( public NamedSqmFunctionDescriptor(
String functionName, String functionName,
boolean useParenthesesWhenNoArgs, boolean useParenthesesWhenNoArgs,
ArgumentsValidator argumentsValidator, @Nullable ArgumentsValidator argumentsValidator,
FunctionReturnTypeResolver returnTypeResolver, @Nullable FunctionReturnTypeResolver returnTypeResolver,
FunctionArgumentTypeResolver argumentTypeResolver, @Nullable FunctionArgumentTypeResolver argumentTypeResolver,
String name, String name,
FunctionKind functionKind, FunctionKind functionKind,
String argumentListSignature, String argumentListSignature,

View File

@ -238,7 +238,7 @@ public class SelfRenderingSqmFunction<T> extends SqmFunction<T> {
@Override @Override
public MappingModelExpressible<?> get() { public MappingModelExpressible<?> get() {
return argumentTypeResolver.resolveFunctionArgumentType( function, argumentIndex, converter ); return argumentTypeResolver.resolveFunctionArgumentType( function.getArguments(), argumentIndex, converter );
} }
} }

View File

@ -6,15 +6,18 @@ package org.hibernate.query.sqm.function;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.function.Supplier;
import org.hibernate.Incubating; import org.hibernate.Incubating;
import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.metamodel.mapping.JdbcMappingContainer; import org.hibernate.metamodel.mapping.JdbcMappingContainer;
import org.hibernate.metamodel.mapping.MappingModelExpressible;
import org.hibernate.metamodel.mapping.SelectableMapping; import org.hibernate.metamodel.mapping.SelectableMapping;
import org.hibernate.query.derived.AnonymousTupleTableGroupProducer; import org.hibernate.query.derived.AnonymousTupleTableGroupProducer;
import org.hibernate.query.derived.AnonymousTupleType; import org.hibernate.query.derived.AnonymousTupleType;
import org.hibernate.query.sqm.NodeBuilder; import org.hibernate.query.sqm.NodeBuilder;
import org.hibernate.query.sqm.produce.function.ArgumentsValidator; import org.hibernate.query.sqm.produce.function.ArgumentsValidator;
import org.hibernate.query.sqm.produce.function.FunctionArgumentTypeResolver;
import org.hibernate.query.sqm.produce.function.SetReturningFunctionTypeResolver; import org.hibernate.query.sqm.produce.function.SetReturningFunctionTypeResolver;
import org.hibernate.query.sqm.sql.SqmToSqlAstConverter; import org.hibernate.query.sqm.sql.SqmToSqlAstConverter;
import org.hibernate.query.sqm.tree.SqmCopyContext; import org.hibernate.query.sqm.tree.SqmCopyContext;
@ -98,13 +101,59 @@ public class SelfRenderingSqmSetReturningFunction<T> extends SqmSetReturningFunc
if ( sqmArguments.isEmpty() ) { if ( sqmArguments.isEmpty() ) {
return emptyList(); return emptyList();
} }
final ArrayList<SqlAstNode> sqlAstArguments = new ArrayList<>( sqmArguments.size() ); final FunctionArgumentTypeResolver argumentTypeResolver;
for ( int i = 0; i < sqmArguments.size(); i++ ) { if ( getFunctionDescriptor() instanceof AbstractSqmSetReturningFunctionDescriptor ) {
sqlAstArguments.add( argumentTypeResolver = ( (AbstractSqmSetReturningFunctionDescriptor) getFunctionDescriptor() ).getArgumentTypeResolver();
(SqlAstNode) sqmArguments.get( i ).accept( walker ) }
); else {
argumentTypeResolver = null;
}
if ( argumentTypeResolver == null ) {
final ArrayList<SqlAstNode> sqlAstArguments = new ArrayList<>( sqmArguments.size() );
for ( int i = 0; i < sqmArguments.size(); i++ ) {
sqlAstArguments.add(
(SqlAstNode) sqmArguments.get( i ).accept( walker )
);
}
return sqlAstArguments;
}
else {
final FunctionArgumentTypeResolverTypeAccess typeAccess = new FunctionArgumentTypeResolverTypeAccess(
walker,
this,
argumentTypeResolver
);
final ArrayList<SqlAstNode> sqlAstArguments = new ArrayList<>( sqmArguments.size() );
for ( int i = 0; i < sqmArguments.size(); i++ ) {
typeAccess.argumentIndex = i;
sqlAstArguments.add(
(SqlAstNode) walker.visitWithInferredType( sqmArguments.get( i ), typeAccess )
);
}
return sqlAstArguments;
}
}
private static class FunctionArgumentTypeResolverTypeAccess implements Supplier<MappingModelExpressible<?>> {
private final SqmToSqlAstConverter converter;
private final SqmSetReturningFunction<?> function;
private final FunctionArgumentTypeResolver argumentTypeResolver;
private int argumentIndex;
public FunctionArgumentTypeResolverTypeAccess(
SqmToSqlAstConverter converter,
SqmSetReturningFunction<?> function,
FunctionArgumentTypeResolver argumentTypeResolver) {
this.converter = converter;
this.function = function;
this.argumentTypeResolver = argumentTypeResolver;
}
@Override
public MappingModelExpressible<?> get() {
return argumentTypeResolver.resolveFunctionArgumentType( function.getArguments(), argumentIndex, converter );
} }
return sqlAstArguments;
} }
@Override @Override
@ -123,8 +172,9 @@ public class SelfRenderingSqmSetReturningFunction<T> extends SqmSetReturningFunc
final SelectableMapping[] selectableMappings = getSetReturningTypeResolver().resolveFunctionReturnType( final SelectableMapping[] selectableMappings = getSetReturningTypeResolver().resolveFunctionReturnType(
arguments, arguments,
identifierVariable, identifierVariable,
lateral,
withOrdinality, withOrdinality,
walker.getCreationContext().getTypeConfiguration() walker
); );
final AnonymousTupleTableGroupProducer tableGroupProducer = getType().resolveTableGroupProducer( final AnonymousTupleTableGroupProducer tableGroupProducer = getType().resolveTableGroupProducer(
identifierVariable, identifierVariable,

View File

@ -19,6 +19,7 @@ import java.time.LocalDateTime;
import java.time.LocalTime; import java.time.LocalTime;
import java.time.temporal.Temporal; import java.time.temporal.Temporal;
import java.time.temporal.TemporalAccessor; import java.time.temporal.TemporalAccessor;
import java.time.temporal.TemporalAmount;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
@ -5842,4 +5843,113 @@ public class SqmCriteriaNodeBuilder implements NodeBuilder, Serializable {
queryEngine queryEngine
); );
} }
@Override
public <E extends Temporal> SqmSetReturningFunction<E> generateTimeSeries(Expression<E> start, Expression<E> stop, Expression<? extends TemporalAmount> step) {
return getSetReturningFunctionDescriptor( "generate_series" ).generateSqmExpression(
asList( (SqmTypedNode<?>) start, (SqmTypedNode<?>) stop, (SqmTypedNode<?>) step ),
queryEngine
);
}
@Override
public <E extends Temporal> SqmSetReturningFunction<E> generateTimeSeries(E start, E stop, TemporalAmount step) {
return generateTimeSeries( value( start ), value( stop ), value( step ) );
}
@Override
public <E extends Temporal> SqmSetReturningFunction<E> generateTimeSeries(E start, Expression<E> stop, TemporalAmount step) {
return generateTimeSeries( value( start ), stop, value( step ) );
}
@Override
public <E extends Temporal> SqmSetReturningFunction<E> generateTimeSeries(Expression<E> start, E stop, TemporalAmount step) {
return generateTimeSeries( start, value( stop ), value( step ) );
}
@Override
public <E extends Temporal> SqmSetReturningFunction<E> generateTimeSeries(Expression<E> start, Expression<E> stop, TemporalAmount step) {
return generateTimeSeries( start, stop, value( step ) );
}
@Override
public <E extends Temporal> SqmSetReturningFunction<E> generateTimeSeries(E start, E stop, Expression<? extends TemporalAmount> step) {
return generateTimeSeries( value( start ), value( stop ), step );
}
@Override
public <E extends Temporal> SqmSetReturningFunction<E> generateTimeSeries(Expression<E> start, E stop, Expression<? extends TemporalAmount> step) {
return generateTimeSeries( start, value( stop ), step );
}
@Override
public <E extends Temporal> SqmSetReturningFunction<E> generateTimeSeries(E start, Expression<E> stop, Expression<? extends TemporalAmount> step) {
return generateTimeSeries( value( start ), stop, step );
}
@Override
public <E extends Number> SqmSetReturningFunction<E> generateSeries(Expression<E> start, Expression<E> stop, Expression<E> step) {
return getSetReturningFunctionDescriptor( "generate_series" ).generateSqmExpression(
asList( (SqmTypedNode<?>) start, (SqmTypedNode<?>) stop, (SqmTypedNode<?>) step ),
queryEngine
);
}
@Override
public <E extends Number> SqmSetReturningFunction<E> generateSeries(E start, E stop, E step) {
return generateSeries( value( start ), value( stop ), value( step ) );
}
@Override
public <E extends Number> SqmSetReturningFunction<E> generateSeries(E start, E stop, Expression<E> step) {
return generateSeries( value( start ), value( stop ), step );
}
@Override
public <E extends Number> SqmSetReturningFunction<E> generateSeries(Expression<E> start, E stop, E step) {
return generateSeries( start, value( stop ), value( step ) );
}
@Override
public <E extends Number> SqmSetReturningFunction<E> generateSeries(E start, Expression<E> stop, E step) {
return generateSeries( value( start ), stop, value( step ) );
}
@Override
public <E extends Number> SqmSetReturningFunction<E> generateSeries(Expression<E> start, Expression<E> stop, E step) {
return generateSeries( start, stop, value( step ) );
}
@Override
public <E extends Number> SqmSetReturningFunction<E> generateSeries(Expression<E> start, E stop, Expression<E> step) {
return generateSeries( start, value( stop ), step );
}
@Override
public <E extends Number> SqmSetReturningFunction<E> generateSeries(E start, Expression<E> stop, Expression<E> step) {
return generateSeries( value( start ), stop, step );
}
@Override
public <E extends Number> SqmSetReturningFunction<E> generateSeries(Expression<E> start, Expression<E> stop) {
return getSetReturningFunctionDescriptor( "generate_series" ).generateSqmExpression(
asList( (SqmTypedNode<?>) start, (SqmTypedNode<?>) stop ),
queryEngine
);
}
@Override
public <E extends Number> SqmSetReturningFunction<E> generateSeries(Expression<E> start, E stop) {
return generateSeries( start, value( stop ) );
}
@Override
public <E extends Number> SqmSetReturningFunction<E> generateSeries(E start, Expression<E> stop) {
return generateSeries( value( start ), stop );
}
@Override
public <E extends Number> SqmSetReturningFunction<E> generateSeries(E start, E stop) {
return generateSeries( value( start ), value( stop ) );
}
} }

View File

@ -4,9 +4,17 @@
*/ */
package org.hibernate.query.sqm.produce.function; package org.hibernate.query.sqm.produce.function;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.hibernate.metamodel.mapping.MappingModelExpressible; import org.hibernate.metamodel.mapping.MappingModelExpressible;
import org.hibernate.query.sqm.function.NamedSqmFunctionDescriptor;
import org.hibernate.query.sqm.sql.SqmToSqlAstConverter; import org.hibernate.query.sqm.sql.SqmToSqlAstConverter;
import org.hibernate.query.sqm.tree.SqmCopyContext;
import org.hibernate.query.sqm.tree.SqmTypedNode;
import org.hibernate.query.sqm.tree.expression.SqmExpression;
import org.hibernate.query.sqm.tree.expression.SqmFunction; import org.hibernate.query.sqm.tree.expression.SqmFunction;
import org.hibernate.sql.ast.tree.expression.Expression;
import java.util.List;
/** /**
* Pluggable strategy for resolving a function argument type for a specific call. * Pluggable strategy for resolving a function argument type for a specific call.
@ -22,9 +30,48 @@ public interface FunctionArgumentTypeResolver {
* the implied type would be defined by the type of `something`. * the implied type would be defined by the type of `something`.
* *
* @return The resolved type. * @return The resolved type.
* @deprecated Use {@link #resolveFunctionArgumentType(List, int, SqmToSqlAstConverter)} instead
*/ */
MappingModelExpressible<?> resolveFunctionArgumentType( @Deprecated(forRemoval = true)
@Nullable MappingModelExpressible<?> resolveFunctionArgumentType(
SqmFunction<?> function, SqmFunction<?> function,
int argumentIndex, int argumentIndex,
SqmToSqlAstConverter converter); SqmToSqlAstConverter converter);
/**
* Resolve the argument type for a function given its context-implied return type.
* <p>
* The <em>context-implied</em> type is the type implied by where the function
* occurs in the query. E.g., for an equality predicate (`something = some_function`)
* the implied type would be defined by the type of `something`.
*
* @return The resolved type.
* @since 7.0
*/
default @Nullable MappingModelExpressible<?> resolveFunctionArgumentType(
List<? extends SqmTypedNode<?>> arguments,
int argumentIndex,
SqmToSqlAstConverter converter) {
return resolveFunctionArgumentType(
new SqmFunction<>(
"",
new NamedSqmFunctionDescriptor( "", false, null, null ),
null,
arguments,
converter.getCreationContext().getSessionFactory().getNodeBuilder()
) {
@Override
public Expression convertToSqlAst(SqmToSqlAstConverter walker) {
throw new UnsupportedOperationException();
}
@Override
public SqmExpression<Object> copy(SqmCopyContext context) {
throw new UnsupportedOperationException();
}
},
argumentIndex,
converter
);
}
} }

View File

@ -9,6 +9,7 @@ import org.hibernate.metamodel.mapping.SelectableMapping;
import org.hibernate.metamodel.mapping.SqlExpressible; import org.hibernate.metamodel.mapping.SqlExpressible;
import org.hibernate.query.derived.AnonymousTupleType; import org.hibernate.query.derived.AnonymousTupleType;
import org.hibernate.query.sqm.produce.function.internal.SetReturningFunctionTypeResolverBuilder; import org.hibernate.query.sqm.produce.function.internal.SetReturningFunctionTypeResolverBuilder;
import org.hibernate.query.sqm.sql.SqmToSqlAstConverter;
import org.hibernate.query.sqm.tree.SqmTypedNode; import org.hibernate.query.sqm.tree.SqmTypedNode;
import org.hibernate.sql.ast.tree.SqlAstNode; import org.hibernate.sql.ast.tree.SqlAstNode;
import org.hibernate.type.BasicType; import org.hibernate.type.BasicType;
@ -40,8 +41,9 @@ public interface SetReturningFunctionTypeResolver {
SelectableMapping[] resolveFunctionReturnType( SelectableMapping[] resolveFunctionReturnType(
List<? extends SqlAstNode> arguments, List<? extends SqlAstNode> arguments,
String tableIdentifierVariable, String tableIdentifierVariable,
boolean lateral,
boolean withOrdinality, boolean withOrdinality,
TypeConfiguration typeConfiguration); SqmToSqlAstConverter converter);
/** /**
* Creates a builder for a type resolver. * Creates a builder for a type resolver.

View File

@ -10,7 +10,10 @@ import java.sql.Time;
import java.sql.Timestamp; import java.sql.Timestamp;
import java.util.List; import java.util.List;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.hibernate.metamodel.mapping.MappingModelExpressible; import org.hibernate.metamodel.mapping.MappingModelExpressible;
import org.hibernate.query.sqm.produce.function.internal.AbstractFunctionArgumentTypeResolver;
import org.hibernate.query.sqm.sql.SqmToSqlAstConverter;
import org.hibernate.query.sqm.tree.SqmTypedNode; import org.hibernate.query.sqm.tree.SqmTypedNode;
import org.hibernate.query.sqm.tree.expression.SqmExpression; import org.hibernate.query.sqm.tree.expression.SqmExpression;
import org.hibernate.type.spi.TypeConfiguration; import org.hibernate.type.spi.TypeConfiguration;
@ -25,44 +28,59 @@ public final class StandardFunctionArgumentTypeResolvers {
private StandardFunctionArgumentTypeResolvers() { private StandardFunctionArgumentTypeResolvers() {
} }
public static final FunctionArgumentTypeResolver NULL = (function, argumentIndex, converter) -> { public static final FunctionArgumentTypeResolver NULL = new AbstractFunctionArgumentTypeResolver() {
return null; @Override
public @Nullable MappingModelExpressible<?> resolveFunctionArgumentType(List<? extends SqmTypedNode<?>> arguments, int argumentIndex, SqmToSqlAstConverter converter) {
return null;
}
}; };
public static final FunctionArgumentTypeResolver IMPLIED_RESULT_TYPE = (function, argumentIndex, converter) -> { public static final FunctionArgumentTypeResolver IMPLIED_RESULT_TYPE = new AbstractFunctionArgumentTypeResolver() {
return converter.resolveFunctionImpliedReturnType(); @Override
public @Nullable MappingModelExpressible<?> resolveFunctionArgumentType(List<? extends SqmTypedNode<?>> arguments, int argumentIndex, SqmToSqlAstConverter converter) {
return converter.resolveFunctionImpliedReturnType();
}
}; };
public static final FunctionArgumentTypeResolver ARGUMENT_OR_IMPLIED_RESULT_TYPE = (function, argumentIndex, converter) -> { public static final FunctionArgumentTypeResolver ARGUMENT_OR_IMPLIED_RESULT_TYPE = new AbstractFunctionArgumentTypeResolver() {
final List<? extends SqmTypedNode<?>> arguments = function.getArguments(); @Override
final int argumentsSize = arguments.size(); public @Nullable MappingModelExpressible<?> resolveFunctionArgumentType(List<? extends SqmTypedNode<?>> arguments, int argumentIndex, SqmToSqlAstConverter converter) {
for ( int i = 0 ; i < argumentIndex; i++ ) { final int argumentsSize = arguments.size();
final SqmTypedNode<?> node = arguments.get( i ); for ( int i = 0; i < argumentIndex; i++ ) {
if ( node instanceof SqmExpression<?> ) { final SqmTypedNode<?> node = arguments.get( i );
final MappingModelExpressible<?> expressible = converter.determineValueMapping( (SqmExpression<?>) node ); if ( node instanceof SqmExpression<?> ) {
if ( expressible != null ) { final MappingModelExpressible<?> expressible = converter.determineValueMapping(
return expressible; (SqmExpression<?>) node );
if ( expressible != null ) {
return expressible;
}
} }
} }
} for ( int i = argumentIndex + 1; i < argumentsSize; i++ ) {
for ( int i = argumentIndex + 1 ; i < argumentsSize; i++ ) { final SqmTypedNode<?> node = arguments.get( i );
final SqmTypedNode<?> node = arguments.get( i ); if ( node instanceof SqmExpression<?> ) {
if ( node instanceof SqmExpression<?> ) { final MappingModelExpressible<?> expressible = converter.determineValueMapping(
final MappingModelExpressible<?> expressible = converter.determineValueMapping( (SqmExpression<?>) node ); (SqmExpression<?>) node );
if ( expressible != null ) { if ( expressible != null ) {
return expressible; return expressible;
}
} }
} }
}
return converter.resolveFunctionImpliedReturnType(); return converter.resolveFunctionImpliedReturnType();
}
}; };
public static FunctionArgumentTypeResolver invariant( public static FunctionArgumentTypeResolver invariant(
TypeConfiguration typeConfiguration, TypeConfiguration typeConfiguration,
FunctionParameterType type) { FunctionParameterType type) {
final MappingModelExpressible<?> expressible = getMappingModelExpressible( typeConfiguration, type ); final MappingModelExpressible<?> expressible = getMappingModelExpressible( typeConfiguration, type );
return (function, argumentIndex, converter) -> expressible; return new AbstractFunctionArgumentTypeResolver() {
@Override
public @Nullable MappingModelExpressible<?> resolveFunctionArgumentType(List<? extends SqmTypedNode<?>> arguments, int argumentIndex, SqmToSqlAstConverter converter) {
return expressible;
}
};
} }
public static FunctionArgumentTypeResolver invariant( public static FunctionArgumentTypeResolver invariant(
@ -73,82 +91,105 @@ public final class StandardFunctionArgumentTypeResolvers {
expressibles[i] = getMappingModelExpressible( typeConfiguration, types[i] ); expressibles[i] = getMappingModelExpressible( typeConfiguration, types[i] );
} }
return (function, argumentIndex, converter) -> expressibles[argumentIndex]; return new AbstractFunctionArgumentTypeResolver() {
@Override
public @Nullable MappingModelExpressible<?> resolveFunctionArgumentType(List<? extends SqmTypedNode<?>> arguments, int argumentIndex, SqmToSqlAstConverter converter) {
return expressibles[argumentIndex];
}
};
} }
public static FunctionArgumentTypeResolver invariant(FunctionParameterType... types) { public static FunctionArgumentTypeResolver invariant(FunctionParameterType... types) {
return (function, argumentIndex, converter) -> getMappingModelExpressible( return new AbstractFunctionArgumentTypeResolver() {
function.nodeBuilder().getTypeConfiguration(), @Override
types[argumentIndex] public @Nullable MappingModelExpressible<?> resolveFunctionArgumentType(List<? extends SqmTypedNode<?>> arguments, int argumentIndex, SqmToSqlAstConverter converter) {
); return getMappingModelExpressible(
converter.getCreationContext().getTypeConfiguration(),
types[argumentIndex]
);
}
};
} }
public static FunctionArgumentTypeResolver impliedOrInvariant( public static FunctionArgumentTypeResolver impliedOrInvariant(
TypeConfiguration typeConfiguration, TypeConfiguration typeConfiguration,
FunctionParameterType type) { FunctionParameterType type) {
final MappingModelExpressible<?> expressible = getMappingModelExpressible( typeConfiguration, type ); final MappingModelExpressible<?> expressible = getMappingModelExpressible( typeConfiguration, type );
return (function, argumentIndex, converter) -> { return new AbstractFunctionArgumentTypeResolver() {
final MappingModelExpressible<?> mappingModelExpressible = converter.resolveFunctionImpliedReturnType(); @Override
if ( mappingModelExpressible != null ) { public @Nullable MappingModelExpressible<?> resolveFunctionArgumentType(List<? extends SqmTypedNode<?>> arguments, int argumentIndex, SqmToSqlAstConverter converter) {
return mappingModelExpressible; final MappingModelExpressible<?> mappingModelExpressible = converter.resolveFunctionImpliedReturnType();
if ( mappingModelExpressible != null ) {
return mappingModelExpressible;
}
return expressible;
} }
return expressible;
}; };
} }
public static FunctionArgumentTypeResolver argumentsOrImplied(int... indices) { public static FunctionArgumentTypeResolver argumentsOrImplied(int... indices) {
return (function, argumentIndex, converter) -> { return new AbstractFunctionArgumentTypeResolver() {
final List<? extends SqmTypedNode<?>> arguments = function.getArguments(); @Override
final int argumentsSize = arguments.size(); public @Nullable MappingModelExpressible<?> resolveFunctionArgumentType(List<? extends SqmTypedNode<?>> arguments, int argumentIndex, SqmToSqlAstConverter converter) {
for ( int index : indices ) { final int argumentsSize = arguments.size();
if ( index >= argumentIndex || index >= argumentsSize ) { for ( int index : indices ) {
break; if ( index >= argumentIndex || index >= argumentsSize ) {
} break;
final SqmTypedNode<?> node = arguments.get( index ); }
if ( node instanceof SqmExpression<?> ) { final SqmTypedNode<?> node = arguments.get( index );
final MappingModelExpressible<?> expressible = converter.determineValueMapping( (SqmExpression<?>) node ); if ( node instanceof SqmExpression<?> ) {
if ( expressible != null ) { final MappingModelExpressible<?> expressible = converter.determineValueMapping(
return expressible; (SqmExpression<?>) node );
if ( expressible != null ) {
return expressible;
}
} }
} }
} for ( int index : indices ) {
for ( int index : indices ) { if ( index <= argumentIndex || index >= argumentsSize ) {
if ( index <= argumentIndex || index >= argumentsSize ) { break;
break; }
} final SqmTypedNode<?> node = arguments.get( index );
final SqmTypedNode<?> node = arguments.get( index ); if ( node instanceof SqmExpression<?> ) {
if ( node instanceof SqmExpression<?> ) { final MappingModelExpressible<?> expressible = converter.determineValueMapping(
final MappingModelExpressible<?> expressible = converter.determineValueMapping( (SqmExpression<?>) node ); (SqmExpression<?>) node );
if ( expressible != null ) { if ( expressible != null ) {
return expressible; return expressible;
}
} }
} }
}
return converter.resolveFunctionImpliedReturnType(); return converter.resolveFunctionImpliedReturnType();
}
}; };
} }
public static FunctionArgumentTypeResolver composite(FunctionArgumentTypeResolver... resolvers) { public static FunctionArgumentTypeResolver composite(FunctionArgumentTypeResolver... resolvers) {
return (function, argumentIndex, converter) -> { return new AbstractFunctionArgumentTypeResolver() {
for ( FunctionArgumentTypeResolver resolver : resolvers ) { @Override
final MappingModelExpressible<?> result = resolver.resolveFunctionArgumentType( public @Nullable MappingModelExpressible<?> resolveFunctionArgumentType(List<? extends SqmTypedNode<?>> arguments, int argumentIndex, SqmToSqlAstConverter converter) {
function, for ( FunctionArgumentTypeResolver resolver : resolvers ) {
argumentIndex, final MappingModelExpressible<?> result = resolver.resolveFunctionArgumentType(
converter arguments,
); argumentIndex,
if ( result != null ) { converter
return result; );
if ( result != null ) {
return result;
}
} }
}
return null; return null;
}
}; };
} }
public static FunctionArgumentTypeResolver byArgument(FunctionArgumentTypeResolver... resolvers) { public static FunctionArgumentTypeResolver byArgument(FunctionArgumentTypeResolver... resolvers) {
return (function, argumentIndex, converter) -> { return new AbstractFunctionArgumentTypeResolver() {
return resolvers[argumentIndex].resolveFunctionArgumentType( function, argumentIndex, converter ); @Override
public @Nullable MappingModelExpressible<?> resolveFunctionArgumentType(List<? extends SqmTypedNode<?>> arguments, int argumentIndex, SqmToSqlAstConverter converter) {
return resolvers[argumentIndex].resolveFunctionArgumentType( arguments, argumentIndex, converter );
}
}; };
} }

View File

@ -0,0 +1,25 @@
/*
* SPDX-License-Identifier: LGPL-2.1-or-later
* Copyright Red Hat Inc. and Hibernate Authors
*/
package org.hibernate.query.sqm.produce.function.internal;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.hibernate.metamodel.mapping.MappingModelExpressible;
import org.hibernate.query.sqm.produce.function.FunctionArgumentTypeResolver;
import org.hibernate.query.sqm.sql.SqmToSqlAstConverter;
import org.hibernate.query.sqm.tree.SqmTypedNode;
import org.hibernate.query.sqm.tree.expression.SqmFunction;
import java.util.List;
public abstract class AbstractFunctionArgumentTypeResolver implements FunctionArgumentTypeResolver {
@Override
@SuppressWarnings("removal")
public @Nullable MappingModelExpressible<?> resolveFunctionArgumentType(SqmFunction<?> function, int argumentIndex, SqmToSqlAstConverter converter) {
return resolveFunctionArgumentType( function.getArguments(), argumentIndex, converter );
}
@Override
public abstract @Nullable MappingModelExpressible<?> resolveFunctionArgumentType(List<? extends SqmTypedNode<?>> arguments, int argumentIndex, SqmToSqlAstConverter converter);
}

View File

@ -13,6 +13,7 @@ import org.hibernate.metamodel.mapping.internal.SelectableMappingImpl;
import org.hibernate.query.derived.AnonymousTupleType; import org.hibernate.query.derived.AnonymousTupleType;
import org.hibernate.query.sqm.SqmExpressible; import org.hibernate.query.sqm.SqmExpressible;
import org.hibernate.query.sqm.produce.function.SetReturningFunctionTypeResolver; import org.hibernate.query.sqm.produce.function.SetReturningFunctionTypeResolver;
import org.hibernate.query.sqm.sql.SqmToSqlAstConverter;
import org.hibernate.query.sqm.tree.SqmTypedNode; import org.hibernate.query.sqm.tree.SqmTypedNode;
import org.hibernate.sql.ast.tree.SqlAstNode; import org.hibernate.sql.ast.tree.SqlAstNode;
import org.hibernate.sql.ast.tree.expression.Expression; import org.hibernate.sql.ast.tree.expression.Expression;
@ -116,12 +117,13 @@ public class SetReturningFunctionTypeResolverBuilder implements SetReturningFunc
public SelectableMapping[] resolveFunctionReturnType( public SelectableMapping[] resolveFunctionReturnType(
List<? extends SqlAstNode> arguments, List<? extends SqlAstNode> arguments,
String tableIdentifierVariable, String tableIdentifierVariable,
boolean lateral,
boolean withOrdinality, boolean withOrdinality,
TypeConfiguration typeConfiguration) { SqmToSqlAstConverter converter) {
final SelectableMapping[] selectableMappings = new SelectableMapping[typeResolvers.length + (withOrdinality ? 1 : 0)]; final SelectableMapping[] selectableMappings = new SelectableMapping[typeResolvers.length + (withOrdinality ? 1 : 0)];
int i = 0; int i = 0;
for ( TypeResolver typeResolver : typeResolvers ) { for ( TypeResolver typeResolver : typeResolvers ) {
final JdbcMapping jdbcMapping = typeResolver.resolveFunctionReturnType( arguments, typeConfiguration ); final JdbcMapping jdbcMapping = typeResolver.resolveFunctionReturnType( arguments, converter );
selectableMappings[i] = new SelectableMappingImpl( selectableMappings[i] = new SelectableMappingImpl(
"", "",
typeResolver.selectionExpression(), typeResolver.selectionExpression(),
@ -146,7 +148,7 @@ public class SetReturningFunctionTypeResolverBuilder implements SetReturningFunc
if ( withOrdinality ) { if ( withOrdinality ) {
selectableMappings[i] = new SelectableMappingImpl( selectableMappings[i] = new SelectableMappingImpl(
"", "",
determineIndexSelectionExpression( selectableMappings, tableIdentifierVariable, typeConfiguration ), determineIndexSelectionExpression( selectableMappings, tableIdentifierVariable, converter ),
new SelectablePath( CollectionPart.Nature.INDEX.getName() ), new SelectablePath( CollectionPart.Nature.INDEX.getName() ),
null, null,
null, null,
@ -161,14 +163,15 @@ public class SetReturningFunctionTypeResolverBuilder implements SetReturningFunc
false, false,
false, false,
false, false,
typeConfiguration.getBasicTypeForJavaType( Long.class ) converter.getCreationContext().getTypeConfiguration().getBasicTypeForJavaType( Long.class )
); );
} }
return selectableMappings; return selectableMappings;
} }
private String determineIndexSelectionExpression(SelectableMapping[] selectableMappings, String tableIdentifierVariable, TypeConfiguration typeConfiguration) { private String determineIndexSelectionExpression(SelectableMapping[] selectableMappings, String tableIdentifierVariable, SqmToSqlAstConverter walker) {
final String defaultOrdinalityColumnName = typeConfiguration.getSessionFactory().getJdbcServices() final String defaultOrdinalityColumnName = walker.getCreationContext().getSessionFactory()
.getJdbcServices()
.getDialect() .getDialect()
.getDefaultOrdinalityColumnName(); .getDefaultOrdinalityColumnName();
String name = defaultOrdinalityColumnName == null ? "i" : defaultOrdinalityColumnName; String name = defaultOrdinalityColumnName == null ? "i" : defaultOrdinalityColumnName;
@ -195,7 +198,7 @@ public class SetReturningFunctionTypeResolverBuilder implements SetReturningFunc
SqmExpressible<?> resolveTupleType(List<? extends SqmTypedNode<?>> arguments, TypeConfiguration typeConfiguration); SqmExpressible<?> resolveTupleType(List<? extends SqmTypedNode<?>> arguments, TypeConfiguration typeConfiguration);
JdbcMapping resolveFunctionReturnType(List<? extends SqlAstNode> arguments, TypeConfiguration typeConfiguration); JdbcMapping resolveFunctionReturnType(List<? extends SqlAstNode> arguments, SqmToSqlAstConverter walker);
} }
private record BasicTypeReferenceTypeResolver( private record BasicTypeReferenceTypeResolver(
@ -210,8 +213,8 @@ public class SetReturningFunctionTypeResolverBuilder implements SetReturningFunc
} }
@Override @Override
public JdbcMapping resolveFunctionReturnType(List<? extends SqlAstNode> arguments, TypeConfiguration typeConfiguration) { public JdbcMapping resolveFunctionReturnType(List<? extends SqlAstNode> arguments, SqmToSqlAstConverter walker) {
return typeConfiguration.getBasicTypeRegistry().resolve( basicTypeReference ); return walker.getCreationContext().getTypeConfiguration().getBasicTypeRegistry().resolve( basicTypeReference );
} }
} }
@ -227,7 +230,7 @@ public class SetReturningFunctionTypeResolverBuilder implements SetReturningFunc
} }
@Override @Override
public JdbcMapping resolveFunctionReturnType(List<? extends SqlAstNode> arguments, TypeConfiguration typeConfiguration) { public JdbcMapping resolveFunctionReturnType(List<? extends SqlAstNode> arguments, SqmToSqlAstConverter walker) {
return basicType; return basicType;
} }
} }
@ -244,7 +247,7 @@ public class SetReturningFunctionTypeResolverBuilder implements SetReturningFunc
} }
@Override @Override
public JdbcMapping resolveFunctionReturnType(List<? extends SqlAstNode> arguments, TypeConfiguration typeConfiguration) { public JdbcMapping resolveFunctionReturnType(List<? extends SqlAstNode> arguments, SqmToSqlAstConverter walker) {
return ((Expression) arguments.get( argPosition )).getExpressionType().getSingleJdbcMapping(); return ((Expression) arguments.get( argPosition )).getExpressionType().getSingleJdbcMapping();
} }
} }

View File

@ -283,8 +283,11 @@ public abstract class BaseSemanticQueryWalker implements SemanticQueryWalker<Obj
} }
protected void consumeFromClauseRoot(SqmRoot<?> sqmRoot) { protected void consumeFromClauseRoot(SqmRoot<?> sqmRoot) {
if ( sqmRoot instanceof SqmDerivedRoot<?> ) { if ( sqmRoot instanceof SqmDerivedRoot<?> derivedRoot ) {
( (SqmDerivedRoot<?>) sqmRoot ).getQueryPart().accept( this ); derivedRoot.getQueryPart().accept( this );
}
else if ( sqmRoot instanceof SqmFunctionRoot<?> functionRoot ) {
functionRoot.getFunction().accept( this );
} }
consumeJoins( sqmRoot ); consumeJoins( sqmRoot );
} }
@ -416,7 +419,7 @@ public abstract class BaseSemanticQueryWalker implements SemanticQueryWalker<Obj
} }
@Override @Override
public Object visitRootFunction(SqmFunctionRoot<?>sqmRoot) { public Object visitRootFunction(SqmFunctionRoot<?> sqmRoot) {
return sqmRoot; return sqmRoot;
} }

View File

@ -7085,7 +7085,10 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
); );
} }
else { else {
BasicValuedMapping durationType = (BasicValuedMapping) toDuration.getNodeType(); final MappingModelExpressible<?> inferredValueMapping = getInferredValueMapping();
final BasicValuedMapping durationType = inferredValueMapping != null
? (BasicValuedMapping) inferredValueMapping
: (BasicValuedMapping) toDuration.getNodeType();
Duration duration; Duration duration;
if ( scaledMagnitude.getExpressionType().getSingleJdbcMapping().getJdbcType().isInterval() ) { if ( scaledMagnitude.getExpressionType().getSingleJdbcMapping().getJdbcType().isInterval() ) {
duration = new Duration( extractEpoch( scaledMagnitude ), SECOND, durationType ); duration = new Duration( extractEpoch( scaledMagnitude ), SECOND, durationType );

View File

@ -53,7 +53,7 @@ public interface SqmToSqlAstConverter extends SemanticQueryWalker<Object>, SqlAs
* Returns the function return type implied from the context within which it is used. * Returns the function return type implied from the context within which it is used.
* If there is no current function being processed or no context implied type, the return is <code>null</code>. * If there is no current function being processed or no context implied type, the return is <code>null</code>.
*/ */
MappingModelExpressible<?> resolveFunctionImpliedReturnType(); @Nullable MappingModelExpressible<?> resolveFunctionImpliedReturnType();
MappingModelExpressible<?> determineValueMapping(SqmExpression<?> sqmExpression); MappingModelExpressible<?> determineValueMapping(SqmExpression<?> sqmExpression);

View File

@ -76,7 +76,9 @@ public class SqmFunctionRoot<E> extends SqmRoot<E> implements JpaFunctionRoot<E>
@Override @Override
public SqmPath<Long> index() { public SqmPath<Long> index() {
return get( CollectionPart.Nature.INDEX.getName() ); //noinspection unchecked
final SqmPathSource<Long> indexPathSource = (SqmPathSource<Long>) function.getType().getSubPathSource( CollectionPart.Nature.INDEX.getName() );
return resolvePath( indexPathSource.getPathName(), indexPathSource );
} }
@Override @Override

View File

@ -129,7 +129,9 @@ public class SqmFunctionJoin<E> extends AbstractSqmJoin<Object, E> implements Jp
@Override @Override
public SqmPath<Long> index() { public SqmPath<Long> index() {
return get( CollectionPart.Nature.INDEX.getName() ); //noinspection unchecked
final SqmPathSource<Long> indexPathSource = (SqmPathSource<Long>) function.getType().getSubPathSource( CollectionPart.Nature.INDEX.getName() );
return resolvePath( indexPathSource.getPathName(), indexPathSource );
} }
@Override @Override

View File

@ -6,6 +6,7 @@ package org.hibernate.sql.ast.spi;
import java.sql.PreparedStatement; import java.sql.PreparedStatement;
import java.sql.SQLException; import java.sql.SQLException;
import java.time.Period;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.BitSet; import java.util.BitSet;
import java.util.Collection; import java.util.Collection;
@ -59,6 +60,7 @@ import org.hibernate.persister.internal.SqlFragmentPredicate;
import org.hibernate.query.IllegalQueryOperationException; import org.hibernate.query.IllegalQueryOperationException;
import org.hibernate.query.ReturnableType; import org.hibernate.query.ReturnableType;
import org.hibernate.query.SortDirection; import org.hibernate.query.SortDirection;
import org.hibernate.query.common.TemporalUnit;
import org.hibernate.query.derived.AnonymousTupleTableGroupProducer; import org.hibernate.query.derived.AnonymousTupleTableGroupProducer;
import org.hibernate.query.internal.NullPrecedenceHelper; import org.hibernate.query.internal.NullPrecedenceHelper;
import org.hibernate.query.spi.Limit; import org.hibernate.query.spi.Limit;
@ -230,7 +232,10 @@ import jakarta.persistence.criteria.Nulls;
import static org.hibernate.persister.entity.DiscriminatorHelper.jdbcLiteral; import static org.hibernate.persister.entity.DiscriminatorHelper.jdbcLiteral;
import static org.hibernate.query.sqm.BinaryArithmeticOperator.DIVIDE_PORTABLE; import static org.hibernate.query.sqm.BinaryArithmeticOperator.DIVIDE_PORTABLE;
import static org.hibernate.query.common.TemporalUnit.DAY;
import static org.hibernate.query.common.TemporalUnit.MONTH;
import static org.hibernate.query.common.TemporalUnit.NANOSECOND; import static org.hibernate.query.common.TemporalUnit.NANOSECOND;
import static org.hibernate.query.common.TemporalUnit.SECOND;
import static org.hibernate.sql.ast.SqlTreePrinter.logSqlAst; import static org.hibernate.sql.ast.SqlTreePrinter.logSqlAst;
import static org.hibernate.sql.results.graph.DomainResultGraphPrinter.logDomainResultGraph; import static org.hibernate.sql.results.graph.DomainResultGraphPrinter.logDomainResultGraph;
@ -7299,8 +7304,16 @@ public abstract class AbstractSqlAstTranslator<T extends JdbcOperation> implemen
@Override @Override
public void visitDuration(Duration duration) { public void visitDuration(Duration duration) {
duration.getMagnitude().accept( this ); if ( duration.getExpressionType().getJdbcMapping().getJdbcType().isInterval() ) {
if ( !duration.getExpressionType().getJdbcMapping().getJdbcType().isInterval() ) { if ( duration.getMagnitude() instanceof Literal literal ) {
renderIntervalLiteral( literal, duration.getUnit() );
}
else {
renderInterval( duration );
}
}
else {
duration.getMagnitude().accept( this );
// Convert to NANOSECOND because DurationJavaType requires values in that unit // Convert to NANOSECOND because DurationJavaType requires values in that unit
appendSql( appendSql(
duration.getUnit().conversionFactor( NANOSECOND, dialect ) duration.getUnit().conversionFactor( NANOSECOND, dialect )
@ -7308,6 +7321,43 @@ public abstract class AbstractSqlAstTranslator<T extends JdbcOperation> implemen
} }
} }
protected void renderInterval(Duration duration) {
final TemporalUnit unit = duration.getUnit();
appendSql( "(interval '1' " );
final TemporalUnit targetResolution = switch ( unit ) {
case NANOSECOND -> SECOND;
case SECOND, MINUTE, HOUR, DAY, MONTH, YEAR -> unit;
case WEEK -> DAY;
case QUARTER -> MONTH;
case DATE, TIME, EPOCH, DAY_OF_MONTH, DAY_OF_WEEK, DAY_OF_YEAR, WEEK_OF_MONTH, WEEK_OF_YEAR, OFFSET,
TIMEZONE_HOUR, TIMEZONE_MINUTE, NATIVE ->
throw new IllegalArgumentException( "Invalid duration unit: " + unit );
};
appendSql( targetResolution.toString() );
appendSql( '*' );
duration.getMagnitude().accept( this );
appendSql( duration.getUnit().conversionFactor( targetResolution, dialect ) );
appendSql( ')' );
}
protected void renderIntervalLiteral(Literal literal, TemporalUnit unit) {
final Number value = (Number) literal.getLiteralValue();
dialect.appendIntervalLiteral( this, switch ( unit ) {
case NANOSECOND -> java.time.Duration.ofNanos( value.longValue() );
case SECOND -> java.time.Duration.ofSeconds( value.longValue() );
case MINUTE -> java.time.Duration.ofMinutes( value.longValue() );
case HOUR -> java.time.Duration.ofHours( value.longValue() );
case DAY -> Period.ofDays( value.intValue() );
case WEEK -> Period.ofWeeks( value.intValue() );
case MONTH -> Period.ofMonths( value.intValue() );
case YEAR -> Period.ofYears( value.intValue() );
case QUARTER -> Period.ofMonths( value.intValue() * 3 );
case DATE, TIME, EPOCH, DAY_OF_MONTH, DAY_OF_WEEK, DAY_OF_YEAR, WEEK_OF_MONTH, WEEK_OF_YEAR, OFFSET,
TIMEZONE_HOUR, TIMEZONE_MINUTE, NATIVE ->
throw new IllegalArgumentException( "Invalid duration unit: " + unit );
} );
}
@Override @Override
public void visitConversion(Conversion conversion) { public void visitConversion(Conversion conversion) {
final Duration duration = conversion.getDuration(); final Duration duration = conversion.getDuration();

View File

@ -4,6 +4,7 @@
*/ */
package org.hibernate.sql.ast.tree.expression; package org.hibernate.sql.ast.tree.expression;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.metamodel.mapping.JdbcMappingContainer; import org.hibernate.metamodel.mapping.JdbcMappingContainer;
import org.hibernate.sql.ast.SqlAstTranslator; import org.hibernate.sql.ast.SqlAstTranslator;
@ -16,9 +17,15 @@ import org.hibernate.sql.ast.spi.SqlAppender;
*/ */
public class SelfRenderingSqlFragmentExpression implements SelfRenderingExpression { public class SelfRenderingSqlFragmentExpression implements SelfRenderingExpression {
private final String expression; private final String expression;
private final @Nullable JdbcMappingContainer expressionType;
public SelfRenderingSqlFragmentExpression(String expression) { public SelfRenderingSqlFragmentExpression(String expression) {
this( expression, null );
}
public SelfRenderingSqlFragmentExpression(String expression, @Nullable JdbcMappingContainer expressionType) {
this.expression = expression; this.expression = expression;
this.expressionType = expressionType;
} }
public String getExpression() { public String getExpression() {
@ -27,7 +34,7 @@ public class SelfRenderingSqlFragmentExpression implements SelfRenderingExpressi
@Override @Override
public JdbcMappingContainer getExpressionType() { public JdbcMappingContainer getExpressionType() {
return null; return expressionType;
} }
@Override @Override

View File

@ -0,0 +1,189 @@
/*
* SPDX-License-Identifier: LGPL-2.1-or-later
* Copyright Red Hat Inc. and Hibernate Authors
*/
package org.hibernate.orm.test.function.srf;
import jakarta.persistence.Tuple;
import org.hibernate.dialect.SybaseASEDialect;
import org.hibernate.query.criteria.JpaCriteriaQuery;
import org.hibernate.query.criteria.JpaFunctionRoot;
import org.hibernate.query.sqm.NodeBuilder;
import org.hibernate.testing.orm.domain.StandardDomainModel;
import org.hibernate.testing.orm.domain.library.Book;
import org.hibernate.testing.orm.junit.DialectFeatureChecks;
import org.hibernate.testing.orm.junit.DomainModel;
import org.hibernate.testing.orm.junit.RequiresDialectFeature;
import org.hibernate.testing.orm.junit.SessionFactory;
import org.hibernate.testing.orm.junit.SessionFactoryScope;
import org.hibernate.testing.orm.junit.SkipForDialect;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import java.time.LocalDate;
import java.time.Month;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertEquals;
/**
* @author Christian Beikov
*/
@DomainModel(standardModels = StandardDomainModel.LIBRARY)
@SessionFactory
@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsGenerateSeries.class)
public class GenerateSeriesTest {
@BeforeAll
public void setup(SessionFactoryScope scope) {
scope.inTransaction( session -> {
session.persist( new Book(2, "Test") );
} );
}
@AfterAll
public void cleanup(SessionFactoryScope scope) {
scope.inTransaction( session -> {
session.createMutationQuery( "delete Book" ).executeUpdate();
} );
}
@Test
public void testGenerateSeries(SessionFactoryScope scope) {
scope.inSession( em -> {
//tag::hql-set-returning-function-generate-series-example[]
List<Integer> resultList = em.createQuery( "select e from generate_series(1, 2) e order by e", Integer.class )
.getResultList();
//end::hql-set-returning-function-generate-series-example[]
assertEquals( 2, resultList.size() );
assertEquals( 1, resultList.get( 0 ) );
assertEquals( 2, resultList.get( 1 ) );
} );
}
@Test
public void testNodeBuilderGenerateSeries(SessionFactoryScope scope) {
scope.inSession( em -> {
final NodeBuilder cb = (NodeBuilder) em.getCriteriaBuilder();
final JpaCriteriaQuery<Integer> cq = cb.createQuery(Integer.class);
final JpaFunctionRoot<Integer> root = cq.from( cb.generateSeries( 1, 2 ) );
cq.select( root );
cq.orderBy( cb.asc( root ) );
List<Integer> resultList = em.createQuery( cq ).getResultList();
assertEquals( 2, resultList.size() );
assertEquals( 1, resultList.get( 0 ) );
assertEquals( 2, resultList.get( 1 ) );
} );
}
@Test
public void testGenerateSeriesOrdinality(SessionFactoryScope scope) {
scope.inSession( em -> {
//tag::hql-set-returning-function-generate-series-ordinality-example[]
List<Tuple> resultList = em.createQuery(
"select index(e), e from generate_series(2, 3, 1) e order by index(e)",
Tuple.class
)
.getResultList();
//end::hql-set-returning-function-generate-series-ordinality-example[]
assertEquals( 2, resultList.size() );
assertEquals( 1L, resultList.get( 0 ).get( 0 ) );
assertEquals( 2, resultList.get( 0 ).get( 1 ) );
assertEquals( 2L, resultList.get( 1 ).get( 0 ) );
assertEquals( 3, resultList.get( 1 ).get( 1 ) );
} );
}
@Test
public void testNodeBuilderGenerateSeriesOrdinality(SessionFactoryScope scope) {
scope.inSession( em -> {
final NodeBuilder cb = (NodeBuilder) em.getCriteriaBuilder();
final JpaCriteriaQuery<Tuple> cq = cb.createTupleQuery();
final JpaFunctionRoot<Integer> root = cq.from( cb.generateSeries( 2, 3, 1 ) );
cq.multiselect( root.index(), root );
cq.orderBy( cb.asc( root.index() ) );
List<Tuple> resultList = em.createQuery( cq ).getResultList();
assertEquals( 2, resultList.size() );
assertEquals( 1L, resultList.get( 0 ).get( 0 ) );
assertEquals( 2, resultList.get( 0 ).get( 1 ) );
assertEquals( 2L, resultList.get( 1 ).get( 0 ) );
assertEquals( 3, resultList.get( 1 ).get( 1 ) );
} );
}
@Test
public void testGenerateTimeSeries(SessionFactoryScope scope) {
scope.inSession( em -> {
//tag::hql-set-returning-function-generate-series-temporal-example[]
List<LocalDate> resultList = em.createQuery( "select e from generate_series(local date 2020-01-31, local date 2020-01-01, -1 day) e order by e", LocalDate.class )
.getResultList();
//end::hql-set-returning-function-generate-series-temporal-example[]
assertEquals( 31, resultList.size() );
for ( int i = 0; i < resultList.size(); i++ ) {
assertEquals( LocalDate.of( 2020, Month.JANUARY, i + 1 ), resultList.get( i ) );
}
} );
}
@Test
@SkipForDialect(dialectClass = SybaseASEDialect.class, reason = "Sybase bug?")
public void testGenerateSeriesCorrelation(SessionFactoryScope scope) {
scope.inSession( em -> {
List<Integer> resultList = em.createQuery(
"select e from Book b join lateral generate_series(1,b.id) e order by e", Integer.class )
.getResultList();
assertEquals( 2, resultList.size() );
} );
}
@Test
public void testGenerateSeriesNegative(SessionFactoryScope scope) {
scope.inSession( em -> {
List<Integer> resultList = em.createQuery( "select e from generate_series(2, 1, -1) e order by e", Integer.class )
.getResultList();
assertEquals( 2, resultList.size() );
assertEquals( 1, resultList.get( 0 ) );
assertEquals( 2, resultList.get( 1 ) );
} );
}
@Test
public void testGenerateSeriesNoProgression(SessionFactoryScope scope) {
scope.inSession( em -> {
List<Integer> resultList = em.createQuery( "select e from generate_series(2, 1, 1) e", Integer.class )
.getResultList();
assertEquals( 0, resultList.size() );
} );
}
@Test
public void testGenerateSeriesNoProgressionOrdinality(SessionFactoryScope scope) {
scope.inSession( em -> {
List<Tuple> resultList = em.createQuery( "select index(e), e from generate_series(2, 1, 1) e", Tuple.class )
.getResultList();
assertEquals( 0, resultList.size() );
} );
}
@Test
public void testGenerateSeriesSameBounds(SessionFactoryScope scope) {
scope.inSession( em -> {
List<Integer> resultList = em.createQuery( "select e from generate_series(2, 2, 1) e", Integer.class )
.getResultList();
assertEquals( 1, resultList.size() );
assertEquals( 2, resultList.get( 0 ) );
} );
}
}

View File

@ -26,7 +26,6 @@ import org.hibernate.tool.schema.internal.ExceptionHandlerLoggedImpl;
import org.hibernate.tool.schema.spi.ContributableMatcher; import org.hibernate.tool.schema.spi.ContributableMatcher;
import org.hibernate.tool.schema.spi.ExceptionHandler; import org.hibernate.tool.schema.spi.ExceptionHandler;
import org.hibernate.tool.schema.spi.ExecutionOptions; import org.hibernate.tool.schema.spi.ExecutionOptions;
import org.hibernate.tool.schema.spi.SchemaFilter;
import org.hibernate.tool.schema.spi.SchemaManagementTool; import org.hibernate.tool.schema.spi.SchemaManagementTool;
import org.hibernate.tool.schema.spi.ScriptSourceInput; import org.hibernate.tool.schema.spi.ScriptSourceInput;
import org.hibernate.tool.schema.spi.ScriptTargetOutput; import org.hibernate.tool.schema.spi.ScriptTargetOutput;

View File

@ -25,7 +25,6 @@ import org.hibernate.tool.schema.internal.ExceptionHandlerLoggedImpl;
import org.hibernate.tool.schema.spi.ContributableMatcher; import org.hibernate.tool.schema.spi.ContributableMatcher;
import org.hibernate.tool.schema.spi.ExceptionHandler; import org.hibernate.tool.schema.spi.ExceptionHandler;
import org.hibernate.tool.schema.spi.ExecutionOptions; import org.hibernate.tool.schema.spi.ExecutionOptions;
import org.hibernate.tool.schema.spi.SchemaFilter;
import org.hibernate.tool.schema.spi.SchemaManagementTool; import org.hibernate.tool.schema.spi.SchemaManagementTool;
import org.hibernate.tool.schema.spi.ScriptSourceInput; import org.hibernate.tool.schema.spi.ScriptSourceInput;
import org.hibernate.tool.schema.spi.ScriptTargetOutput; import org.hibernate.tool.schema.spi.ScriptTargetOutput;

View File

@ -24,7 +24,6 @@ import org.hibernate.tool.schema.internal.ExceptionHandlerLoggedImpl;
import org.hibernate.tool.schema.spi.ContributableMatcher; import org.hibernate.tool.schema.spi.ContributableMatcher;
import org.hibernate.tool.schema.spi.ExceptionHandler; import org.hibernate.tool.schema.spi.ExceptionHandler;
import org.hibernate.tool.schema.spi.ExecutionOptions; import org.hibernate.tool.schema.spi.ExecutionOptions;
import org.hibernate.tool.schema.spi.SchemaFilter;
import org.hibernate.tool.schema.spi.SchemaManagementTool; import org.hibernate.tool.schema.spi.SchemaManagementTool;
import org.hibernate.tool.schema.spi.ScriptSourceInput; import org.hibernate.tool.schema.spi.ScriptSourceInput;
import org.hibernate.tool.schema.spi.ScriptTargetOutput; import org.hibernate.tool.schema.spi.ScriptTargetOutput;

View File

@ -27,7 +27,6 @@ import org.hibernate.tool.schema.internal.ExceptionHandlerLoggedImpl;
import org.hibernate.tool.schema.spi.ContributableMatcher; import org.hibernate.tool.schema.spi.ContributableMatcher;
import org.hibernate.tool.schema.spi.ExceptionHandler; import org.hibernate.tool.schema.spi.ExceptionHandler;
import org.hibernate.tool.schema.spi.ExecutionOptions; import org.hibernate.tool.schema.spi.ExecutionOptions;
import org.hibernate.tool.schema.spi.SchemaFilter;
import org.hibernate.tool.schema.spi.SchemaManagementTool; import org.hibernate.tool.schema.spi.SchemaManagementTool;
import org.hibernate.tool.schema.spi.ScriptSourceInput; import org.hibernate.tool.schema.spi.ScriptSourceInput;
import org.hibernate.tool.schema.spi.ScriptTargetOutput; import org.hibernate.tool.schema.spi.ScriptTargetOutput;

View File

@ -835,6 +835,12 @@ abstract public class DialectFeatureChecks {
} }
} }
public static class SupportsGenerateSeries implements DialectFeatureCheck {
public boolean apply(Dialect dialect) {
return definesSetReturningFunction( dialect, "generate_series" );
}
}
public static class SupportsArrayAgg implements DialectFeatureCheck { public static class SupportsArrayAgg implements DialectFeatureCheck {
public boolean apply(Dialect dialect) { public boolean apply(Dialect dialect) {
return definesFunction( dialect, "array_agg" ); return definesFunction( dialect, "array_agg" );

View File

@ -6,28 +6,25 @@ package org.hibernate.vector;
import java.util.List; import java.util.List;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.hibernate.metamodel.mapping.MappingModelExpressible; import org.hibernate.metamodel.mapping.MappingModelExpressible;
import org.hibernate.query.sqm.produce.function.FunctionArgumentTypeResolver; import org.hibernate.query.sqm.produce.function.FunctionArgumentTypeResolver;
import org.hibernate.query.sqm.produce.function.internal.AbstractFunctionArgumentTypeResolver;
import org.hibernate.query.sqm.sql.SqmToSqlAstConverter; import org.hibernate.query.sqm.sql.SqmToSqlAstConverter;
import org.hibernate.query.sqm.tree.SqmTypedNode; import org.hibernate.query.sqm.tree.SqmTypedNode;
import org.hibernate.query.sqm.tree.expression.SqmExpression; import org.hibernate.query.sqm.tree.expression.SqmExpression;
import org.hibernate.query.sqm.tree.expression.SqmFunction;
import org.hibernate.type.SqlTypes; import org.hibernate.type.SqlTypes;
import org.hibernate.type.StandardBasicTypes; import org.hibernate.type.StandardBasicTypes;
/** /**
* A {@link FunctionArgumentTypeResolver} for {@link SqlTypes#VECTOR} functions. * A {@link FunctionArgumentTypeResolver} for {@link SqlTypes#VECTOR} functions.
*/ */
public class VectorArgumentTypeResolver implements FunctionArgumentTypeResolver { public class VectorArgumentTypeResolver extends AbstractFunctionArgumentTypeResolver {
public static final FunctionArgumentTypeResolver INSTANCE = new VectorArgumentTypeResolver(); public static final FunctionArgumentTypeResolver INSTANCE = new VectorArgumentTypeResolver();
@Override @Override
public MappingModelExpressible<?> resolveFunctionArgumentType( public @Nullable MappingModelExpressible<?> resolveFunctionArgumentType(List<? extends SqmTypedNode<?>> arguments, int argumentIndex, SqmToSqlAstConverter converter) {
SqmFunction<?> function,
int argumentIndex,
SqmToSqlAstConverter converter) {
final List<? extends SqmTypedNode<?>> arguments = function.getArguments();
for ( int i = 0; i < arguments.size(); i++ ) { for ( int i = 0; i < arguments.size(); i++ ) {
if ( i != argumentIndex ) { if ( i != argumentIndex ) {
final SqmTypedNode<?> node = arguments.get( i ); final SqmTypedNode<?> node = arguments.get( i );

View File

@ -83,7 +83,8 @@ A set-returning function is a new type of function that can return rows and is e
The concept is known in many different database SQL dialects and is sometimes referred to as table valued function or table function. The concept is known in many different database SQL dialects and is sometimes referred to as table valued function or table function.
Custom set-returning functions can be registered via a `FunctionContributor` and Hibernate ORM Custom set-returning functions can be registered via a `FunctionContributor` and Hibernate ORM
also comes with out-of-the-box support for the set-returning function `unnest()`, which allows to turn an array into rows. also comes with out-of-the-box support for the set-returning functions `unnest()`, which allows to turn an array into rows,
and `generate_series()`, which can be used to create a series of values as rows.
[[cleanup]] [[cleanup]]
== Clean-up == Clean-up