Implement query transformer to emulate for ordered set aggregate functions through their window variants
This commit is contained in:
parent
f52cf04a16
commit
236ece769b
|
@ -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() ) );
|
||||
|
|
|
@ -267,7 +267,7 @@ public class DB2Dialect extends Dialect {
|
|||
functionFactory.listagg( null );
|
||||
if ( getDB2Version().isSameOrAfter( 11, 1 ) ) {
|
||||
functionFactory.inverseDistributionOrderedSetAggregates();
|
||||
functionFactory.hypotheticalOrderedSetAggregates();
|
||||
functionFactory.hypotheticalOrderedSetAggregates_windowEmulation();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -49,7 +49,7 @@ public class DB2iDialect extends DB2Dialect {
|
|||
CommonFunctionFactory functionFactory = new CommonFunctionFactory(queryEngine);
|
||||
functionFactory.listagg( null );
|
||||
functionFactory.inverseDistributionOrderedSetAggregates();
|
||||
functionFactory.hypotheticalOrderedSetAggregates();
|
||||
functionFactory.hypotheticalOrderedSetAggregates_windowEmulation();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -60,7 +60,7 @@ public class DB2zDialect extends DB2Dialect {
|
|||
CommonFunctionFactory functionFactory = new CommonFunctionFactory(queryEngine);
|
||||
functionFactory.listagg( null );
|
||||
functionFactory.inverseDistributionOrderedSetAggregates();
|
||||
functionFactory.hypotheticalOrderedSetAggregates();
|
||||
functionFactory.hypotheticalOrderedSetAggregates_windowEmulation();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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)" );
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
|
@ -57,63 +57,14 @@ 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
|
||||
queryEngine
|
||||
);
|
||||
}
|
||||
catch (Exception e) {
|
||||
return null; // this works at least approximately
|
||||
}
|
||||
}
|
||||
return mapping;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public void render(
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
|
@ -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(
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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()" ),
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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 );
|
||||
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
|
||||
}
|
||||
|
|
|
@ -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 );
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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 ) {
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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 ) );
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue