Implement query transformer to emulate for ordered set aggregate functions through their window variants

This commit is contained in:
Christian Beikov 2022-02-01 20:34:37 +01:00
parent f52cf04a16
commit 236ece769b
24 changed files with 1480 additions and 100 deletions

View File

@ -313,7 +313,7 @@ public abstract class AbstractHANADialect extends Dialect {
functionFactory.listagg_stringAgg( "varchar" );
functionFactory.inverseDistributionOrderedSetAggregates();
functionFactory.hypotheticalOrderedSetAggregates();
functionFactory.hypotheticalOrderedSetAggregates_windowEmulation();
queryEngine.getSqmFunctionRegistry().register( "timestampadd",
new IntegralTimestampaddFunction( this, queryEngine.getTypeConfiguration() ) );

View File

@ -267,7 +267,7 @@ public class DB2Dialect extends Dialect {
functionFactory.listagg( null );
if ( getDB2Version().isSameOrAfter( 11, 1 ) ) {
functionFactory.inverseDistributionOrderedSetAggregates();
functionFactory.hypotheticalOrderedSetAggregates();
functionFactory.hypotheticalOrderedSetAggregates_windowEmulation();
}
}
}

View File

@ -49,7 +49,7 @@ public class DB2iDialect extends DB2Dialect {
CommonFunctionFactory functionFactory = new CommonFunctionFactory(queryEngine);
functionFactory.listagg( null );
functionFactory.inverseDistributionOrderedSetAggregates();
functionFactory.hypotheticalOrderedSetAggregates();
functionFactory.hypotheticalOrderedSetAggregates_windowEmulation();
}
}

View File

@ -60,7 +60,7 @@ public class DB2zDialect extends DB2Dialect {
CommonFunctionFactory functionFactory = new CommonFunctionFactory(queryEngine);
functionFactory.listagg( null );
functionFactory.inverseDistributionOrderedSetAggregates();
functionFactory.hypotheticalOrderedSetAggregates();
functionFactory.hypotheticalOrderedSetAggregates_windowEmulation();
}
}

View File

@ -330,7 +330,7 @@ public class OracleSqlAstTranslator<T extends JdbcOperation> extends AbstractSql
}
@Override
public void visitOver(Over over) {
public void visitOver(Over<?> over) {
final Expression expression = over.getExpression();
if ( expression instanceof FunctionExpression && "row_number".equals( ( (FunctionExpression) expression ).getFunctionName() ) ) {
if ( over.getPartitions().isEmpty() && over.getOrderList().isEmpty()

View File

@ -267,8 +267,8 @@ public class SQLServerDialect extends AbstractTransactSQLDialect {
.setParameterTypes(INTEGER)
.register();
}
functionFactory.inverseDistributionOrderedSetAggregates();
functionFactory.hypotheticalOrderedSetAggregates();
functionFactory.inverseDistributionOrderedSetAggregates_windowEmulation();
functionFactory.hypotheticalOrderedSetAggregates_windowEmulation();
if ( getVersion().isSameOrAfter( 14 ) ) {
functionFactory.listagg_stringAggWithinGroup( "varchar(max)" );
}

View File

@ -0,0 +1,440 @@
/*
* 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.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.metamodel.mapping.BasicValuedMapping;
import org.hibernate.metamodel.mapping.JdbcMapping;
import org.hibernate.query.ReturnableType;
import org.hibernate.query.spi.NavigablePath;
import org.hibernate.query.sqm.ComparisonOperator;
import org.hibernate.query.sqm.function.SelfRenderingAggregateFunctionSqlAstExpression;
import org.hibernate.query.sqm.sql.SqmToSqlAstConverter;
import org.hibernate.query.sqm.sql.internal.SqmPathInterpretation;
import org.hibernate.sql.ast.spi.ExpressionReplacementWalker;
import org.hibernate.sql.ast.spi.SqlSelection;
import org.hibernate.sql.ast.tree.SqlAstNode;
import org.hibernate.sql.ast.tree.cte.CteContainer;
import org.hibernate.sql.ast.tree.expression.ColumnReference;
import org.hibernate.sql.ast.tree.expression.Expression;
import org.hibernate.sql.ast.tree.expression.JdbcParameter;
import org.hibernate.sql.ast.tree.expression.Literal;
import org.hibernate.sql.ast.tree.expression.Over;
import org.hibernate.sql.ast.tree.expression.QueryTransformer;
import org.hibernate.sql.ast.tree.expression.SqlSelectionExpression;
import org.hibernate.sql.ast.tree.expression.SqlTuple;
import org.hibernate.sql.ast.tree.from.QueryPartTableGroup;
import org.hibernate.sql.ast.tree.predicate.ComparisonPredicate;
import org.hibernate.sql.ast.tree.predicate.Predicate;
import org.hibernate.sql.ast.tree.select.QuerySpec;
import org.hibernate.sql.ast.tree.select.SelectClause;
import org.hibernate.sql.ast.tree.select.SortSpecification;
import org.hibernate.sql.results.internal.ResolvedSqlSelection;
import org.hibernate.type.BasicType;
/**
* Since the query spec will render a hypothetical set window function instead of an aggregate,
* the following query transformer will wrap the query spec and apply aggregation in the outer query.
* We can do this because these functions can only be used in the select clause.
* A hypothetical set aggregate function like e.g. "rank" returns the rank of a passed value within
* the ordered set as defined through the WITHIN GROUP clause.
* When used as window function, the function provides the rank of the current row within the ordered set
* as defined for the window frame through the OVER clause, but does not do aggregation.
* The aggregation effect can be achieved by:
* 1. Selecting the elements by which the ordered set is sorted
* 2. In the outer query, add a comparison predicate `function_args`=`sort_expressions`
* 3. Use an arbitrary row produced by the inner query by using e.g. the "min" function
*
* The following query
*
* <code>
* select rank(5) within group (order by e.num)
* from (values (1), (2), (5)) e(num)
* </code>
*
* can be rewritten to
*
* <code>
* select min(t.c1) from (
* select rank() over (order by e.num), e.num
* from (values (1), (2), (5)) e(num)
* ) t(c1,c2)
* where t.c2 = 5
* </code>
*
* @author Christian Beikov
*/
public class AggregateWindowEmulationQueryTransformer implements QueryTransformer {
private final Over<Object> windowFunction;
private final List<SortSpecification> withinGroup;
private final List<SqlAstNode> arguments;
public AggregateWindowEmulationQueryTransformer(
Over<Object> windowFunction,
List<SortSpecification> withinGroup,
List<SqlAstNode> arguments) {
this.windowFunction = windowFunction;
this.withinGroup = withinGroup;
this.arguments = arguments;
}
@Override
public QuerySpec transform(
CteContainer cteContainer,
QuerySpec querySpec,
SqmToSqlAstConverter converter) {
final SessionFactoryImplementor factory = converter.getCreationContext()
.getSessionFactory();
final QuerySpec outerQuerySpec = new QuerySpec( querySpec.isRoot() );
final String identifierVariable = "hhh_";
final NavigablePath navigablePath = new NavigablePath(
identifierVariable,
identifierVariable
);
final SelectClause selectClause = outerQuerySpec.getSelectClause();
final QuerySpec subQuerySpec = querySpec.asSubQuery();
final SelectClause subSelectClause = subQuerySpec.getSelectClause();
final List<SqlSelection> subSelections = subSelectClause.getSqlSelections();
final List<String> columnNames = new ArrayList<>( subSelections.size() );
// A map to find the select item position for an expression
// which is needed to decide if we need to introduce synthetic select items
// for group by items, since these group by items are migrated to the outer query
final Map<Expression, Integer> selectionMapping = new HashMap<>( subSelections.size() );
// Create the expressions/selections for the outer query and the columnNames list
// for the QueryPartTableGroup within which the sub query spec is embedded
for ( int i = 0; i < subSelections.size(); i++ ) {
final BasicValuedMapping mapping = (BasicValuedMapping) subSelections.get( i )
.getExpressionType();
final String columnName = "col" + i;
final ColumnReference columnReference = new ColumnReference(
identifierVariable,
columnName,
false,
null,
null,
mapping.getJdbcMapping(),
factory
);
final Expression expression = subSelections.get( i ).getExpression();
final Expression finalExpression;
if ( expression == windowFunction ) {
finalExpression = new SelfRenderingAggregateFunctionSqlAstExpression(
"min",
(sqlAppender, sqlAstArguments, walker1) -> {
sqlAppender.appendSql( "min(" );
sqlAstArguments.get( 0 ).accept( walker1 );
sqlAppender.append( ')' );
},
Collections.singletonList( columnReference ),
null,
(ReturnableType<?>) mapping.getMappedType(),
expression.getExpressionType()
);
}
else {
finalExpression = columnReference;
selectionMapping.put( expression, i );
}
columnNames.add( columnName );
selectClause.addSqlSelection(
new ResolvedSqlSelection(
i + 1,
i,
finalExpression,
(BasicType<Object>) mapping.getJdbcMapping()
)
);
}
// Migrate the group by clause to the outer query
// and push group by expressions into the partition by clause of the window function
final List<Expression> groupByExpressions = new ArrayList<>(
subQuerySpec.getGroupByClauseExpressions().size()
);
for ( Expression groupByClauseExpression : subQuerySpec.getGroupByClauseExpressions() ) {
final Expression realExpression;
final Expression outerGroupByExpression;
if ( groupByClauseExpression instanceof SqlSelectionExpression ) {
final SqlSelection selection = ( (SqlSelectionExpression) groupByClauseExpression ).getSelection();
outerGroupByExpression = new SqlSelectionExpression(
selectClause.getSqlSelections().get( selection.getValuesArrayPosition() )
);
realExpression = selection.getExpression();
}
else {
if ( groupByClauseExpression instanceof SqmPathInterpretation<?> ) {
realExpression = ( (SqmPathInterpretation<?>) groupByClauseExpression ).getSqlExpression();
}
else {
realExpression = groupByClauseExpression;
}
final Integer position = selectionMapping.get( realExpression );
if ( position == null ) {
// Group by something that has no corresponding selection item,
// so we need to introduce an intermediate selection item
final int valuesPosition = selectClause.getSqlSelections().size();
final String columnName = "col" + valuesPosition;
final JdbcMapping jdbcMapping = realExpression.getExpressionType()
.getJdbcMappings()
.get( 0 );
final ColumnReference columnReference = new ColumnReference(
identifierVariable,
columnName,
false,
null,
null,
jdbcMapping,
factory
);
final int subValuesPosition = subSelectClause.getSqlSelections().size();
final SqlSelection subSelection = new ResolvedSqlSelection(
subValuesPosition + 1,
subValuesPosition,
realExpression,
(BasicType<Object>) jdbcMapping
);
columnNames.add( columnName );
subSelectClause.addSqlSelection( subSelection );
outerGroupByExpression = columnReference;
selectionMapping.put( realExpression, subValuesPosition );
}
else {
outerGroupByExpression = new SqlSelectionExpression(
selectClause.getSqlSelections().get( position )
);
}
}
windowFunction.getPartitions().add( realExpression );
groupByExpressions.add( outerGroupByExpression );
}
outerQuerySpec.setGroupByClauseExpressions( groupByExpressions );
subQuerySpec.setGroupByClauseExpressions( null );
// Migrate the having clause to the outer query
if ( subQuerySpec.getHavingClauseRestrictions() != null ) {
final Predicate predicate = new ExpressionReplacementWalker() {
@Override
protected <X extends SqlAstNode> X replaceExpression(X expression) {
if ( expression instanceof Literal || expression instanceof JdbcParameter ) {
return expression;
}
final Expression outerExpression;
if ( expression instanceof SqlSelectionExpression ) {
final SqlSelection selection = ( (SqlSelectionExpression) expression ).getSelection();
outerExpression = selectClause.getSqlSelections()
.get( selection.getValuesArrayPosition() )
.getExpression();
}
else {
final Expression realExpression;
if ( expression instanceof SqmPathInterpretation<?> ) {
realExpression = ( (SqmPathInterpretation<?>) expression ).getSqlExpression();
}
else {
realExpression = (Expression) expression;
}
final Integer position = selectionMapping.get( realExpression );
if ( position == null ) {
// An expression that has no corresponding selection item,
// so we need to introduce an intermediate selection item
final int valuesPosition = selectClause.getSqlSelections().size();
final String columnName = "col" + valuesPosition;
final JdbcMapping jdbcMapping = realExpression.getExpressionType()
.getJdbcMappings()
.get( 0 );
final ColumnReference columnReference = new ColumnReference(
identifierVariable,
columnName,
false,
null,
null,
jdbcMapping,
factory
);
final int subValuesPosition = subSelectClause.getSqlSelections().size();
final SqlSelection subSelection = new ResolvedSqlSelection(
subValuesPosition + 1,
subValuesPosition,
realExpression,
(BasicType<Object>) jdbcMapping
);
columnNames.add( columnName );
subSelectClause.addSqlSelection( subSelection );
outerExpression = columnReference;
selectionMapping.put( realExpression, subValuesPosition );
}
else {
outerExpression = selectClause.getSqlSelections().get( position )
.getExpression();
}
}
return (X) outerExpression;
}
}.replaceExpressions( subQuerySpec.getHavingClauseRestrictions() );
outerQuerySpec.setHavingClauseRestrictions( predicate );
subQuerySpec.setHavingClauseRestrictions( null );
}
// Migrate the order by clause to the outer query
if ( subQuerySpec.hasSortSpecifications() ) {
for ( SortSpecification sortSpecification : subQuerySpec.getSortSpecifications() ) {
final Expression sortExpression = sortSpecification.getSortExpression();
final Expression outerSortExpression;
if ( sortExpression instanceof SqlSelectionExpression ) {
final SqlSelection selection = ( (SqlSelectionExpression) sortExpression ).getSelection();
outerSortExpression = new SqlSelectionExpression(
selectClause.getSqlSelections()
.get( selection.getValuesArrayPosition() )
);
}
else {
final Expression realExpression;
if ( sortExpression instanceof SqmPathInterpretation<?> ) {
realExpression = ( (SqmPathInterpretation<?>) sortExpression ).getSqlExpression();
}
else {
realExpression = sortExpression;
}
final Integer position = selectionMapping.get( realExpression );
if ( position == null ) {
// Group by something that has no corresponding selection item,
// so we need to introduce an intermediate selection item
final int valuesPosition = selectClause.getSqlSelections().size();
final String columnName = "col" + valuesPosition;
final JdbcMapping jdbcMapping = realExpression.getExpressionType()
.getJdbcMappings()
.get( 0 );
final ColumnReference columnReference = new ColumnReference(
identifierVariable,
columnName,
false,
null,
null,
jdbcMapping,
factory
);
final int subValuesPosition = subSelectClause.getSqlSelections().size();
final SqlSelection subSelection = new ResolvedSqlSelection(
subValuesPosition + 1,
subValuesPosition,
realExpression,
(BasicType<Object>) jdbcMapping
);
columnNames.add( columnName );
subSelectClause.addSqlSelection( subSelection );
outerSortExpression = columnReference;
selectionMapping.put( realExpression, subValuesPosition );
}
else {
outerSortExpression = new SqlSelectionExpression(
selectClause.getSqlSelections().get( position )
);
}
}
outerQuerySpec.addSortSpecification(
new SortSpecification(
outerSortExpression,
sortSpecification.getSortOrder(),
sortSpecification.getNullPrecedence()
)
);
}
subQuerySpec.getSortSpecifications().clear();
}
// We need to add selection items for the expressions we order by to the sub query spec.
final int selectionOffset = columnNames.size();
// Collect the sorting column references so we can apply the filter later
final List<ColumnReference> sortingColumns = new ArrayList<>( withinGroup.size() );
for ( int i = 0; i < withinGroup.size(); i++ ) {
final int valueIndex = selectionOffset + i;
final Expression sortExpression = withinGroup.get( i ).getSortExpression();
final BasicValuedMapping mapping = (BasicValuedMapping) sortExpression.getExpressionType();
final String columnName = "col" + valueIndex;
final int oldValueIndex = subSelectClause.getSqlSelections().size();
columnNames.add( columnName );
subSelectClause.addSqlSelection(
new ResolvedSqlSelection(
oldValueIndex + 1,
oldValueIndex,
sortExpression,
(BasicType<Object>) mapping.getJdbcMapping()
)
);
sortingColumns.add(
new ColumnReference(
identifierVariable,
columnName,
false,
null,
null,
mapping.getJdbcMapping(),
factory
)
);
}
if ( arguments != null ) {
// Hypothetical set aggregate functions usually provide some rank based on a value
// i.e. which rank does the value 5 have when ordering by a column ascending
// So we add a filter to the outer query so we can extract the rank
switch ( arguments.size() ) {
case 0:
break;
case 1:
outerQuerySpec.applyPredicate(
new ComparisonPredicate(
sortingColumns.get( 0 ),
ComparisonOperator.EQUAL,
(Expression) arguments.get( 0 )
)
);
break;
default:
outerQuerySpec.applyPredicate(
new ComparisonPredicate(
new SqlTuple( sortingColumns, null ),
ComparisonOperator.EQUAL,
new SqlTuple(
(List<? extends Expression>) (List<?>) arguments,
null
)
)
);
}
}
final QueryPartTableGroup queryPartTableGroup = new QueryPartTableGroup(
navigablePath,
null,
subQuerySpec,
identifierVariable,
columnNames,
false,
true,
factory
);
outerQuerySpec.getFromClause().addRoot( queryPartTableGroup );
// Migrate the offset/fetch clause
outerQuerySpec.setOffsetClauseExpression( subQuerySpec.getOffsetClauseExpression() );
outerQuerySpec.setFetchClauseExpression(
subQuerySpec.getFetchClauseExpression(),
subQuerySpec.getFetchClauseType()
);
subQuerySpec.setOffsetClauseExpression( null );
subQuerySpec.setFetchClauseExpression( null, null );
return outerQuerySpec;
}
}

View File

@ -43,14 +43,14 @@ public class CommonFunctionFactory {
private final BasicType<Date> dateType;
private final BasicType<Date> timeType;
private final BasicType<Date> timestampType;
private final SqmFunctionRegistry functionRegistry;
private final TypeConfiguration typeConfiguration;
public CommonFunctionFactory(QueryEngine queryEngine) {
functionRegistry = queryEngine.getSqmFunctionRegistry();
typeConfiguration = queryEngine.getTypeConfiguration();
BasicTypeRegistry basicTypeRegistry = typeConfiguration.getBasicTypeRegistry();
dateType = basicTypeRegistry.resolve(StandardBasicTypes.DATE);
timeType = basicTypeRegistry.resolve(StandardBasicTypes.TIME);
@ -1778,6 +1778,17 @@ public class CommonFunctionFactory {
);
}
public void inverseDistributionOrderedSetAggregates_windowEmulation() {
functionRegistry.register(
"percentile_cont",
new InverseDistributionWindowEmulation( "percentile_cont", NUMERIC, typeConfiguration )
);
functionRegistry.register(
"percentile_disc",
new InverseDistributionWindowEmulation( "percentile_disc", NUMERIC, typeConfiguration )
);
}
public void hypotheticalOrderedSetAggregates() {
functionRegistry.register(
"rank",
@ -1797,6 +1808,25 @@ public class CommonFunctionFactory {
);
}
public void hypotheticalOrderedSetAggregates_windowEmulation() {
functionRegistry.register(
"rank",
new HypotheticalSetWindowEmulation( "rank", StandardBasicTypes.LONG, typeConfiguration )
);
functionRegistry.register(
"dense_rank",
new HypotheticalSetWindowEmulation( "dense_rank", StandardBasicTypes.LONG, typeConfiguration )
);
functionRegistry.register(
"percent_rank",
new HypotheticalSetWindowEmulation( "percent_rank", StandardBasicTypes.DOUBLE, typeConfiguration )
);
functionRegistry.register(
"cume_dist",
new HypotheticalSetWindowEmulation( "cume_dist", StandardBasicTypes.DOUBLE, typeConfiguration )
);
}
public void math() {
functionRegistry.namedDescriptorBuilder( "round" )
// To avoid truncating to a specific data type, we default to using the argument type

View File

@ -0,0 +1,123 @@
/*
* 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.ArrayList;
import java.util.Collections;
import java.util.List;
import org.hibernate.query.ReturnableType;
import org.hibernate.query.spi.QueryEngine;
import org.hibernate.query.sqm.function.SelfRenderingFunctionSqlAstExpression;
import org.hibernate.query.sqm.function.SelfRenderingOrderedSetAggregateFunctionSqlAstExpression;
import org.hibernate.query.sqm.function.SelfRenderingSqmAggregateFunction;
import org.hibernate.query.sqm.function.SelfRenderingSqmOrderedSetAggregateFunction;
import org.hibernate.query.sqm.produce.function.ArgumentsValidator;
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.query.sqm.tree.select.SqmSortSpecification;
import org.hibernate.sql.ast.Clause;
import org.hibernate.sql.ast.tree.SqlAstNode;
import org.hibernate.sql.ast.tree.expression.Expression;
import org.hibernate.sql.ast.tree.expression.Over;
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 HypotheticalSetWindowEmulation extends HypotheticalSetFunction {
public HypotheticalSetWindowEmulation(String name, BasicTypeReference<?> returnType, TypeConfiguration typeConfiguration) {
super(
name,
returnType,
typeConfiguration
);
}
@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
public Expression convertToSqlAst(SqmToSqlAstConverter walker) {
final Clause currentClause = walker.getCurrentClauseStack().getCurrent();
if ( currentClause == Clause.OVER ) {
return super.convertToSqlAst( walker );
}
else if ( currentClause != Clause.SELECT ) {
throw new IllegalArgumentException( "Can't emulate [" + getName() + "] in clause " + currentClause + ". Only the SELECT clause is supported!" );
}
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.getWithinGroup() == null ) {
withinGroup = Collections.emptyList();
}
else {
walker.getCurrentClauseStack().push( Clause.ORDER );
try {
final List<SqmSortSpecification> sortSpecifications = this.getWithinGroup().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();
}
}
final SelfRenderingFunctionSqlAstExpression function = new SelfRenderingOrderedSetAggregateFunctionSqlAstExpression(
getFunctionName(),
getRenderingSupport(),
Collections.emptyList(),
getFilter() == null ? null : (Predicate) getFilter().accept( walker ),
Collections.emptyList(),
resultType,
getMappingModelExpressible( walker, resultType )
);
final Over<Object> windowFunction = new Over<>( function, new ArrayList<>(), withinGroup );
walker.registerQueryTransformer(
new AggregateWindowEmulationQueryTransformer( windowFunction, withinGroup, arguments )
);
return windowFunction;
}
};
}
}

View File

@ -57,62 +57,13 @@ public class InverseDistributionFunction extends AbstractSqmSelfRenderingFunctio
ReturnableType<T> impliedResultType,
QueryEngine queryEngine,
TypeConfiguration typeConfiguration) {
return new SelfRenderingSqmOrderedSetAggregateFunction<>(
this,
this,
return new SelfRenderingInverseDistributionFunction<>(
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;
}
};
queryEngine
);
}
@Override
@ -174,4 +125,71 @@ public class InverseDistributionFunction extends AbstractSqmSelfRenderingFunctio
}
}
protected class SelfRenderingInverseDistributionFunction<T> extends SelfRenderingSqmOrderedSetAggregateFunction<T> {
private final SqmOrderByClause withinGroupClause;
public SelfRenderingInverseDistributionFunction(
List<? extends SqmTypedNode<?>> arguments,
SqmPredicate filter,
SqmOrderByClause withinGroupClause,
ReturnableType<T> impliedResultType, QueryEngine queryEngine) {
super(
InverseDistributionFunction.this,
InverseDistributionFunction.this,
arguments,
filter,
withinGroupClause,
impliedResultType,
InverseDistributionFunction.this.getArgumentsValidator(),
InverseDistributionFunction.this.getReturnTypeResolver(),
queryEngine.getCriteriaBuilder(),
InverseDistributionFunction.this.getName()
);
this.withinGroupClause = withinGroupClause;
}
@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;
}
}
}

View File

@ -0,0 +1,118 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
*/
package org.hibernate.dialect.function;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.hibernate.query.ReturnableType;
import org.hibernate.query.spi.QueryEngine;
import org.hibernate.query.sqm.function.SelfRenderingFunctionSqlAstExpression;
import org.hibernate.query.sqm.function.SelfRenderingOrderedSetAggregateFunctionSqlAstExpression;
import org.hibernate.query.sqm.function.SelfRenderingSqmAggregateFunction;
import org.hibernate.query.sqm.produce.function.ArgumentsValidator;
import org.hibernate.query.sqm.produce.function.FunctionParameterType;
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.query.sqm.tree.select.SqmSortSpecification;
import org.hibernate.sql.ast.Clause;
import org.hibernate.sql.ast.tree.SqlAstNode;
import org.hibernate.sql.ast.tree.expression.Expression;
import org.hibernate.sql.ast.tree.expression.Over;
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 InverseDistributionWindowEmulation extends InverseDistributionFunction {
public InverseDistributionWindowEmulation(String name, FunctionParameterType parameterType, TypeConfiguration typeConfiguration) {
super(
name,
parameterType,
typeConfiguration
);
}
@Override
public <T> SelfRenderingSqmAggregateFunction<T> generateSqmOrderedSetAggregateFunctionExpression(
List<? extends SqmTypedNode<?>> arguments,
SqmPredicate filter,
SqmOrderByClause withinGroupClause,
ReturnableType<T> impliedResultType,
QueryEngine queryEngine,
TypeConfiguration typeConfiguration) {
return new SelfRenderingInverseDistributionFunction<>(
arguments,
filter,
withinGroupClause,
impliedResultType,
queryEngine
) {
@Override
public Expression convertToSqlAst(SqmToSqlAstConverter walker) {
final Clause currentClause = walker.getCurrentClauseStack().getCurrent();
if ( currentClause == Clause.OVER ) {
return super.convertToSqlAst( walker );
}
else if ( currentClause != Clause.SELECT ) {
throw new IllegalArgumentException( "Can't emulate [" + getName() + "] in clause " + currentClause + ". Only the SELECT clause is supported!" );
}
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.getWithinGroup() == null ) {
withinGroup = Collections.emptyList();
}
else {
walker.getCurrentClauseStack().push( Clause.ORDER );
try {
final List<SqmSortSpecification> sortSpecifications = this.getWithinGroup().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();
}
}
final SelfRenderingFunctionSqlAstExpression function = new SelfRenderingOrderedSetAggregateFunctionSqlAstExpression(
getFunctionName(),
getRenderingSupport(),
arguments,
getFilter() == null ? null : (Predicate) getFilter().accept( walker ),
withinGroup,
resultType,
getMappingModelExpressible( walker, resultType )
);
final Over<Object> windowFunction = new Over<>( function, new ArrayList<>(), Collections.emptyList() );
walker.registerQueryTransformer(
new AggregateWindowEmulationQueryTransformer( windowFunction, withinGroup, null )
);
return windowFunction;
}
};
}
}

View File

@ -272,7 +272,7 @@ public class CteInsertHandler implements InsertHandler {
new SqlSelectionImpl(
1,
0,
new Over(
new Over<>(
new SelfRenderingFunctionSqlAstExpression(
"row_number",
(appender, args, walker) -> appender.appendSql(
@ -985,7 +985,7 @@ public class CteInsertHandler implements InsertHandler {
new SqlSelectionImpl(
1,
0,
new Over(
new Over<>(
new SelfRenderingFunctionSqlAstExpression(
"row_number",
(appender, args, walker) -> appender.appendSql(

View File

@ -222,7 +222,7 @@ public class TableBasedInsertHandler implements InsertHandler {
new SqlSelectionImpl(
1,
0,
new Over(
new Over<>(
new SelfRenderingFunctionSqlAstExpression(
"row_number",
(appender, args, walker) -> appender.appendSql(

View File

@ -1296,7 +1296,7 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
identifierSelection = new SqlSelectionImpl(
1,
0,
new Over(
new Over<>(
new SelfRenderingFunctionSqlAstExpression(
"row_number",
(appender, args, walker) -> appender.appendSql( "row_number()" ),

View File

@ -123,7 +123,7 @@ public interface SqlAstWalker {
void visitDistinct(Distinct distinct);
void visitOverflow(Overflow distinct);
void visitOverflow(Overflow overflow);
void visitStar(Star star);
@ -143,7 +143,7 @@ public interface SqlAstWalker {
void visitSummarization(Summarization every);
void visitOver(Over over);
void visitOver(Over<?> over);
void visitSelfRenderingExpression(SelfRenderingExpression expression);

View File

@ -101,6 +101,7 @@ import org.hibernate.sql.ast.tree.expression.JdbcParameter;
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.OrderedSetAggregateFunctionExpression;
import org.hibernate.sql.ast.tree.expression.Over;
import org.hibernate.sql.ast.tree.expression.Overflow;
import org.hibernate.sql.ast.tree.expression.QueryLiteral;
@ -3242,8 +3243,17 @@ public abstract class AbstractSqlAstTranslator<T extends JdbcOperation> implemen
}
@Override
public void visitOver(Over over) {
over.getExpression().accept( this );
public void visitOver(Over<?> over) {
final Expression overExpression = over.getExpression();
overExpression.accept( this );
final boolean orderedSetAggregate;
if ( overExpression instanceof OrderedSetAggregateFunctionExpression ) {
final OrderedSetAggregateFunctionExpression expression = (OrderedSetAggregateFunctionExpression) overExpression;
orderedSetAggregate = expression.getWithinGroup() != null && !expression.getWithinGroup().isEmpty();
}
else {
orderedSetAggregate = false;
}
visitOverClause(
over.getPartitions(),
over.getOrderList(),
@ -3252,7 +3262,8 @@ public abstract class AbstractSqlAstTranslator<T extends JdbcOperation> implemen
over.getStartExpression(),
over.getEndKind(),
over.getEndExpression(),
over.getExclusion()
over.getExclusion(),
orderedSetAggregate
);
}
@ -3267,7 +3278,8 @@ public abstract class AbstractSqlAstTranslator<T extends JdbcOperation> implemen
null,
Over.FrameKind.CURRENT_ROW,
null,
Over.FrameExclusion.NO_OTHERS
Over.FrameExclusion.NO_OTHERS,
false
);
}
@ -3279,12 +3291,15 @@ public abstract class AbstractSqlAstTranslator<T extends JdbcOperation> implemen
Expression startExpression,
Over.FrameKind endKind,
Expression endExpression,
Over.FrameExclusion exclusion) {
Over.FrameExclusion exclusion,
boolean orderedSetAggregate) {
try {
clauseStack.push( Clause.OVER );
appendSql( " over(" );
visitPartitionByClause( partitionExpressions );
renderOrderBy( !partitionExpressions.isEmpty(), sortSpecifications );
if ( !orderedSetAggregate ) {
renderOrderBy( !partitionExpressions.isEmpty(), sortSpecifications );
}
if ( mode == Over.FrameMode.ROWS && startKind == Over.FrameKind.UNBOUNDED_PRECEDING && endKind == Over.FrameKind.CURRENT_ROW && exclusion == Over.FrameExclusion.NO_OTHERS ) {
// This is the default, so we don't need to render anything
}

View File

@ -232,7 +232,7 @@ public class AbstractSqlAstWalker implements SqlAstWalker {
}
@Override
public void visitOver(Over over) {
public void visitOver(Over<?> over) {
over.getExpression().accept( this );
for ( Expression partition : over.getPartitions() ) {
partition.accept( this );

View File

@ -100,7 +100,7 @@ public class AggregateFunctionChecker extends AbstractSqlAstWalker {
}
@Override
public void visitOver(Over over) {
public void visitOver(Over<?> over) {
// Only need to visit the expression over which the window is created as the window definition can't have aggregates
// If the expression is an aggregate function, this means the aggregate is used as window function, which is fine
// We only care about actually aggregating functions, which might be an argument of this function though

View File

@ -0,0 +1,566 @@
/*
* 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.spi;
import java.util.ArrayList;
import java.util.List;
import org.hibernate.persister.internal.SqlFragmentPredicate;
import org.hibernate.query.sqm.tree.expression.Conversion;
import org.hibernate.sql.ast.SqlAstWalker;
import org.hibernate.sql.ast.tree.SqlAstNode;
import org.hibernate.sql.ast.tree.delete.DeleteStatement;
import org.hibernate.sql.ast.tree.expression.Any;
import org.hibernate.sql.ast.tree.expression.BinaryArithmeticExpression;
import org.hibernate.sql.ast.tree.expression.CaseSearchedExpression;
import org.hibernate.sql.ast.tree.expression.CaseSimpleExpression;
import org.hibernate.sql.ast.tree.expression.CastTarget;
import org.hibernate.sql.ast.tree.expression.Collation;
import org.hibernate.sql.ast.tree.expression.ColumnReference;
import org.hibernate.sql.ast.tree.expression.ConvertedQueryLiteral;
import org.hibernate.sql.ast.tree.expression.Distinct;
import org.hibernate.sql.ast.tree.expression.Duration;
import org.hibernate.sql.ast.tree.expression.DurationUnit;
import org.hibernate.sql.ast.tree.expression.EntityTypeLiteral;
import org.hibernate.sql.ast.tree.expression.Every;
import org.hibernate.sql.ast.tree.expression.Expression;
import org.hibernate.sql.ast.tree.expression.ExtractUnit;
import org.hibernate.sql.ast.tree.expression.Format;
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;
import org.hibernate.sql.ast.tree.expression.SqlTuple;
import org.hibernate.sql.ast.tree.expression.Star;
import org.hibernate.sql.ast.tree.expression.Summarization;
import org.hibernate.sql.ast.tree.expression.TrimSpecification;
import org.hibernate.sql.ast.tree.expression.UnaryOperation;
import org.hibernate.sql.ast.tree.from.FromClause;
import org.hibernate.sql.ast.tree.from.FunctionTableReference;
import org.hibernate.sql.ast.tree.from.NamedTableReference;
import org.hibernate.sql.ast.tree.from.QueryPartTableReference;
import org.hibernate.sql.ast.tree.from.TableGroup;
import org.hibernate.sql.ast.tree.from.TableGroupJoin;
import org.hibernate.sql.ast.tree.from.TableReferenceJoin;
import org.hibernate.sql.ast.tree.from.ValuesTableReference;
import org.hibernate.sql.ast.tree.insert.InsertStatement;
import org.hibernate.sql.ast.tree.predicate.BetweenPredicate;
import org.hibernate.sql.ast.tree.predicate.BooleanExpressionPredicate;
import org.hibernate.sql.ast.tree.predicate.ComparisonPredicate;
import org.hibernate.sql.ast.tree.predicate.ExistsPredicate;
import org.hibernate.sql.ast.tree.predicate.FilterPredicate;
import org.hibernate.sql.ast.tree.predicate.GroupedPredicate;
import org.hibernate.sql.ast.tree.predicate.InListPredicate;
import org.hibernate.sql.ast.tree.predicate.InSubQueryPredicate;
import org.hibernate.sql.ast.tree.predicate.Junction;
import org.hibernate.sql.ast.tree.predicate.LikePredicate;
import org.hibernate.sql.ast.tree.predicate.NegatedPredicate;
import org.hibernate.sql.ast.tree.predicate.NullnessPredicate;
import org.hibernate.sql.ast.tree.predicate.Predicate;
import org.hibernate.sql.ast.tree.predicate.SelfRenderingPredicate;
import org.hibernate.sql.ast.tree.select.QueryGroup;
import org.hibernate.sql.ast.tree.select.QueryPart;
import org.hibernate.sql.ast.tree.select.QuerySpec;
import org.hibernate.sql.ast.tree.select.SelectClause;
import org.hibernate.sql.ast.tree.select.SelectStatement;
import org.hibernate.sql.ast.tree.select.SortSpecification;
import org.hibernate.sql.ast.tree.update.Assignment;
import org.hibernate.sql.ast.tree.update.UpdateStatement;
/**
* A walker that allows to replace expressions.
*
* @author Christian Beikov
*/
public class ExpressionReplacementWalker implements SqlAstWalker {
/**
* To avoid introducing a walker/visitor that returns an object,
* we use a heap variable to transfer the return value.
*/
private SqlAstNode returnedNode;
public final <X extends SqlAstNode> X replaceExpressions(X expression) {
expression.accept( this );
//noinspection unchecked
return (X) returnedNode;
}
protected <X extends SqlAstNode> X replaceExpression(X expression) {
return expression;
}
private void doReplaceExpression(SqlAstNode expression) {
returnedNode = replaceExpression( expression );
}
@Override
public void visitColumnReference(ColumnReference columnReference) {
doReplaceExpression( columnReference );
}
@Override
public void visitExtractUnit(ExtractUnit extractUnit) {
doReplaceExpression( extractUnit );
}
@Override
public void visitFormat(Format format) {
doReplaceExpression( format );
}
@Override
public void visitDistinct(Distinct distinct) {
doReplaceExpression( distinct );
}
@Override
public void visitOverflow(Overflow overflow) {
doReplaceExpression( overflow );
}
@Override
public void visitStar(Star star) {
doReplaceExpression( star );
}
@Override
public void visitTrimSpecification(TrimSpecification trimSpecification) {
doReplaceExpression( trimSpecification );
}
@Override
public void visitCastTarget(CastTarget castTarget) {
doReplaceExpression( castTarget );
}
@Override
public void visitBinaryArithmeticExpression(BinaryArithmeticExpression arithmeticExpression) {
doReplaceExpression( arithmeticExpression );
}
@Override
public void visitCaseSearchedExpression(CaseSearchedExpression caseSearchedExpression) {
doReplaceExpression( caseSearchedExpression );
}
@Override
public void visitCaseSimpleExpression(CaseSimpleExpression caseSimpleExpression) {
doReplaceExpression( caseSimpleExpression );
}
@Override
public void visitAny(Any any) {
doReplaceExpression( any );
}
@Override
public void visitEvery(Every every) {
doReplaceExpression( every );
}
@Override
public void visitSummarization(Summarization every) {
doReplaceExpression( every );
}
@Override
public void visitOver(Over<?> over) {
doReplaceExpression( over );
}
@Override
public void visitSelfRenderingExpression(SelfRenderingExpression expression) {
doReplaceExpression( expression );
}
@Override
public void visitSqlSelectionExpression(SqlSelectionExpression expression) {
doReplaceExpression( expression );
}
@Override
public void visitEntityTypeLiteral(EntityTypeLiteral expression) {
doReplaceExpression( expression );
}
@Override
public void visitTuple(SqlTuple tuple) {
doReplaceExpression( tuple );
}
@Override
public void visitCollation(Collation collation) {
doReplaceExpression( collation );
}
@Override
public void visitParameter(JdbcParameter jdbcParameter) {
doReplaceExpression( jdbcParameter );
}
@Override
public void visitJdbcLiteral(JdbcLiteral<?> jdbcLiteral) {
doReplaceExpression( jdbcLiteral );
}
@Override
public void visitQueryLiteral(QueryLiteral<?> queryLiteral) {
doReplaceExpression( queryLiteral );
}
@Override
public void acceptConvertedQueryLiteral(ConvertedQueryLiteral<?, ?> convertedQueryLiteral) {
doReplaceExpression( convertedQueryLiteral );
}
@Override
public void visitUnaryOperationExpression(UnaryOperation unaryOperationExpression) {
doReplaceExpression( unaryOperationExpression );
}
@Override
public void visitModifiedSubQueryExpression(ModifiedSubQueryExpression expression) {
doReplaceExpression( expression );
}
@Override
public void visitDurationUnit(DurationUnit durationUnit) {
doReplaceExpression( durationUnit );
}
@Override
public void visitDuration(Duration duration) {
doReplaceExpression( duration );
}
@Override
public void visitConversion(Conversion conversion) {
doReplaceExpression( conversion );
}
@Override
public void visitBooleanExpressionPredicate(BooleanExpressionPredicate booleanExpressionPredicate) {
doReplaceExpression( booleanExpressionPredicate );
}
@Override
public void visitSqlFragmentPredicate(SqlFragmentPredicate predicate) {
doReplaceExpression( predicate );
}
@Override
public void visitBetweenPredicate(BetweenPredicate betweenPredicate) {
final Expression expression = replaceExpression( betweenPredicate.getExpression() );
final Expression lowerBound = replaceExpression( betweenPredicate.getLowerBound() );
final Expression upperBound = replaceExpression( betweenPredicate.getUpperBound() );
if ( expression != betweenPredicate.getExpression()
|| lowerBound != betweenPredicate.getLowerBound()
|| upperBound != betweenPredicate.getUpperBound() ) {
returnedNode = new BetweenPredicate(
expression,
lowerBound,
upperBound,
betweenPredicate.isNegated(),
betweenPredicate.getExpressionType()
);
}
else {
returnedNode = betweenPredicate;
}
}
@Override
public void visitGroupedPredicate(GroupedPredicate groupedPredicate) {
groupedPredicate.getSubPredicate().accept( this );
if ( returnedNode != groupedPredicate.getSubPredicate() ) {
returnedNode = new GroupedPredicate( (Predicate) returnedNode );
}
else {
returnedNode = groupedPredicate;
}
}
@Override
public void visitInListPredicate(InListPredicate inListPredicate) {
final Expression testExpression = replaceExpression( inListPredicate.getTestExpression() );
List<Expression> items = null;
final List<Expression> listExpressions = inListPredicate.getListExpressions();
for ( int i = 0; i < listExpressions.size(); i++ ) {
final Expression listExpression = listExpressions.get( i );
final Expression newListExpression = replaceExpression( listExpression );
if ( newListExpression != listExpression ) {
if ( items == null ) {
items = new ArrayList<>( listExpressions );
}
items.set( i, newListExpression );
}
}
if ( testExpression != inListPredicate.getTestExpression() || items != null ) {
returnedNode = new InListPredicate(
testExpression,
items == null ? listExpressions : items,
inListPredicate.isNegated(),
inListPredicate.getExpressionType()
);
}
else {
returnedNode = inListPredicate;
}
}
@Override
public void visitInSubQueryPredicate(InSubQueryPredicate inSubQueryPredicate) {
final Expression testExpression = replaceExpression( inSubQueryPredicate.getTestExpression() );
final QueryPart subQuery = replaceExpression( inSubQueryPredicate.getSubQuery() );
if ( testExpression != inSubQueryPredicate.getTestExpression()
|| subQuery != inSubQueryPredicate.getSubQuery() ) {
returnedNode = new InSubQueryPredicate(
testExpression,
subQuery,
inSubQueryPredicate.isNegated(),
inSubQueryPredicate.getExpressionType()
);
}
else {
returnedNode = inSubQueryPredicate;
}
}
@Override
public void visitExistsPredicate(ExistsPredicate existsPredicate) {
final QueryPart queryPart = replaceExpression( existsPredicate.getExpression() );
if ( queryPart != existsPredicate.getExpression() ) {
returnedNode = new ExistsPredicate(
queryPart,
existsPredicate.isNegated(),
existsPredicate.getExpressionType()
);
}
else {
returnedNode = existsPredicate;
}
}
@Override
public void visitJunction(Junction junction) {
final List<Predicate> predicates = junction.getPredicates();
List<Predicate> newPredicates = null;
for ( int i = 0; i < predicates.size(); i++ ) {
predicates.get( i ).accept( this );
if ( returnedNode != predicates.get( i ) ) {
if ( newPredicates == null ) {
newPredicates = new ArrayList<>( predicates );
}
newPredicates.set( i, (Predicate) returnedNode );
}
}
if ( newPredicates != null ) {
returnedNode = new Junction(
junction.getNature(),
newPredicates,
junction.getExpressionType()
);
}
else {
returnedNode = junction;
}
}
@Override
public void visitLikePredicate(LikePredicate likePredicate) {
final Expression matchExpression = replaceExpression( likePredicate.getMatchExpression() );
final Expression patternExpression = replaceExpression( likePredicate.getPattern() );
final Expression escapeExpression = likePredicate.getEscapeCharacter() == null
? null
: replaceExpression( likePredicate.getEscapeCharacter() );
if ( matchExpression != likePredicate.getMatchExpression()
|| patternExpression != likePredicate.getPattern()
|| escapeExpression != likePredicate.getEscapeCharacter() ) {
returnedNode = new LikePredicate(
matchExpression,
patternExpression,
escapeExpression,
likePredicate.isNegated(),
likePredicate.isCaseSensitive(),
likePredicate.getExpressionType()
);
}
else {
returnedNode = likePredicate;
}
}
@Override
public void visitNegatedPredicate(NegatedPredicate negatedPredicate) {
negatedPredicate.getPredicate().accept( this );
if ( returnedNode != negatedPredicate.getPredicate() ) {
returnedNode = new NegatedPredicate( (Predicate) returnedNode );
}
else {
returnedNode = negatedPredicate;
}
}
@Override
public void visitNullnessPredicate(NullnessPredicate nullnessPredicate) {
final Expression expression = replaceExpression( nullnessPredicate.getExpression() );
if ( expression != nullnessPredicate.getExpression() ) {
returnedNode = new NullnessPredicate(
expression,
nullnessPredicate.isNegated(),
nullnessPredicate.getExpressionType()
);
}
else {
returnedNode = nullnessPredicate;
}
}
@Override
public void visitRelationalPredicate(ComparisonPredicate comparisonPredicate) {
final Expression lhs = replaceExpression( comparisonPredicate.getLeftHandExpression() );
final Expression rhs = replaceExpression( comparisonPredicate.getRightHandExpression() );
if ( lhs != comparisonPredicate.getLeftHandExpression()
|| rhs != comparisonPredicate.getRightHandExpression() ) {
returnedNode = new ComparisonPredicate(
lhs,
comparisonPredicate.getOperator(),
rhs,
comparisonPredicate.getExpressionType()
);
}
else {
returnedNode = comparisonPredicate;
}
}
@Override
public void visitSelfRenderingPredicate(SelfRenderingPredicate selfRenderingPredicate) {
final SelfRenderingExpression selfRenderingExpression = replaceExpression( selfRenderingPredicate.getSelfRenderingExpression() );
if ( selfRenderingExpression != selfRenderingPredicate.getSelfRenderingExpression() ) {
returnedNode = new SelfRenderingPredicate(
selfRenderingExpression
);
}
else {
returnedNode = selfRenderingPredicate;
}
}
/* Unsupported */
@Override
public void visitSelectStatement(SelectStatement statement) {
throw new UnsupportedOperationException();
}
@Override
public void visitDeleteStatement(DeleteStatement statement) {
throw new UnsupportedOperationException();
}
@Override
public void visitUpdateStatement(UpdateStatement statement) {
throw new UnsupportedOperationException();
}
@Override
public void visitInsertStatement(InsertStatement statement) {
throw new UnsupportedOperationException();
}
@Override
public void visitAssignment(Assignment assignment) {
throw new UnsupportedOperationException();
}
@Override
public void visitQueryGroup(QueryGroup queryGroup) {
throw new UnsupportedOperationException();
}
@Override
public void visitQuerySpec(QuerySpec querySpec) {
throw new UnsupportedOperationException();
}
@Override
public void visitSortSpecification(SortSpecification sortSpecification) {
throw new UnsupportedOperationException();
}
@Override
public void visitOffsetFetchClause(QueryPart querySpec) {
throw new UnsupportedOperationException();
}
@Override
public void visitSelectClause(SelectClause selectClause) {
throw new UnsupportedOperationException();
}
@Override
public void visitSqlSelection(SqlSelection sqlSelection) {
throw new UnsupportedOperationException();
}
@Override
public void visitFromClause(FromClause fromClause) {
throw new UnsupportedOperationException();
}
@Override
public void visitTableGroup(TableGroup tableGroup) {
throw new UnsupportedOperationException();
}
@Override
public void visitTableGroupJoin(TableGroupJoin tableGroupJoin) {
throw new UnsupportedOperationException();
}
@Override
public void visitNamedTableReference(NamedTableReference tableReference) {
throw new UnsupportedOperationException();
}
@Override
public void visitValuesTableReference(ValuesTableReference tableReference) {
throw new UnsupportedOperationException();
}
@Override
public void visitQueryPartTableReference(QueryPartTableReference tableReference) {
throw new UnsupportedOperationException();
}
@Override
public void visitFunctionTableReference(FunctionTableReference tableReference) {
throw new UnsupportedOperationException();
}
@Override
public void visitTableReferenceJoin(TableReferenceJoin tableReferenceJoin) {
throw new UnsupportedOperationException();
}
@Override
public void visitFilterPredicate(FilterPredicate filterPredicate) {
throw new UnsupportedOperationException();
}
@Override
public void visitFilterFragmentPredicate(FilterPredicate.FilterFragmentPredicate fragmentPredicate) {
throw new UnsupportedOperationException();
}
}

View File

@ -9,13 +9,19 @@ package org.hibernate.sql.ast.tree.expression;
import java.util.List;
import org.hibernate.metamodel.mapping.JdbcMappingContainer;
import org.hibernate.query.sqm.sql.internal.DomainResultProducer;
import org.hibernate.sql.ast.SqlAstWalker;
import org.hibernate.sql.ast.spi.SqlAstCreationState;
import org.hibernate.sql.ast.spi.SqlSelection;
import org.hibernate.sql.ast.tree.select.SortSpecification;
import org.hibernate.sql.results.graph.DomainResult;
import org.hibernate.sql.results.graph.DomainResultCreationState;
import org.hibernate.sql.results.graph.basic.BasicResult;
/**
* @author Christian Beikov
*/
public class Over implements Expression {
public class Over<T> implements Expression, DomainResultProducer<T> {
private final Expression expression;
private final List<Expression> partitions;
@ -109,6 +115,29 @@ public class Over implements Expression {
walker.visitOver( this );
}
@Override
public DomainResult<T> createDomainResult(String resultVariable, DomainResultCreationState creationState) {
final SqlSelection sqlSelection = createSelection( creationState.getSqlAstCreationState() );
return new BasicResult(
sqlSelection.getValuesArrayPosition(),
resultVariable,
expression.getExpressionType().getJdbcMappings().get( 0 ).getMappedJavaType()
);
}
@Override
public void applySqlSelections(DomainResultCreationState creationState) {
createSelection( creationState.getSqlAstCreationState() );
}
private SqlSelection createSelection(SqlAstCreationState creationState) {
return creationState.getSqlExpressionResolver().resolveSqlSelection(
this,
expression.getExpressionType().getJdbcMappings().get( 0 ).getMappedJavaType(),
creationState.getCreationContext().getSessionFactory().getTypeConfiguration()
);
}
public static enum FrameMode {
ROWS,
RANGE,

View File

@ -29,7 +29,7 @@ public class Junction implements Predicate {
private final Nature nature;
private final JdbcMappingContainer expressionType;
private final List<Predicate> predicates = new ArrayList<>();
private final List<Predicate> predicates;
public Junction() {
this( Nature.CONJUNCTION );
@ -42,6 +42,16 @@ public class Junction implements Predicate {
public Junction(Nature nature, JdbcMappingContainer expressionType) {
this.nature = nature;
this.expressionType = expressionType;
this.predicates = new ArrayList<>();
}
public Junction(
Nature nature,
List<Predicate> predicates,
JdbcMappingContainer expressionType) {
this.nature = nature;
this.expressionType = expressionType;
this.predicates = predicates;
}
public void add(Predicate predicate) {

View File

@ -34,6 +34,15 @@ public abstract class QueryPart implements SqlAstNode, Expression, DomainResultP
this.isRoot = isRoot;
}
protected QueryPart(boolean isRoot, QueryPart original) {
this.isRoot = isRoot;
this.hasPositionalSortItem = original.hasPositionalSortItem;
this.sortSpecifications = original.sortSpecifications;
this.offsetClauseExpression = original.offsetClauseExpression;
this.fetchClauseExpression = original.fetchClauseExpression;
this.fetchClauseType = original.fetchClauseType;
}
public abstract QuerySpec getFirstQuerySpec();
public abstract QuerySpec getLastQuerySpec();
@ -100,7 +109,7 @@ public abstract class QueryPart implements SqlAstNode, Expression, DomainResultP
public void setFetchClauseExpression(Expression fetchClauseExpression, FetchClauseType fetchClauseType) {
if ( fetchClauseExpression == null ) {
this.fetchClauseExpression = null;
this.fetchClauseType = null;
this.fetchClauseType = FetchClauseType.ROWS_ONLY;
}
else {
if ( fetchClauseType == null ) {

View File

@ -35,22 +35,36 @@ import org.hibernate.type.spi.TypeConfiguration;
public class QuerySpec extends QueryPart implements SqlAstNode, PredicateContainer, Expression, DomainResultProducer {
private final FromClause fromClause;
private final SelectClause selectClause = new SelectClause();
private final SelectClause selectClause;
private Predicate whereClauseRestrictions;
private boolean hasPositionalGroupItem;
private List<Expression> groupByClauseExpressions = Collections.emptyList();
private Predicate havingClauseRestrictions;
public QuerySpec(boolean isRoot) {
super( isRoot );
this.fromClause = new FromClause();
this.selectClause = new SelectClause();
}
public QuerySpec(boolean isRoot, int expectedNumberOfRoots) {
super( isRoot );
this.fromClause = new FromClause( expectedNumberOfRoots );
this.selectClause = new SelectClause();
}
private QuerySpec(QuerySpec original) {
super( false, original );
this.fromClause = original.fromClause;
this.selectClause = original.selectClause;
this.whereClauseRestrictions = original.whereClauseRestrictions;
this.groupByClauseExpressions = original.groupByClauseExpressions;
this.havingClauseRestrictions = original.havingClauseRestrictions;
}
public QuerySpec asSubQuery() {
return isRoot() ? new QuerySpec( this ) : this;
}
@Override
@ -94,20 +108,8 @@ public class QuerySpec extends QueryPart implements SqlAstNode, PredicateContain
return groupByClauseExpressions;
}
public boolean hasPositionalGroupItem() {
return hasPositionalGroupItem;
}
public void setGroupByClauseExpressions(List<Expression> groupByClauseExpressions) {
this.groupByClauseExpressions = groupByClauseExpressions == null ? Collections.emptyList() : groupByClauseExpressions;
if ( isRoot() ) {
for ( int i = 0; i < groupByClauseExpressions.size(); i++ ) {
final Expression groupItem = groupByClauseExpressions.get( i );
if ( groupItem instanceof SqmAliasedNodeRef ) {
hasPositionalGroupItem = true;
}
}
}
}
public Predicate getHavingClauseRestrictions() {

View File

@ -10,9 +10,6 @@ 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;
@ -21,11 +18,11 @@ 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.Tuple;
import jakarta.persistence.TypedQuery;
import static org.junit.jupiter.api.Assertions.assertEquals;
@ -141,7 +138,6 @@ public class OrderedSetAggregateTest {
@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 -> {
@ -153,9 +149,7 @@ public class OrderedSetAggregateTest {
@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) {
public void testHypotheticalSetPercentRank(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 );
@ -163,4 +157,30 @@ public class OrderedSetAggregateTest {
}
);
}
@Test
@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsHypotheticalSetFunctions.class)
public void testHypotheticalSetRank(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
TypedQuery<Long> q = session.createQuery( "select rank(5) within group (order by eob.theInt asc) from EntityOfBasics eob", Long.class );
assertEquals( 1L, q.getSingleResult() );
}
);
}
@Test
@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsHypotheticalSetFunctions.class)
public void testHypotheticalSetRankWithGroupByHavingOrderByLimit(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
TypedQuery<Tuple> q = session.createQuery( "select eob2.id, rank(5) within group (order by eob.theInt asc) from EntityOfBasics eob cross join EntityOfBasics eob2 group by eob2.id having eob2.id > 1 order by 1,2 offset 1", Tuple.class );
List<Tuple> resultList = q.getResultList();
assertEquals( 3, resultList.size() );
assertEquals( 1L, resultList.get( 0 ).get( 1, Long.class ) );
assertEquals( 1L, resultList.get( 1 ).get( 1, Long.class ) );
assertEquals( 1L, resultList.get( 2 ).get( 1, Long.class ) );
}
);
}
}