HHH-15654 Added 'simple' aggregate functions and frame APIs for JpaWindows

This commit is contained in:
Marco Belladelli 2022-12-05 17:06:23 +01:00 committed by Christian Beikov
parent 28b0d6c5a4
commit 6d9c448db2
10 changed files with 685 additions and 37 deletions

View File

@ -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,

View File

@ -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);
}

View File

@ -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();
}

View File

@ -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 );

View File

@ -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
);
}

View File

@ -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;

View File

@ -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) {

View File

@ -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 )
)
);
}
}

View File

@ -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 ) );
} );
}
}

View File

@ -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 ) );