HHH-15654 Criteria APIs for window and ordered-set aggregate functions
This commit is contained in:
parent
bc36eb3eeb
commit
2f1f6870b6
|
@ -892,4 +892,240 @@ public interface HibernateCriteriaBuilder extends CriteriaBuilder {
|
|||
*/
|
||||
@Incubating
|
||||
JpaSearchOrder desc(JpaCteCriteriaAttribute x, boolean nullsFirst);
|
||||
|
||||
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// Window functions
|
||||
|
||||
/**
|
||||
* Create an empty window to use for window and set-order aggregate functions.
|
||||
*
|
||||
* @return the empty window
|
||||
*/
|
||||
JpaWindow createWindow();
|
||||
|
||||
/**
|
||||
* Create a generic window function expression that will be applied
|
||||
* over the specified {@link JpaWindow window}.
|
||||
*
|
||||
* @param name name of the window function
|
||||
* @param type type of this expression
|
||||
* @param window window over which the function will be applied
|
||||
* @param args arguments to the function
|
||||
* @param <T> type of this expression
|
||||
*
|
||||
* @return window function expression
|
||||
*/
|
||||
<T> JpaExpression<T> windowFunction(String name, Class<T> type, JpaWindow window, Expression<?>... args);
|
||||
|
||||
/**
|
||||
* Create a {@code row_number} window function expression.
|
||||
*
|
||||
* @param window window over which the function will be applied
|
||||
*
|
||||
* @return window function expression
|
||||
*
|
||||
* @see #windowFunction
|
||||
*/
|
||||
JpaExpression<Long> rowNumber(JpaWindow window);
|
||||
|
||||
/**
|
||||
* Create a {@code first_value} window function expression.
|
||||
*
|
||||
* @param argument argument expression to pass to {@code first_value}
|
||||
* @param window window over which the function will be applied
|
||||
* @param <T> type of the expression
|
||||
*
|
||||
* @return window function expression
|
||||
*
|
||||
* @see #windowFunction
|
||||
*/
|
||||
<T> JpaExpression<T> firstValue(Expression<T> argument, JpaWindow window);
|
||||
|
||||
/**
|
||||
* Create a {@code last_value} window function expression.
|
||||
*
|
||||
* @param argument argument expression to pass to {@code last_value}
|
||||
* @param window window over which the function will be applied
|
||||
* @param <T> type of the expression
|
||||
*
|
||||
* @return window function expression
|
||||
*
|
||||
* @see #windowFunction
|
||||
*/
|
||||
<T> JpaExpression<T> lastValue(Expression<T> argument, JpaWindow window);
|
||||
|
||||
/**
|
||||
* Create a {@code nth_value} window function expression.
|
||||
*
|
||||
* @param argument argument expression to pass to {@code nth_value}
|
||||
* @param n the {@code N} argument for the function
|
||||
* @param window window over which the function will be applied
|
||||
* @param <T> type of the expression
|
||||
*
|
||||
* @return window function expression
|
||||
*
|
||||
* @see #windowFunction
|
||||
*/
|
||||
<T> JpaExpression<T> nthValue(Expression<T> argument, Expression<Integer> n, JpaWindow window);
|
||||
|
||||
/**
|
||||
* Create a {@code rank} window function expression.
|
||||
*
|
||||
* @param window window over which the function will be applied
|
||||
*
|
||||
* @return window function expression
|
||||
*
|
||||
* @see #windowFunction
|
||||
*/
|
||||
JpaExpression<Long> rank(JpaWindow window);
|
||||
|
||||
/**
|
||||
* Create a {@code dense_rank} window function expression.
|
||||
*
|
||||
* @param window window over which the function will be applied
|
||||
*
|
||||
* @return window function expression
|
||||
*
|
||||
* @see #windowFunction
|
||||
*/
|
||||
JpaExpression<Long> denseRank(JpaWindow window);
|
||||
|
||||
/**
|
||||
* Create a {@code percent_rank} window function expression.
|
||||
*
|
||||
* @param window window over which the function will be applied
|
||||
*
|
||||
* @return window function expression
|
||||
*
|
||||
* @see #windowFunction
|
||||
*/
|
||||
JpaExpression<Double> percentRank(JpaWindow window);
|
||||
|
||||
/**
|
||||
* Create a {@code cume_dist} window function expression.
|
||||
*
|
||||
* @param window window over which the function will be applied
|
||||
*
|
||||
* @return window function expression
|
||||
*
|
||||
* @see #windowFunction
|
||||
*/
|
||||
JpaExpression<Double> cumeDist(JpaWindow window);
|
||||
|
||||
/**
|
||||
* Create a generic ordered set-aggregate function expression.
|
||||
*
|
||||
* @param name name of the ordered set-aggregate function
|
||||
* @param type type of this expression
|
||||
* @param order order by clause used in within group
|
||||
* @param filter optional filter clause
|
||||
* @param args optional arguments to the function
|
||||
* @param <T> type of this expression
|
||||
*
|
||||
* @return ordered set-aggregate function expression
|
||||
*/
|
||||
<T> JpaExpression<T> functionWithinGroup(
|
||||
String name,
|
||||
Class<T> type,
|
||||
JpaOrder order,
|
||||
JpaPredicate filter,
|
||||
Expression<?>... args);
|
||||
|
||||
/**
|
||||
* Create a generic ordered set-aggregate function expression.
|
||||
*
|
||||
* @param name name of the ordered set-aggregate function
|
||||
* @param type type of this expression
|
||||
* @param order order by clause used in within group
|
||||
* @param args optional arguments to the function
|
||||
* @param <T> type of this expression
|
||||
*
|
||||
* @return ordered set-aggregate function expression
|
||||
*
|
||||
* @see #functionWithinGroup(String, Class, JpaOrder, JpaPredicate, Expression...) functionWithinGroup
|
||||
*/
|
||||
<T> JpaExpression<T> functionWithinGroup(String name, Class<T> type, JpaOrder order, Expression<?>... args);
|
||||
|
||||
/**
|
||||
* Create a {@code listagg} ordered set-aggregate function expression.
|
||||
*
|
||||
* @param order order by clause used in within group
|
||||
* @param filter optional filter clause
|
||||
* @param argument values to join
|
||||
* @param separator the separator used to join the values
|
||||
*
|
||||
* @return ordered set-aggregate expression
|
||||
*
|
||||
* @see #functionWithinGroup
|
||||
*/
|
||||
JpaExpression<String> listagg(
|
||||
JpaOrder order,
|
||||
JpaPredicate filter,
|
||||
Expression<String> argument,
|
||||
Expression<String> separator);
|
||||
|
||||
/**
|
||||
* Create a {@code mode} ordered set-aggregate function expression.
|
||||
*
|
||||
* @param order order by clause used in within group
|
||||
* @param filter optional filter clause
|
||||
* @param argument argument to the function
|
||||
*
|
||||
* @return ordered set-aggregate expression
|
||||
*
|
||||
* @see #functionWithinGroup
|
||||
*/
|
||||
JpaExpression<?> mode(JpaOrder order, JpaPredicate filter, Expression<?> argument);
|
||||
|
||||
/**
|
||||
* Create a {@code percentile_cont} ordered set-aggregate function expression.
|
||||
*
|
||||
* @param order order by clause used in within group
|
||||
* @param filter optional filter clause
|
||||
* @param argument argument to the function
|
||||
*
|
||||
* @return ordered set-aggregate expression
|
||||
*
|
||||
* @see #functionWithinGroup
|
||||
*/
|
||||
JpaExpression<Integer> percentileCont(JpaOrder order, JpaPredicate filter, Expression<? extends Number> argument);
|
||||
|
||||
/**
|
||||
* Create a {@code percentile_disc} ordered set-aggregate function expression.
|
||||
*
|
||||
* @param order order by clause used in within group
|
||||
* @param filter optional filter clause
|
||||
* @param argument argument to the function
|
||||
*
|
||||
* @return ordered set-aggregate expression
|
||||
*
|
||||
* @see #functionWithinGroup
|
||||
*/
|
||||
JpaExpression<Integer> percentileDisc(JpaOrder order, JpaPredicate filter, Expression<? extends Number> argument);
|
||||
|
||||
/**
|
||||
* Create a {@code rank} ordered set-aggregate function expression.
|
||||
*
|
||||
* @param order order by clause used in within group
|
||||
* @param filter optional filter clause
|
||||
* @param argument argument to the function
|
||||
*
|
||||
* @return ordered set-aggregate expression
|
||||
*
|
||||
* @see #functionWithinGroup
|
||||
*/
|
||||
JpaExpression<Long> rank(JpaOrder order, JpaPredicate filter, Expression<Integer> argument);
|
||||
|
||||
/**
|
||||
* Create a {@code percent_rank} ordered set-aggregate function expression.
|
||||
*
|
||||
* @param order order by clause used in within group
|
||||
* @param filter optional filter clause
|
||||
* @param argument argument to the function
|
||||
*
|
||||
* @return ordered set-aggregate expression
|
||||
*
|
||||
* @see #functionWithinGroup
|
||||
*/
|
||||
JpaExpression<Double> percentRank(JpaOrder order, JpaPredicate filter, Expression<Integer> argument);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
|
||||
*/
|
||||
package org.hibernate.query.criteria;
|
||||
|
||||
import jakarta.persistence.criteria.Expression;
|
||||
import jakarta.persistence.criteria.Order;
|
||||
|
||||
/**
|
||||
* @author Marco Belladelli
|
||||
*/
|
||||
public interface JpaWindow {
|
||||
JpaWindow partitionBy(Expression<?>... expressions);
|
||||
|
||||
JpaWindow orderBy(Order... expressions);
|
||||
}
|
|
@ -47,6 +47,7 @@ import org.hibernate.query.criteria.JpaSelection;
|
|||
import org.hibernate.query.criteria.JpaSetJoin;
|
||||
import org.hibernate.query.criteria.JpaSimpleCase;
|
||||
import org.hibernate.query.criteria.JpaSubQuery;
|
||||
import org.hibernate.query.criteria.JpaWindow;
|
||||
import org.hibernate.query.sqm.NullPrecedence;
|
||||
import org.hibernate.query.sqm.SortOrder;
|
||||
import org.hibernate.query.sqm.tree.expression.SqmExpression;
|
||||
|
@ -1254,4 +1255,109 @@ public class HibernateCriteriaBuilderDelegate implements HibernateCriteriaBuilde
|
|||
public JpaSearchOrder desc(JpaCteCriteriaAttribute x, boolean nullsFirst) {
|
||||
return criteriaBuilder.desc( x, nullsFirst );
|
||||
}
|
||||
|
||||
@Override
|
||||
public JpaWindow createWindow() {
|
||||
return criteriaBuilder.createWindow();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> JpaExpression<T> windowFunction(String name, Class<T> type, JpaWindow window, Expression<?>... args) {
|
||||
return criteriaBuilder.windowFunction( name, type, window, args );
|
||||
}
|
||||
|
||||
@Override
|
||||
public JpaExpression<Long> rowNumber(JpaWindow window) {
|
||||
return criteriaBuilder.rowNumber( window );
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> JpaExpression<T> firstValue(Expression<T> argument, JpaWindow window) {
|
||||
return criteriaBuilder.firstValue( argument, window );
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> JpaExpression<T> lastValue(Expression<T> argument, JpaWindow window) {
|
||||
return criteriaBuilder.lastValue( argument, window );
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> JpaExpression<T> nthValue(Expression<T> argument, Expression<Integer> n, JpaWindow window) {
|
||||
return criteriaBuilder.nthValue( argument, n, window );
|
||||
}
|
||||
|
||||
@Override
|
||||
public JpaExpression<Long> rank(JpaWindow window) {
|
||||
return criteriaBuilder.rank( window );
|
||||
}
|
||||
|
||||
@Override
|
||||
public JpaExpression<Long> denseRank(JpaWindow window) {
|
||||
return criteriaBuilder.denseRank( window );
|
||||
}
|
||||
|
||||
@Override
|
||||
public JpaExpression<Double> percentRank(JpaWindow window) {
|
||||
return criteriaBuilder.percentRank( window );
|
||||
}
|
||||
|
||||
@Override
|
||||
public JpaExpression<Double> cumeDist(JpaWindow window) {
|
||||
return criteriaBuilder.cumeDist( window );
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> JpaExpression<T> functionWithinGroup(
|
||||
String name,
|
||||
Class<T> type,
|
||||
JpaOrder order,
|
||||
JpaPredicate filter,
|
||||
Expression<?>... args) {
|
||||
return criteriaBuilder.functionWithinGroup( name, type, order, filter, args );
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> JpaExpression<T> functionWithinGroup(String name, Class<T> type, JpaOrder order, Expression<?>... args) {
|
||||
return criteriaBuilder.functionWithinGroup( name, type, order, args );
|
||||
}
|
||||
|
||||
@Override
|
||||
public JpaExpression<String> listagg(
|
||||
JpaOrder order,
|
||||
JpaPredicate filter,
|
||||
Expression<String> argument,
|
||||
Expression<String> separator) {
|
||||
return criteriaBuilder.listagg( order, filter, argument, separator );
|
||||
}
|
||||
|
||||
@Override
|
||||
public JpaExpression<?> mode(JpaOrder order, JpaPredicate filter, Expression<?> argument) {
|
||||
return criteriaBuilder.mode( order, filter, argument );
|
||||
}
|
||||
|
||||
@Override
|
||||
public JpaExpression<Integer> percentileCont(
|
||||
JpaOrder order,
|
||||
JpaPredicate filter,
|
||||
Expression<? extends Number> argument) {
|
||||
return criteriaBuilder.percentileCont( order, filter, argument );
|
||||
}
|
||||
|
||||
@Override
|
||||
public JpaExpression<Integer> percentileDisc(
|
||||
JpaOrder order,
|
||||
JpaPredicate filter,
|
||||
Expression<? extends Number> argument) {
|
||||
return criteriaBuilder.percentileDisc( order, filter, argument );
|
||||
}
|
||||
|
||||
@Override
|
||||
public JpaExpression<Long> rank(JpaOrder order, JpaPredicate filter, Expression<Integer> argument) {
|
||||
return criteriaBuilder.rank( order, filter, argument );
|
||||
}
|
||||
|
||||
@Override
|
||||
public JpaExpression<Double> percentRank(JpaOrder order, JpaPredicate filter, Expression<Integer> argument) {
|
||||
return criteriaBuilder.percentRank( order, filter, argument );
|
||||
}
|
||||
}
|
||||
|
|
|
@ -63,6 +63,7 @@ import org.hibernate.query.sqm.tree.expression.SqmToDuration;
|
|||
import org.hibernate.query.sqm.tree.expression.SqmTrimSpecification;
|
||||
import org.hibernate.query.sqm.tree.expression.SqmTuple;
|
||||
import org.hibernate.query.sqm.tree.expression.SqmUnaryOperation;
|
||||
import org.hibernate.query.sqm.tree.expression.SqmWindow;
|
||||
import org.hibernate.query.sqm.tree.from.SqmAttributeJoin;
|
||||
import org.hibernate.query.sqm.tree.from.SqmCrossJoin;
|
||||
import org.hibernate.query.sqm.tree.from.SqmCteJoin;
|
||||
|
@ -259,6 +260,8 @@ public interface SemanticQueryWalker<T> {
|
|||
|
||||
T visitOver(SqmOver<?> over);
|
||||
|
||||
T visitWindow(SqmWindow widow);
|
||||
|
||||
T visitOverflow(SqmOverflow<?> sqmOverflow);
|
||||
|
||||
T visitCoalesce(SqmCoalesce<?> sqmCoalesce);
|
||||
|
|
|
@ -43,32 +43,34 @@ import org.hibernate.metamodel.model.domain.BasicDomainType;
|
|||
import org.hibernate.metamodel.model.domain.DomainType;
|
||||
import org.hibernate.metamodel.model.domain.JpaMetamodel;
|
||||
import org.hibernate.metamodel.model.domain.spi.JpaMetamodelImplementor;
|
||||
import org.hibernate.query.ReturnableType;
|
||||
import org.hibernate.query.BindableType;
|
||||
import org.hibernate.query.ReturnableType;
|
||||
import org.hibernate.query.SemanticException;
|
||||
import org.hibernate.query.criteria.spi.CriteriaBuilderExtension;
|
||||
import org.hibernate.query.criteria.HibernateCriteriaBuilder;
|
||||
import org.hibernate.query.criteria.JpaCteCriteriaAttribute;
|
||||
import org.hibernate.query.criteria.JpaSearchOrder;
|
||||
import org.hibernate.query.criteria.JpaSubQuery;
|
||||
import org.hibernate.query.sqm.BinaryArithmeticOperator;
|
||||
import org.hibernate.query.sqm.ComparisonOperator;
|
||||
import org.hibernate.query.sqm.NullPrecedence;
|
||||
import org.hibernate.query.sqm.SetOperator;
|
||||
import org.hibernate.query.sqm.SortOrder;
|
||||
import org.hibernate.query.sqm.TrimSpec;
|
||||
import org.hibernate.query.sqm.UnaryArithmeticOperator;
|
||||
import org.hibernate.query.criteria.JpaCoalesce;
|
||||
import org.hibernate.query.criteria.JpaCompoundSelection;
|
||||
import org.hibernate.query.criteria.JpaCriteriaQuery;
|
||||
import org.hibernate.query.criteria.JpaCteCriteriaAttribute;
|
||||
import org.hibernate.query.criteria.JpaExpression;
|
||||
import org.hibernate.query.criteria.JpaOrder;
|
||||
import org.hibernate.query.criteria.JpaPredicate;
|
||||
import org.hibernate.query.criteria.JpaSearchOrder;
|
||||
import org.hibernate.query.criteria.JpaSelection;
|
||||
import org.hibernate.query.criteria.JpaSubQuery;
|
||||
import org.hibernate.query.criteria.JpaWindow;
|
||||
import org.hibernate.query.criteria.ValueHandlingMode;
|
||||
import org.hibernate.query.criteria.spi.CriteriaBuilderExtension;
|
||||
import org.hibernate.query.spi.QueryEngine;
|
||||
import org.hibernate.query.sqm.BinaryArithmeticOperator;
|
||||
import org.hibernate.query.sqm.ComparisonOperator;
|
||||
import org.hibernate.query.sqm.NodeBuilder;
|
||||
import org.hibernate.query.sqm.NullPrecedence;
|
||||
import org.hibernate.query.sqm.SetOperator;
|
||||
import org.hibernate.query.sqm.SortOrder;
|
||||
import org.hibernate.query.sqm.SqmExpressible;
|
||||
import org.hibernate.query.sqm.SqmQuerySource;
|
||||
import org.hibernate.query.sqm.TrimSpec;
|
||||
import org.hibernate.query.sqm.UnaryArithmeticOperator;
|
||||
import org.hibernate.query.sqm.function.NamedSqmFunctionDescriptor;
|
||||
import org.hibernate.query.sqm.function.SqmFunctionDescriptor;
|
||||
import org.hibernate.query.sqm.produce.function.StandardFunctionReturnTypeResolvers;
|
||||
|
@ -89,7 +91,6 @@ import org.hibernate.query.sqm.tree.domain.SqmPluralValuedSimplePath;
|
|||
import org.hibernate.query.sqm.tree.domain.SqmSetJoin;
|
||||
import org.hibernate.query.sqm.tree.domain.SqmSingularJoin;
|
||||
import org.hibernate.query.sqm.tree.expression.JpaCriteriaParameter;
|
||||
import org.hibernate.query.sqm.tree.expression.ValueBindJpaCriteriaParameter;
|
||||
import org.hibernate.query.sqm.tree.expression.SqmBinaryArithmetic;
|
||||
import org.hibernate.query.sqm.tree.expression.SqmCaseSearched;
|
||||
import org.hibernate.query.sqm.tree.expression.SqmCaseSimple;
|
||||
|
@ -102,9 +103,12 @@ import org.hibernate.query.sqm.tree.expression.SqmFunction;
|
|||
import org.hibernate.query.sqm.tree.expression.SqmLiteral;
|
||||
import org.hibernate.query.sqm.tree.expression.SqmLiteralNull;
|
||||
import org.hibernate.query.sqm.tree.expression.SqmModifiedSubQueryExpression;
|
||||
import org.hibernate.query.sqm.tree.expression.SqmOver;
|
||||
import org.hibernate.query.sqm.tree.expression.SqmTrimSpecification;
|
||||
import org.hibernate.query.sqm.tree.expression.SqmTuple;
|
||||
import org.hibernate.query.sqm.tree.expression.SqmUnaryOperation;
|
||||
import org.hibernate.query.sqm.tree.expression.SqmWindow;
|
||||
import org.hibernate.query.sqm.tree.expression.ValueBindJpaCriteriaParameter;
|
||||
import org.hibernate.query.sqm.tree.from.SqmRoot;
|
||||
import org.hibernate.query.sqm.tree.insert.SqmInsertSelectStatement;
|
||||
import org.hibernate.query.sqm.tree.predicate.SqmBetweenPredicate;
|
||||
|
@ -123,6 +127,7 @@ import org.hibernate.query.sqm.tree.predicate.SqmPredicate;
|
|||
import org.hibernate.query.sqm.tree.select.SqmDynamicInstantiation;
|
||||
import org.hibernate.query.sqm.tree.select.SqmDynamicInstantiationArgument;
|
||||
import org.hibernate.query.sqm.tree.select.SqmJpaCompoundSelection;
|
||||
import org.hibernate.query.sqm.tree.select.SqmOrderByClause;
|
||||
import org.hibernate.query.sqm.tree.select.SqmQueryGroup;
|
||||
import org.hibernate.query.sqm.tree.select.SqmQueryPart;
|
||||
import org.hibernate.query.sqm.tree.select.SqmSelectQuery;
|
||||
|
@ -2559,4 +2564,148 @@ public class SqmCriteriaNodeBuilder implements NodeBuilder, SqmCreationContext,
|
|||
|
||||
throw new InvalidObjectException( "Could not find a SessionFactory [uuid=" + uuid + ",name=" + name + "]" );
|
||||
}
|
||||
|
||||
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// Window functions
|
||||
|
||||
@Override
|
||||
public SqmWindow createWindow() {
|
||||
return new SqmWindow( this );
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> SqmExpression<T> windowFunction(String name, Class<T> type, JpaWindow window, Expression<?>... args) {
|
||||
// todo marco : some functions (`rank` and `percent_rank`) are not registered as window functions
|
||||
// but as ordered set-aggregate functions, but they can serve both purposes
|
||||
// getting an error when calling generateWindowSqmExpression
|
||||
// (@see CommonFunctionFactory#hypotheticalOrderedSetAggregates)
|
||||
SqmExpression<T> function = getFunctionDescriptor( name ).generateSqmExpression(
|
||||
expressionList(args),
|
||||
null,
|
||||
queryEngine,
|
||||
getJpaMetamodel().getTypeConfiguration()
|
||||
);
|
||||
return new SqmOver<>( function, (SqmWindow) window );
|
||||
}
|
||||
|
||||
@Override
|
||||
public SqmExpression<Long> rowNumber(JpaWindow window) {
|
||||
return windowFunction( "row_number", Long.class, window );
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> SqmExpression<T> firstValue(Expression<T> argument, JpaWindow window) {
|
||||
return (SqmExpression<T>) windowFunction( "first_value", argument.getJavaType(), window, argument );
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> SqmExpression<T> lastValue(Expression<T> argument, JpaWindow window) {
|
||||
return (SqmExpression<T>) windowFunction( "last_value", argument.getJavaType(), window, argument );
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> SqmExpression<T> nthValue(Expression<T> argument, Expression<Integer> n, JpaWindow window) {
|
||||
return (SqmExpression<T>) windowFunction( "nth_value", argument.getJavaType(), window, argument, n );
|
||||
}
|
||||
|
||||
@Override
|
||||
public SqmExpression<Long> rank(JpaWindow window) {
|
||||
return windowFunction( "rank", Long.class, window );
|
||||
}
|
||||
|
||||
@Override
|
||||
public SqmExpression<Long> denseRank(JpaWindow window) {
|
||||
return windowFunction( "dense_rank", Long.class, window );
|
||||
}
|
||||
|
||||
@Override
|
||||
public SqmExpression<Double> percentRank(JpaWindow window) {
|
||||
return windowFunction( "percent_rank", Double.class, window );
|
||||
}
|
||||
|
||||
@Override
|
||||
public SqmExpression<Double> cumeDist(JpaWindow window) {
|
||||
return windowFunction( "cume_dist", Double.class, window );
|
||||
}
|
||||
|
||||
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// Ordered set-aggregate functions
|
||||
|
||||
@Override
|
||||
public <T> SqmExpression<T> functionWithinGroup(
|
||||
String name,
|
||||
Class<T> type,
|
||||
JpaOrder order,
|
||||
JpaPredicate filter,
|
||||
Expression<?>... args) {
|
||||
// todo marco : we could also pass a Window (?)
|
||||
// set aggregate function support OVER clauses
|
||||
// eg. https://docs.oracle.com/cd/B19306_01/server.102/b14200/functions110.htm
|
||||
SqmOrderByClause withinGroupClause = new SqmOrderByClause();
|
||||
if ( order != null ) {
|
||||
withinGroupClause.addSortSpecification( (SqmSortSpecification) order );
|
||||
}
|
||||
SqmPredicate sqmFilter = filter != null ? (SqmPredicate) filter : null;
|
||||
return getFunctionDescriptor( name ).generateOrderedSetAggregateSqmExpression(
|
||||
expressionList( args ),
|
||||
sqmFilter,
|
||||
withinGroupClause,
|
||||
null,
|
||||
queryEngine,
|
||||
getJpaMetamodel().getTypeConfiguration()
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> SqmExpression<T> functionWithinGroup(
|
||||
String name,
|
||||
Class<T> type,
|
||||
JpaOrder order,
|
||||
Expression<?>... args) {
|
||||
return functionWithinGroup( name, type, order, null, args );
|
||||
}
|
||||
|
||||
@Override
|
||||
public SqmExpression<String> listagg(
|
||||
JpaOrder order,
|
||||
JpaPredicate filter,
|
||||
Expression<String> argument,
|
||||
Expression<String> separator) {
|
||||
return functionWithinGroup( "listagg", String.class, order, filter, argument, separator );
|
||||
}
|
||||
|
||||
@Override
|
||||
public SqmExpression<?> mode(JpaOrder order, JpaPredicate filter, Expression<?> argument) {
|
||||
return functionWithinGroup( "mode", argument.getJavaType(), order, filter, argument );
|
||||
}
|
||||
|
||||
@Override
|
||||
public SqmExpression<Integer> percentileCont(
|
||||
JpaOrder order,
|
||||
JpaPredicate filter,
|
||||
Expression<? extends Number> argument) {
|
||||
return functionWithinGroup( "percentile_cont", Integer.class, order, filter, argument );
|
||||
}
|
||||
|
||||
@Override
|
||||
public SqmExpression<Integer> percentileDisc(
|
||||
JpaOrder order,
|
||||
JpaPredicate filter,
|
||||
Expression<? extends Number> argument) {
|
||||
return functionWithinGroup( "percentile_disc", Integer.class, order, filter, argument );
|
||||
}
|
||||
|
||||
@Override
|
||||
public SqmExpression<Long> rank(JpaOrder order, JpaPredicate filter, Expression<Integer> argument) {
|
||||
return functionWithinGroup( "rank", Long.class, order, filter, argument );
|
||||
}
|
||||
|
||||
@Override
|
||||
public SqmExpression<Double> percentRank(JpaOrder order, JpaPredicate filter, Expression<Integer> argument) {
|
||||
return functionWithinGroup( "percent_rank", Double.class, order, filter, argument );
|
||||
}
|
||||
}
|
||||
|
|
|
@ -67,6 +67,7 @@ import org.hibernate.query.sqm.tree.expression.SqmToDuration;
|
|||
import org.hibernate.query.sqm.tree.expression.SqmTrimSpecification;
|
||||
import org.hibernate.query.sqm.tree.expression.SqmTuple;
|
||||
import org.hibernate.query.sqm.tree.expression.SqmUnaryOperation;
|
||||
import org.hibernate.query.sqm.tree.expression.SqmWindow;
|
||||
import org.hibernate.query.sqm.tree.from.SqmAttributeJoin;
|
||||
import org.hibernate.query.sqm.tree.from.SqmCrossJoin;
|
||||
import org.hibernate.query.sqm.tree.from.SqmCteJoin;
|
||||
|
@ -847,6 +848,11 @@ public class SqmTreePrinter implements SemanticQueryWalker<Object> {
|
|||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object visitWindow(SqmWindow window) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object visitWhereClause(SqmWhereClause whereClause) {
|
||||
if ( whereClause != null && whereClause.getPredicate() != null ) {
|
||||
|
|
|
@ -69,6 +69,7 @@ import org.hibernate.query.sqm.tree.expression.SqmToDuration;
|
|||
import org.hibernate.query.sqm.tree.expression.SqmTrimSpecification;
|
||||
import org.hibernate.query.sqm.tree.expression.SqmTuple;
|
||||
import org.hibernate.query.sqm.tree.expression.SqmUnaryOperation;
|
||||
import org.hibernate.query.sqm.tree.expression.SqmWindow;
|
||||
import org.hibernate.query.sqm.tree.from.SqmAttributeJoin;
|
||||
import org.hibernate.query.sqm.tree.from.SqmCrossJoin;
|
||||
import org.hibernate.query.sqm.tree.from.SqmCteJoin;
|
||||
|
@ -704,19 +705,25 @@ public abstract class BaseSemanticQueryWalker implements SemanticQueryWalker<Obj
|
|||
@Override
|
||||
public Object visitOver(SqmOver<?> over) {
|
||||
over.getExpression().accept( this );
|
||||
for ( SqmExpression<?> partition : over.getPartitions() ) {
|
||||
over.getWindow().accept( this );
|
||||
return over;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object visitWindow(SqmWindow window) {
|
||||
for ( SqmExpression<?> partition : window.getPartitions() ) {
|
||||
partition.accept( this );
|
||||
}
|
||||
for ( SqmSortSpecification sqmSortSpecification : over.getOrderList() ) {
|
||||
for ( SqmSortSpecification sqmSortSpecification : window.getOrderList() ) {
|
||||
visitSortSpecification( sqmSortSpecification );
|
||||
}
|
||||
if ( over.getStartExpression() != null ) {
|
||||
over.getStartExpression().accept( this );
|
||||
if ( window.getStartExpression() != null ) {
|
||||
window.getStartExpression().accept( this );
|
||||
}
|
||||
if ( over.getEndExpression() != null ) {
|
||||
over.getEndExpression().accept( this );
|
||||
if ( window.getEndExpression() != null ) {
|
||||
window.getEndExpression().accept( this );
|
||||
}
|
||||
return over;
|
||||
return window;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -5696,24 +5696,29 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
|
|||
public Object visitOver(SqmOver<?> over) {
|
||||
currentClauseStack.push( Clause.OVER );
|
||||
final Expression expression = (Expression) over.getExpression().accept( this );
|
||||
final List<Expression> partitions = new ArrayList<>(over.getPartitions().size());
|
||||
for ( SqmExpression<?> partition : over.getPartitions() ) {
|
||||
|
||||
final List<Expression> partitions = new ArrayList<>(over.getWindow().getPartitions().size());
|
||||
for ( SqmExpression<?> partition : over.getWindow().getPartitions() ) {
|
||||
partitions.add( (Expression) partition.accept( this ) );
|
||||
}
|
||||
final List<SortSpecification> orderList = new ArrayList<>( over.getOrderList().size() );
|
||||
for ( SqmSortSpecification sortSpecification : over.getOrderList() ) {
|
||||
final List<SortSpecification> orderList = new ArrayList<>( over.getWindow().getOrderList().size() );
|
||||
for ( SqmSortSpecification sortSpecification : over.getWindow().getOrderList() ) {
|
||||
orderList.add( visitSortSpecification( sortSpecification ) );
|
||||
}
|
||||
final Over<Object> overExpression = new Over<>(
|
||||
expression,
|
||||
partitions,
|
||||
orderList,
|
||||
over.getMode(),
|
||||
over.getStartKind(),
|
||||
over.getStartExpression() == null ? null : (Expression) over.getStartExpression().accept( this ),
|
||||
over.getEndKind(),
|
||||
over.getEndExpression() == null ? null : (Expression) over.getEndExpression().accept( this ),
|
||||
over.getExclusion()
|
||||
over.getWindow().getMode(),
|
||||
over.getWindow().getStartKind(),
|
||||
over.getWindow().getStartExpression() == null ?
|
||||
null :
|
||||
(Expression) over.getWindow().getStartExpression().accept( this ),
|
||||
over.getWindow().getEndKind(),
|
||||
over.getWindow().getEndExpression() == null ?
|
||||
null :
|
||||
(Expression) over.getWindow().getEndExpression().accept( this ),
|
||||
over.getWindow().getExclusion()
|
||||
);
|
||||
currentClauseStack.pop();
|
||||
return overExpression;
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
*/
|
||||
package org.hibernate.query.sqm.tree.expression;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.hibernate.query.sqm.FrameExclusion;
|
||||
|
@ -19,18 +18,23 @@ import org.hibernate.query.sqm.tree.select.SqmSortSpecification;
|
|||
|
||||
/**
|
||||
* @author Christian Beikov
|
||||
* @author Marco Belladelli
|
||||
*/
|
||||
// todo marco : changed this for window functions, but isn't this API ?
|
||||
// what changed ? getter methods + renderFrameKind moved to SqmWindow
|
||||
public class SqmOver<T> extends AbstractSqmExpression<T> {
|
||||
|
||||
private final SqmExpression<T> expression;
|
||||
private final List<SqmExpression<?>> partitions;
|
||||
private final List<SqmSortSpecification> orderList;
|
||||
private final FrameMode mode;
|
||||
private final FrameKind startKind;
|
||||
private final SqmExpression<?> startExpression;
|
||||
private final FrameKind endKind;
|
||||
private final SqmExpression<?> endExpression;
|
||||
private final FrameExclusion exclusion;
|
||||
|
||||
private final SqmWindow window;
|
||||
|
||||
public SqmOver(
|
||||
SqmExpression<T> expression,
|
||||
SqmWindow window) {
|
||||
super( expression.getNodeType(), expression.nodeBuilder() );
|
||||
this.expression = expression;
|
||||
this.window = window;
|
||||
}
|
||||
|
||||
public SqmOver(
|
||||
SqmExpression<T> expression,
|
||||
|
@ -42,16 +46,20 @@ public class SqmOver<T> extends AbstractSqmExpression<T> {
|
|||
FrameKind endKind,
|
||||
SqmExpression<?> endExpression,
|
||||
FrameExclusion exclusion) {
|
||||
super( expression.getNodeType(), expression.nodeBuilder() );
|
||||
this.expression = expression;
|
||||
this.partitions = partitions;
|
||||
this.orderList = orderList;
|
||||
this.mode = mode;
|
||||
this.startKind = startKind;
|
||||
this.startExpression = startExpression;
|
||||
this.endKind = endKind;
|
||||
this.endExpression = endExpression;
|
||||
this.exclusion = exclusion;
|
||||
this(
|
||||
expression,
|
||||
new SqmWindow(
|
||||
expression.nodeBuilder(),
|
||||
partitions,
|
||||
orderList,
|
||||
mode,
|
||||
startKind,
|
||||
startExpression,
|
||||
endKind,
|
||||
endExpression,
|
||||
exclusion
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -60,26 +68,11 @@ public class SqmOver<T> extends AbstractSqmExpression<T> {
|
|||
if ( existing != null ) {
|
||||
return existing;
|
||||
}
|
||||
final List<SqmExpression<?>> partitions = new ArrayList<>(this.partitions.size());
|
||||
for ( SqmExpression<?> partition : this.partitions ) {
|
||||
partitions.add( partition.copy( context ) );
|
||||
}
|
||||
final List<SqmSortSpecification> orderList = new ArrayList<>(this.orderList.size());
|
||||
for ( SqmSortSpecification sortSpecification : this.orderList ) {
|
||||
orderList.add( sortSpecification.copy( context ) );
|
||||
}
|
||||
final SqmOver<T> over = context.registerCopy(
|
||||
this,
|
||||
new SqmOver<>(
|
||||
expression.copy( context ),
|
||||
partitions,
|
||||
orderList,
|
||||
mode,
|
||||
startKind,
|
||||
startExpression == null ? null : startExpression.copy( context ),
|
||||
endKind,
|
||||
endExpression == null ? null : endExpression.copy( context ),
|
||||
exclusion
|
||||
window.copy( context )
|
||||
)
|
||||
);
|
||||
copyTo( over, context );
|
||||
|
@ -90,36 +83,8 @@ public class SqmOver<T> extends AbstractSqmExpression<T> {
|
|||
return expression;
|
||||
}
|
||||
|
||||
public List<SqmExpression<?>> getPartitions() {
|
||||
return partitions;
|
||||
}
|
||||
|
||||
public List<SqmSortSpecification> getOrderList() {
|
||||
return orderList;
|
||||
}
|
||||
|
||||
public SqmExpression<?> getStartExpression() {
|
||||
return startExpression;
|
||||
}
|
||||
|
||||
public SqmExpression<?> getEndExpression() {
|
||||
return endExpression;
|
||||
}
|
||||
|
||||
public FrameMode getMode() {
|
||||
return mode;
|
||||
}
|
||||
|
||||
public FrameKind getStartKind() {
|
||||
return startKind;
|
||||
}
|
||||
|
||||
public FrameKind getEndKind() {
|
||||
return endKind;
|
||||
}
|
||||
|
||||
public FrameExclusion getExclusion() {
|
||||
return exclusion;
|
||||
public SqmWindow getWindow() {
|
||||
return window;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -136,91 +101,7 @@ public class SqmOver<T> extends AbstractSqmExpression<T> {
|
|||
public void appendHqlString(StringBuilder sb) {
|
||||
expression.appendHqlString( sb );
|
||||
sb.append( " over (" );
|
||||
boolean needsWhitespace = false;
|
||||
if (!partitions.isEmpty()) {
|
||||
needsWhitespace = true;
|
||||
sb.append( "partition by " );
|
||||
partitions.get( 0 ).appendHqlString( sb );
|
||||
for ( int i = 1; i < partitions.size(); i++ ) {
|
||||
sb.append( ',' );
|
||||
partitions.get( i ).appendHqlString( sb );
|
||||
}
|
||||
}
|
||||
if (!orderList.isEmpty()) {
|
||||
if ( needsWhitespace ) {
|
||||
sb.append( ' ' );
|
||||
}
|
||||
needsWhitespace = true;
|
||||
sb.append( "order by " );
|
||||
orderList.get( 0 ).appendHqlString( sb );
|
||||
for ( int i = 1; i < orderList.size(); i++ ) {
|
||||
sb.append( ',' );
|
||||
orderList.get( i ).appendHqlString( sb );
|
||||
}
|
||||
}
|
||||
if ( mode == FrameMode.ROWS && startKind == FrameKind.UNBOUNDED_PRECEDING && endKind == FrameKind.CURRENT_ROW && exclusion == FrameExclusion.NO_OTHERS ) {
|
||||
// This is the default, so we don't need to render anything
|
||||
}
|
||||
else {
|
||||
if ( needsWhitespace ) {
|
||||
sb.append( ' ' );
|
||||
}
|
||||
switch ( mode ) {
|
||||
case GROUPS:
|
||||
sb.append( "groups " );
|
||||
break;
|
||||
case RANGE:
|
||||
sb.append( "range " );
|
||||
break;
|
||||
case ROWS:
|
||||
sb.append( "rows " );
|
||||
break;
|
||||
}
|
||||
if ( endKind == FrameKind.CURRENT_ROW ) {
|
||||
renderFrameKind( sb, startKind, startExpression );
|
||||
}
|
||||
else {
|
||||
sb.append( "between " );
|
||||
renderFrameKind( sb, startKind, startExpression );
|
||||
sb.append( " and " );
|
||||
renderFrameKind( sb, endKind, endExpression );
|
||||
}
|
||||
switch ( exclusion ) {
|
||||
case TIES:
|
||||
sb.append( " exclude ties" );
|
||||
break;
|
||||
case CURRENT_ROW:
|
||||
sb.append( " exclude current row" );
|
||||
break;
|
||||
case GROUP:
|
||||
sb.append( " exclude group" );
|
||||
break;
|
||||
}
|
||||
}
|
||||
window.appendHqlString( sb );
|
||||
sb.append( ')' );
|
||||
}
|
||||
|
||||
private static void renderFrameKind(StringBuilder sb, FrameKind kind, SqmExpression<?> expression) {
|
||||
switch ( kind ) {
|
||||
case CURRENT_ROW:
|
||||
sb.append( "current row" );
|
||||
break;
|
||||
case UNBOUNDED_PRECEDING:
|
||||
sb.append( "unbounded preceding" );
|
||||
break;
|
||||
case UNBOUNDED_FOLLOWING:
|
||||
sb.append( "unbounded following" );
|
||||
break;
|
||||
case OFFSET_PRECEDING:
|
||||
expression.appendHqlString( sb );
|
||||
sb.append( " preceding" );
|
||||
break;
|
||||
case OFFSET_FOLLOWING:
|
||||
expression.appendHqlString( sb );
|
||||
sb.append( " following" );
|
||||
break;
|
||||
default:
|
||||
throw new UnsupportedOperationException( "Unsupported frame kind: " + kind );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,249 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
|
||||
*/
|
||||
package org.hibernate.query.sqm.tree.expression;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.hibernate.query.criteria.JpaWindow;
|
||||
import org.hibernate.query.sqm.FrameExclusion;
|
||||
import org.hibernate.query.sqm.FrameKind;
|
||||
import org.hibernate.query.sqm.FrameMode;
|
||||
import org.hibernate.query.sqm.NodeBuilder;
|
||||
import org.hibernate.query.sqm.SemanticQueryWalker;
|
||||
import org.hibernate.query.sqm.tree.AbstractSqmNode;
|
||||
import org.hibernate.query.sqm.tree.SqmCopyContext;
|
||||
import org.hibernate.query.sqm.tree.SqmVisitableNode;
|
||||
import org.hibernate.query.sqm.tree.select.SqmSortSpecification;
|
||||
|
||||
import jakarta.persistence.criteria.Expression;
|
||||
import jakarta.persistence.criteria.Order;
|
||||
|
||||
import static org.hibernate.query.sqm.FrameExclusion.NO_OTHERS;
|
||||
import static org.hibernate.query.sqm.FrameKind.CURRENT_ROW;
|
||||
import static org.hibernate.query.sqm.FrameKind.UNBOUNDED_PRECEDING;
|
||||
import static org.hibernate.query.sqm.FrameMode.ROWS;
|
||||
|
||||
/**
|
||||
* @author Marco Belladelli
|
||||
*/
|
||||
public class SqmWindow extends AbstractSqmNode implements JpaWindow, SqmVisitableNode {
|
||||
private final List<SqmExpression<?>> partitions;
|
||||
private final List<SqmSortSpecification> orderList;
|
||||
private final FrameMode mode;
|
||||
private final FrameKind startKind;
|
||||
private final SqmExpression<?> startExpression;
|
||||
private final FrameKind endKind;
|
||||
private final SqmExpression<?> endExpression;
|
||||
private final FrameExclusion exclusion;
|
||||
|
||||
public SqmWindow(NodeBuilder nodeBuilder) {
|
||||
this(
|
||||
nodeBuilder,
|
||||
new ArrayList<>(),
|
||||
new ArrayList<>(),
|
||||
ROWS,
|
||||
UNBOUNDED_PRECEDING,
|
||||
null,
|
||||
CURRENT_ROW,
|
||||
null,
|
||||
NO_OTHERS
|
||||
);
|
||||
}
|
||||
|
||||
public SqmWindow(
|
||||
NodeBuilder nodeBuilder,
|
||||
List<SqmExpression<?>> partitions,
|
||||
List<SqmSortSpecification> orderList,
|
||||
FrameMode mode,
|
||||
FrameKind startKind,
|
||||
SqmExpression<?> startExpression,
|
||||
FrameKind endKind,
|
||||
SqmExpression<?> endExpression,
|
||||
FrameExclusion exclusion) {
|
||||
super( nodeBuilder );
|
||||
this.partitions = partitions;
|
||||
this.orderList = orderList;
|
||||
this.mode = mode;
|
||||
this.startKind = startKind;
|
||||
this.startExpression = startExpression;
|
||||
this.endKind = endKind;
|
||||
this.endExpression = endExpression;
|
||||
this.exclusion = exclusion;
|
||||
}
|
||||
|
||||
public List<SqmExpression<?>> getPartitions() {
|
||||
return partitions;
|
||||
}
|
||||
|
||||
public List<SqmSortSpecification> getOrderList() {
|
||||
return orderList;
|
||||
}
|
||||
|
||||
public SqmExpression<?> getStartExpression() {
|
||||
return startExpression;
|
||||
}
|
||||
|
||||
public SqmExpression<?> getEndExpression() {
|
||||
return endExpression;
|
||||
}
|
||||
|
||||
public FrameMode getMode() {
|
||||
return mode;
|
||||
}
|
||||
|
||||
public FrameKind getStartKind() {
|
||||
return startKind;
|
||||
}
|
||||
|
||||
public FrameKind getEndKind() {
|
||||
return endKind;
|
||||
}
|
||||
|
||||
public FrameExclusion getExclusion() {
|
||||
return exclusion;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JpaWindow partitionBy(Expression<?>... expressions) {
|
||||
for (Expression<?> expression : expressions) {
|
||||
this.partitions.add((SqmExpression<?>) expression);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JpaWindow orderBy(Order... orders) {
|
||||
for (Order order : orders) {
|
||||
this.orderList.add( (SqmSortSpecification) order );
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public SqmWindow copy(SqmCopyContext context) {
|
||||
final SqmWindow existing = context.getCopy( this );
|
||||
if ( existing != null ) {
|
||||
return existing;
|
||||
}
|
||||
final List<SqmExpression<?>> partitionsCopy = new ArrayList<>( this.partitions.size() );
|
||||
for ( SqmExpression<?> partition : this.partitions ) {
|
||||
partitions.add( partition.copy( context ) );
|
||||
}
|
||||
final List<SqmSortSpecification> orderListCopy = new ArrayList<>( this.orderList.size() );
|
||||
for ( SqmSortSpecification sortSpecification : this.orderList ) {
|
||||
orderList.add( sortSpecification.copy( context ) );
|
||||
}
|
||||
return context.registerCopy(
|
||||
this,
|
||||
new SqmWindow(
|
||||
nodeBuilder(),
|
||||
partitionsCopy,
|
||||
orderListCopy,
|
||||
mode,
|
||||
startKind,
|
||||
startExpression == null ? null : startExpression.copy( context ),
|
||||
endKind,
|
||||
endExpression == null ? null : endExpression.copy( context ),
|
||||
exclusion
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <X> X accept(SemanticQueryWalker<X> walker) {
|
||||
return walker.visitWindow( this );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void appendHqlString(StringBuilder sb) {
|
||||
boolean needsWhitespace = false;
|
||||
if ( !this.partitions.isEmpty() ) {
|
||||
needsWhitespace = true;
|
||||
sb.append( "partition by " );
|
||||
this.partitions.get( 0 ).appendHqlString( sb );
|
||||
for ( int i = 1; i < this.partitions.size(); i++ ) {
|
||||
sb.append( ',' );
|
||||
this.partitions.get( i ).appendHqlString( sb );
|
||||
}
|
||||
}
|
||||
if ( !orderList.isEmpty() ) {
|
||||
if ( needsWhitespace ) {
|
||||
sb.append( ' ' );
|
||||
}
|
||||
needsWhitespace = true;
|
||||
sb.append( "order by " );
|
||||
orderList.get( 0 ).appendHqlString( sb );
|
||||
for ( int i = 1; i < orderList.size(); i++ ) {
|
||||
sb.append( ',' );
|
||||
orderList.get( i ).appendHqlString( sb );
|
||||
}
|
||||
}
|
||||
if ( mode == ROWS && startKind == UNBOUNDED_PRECEDING && endKind == CURRENT_ROW && exclusion == NO_OTHERS ) {
|
||||
// This is the default, so we don't need to render anything
|
||||
}
|
||||
else {
|
||||
if ( needsWhitespace ) {
|
||||
sb.append( ' ' );
|
||||
}
|
||||
switch ( mode ) {
|
||||
case GROUPS:
|
||||
sb.append( "groups " );
|
||||
break;
|
||||
case RANGE:
|
||||
sb.append( "range " );
|
||||
break;
|
||||
case ROWS:
|
||||
sb.append( "rows " );
|
||||
break;
|
||||
}
|
||||
if ( endKind == CURRENT_ROW ) {
|
||||
renderFrameKind( sb, startKind, startExpression );
|
||||
}
|
||||
else {
|
||||
sb.append( "between " );
|
||||
renderFrameKind( sb, startKind, startExpression );
|
||||
sb.append( " and " );
|
||||
renderFrameKind( sb, endKind, endExpression );
|
||||
}
|
||||
switch ( exclusion ) {
|
||||
case TIES:
|
||||
sb.append( " exclude ties" );
|
||||
break;
|
||||
case CURRENT_ROW:
|
||||
sb.append( " exclude current row" );
|
||||
break;
|
||||
case GROUP:
|
||||
sb.append( " exclude group" );
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void renderFrameKind(StringBuilder sb, FrameKind kind, SqmExpression<?> expression) {
|
||||
switch ( kind ) {
|
||||
case CURRENT_ROW:
|
||||
sb.append( "current row" );
|
||||
break;
|
||||
case UNBOUNDED_PRECEDING:
|
||||
sb.append( "unbounded preceding" );
|
||||
break;
|
||||
case UNBOUNDED_FOLLOWING:
|
||||
sb.append( "unbounded following" );
|
||||
break;
|
||||
case OFFSET_PRECEDING:
|
||||
expression.appendHqlString( sb );
|
||||
sb.append( " preceding" );
|
||||
break;
|
||||
case OFFSET_FOLLOWING:
|
||||
expression.appendHqlString( sb );
|
||||
sb.append( " following" );
|
||||
break;
|
||||
default:
|
||||
throw new UnsupportedOperationException( "Unsupported frame kind: " + kind );
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,266 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
|
||||
*/
|
||||
package org.hibernate.orm.test.query.criteria;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import org.hibernate.query.criteria.HibernateCriteriaBuilder;
|
||||
import org.hibernate.query.criteria.JpaExpression;
|
||||
|
||||
import org.hibernate.testing.orm.domain.StandardDomainModel;
|
||||
import org.hibernate.testing.orm.domain.gambit.EntityOfBasics;
|
||||
import org.hibernate.testing.orm.junit.DialectFeatureChecks;
|
||||
import org.hibernate.testing.orm.junit.DomainModel;
|
||||
import org.hibernate.testing.orm.junit.RequiresDialectFeature;
|
||||
import org.hibernate.testing.orm.junit.ServiceRegistry;
|
||||
import org.hibernate.testing.orm.junit.SessionFactory;
|
||||
import org.hibernate.testing.orm.junit.SessionFactoryScope;
|
||||
import org.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 jakarta.persistence.criteria.CriteriaQuery;
|
||||
import jakarta.persistence.criteria.Join;
|
||||
import jakarta.persistence.criteria.Root;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
/**
|
||||
* @author Marco Belladelli
|
||||
*/
|
||||
@ServiceRegistry
|
||||
@DomainModel(standardModels = StandardDomainModel.GAMBIT)
|
||||
@SessionFactory
|
||||
public class CriteriaOrderedSetAggregateTest {
|
||||
|
||||
@BeforeEach
|
||||
public void prepareData(SessionFactoryScope scope) {
|
||||
scope.inTransaction( em -> {
|
||||
Date now = new Date();
|
||||
|
||||
EntityOfBasics entity1 = new EntityOfBasics();
|
||||
entity1.setId( 1 );
|
||||
entity1.setTheString( "5" );
|
||||
entity1.setTheInt( 5 );
|
||||
entity1.setTheInteger( -1 );
|
||||
entity1.setTheDouble( 5.0 );
|
||||
entity1.setTheDate( now );
|
||||
entity1.setTheBoolean( true );
|
||||
em.persist( entity1 );
|
||||
|
||||
EntityOfBasics entity2 = new EntityOfBasics();
|
||||
entity2.setId( 2 );
|
||||
entity2.setTheString( "6" );
|
||||
entity2.setTheInt( 6 );
|
||||
entity2.setTheInteger( -2 );
|
||||
entity2.setTheDouble( 6.0 );
|
||||
entity2.setTheBoolean( true );
|
||||
em.persist( entity2 );
|
||||
|
||||
EntityOfBasics entity3 = new EntityOfBasics();
|
||||
entity3.setId( 3 );
|
||||
entity3.setTheString( "7" );
|
||||
entity3.setTheInt( 7 );
|
||||
entity3.setTheInteger( 3 );
|
||||
entity3.setTheDouble( 7.0 );
|
||||
entity3.setTheBoolean( false );
|
||||
entity3.setTheDate( new Date( now.getTime() + 200000L ) );
|
||||
em.persist( entity3 );
|
||||
|
||||
EntityOfBasics entity4 = new EntityOfBasics();
|
||||
entity4.setId( 4 );
|
||||
entity4.setTheString( "13" );
|
||||
entity4.setTheInt( 13 );
|
||||
entity4.setTheInteger( 4 );
|
||||
entity4.setTheDouble( 13.0 );
|
||||
entity4.setTheBoolean( false );
|
||||
entity4.setTheDate( new Date( now.getTime() + 300000L ) );
|
||||
em.persist( entity4 );
|
||||
|
||||
EntityOfBasics entity5 = new EntityOfBasics();
|
||||
entity5.setId( 5 );
|
||||
entity5.setTheString( "5" );
|
||||
entity5.setTheInt( 5 );
|
||||
entity5.setTheInteger( 5 );
|
||||
entity5.setTheDouble( 9.0 );
|
||||
entity5.setTheBoolean( false );
|
||||
em.persist( entity5 );
|
||||
} );
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
public void tearDown(SessionFactoryScope scope) {
|
||||
scope.inTransaction( session -> session.createQuery( "delete from EntityOfBasics" ).executeUpdate() );
|
||||
}
|
||||
|
||||
@Test
|
||||
@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsStringAggregation.class)
|
||||
public void testListaggWithoutOrder(SessionFactoryScope scope) {
|
||||
scope.inTransaction( session -> {
|
||||
HibernateCriteriaBuilder cb = session.getCriteriaBuilder();
|
||||
CriteriaQuery<String> cr = cb.createQuery( String.class );
|
||||
Root<EntityOfBasics> root = cr.from( EntityOfBasics.class );
|
||||
|
||||
JpaExpression<String> function = cb.listagg( null, null, root.get( "theString" ), cb.literal( "," ) );
|
||||
|
||||
cr.select( function );
|
||||
List<String> elements = Arrays.asList( session.createQuery( cr ).getSingleResult().split( "," ) );
|
||||
List<String> expectedElements = List.of( "13", "5", "5", "6", "7" );
|
||||
elements.sort( String.CASE_INSENSITIVE_ORDER );
|
||||
|
||||
assertEquals( expectedElements, elements );
|
||||
} );
|
||||
}
|
||||
|
||||
@Test
|
||||
@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsStringAggregation.class)
|
||||
public void testListagg(SessionFactoryScope scope) {
|
||||
scope.inTransaction( session -> {
|
||||
HibernateCriteriaBuilder cb = session.getCriteriaBuilder();
|
||||
CriteriaQuery<String> cr = cb.createQuery( String.class );
|
||||
Root<EntityOfBasics> root = cr.from( EntityOfBasics.class );
|
||||
|
||||
JpaExpression<String> function = cb.listagg(
|
||||
cb.desc( root.get( "id" ) ),
|
||||
null,
|
||||
root.get( "theString" ),
|
||||
cb.literal( "," )
|
||||
);
|
||||
|
||||
cr.select( function );
|
||||
String result = session.createQuery( cr ).getSingleResult();
|
||||
assertEquals( "5,13,7,6,5", result );
|
||||
} );
|
||||
}
|
||||
|
||||
@Test
|
||||
@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsStringAggregation.class)
|
||||
public void testListaggWithFilter(SessionFactoryScope scope) {
|
||||
scope.inTransaction( session -> {
|
||||
HibernateCriteriaBuilder cb = session.getCriteriaBuilder();
|
||||
CriteriaQuery<String> cr = cb.createQuery( String.class );
|
||||
Root<EntityOfBasics> root = cr.from( EntityOfBasics.class );
|
||||
|
||||
JpaExpression<String> function = cb.listagg(
|
||||
cb.desc( root.get( "id" ) ),
|
||||
cb.lt( root.get( "theInt" ), cb.literal( 10 ) ),
|
||||
root.get( "theString" ),
|
||||
cb.literal( "," )
|
||||
);
|
||||
|
||||
cr.select( function );
|
||||
String result = session.createQuery( cr ).getSingleResult();
|
||||
assertEquals( "5,7,6,5", result );
|
||||
} );
|
||||
}
|
||||
|
||||
@Test
|
||||
@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsStringAggregation.class)
|
||||
public void testListaggWithNullsClause(SessionFactoryScope scope) {
|
||||
scope.inTransaction( session -> {
|
||||
HibernateCriteriaBuilder cb = session.getCriteriaBuilder();
|
||||
CriteriaQuery<String> cr = cb.createQuery( String.class );
|
||||
Root<EntityOfBasics> root = cr.from( EntityOfBasics.class );
|
||||
|
||||
JpaExpression<String> function = cb.listagg(
|
||||
cb.desc( root.get( "id" ), true ),
|
||||
null,
|
||||
root.get( "theString" ),
|
||||
cb.literal( "," )
|
||||
);
|
||||
|
||||
cr.select( function );
|
||||
String result = session.createQuery( cr ).getSingleResult();
|
||||
assertEquals( "5,13,7,6,5", result );
|
||||
} );
|
||||
}
|
||||
|
||||
@Test
|
||||
@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsInverseDistributionFunctions.class)
|
||||
public void testInverseDistribution(SessionFactoryScope scope) {
|
||||
scope.inTransaction( session -> {
|
||||
HibernateCriteriaBuilder cb = session.getCriteriaBuilder();
|
||||
CriteriaQuery<Integer> cr = cb.createQuery( Integer.class );
|
||||
Root<EntityOfBasics> root = cr.from( EntityOfBasics.class );
|
||||
|
||||
JpaExpression<Integer> function = cb.percentileDisc(
|
||||
cb.asc( root.get( "theInt" ) ),
|
||||
null,
|
||||
cb.literal( 0.5 )
|
||||
);
|
||||
|
||||
cr.select( function );
|
||||
Integer result = session.createQuery( cr ).getSingleResult();
|
||||
assertEquals( 6, result );
|
||||
} );
|
||||
}
|
||||
|
||||
@Test
|
||||
@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsHypotheticalSetFunctions.class)
|
||||
public void testHypotheticalSetPercentRank(SessionFactoryScope scope) {
|
||||
scope.inTransaction( session -> {
|
||||
HibernateCriteriaBuilder cb = session.getCriteriaBuilder();
|
||||
CriteriaQuery<Double> cr = cb.createQuery( Double.class );
|
||||
Root<EntityOfBasics> root = cr.from( EntityOfBasics.class );
|
||||
|
||||
JpaExpression<Double> function = cb.percentRank( cb.asc( root.get( "theInt" ) ), null, cb.literal( 5 ) );
|
||||
|
||||
cr.select( function );
|
||||
Double result = session.createQuery( cr ).getSingleResult();
|
||||
assertEquals( 0.0D, result );
|
||||
} );
|
||||
}
|
||||
|
||||
@Test
|
||||
@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsHypotheticalSetFunctions.class)
|
||||
public void testHypotheticalSetRank(SessionFactoryScope scope) {
|
||||
scope.inTransaction( session -> {
|
||||
HibernateCriteriaBuilder cb = session.getCriteriaBuilder();
|
||||
CriteriaQuery<Long> cr = cb.createQuery( Long.class );
|
||||
Root<EntityOfBasics> root = cr.from( EntityOfBasics.class );
|
||||
|
||||
JpaExpression<Long> function = cb.rank( cb.asc( root.get( "theInt" ) ), null, cb.literal( 5 ) );
|
||||
|
||||
cr.select( function );
|
||||
Long result = session.createQuery( cr ).getSingleResult();
|
||||
assertEquals( 1L, result );
|
||||
} );
|
||||
}
|
||||
|
||||
// @Test
|
||||
@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsHypotheticalSetFunctions.class)
|
||||
public void testHypotheticalSetRankWithGroupByHavingOrderByLimit(SessionFactoryScope scope) {
|
||||
scope.inTransaction( session -> {
|
||||
HibernateCriteriaBuilder cb = session.getCriteriaBuilder();
|
||||
CriteriaQuery<Tuple> cr = cb.createQuery( Tuple.class );
|
||||
Root<EntityOfBasics> root1 = cr.from( EntityOfBasics.class );
|
||||
Root<EntityOfBasics> root2 = cr.from( EntityOfBasics.class );
|
||||
|
||||
JpaExpression<Long> function = cb.rank( cb.asc( root1.get( "theInt" ) ), null, cb.literal( 5 ) );
|
||||
|
||||
cr.multiselect( root2.get( "id" ), function )
|
||||
.groupBy( root2.get( "id" ) ).having( cb.gt( root2.get( "id" ), cb.literal( 1 ) ) )
|
||||
.orderBy( cb.asc( cb.literal( 1 ) ), cb.asc( cb.literal( 2 ) ) );
|
||||
|
||||
// todo marco : this test causes problems but only with mssql and db2, the sql obtained is not correct.
|
||||
// we are trying to get something like this query:
|
||||
// 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
|
||||
|
||||
List<Tuple> resultList = session.createQuery( cr ).setFirstResult( 1 ).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 ) );
|
||||
} );
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,282 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
|
||||
*/
|
||||
package org.hibernate.orm.test.query.criteria;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import org.hibernate.dialect.DB2Dialect;
|
||||
import org.hibernate.dialect.SQLServerDialect;
|
||||
import org.hibernate.query.criteria.HibernateCriteriaBuilder;
|
||||
import org.hibernate.query.criteria.JpaExpression;
|
||||
import org.hibernate.query.criteria.JpaWindow;
|
||||
|
||||
import org.hibernate.testing.orm.domain.StandardDomainModel;
|
||||
import org.hibernate.testing.orm.domain.gambit.EntityOfBasics;
|
||||
import org.hibernate.testing.orm.junit.DialectFeatureChecks;
|
||||
import org.hibernate.testing.orm.junit.DomainModel;
|
||||
import org.hibernate.testing.orm.junit.RequiresDialectFeature;
|
||||
import org.hibernate.testing.orm.junit.ServiceRegistry;
|
||||
import org.hibernate.testing.orm.junit.SessionFactory;
|
||||
import org.hibernate.testing.orm.junit.SessionFactoryScope;
|
||||
import org.hibernate.testing.orm.junit.SkipForDialect;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import jakarta.persistence.Tuple;
|
||||
import jakarta.persistence.TypedQuery;
|
||||
import jakarta.persistence.criteria.CriteriaQuery;
|
||||
import jakarta.persistence.criteria.Root;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
|
||||
/**
|
||||
* @author Marco Belladelli
|
||||
*/
|
||||
@ServiceRegistry
|
||||
@DomainModel(standardModels = StandardDomainModel.GAMBIT)
|
||||
@SessionFactory
|
||||
@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsWindowFunctions.class)
|
||||
public class CriteriaWindowFunctionTest {
|
||||
@BeforeEach
|
||||
public void prepareData(SessionFactoryScope scope) {
|
||||
scope.inTransaction(
|
||||
em -> {
|
||||
Date now = new Date();
|
||||
|
||||
EntityOfBasics entity1 = new EntityOfBasics();
|
||||
entity1.setId( 1 );
|
||||
entity1.setTheString( "5" );
|
||||
entity1.setTheInt( 5 );
|
||||
entity1.setTheInteger( -1 );
|
||||
entity1.setTheDouble( 5.0 );
|
||||
entity1.setTheDate( now );
|
||||
entity1.setTheBoolean( true );
|
||||
em.persist( entity1 );
|
||||
|
||||
EntityOfBasics entity2 = new EntityOfBasics();
|
||||
entity2.setId( 2 );
|
||||
entity2.setTheString( "6" );
|
||||
entity2.setTheInt( 6 );
|
||||
entity2.setTheInteger( -2 );
|
||||
entity2.setTheDouble( 6.0 );
|
||||
entity2.setTheBoolean( true );
|
||||
em.persist( entity2 );
|
||||
|
||||
EntityOfBasics entity3 = new EntityOfBasics();
|
||||
entity3.setId( 3 );
|
||||
entity3.setTheString( "7" );
|
||||
entity3.setTheInt( 7 );
|
||||
entity3.setTheInteger( 3 );
|
||||
entity3.setTheDouble( 7.0 );
|
||||
entity3.setTheBoolean( false );
|
||||
entity3.setTheDate( new Date( now.getTime() + 200000L ) );
|
||||
em.persist( entity3 );
|
||||
|
||||
EntityOfBasics entity4 = new EntityOfBasics();
|
||||
entity4.setId( 4 );
|
||||
entity4.setTheString( "13" );
|
||||
entity4.setTheInt( 13 );
|
||||
entity4.setTheInteger( 4 );
|
||||
entity4.setTheDouble( 13.0 );
|
||||
entity4.setTheBoolean( false );
|
||||
entity4.setTheDate( new Date( now.getTime() + 300000L ) );
|
||||
em.persist( entity4 );
|
||||
|
||||
EntityOfBasics entity5 = new EntityOfBasics();
|
||||
entity5.setId( 5 );
|
||||
entity5.setTheString( "5" );
|
||||
entity5.setTheInt( 5 );
|
||||
entity5.setTheInteger( 5 );
|
||||
entity5.setTheDouble( 9.0 );
|
||||
entity5.setTheBoolean( false );
|
||||
em.persist( entity5 );
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
public void tearDown(SessionFactoryScope scope) {
|
||||
scope.inTransaction(
|
||||
session -> session.createQuery( "delete from EntityOfBasics" ).executeUpdate()
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRowNumberWithoutOrder(SessionFactoryScope scope) {
|
||||
scope.inTransaction(
|
||||
session -> {
|
||||
HibernateCriteriaBuilder cb = session.getCriteriaBuilder();
|
||||
CriteriaQuery<Long> cr = cb.createQuery( Long.class );
|
||||
cr.from( EntityOfBasics.class );
|
||||
|
||||
JpaWindow window = cb.createWindow();
|
||||
JpaExpression<Long> rowNumber = cb.rowNumber( window );
|
||||
|
||||
cr.select( rowNumber ).orderBy( cb.asc( cb.literal( 1 ) ) );
|
||||
List<Long> resultList = session.createQuery( cr ).getResultList();
|
||||
assertEquals( 5, resultList.size() );
|
||||
assertEquals( 1L, resultList.get( 0 ) );
|
||||
assertEquals( 2L, resultList.get( 1 ) );
|
||||
assertEquals( 3L, resultList.get( 2 ) );
|
||||
assertEquals( 4L, resultList.get( 3 ) );
|
||||
assertEquals( 5L, resultList.get( 4 ) );
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFirstValue(SessionFactoryScope scope) {
|
||||
scope.inTransaction(
|
||||
session -> {
|
||||
HibernateCriteriaBuilder cb = session.getCriteriaBuilder();
|
||||
CriteriaQuery<Integer> cr = cb.createQuery( Integer.class );
|
||||
Root<EntityOfBasics> root = cr.from( EntityOfBasics.class );
|
||||
|
||||
JpaWindow window = cb.createWindow().orderBy( cb.desc( root.get( "theInt" ) ) );
|
||||
JpaExpression<Integer> firstValue = cb.firstValue( root.get( "theInt" ), window );
|
||||
|
||||
cr.select( firstValue ).orderBy( cb.asc( cb.literal( 1 ) ) );
|
||||
List<Integer> resultList = session.createQuery( cr ).getResultList();
|
||||
assertEquals( 5, resultList.size() );
|
||||
assertEquals( 13, resultList.get( 0 ) );
|
||||
assertEquals( 13, resultList.get( 1 ) );
|
||||
assertEquals( 13, resultList.get( 2 ) );
|
||||
assertEquals( 13, resultList.get( 3 ) );
|
||||
assertEquals( 13, resultList.get( 4 ) );
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
@SkipForDialect(dialectClass = SQLServerDialect.class)
|
||||
@SkipForDialect(dialectClass = DB2Dialect.class)
|
||||
public void testNthValue(SessionFactoryScope scope) {
|
||||
scope.inTransaction(
|
||||
session -> {
|
||||
HibernateCriteriaBuilder cb = session.getCriteriaBuilder();
|
||||
CriteriaQuery<Integer> cr = cb.createQuery( Integer.class );
|
||||
Root<EntityOfBasics> root = cr.from( EntityOfBasics.class );
|
||||
|
||||
JpaWindow window = cb.createWindow().orderBy( cb.desc( root.get( "theInt" ) ) );
|
||||
JpaExpression<Integer> nthValue = cb.nthValue(
|
||||
root.get( "theInt" ),
|
||||
cb.literal( 2 ),
|
||||
window
|
||||
);
|
||||
|
||||
// todo marco : db2 throws java.lang.IndexOutOfBoundsException: Index 1 out of bounds for length 1
|
||||
// on getResultList() line, and SqlServer doesn't support nth_value function (even tho it's in Dialect)
|
||||
|
||||
cr.select( nthValue ).orderBy( cb.asc( cb.literal( 1 ), true ) );
|
||||
List<Integer> resultList = session.createQuery( cr ).getResultList();
|
||||
assertEquals( 5, resultList.size() );
|
||||
assertNull( resultList.get( 0 ) );
|
||||
assertEquals( 7, resultList.get( 1 ) );
|
||||
assertEquals( 7, resultList.get( 2 ) );
|
||||
assertEquals( 7, resultList.get( 3 ) );
|
||||
assertEquals( 7, resultList.get( 4 ) );
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRank(SessionFactoryScope scope) {
|
||||
scope.inTransaction(
|
||||
session -> {
|
||||
HibernateCriteriaBuilder cb = session.getCriteriaBuilder();
|
||||
CriteriaQuery<Long> cr = cb.createQuery( Long.class );
|
||||
Root<EntityOfBasics> root = cr.from( EntityOfBasics.class );
|
||||
|
||||
JpaWindow window = cb.createWindow()
|
||||
.partitionBy( root.get( "theInt" ) )
|
||||
.orderBy( cb.asc( root.get( "id" ) ) );
|
||||
JpaExpression<Long> rank = cb.rank( window );
|
||||
|
||||
cr.select( rank ).orderBy( cb.asc( cb.literal( 1 ) ) );
|
||||
List<Long> resultList = session.createQuery( cr ).getResultList();
|
||||
assertEquals( 5, resultList.size() );
|
||||
assertEquals( 1L, resultList.get( 0 ) );
|
||||
assertEquals( 1L, resultList.get( 1 ) );
|
||||
assertEquals( 1L, resultList.get( 2 ) );
|
||||
assertEquals( 1L, resultList.get( 3 ) );
|
||||
assertEquals( 2L, resultList.get( 4 ) );
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReusableWindow(SessionFactoryScope scope) {
|
||||
scope.inTransaction(
|
||||
session -> {
|
||||
HibernateCriteriaBuilder cb = session.getCriteriaBuilder();
|
||||
CriteriaQuery<Tuple> cr = cb.createTupleQuery();
|
||||
Root<EntityOfBasics> root = cr.from( EntityOfBasics.class );
|
||||
|
||||
JpaWindow window = cb.createWindow()
|
||||
.partitionBy( root.get( "theInt" ) )
|
||||
.orderBy( cb.asc( root.get( "id" ) ) );
|
||||
JpaExpression<Long> rowNumber = cb.rowNumber( window );
|
||||
JpaExpression<Double> percentRank = cb.percentRank( window );
|
||||
JpaExpression<Double> cumeDist = cb.cumeDist( window );
|
||||
|
||||
cr.multiselect( rowNumber, percentRank, cumeDist ).orderBy( cb.asc( cb.literal( 1 ) ) );
|
||||
List<Tuple> resultList = session.createQuery( cr ).getResultList();
|
||||
assertEquals( 5, resultList.size() );
|
||||
assertEquals( 0D, resultList.get( 0 ).get( 1 ) );
|
||||
assertEquals( 0D, resultList.get( 1 ).get( 1 ) );
|
||||
assertEquals( 0D, resultList.get( 2 ).get( 1 ) );
|
||||
assertEquals( 0D, resultList.get( 3 ).get( 1 ) );
|
||||
assertEquals( 1D, resultList.get( 4 ).get( 1 ) );
|
||||
assertEquals( 1D, resultList.get( 4 ).get( 2 ) );
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// @Test
|
||||
public void testSumWithFilterAsWindowFunction(SessionFactoryScope scope) {
|
||||
scope.inTransaction(
|
||||
session -> {
|
||||
// todo marco : add filter clause predicate to CriteriaBuilder ?
|
||||
// problem with getting the window functions as aggregate, @see SqmCriteriaNodeBuilder#windowFunction
|
||||
TypedQuery<Long> q = session.createQuery(
|
||||
"select sum(eob.theInt) filter (where eob.theInt > 5) over (order by eob.theInt) from EntityOfBasics eob order by eob.theInt",
|
||||
Long.class
|
||||
);
|
||||
List<Long> resultList = q.getResultList();
|
||||
assertEquals( 5L, resultList.size() );
|
||||
assertNull( resultList.get( 0 ) );
|
||||
assertNull( resultList.get( 1 ) );
|
||||
assertEquals( 6L, resultList.get( 2 ) );
|
||||
assertEquals( 13L, resultList.get( 3 ) );
|
||||
assertEquals( 26L, resultList.get( 4 ) );
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// @Test
|
||||
public void testFrame(SessionFactoryScope scope) {
|
||||
scope.inTransaction(
|
||||
session -> {
|
||||
// todo marco : how should we do window function frames in Criteria ?
|
||||
TypedQuery<Integer> q = session.createQuery(
|
||||
"select first_value(eob.theInt) over (order by eob.id rows between 2 preceding and current row) from EntityOfBasics eob order by eob.id",
|
||||
Integer.class
|
||||
);
|
||||
List<Integer> resultList = q.getResultList();
|
||||
assertEquals( 5, resultList.size() );
|
||||
assertEquals( 5, resultList.get( 0 ) );
|
||||
assertEquals( 5, resultList.get( 1 ) );
|
||||
assertEquals( 5, resultList.get( 2 ) );
|
||||
assertEquals( 6, resultList.get( 3 ) );
|
||||
assertEquals( 7, resultList.get( 4 ) );
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue