Implement support for ordered set-aggregate functions like listagg, percentile_disc, rank etc.
This commit is contained in:
parent
bd2446a5d5
commit
0509b10040
|
@ -321,6 +321,8 @@ public class FirebirdDialect extends Dialect {
|
|||
integerType
|
||||
);
|
||||
}
|
||||
|
||||
CommonFunctionFactory.listagg_list( "varchar", queryEngine );
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -316,6 +316,7 @@ public class SQLiteDialect extends Dialect {
|
|||
.setParameterTypes(NUMERIC)
|
||||
.register();
|
||||
}
|
||||
CommonFunctionFactory.listagg_groupConcat( queryEngine );
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 );
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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 :)
|
||||
|
|
|
@ -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() );
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) );
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -224,6 +224,7 @@ public class HSQLDialect extends Dialect {
|
|||
if ( getVersion().isSameOrAfter( 2, 2 ) ) {
|
||||
CommonFunctionFactory.rownum( queryEngine );
|
||||
}
|
||||
CommonFunctionFactory.listagg_groupConcat( queryEngine );
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -446,6 +446,7 @@ public class MySQLDialect extends Dialect {
|
|||
}
|
||||
|
||||
queryEngine.getSqmFunctionRegistry().register( "field", new FieldFunction( queryEngine.getTypeConfiguration() ) );
|
||||
CommonFunctionFactory.listagg_groupConcat( queryEngine );
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 );
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 );
|
||||
|
|
|
@ -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( ')' );
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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( ')' );
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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( ')' );
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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( ')' );
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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( ')' );
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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( ')' );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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 ) {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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 );
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,5 +13,6 @@ package org.hibernate.query.sqm.function;
|
|||
*/
|
||||
public enum FunctionKind {
|
||||
NORMAL,
|
||||
AGGREGATE;
|
||||
AGGREGATE,
|
||||
ORDERED_SET_AGGREGATE;
|
||||
}
|
||||
|
|
|
@ -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 );
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 );
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 );
|
||||
}
|
||||
}
|
|
@ -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( ')' );
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
*/
|
||||
|
|
|
@ -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 );
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 );
|
||||
|
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
@ -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() );
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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 );
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
|
@ -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" );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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 ) {
|
||||
|
|
|
@ -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 ) {
|
||||
|
|
|
@ -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) {
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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() );
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue