Implement support for ordered set-aggregate functions like listagg, percentile_disc, rank etc.

This commit is contained in:
Christian Beikov 2022-01-31 19:04:31 +01:00
parent bd2446a5d5
commit 0509b10040
54 changed files with 2011 additions and 53 deletions

View File

@ -321,6 +321,8 @@ public class FirebirdDialect extends Dialect {
integerType
);
}
CommonFunctionFactory.listagg_list( "varchar", queryEngine );
}
@Override

View File

@ -299,6 +299,11 @@ public class IngresDialect extends Dialect {
.setInvariantType( stringType )
.register();
// No idea since when this is supported
CommonFunctionFactory.listagg( null, queryEngine );
CommonFunctionFactory.inverseDistributionOrderedSetAggregates( queryEngine );
CommonFunctionFactory.hypotheticalOrderedSetAggregates( queryEngine );
}
@Override

View File

@ -316,6 +316,7 @@ public class SQLiteDialect extends Dialect {
.setParameterTypes(NUMERIC)
.register();
}
CommonFunctionFactory.listagg_groupConcat( queryEngine );
}
@Override

View File

@ -15,11 +15,13 @@ import org.hibernate.dialect.DatabaseVersion;
import org.hibernate.dialect.RowLockStrategy;
import org.hibernate.dialect.SybaseDialect;
import org.hibernate.dialect.TimeZoneSupport;
import org.hibernate.dialect.function.CommonFunctionFactory;
import org.hibernate.dialect.identity.IdentityColumnSupport;
import org.hibernate.dialect.pagination.LimitHandler;
import org.hibernate.dialect.pagination.TopLimitHandler;
import org.hibernate.engine.jdbc.dialect.spi.DialectResolutionInfo;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.query.spi.QueryEngine;
import org.hibernate.sql.ForUpdateFragment;
import org.hibernate.sql.ast.SqlAstTranslator;
import org.hibernate.sql.ast.SqlAstTranslatorFactory;
@ -75,6 +77,12 @@ public class SybaseAnywhereDialect extends SybaseDialect {
}
}
@Override
public void initializeFunctionRegistry(QueryEngine queryEngine) {
super.initializeFunctionRegistry( queryEngine );
CommonFunctionFactory.listagg_list( "varchar", queryEngine );
}
@Override
public int getMaxVarcharLength() {
return 32_767;

View File

@ -284,6 +284,9 @@ public class TeradataDialect extends Dialect {
CommonFunctionFactory.varPopSamp( queryEngine );
}
// No idea since when this is supported
CommonFunctionFactory.inverseDistributionOrderedSetAggregates( queryEngine );
CommonFunctionFactory.hypotheticalOrderedSetAggregates( queryEngine );
}
/**

View File

@ -152,6 +152,7 @@ BOTH : [bB] [oO] [tT] [hH];
CASE : [cC] [aA] [sS] [eE];
CAST : [cC] [aA] [sS] [tT];
COLLATE : [cC] [oO] [lL] [lL] [aA] [tT] [eE];
COUNT : [cC] [oO] [uU] [nN] [tT];
CROSS : [cC] [rR] [oO] [sS] [sS];
CUBE : [cC] [uU] [bB] [eE];
CURRENT : [cC] [uU] [rR] [rR] [eE] [nN] [tT];
@ -171,6 +172,7 @@ ELSE : [eE] [lL] [sS] [eE];
EMPTY : [eE] [mM] [pP] [tT] [yY];
END : [eE] [nN] [dD];
ENTRY : [eE] [nN] [tT] [rR] [yY];
ERROR : [eE] [rR] [rR] [oO] [rR];
ESCAPE : [eE] [sS] [cC] [aA] [pP] [eE];
EVERY : [eE] [vV] [eE] [rR] [yY];
EXCEPT : [eE] [xX] [cC] [eE] [pP] [tT];
@ -205,6 +207,7 @@ LIKE : [lL] [iI] [kK] [eE];
ILIKE : [iI] [lL] [iI] [kK] [eE];
LIMIT : [lL] [iI] [mM] [iI] [tT];
LIST : [lL] [iI] [sS] [tT];
LISTAGG : [lL] [iI] [sS] [tT] [aA] [gG] [gG];
LOCAL : [lL] [oO] [cC] [aA] [lL];
LOCAL_DATE : [lL] [oO] [cC] [aA] [lL] '_' [dD] [aA] [tT] [eE];
LOCAL_DATETIME : [lL] [oO] [cC] [aA] [lL] '_' [dD] [aA] [tT] [eE] [tT] [iI] [mM] [eE];
@ -235,6 +238,7 @@ ONLY : [oO] [nN] [lL] [yY];
OR : [oO] [rR];
ORDER : [oO] [rR] [dD] [eE] [rR];
OUTER : [oO] [uU] [tT] [eE] [rR];
OVERFLOW : [oO] [vV] [eE] [rR] [fF] [lL] [oO] [wW];
OVERLAY : [oO] [vV] [eE] [rR] [lL] [aA] [yY];
PAD : [pP] [aA] [dD];
PERCENT : [pP] [eE] [rR] [cC] [eE] [nN] [tT];
@ -261,6 +265,7 @@ TIMEZONE_MINUTE : [tT] [iI] [mM] [eE] [zZ] [oO] [nN] [eE] '_' [mM] [iI] [nN] [u
TRAILING : [tT] [rR] [aA] [iI] [lL] [iI] [nN] [gG];
TREAT : [tT] [rR] [eE] [aA] [tT];
TRIM : [tT] [rR] [iI] [mM];
TRUNCATE : [tT] [rR] [uU] [nN] [cC] [aA] [tT] [eE];
TYPE : [tT] [yY] [pP] [eE];
UNION : [uU] [nN] [iI] [oO] [nN];
UPDATE : [uU] [pP] [dD] [aA] [tT] [eE];
@ -270,6 +275,8 @@ WEEK : [wW] [eE] [eE] [kK];
WHEN : [wW] [hH] [eE] [nN];
WHERE : [wW] [hH] [eE] [rR] [eE];
WITH : [wW] [iI] [tT] [hH];
WITHIN : [wW] [iI] [tT] [hH] [iI] [nN];
WITHOUT : [wW] [iI] [tT] [hH] [oO] [uU] [tT];
YEAR : [yY] [eE] [aA] [rR];
// case-insensitive true, false and null recognition (split vote :)

View File

@ -975,7 +975,7 @@ jpaNonstandardFunctionName
* The function name, followed by a parenthesized list of comma-separated expressions
*/
genericFunction
: genericFunctionName LEFT_PAREN (genericFunctionArguments | ASTERISK)? RIGHT_PAREN filterClause?
: genericFunctionName LEFT_PAREN (genericFunctionArguments | ASTERISK)? RIGHT_PAREN withinGroupClause? filterClause?
;
/**
@ -1045,6 +1045,7 @@ collectionFunctionMisuse
aggregateFunction
: everyFunction
| anyFunction
| listaggFunction
;
/**
@ -1065,6 +1066,27 @@ anyFunction
| (ANY|SOME) (ELEMENTS|INDICES) LEFT_PAREN simplePath RIGHT_PAREN
;
/**
* The `listagg()` ordered set-aggregate function
*/
listaggFunction
: LISTAGG LEFT_PAREN DISTINCT? expressionOrPredicate COMMA expressionOrPredicate onOverflowClause? RIGHT_PAREN withinGroupClause? filterClause?
;
/**
* A `on overflow` clause: what to do when the text data type used for `listagg` overflows
*/
onOverflowClause
: ON OVERFLOW (ERROR | (TRUNCATE expression? (WITH|WITHOUT) COUNT))
;
/**
* A 'within group' clause: defines the order in which the ordered set-aggregate function should work
*/
withinGroupClause
: WITHIN GROUP LEFT_PAREN orderByClause RIGHT_PAREN
;
/**
* A 'filter' clause: a restriction applied to an aggregate function
*/
@ -1387,6 +1409,7 @@ identifier
| CASE
| CAST
| COLLATE
| COUNT
| CROSS
| CUBE
| CURRENT
@ -1406,6 +1429,7 @@ identifier
| EMPTY
| END
| ENTRY
| ERROR
| ESCAPE
| EVERY
| EXCEPT
@ -1441,6 +1465,7 @@ identifier
| LIKE
| LIMIT
| LIST
| LISTAGG
| LOCAL
| LOCAL_DATE
| LOCAL_DATETIME
@ -1472,6 +1497,7 @@ identifier
| OR
| ORDER
| OUTER
| OVERFLOW
| OVERLAY
| PAD
| PERCENT
@ -1498,6 +1524,7 @@ identifier
| TRAILING
| TREAT
| TRIM
| TRUNCATE
| TYPE
| UNION
| UPDATE
@ -1509,6 +1536,8 @@ identifier
| WHEN
| WHERE
| WITH
| WITHIN
| WITHOUT
| YEAR) {
logUseOfReservedWordAsIdentifier( getCurrentToken() );
}

View File

@ -307,6 +307,10 @@ public abstract class AbstractHANADialect extends Dialect {
CommonFunctionFactory.currentUtcdatetimetimestamp( queryEngine );
CommonFunctionFactory.everyAny_sumCaseCase( queryEngine );
CommonFunctionFactory.bitLength_pattern( queryEngine, "length(to_binary(?1))*8" );
CommonFunctionFactory.listagg_stringAgg( "varchar", queryEngine );
CommonFunctionFactory.inverseDistributionOrderedSetAggregates( queryEngine );
CommonFunctionFactory.hypotheticalOrderedSetAggregates( queryEngine );
}
@Override

View File

@ -234,6 +234,9 @@ public class CockroachDialect extends Dialect {
.setArgumentsValidator( CommonFunctionFactory.formatValidator() )
.setArgumentListSignature("(TEMPORAL datetime as STRING pattern)")
.register();
CommonFunctionFactory.listagg_stringAgg( "string", queryEngine );
CommonFunctionFactory.inverseDistributionOrderedSetAggregates( queryEngine );
CommonFunctionFactory.hypotheticalOrderedSetAggregates( queryEngine );
}
@Override

View File

@ -261,6 +261,14 @@ public class DB2Dialect extends Dialect {
.setParameterTypes(FunctionParameterType.STRING, FunctionParameterType.STRING)
.setArgumentListSignature("(STRING string, STRING pattern)")
.register();
if ( getDB2Version().isSameOrAfter( 9, 5 ) ) {
CommonFunctionFactory.listagg( null, queryEngine );
if ( getDB2Version().isSameOrAfter( 11, 1 ) ) {
CommonFunctionFactory.inverseDistributionOrderedSetAggregates( queryEngine );
CommonFunctionFactory.hypotheticalOrderedSetAggregates( queryEngine );
}
}
}
@Override

View File

@ -6,6 +6,7 @@
*/
package org.hibernate.dialect;
import org.hibernate.dialect.function.CommonFunctionFactory;
import org.hibernate.dialect.identity.DB2390IdentityColumnSupport;
import org.hibernate.dialect.identity.DB2IdentityColumnSupport;
import org.hibernate.dialect.identity.IdentityColumnSupport;
@ -19,6 +20,7 @@ import org.hibernate.dialect.unique.DefaultUniqueDelegate;
import org.hibernate.dialect.unique.UniqueDelegate;
import org.hibernate.engine.jdbc.dialect.spi.DialectResolutionInfo;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.query.spi.QueryEngine;
import org.hibernate.sql.ast.SqlAstTranslator;
import org.hibernate.sql.ast.SqlAstTranslatorFactory;
import org.hibernate.sql.ast.spi.StandardSqlAstTranslatorFactory;
@ -40,6 +42,16 @@ public class DB2iDialect extends DB2Dialect {
registerKeywords( info );
}
@Override
public void initializeFunctionRegistry(QueryEngine queryEngine) {
super.initializeFunctionRegistry( queryEngine );
if ( getVersion().isSameOrAfter( 7, 2 ) ) {
CommonFunctionFactory.listagg( null, queryEngine );
CommonFunctionFactory.inverseDistributionOrderedSetAggregates( queryEngine );
CommonFunctionFactory.hypotheticalOrderedSetAggregates( queryEngine );
}
}
public DB2iDialect() {
this( DatabaseVersion.make(7) );
}

View File

@ -9,6 +9,7 @@ package org.hibernate.dialect;
import jakarta.persistence.TemporalType;
import org.hibernate.dialect.function.CommonFunctionFactory;
import org.hibernate.dialect.identity.DB2390IdentityColumnSupport;
import org.hibernate.dialect.identity.IdentityColumnSupport;
import org.hibernate.dialect.pagination.FetchLimitHandler;
@ -19,6 +20,7 @@ import org.hibernate.dialect.sequence.NoSequenceSupport;
import org.hibernate.dialect.sequence.SequenceSupport;
import org.hibernate.engine.jdbc.dialect.spi.DialectResolutionInfo;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.query.spi.QueryEngine;
import org.hibernate.query.sqm.IntervalType;
import org.hibernate.query.sqm.TemporalUnit;
import org.hibernate.sql.ast.SqlAstTranslator;
@ -51,6 +53,16 @@ public class DB2zDialect extends DB2Dialect {
super(version);
}
@Override
public void initializeFunctionRegistry(QueryEngine queryEngine) {
super.initializeFunctionRegistry( queryEngine );
if ( getVersion().isSameOrAfter( 12 ) ) {
CommonFunctionFactory.listagg( null, queryEngine );
CommonFunctionFactory.inverseDistributionOrderedSetAggregates( queryEngine );
CommonFunctionFactory.hypotheticalOrderedSetAggregates( queryEngine );
}
}
@Override
protected String columnType(int jdbcTypeCode) {
// See https://www.ibm.com/support/knowledgecenter/SSEPEK_10.0.0/wnew/src/tpc/db2z_10_timestamptimezone.html

View File

@ -278,6 +278,16 @@ public class H2Dialect extends Dialect {
CommonFunctionFactory.format_formatdatetime( queryEngine );
}
CommonFunctionFactory.rownum( queryEngine );
if ( getVersion().isSameOrAfter( 1, 4, 200 ) ) {
CommonFunctionFactory.listagg( null, queryEngine );
if ( getVersion().isSameOrAfter( 2 ) ) {
CommonFunctionFactory.inverseDistributionOrderedSetAggregates( queryEngine );
CommonFunctionFactory.hypotheticalOrderedSetAggregates( queryEngine );
}
}
else {
CommonFunctionFactory.listagg_groupConcat( queryEngine );
}
}
@Override

View File

@ -224,6 +224,7 @@ public class HSQLDialect extends Dialect {
if ( getVersion().isSameOrAfter( 2, 2 ) ) {
CommonFunctionFactory.rownum( queryEngine );
}
CommonFunctionFactory.listagg_groupConcat( queryEngine );
}
@Override

View File

@ -446,6 +446,7 @@ public class MySQLDialect extends Dialect {
}
queryEngine.getSqmFunctionRegistry().register( "field", new FieldFunction( queryEngine.getTypeConfiguration() ) );
CommonFunctionFactory.listagg_groupConcat( queryEngine );
}
@Override

View File

@ -22,6 +22,7 @@ import org.hibernate.QueryTimeoutException;
import org.hibernate.boot.model.TypeContributions;
import org.hibernate.cfg.Environment;
import org.hibernate.dialect.function.CommonFunctionFactory;
import org.hibernate.dialect.function.ModeStatsModeEmulation;
import org.hibernate.dialect.function.NvlCoalesceEmulation;
import org.hibernate.dialect.identity.IdentityColumnSupport;
import org.hibernate.dialect.identity.Oracle12cIdentityColumnSupport;
@ -188,6 +189,20 @@ public class OracleDialect extends Dialect {
"instr(?2,?1,?3)",
FunctionParameterType.STRING, FunctionParameterType.STRING, FunctionParameterType.INTEGER
).setArgumentListSignature("(pattern, string[, start])");
// The within group clause became optional in 18
if ( getVersion().isSameOrAfter( 18 ) ) {
CommonFunctionFactory.listagg( null, queryEngine );
}
else {
CommonFunctionFactory.listagg( "within group (order by rownum)", queryEngine );
}
CommonFunctionFactory.hypotheticalOrderedSetAggregates( queryEngine );
CommonFunctionFactory.inverseDistributionOrderedSetAggregates( queryEngine );
// Oracle has a regular aggregate function named stats_mode
queryEngine.getSqmFunctionRegistry().register(
"mode",
new ModeStatsModeEmulation( queryEngine.getTypeConfiguration() )
);
}
@Override

View File

@ -448,9 +448,13 @@ public class PostgreSQLDialect extends Dialect {
CommonFunctionFactory.soundex( queryEngine ); //was introduced in Postgres 9 apparently
CommonFunctionFactory.locate_positionSubstring( queryEngine );
CommonFunctionFactory.listagg_stringAgg( "varchar", queryEngine );
if ( getVersion().isSameOrAfter( 9, 4 ) ) {
CommonFunctionFactory.makeDateTimeTimestamp( queryEngine );
// Note that PostgreSQL doesn't support the OVER clause for ordered set-aggregate functions
CommonFunctionFactory.inverseDistributionOrderedSetAggregates( queryEngine );
CommonFunctionFactory.hypotheticalOrderedSetAggregates( queryEngine );
}
}

View File

@ -265,6 +265,11 @@ public class SQLServerDialect extends AbstractTransactSQLDialect {
.setParameterTypes(INTEGER)
.register();
}
CommonFunctionFactory.inverseDistributionOrderedSetAggregates( queryEngine );
CommonFunctionFactory.hypotheticalOrderedSetAggregates( queryEngine );
if ( getVersion().isSameOrAfter( 14 ) ) {
CommonFunctionFactory.listagg_stringAggWithinGroup( "varchar(max)", queryEngine );
}
}
@Override

View File

@ -427,6 +427,9 @@ public class SpannerDialect extends Dialect {
.setArgumentsValidator( CommonFunctionFactory.formatValidator() )
.setArgumentListSignature("(TIMESTAMP datetime as STRING pattern)")
.register();
CommonFunctionFactory.listagg_stringAgg( "string", queryEngine );
CommonFunctionFactory.inverseDistributionOrderedSetAggregates( queryEngine );
CommonFunctionFactory.hypotheticalOrderedSetAggregates( queryEngine );
}
@Override

View File

@ -1920,6 +1920,75 @@ public class CommonFunctionFactory {
);
}
public static void listagg(String emptyWithinReplacement, QueryEngine queryEngine) {
queryEngine.getSqmFunctionRegistry().register(
ListaggFunction.FUNCTION_NAME,
new ListaggFunction( emptyWithinReplacement, queryEngine.getTypeConfiguration() )
);
}
public static void listagg_groupConcat(QueryEngine queryEngine) {
queryEngine.getSqmFunctionRegistry().register(
ListaggGroupConcatEmulation.FUNCTION_NAME,
new ListaggGroupConcatEmulation( queryEngine.getTypeConfiguration() )
);
}
public static void listagg_list(String stringType, QueryEngine queryEngine) {
queryEngine.getSqmFunctionRegistry().register(
ListaggStringAggEmulation.FUNCTION_NAME,
new ListaggStringAggEmulation( "list", stringType, false, queryEngine.getTypeConfiguration() )
);
}
public static void listagg_stringAgg(String stringType, QueryEngine queryEngine) {
queryEngine.getSqmFunctionRegistry().register(
ListaggStringAggEmulation.FUNCTION_NAME,
new ListaggStringAggEmulation( "string_agg", stringType, false, queryEngine.getTypeConfiguration() )
);
}
public static void listagg_stringAggWithinGroup(String stringType, QueryEngine queryEngine) {
queryEngine.getSqmFunctionRegistry().register(
ListaggStringAggEmulation.FUNCTION_NAME,
new ListaggStringAggEmulation( "string_agg", stringType, true, queryEngine.getTypeConfiguration() )
);
}
public static void inverseDistributionOrderedSetAggregates(QueryEngine queryEngine) {
queryEngine.getSqmFunctionRegistry().register(
"mode",
new InverseDistributionFunction( "mode", null, queryEngine.getTypeConfiguration() )
);
queryEngine.getSqmFunctionRegistry().register(
"percentile_cont",
new InverseDistributionFunction( "percentile_cont", NUMERIC, queryEngine.getTypeConfiguration() )
);
queryEngine.getSqmFunctionRegistry().register(
"percentile_disc",
new InverseDistributionFunction( "percentile_disc", NUMERIC, queryEngine.getTypeConfiguration() )
);
}
public static void hypotheticalOrderedSetAggregates(QueryEngine queryEngine) {
queryEngine.getSqmFunctionRegistry().register(
"rank",
new HypotheticalSetFunction( "rank", StandardBasicTypes.LONG, queryEngine.getTypeConfiguration() )
);
queryEngine.getSqmFunctionRegistry().register(
"dense_rank",
new HypotheticalSetFunction( "dense_rank", StandardBasicTypes.LONG, queryEngine.getTypeConfiguration() )
);
queryEngine.getSqmFunctionRegistry().register(
"percent_rank",
new HypotheticalSetFunction( "percent_rank", StandardBasicTypes.DOUBLE, queryEngine.getTypeConfiguration() )
);
queryEngine.getSqmFunctionRegistry().register(
"cume_dist",
new HypotheticalSetFunction( "cume_dist", StandardBasicTypes.DOUBLE, queryEngine.getTypeConfiguration() )
);
}
public static void math(QueryEngine queryEngine) {
final BasicType<Integer> integerType = queryEngine.getTypeConfiguration().getBasicTypeRegistry()
.resolve( StandardBasicTypes.INTEGER );

View File

@ -0,0 +1,102 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
*/
package org.hibernate.dialect.function;
import java.util.Collections;
import java.util.List;
import org.hibernate.query.sqm.function.AbstractSqmSelfRenderingFunctionDescriptor;
import org.hibernate.query.sqm.function.FunctionKind;
import org.hibernate.query.sqm.produce.function.StandardFunctionReturnTypeResolvers;
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.predicate.Predicate;
import org.hibernate.sql.ast.tree.select.SortSpecification;
import org.hibernate.type.BasicTypeReference;
import org.hibernate.type.spi.TypeConfiguration;
/**
* @author Christian Beikov
*/
public class HypotheticalSetFunction extends AbstractSqmSelfRenderingFunctionDescriptor {
public HypotheticalSetFunction(String name, BasicTypeReference<?> returnType, TypeConfiguration typeConfiguration) {
super(
name,
FunctionKind.ORDERED_SET_AGGREGATE,
null,
StandardFunctionReturnTypeResolvers.invariant(
typeConfiguration.getBasicTypeRegistry().resolve( returnType )
)
);
}
@Override
public void render(
SqlAppender sqlAppender,
List<? extends SqlAstNode> sqlAstArguments,
SqlAstTranslator<?> walker) {
render( sqlAppender, sqlAstArguments, null, Collections.emptyList(), walker );
}
@Override
public void render(
SqlAppender sqlAppender,
List<? extends SqlAstNode> sqlAstArguments,
Predicate filter,
SqlAstTranslator<?> walker) {
render( sqlAppender, sqlAstArguments, filter, Collections.emptyList(), walker );
}
@Override
public void render(
SqlAppender sqlAppender,
List<? extends SqlAstNode> sqlAstArguments,
Predicate filter,
List<SortSpecification> withinGroup,
SqlAstTranslator<?> translator) {
final boolean caseWrapper = filter != null && !translator.supportsFilterClause();
sqlAppender.appendSql( getName() );
sqlAppender.appendSql( '(' );
if ( !sqlAstArguments.isEmpty() ) {
if ( caseWrapper ) {
sqlAppender.appendSql( "case when " );
filter.accept( translator );
sqlAppender.appendSql( " then " );
sqlAstArguments.get( 0 ).accept( translator );
sqlAppender.appendSql( " else null end" );
}
else {
sqlAstArguments.get( 0 ).accept( translator );
}
for ( int i = 1; i < sqlAstArguments.size(); i++ ) {
sqlAppender.appendSql( ',' );
sqlAstArguments.get( i ).accept( translator );
}
}
else if ( caseWrapper ) {
throw new IllegalArgumentException( "Can't emulate filter clause for function [" + getName() + "] without arguments!" );
}
sqlAppender.appendSql( ')' );
if ( withinGroup != null && !withinGroup.isEmpty() ) {
sqlAppender.appendSql( " within group (order by " );
withinGroup.get( 0 ).accept( translator );
for ( int i = 1; i < withinGroup.size(); i++ ) {
sqlAppender.appendSql( ',' );
withinGroup.get( i ).accept( translator );
}
sqlAppender.appendSql( ')' );
}
if ( filter != null ) {
sqlAppender.appendSql( " filter (where " );
filter.accept( translator );
sqlAppender.appendSql( ')' );
}
}
}

View File

@ -0,0 +1,177 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
*/
package org.hibernate.dialect.function;
import java.util.Collections;
import java.util.List;
import org.hibernate.metamodel.mapping.BasicValuedMapping;
import org.hibernate.metamodel.mapping.MappingModelExpressible;
import org.hibernate.metamodel.spi.MappingMetamodelImplementor;
import org.hibernate.query.ReturnableType;
import org.hibernate.query.spi.QueryEngine;
import org.hibernate.query.sqm.function.AbstractSqmSelfRenderingFunctionDescriptor;
import org.hibernate.query.sqm.function.FunctionKind;
import org.hibernate.query.sqm.function.SelfRenderingSqmAggregateFunction;
import org.hibernate.query.sqm.function.SelfRenderingSqmOrderedSetAggregateFunction;
import org.hibernate.query.sqm.produce.function.ArgumentTypesValidator;
import org.hibernate.query.sqm.produce.function.FunctionParameterType;
import org.hibernate.query.sqm.produce.function.StandardArgumentsValidators;
import org.hibernate.query.sqm.sql.SqmToSqlAstConverter;
import org.hibernate.query.sqm.tree.SqmTypedNode;
import org.hibernate.query.sqm.tree.predicate.SqmPredicate;
import org.hibernate.query.sqm.tree.select.SqmOrderByClause;
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.sql.ast.tree.predicate.Predicate;
import org.hibernate.sql.ast.tree.select.SortSpecification;
import org.hibernate.type.spi.TypeConfiguration;
/**
* @author Christian Beikov
*/
public class InverseDistributionFunction extends AbstractSqmSelfRenderingFunctionDescriptor {
public InverseDistributionFunction(String name, FunctionParameterType parameterType, TypeConfiguration typeConfiguration) {
super(
name,
FunctionKind.ORDERED_SET_AGGREGATE,
parameterType == null
? StandardArgumentsValidators.exactly( 0 )
: new ArgumentTypesValidator( StandardArgumentsValidators.exactly( 1 ), parameterType ),
null
);
}
@Override
public <T> SelfRenderingSqmAggregateFunction<T> generateSqmOrderedSetAggregateFunctionExpression(
List<? extends SqmTypedNode<?>> arguments,
SqmPredicate filter,
SqmOrderByClause withinGroupClause,
ReturnableType<T> impliedResultType,
QueryEngine queryEngine,
TypeConfiguration typeConfiguration) {
return new SelfRenderingSqmOrderedSetAggregateFunction<>(
this,
this,
arguments,
filter,
withinGroupClause,
impliedResultType,
getArgumentsValidator(),
getReturnTypeResolver(),
queryEngine.getCriteriaBuilder(),
getName()
) {
@Override
protected ReturnableType<?> resolveResultType(TypeConfiguration typeConfiguration) {
return (ReturnableType<?>) withinGroupClause.getSortSpecifications().get( 0 ).getSortExpression()
.getExpressible();
}
@Override
protected MappingModelExpressible<?> getMappingModelExpressible(
SqmToSqlAstConverter walker,
ReturnableType<?> resultType) {
MappingModelExpressible<?> mapping;
if ( resultType instanceof MappingModelExpressible) {
// here we have a BasicType, which can be cast
// directly to BasicValuedMapping
mapping = (MappingModelExpressible<?>) resultType;
}
else {
// here we have something that is not a BasicType,
// and we have no way to get a BasicValuedMapping
// from it directly
final Expression expression = (Expression) withinGroupClause.getSortSpecifications().get( 0 )
.getSortExpression()
.accept( walker );
if ( expression.getExpressionType() instanceof BasicValuedMapping ) {
return (BasicValuedMapping) expression.getExpressionType();
}
try {
final MappingMetamodelImplementor domainModel = walker.getCreationContext()
.getSessionFactory()
.getRuntimeMetamodels()
.getMappingMetamodel();
return domainModel.resolveMappingExpressible(
getNodeType(),
walker.getFromClauseAccess()::getTableGroup
);
}
catch (Exception e) {
return null; // this works at least approximately
}
}
return mapping;
}
};
}
@Override
public void render(
SqlAppender sqlAppender,
List<? extends SqlAstNode> sqlAstArguments,
SqlAstTranslator<?> walker) {
render( sqlAppender, sqlAstArguments, null, Collections.emptyList(), walker );
}
@Override
public void render(
SqlAppender sqlAppender,
List<? extends SqlAstNode> sqlAstArguments,
Predicate filter,
SqlAstTranslator<?> walker) {
render( sqlAppender, sqlAstArguments, filter, Collections.emptyList(), walker );
}
@Override
public void render(
SqlAppender sqlAppender,
List<? extends SqlAstNode> sqlAstArguments,
Predicate filter,
List<SortSpecification> withinGroup,
SqlAstTranslator<?> translator) {
final boolean caseWrapper = filter != null && !translator.supportsFilterClause();
sqlAppender.appendSql( getName() );
sqlAppender.appendSql( '(' );
if ( !sqlAstArguments.isEmpty() ) {
if ( caseWrapper ) {
sqlAppender.appendSql( "case when " );
filter.accept( translator );
sqlAppender.appendSql( " then " );
sqlAstArguments.get( 0 ).accept( translator );
sqlAppender.appendSql( " else null end" );
}
else {
sqlAstArguments.get( 0 ).accept( translator );
}
}
else if ( caseWrapper ) {
throw new IllegalArgumentException( "Can't emulate filter clause for function [" + getName() + "] without arguments!" );
}
sqlAppender.appendSql( ')' );
if ( withinGroup != null && !withinGroup.isEmpty() ) {
sqlAppender.appendSql( " within group (order by " );
withinGroup.get( 0 ).accept( translator );
for ( int i = 1; i < withinGroup.size(); i++ ) {
sqlAppender.appendSql( ',' );
withinGroup.get( i ).accept( translator );
}
sqlAppender.appendSql( ')' );
}
if ( filter != null ) {
sqlAppender.appendSql( " filter (where " );
filter.accept( translator );
sqlAppender.appendSql( ')' );
}
}
}

View File

@ -0,0 +1,120 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
*/
package org.hibernate.dialect.function;
import java.util.Collections;
import java.util.List;
import org.hibernate.query.sqm.function.AbstractSqmSelfRenderingFunctionDescriptor;
import org.hibernate.query.sqm.function.FunctionKind;
import org.hibernate.query.sqm.produce.function.ArgumentTypesValidator;
import org.hibernate.query.sqm.produce.function.StandardArgumentsValidators;
import org.hibernate.query.sqm.produce.function.StandardFunctionReturnTypeResolvers;
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.Distinct;
import org.hibernate.sql.ast.tree.expression.Expression;
import org.hibernate.sql.ast.tree.predicate.Predicate;
import org.hibernate.sql.ast.tree.select.SortSpecification;
import org.hibernate.type.StandardBasicTypes;
import org.hibernate.type.spi.TypeConfiguration;
import static org.hibernate.query.sqm.produce.function.FunctionParameterType.STRING;
/**
* @author Christian Beikov
*/
public class ListaggFunction extends AbstractSqmSelfRenderingFunctionDescriptor {
public static final String FUNCTION_NAME = "listagg";
private final String emptyWithinReplacement;
public ListaggFunction(String emptyWithinReplacement, TypeConfiguration typeConfiguration) {
super(
FUNCTION_NAME,
FunctionKind.ORDERED_SET_AGGREGATE,
new ArgumentTypesValidator( StandardArgumentsValidators.exactly( 2 ), STRING, STRING ),
StandardFunctionReturnTypeResolvers.invariant(
typeConfiguration.getBasicTypeRegistry().resolve( StandardBasicTypes.STRING )
)
);
this.emptyWithinReplacement = emptyWithinReplacement;
}
@Override
public void render(
SqlAppender sqlAppender,
List<? extends SqlAstNode> sqlAstArguments,
SqlAstTranslator<?> walker) {
render( sqlAppender, sqlAstArguments, null, Collections.emptyList(), walker );
}
@Override
public void render(
SqlAppender sqlAppender,
List<? extends SqlAstNode> sqlAstArguments,
Predicate filter,
SqlAstTranslator<?> walker) {
render( sqlAppender, sqlAstArguments, filter, Collections.emptyList(), walker );
}
@Override
public void render(
SqlAppender sqlAppender,
List<? extends SqlAstNode> sqlAstArguments,
Predicate filter,
List<SortSpecification> withinGroup,
SqlAstTranslator<?> translator) {
final boolean caseWrapper = filter != null && !translator.supportsFilterClause();
sqlAppender.appendSql( "listagg(" );
final SqlAstNode firstArg = sqlAstArguments.get( 0 );
final Expression arg;
if ( firstArg instanceof Distinct ) {
sqlAppender.appendSql( "distinct " );
arg = ( (Distinct) firstArg ).getExpression();
}
else {
arg = (Expression) firstArg;
}
if ( caseWrapper ) {
sqlAppender.appendSql( "case when " );
filter.accept( translator );
sqlAppender.appendSql( " then " );
arg.accept( translator );
sqlAppender.appendSql( " else null end" );
}
else {
arg.accept( translator );
}
if ( sqlAstArguments.size() != 1 ) {
sqlAppender.appendSql( ',' );
sqlAstArguments.get( 1 ).accept( translator );
}
sqlAppender.appendSql( ')' );
if ( withinGroup != null && !withinGroup.isEmpty() ) {
sqlAppender.appendSql( " within group (order by " );
withinGroup.get( 0 ).accept( translator );
for ( int i = 1; i < withinGroup.size(); i++ ) {
sqlAppender.appendSql( ',' );
withinGroup.get( i ).accept( translator );
}
sqlAppender.appendSql( ')' );
}
else if ( emptyWithinReplacement != null ) {
sqlAppender.appendSql( ' ' );
sqlAppender.appendSql( emptyWithinReplacement );
}
if ( !caseWrapper && filter != null ) {
sqlAppender.appendSql( " filter (where " );
filter.accept( translator );
sqlAppender.appendSql( ')' );
}
}
}

View File

@ -0,0 +1,118 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
*/
package org.hibernate.dialect.function;
import java.util.Collections;
import java.util.List;
import org.hibernate.query.sqm.function.AbstractSqmSelfRenderingFunctionDescriptor;
import org.hibernate.query.sqm.function.FunctionKind;
import org.hibernate.query.sqm.produce.function.ArgumentTypesValidator;
import org.hibernate.query.sqm.produce.function.StandardArgumentsValidators;
import org.hibernate.query.sqm.produce.function.StandardFunctionReturnTypeResolvers;
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.Distinct;
import org.hibernate.sql.ast.tree.expression.Expression;
import org.hibernate.sql.ast.tree.expression.Overflow;
import org.hibernate.sql.ast.tree.predicate.Predicate;
import org.hibernate.sql.ast.tree.select.SortSpecification;
import org.hibernate.type.StandardBasicTypes;
import org.hibernate.type.spi.TypeConfiguration;
import static org.hibernate.query.sqm.produce.function.FunctionParameterType.STRING;
/**
* @author Christian Beikov
*/
public class ListaggGroupConcatEmulation extends AbstractSqmSelfRenderingFunctionDescriptor {
public static final String FUNCTION_NAME = "listagg";
public ListaggGroupConcatEmulation(TypeConfiguration typeConfiguration) {
super(
FUNCTION_NAME,
FunctionKind.ORDERED_SET_AGGREGATE,
new ArgumentTypesValidator( StandardArgumentsValidators.exactly( 2 ), STRING, STRING ),
StandardFunctionReturnTypeResolvers.invariant(
typeConfiguration.getBasicTypeRegistry().resolve( StandardBasicTypes.STRING )
)
);
}
@Override
public void render(
SqlAppender sqlAppender,
List<? extends SqlAstNode> sqlAstArguments,
SqlAstTranslator<?> walker) {
render( sqlAppender, sqlAstArguments, null, Collections.emptyList(), walker );
}
@Override
public void render(
SqlAppender sqlAppender,
List<? extends SqlAstNode> sqlAstArguments,
Predicate filter,
SqlAstTranslator<?> walker) {
render( sqlAppender, sqlAstArguments, filter, Collections.emptyList(), walker );
}
@Override
public void render(
SqlAppender sqlAppender,
List<? extends SqlAstNode> sqlAstArguments,
Predicate filter,
List<SortSpecification> withinGroup,
SqlAstTranslator<?> translator) {
final boolean caseWrapper = filter != null && !translator.supportsFilterClause();
sqlAppender.appendSql( "group_concat(" );
final SqlAstNode firstArg = sqlAstArguments.get( 0 );
final Expression arg;
if ( firstArg instanceof Distinct ) {
sqlAppender.appendSql( "distinct " );
arg = ( (Distinct) firstArg ).getExpression();
}
else {
arg = (Expression) firstArg;
}
if ( caseWrapper ) {
sqlAppender.appendSql( "case when " );
filter.accept( translator );
sqlAppender.appendSql( " then " );
arg.accept( translator );
sqlAppender.appendSql( " else null end" );
}
else {
arg.accept( translator );
}
if ( withinGroup != null && !withinGroup.isEmpty() ) {
sqlAppender.appendSql( " order by " );
withinGroup.get( 0 ).accept( translator );
for ( int i = 1; i < withinGroup.size(); i++ ) {
sqlAppender.appendSql( ',' );
withinGroup.get( i ).accept( translator );
}
}
if ( sqlAstArguments.size() != 1 ) {
SqlAstNode separator = sqlAstArguments.get( 1 );
// group_concat doesn't support the overflow clause, so we just omit it
if ( separator instanceof Overflow ) {
separator = ( (Overflow) separator ).getSeparatorExpression();
}
sqlAppender.appendSql( " separator " );
separator.accept( translator );
}
sqlAppender.appendSql( ')' );
if ( !caseWrapper && filter != null ) {
sqlAppender.appendSql( " filter (where " );
filter.accept( translator );
sqlAppender.appendSql( ')' );
}
}
}

View File

@ -0,0 +1,156 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
*/
package org.hibernate.dialect.function;
import java.util.Collections;
import java.util.List;
import org.hibernate.metamodel.mapping.JdbcMapping;
import org.hibernate.query.sqm.CastType;
import org.hibernate.query.sqm.function.AbstractSqmSelfRenderingFunctionDescriptor;
import org.hibernate.query.sqm.function.FunctionKind;
import org.hibernate.query.sqm.produce.function.ArgumentTypesValidator;
import org.hibernate.query.sqm.produce.function.StandardArgumentsValidators;
import org.hibernate.query.sqm.produce.function.StandardFunctionReturnTypeResolvers;
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.Distinct;
import org.hibernate.sql.ast.tree.expression.Expression;
import org.hibernate.sql.ast.tree.expression.Overflow;
import org.hibernate.sql.ast.tree.predicate.Predicate;
import org.hibernate.sql.ast.tree.select.SortSpecification;
import org.hibernate.type.StandardBasicTypes;
import org.hibernate.type.spi.TypeConfiguration;
import static org.hibernate.query.sqm.produce.function.FunctionParameterType.STRING;
/**
* @author Christian Beikov
*/
public class ListaggStringAggEmulation extends AbstractSqmSelfRenderingFunctionDescriptor {
public static final String FUNCTION_NAME = "listagg";
private final String functionName;
private final String stringType;
private final boolean withinGroupClause;
public ListaggStringAggEmulation(
String functionName,
String stringType,
boolean withinGroupClause,
TypeConfiguration typeConfiguration) {
super(
FUNCTION_NAME,
FunctionKind.ORDERED_SET_AGGREGATE,
new ArgumentTypesValidator( StandardArgumentsValidators.exactly( 2 ), STRING, STRING ),
StandardFunctionReturnTypeResolvers.invariant(
typeConfiguration.getBasicTypeRegistry().resolve( StandardBasicTypes.STRING )
)
);
this.functionName = functionName;
this.stringType = stringType;
this.withinGroupClause = withinGroupClause;
}
@Override
public void render(
SqlAppender sqlAppender,
List<? extends SqlAstNode> sqlAstArguments,
SqlAstTranslator<?> walker) {
render( sqlAppender, sqlAstArguments, null, Collections.emptyList(), walker );
}
@Override
public void render(
SqlAppender sqlAppender,
List<? extends SqlAstNode> sqlAstArguments,
Predicate filter,
SqlAstTranslator<?> walker) {
render( sqlAppender, sqlAstArguments, filter, Collections.emptyList(), walker );
}
@Override
public void render(
SqlAppender sqlAppender,
List<? extends SqlAstNode> sqlAstArguments,
Predicate filter,
List<SortSpecification> withinGroup,
SqlAstTranslator<?> translator) {
final boolean caseWrapper = filter != null && !translator.supportsFilterClause();
sqlAppender.appendSql( functionName );
sqlAppender.appendSql( '(' );
final SqlAstNode firstArg = sqlAstArguments.get( 0 );
final Expression arg;
if ( firstArg instanceof Distinct ) {
sqlAppender.appendSql( "distinct " );
arg = ( (Distinct) firstArg ).getExpression();
}
else {
arg = (Expression) firstArg;
}
if ( caseWrapper ) {
sqlAppender.appendSql( "case when " );
filter.accept( translator );
sqlAppender.appendSql( " then " );
renderAsString( sqlAppender, translator, arg );
sqlAppender.appendSql( " else null end" );
}
else {
renderAsString( sqlAppender, translator, arg );
}
if ( sqlAstArguments.size() != 1 ) {
SqlAstNode separator = sqlAstArguments.get( 1 );
// string_agg doesn't support the overflow clause, so we just omit it
if ( separator instanceof Overflow ) {
separator = ( (Overflow) separator ).getSeparatorExpression();
}
sqlAppender.appendSql( ',' );
separator.accept( translator );
if ( !withinGroupClause && withinGroup != null && !withinGroup.isEmpty() ) {
sqlAppender.appendSql( " order by " );
withinGroup.get( 0 ).accept( translator );
for ( int i = 1; i < withinGroup.size(); i++ ) {
sqlAppender.appendSql( ',' );
withinGroup.get( i ).accept( translator );
}
}
}
sqlAppender.appendSql( ')' );
if ( withinGroupClause && withinGroup != null && !withinGroup.isEmpty() ) {
sqlAppender.appendSql( " within group (order by " );
withinGroup.get( 0 ).accept( translator );
for ( int i = 1; i < withinGroup.size(); i++ ) {
sqlAppender.appendSql( ',' );
withinGroup.get( i ).accept( translator );
}
sqlAppender.appendSql( ')' );
}
if ( !caseWrapper && filter != null ) {
sqlAppender.appendSql( " filter (where " );
filter.accept( translator );
sqlAppender.appendSql( ')' );
}
}
private void renderAsString(SqlAppender sqlAppender, SqlAstTranslator<?> translator, Expression expression) {
final JdbcMapping sourceMapping = expression.getExpressionType().getJdbcMappings().get( 0 );
// No need to cast if we already have a string
if ( sourceMapping.getCastType() == CastType.STRING ) {
expression.accept( translator );
}
else {
sqlAppender.appendSql( "cast(" );
expression.accept( translator );
sqlAppender.appendSql( " as " );
sqlAppender.appendSql( stringType );
sqlAppender.appendSql( ')' );
}
}
}

View File

@ -0,0 +1,63 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
*/
package org.hibernate.dialect.function;
import java.util.List;
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.predicate.Predicate;
import org.hibernate.sql.ast.tree.select.SortSpecification;
import org.hibernate.type.spi.TypeConfiguration;
/**
* @author Christian Beikov
*/
public class ModeStatsModeEmulation extends InverseDistributionFunction {
public static final String FUNCTION_NAME = "mode";
public ModeStatsModeEmulation(TypeConfiguration typeConfiguration) {
super(
FUNCTION_NAME,
null,
typeConfiguration
);
}
@Override
public void render(
SqlAppender sqlAppender,
List<? extends SqlAstNode> sqlAstArguments,
Predicate filter,
List<SortSpecification> withinGroup,
SqlAstTranslator<?> translator) {
final boolean caseWrapper = filter != null && !translator.supportsFilterClause();
sqlAppender.appendSql( "stats_mode(" );
if ( withinGroup == null || withinGroup.size() != 1 ) {
throw new IllegalArgumentException( "MODE function requires a WITHIN GROUP clause with exactly one order by item!" );
}
if ( caseWrapper ) {
sqlAppender.appendSql( "case when " );
filter.accept( translator );
sqlAppender.appendSql( " then " );
withinGroup.get( 0 ).accept( translator );
sqlAppender.appendSql( " else null end)" );
}
else {
withinGroup.get( 0 ).accept( translator );
sqlAppender.appendSql( ')' );
if ( filter != null ) {
sqlAppender.appendSql( " filter (where " );
filter.accept( translator );
sqlAppender.appendSql( ')' );
}
}
}
}

View File

@ -32,7 +32,6 @@ import java.util.Locale;
import java.util.Map;
import java.util.Set;
import jakarta.persistence.metamodel.SingularAttribute;
import org.hibernate.NotYetImplementedFor6Exception;
import org.hibernate.QueryException;
import org.hibernate.boot.registry.classloading.spi.ClassLoaderService;
@ -46,7 +45,6 @@ import org.hibernate.internal.util.collections.Stack;
import org.hibernate.internal.util.collections.StandardStack;
import org.hibernate.metamodel.CollectionClassification;
import org.hibernate.metamodel.mapping.CollectionPart;
import org.hibernate.query.ReturnableType;
import org.hibernate.metamodel.model.domain.BasicDomainType;
import org.hibernate.metamodel.model.domain.DomainType;
import org.hibernate.metamodel.model.domain.EntityDomainType;
@ -55,17 +53,9 @@ import org.hibernate.metamodel.model.domain.ManagedDomainType;
import org.hibernate.metamodel.model.domain.PersistentAttribute;
import org.hibernate.metamodel.model.domain.PluralPersistentAttribute;
import org.hibernate.metamodel.model.domain.SingularPersistentAttribute;
import org.hibernate.query.sqm.BinaryArithmeticOperator;
import org.hibernate.query.sqm.ComparisonOperator;
import org.hibernate.query.sqm.FetchClauseType;
import org.hibernate.query.sqm.NullPrecedence;
import org.hibernate.query.PathException;
import org.hibernate.query.ReturnableType;
import org.hibernate.query.SemanticException;
import org.hibernate.query.sqm.SetOperator;
import org.hibernate.query.sqm.SortOrder;
import org.hibernate.query.sqm.TemporalUnit;
import org.hibernate.query.sqm.TrimSpec;
import org.hibernate.query.sqm.UnaryArithmeticOperator;
import org.hibernate.query.hql.HqlLogging;
import org.hibernate.query.hql.spi.DotIdentifierConsumer;
import org.hibernate.query.hql.spi.SemanticPathPart;
@ -73,14 +63,23 @@ import org.hibernate.query.hql.spi.SqmCreationOptions;
import org.hibernate.query.hql.spi.SqmCreationProcessingState;
import org.hibernate.query.hql.spi.SqmCreationState;
import org.hibernate.query.hql.spi.SqmPathRegistry;
import org.hibernate.query.sqm.BinaryArithmeticOperator;
import org.hibernate.query.sqm.ComparisonOperator;
import org.hibernate.query.sqm.FetchClauseType;
import org.hibernate.query.sqm.LiteralNumberFormatException;
import org.hibernate.query.sqm.NodeBuilder;
import org.hibernate.query.sqm.NullPrecedence;
import org.hibernate.query.sqm.ParsingException;
import org.hibernate.query.sqm.SetOperator;
import org.hibernate.query.sqm.SortOrder;
import org.hibernate.query.sqm.SqmExpressible;
import org.hibernate.query.sqm.SqmPathSource;
import org.hibernate.query.sqm.SqmQuerySource;
import org.hibernate.query.sqm.SqmTreeCreationLogger;
import org.hibernate.query.sqm.StrictJpaComplianceViolation;
import org.hibernate.query.sqm.TemporalUnit;
import org.hibernate.query.sqm.TrimSpec;
import org.hibernate.query.sqm.UnaryArithmeticOperator;
import org.hibernate.query.sqm.UnknownEntityException;
import org.hibernate.query.sqm.function.FunctionKind;
import org.hibernate.query.sqm.function.NamedSqmFunctionDescriptor;
@ -99,10 +98,10 @@ import org.hibernate.query.sqm.tree.SqmTypedNode;
import org.hibernate.query.sqm.tree.delete.SqmDeleteStatement;
import org.hibernate.query.sqm.tree.domain.AbstractSqmFrom;
import org.hibernate.query.sqm.tree.domain.SqmCorrelation;
import org.hibernate.query.sqm.tree.domain.SqmMapEntryReference;
import org.hibernate.query.sqm.tree.domain.SqmMapJoin;
import org.hibernate.query.sqm.tree.domain.SqmElementAggregateFunction;
import org.hibernate.query.sqm.tree.domain.SqmIndexAggregateFunction;
import org.hibernate.query.sqm.tree.domain.SqmMapEntryReference;
import org.hibernate.query.sqm.tree.domain.SqmMapJoin;
import org.hibernate.query.sqm.tree.domain.SqmPath;
import org.hibernate.query.sqm.tree.domain.SqmPluralValuedSimplePath;
import org.hibernate.query.sqm.tree.domain.SqmPolymorphicRootDescriptor;
@ -124,6 +123,7 @@ import org.hibernate.query.sqm.tree.expression.SqmFormat;
import org.hibernate.query.sqm.tree.expression.SqmLiteral;
import org.hibernate.query.sqm.tree.expression.SqmLiteralNull;
import org.hibernate.query.sqm.tree.expression.SqmNamedParameter;
import org.hibernate.query.sqm.tree.expression.SqmOverflow;
import org.hibernate.query.sqm.tree.expression.SqmParameter;
import org.hibernate.query.sqm.tree.expression.SqmParameterizedEntityType;
import org.hibernate.query.sqm.tree.expression.SqmPositionalParameter;
@ -185,6 +185,7 @@ import org.hibernate.type.descriptor.java.PrimitiveByteArrayJavaType;
import org.jboss.logging.Logger;
import jakarta.persistence.metamodel.SingularAttribute;
import org.antlr.v4.runtime.ParserRuleContext;
import org.antlr.v4.runtime.Token;
import org.antlr.v4.runtime.tree.ParseTree;
@ -195,7 +196,15 @@ import static java.time.format.DateTimeFormatter.ISO_LOCAL_TIME;
import static java.util.Arrays.asList;
import static java.util.Collections.emptyList;
import static java.util.Collections.singletonList;
import static org.hibernate.grammars.hql.HqlParser.*;
import static org.hibernate.grammars.hql.HqlParser.ELEMENTS;
import static org.hibernate.grammars.hql.HqlParser.EXCEPT;
import static org.hibernate.grammars.hql.HqlParser.IDENTIFIER;
import static org.hibernate.grammars.hql.HqlParser.INDICES;
import static org.hibernate.grammars.hql.HqlParser.INTERSECT;
import static org.hibernate.grammars.hql.HqlParser.ListaggFunctionContext;
import static org.hibernate.grammars.hql.HqlParser.OnOverflowClauseContext;
import static org.hibernate.grammars.hql.HqlParser.PLUS;
import static org.hibernate.grammars.hql.HqlParser.UNION;
import static org.hibernate.query.sqm.TemporalUnit.DATE;
import static org.hibernate.query.sqm.TemporalUnit.DAY_OF_MONTH;
import static org.hibernate.query.sqm.TemporalUnit.DAY_OF_WEEK;
@ -3219,9 +3228,17 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> implem
functionArguments = emptyList();
}
final SqmOrderByClause withinGroup = getWithinGroup( ctx );
final SqmPredicate filterExpression = getFilterExpression( ctx );
SqmFunctionDescriptor functionTemplate = getFunctionDescriptor( functionName );
if ( functionTemplate == null ) {
FunctionKind functionKind = FunctionKind.NORMAL;
if ( withinGroup != null ) {
functionKind = FunctionKind.ORDERED_SET_AGGREGATE;
}
else if ( filterExpression != null ) {
functionKind = FunctionKind.AGGREGATE;
}
functionTemplate = new NamedSqmFunctionDescriptor(
functionName,
true,
@ -3230,13 +3247,23 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> implem
resolveExpressibleTypeBasic( Object.class )
),
functionName,
filterExpression != null ? FunctionKind.AGGREGATE : FunctionKind.NORMAL,
functionKind,
null,
SqlAstNodeRenderingMode.DEFAULT
);
}
if ( functionTemplate.getFunctionKind() == FunctionKind.AGGREGATE ) {
switch ( functionTemplate.getFunctionKind() ) {
case ORDERED_SET_AGGREGATE:
return functionTemplate.generateOrderedSetAggregateSqmExpression(
functionArguments,
filterExpression,
withinGroup,
null,
creationContext.getQueryEngine(),
creationContext.getJpaMetamodel().getTypeConfiguration()
);
case AGGREGATE:
return functionTemplate.generateAggregateSqmExpression(
functionArguments,
filterExpression,
@ -3244,8 +3271,7 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> implem
creationContext.getQueryEngine(),
creationContext.getJpaMetamodel().getTypeConfiguration()
);
}
else {
default:
if ( filterExpression != null ) {
throw new ParsingException( "Illegal use of a FILTER clause for non-aggregate function: " + originalFunctionName );
}
@ -3258,6 +3284,83 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> implem
}
}
@Override
public Object visitListaggFunction(ListaggFunctionContext ctx) {
if ( creationOptions.useStrictJpaCompliance() ) {
throw new StrictJpaComplianceViolation(
"Encountered non-compliant non-standard function call [listagg], but strict JPA " +
"compliance was requested; use JPA's FUNCTION(functionName[,...]) " +
"syntax name instead",
StrictJpaComplianceViolation.Type.FUNCTION_CALL
);
}
final SqmFunctionDescriptor functionTemplate = getFunctionDescriptor( "listagg" );
if ( functionTemplate == null ) {
throw new SemanticException(
"The listagg function was not registered for the dialect!"
);
}
final int argumentStartIndex;
final ParseTree thirdChild = ctx.getChild( 2 );
final boolean distinct;
if ( thirdChild instanceof TerminalNode ) {
distinct = true;
argumentStartIndex = 3;
}
else {
distinct = false;
argumentStartIndex = 2;
}
final SqmExpression<?> firstArgument = (SqmExpression<?>) ctx.getChild( argumentStartIndex ).accept( this );
final SqmExpression<?> secondArgument = (SqmExpression<?>) ctx.getChild( argumentStartIndex + 2 ).accept( this );
final ParseTree overflowCtx = ctx.getChild( argumentStartIndex + 3 );
final List<SqmTypedNode<?>> functionArguments = new ArrayList<>( 3 );
if ( distinct ) {
functionArguments.add( new SqmDistinct<>( firstArgument, creationContext.getNodeBuilder() ) );
}
else {
functionArguments.add( firstArgument );
}
if ( overflowCtx instanceof OnOverflowClauseContext ) {
if ( overflowCtx.getChildCount() > 3 ) {
// ON OVERFLOW TRUNCATE
final TerminalNode countNode = (TerminalNode) overflowCtx.getChild( overflowCtx.getChildCount() - 2 );
final boolean withCount = countNode.getSymbol().getType() == HqlParser.WITH;
final SqmExpression<?> fillerExpression;
if ( overflowCtx.getChildCount() == 6 ) {
fillerExpression = (SqmExpression<?>) overflowCtx.getChild( 3 ).accept( this );
}
else {
// The SQL standard says the default is three periods `...`
fillerExpression = new SqmLiteral<>(
"...",
secondArgument.getNodeType(),
secondArgument.nodeBuilder()
);
}
//noinspection unchecked,rawtypes
functionArguments.add( new SqmOverflow( secondArgument, fillerExpression, withCount ) );
}
else {
// ON OVERFLOW ERROR
functionArguments.add( new SqmOverflow<>( secondArgument, null, false ) );
}
}
else {
functionArguments.add( secondArgument );
}
final SqmOrderByClause withinGroup = getWithinGroup( ctx );
final SqmPredicate filterExpression = getFilterExpression( ctx );
return functionTemplate.generateOrderedSetAggregateSqmExpression(
functionArguments,
filterExpression,
withinGroup,
null,
creationContext.getQueryEngine(),
creationContext.getJpaMetamodel().getTypeConfiguration()
);
}
@Override
public List<SqmTypedNode<?>> visitGenericFunctionArguments(HqlParser.GenericFunctionArgumentsContext ctx) {
final int size = ctx.getChildCount();
@ -3700,6 +3803,21 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> implem
return (SqmSubQuery<X>) subQuery;
}
private SqmOrderByClause getWithinGroup(ParseTree functionCtx) {
HqlParser.WithinGroupClauseContext ctx = null;
for ( int i = functionCtx.getChildCount() - 2; i < functionCtx.getChildCount(); i++ ) {
final ParseTree child = functionCtx.getChild( i );
if ( child instanceof HqlParser.WithinGroupClauseContext ) {
ctx = (HqlParser.WithinGroupClauseContext) child;
break;
}
}
if ( ctx != null ) {
return visitOrderByClause( (HqlParser.OrderByClauseContext) ctx.getChild( 3 ) );
}
return null;
}
private SqmPredicate getFilterExpression(ParseTree functionCtx) {
final ParseTree lastChild = functionCtx.getChild( functionCtx.getChildCount() - 1 );
if ( lastChild instanceof HqlParser.FilterClauseContext ) {

View File

@ -48,6 +48,7 @@ import org.hibernate.query.sqm.tree.expression.SqmLiteral;
import org.hibernate.query.sqm.tree.expression.SqmLiteralEntityType;
import org.hibernate.query.sqm.tree.expression.SqmModifiedSubQueryExpression;
import org.hibernate.query.sqm.tree.expression.SqmNamedParameter;
import org.hibernate.query.sqm.tree.expression.SqmOverflow;
import org.hibernate.query.sqm.tree.expression.SqmParameterizedEntityType;
import org.hibernate.query.sqm.tree.expression.SqmPositionalParameter;
import org.hibernate.query.sqm.tree.expression.SqmStar;
@ -235,6 +236,8 @@ public interface SemanticQueryWalker<T> {
T visitStar(SqmStar sqmStar);
T visitOverflow(SqmOverflow<?> sqmOverflow);
T visitCoalesce(SqmCoalesce<?> sqmCoalesce);
T visitToDuration(SqmToDuration<?> toDuration);

View File

@ -16,6 +16,7 @@ import org.hibernate.query.sqm.sql.SqmToSqlAstConverter;
import org.hibernate.query.sqm.tree.SqmTypedNode;
import org.hibernate.query.sqm.tree.SqmVisitableNode;
import org.hibernate.query.sqm.tree.predicate.SqmPredicate;
import org.hibernate.query.sqm.tree.select.SqmOrderByClause;
import org.hibernate.sql.ast.tree.SqlAstNode;
import org.hibernate.type.spi.TypeConfiguration;
@ -131,6 +132,26 @@ public abstract class AbstractSqmFunctionDescriptor implements SqmFunctionDescri
);
}
@Override
public final <T> SelfRenderingSqmFunction<T> generateOrderedSetAggregateSqmExpression(
List<? extends SqmTypedNode<?>> arguments,
SqmPredicate filter,
SqmOrderByClause withinGroupClause,
ReturnableType<T> impliedResultType,
QueryEngine queryEngine,
TypeConfiguration typeConfiguration) {
argumentsValidator.validate( arguments, getName(), queryEngine );
return generateSqmOrderedSetAggregateFunctionExpression(
arguments,
filter,
withinGroupClause,
impliedResultType,
queryEngine,
typeConfiguration
);
}
/**
* Return an SQM node or subtree representing an invocation of this function
* with the given arguments. This method may be overridden in the case of
@ -165,5 +186,28 @@ public abstract class AbstractSqmFunctionDescriptor implements SqmFunctionDescri
typeConfiguration
);
}
/**
* Return an SQM node or subtree representing an invocation of this ordered set-aggregate function
* with the given arguments. This method may be overridden in the case of
* function descriptors that wish to customize creation of the node.
*
* @param arguments the arguments of the function invocation
* @param impliedResultType the function return type as inferred from its usage
*/
protected <T> SelfRenderingSqmAggregateFunction<T> generateSqmOrderedSetAggregateFunctionExpression(
List<? extends SqmTypedNode<?>> arguments,
SqmPredicate filter,
SqmOrderByClause withinGroupClause,
ReturnableType<T> impliedResultType,
QueryEngine queryEngine,
TypeConfiguration typeConfiguration) {
return (SelfRenderingSqmAggregateFunction<T>) generateSqmExpression(
arguments,
impliedResultType,
queryEngine,
typeConfiguration
);
}
}

View File

@ -12,10 +12,12 @@ import org.hibernate.query.sqm.produce.function.ArgumentsValidator;
import org.hibernate.query.sqm.produce.function.FunctionReturnTypeResolver;
import org.hibernate.query.sqm.tree.SqmTypedNode;
import org.hibernate.query.sqm.tree.predicate.SqmPredicate;
import org.hibernate.query.sqm.tree.select.SqmOrderByClause;
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.predicate.Predicate;
import org.hibernate.sql.ast.tree.select.SortSpecification;
import org.hibernate.type.spi.TypeConfiguration;
import java.util.List;
@ -56,9 +58,25 @@ public abstract class AbstractSqmSelfRenderingFunctionDescriptor
ReturnableType<T> impliedResultType,
QueryEngine queryEngine,
TypeConfiguration typeConfiguration) {
if ( functionKind == FunctionKind.AGGREGATE ) {
return generateAggregateSqmExpression( arguments, null, impliedResultType, queryEngine, typeConfiguration );
}
switch ( functionKind ) {
case ORDERED_SET_AGGREGATE:
return generateOrderedSetAggregateSqmExpression(
arguments,
null,
null,
impliedResultType,
queryEngine,
typeConfiguration
);
case AGGREGATE:
return generateAggregateSqmExpression(
arguments,
null,
impliedResultType,
queryEngine,
typeConfiguration
);
default:
return new SelfRenderingSqmFunction<>(
this,
(sqlAppender, sqlAstArguments, walker) -> render(sqlAppender, sqlAstArguments, walker),
@ -70,6 +88,7 @@ public abstract class AbstractSqmSelfRenderingFunctionDescriptor
getName()
);
}
}
@Override
public <T> SelfRenderingSqmAggregateFunction<T> generateSqmAggregateFunctionExpression(
@ -94,6 +113,31 @@ public abstract class AbstractSqmSelfRenderingFunctionDescriptor
);
}
@Override
public <T> SelfRenderingSqmAggregateFunction<T> generateSqmOrderedSetAggregateFunctionExpression(
List<? extends SqmTypedNode<?>> arguments,
SqmPredicate filter,
SqmOrderByClause withinGroupClause,
ReturnableType<T> impliedResultType,
QueryEngine queryEngine,
TypeConfiguration typeConfiguration) {
if ( functionKind != FunctionKind.ORDERED_SET_AGGREGATE ) {
throw new UnsupportedOperationException( "The function " + getName() + " is not an ordered set-aggregate function!" );
}
return new SelfRenderingSqmOrderedSetAggregateFunction<>(
this,
this,
arguments,
filter,
withinGroupClause,
impliedResultType,
getArgumentsValidator(),
getReturnTypeResolver(),
queryEngine.getCriteriaBuilder(),
getName()
);
}
/**
* Must be overridden by subclasses
*/
@ -109,4 +153,13 @@ public abstract class AbstractSqmSelfRenderingFunctionDescriptor
SqlAstTranslator<?> walker) {
render( sqlAppender, sqlAstArguments, walker );
}
public void render(
SqlAppender sqlAppender,
List<? extends SqlAstNode> sqlAstArguments,
Predicate filter,
List<SortSpecification> withinGroup,
SqlAstTranslator<?> walker) {
render( sqlAppender, sqlAstArguments, walker );
}
}

View File

@ -13,5 +13,6 @@ package org.hibernate.query.sqm.function;
*/
public enum FunctionKind {
NORMAL,
AGGREGATE;
AGGREGATE,
ORDERED_SET_AGGREGATE;
}

View File

@ -10,6 +10,7 @@ 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.predicate.Predicate;
import org.hibernate.sql.ast.tree.select.SortSpecification;
import java.util.List;
@ -38,4 +39,14 @@ public interface FunctionRenderingSupport {
// Ignore the filter by default. Subclasses will override this
render( sqlAppender, sqlAstArguments, walker );
}
default void render(
SqlAppender sqlAppender,
List<? extends SqlAstNode> sqlAstArguments,
Predicate filter,
List<SortSpecification> withinGroup,
SqlAstTranslator<?> walker) {
// Ignore the filter by default. Subclasses will override this
render( sqlAppender, sqlAstArguments, walker );
}
}

View File

@ -15,7 +15,9 @@ import org.hibernate.sql.ast.tree.SqlAstNode;
import org.hibernate.sql.ast.tree.expression.Distinct;
import org.hibernate.sql.ast.tree.expression.Star;
import org.hibernate.sql.ast.tree.predicate.Predicate;
import org.hibernate.sql.ast.tree.select.SortSpecification;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
@ -93,7 +95,15 @@ public class NamedSqmFunctionDescriptor
SqlAppender sqlAppender,
List<? extends SqlAstNode> sqlAstArguments,
SqlAstTranslator<?> translator) {
render( sqlAppender, sqlAstArguments, null, translator );
render( sqlAppender, sqlAstArguments, null, Collections.emptyList(), translator );
}
public void render(
SqlAppender sqlAppender,
List<? extends SqlAstNode> args,
Predicate filter,
SqlAstTranslator<?> translator) {
render( sqlAppender, args, filter, Collections.emptyList(), translator );
}
@Override
@ -101,6 +111,7 @@ public class NamedSqmFunctionDescriptor
SqlAppender sqlAppender,
List<? extends SqlAstNode> sqlAstArguments,
Predicate filter,
List<SortSpecification> withinGroup,
SqlAstTranslator<?> translator) {
final boolean useParens = useParenthesesWhenNoArgs || !sqlAstArguments.isEmpty();
final boolean caseWrapper = filter != null && !translator.supportsFilterClause();
@ -137,6 +148,16 @@ public class NamedSqmFunctionDescriptor
sqlAppender.appendSql( ")" );
}
if ( withinGroup != null && !withinGroup.isEmpty() ) {
sqlAppender.appendSql( " within group (order by" );
translator.render( withinGroup.get( 0 ), argumentRenderingMode );
for ( int i = 1; i < withinGroup.size(); i++ ) {
sqlAppender.appendSql( SqlAppender.COMA_SEPARATOR_CHAR );
translator.render( withinGroup.get( 0 ), argumentRenderingMode );
}
sqlAppender.appendSql( ')' );
}
if ( filter != null && !caseWrapper ) {
sqlAppender.appendSql( " filter (where " );
filter.accept( translator );

View File

@ -14,6 +14,7 @@ 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.predicate.Predicate;
import org.hibernate.sql.ast.tree.select.SortSpecification;
import java.util.List;
@ -77,10 +78,24 @@ public class PatternBasedSqmFunctionDescriptor
}
@Override
public void render(SqlAppender sqlAppender, List<? extends SqlAstNode> sqlAstArguments, Predicate filter, SqlAstTranslator<?> walker) {
public void render(
SqlAppender sqlAppender,
List<? extends SqlAstNode> sqlAstArguments,
Predicate filter,
SqlAstTranslator<?> walker) {
renderer.render( sqlAppender, sqlAstArguments, filter, walker );
}
@Override
public void render(
SqlAppender sqlAppender,
List<? extends SqlAstNode> sqlAstArguments,
Predicate filter,
List<SortSpecification> withinGroup,
SqlAstTranslator<?> walker) {
renderer.render( sqlAppender, sqlAstArguments, filter, withinGroup, walker );
}
@Override
public String getArgumentListSignature() {
return argumentListSignature == null ? super.getArgumentListSignature() : argumentListSignature;

View File

@ -0,0 +1,56 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
*/
package org.hibernate.query.sqm.function;
import java.util.List;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.metamodel.mapping.JdbcMappingContainer;
import org.hibernate.query.ReturnableType;
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.OrderedSetAggregateFunctionExpression;
import org.hibernate.sql.ast.tree.predicate.Predicate;
import org.hibernate.sql.ast.tree.select.SortSpecification;
/**
* Representation of an aggregate function call in the SQL AST for impls that know how to
* render themselves.
*
* @author Christian Beikov
*/
public class SelfRenderingOrderedSetAggregateFunctionSqlAstExpression extends SelfRenderingAggregateFunctionSqlAstExpression
implements OrderedSetAggregateFunctionExpression {
private final List<SortSpecification> withinGroup;
public SelfRenderingOrderedSetAggregateFunctionSqlAstExpression(
String functionName,
FunctionRenderingSupport renderer,
List<? extends SqlAstNode> sqlAstArguments,
Predicate filter,
List<SortSpecification> withinGroup,
ReturnableType<?> type,
JdbcMappingContainer expressible) {
super( functionName, renderer, sqlAstArguments, filter, type, expressible );
this.withinGroup = withinGroup;
}
@Override
public List<SortSpecification> getWithinGroup() {
return withinGroup;
}
@Override
public void renderToSql(
SqlAppender sqlAppender,
SqlAstTranslator<?> walker,
SessionFactoryImplementor sessionFactory) {
getRenderer().render( sqlAppender, getArguments(), getFilter(), withinGroup, walker );
}
}

View File

@ -0,0 +1,175 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
*/
package org.hibernate.query.sqm.function;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.hibernate.query.ReturnableType;
import org.hibernate.query.sqm.NodeBuilder;
import org.hibernate.query.sqm.produce.function.ArgumentsValidator;
import org.hibernate.query.sqm.produce.function.FunctionReturnTypeResolver;
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.SqmDistinct;
import org.hibernate.query.sqm.tree.expression.SqmOrderedSetAggregateFunction;
import org.hibernate.query.sqm.tree.predicate.SqmPredicate;
import org.hibernate.query.sqm.tree.select.SqmOrderByClause;
import org.hibernate.query.sqm.tree.select.SqmSelectableNode;
import org.hibernate.query.sqm.tree.select.SqmSortSpecification;
import org.hibernate.sql.ast.Clause;
import org.hibernate.sql.ast.tree.SqlAstNode;
import org.hibernate.sql.ast.tree.predicate.Predicate;
import org.hibernate.sql.ast.tree.select.SortSpecification;
/**
* @author Christian Beikov
*/
public class SelfRenderingSqmOrderedSetAggregateFunction<T> extends SelfRenderingSqmAggregateFunction<T>
implements SqmOrderedSetAggregateFunction<T> {
private final SqmOrderByClause withinGroup;
public SelfRenderingSqmOrderedSetAggregateFunction(
SqmFunctionDescriptor descriptor,
FunctionRenderingSupport renderingSupport,
List<? extends SqmTypedNode<?>> arguments,
SqmPredicate filter,
SqmOrderByClause withinGroup,
ReturnableType<T> impliedResultType,
ArgumentsValidator argumentsValidator,
FunctionReturnTypeResolver returnTypeResolver,
NodeBuilder nodeBuilder,
String name) {
super(
descriptor,
renderingSupport,
arguments,
filter,
impliedResultType,
argumentsValidator,
returnTypeResolver,
nodeBuilder,
name
);
this.withinGroup = withinGroup;
}
@Override
public SelfRenderingSqmOrderedSetAggregateFunction<T> copy(SqmCopyContext context) {
final SelfRenderingSqmOrderedSetAggregateFunction<T> existing = context.getCopy( this );
if ( existing != null ) {
return existing;
}
final List<SqmTypedNode<?>> arguments = new ArrayList<>( getArguments().size() );
for ( SqmTypedNode<?> argument : getArguments() ) {
arguments.add( argument.copy( context ) );
}
final SelfRenderingSqmOrderedSetAggregateFunction<T> expression = context.registerCopy(
this,
new SelfRenderingSqmOrderedSetAggregateFunction<>(
getFunctionDescriptor(),
getRenderingSupport(),
arguments,
getFilter() == null ? null : getFilter().copy( context ),
withinGroup == null ? null : withinGroup.copy( context ),
getImpliedResultType(),
getArgumentsValidator(),
getReturnTypeResolver(),
nodeBuilder(),
getFunctionName()
)
);
copyTo( expression, context );
return expression;
}
@Override
public SelfRenderingFunctionSqlAstExpression convertToSqlAst(SqmToSqlAstConverter walker) {
final ReturnableType<?> resultType = resolveResultType(
walker.getCreationContext().getMappingMetamodel().getTypeConfiguration()
);
List<SqlAstNode> arguments = resolveSqlAstArguments( getArguments(), walker );
ArgumentsValidator argumentsValidator = getArgumentsValidator();
if ( argumentsValidator != null ) {
argumentsValidator.validateSqlTypes( arguments, getFunctionName() );
}
List<SortSpecification> withinGroup;
if ( this.withinGroup == null ) {
withinGroup = Collections.emptyList();
}
else {
walker.getCurrentClauseStack().push( Clause.ORDER );
try {
final List<SqmSortSpecification> sortSpecifications = this.withinGroup.getSortSpecifications();
withinGroup = new ArrayList<>( sortSpecifications.size() );
for ( SqmSortSpecification sortSpecification : sortSpecifications ) {
final SortSpecification specification = (SortSpecification) walker.visitSortSpecification( sortSpecification );
if ( specification != null ) {
withinGroup.add( specification );
}
}
}
finally {
walker.getCurrentClauseStack().pop();
}
}
return new SelfRenderingOrderedSetAggregateFunctionSqlAstExpression(
getFunctionName(),
getRenderingSupport(),
resolveSqlAstArguments( getArguments(), walker ),
getFilter() == null ? null : (Predicate) getFilter().accept( walker ),
withinGroup,
resultType,
getMappingModelExpressible( walker, resultType )
);
}
@Override
public SqmOrderByClause getWithinGroup() {
return withinGroup;
}
@Override
public void appendHqlString(StringBuilder sb) {
final List<? extends SqmTypedNode<?>> arguments = getArguments();
sb.append( getFunctionName() );
sb.append( '(' );
int i = 1;
if ( arguments.get( 0 ) instanceof SqmDistinct<?> ) {
( (SqmSelectableNode<?>) arguments.get( 0 ) ).appendHqlString( sb );
sb.append( ' ' );
( (SqmSelectableNode<?>) arguments.get( 1 ) ).appendHqlString( sb );
i = 2;
}
for ( ; i < arguments.size(); i++ ) {
sb.append(", ");
( (SqmSelectableNode<?>) arguments.get( i ) ).appendHqlString( sb );
}
sb.append( ')' );
if ( withinGroup != null ) {
sb.append( " within group (order by " );
final List<SqmSortSpecification> sortSpecifications = withinGroup.getSortSpecifications();
sortSpecifications.get( 0 ).appendHqlString( sb );
for ( int j = 1; j < sortSpecifications.size(); j++ ) {
sb.append( ", " );
sortSpecifications.get( j ).appendHqlString( sb );
}
sb.append( ')' );
}
if ( getFilter() != null ) {
sb.append( " filter (where " );
getFilter().appendHqlString( sb );
sb.append( ')' );
}
}
}

View File

@ -11,6 +11,7 @@ import org.hibernate.query.spi.QueryEngine;
import org.hibernate.query.sqm.produce.function.ArgumentsValidator;
import org.hibernate.query.sqm.tree.SqmTypedNode;
import org.hibernate.query.sqm.tree.predicate.SqmPredicate;
import org.hibernate.query.sqm.tree.select.SqmOrderByClause;
import org.hibernate.type.spi.TypeConfiguration;
import java.util.List;
@ -60,6 +61,20 @@ public interface SqmFunctionDescriptor {
throw new UnsupportedOperationException( "Not an aggregate function!" );
}
/**
* Like {@link #generateSqmExpression(List, ReturnableType, QueryEngine, TypeConfiguration)}
* but also accepts a filter predicate. This method is intended for ordered set-aggregate functions.
*/
default <T> SelfRenderingSqmFunction<T> generateOrderedSetAggregateSqmExpression(
List<? extends SqmTypedNode<?>> arguments,
SqmPredicate filter,
SqmOrderByClause withinGroupClause,
ReturnableType<T> impliedResultType,
QueryEngine queryEngine,
TypeConfiguration typeConfiguration) {
throw new UnsupportedOperationException( "Not an ordered set-aggregate function!" );
}
/**
* Convenience for single argument
*/

View File

@ -175,6 +175,19 @@ public class SqmFunctionRegistry {
return namedAggregateDescriptorBuilder( name, name );
}
/**
* Get a builder for creating and registering a name-based ordered set-aggregate function descriptor
* using the passed name as both the registration key and underlying SQL
* function name
*
* @param name The function name (and registration key)
*
* @return The builder
*/
public NamedFunctionDescriptorBuilder namedOrderedSetAggregateDescriptorBuilder(String name) {
return namedOrderedSetAggregateDescriptorBuilder( name, name );
}
/**
* Get a builder for creating and registering a name-based function descriptor.
*
@ -199,6 +212,18 @@ public class SqmFunctionRegistry {
return new NamedFunctionDescriptorBuilder( this, registrationKey, FunctionKind.AGGREGATE, name );
}
/**
* Get a builder for creating and registering a name-based ordered set-aggregate function descriptor.
*
* @param registrationKey The name under which the descriptor will get registered
* @param name The underlying SQL function name to use
*
* @return The builder
*/
public NamedFunctionDescriptorBuilder namedOrderedSetAggregateDescriptorBuilder(String registrationKey, String name) {
return new NamedFunctionDescriptorBuilder( this, registrationKey, FunctionKind.ORDERED_SET_AGGREGATE, name );
}
public NamedFunctionDescriptorBuilder noArgsBuilder(String name) {
return noArgsBuilder( name, name );
}

View File

@ -53,6 +53,7 @@ import org.hibernate.query.sqm.tree.expression.SqmLiteral;
import org.hibernate.query.sqm.tree.expression.SqmLiteralEntityType;
import org.hibernate.query.sqm.tree.expression.SqmModifiedSubQueryExpression;
import org.hibernate.query.sqm.tree.expression.SqmNamedParameter;
import org.hibernate.query.sqm.tree.expression.SqmOverflow;
import org.hibernate.query.sqm.tree.expression.SqmParameterizedEntityType;
import org.hibernate.query.sqm.tree.expression.SqmPositionalParameter;
import org.hibernate.query.sqm.tree.expression.SqmStar;
@ -751,6 +752,11 @@ public class SqmTreePrinter implements SemanticQueryWalker<Object> {
return null;
}
@Override
public Object visitOverflow(SqmOverflow<?> sqmOverflow) {
return null;
}
@Override
public Object visitDurationUnit(SqmDurationUnit<?> durationUnit) {
return null;

View File

@ -15,8 +15,10 @@ import org.hibernate.sql.ast.tree.SqlAstNode;
import org.hibernate.sql.ast.tree.expression.Distinct;
import org.hibernate.sql.ast.tree.expression.Star;
import org.hibernate.sql.ast.tree.predicate.Predicate;
import org.hibernate.sql.ast.tree.select.SortSpecification;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@ -136,7 +138,7 @@ public class PatternRenderer {
SqlAppender sqlAppender,
List<? extends SqlAstNode> args,
SqlAstTranslator<?> translator) {
render( sqlAppender, args, null, translator );
render( sqlAppender, args, null, Collections.emptyList(), translator );
}
public void render(
@ -144,6 +146,15 @@ public class PatternRenderer {
List<? extends SqlAstNode> args,
Predicate filter,
SqlAstTranslator<?> translator) {
render( sqlAppender, args, filter, Collections.emptyList(), translator );
}
public void render(
SqlAppender sqlAppender,
List<? extends SqlAstNode> args,
Predicate filter,
List<SortSpecification> withinGroup,
SqlAstTranslator<?> translator) {
final int numberOfArguments = args.size();
final boolean caseWrapper = filter != null && !translator.supportsFilterClause();
if ( numberOfArguments < maxParamIndex ) {
@ -193,6 +204,16 @@ public class PatternRenderer {
}
}
if ( withinGroup != null && !withinGroup.isEmpty() ) {
sqlAppender.appendSql( " within group (order by" );
translator.render( withinGroup.get( 0 ), argumentRenderingMode );
for ( int i = 1; i < withinGroup.size(); i++ ) {
sqlAppender.appendSql( SqlAppender.COMA_SEPARATOR_CHAR );
translator.render( withinGroup.get( 0 ), argumentRenderingMode );
}
sqlAppender.appendSql( ')' );
}
if ( filter != null && !caseWrapper ) {
sqlAppender.appendSql( " filter (where " );
filter.accept( translator );

View File

@ -53,6 +53,7 @@ import org.hibernate.query.sqm.tree.expression.SqmLiteral;
import org.hibernate.query.sqm.tree.expression.SqmLiteralEntityType;
import org.hibernate.query.sqm.tree.expression.SqmModifiedSubQueryExpression;
import org.hibernate.query.sqm.tree.expression.SqmNamedParameter;
import org.hibernate.query.sqm.tree.expression.SqmOverflow;
import org.hibernate.query.sqm.tree.expression.SqmParameterizedEntityType;
import org.hibernate.query.sqm.tree.expression.SqmPositionalParameter;
import org.hibernate.query.sqm.tree.expression.SqmStar;
@ -625,6 +626,7 @@ public abstract class BaseSemanticQueryWalker implements SemanticQueryWalker<Obj
@Override
public Object visitDistinct(SqmDistinct<?> distinct) {
distinct.getExpression().accept( this );
return distinct;
}
@ -633,7 +635,15 @@ public abstract class BaseSemanticQueryWalker implements SemanticQueryWalker<Obj
return sqmStar;
}
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@Override
public Object visitOverflow(SqmOverflow<?> sqmOverflow) {
sqmOverflow.getSeparatorExpression().accept( this );
if ( sqmOverflow.getFillerExpression() != null ) {
sqmOverflow.getFillerExpression().accept( this );
}
return sqmOverflow;
}
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// expressions

View File

@ -188,6 +188,7 @@ import org.hibernate.query.sqm.tree.expression.SqmLiteralEntityType;
import org.hibernate.query.sqm.tree.expression.SqmLiteralNull;
import org.hibernate.query.sqm.tree.expression.SqmModifiedSubQueryExpression;
import org.hibernate.query.sqm.tree.expression.SqmNamedParameter;
import org.hibernate.query.sqm.tree.expression.SqmOverflow;
import org.hibernate.query.sqm.tree.expression.SqmParameter;
import org.hibernate.query.sqm.tree.expression.SqmParameterizedEntityType;
import org.hibernate.query.sqm.tree.expression.SqmPositionalParameter;
@ -283,6 +284,7 @@ import org.hibernate.sql.ast.tree.expression.JdbcLiteral;
import org.hibernate.sql.ast.tree.expression.JdbcParameter;
import org.hibernate.sql.ast.tree.expression.ModifiedSubQueryExpression;
import org.hibernate.sql.ast.tree.expression.Over;
import org.hibernate.sql.ast.tree.expression.Overflow;
import org.hibernate.sql.ast.tree.expression.QueryLiteral;
import org.hibernate.sql.ast.tree.expression.SelfRenderingExpression;
import org.hibernate.sql.ast.tree.expression.SelfRenderingSqlFragmentExpression;
@ -4574,6 +4576,17 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
return new Distinct( (Expression) sqmDistinct.getExpression().accept( this ) );
}
@Override
public Object visitOverflow(SqmOverflow<?> sqmOverflow) {
return new Overflow(
(Expression) sqmOverflow.getSeparatorExpression().accept( this ),
sqmOverflow.getFillerExpression() == null
? null
: (Expression) sqmOverflow.getFillerExpression().accept( this ),
sqmOverflow.isWithCount()
);
}
@Override
public Object visitTrimSpecification(SqmTrimSpecification specification) {
return new TrimSpecification( specification.getSpecification() );

View File

@ -108,8 +108,7 @@ public class SqlAstProcessingStateImpl
protected Expression normalize(Expression expression) {
final Clause currentClause = currentClauseAccess.get();
if ( currentClause == Clause.ORDER
|| currentClause == Clause.GROUP ) {
if ( sqlSelectionMap() != null && ( currentClause == Clause.ORDER || currentClause == Clause.GROUP ) ) {
// see if this (Sql)Expression is used as a selection, and if so
// wrap the (Sql)Expression in a special wrapper with access to both
// the (Sql)Expression and the SqlSelection.

View File

@ -57,6 +57,7 @@ public class SqmDistinct<T> extends AbstractSqmNode implements SqmTypedNode<T>,
@Override
public void appendHqlString(StringBuilder sb) {
sb.append( "distinct" );
sb.append( "distinct " );
expression.appendHqlString( sb );
}
}

View File

@ -0,0 +1,21 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
*/
package org.hibernate.query.sqm.tree.expression;
import org.hibernate.query.sqm.tree.select.SqmOrderByClause;
/**
* A SQM ordered set-aggregate function.
*
* @param <T> The Java type of the expression
*
* @author Christian Beikov
*/
public interface SqmOrderedSetAggregateFunction<T> extends SqmAggregateFunction<T> {
SqmOrderByClause getWithinGroup();
}

View File

@ -0,0 +1,82 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
*/
package org.hibernate.query.sqm.tree.expression;
import org.hibernate.query.sqm.SemanticQueryWalker;
import org.hibernate.query.sqm.tree.SqmCopyContext;
/**
* @author Christian Beikov
*/
public class SqmOverflow<T> extends AbstractSqmExpression<T> {
private final SqmExpression<T> separatorExpression;
private final SqmExpression<T> fillerExpression;
private final boolean withCount;
public SqmOverflow(SqmExpression<T> separatorExpression, SqmExpression<T> fillerExpression, boolean withCount) {
super( separatorExpression.getNodeType(), separatorExpression.nodeBuilder() );
this.separatorExpression = separatorExpression;
this.fillerExpression = fillerExpression;
this.withCount = withCount;
}
@Override
public SqmOverflow<T> copy(SqmCopyContext context) {
final SqmOverflow<T> existing = context.getCopy( this );
if ( existing != null ) {
return existing;
}
final SqmOverflow<T> expression = context.registerCopy(
this,
new SqmOverflow<>(
separatorExpression.copy( context ),
fillerExpression == null ? null : fillerExpression.copy( context ),
withCount
)
);
copyTo( expression, context );
return expression;
}
public SqmExpression<T> getSeparatorExpression() {
return separatorExpression;
}
public SqmExpression<T> getFillerExpression() {
return fillerExpression;
}
public boolean isWithCount() {
return withCount;
}
@Override
public <X> X accept(SemanticQueryWalker<X> walker) {
return walker.visitOverflow( this );
}
@Override
public void appendHqlString(StringBuilder sb) {
separatorExpression.appendHqlString( sb );
sb.append( " on overflow " );
if ( fillerExpression == null ) {
sb.append( "error" );
}
else {
sb.append( "truncate " );
fillerExpression.appendHqlString( sb );
if ( withCount ) {
sb.append( " with count" );
}
else {
sb.append( " without count" );
}
}
}
}

View File

@ -30,6 +30,7 @@ import org.hibernate.sql.ast.tree.expression.JdbcLiteral;
import org.hibernate.sql.ast.tree.expression.JdbcParameter;
import org.hibernate.sql.ast.tree.expression.ModifiedSubQueryExpression;
import org.hibernate.sql.ast.tree.expression.Over;
import org.hibernate.sql.ast.tree.expression.Overflow;
import org.hibernate.sql.ast.tree.expression.QueryLiteral;
import org.hibernate.sql.ast.tree.expression.SelfRenderingExpression;
import org.hibernate.sql.ast.tree.expression.SqlSelectionExpression;
@ -122,6 +123,8 @@ public interface SqlAstWalker {
void visitDistinct(Distinct distinct);
void visitOverflow(Overflow distinct);
void visitStar(Star star);
void visitTrimSpecification(TrimSpecification trimSpecification);

View File

@ -102,6 +102,7 @@ import org.hibernate.sql.ast.tree.expression.Literal;
import org.hibernate.sql.ast.tree.expression.LiteralAsParameter;
import org.hibernate.sql.ast.tree.expression.ModifiedSubQueryExpression;
import org.hibernate.sql.ast.tree.expression.Over;
import org.hibernate.sql.ast.tree.expression.Overflow;
import org.hibernate.sql.ast.tree.expression.QueryLiteral;
import org.hibernate.sql.ast.tree.expression.SelfRenderingExpression;
import org.hibernate.sql.ast.tree.expression.SqlSelectionExpression;
@ -4225,6 +4226,25 @@ public abstract class AbstractSqlAstTranslator<T extends JdbcOperation> implemen
distinct.getExpression().accept( this );
}
@Override
public void visitOverflow(Overflow overflow) {
overflow.getSeparatorExpression().accept( this );
appendSql( " on overflow " );
if ( overflow.getFillerExpression() == null ) {
appendSql( "error" );
}
else {
appendSql( " truncate " );
overflow.getFillerExpression().accept( this );
if ( overflow.isWithCount() ) {
appendSql( " with count" );
}
else {
appendSql( " without count" );
}
}
}
@Override
public void visitParameter(JdbcParameter jdbcParameter) {
switch ( parameterRenderingMode ) {

View File

@ -35,6 +35,7 @@ import org.hibernate.sql.ast.tree.expression.JdbcLiteral;
import org.hibernate.sql.ast.tree.expression.JdbcParameter;
import org.hibernate.sql.ast.tree.expression.ModifiedSubQueryExpression;
import org.hibernate.sql.ast.tree.expression.Over;
import org.hibernate.sql.ast.tree.expression.Overflow;
import org.hibernate.sql.ast.tree.expression.QueryLiteral;
import org.hibernate.sql.ast.tree.expression.SelfRenderingExpression;
import org.hibernate.sql.ast.tree.expression.SqlSelectionExpression;
@ -325,6 +326,14 @@ public class AbstractSqlAstWalker implements SqlAstWalker {
distinct.getExpression().accept( this );
}
@Override
public void visitOverflow(Overflow overflow) {
overflow.getSeparatorExpression().accept( this );
if ( overflow.getFillerExpression() != null ) {
overflow.getFillerExpression().accept( this );
}
}
@Override
public void visitOffsetFetchClause(QueryPart querySpec) {
if ( querySpec.getSortSpecifications() != null ) {

View File

@ -27,6 +27,7 @@ import org.hibernate.sql.ast.tree.expression.JdbcLiteral;
import org.hibernate.sql.ast.tree.expression.JdbcParameter;
import org.hibernate.sql.ast.tree.expression.ModifiedSubQueryExpression;
import org.hibernate.sql.ast.tree.expression.Over;
import org.hibernate.sql.ast.tree.expression.Overflow;
import org.hibernate.sql.ast.tree.expression.QueryLiteral;
import org.hibernate.sql.ast.tree.expression.SelfRenderingExpression;
import org.hibernate.sql.ast.tree.expression.SqlSelectionExpression;
@ -164,6 +165,10 @@ public class AggregateFunctionChecker extends AbstractSqlAstWalker {
public void visitDistinct(Distinct distinct) {
}
@Override
public void visitOverflow(Overflow overflow) {
}
@Override
public void visitStar(Star star) {
}

View File

@ -0,0 +1,21 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
*/
package org.hibernate.sql.ast.tree.expression;
import java.util.List;
import org.hibernate.sql.ast.tree.select.SortSpecification;
/**
* Models an ordered set-aggregate function expression at the SQL AST level.
*
* @author Christian Beikov
*/
public interface OrderedSetAggregateFunctionExpression extends AggregateFunctionExpression {
List<SortSpecification> getWithinGroup();
}

View File

@ -0,0 +1,62 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
*/
package org.hibernate.sql.ast.tree.expression;
import org.hibernate.mapping.IndexedConsumer;
import org.hibernate.metamodel.mapping.JdbcMapping;
import org.hibernate.metamodel.mapping.JdbcMappingContainer;
import org.hibernate.metamodel.mapping.SqlExpressible;
import org.hibernate.sql.ast.SqlAstWalker;
import org.hibernate.sql.ast.tree.SqlAstNode;
/**
* @author Christian Beikov
*/
public class Overflow implements Expression, SqlExpressible, SqlAstNode {
private final Expression separatorExpression;
private final Expression fillerExpression;
private final boolean withCount;
public Overflow(Expression separatorExpression, Expression fillerExpression, boolean withCount) {
this.separatorExpression = separatorExpression;
this.fillerExpression = fillerExpression;
this.withCount = withCount;
}
public Expression getSeparatorExpression() {
return separatorExpression;
}
public Expression getFillerExpression() {
return fillerExpression;
}
public boolean isWithCount() {
return withCount;
}
@Override
public JdbcMapping getJdbcMapping() {
return ( (SqlExpressible) separatorExpression ).getJdbcMapping();
}
@Override
public JdbcMappingContainer getExpressionType() {
return separatorExpression.getExpressionType();
}
@Override
public void accept(SqlAstWalker sqlTreeWalker) {
sqlTreeWalker.visitOverflow( this );
}
@Override
public int forEachJdbcType(int offset, IndexedConsumer<JdbcMapping> action) {
action.accept( offset, getJdbcMapping() );
return getJdbcTypeCount();
}
}

View File

@ -0,0 +1,166 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
*/
package org.hibernate.orm.test.query.hql;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import org.hibernate.dialect.AbstractHANADialect;
import org.hibernate.dialect.SQLServerDialect;
import org.hibernate.testing.orm.domain.StandardDomainModel;
import org.hibernate.testing.orm.domain.gambit.EntityOfBasics;
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.ServiceRegistry;
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.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import jakarta.persistence.TypedQuery;
import static org.junit.jupiter.api.Assertions.assertEquals;
/**
* @author Christian Beikov
*/
@ServiceRegistry
@DomainModel(standardModels = StandardDomainModel.GAMBIT)
@SessionFactory
public class OrderedSetAggregateTest {
@BeforeEach
public void prepareData(SessionFactoryScope scope) {
scope.inTransaction(
em -> {
Date now = new Date();
EntityOfBasics entity1 = new EntityOfBasics();
entity1.setId( 1 );
entity1.setTheString( "5" );
entity1.setTheInt( 5 );
entity1.setTheInteger( -1 );
entity1.setTheDouble( 5.0 );
entity1.setTheDate( now );
entity1.setTheBoolean( true );
em.persist( entity1 );
EntityOfBasics entity2 = new EntityOfBasics();
entity2.setId( 2 );
entity2.setTheString( "6" );
entity2.setTheInt( 6 );
entity2.setTheInteger( -2 );
entity2.setTheDouble( 6.0 );
entity2.setTheBoolean( true );
em.persist( entity2 );
EntityOfBasics entity3 = new EntityOfBasics();
entity3.setId( 3 );
entity3.setTheString( "7" );
entity3.setTheInt( 7 );
entity3.setTheInteger( 3 );
entity3.setTheDouble( 7.0 );
entity3.setTheBoolean( false );
entity3.setTheDate( new Date(now.getTime() + 200000L) );
em.persist( entity3 );
EntityOfBasics entity4 = new EntityOfBasics();
entity4.setId( 4 );
entity4.setTheString( "13" );
entity4.setTheInt( 13 );
entity4.setTheInteger( 4 );
entity4.setTheDouble( 13.0 );
entity4.setTheBoolean( false );
entity4.setTheDate( new Date(now.getTime() + 300000L) );
em.persist( entity4 );
EntityOfBasics entity5 = new EntityOfBasics();
entity5.setId( 5 );
entity5.setTheString( "5" );
entity5.setTheInt( 5 );
entity5.setTheInteger( 5 );
entity5.setTheDouble( 9.0 );
entity5.setTheBoolean( false );
em.persist( entity5 );
}
);
}
@AfterEach
public void tearDown(SessionFactoryScope scope) {
scope.inTransaction(
session -> session.createQuery( "delete from EntityOfBasics" ).executeUpdate()
);
}
@Test
@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsStringAggregation.class)
public void testListaggWithoutOrder(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
TypedQuery<String> q = session.createQuery( "select listagg(eob.theString, ',') from EntityOfBasics eob", String.class );
List<String> elements = Arrays.asList( q.getSingleResult().split( "," ) );
List<String> expectedElements = List.of( "13", "5", "5", "6", "7" );
elements.sort( String.CASE_INSENSITIVE_ORDER );
assertEquals( expectedElements, elements );
}
);
}
@Test
@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsStringAggregation.class)
public void testSimpleListagg(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
TypedQuery<String> q = session.createQuery( "select listagg(eob.theString, ',') within group (order by eob.id desc) from EntityOfBasics eob", String.class );
assertEquals( "5,13,7,6,5", q.getSingleResult() );
}
);
}
@Test
@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsStringAggregation.class)
public void testListaggWithFilter(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
TypedQuery<String> q = session.createQuery( "select listagg(eob.theString, ',') within group (order by eob.id desc) filter (where eob.theInt < 10) from EntityOfBasics eob", String.class );
assertEquals( "5,7,6,5", q.getSingleResult() );
}
);
}
@Test
@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsInverseDistributionFunctions.class)
@SkipForDialect(dialectClass = SQLServerDialect.class, reason = "The function is only supported as window function and needs emulation")
public void testInverseDistribution(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
TypedQuery<Integer> q = session.createQuery( "select percentile_disc(0.5) within group (order by eob.theInt asc) from EntityOfBasics eob", Integer.class );
assertEquals( 6, q.getSingleResult() );
}
);
}
@Test
@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsHypotheticalSetFunctions.class)
@SkipForDialect(dialectClass = SQLServerDialect.class, reason = "The function is only supported as window function and needs emulation")
@SkipForDialect(dialectClass = AbstractHANADialect.class, matchSubTypes = true, reason = "The function is only supported as window function and needs emulation")
public void testHypotheticalSet(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
TypedQuery<Double> q = session.createQuery( "select percent_rank(5) within group (order by eob.theInt asc) from EntityOfBasics eob", Double.class );
assertEquals( 0.0D, q.getSingleResult() );
}
);
}
}

View File

@ -11,12 +11,15 @@ import org.hibernate.dialect.CockroachDialect;
import org.hibernate.dialect.DB2Dialect;
import org.hibernate.dialect.DerbyDialect;
import org.hibernate.dialect.Dialect;
import org.hibernate.dialect.H2Dialect;
import org.hibernate.dialect.HSQLDialect;
import org.hibernate.dialect.MariaDBDialect;
import org.hibernate.dialect.MySQLDialect;
import org.hibernate.dialect.NationalizationSupport;
import org.hibernate.dialect.OracleDialect;
import org.hibernate.dialect.PostgreSQLDialect;
import org.hibernate.dialect.SQLServerDialect;
import org.hibernate.dialect.SpannerDialect;
import org.hibernate.dialect.TimeZoneSupport;
import org.hibernate.dialect.TiDBDialect;
import org.hibernate.query.sqm.FetchClauseType;
@ -380,4 +383,45 @@ abstract public class DialectFeatureChecks {
return dialect.forceLobAsLastValue();
}
}
public static class SupportsStringAggregation implements DialectFeatureCheck {
public boolean apply(Dialect dialect) {
return dialect instanceof H2Dialect
|| dialect instanceof HSQLDialect
|| dialect instanceof MySQLDialect
|| dialect instanceof PostgreSQLDialect
|| dialect instanceof AbstractHANADialect
|| dialect instanceof CockroachDialect
|| dialect instanceof DB2Dialect
|| dialect instanceof OracleDialect
|| dialect instanceof SpannerDialect
|| dialect instanceof SQLServerDialect;
}
}
public static class SupportsInverseDistributionFunctions implements DialectFeatureCheck {
public boolean apply(Dialect dialect) {
return dialect instanceof H2Dialect && dialect.getVersion().isSameOrAfter( 2 )
|| dialect instanceof PostgreSQLDialect
|| dialect instanceof AbstractHANADialect
|| dialect instanceof CockroachDialect
|| dialect instanceof DB2Dialect
|| dialect instanceof OracleDialect
|| dialect instanceof SpannerDialect
|| dialect instanceof SQLServerDialect;
}
}
public static class SupportsHypotheticalSetFunctions implements DialectFeatureCheck {
public boolean apply(Dialect dialect) {
return dialect instanceof H2Dialect && dialect.getVersion().isSameOrAfter( 2 )
|| dialect instanceof PostgreSQLDialect
|| dialect instanceof AbstractHANADialect
|| dialect instanceof CockroachDialect
|| dialect instanceof DB2Dialect
|| dialect instanceof OracleDialect
|| dialect instanceof SpannerDialect
|| dialect instanceof SQLServerDialect;
}
}
}