HHH-15654 Added 'simple' aggregate functions and frame APIs for JpaWindows
This commit is contained in:
parent
28b0d6c5a4
commit
6d9c448db2
|
@ -32,6 +32,7 @@ import jakarta.persistence.criteria.SetJoin;
|
|||
import jakarta.persistence.criteria.Subquery;
|
||||
|
||||
import org.hibernate.Incubating;
|
||||
import org.hibernate.query.sqm.FrameKind;
|
||||
import org.hibernate.query.sqm.NullPrecedence;
|
||||
import org.hibernate.query.sqm.SortOrder;
|
||||
import org.hibernate.query.sqm.tree.expression.SqmExpression;
|
||||
|
@ -897,12 +898,61 @@ public interface HibernateCriteriaBuilder extends CriteriaBuilder {
|
|||
// Window functions
|
||||
|
||||
/**
|
||||
* Create an empty window to use for window and set-order aggregate functions.
|
||||
* Create an empty {@link JpaWindow} to use with window and aggregate functions.
|
||||
*
|
||||
* @return the empty window
|
||||
*/
|
||||
JpaWindow createWindow();
|
||||
|
||||
/**
|
||||
* Create a window frame of type {@link FrameKind#UNBOUNDED_PRECEDING} to use with {@link JpaWindow}s.
|
||||
*
|
||||
* @return the window frame
|
||||
*/
|
||||
JpaWindowFrame frameUnboundedPreceding();
|
||||
|
||||
/**
|
||||
* @see #frameBetweenPreceding(Expression)
|
||||
*/
|
||||
JpaWindowFrame frameBetweenPreceding(int offset);
|
||||
|
||||
/**
|
||||
* Create window frame of type {@link FrameKind#OFFSET_PRECEDING} to use with {@link JpaWindow}s.
|
||||
*
|
||||
* @param offset the {@code offset} expression
|
||||
*
|
||||
* @return the window frame
|
||||
*/
|
||||
JpaWindowFrame frameBetweenPreceding(Expression<?> offset);
|
||||
|
||||
/**
|
||||
* Create a window frame of type {@link FrameKind#CURRENT_ROW} to use with {@link JpaWindow}s.
|
||||
*
|
||||
* @return the window frame
|
||||
*/
|
||||
JpaWindowFrame frameCurrentRow();
|
||||
|
||||
/**
|
||||
* @see #frameBetweenFollowing(Expression)
|
||||
*/
|
||||
JpaWindowFrame frameBetweenFollowing(int offset);
|
||||
|
||||
/**
|
||||
* Create a window frame of type {@link FrameKind#OFFSET_FOLLOWING} to use with {@link JpaWindow}s.
|
||||
*
|
||||
* @param offset the {@code offset} expression
|
||||
*
|
||||
* @return the window frame
|
||||
*/
|
||||
JpaWindowFrame frameBetweenFollowing(Expression<?> offset);
|
||||
|
||||
/**
|
||||
* Create a window frame of type {@link FrameKind#UNBOUNDED_FOLLOWING} to use with {@link JpaWindow}s.
|
||||
*
|
||||
* @return the window frame
|
||||
*/
|
||||
JpaWindowFrame frameUnboundedFollowing();
|
||||
|
||||
/**
|
||||
* Create a generic window function expression that will be applied
|
||||
* over the specified {@link JpaWindow window}.
|
||||
|
@ -1017,6 +1067,120 @@ public interface HibernateCriteriaBuilder extends CriteriaBuilder {
|
|||
*/
|
||||
JpaExpression<Double> cumeDist(JpaWindow window);
|
||||
|
||||
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// Aggregate functions
|
||||
|
||||
/**
|
||||
* @see #functionAggregate(String, Class, JpaPredicate, JpaWindow, Expression...)
|
||||
*/
|
||||
<T> JpaExpression<T> functionAggregate(
|
||||
String name,
|
||||
Class<T> type,
|
||||
JpaPredicate filter,
|
||||
Expression<?>... args);
|
||||
|
||||
/**
|
||||
* @see #functionAggregate(String, Class, JpaPredicate, JpaWindow, Expression...)
|
||||
*/
|
||||
<T> JpaExpression<T> functionAggregate(
|
||||
String name,
|
||||
Class<T> type,
|
||||
JpaWindow window,
|
||||
Expression<?>... args);
|
||||
|
||||
/**
|
||||
* Create a generic aggregate function expression.
|
||||
*
|
||||
* @param name name of the ordered set-aggregate function
|
||||
* @param type type of this expression
|
||||
* @param filter optional filter clause
|
||||
* @param window optional window over which to apply the function
|
||||
* @param args optional arguments to the function
|
||||
* @param <T> type of this expression
|
||||
*
|
||||
* @return aggregate function expression
|
||||
*/
|
||||
<T> JpaExpression<T> functionAggregate(
|
||||
String name,
|
||||
Class<T> type,
|
||||
JpaPredicate filter,
|
||||
JpaWindow window,
|
||||
Expression<?>... args);
|
||||
|
||||
/**
|
||||
* @see #sum(Expression, JpaPredicate, JpaWindow)
|
||||
*/
|
||||
<N extends Number> JpaExpression<Number> sum(Expression<N> argument, JpaPredicate filter);
|
||||
|
||||
/**
|
||||
* @see #sum(Expression, JpaPredicate, JpaWindow)
|
||||
*/
|
||||
<N extends Number> JpaExpression<Number> sum(Expression<N> argument, JpaWindow window);
|
||||
|
||||
/**
|
||||
* Create a {@code sum} aggregate function expression.
|
||||
*
|
||||
* @param argument argument to the function
|
||||
* @param filter optional filter clause
|
||||
* @param window optional window over which to apply the function
|
||||
* @param <N> type of the input expression
|
||||
*
|
||||
* @return aggregate function expression
|
||||
*
|
||||
* @see #functionAggregate(String, Class, JpaPredicate, JpaWindow, Expression...)
|
||||
*/
|
||||
<N extends Number> JpaExpression<Number> sum(Expression<N> argument, JpaPredicate filter, JpaWindow window);
|
||||
|
||||
/**
|
||||
* @see #avg(Expression, JpaPredicate, JpaWindow)
|
||||
*/
|
||||
<N extends Number> JpaExpression<Double> avg(Expression<N> argument, JpaPredicate filter);
|
||||
|
||||
/**
|
||||
* @see #avg(Expression, JpaPredicate, JpaWindow)
|
||||
*/
|
||||
<N extends Number> JpaExpression<Double> avg(Expression<N> argument, JpaWindow window);
|
||||
|
||||
/**
|
||||
* Create an {@code avg} aggregate function expression.
|
||||
*
|
||||
* @param argument argument to the function
|
||||
* @param filter optional filter clause
|
||||
* @param window optional window over which to apply the function
|
||||
* @param <N> type of the input expression
|
||||
*
|
||||
* @return aggregate function expression
|
||||
*
|
||||
* @see #functionAggregate(String, Class, JpaPredicate, JpaWindow, Expression...)
|
||||
*/
|
||||
<N extends Number> JpaExpression<Double> avg(Expression<N> argument, JpaPredicate filter, JpaWindow window);
|
||||
|
||||
/**
|
||||
* @see #count(Expression, JpaPredicate, JpaWindow)
|
||||
*/
|
||||
JpaExpression<Long> count(Expression<?> argument, JpaPredicate filter);
|
||||
|
||||
/**
|
||||
* @see #count(Expression, JpaPredicate, JpaWindow)
|
||||
*/
|
||||
JpaExpression<Long> count(Expression<?> argument, JpaWindow window);
|
||||
|
||||
/**
|
||||
* Create a {@code count} aggregate function expression.
|
||||
*
|
||||
* @param argument argument to the function
|
||||
* @param filter optional filter clause
|
||||
* @param window optional window over which to apply the function
|
||||
*
|
||||
* @return aggregate function expression
|
||||
*
|
||||
* @see #functionAggregate(String, Class, JpaPredicate, JpaWindow, Expression...)
|
||||
*/
|
||||
JpaExpression<Long> count(Expression<?> argument, JpaPredicate filter, JpaWindow window);
|
||||
|
||||
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// Ordered-Set Aggregate functions
|
||||
|
||||
/**
|
||||
* @see #functionWithinGroup(String, Class, JpaOrder, JpaPredicate, JpaWindow, Expression...)
|
||||
*/
|
||||
|
@ -1162,8 +1326,10 @@ public interface HibernateCriteriaBuilder extends CriteriaBuilder {
|
|||
* @param sortExpression the sort expression
|
||||
* @param sortOrder the sort order
|
||||
* @param nullPrecedence the null precedence
|
||||
* @return ordered set-aggregate expression
|
||||
* @param <T> type of this expression
|
||||
*
|
||||
* @return ordered set-aggregate expression
|
||||
*
|
||||
* @see #functionWithinGroup(String, Class, JpaOrder, JpaPredicate, JpaWindow, Expression...)
|
||||
*/
|
||||
<T> JpaExpression<T> mode(
|
||||
|
@ -1300,7 +1466,7 @@ public interface HibernateCriteriaBuilder extends CriteriaBuilder {
|
|||
*
|
||||
* @return ordered set-aggregate expression
|
||||
*
|
||||
* @see #functionWithinGroup
|
||||
* @see #functionWithinGroup(String, Class, JpaOrder, JpaPredicate, JpaWindow, Expression...)
|
||||
*/
|
||||
JpaExpression<Long> rank(JpaOrder order, JpaPredicate filter, JpaWindow window, Expression<?>... arguments);
|
||||
|
||||
|
@ -1329,7 +1495,7 @@ public interface HibernateCriteriaBuilder extends CriteriaBuilder {
|
|||
*
|
||||
* @return ordered set-aggregate expression
|
||||
*
|
||||
* @see #functionWithinGroup
|
||||
* @see #functionWithinGroup(String, Class, JpaOrder, JpaPredicate, JpaWindow, Expression...)
|
||||
*/
|
||||
JpaExpression<Double> percentRank(
|
||||
JpaOrder order,
|
||||
|
|
|
@ -6,6 +6,8 @@
|
|||
*/
|
||||
package org.hibernate.query.criteria;
|
||||
|
||||
import org.hibernate.query.sqm.FrameExclusion;
|
||||
|
||||
import jakarta.persistence.criteria.Expression;
|
||||
import jakarta.persistence.criteria.Order;
|
||||
|
||||
|
@ -16,4 +18,12 @@ public interface JpaWindow {
|
|||
JpaWindow partitionBy(Expression<?>... expressions);
|
||||
|
||||
JpaWindow orderBy(Order... expressions);
|
||||
|
||||
JpaWindow frameRows(JpaWindowFrame startFrame, JpaWindowFrame endFrame);
|
||||
|
||||
JpaWindow frameRange(JpaWindowFrame startFrame, JpaWindowFrame endFrame);
|
||||
|
||||
JpaWindow frameGroups(JpaWindowFrame startFrame, JpaWindowFrame endFrame);
|
||||
|
||||
JpaWindow frameExclude(FrameExclusion frameExclusion);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
* 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 org.hibernate.query.sqm.FrameKind;
|
||||
|
||||
import jakarta.persistence.criteria.Expression;
|
||||
|
||||
/**
|
||||
* @author Marco Belladelli
|
||||
*/
|
||||
public interface JpaWindowFrame {
|
||||
FrameKind getKind();
|
||||
|
||||
Expression<?> getExpression();
|
||||
}
|
|
@ -48,6 +48,7 @@ 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.criteria.JpaWindowFrame;
|
||||
import org.hibernate.query.sqm.NullPrecedence;
|
||||
import org.hibernate.query.sqm.SortOrder;
|
||||
import org.hibernate.query.sqm.tree.expression.SqmExpression;
|
||||
|
@ -1261,6 +1262,41 @@ public class HibernateCriteriaBuilderDelegate implements HibernateCriteriaBuilde
|
|||
return criteriaBuilder.createWindow();
|
||||
}
|
||||
|
||||
@Override
|
||||
public JpaWindowFrame frameUnboundedPreceding() {
|
||||
return criteriaBuilder.frameUnboundedPreceding();
|
||||
}
|
||||
|
||||
@Override
|
||||
public JpaWindowFrame frameBetweenPreceding(int offset) {
|
||||
return criteriaBuilder.frameBetweenPreceding( offset );
|
||||
}
|
||||
|
||||
@Override
|
||||
public JpaWindowFrame frameBetweenPreceding(Expression<?> offset) {
|
||||
return criteriaBuilder.frameBetweenPreceding( offset );
|
||||
}
|
||||
|
||||
@Override
|
||||
public JpaWindowFrame frameCurrentRow() {
|
||||
return criteriaBuilder.frameCurrentRow();
|
||||
}
|
||||
|
||||
@Override
|
||||
public JpaWindowFrame frameBetweenFollowing(int offset) {
|
||||
return criteriaBuilder.frameBetweenFollowing( offset );
|
||||
}
|
||||
|
||||
@Override
|
||||
public JpaWindowFrame frameBetweenFollowing(Expression<?> offset) {
|
||||
return criteriaBuilder.frameBetweenFollowing( offset );
|
||||
}
|
||||
|
||||
@Override
|
||||
public JpaWindowFrame frameUnboundedFollowing() {
|
||||
return criteriaBuilder.frameUnboundedFollowing();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> JpaExpression<T> windowFunction(String name, Class<T> type, JpaWindow window, Expression<?>... args) {
|
||||
return criteriaBuilder.windowFunction( name, type, window, args );
|
||||
|
@ -1311,6 +1347,75 @@ public class HibernateCriteriaBuilderDelegate implements HibernateCriteriaBuilde
|
|||
return criteriaBuilder.cumeDist( window );
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> JpaExpression<T> functionAggregate(
|
||||
String name,
|
||||
Class<T> type,
|
||||
JpaPredicate filter,
|
||||
Expression<?>... args) {
|
||||
return criteriaBuilder.functionAggregate( name, type, filter, args );
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> JpaExpression<T> functionAggregate(String name, Class<T> type, JpaWindow window, Expression<?>... args) {
|
||||
return criteriaBuilder.functionAggregate( name, type, window, args );
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> JpaExpression<T> functionAggregate(
|
||||
String name,
|
||||
Class<T> type,
|
||||
JpaPredicate filter,
|
||||
JpaWindow window,
|
||||
Expression<?>... args) {
|
||||
return criteriaBuilder.functionAggregate( name, type, filter, window, args );
|
||||
}
|
||||
|
||||
@Override
|
||||
public <N extends Number> JpaExpression<Number> sum(Expression<N> argument, JpaPredicate filter) {
|
||||
return criteriaBuilder.sum( argument, filter );
|
||||
}
|
||||
|
||||
@Override
|
||||
public <N extends Number> JpaExpression<Number> sum(Expression<N> argument, JpaWindow window) {
|
||||
return criteriaBuilder.sum( argument, window );
|
||||
}
|
||||
|
||||
@Override
|
||||
public <N extends Number> JpaExpression<Number> sum(Expression<N> argument, JpaPredicate filter, JpaWindow window) {
|
||||
return criteriaBuilder.sum( argument, filter, window );
|
||||
}
|
||||
|
||||
@Override
|
||||
public <N extends Number> JpaExpression<Double> avg(Expression<N> argument, JpaPredicate filter) {
|
||||
return criteriaBuilder.avg( argument, filter );
|
||||
}
|
||||
|
||||
@Override
|
||||
public <N extends Number> JpaExpression<Double> avg(Expression<N> argument, JpaWindow window) {
|
||||
return criteriaBuilder.avg( argument, window );
|
||||
}
|
||||
|
||||
@Override
|
||||
public <N extends Number> JpaExpression<Double> avg(Expression<N> argument, JpaPredicate filter, JpaWindow window) {
|
||||
return criteriaBuilder.avg( argument, filter, window );
|
||||
}
|
||||
|
||||
@Override
|
||||
public JpaExpression<Long> count(Expression<?> argument, JpaPredicate filter) {
|
||||
return criteriaBuilder.count( argument, filter );
|
||||
}
|
||||
|
||||
@Override
|
||||
public JpaExpression<Long> count(Expression<?> argument, JpaWindow window) {
|
||||
return criteriaBuilder.count( argument, window );
|
||||
}
|
||||
|
||||
@Override
|
||||
public JpaExpression<Long> count(Expression<?> argument, JpaPredicate filter, JpaWindow window) {
|
||||
return criteriaBuilder.count( argument, filter, window );
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> JpaExpression<T> functionWithinGroup(String name, Class<T> type, JpaOrder order, Expression<?>... args) {
|
||||
return criteriaBuilder.functionWithinGroup( name, type, order, args );
|
||||
|
|
|
@ -58,11 +58,13 @@ 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.JpaWindowFrame;
|
||||
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.FrameKind;
|
||||
import org.hibernate.query.sqm.NodeBuilder;
|
||||
import org.hibernate.query.sqm.NullPrecedence;
|
||||
import org.hibernate.query.sqm.SetOperator;
|
||||
|
@ -108,6 +110,7 @@ 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.SqmWindowFrame;
|
||||
import org.hibernate.query.sqm.tree.expression.ValueBindJpaCriteriaParameter;
|
||||
import org.hibernate.query.sqm.tree.from.SqmRoot;
|
||||
import org.hibernate.query.sqm.tree.insert.SqmInsertSelectStatement;
|
||||
|
@ -140,6 +143,7 @@ import org.hibernate.service.ServiceRegistry;
|
|||
import org.hibernate.type.BasicType;
|
||||
import org.hibernate.type.StandardBasicTypes;
|
||||
import org.hibernate.type.descriptor.java.JavaType;
|
||||
import org.hibernate.type.descriptor.jdbc.SmallIntJdbcType;
|
||||
import org.hibernate.type.spi.TypeConfiguration;
|
||||
|
||||
import jakarta.persistence.Tuple;
|
||||
|
@ -2573,14 +2577,45 @@ public class SqmCriteriaNodeBuilder implements NodeBuilder, SqmCreationContext,
|
|||
return new SqmWindow( this );
|
||||
}
|
||||
|
||||
@Override
|
||||
public SqmWindowFrame frameUnboundedPreceding() {
|
||||
return new SqmWindowFrame( this, FrameKind.UNBOUNDED_PRECEDING );
|
||||
}
|
||||
|
||||
@Override
|
||||
public SqmWindowFrame frameBetweenPreceding(int offset) {
|
||||
return new SqmWindowFrame( this, FrameKind.OFFSET_PRECEDING, literal( offset ) );
|
||||
}
|
||||
|
||||
@Override
|
||||
public SqmWindowFrame frameBetweenPreceding(Expression<?> offset) {
|
||||
return new SqmWindowFrame( this, FrameKind.OFFSET_PRECEDING, (SqmExpression<?>) offset );
|
||||
}
|
||||
|
||||
@Override
|
||||
public SqmWindowFrame frameCurrentRow() {
|
||||
return new SqmWindowFrame( this, FrameKind.CURRENT_ROW );
|
||||
}
|
||||
|
||||
@Override
|
||||
public SqmWindowFrame frameBetweenFollowing(int offset) {
|
||||
return new SqmWindowFrame( this, FrameKind.OFFSET_FOLLOWING, literal( offset ) );
|
||||
}
|
||||
|
||||
@Override
|
||||
public SqmWindowFrame frameBetweenFollowing(Expression<?> offset) {
|
||||
return new SqmWindowFrame( this, FrameKind.OFFSET_FOLLOWING, (SqmExpression<?>) offset );
|
||||
}
|
||||
|
||||
@Override
|
||||
public SqmWindowFrame frameUnboundedFollowing() {
|
||||
return new SqmWindowFrame( this, FrameKind.UNBOUNDED_FOLLOWING );
|
||||
}
|
||||
|
||||
@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),
|
||||
expressionList( args ),
|
||||
null,
|
||||
queryEngine,
|
||||
getJpaMetamodel().getTypeConfiguration()
|
||||
|
@ -2638,7 +2673,97 @@ public class SqmCriteriaNodeBuilder implements NodeBuilder, SqmCreationContext,
|
|||
}
|
||||
|
||||
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// Ordered set-aggregate functions
|
||||
// Aggregate functions
|
||||
|
||||
@Override
|
||||
public <T> SqmExpression<T> functionAggregate(
|
||||
String name,
|
||||
Class<T> type,
|
||||
JpaPredicate filter,
|
||||
Expression<?>... args) {
|
||||
return functionAggregate( name, type, filter, null, args );
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> SqmExpression<T> functionAggregate(
|
||||
String name,
|
||||
Class<T> type,
|
||||
JpaWindow window,
|
||||
Expression<?>... args) {
|
||||
return functionAggregate( name, type, null, window, args );
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> SqmExpression<T> functionAggregate(
|
||||
String name,
|
||||
Class<T> type,
|
||||
JpaPredicate filter,
|
||||
JpaWindow window,
|
||||
Expression<?>... args) {
|
||||
SqmPredicate sqmFilter = filter != null ? (SqmPredicate) filter : null;
|
||||
SqmExpression<T> function = getFunctionDescriptor( name ).generateAggregateSqmExpression(
|
||||
expressionList( args ),
|
||||
sqmFilter,
|
||||
null,
|
||||
queryEngine,
|
||||
getJpaMetamodel().getTypeConfiguration()
|
||||
);
|
||||
if ( window == null ) {
|
||||
return function;
|
||||
}
|
||||
else {
|
||||
return new SqmOver<>( function, (SqmWindow) window );
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public <N extends Number> SqmExpression<Number> sum(Expression<N> argument, JpaPredicate filter) {
|
||||
return sum( argument, filter, null );
|
||||
}
|
||||
|
||||
@Override
|
||||
public <N extends Number> SqmExpression<Number> sum(Expression<N> argument, JpaWindow window) {
|
||||
return sum( argument, null, window );
|
||||
}
|
||||
|
||||
@Override
|
||||
public <N extends Number> SqmExpression<Number> sum(Expression<N> argument, JpaPredicate filter, JpaWindow window) {
|
||||
return functionAggregate( "sum", Number.class, filter, window, argument );
|
||||
}
|
||||
|
||||
@Override
|
||||
public <N extends Number> SqmExpression<Double> avg(Expression<N> argument, JpaPredicate filter) {
|
||||
return avg( argument, filter, null );
|
||||
}
|
||||
|
||||
@Override
|
||||
public <N extends Number> SqmExpression<Double> avg(Expression<N> argument, JpaWindow window) {
|
||||
return avg( argument, null, window );
|
||||
}
|
||||
|
||||
@Override
|
||||
public <N extends Number> SqmExpression<Double> avg(Expression<N> argument, JpaPredicate filter, JpaWindow window) {
|
||||
return functionAggregate( "avg", Double.class, filter, window, argument );
|
||||
}
|
||||
|
||||
@Override
|
||||
public SqmExpression<Long> count(Expression<?> argument, JpaPredicate filter) {
|
||||
return count( argument, filter, null );
|
||||
}
|
||||
|
||||
@Override
|
||||
public SqmExpression<Long> count(Expression<?> argument, JpaWindow window) {
|
||||
return count( argument, null, window );
|
||||
}
|
||||
|
||||
@Override
|
||||
public SqmExpression<Long> count(Expression<?> argument, JpaPredicate filter, JpaWindow window) {
|
||||
return functionAggregate( "count", Long.class, filter, window, argument );
|
||||
}
|
||||
|
||||
|
||||
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// Ordered-Set Aggregate functions
|
||||
|
||||
@Override
|
||||
public <T> SqmExpression<T> functionWithinGroup(String name, Class<T> type, JpaOrder order, Expression<?>... args) {
|
||||
|
@ -2747,7 +2872,7 @@ public class SqmCriteriaNodeBuilder implements NodeBuilder, SqmCreationContext,
|
|||
JpaWindow window,
|
||||
Expression<String> argument,
|
||||
String separator) {
|
||||
return listagg( order, filter, window, argument, value( separator, (SqmExpression<String>) argument ) );
|
||||
return listagg( order, filter, window, argument, literal( separator ) );
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -2891,6 +3016,7 @@ public class SqmCriteriaNodeBuilder implements NodeBuilder, SqmCreationContext,
|
|||
sortExpression.getJavaType(),
|
||||
sort( (SqmExpression<T>) sortExpression, sortOrder, nullPrecedence ),
|
||||
filter,
|
||||
window,
|
||||
argument
|
||||
);
|
||||
}
|
||||
|
|
|
@ -20,8 +20,6 @@ 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;
|
||||
|
|
|
@ -10,6 +10,7 @@ import java.util.ArrayList;
|
|||
import java.util.List;
|
||||
|
||||
import org.hibernate.query.criteria.JpaWindow;
|
||||
import org.hibernate.query.criteria.JpaWindowFrame;
|
||||
import org.hibernate.query.sqm.FrameExclusion;
|
||||
import org.hibernate.query.sqm.FrameKind;
|
||||
import org.hibernate.query.sqm.FrameMode;
|
||||
|
@ -34,12 +35,12 @@ import static org.hibernate.query.sqm.FrameMode.ROWS;
|
|||
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;
|
||||
private FrameMode mode;
|
||||
private FrameKind startKind;
|
||||
private SqmExpression<?> startExpression;
|
||||
private FrameKind endKind;
|
||||
private SqmExpression<?> endExpression;
|
||||
private FrameExclusion exclusion;
|
||||
|
||||
public SqmWindow(NodeBuilder nodeBuilder) {
|
||||
this(
|
||||
|
@ -108,6 +109,42 @@ public class SqmWindow extends AbstractSqmNode implements JpaWindow, SqmVisitabl
|
|||
return exclusion;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JpaWindow frameRows(JpaWindowFrame startFrame, JpaWindowFrame endFrame) {
|
||||
return this.setFrames(FrameMode.ROWS, startFrame, endFrame);
|
||||
}
|
||||
|
||||
@Override
|
||||
public JpaWindow frameRange(JpaWindowFrame startFrame, JpaWindowFrame endFrame) {
|
||||
return this.setFrames(FrameMode.RANGE, startFrame, endFrame);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public JpaWindow frameGroups(JpaWindowFrame startFrame, JpaWindowFrame endFrame) {
|
||||
return this.setFrames(FrameMode.GROUPS, startFrame, endFrame);
|
||||
|
||||
}
|
||||
|
||||
private SqmWindow setFrames(FrameMode frameMode, JpaWindowFrame startFrame, JpaWindowFrame endFrame) {
|
||||
this.mode = frameMode;
|
||||
if (startFrame != null) {
|
||||
this.startKind = startFrame.getKind();
|
||||
this.startExpression = (SqmExpression<?>) startFrame.getExpression();
|
||||
}
|
||||
if (endFrame != null) {
|
||||
this.endKind = endFrame.getKind();
|
||||
this.endExpression = (SqmExpression<?>) endFrame.getExpression();
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JpaWindow frameExclude(FrameExclusion frameExclusion) {
|
||||
this.exclusion = frameExclusion;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JpaWindow partitionBy(Expression<?>... expressions) {
|
||||
for (Expression<?> expression : expressions) {
|
||||
|
|
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
|
||||
*/
|
||||
package org.hibernate.query.sqm.tree.expression;
|
||||
|
||||
import org.hibernate.query.criteria.JpaWindowFrame;
|
||||
import org.hibernate.query.sqm.FrameKind;
|
||||
import org.hibernate.query.sqm.NodeBuilder;
|
||||
import org.hibernate.query.sqm.tree.AbstractSqmNode;
|
||||
import org.hibernate.query.sqm.tree.SqmCopyContext;
|
||||
|
||||
/**
|
||||
* @author Marco Belladelli
|
||||
*/
|
||||
public class SqmWindowFrame extends AbstractSqmNode implements JpaWindowFrame {
|
||||
private final FrameKind kind;
|
||||
private final SqmExpression<?> expression;
|
||||
|
||||
public SqmWindowFrame(NodeBuilder nodeBuilder, FrameKind kind) {
|
||||
this( nodeBuilder, kind, null );
|
||||
}
|
||||
|
||||
public SqmWindowFrame(NodeBuilder nodeBuilder, FrameKind kind, SqmExpression<?> expression) {
|
||||
super( nodeBuilder );
|
||||
this.kind = kind;
|
||||
this.expression = expression;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FrameKind getKind() {
|
||||
return kind;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SqmExpression<?> getExpression() {
|
||||
return expression;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SqmWindowFrame copy(SqmCopyContext context) {
|
||||
final SqmWindowFrame existing = context.getCopy( this );
|
||||
if ( existing != null ) {
|
||||
return existing;
|
||||
}
|
||||
return context.registerCopy(
|
||||
this,
|
||||
new SqmWindowFrame(
|
||||
nodeBuilder(),
|
||||
kind,
|
||||
expression == null ? null : expression.copy( context )
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
|
@ -10,6 +10,8 @@ import java.util.Arrays;
|
|||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import org.hibernate.dialect.H2Dialect;
|
||||
import org.hibernate.dialect.PostgreSQLDialect;
|
||||
import org.hibernate.query.criteria.HibernateCriteriaBuilder;
|
||||
import org.hibernate.query.criteria.JpaExpression;
|
||||
import org.hibernate.query.criteria.JpaWindow;
|
||||
|
@ -20,10 +22,12 @@ 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.RequiresDialect;
|
||||
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;
|
||||
|
@ -33,6 +37,7 @@ 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
|
||||
|
@ -129,7 +134,11 @@ public class CriteriaOrderedSetAggregateTest {
|
|||
CriteriaQuery<String> cr = cb.createQuery( String.class );
|
||||
Root<EntityOfBasics> root = cr.from( EntityOfBasics.class );
|
||||
|
||||
JpaExpression<String> function = cb.listagg( cb.desc( root.get( "id" ) ), root.get( "theString" ), "," );
|
||||
JpaExpression<String> function = cb.listagg(
|
||||
cb.desc( root.get( "id" ) ),
|
||||
root.get( "theString" ),
|
||||
","
|
||||
);
|
||||
|
||||
cr.select( function );
|
||||
String result = session.createQuery( cr ).getSingleResult();
|
||||
|
@ -158,6 +167,36 @@ public class CriteriaOrderedSetAggregateTest {
|
|||
} );
|
||||
}
|
||||
|
||||
@Test
|
||||
@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsStringAggregation.class)
|
||||
@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsWindowFunctions.class)
|
||||
@RequiresDialect(H2Dialect.class)
|
||||
public void testListaggWithFilterAndWindow(SessionFactoryScope scope) {
|
||||
// note : not many dbs support this for now but the generated sql is correct
|
||||
scope.inTransaction( session -> {
|
||||
HibernateCriteriaBuilder cb = session.getCriteriaBuilder();
|
||||
CriteriaQuery<String> cr = cb.createQuery( String.class );
|
||||
Root<EntityOfBasics> root = cr.from( EntityOfBasics.class );
|
||||
|
||||
JpaWindow window = cb.createWindow().partitionBy( root.get( "theInt" ) );
|
||||
JpaExpression<String> function = cb.listagg(
|
||||
cb.desc( root.get( "id" ) ),
|
||||
cb.lt( root.get( "theInt" ), cb.literal( 10 ) ),
|
||||
window,
|
||||
root.get( "theString" ),
|
||||
","
|
||||
);
|
||||
|
||||
cr.select( function );
|
||||
List<String> resultList = session.createQuery( cr ).getResultList();
|
||||
assertEquals( "5,5", resultList.get( 0 ) );
|
||||
assertEquals( "6", resultList.get( 1 ) );
|
||||
assertEquals( "7", resultList.get( 2 ) );
|
||||
assertNull( resultList.get( 3 ) );
|
||||
assertEquals( "5,5", resultList.get( 4 ) );
|
||||
} );
|
||||
}
|
||||
|
||||
@Test
|
||||
@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsStringAggregation.class)
|
||||
public void testListaggWithNullsClause(SessionFactoryScope scope) {
|
||||
|
@ -199,6 +238,37 @@ public class CriteriaOrderedSetAggregateTest {
|
|||
} );
|
||||
}
|
||||
|
||||
@Test
|
||||
@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsInverseDistributionFunctions.class)
|
||||
@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsWindowFunctions.class)
|
||||
@SkipForDialect(dialectClass = PostgreSQLDialect.class)
|
||||
public void testInverseDistributionWithWindow(SessionFactoryScope scope) {
|
||||
// note : PostgreSQL currently does not support ordered-set aggregate functions with OVER clause
|
||||
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().partitionBy( root.get( "theInt" ) );
|
||||
JpaExpression<Integer> function = cb.percentileDisc(
|
||||
cb.literal( 0.5 ),
|
||||
window,
|
||||
root.get( "theInt" ),
|
||||
SortOrder.ASCENDING,
|
||||
NullPrecedence.FIRST
|
||||
);
|
||||
|
||||
cr.select( function ).orderBy( cb.asc( cb.literal( 1 ) ) );
|
||||
List<Integer> resultList = session.createQuery( cr ).getResultList();
|
||||
assertEquals( 5, resultList.size() );
|
||||
assertEquals( 5, resultList.get( 0 ) );
|
||||
assertEquals( 5, resultList.get( 1 ) );
|
||||
assertEquals( 6, resultList.get( 2 ) );
|
||||
assertEquals( 7, resultList.get( 3 ) );
|
||||
assertEquals( 13, resultList.get( 4 ) );
|
||||
} );
|
||||
}
|
||||
|
||||
@Test
|
||||
@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsHypotheticalSetFunctions.class)
|
||||
public void testHypotheticalSetPercentRank(SessionFactoryScope scope) {
|
||||
|
@ -258,5 +328,4 @@ public class CriteriaOrderedSetAggregateTest {
|
|||
assertEquals( 1L, resultList.get( 2 ).get( 1, Long.class ) );
|
||||
} );
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -29,8 +29,8 @@ 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.Path;
|
||||
import jakarta.persistence.criteria.Root;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
@ -239,16 +239,21 @@ public class CriteriaWindowFunctionTest {
|
|||
);
|
||||
}
|
||||
|
||||
// @Test
|
||||
public void testSumWithFilterAsWindowFunction(SessionFactoryScope scope) {
|
||||
@Test
|
||||
public void testSumWithFilterAndWindow(SessionFactoryScope scope) {
|
||||
scope.inTransaction(
|
||||
session -> {
|
||||
// todo marco : add 'simple` aggregate functions (sum, avg, count)
|
||||
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();
|
||||
HibernateCriteriaBuilder cb = session.getCriteriaBuilder();
|
||||
CriteriaQuery<Long> cr = cb.createQuery( Long.class );
|
||||
Root<EntityOfBasics> root = cr.from( EntityOfBasics.class );
|
||||
|
||||
Path<Integer> theInt = root.get( "theInt" );
|
||||
JpaWindow window = cb.createWindow().orderBy( cb.asc( theInt ) );
|
||||
JpaExpression<Long> sum = cb.sum( theInt, cb.gt( theInt, 5 ), window ).asLong();
|
||||
|
||||
cr.select( sum ).orderBy( cb.asc( theInt, true ) );
|
||||
|
||||
List<Long> resultList = session.createQuery( cr ).getResultList();
|
||||
assertEquals( 5L, resultList.size() );
|
||||
assertNull( resultList.get( 0 ) );
|
||||
assertNull( resultList.get( 1 ) );
|
||||
|
@ -259,16 +264,71 @@ public class CriteriaWindowFunctionTest {
|
|||
);
|
||||
}
|
||||
|
||||
// @Test
|
||||
@Test
|
||||
public void testAvgAsWindowFunctionWithoutFilter(SessionFactoryScope scope) {
|
||||
scope.inTransaction(
|
||||
session -> {
|
||||
HibernateCriteriaBuilder cb = session.getCriteriaBuilder();
|
||||
CriteriaQuery<Double> cr = cb.createQuery( Double.class );
|
||||
Root<EntityOfBasics> root = cr.from( EntityOfBasics.class );
|
||||
|
||||
Path<Integer> id = root.get( "id" );
|
||||
JpaWindow window = cb.createWindow().orderBy( cb.asc( id ) );
|
||||
JpaExpression<Double> avg = cb.avg( id, window );
|
||||
|
||||
cr.select( avg ).orderBy( cb.asc( cb.literal( 1 ) ) );
|
||||
|
||||
List<Double> resultList = session.createQuery( cr ).getResultList();
|
||||
assertEquals( 5L, resultList.size() );
|
||||
assertEquals( 1.0, resultList.get( 0 ) );
|
||||
assertEquals( 1.5, resultList.get( 1 ) );
|
||||
assertEquals( 2.0, resultList.get( 2 ) );
|
||||
assertEquals( 2.5, resultList.get( 3 ) );
|
||||
assertEquals( 3.0, resultList.get( 4 ) );
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCountAsWindowFunctionWithFilter(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();
|
||||
JpaExpression<Long> count = cb.count( root, cb.gt( root.get( "id" ), 2 ), window );
|
||||
|
||||
cr.select( count );
|
||||
|
||||
List<Long> resultList = session.createQuery( cr ).getResultList();
|
||||
assertEquals( 5L, resultList.size() );
|
||||
assertEquals( 3L, resultList.get( 0 ) );
|
||||
assertEquals( 3L, resultList.get( 1 ) );
|
||||
assertEquals( 3L, resultList.get( 2 ) );
|
||||
assertEquals( 3L, resultList.get( 3 ) );
|
||||
assertEquals( 3L, 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();
|
||||
HibernateCriteriaBuilder cb = session.getCriteriaBuilder();
|
||||
CriteriaQuery<Integer> cr = cb.createQuery( Integer.class );
|
||||
Root<EntityOfBasics> root = cr.from( EntityOfBasics.class );
|
||||
|
||||
JpaWindow window = cb.createWindow()
|
||||
.orderBy( cb.asc( root.get( "id" ) ) )
|
||||
.frameRows( cb.frameBetweenPreceding( 2 ), cb.frameCurrentRow() );
|
||||
JpaExpression<Integer> firstValue = cb.firstValue( root.get( "theInt" ), window );
|
||||
|
||||
cr.select( firstValue ).orderBy( cb.asc( root.get( "id" ) ) );
|
||||
|
||||
List<Integer> resultList = session.createQuery( cr ).getResultList();
|
||||
assertEquals( 5, resultList.size() );
|
||||
assertEquals( 5, resultList.get( 0 ) );
|
||||
assertEquals( 5, resultList.get( 1 ) );
|
||||
|
|
Loading…
Reference in New Issue