introduce new syntax for aggregate functions applying to collections

max(element x.y), min(index x.y), sum(element x.y)

and rationalize the node types here
This commit is contained in:
Gavin King 2022-01-10 11:08:05 +01:00
parent 38fc97feb3
commit 4b5e6e1969
18 changed files with 205 additions and 283 deletions

View File

@ -1782,7 +1782,7 @@ public class HQLTest extends BaseEntityManagerFunctionalTestCase {
List<Phone> phones = entityManager.createQuery(
"select p " +
"from Phone p " +
"where maxelement(p.calls) = :call",
"where max(element p.calls) = :call",
Phone.class)
.setParameter("call", call)
.getResultList();
@ -1800,7 +1800,7 @@ public class HQLTest extends BaseEntityManagerFunctionalTestCase {
List<Phone> phones = entityManager.createQuery(
"select p " +
"from Phone p " +
"where minelement(p.calls) = :call",
"where min(element p.calls) = :call",
Phone.class)
.setParameter("call", call)
.getResultList();
@ -1817,7 +1817,7 @@ public class HQLTest extends BaseEntityManagerFunctionalTestCase {
List<Person> persons = entityManager.createQuery(
"select p " +
"from Person p " +
"where maxindex(p.phones) = 0",
"where max(index p.phones) = 0",
Person.class)
.getResultList();
//end::hql-collection-expressions-example[]
@ -1991,7 +1991,7 @@ public class HQLTest extends BaseEntityManagerFunctionalTestCase {
List<Person> persons = entityManager.createQuery(
"select pr " +
"from Person pr " +
"where pr.phones[maxindex(pr.phones)].type = 'LAND_LINE'",
"where pr.phones[max(index pr.phones)].type = 'LAND_LINE'",
Person.class)
.getResultList();
//end::hql-collection-index-operator-example[]

View File

@ -145,6 +145,7 @@ AND : [aA] [nN] [dD];
ANY : [aA] [nN] [yY];
AS : [aA] [sS];
ASC : [aA] [sS] [cC];
AVG : [aA] [vV] [gG];
BY : [bB] [yY];
BETWEEN : [bB] [eE] [tT] [wW] [eE] [eE] [nN];
BOTH : [bB] [oO] [tT] [hH];
@ -164,6 +165,7 @@ DAY : [dD] [aA] [yY];
DELETE : [dD] [eE] [lL] [eE] [tT] [eE];
DESC : [dD] [eE] [sS] [cC];
DISTINCT : [dD] [iI] [sS] [tT] [iI] [nN] [cC] [tT];
ELEMENT : [eE] [lL] [eE] [mM] [eE] [nN] [tT];
ELEMENTS : [eE] [lL] [eE] [mM] [eE] [nN] [tT] [sS];
ELSE : [eE] [lL] [sS] [eE];
EMPTY : [eE] [mM] [pP] [tT] [yY];
@ -208,7 +210,9 @@ LOCAL_DATE : [lL] [oO] [cC] [aA] [lL] '_' [dD] [aA] [tT] [eE];
LOCAL_DATETIME : [lL] [oO] [cC] [aA] [lL] '_' [dD] [aA] [tT] [eE] [tT] [iI] [mM] [eE];
LOCAL_TIME : [lL] [oO] [cC] [aA] [lL] '_' [tT] [iI] [mM] [eE];
MAP : [mM] [aA] [pP];
MAX : [mM] [aA] [xX];
MAXELEMENT : [mM] [aA] [xX] [eE] [lL] [eE] [mM] [eE] [nN] [tT];
MIN : [mM] [iI] [nN];
MAXINDEX : [mM] [aA] [xX] [iI] [nN] [dD] [eE] [xX];
MEMBER : [mM] [eE] [mM] [bB] [eE] [rR];
MICROSECOND : [mM] [iI] [cC] [rR] [oO] [sS] [eE] [cC] [oO] [nN] [dD];
@ -247,6 +251,7 @@ SET : [sS] [eE] [tT];
SIZE : [sS] [iI] [zZ] [eE];
SOME : [sS] [oO] [mM] [eE];
SUBSTRING : [sS] [uU] [bB] [sS] [tT] [rR] [iI] [nN] [gG];
SUM : [sS] [uM] [mM];
THEN : [tT] [hH] [eE] [nN];
TIES : [tT] [iI] [eE] [sS];
TIME : [tT] [iI] [mM] [eE];

View File

@ -948,9 +948,10 @@ function
: standardFunction
| aggregateFunction
| jpaCollectionFunction
| hqlCollectionFunction
| jpaNonstandardFunction
| indexAggregateFunction
| elementAggregateFunction
| collectionFunctionMisuse
| jpaNonstandardFunction
| genericFunction
;
@ -1002,13 +1003,27 @@ jpaCollectionFunction
;
/**
* The special collection functions defined by HQL
* The special aggregate collection functions defined by HQL
*/
hqlCollectionFunction
: MAXINDEX LEFT_PAREN path RIGHT_PAREN # MaxIndexFunction
| MAXELEMENT LEFT_PAREN path RIGHT_PAREN # MaxElementFunction
| MININDEX LEFT_PAREN path RIGHT_PAREN # MinIndexFunction
| MINELEMENT LEFT_PAREN path RIGHT_PAREN # MinElementFunction
indexAggregateFunction
: MAXINDEX LEFT_PAREN path RIGHT_PAREN
| MININDEX LEFT_PAREN path RIGHT_PAREN
| MAX LEFT_PAREN INDEX path RIGHT_PAREN
| MIN LEFT_PAREN INDEX path RIGHT_PAREN
| SUM LEFT_PAREN INDEX path RIGHT_PAREN
| AVG LEFT_PAREN ELEMENT path RIGHT_PAREN
;
/**
* The special aggregate collection functions defined by HQL
*/
elementAggregateFunction
: MAXELEMENT LEFT_PAREN path RIGHT_PAREN
| MINELEMENT LEFT_PAREN path RIGHT_PAREN
| MAX LEFT_PAREN ELEMENT path RIGHT_PAREN
| MIN LEFT_PAREN ELEMENT path RIGHT_PAREN
| SUM LEFT_PAREN ELEMENT path RIGHT_PAREN
| AVG LEFT_PAREN ELEMENT path RIGHT_PAREN
;
/**
@ -1365,6 +1380,7 @@ identifier
| ANY
| AS
| ASC
| AVG
| BETWEEN
| BOTH
| BY
@ -1429,11 +1445,13 @@ identifier
| LOCAL_DATETIME
| LOCAL_TIME
| MAP
| MAX
| MAXELEMENT
| MAXINDEX
| MEMBER
| MICROSECOND
| MILLISECOND
| MIN
| MINELEMENT
| MININDEX
| MINUTE
@ -1469,6 +1487,7 @@ identifier
| SIZE
| SOME
| SUBSTRING
| SUM
| THEN
| TIES
| TIME

View File

@ -102,10 +102,8 @@ import org.hibernate.query.sqm.tree.domain.AbstractSqmFrom;
import org.hibernate.query.sqm.tree.domain.SqmCorrelation;
import org.hibernate.query.sqm.tree.domain.SqmMapEntryReference;
import org.hibernate.query.sqm.tree.domain.SqmMapJoin;
import org.hibernate.query.sqm.tree.domain.SqmMaxElementPath;
import org.hibernate.query.sqm.tree.domain.SqmMaxIndexPath;
import org.hibernate.query.sqm.tree.domain.SqmMinElementPath;
import org.hibernate.query.sqm.tree.domain.SqmMinIndexPath;
import org.hibernate.query.sqm.tree.domain.SqmElementAggregateFunction;
import org.hibernate.query.sqm.tree.domain.SqmIndexAggregateFunction;
import org.hibernate.query.sqm.tree.domain.SqmPath;
import org.hibernate.query.sqm.tree.domain.SqmPluralValuedSimplePath;
import org.hibernate.query.sqm.tree.domain.SqmPolymorphicRootDescriptor;
@ -3969,57 +3967,40 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> implem
}
@Override
public SqmMaxElementPath<?> visitMaxElementFunction(HqlParser.MaxElementFunctionContext ctx) {
public SqmElementAggregateFunction<?> visitElementAggregateFunction(HqlParser.ElementAggregateFunctionContext ctx) {
if ( creationOptions.useStrictJpaCompliance() ) {
throw new StrictJpaComplianceViolation( StrictJpaComplianceViolation.Type.HQL_COLLECTION_FUNCTION );
}
return new SqmMaxElementPath<>( consumePluralAttributeReference( (HqlParser.PathContext) ctx.getChild( 2 ) ) );
SqmPath<?> pluralPath = consumePluralAttributeReference( ctx.path() );
if ( !(pluralPath instanceof SqmPluralValuedSimplePath) ) {
throw new SemanticException( "Path '" + ctx.path().getText() + "' did not resolve to a many-valued attribute" );
}
String functionName = ctx.getChild(0).getText().substring(0, 3);
return new SqmElementAggregateFunction<>( pluralPath, functionName );
}
@Override
public SqmMinElementPath<?> visitMinElementFunction(HqlParser.MinElementFunctionContext ctx) {
public SqmIndexAggregateFunction<?> visitIndexAggregateFunction(HqlParser.IndexAggregateFunctionContext ctx) {
if ( creationOptions.useStrictJpaCompliance() ) {
throw new StrictJpaComplianceViolation( StrictJpaComplianceViolation.Type.HQL_COLLECTION_FUNCTION );
}
return new SqmMinElementPath<>( consumePluralAttributeReference( (HqlParser.PathContext) ctx.getChild( 2 ) ) );
final SqmPath<?> pluralPath = consumePluralAttributeReference( ctx.path() );
if ( !(pluralPath instanceof SqmPluralValuedSimplePath) ) {
throw new SemanticException( "Path '" + ctx.path().getText() + "' did not resolve to a many-valued attribute" );
}
@Override
public SqmMaxIndexPath<?> visitMaxIndexFunction(HqlParser.MaxIndexFunctionContext ctx) {
if ( creationOptions.useStrictJpaCompliance() ) {
throw new StrictJpaComplianceViolation( StrictJpaComplianceViolation.Type.HQL_COLLECTION_FUNCTION );
}
final SqmPath<?> pluralPath = consumePluralAttributeReference( (HqlParser.PathContext) ctx.getChild( 2 ) );
if ( !isIndexedPluralAttribute( pluralPath ) ) {
throw new SemanticException(
"maxindex() function can only be applied to path expressions which resolve to an " +
"indexed collection (list,map); specified path [" + ctx.getChild( 2 ).getText() +
"indexed collection (list,map); specified path [" + ctx.path() +
"] resolved to " + pluralPath.getReferencedPathSource()
);
}
return new SqmMaxIndexPath<>( pluralPath );
}
@Override
public SqmMinIndexPath<?> visitMinIndexFunction(HqlParser.MinIndexFunctionContext ctx) {
if ( creationOptions.useStrictJpaCompliance() ) {
throw new StrictJpaComplianceViolation( StrictJpaComplianceViolation.Type.HQL_COLLECTION_FUNCTION );
}
final SqmPath<?> pluralPath = consumePluralAttributeReference( (HqlParser.PathContext) ctx.getChild( 2 ) );
if ( !isIndexedPluralAttribute( pluralPath ) ) {
throw new SemanticException(
"minindex() function can only be applied to path expressions which resolve to an " +
"indexed collection (list,map); specified path [" + ctx.getChild( 2 ).getText() +
"] resolved to " + pluralPath.getReferencedPathSource()
);
}
return new SqmMinIndexPath<>( pluralPath );
String functionName = ctx.getChild(0).getText().substring(0, 3);
return new SqmIndexAggregateFunction<>( pluralPath, functionName );
}
@Override

View File

@ -20,10 +20,8 @@ import org.hibernate.query.sqm.tree.domain.SqmEmbeddedValuedSimplePath;
import org.hibernate.query.sqm.tree.domain.SqmEntityValuedSimplePath;
import org.hibernate.query.sqm.tree.domain.SqmIndexedCollectionAccessPath;
import org.hibernate.query.sqm.tree.domain.SqmMapEntryReference;
import org.hibernate.query.sqm.tree.domain.SqmMaxElementPath;
import org.hibernate.query.sqm.tree.domain.SqmMaxIndexPath;
import org.hibernate.query.sqm.tree.domain.SqmMinElementPath;
import org.hibernate.query.sqm.tree.domain.SqmMinIndexPath;
import org.hibernate.query.sqm.tree.domain.SqmElementAggregateFunction;
import org.hibernate.query.sqm.tree.domain.SqmIndexAggregateFunction;
import org.hibernate.query.sqm.tree.domain.SqmPluralPartJoin;
import org.hibernate.query.sqm.tree.domain.SqmPluralValuedSimplePath;
import org.hibernate.query.sqm.tree.domain.SqmTreatedPath;
@ -152,13 +150,9 @@ public interface SemanticQueryWalker<T> {
T visitIndexedPluralAccessPath(SqmIndexedCollectionAccessPath<?> path);
T visitMaxElementPath(SqmMaxElementPath<?> path);
T visitElementAggregateFunction(SqmElementAggregateFunction<?> path);
T visitMinElementPath(SqmMinElementPath<?> path);
T visitMaxIndexPath(SqmMaxIndexPath<?> path);
T visitMinIndexPath(SqmMinIndexPath<?> path);
T visitIndexAggregateFunction(SqmIndexAggregateFunction<?> path);
T visitTreatedPath(SqmTreatedPath<?, ?> sqmTreatedPath);

View File

@ -25,10 +25,8 @@ import org.hibernate.query.sqm.tree.domain.SqmEmbeddedValuedSimplePath;
import org.hibernate.query.sqm.tree.domain.SqmEntityValuedSimplePath;
import org.hibernate.query.sqm.tree.domain.SqmIndexedCollectionAccessPath;
import org.hibernate.query.sqm.tree.domain.SqmMapEntryReference;
import org.hibernate.query.sqm.tree.domain.SqmMaxElementPath;
import org.hibernate.query.sqm.tree.domain.SqmMaxIndexPath;
import org.hibernate.query.sqm.tree.domain.SqmMinElementPath;
import org.hibernate.query.sqm.tree.domain.SqmMinIndexPath;
import org.hibernate.query.sqm.tree.domain.SqmElementAggregateFunction;
import org.hibernate.query.sqm.tree.domain.SqmIndexAggregateFunction;
import org.hibernate.query.sqm.tree.domain.SqmPluralPartJoin;
import org.hibernate.query.sqm.tree.domain.SqmPluralValuedSimplePath;
import org.hibernate.query.sqm.tree.domain.SqmTreatedPath;
@ -947,22 +945,12 @@ public class SqmTreePrinter implements SemanticQueryWalker<Object> {
}
@Override
public Object visitMaxElementPath(SqmMaxElementPath binding) {
public Object visitElementAggregateFunction(SqmElementAggregateFunction binding) {
return null;
}
@Override
public Object visitMinElementPath(SqmMinElementPath path) {
return null;
}
@Override
public Object visitMaxIndexPath(SqmMaxIndexPath path) {
return null;
}
@Override
public Object visitMinIndexPath(SqmMinIndexPath path) {
public Object visitIndexAggregateFunction(SqmIndexAggregateFunction path) {
return null;
}

View File

@ -23,10 +23,8 @@ import org.hibernate.query.sqm.tree.domain.SqmEmbeddedValuedSimplePath;
import org.hibernate.query.sqm.tree.domain.SqmEntityValuedSimplePath;
import org.hibernate.query.sqm.tree.domain.SqmIndexedCollectionAccessPath;
import org.hibernate.query.sqm.tree.domain.SqmMapEntryReference;
import org.hibernate.query.sqm.tree.domain.SqmMaxElementPath;
import org.hibernate.query.sqm.tree.domain.SqmMaxIndexPath;
import org.hibernate.query.sqm.tree.domain.SqmMinElementPath;
import org.hibernate.query.sqm.tree.domain.SqmMinIndexPath;
import org.hibernate.query.sqm.tree.domain.SqmElementAggregateFunction;
import org.hibernate.query.sqm.tree.domain.SqmIndexAggregateFunction;
import org.hibernate.query.sqm.tree.domain.SqmPath;
import org.hibernate.query.sqm.tree.domain.SqmPluralPartJoin;
import org.hibernate.query.sqm.tree.domain.SqmPluralValuedSimplePath;
@ -318,22 +316,12 @@ public abstract class BaseSemanticQueryWalker implements SemanticQueryWalker<Obj
}
@Override
public Object visitMaxElementPath(SqmMaxElementPath<?> path) {
public Object visitElementAggregateFunction(SqmElementAggregateFunction<?> path) {
return path;
}
@Override
public Object visitMinElementPath(SqmMinElementPath<?> path) {
return path;
}
@Override
public Object visitMaxIndexPath(SqmMaxIndexPath<?> path) {
return path;
}
@Override
public Object visitMinIndexPath(SqmMinIndexPath<?> path) {
public Object visitIndexAggregateFunction(SqmIndexAggregateFunction<?> path) {
return path;
}

View File

@ -168,10 +168,8 @@ import org.hibernate.query.sqm.tree.domain.SqmEmbeddedValuedSimplePath;
import org.hibernate.query.sqm.tree.domain.SqmEntityValuedSimplePath;
import org.hibernate.query.sqm.tree.domain.SqmIndexedCollectionAccessPath;
import org.hibernate.query.sqm.tree.domain.SqmMapEntryReference;
import org.hibernate.query.sqm.tree.domain.SqmMaxElementPath;
import org.hibernate.query.sqm.tree.domain.SqmMaxIndexPath;
import org.hibernate.query.sqm.tree.domain.SqmMinElementPath;
import org.hibernate.query.sqm.tree.domain.SqmMinIndexPath;
import org.hibernate.query.sqm.tree.domain.SqmElementAggregateFunction;
import org.hibernate.query.sqm.tree.domain.SqmIndexAggregateFunction;
import org.hibernate.query.sqm.tree.domain.SqmPath;
import org.hibernate.query.sqm.tree.domain.SqmPluralValuedSimplePath;
import org.hibernate.query.sqm.tree.domain.SqmTreatedPath;
@ -3270,24 +3268,29 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
);
}
@Override
public Expression visitMaxElementPath(SqmMaxElementPath<?> path) {
return createMinOrMaxIndexOrElement( path, false, true );
protected Expression createMinOrMaxIndexOrElement(
AbstractSqmSpecificPluralPartPath<?> pluralPartPath,
boolean index,
String functionName) {
boolean isMinOrMax = functionName.equalsIgnoreCase("min")
|| functionName.equalsIgnoreCase("max");
// Try to create a lateral sub-query join if possible which allows the re-use of the expression
if ( isMinOrMax && creationContext.getSessionFactory().getJdbcServices().getDialect().supportsLateral() ) {
return createLateralJoinExpression( pluralPartPath, index, functionName );
}
else {
return createCorrelatedAggregateSubQuery( pluralPartPath, index, functionName );
}
}
@Override
public Expression visitMinElementPath(SqmMinElementPath<?> path) {
return createMinOrMaxIndexOrElement( path, false, false );
public Expression visitElementAggregateFunction(SqmElementAggregateFunction<?> path) {
return createMinOrMaxIndexOrElement( path, false, path.getFunctionName() );
}
@Override
public Expression visitMaxIndexPath(SqmMaxIndexPath<?> path) {
return createMinOrMaxIndexOrElement( path, true, true );
}
@Override
public Expression visitMinIndexPath(SqmMinIndexPath<?> path) {
return createMinOrMaxIndexOrElement( path, true, false );
public Expression visitIndexAggregateFunction(SqmIndexAggregateFunction<?> path) {
return createMinOrMaxIndexOrElement( path, true, path.getFunctionName() );
}
@Override
@ -3466,23 +3469,10 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
};
}
protected Expression createMinOrMaxIndexOrElement(
AbstractSqmSpecificPluralPartPath<?> pluralPartPath,
boolean index,
boolean max) {
// Try to create a lateral sub-query join if possible which allows the re-use of the expression
if ( creationContext.getSessionFactory().getJdbcServices().getDialect().supportsLateral() ) {
return createLateralJoinExpression( pluralPartPath, index, max );
}
else {
return createCorrelatedAggregateSubQuery( pluralPartPath, index, max );
}
}
protected Expression createCorrelatedAggregateSubQuery(
AbstractSqmSpecificPluralPartPath<?> pluralPartPath,
boolean index,
boolean max) {
String function) {
prepareReusablePath( pluralPartPath.getLhs(), () -> null );
final PluralAttributeMapping pluralAttributeMapping = (PluralAttributeMapping) determineValueMapping(
@ -3520,11 +3510,12 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
registerPluralTableGroupParts( tableGroup );
subQuerySpec.getFromClause().addRoot( tableGroup );
final AbstractSqmSelfRenderingFunctionDescriptor functionDescriptor = (AbstractSqmSelfRenderingFunctionDescriptor) creationContext
final AbstractSqmSelfRenderingFunctionDescriptor functionDescriptor =
(AbstractSqmSelfRenderingFunctionDescriptor) creationContext
.getSessionFactory()
.getQueryEngine()
.getSqmFunctionRegistry()
.findFunctionDescriptor( max ? "max" : "min" );
.findFunctionDescriptor( function );
final CollectionPart collectionPart = index
? pluralAttributeMapping.getIndexDescriptor()
: pluralAttributeMapping.getElementDescriptor();
@ -3591,7 +3582,7 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
protected Expression createLateralJoinExpression(
AbstractSqmSpecificPluralPartPath<?> pluralPartPath,
boolean index,
boolean max) {
String functionName) {
prepareReusablePath( pluralPartPath.getLhs(), () -> null );
final PluralAttributeMapping pluralAttributeMapping = (PluralAttributeMapping) determineValueMapping(
@ -3611,7 +3602,7 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
modelPart = collectionPart;
}
final int jdbcTypeCount = modelPart.getJdbcTypeCount();
final String pathName = ( max ? "max" : "min" ) + ( index ? "_index" : "_element" );
final String pathName = functionName + ( index ? "_index" : "_element" );
final String identifierVariable = parentTableGroup.getPrimaryTableReference().getIdentificationVariable()
+ "_" + pathName;
final NavigablePath queryPath = new NavigablePath( parentTableGroup.getNavigablePath(), pathName, identifierVariable );
@ -3680,7 +3671,9 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
subQuerySpec.addSortSpecification(
new SortSpecification(
columnReference,
max ? SortOrder.DESCENDING : SortOrder.ASCENDING
functionName.equalsIgnoreCase("max")
? SortOrder.DESCENDING
: SortOrder.ASCENDING
)
);
resultColumnReferences.add(

View File

@ -14,16 +14,23 @@ import org.hibernate.query.sqm.SqmPathSource;
/**
* @author Steve Ebersole
*/
public class SqmMaxElementPath<T> extends AbstractSqmSpecificPluralPartPath<T> {
public class SqmElementAggregateFunction<T> extends AbstractSqmSpecificPluralPartPath<T> {
public static final String NAVIGABLE_NAME = "{max-element}";
public SqmMaxElementPath(SqmPath<?> pluralDomainPath) {
private final String functionName;
public SqmElementAggregateFunction(SqmPath<?> pluralDomainPath, String functionName) {
//noinspection unchecked
super(
pluralDomainPath.getNavigablePath().getParent().append( pluralDomainPath.getNavigablePath().getLocalName(), NAVIGABLE_NAME ),
pluralDomainPath,
(PluralPersistentAttribute<?, ?, T>) pluralDomainPath.getReferencedPathSource()
);
this.functionName = functionName;
}
public String getFunctionName() {
return functionName;
}
@Override
@ -43,12 +50,12 @@ public class SqmMaxElementPath<T> extends AbstractSqmSpecificPluralPartPath<T> {
@Override
public <X> X accept(SemanticQueryWalker<X> walker) {
return walker.visitMaxElementPath( this );
return walker.visitElementAggregateFunction( this );
}
@Override
public void appendHqlString(StringBuilder sb) {
sb.append( "maxelement(" );
sb.append(functionName).append( "(" );
getLhs().appendHqlString( sb );
sb.append( ')' );
}

View File

@ -11,24 +11,25 @@ import org.hibernate.metamodel.model.domain.MapPersistentAttribute;
import org.hibernate.metamodel.model.domain.PluralPersistentAttribute;
import org.hibernate.query.sqm.SqmPathSource;
import org.hibernate.query.sqm.SemanticQueryWalker;
import org.hibernate.query.hql.spi.SemanticPathPart;
import org.hibernate.query.hql.spi.SqmCreationState;
/**
* @author Steve Ebersole
*/
public class SqmMaxIndexPath<T> extends AbstractSqmSpecificPluralPartPath<T> {
public class SqmIndexAggregateFunction<T> extends AbstractSqmSpecificPluralPartPath<T> {
public static final String NAVIGABLE_NAME = "{max-index}";
private final SqmPathSource<T> indexPathSource;
private final String functionName;
public SqmMaxIndexPath(SqmPath<?> pluralDomainPath) {
public SqmIndexAggregateFunction(SqmPath<?> pluralDomainPath, String functionName) {
//noinspection unchecked
super(
pluralDomainPath.getNavigablePath().getParent().append( pluralDomainPath.getNavigablePath().getLocalName(), NAVIGABLE_NAME ),
pluralDomainPath,
(PluralPersistentAttribute<?, ?, T>) pluralDomainPath.getReferencedPathSource()
);
this.functionName = functionName;
if ( getPluralAttribute() instanceof ListPersistentAttribute ) {
//noinspection unchecked
@ -43,6 +44,10 @@ public class SqmMaxIndexPath<T> extends AbstractSqmSpecificPluralPartPath<T> {
}
}
public String getFunctionName() {
return functionName;
}
@Override
public SqmPath<?> resolvePathPart(
String name,
@ -60,12 +65,12 @@ public class SqmMaxIndexPath<T> extends AbstractSqmSpecificPluralPartPath<T> {
@Override
public <X> X accept(SemanticQueryWalker<X> walker) {
return walker.visitMaxIndexPath( this );
return walker.visitIndexAggregateFunction( this );
}
@Override
public void appendHqlString(StringBuilder sb) {
sb.append( "maxindex(" );
sb.append(functionName).append( "(" );
getLhs().appendHqlString( sb );
sb.append( ')' );
}

View File

@ -1,58 +0,0 @@
/*
* 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.domain;
import org.hibernate.metamodel.model.domain.ManagedDomainType;
import org.hibernate.metamodel.model.domain.PluralPersistentAttribute;
import org.hibernate.query.SemanticException;
import org.hibernate.query.sqm.SqmPathSource;
import org.hibernate.query.sqm.SemanticQueryWalker;
import org.hibernate.query.hql.spi.SemanticPathPart;
import org.hibernate.query.hql.spi.SqmCreationState;
/**
* @author Steve Ebersole
*/
public class SqmMinElementPath<T> extends AbstractSqmSpecificPluralPartPath<T> {
public static final String NAVIGABLE_NAME = "{min-element}";
public SqmMinElementPath(SqmPath<?> pluralDomainPath) {
//noinspection unchecked
super(
pluralDomainPath.getNavigablePath().getParent().append( pluralDomainPath.getNavigablePath().getLocalName(), NAVIGABLE_NAME ),
pluralDomainPath,
(PluralPersistentAttribute<?, ?, T>) pluralDomainPath.getReferencedPathSource()
);
}
@Override
public SqmPath<?> resolvePathPart(
String name,
boolean isTerminal,
SqmCreationState creationState) {
final SqmPath<?> sqmPath = get( name );
creationState.getProcessingStateStack().getCurrent().getPathRegistry().register( sqmPath );
return sqmPath;
}
@Override
public SqmPathSource<T> getReferencedPathSource() {
return getPluralAttribute().getElementPathSource();
}
@Override
public <X> X accept(SemanticQueryWalker<X> walker) {
return walker.visitMinElementPath( this );
}
@Override
public void appendHqlString(StringBuilder sb) {
sb.append( "minelement(" );
getLhs().appendHqlString( sb );
sb.append( ')' );
}
}

View File

@ -1,73 +0,0 @@
/*
* 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.domain;
import org.hibernate.metamodel.model.domain.ListPersistentAttribute;
import org.hibernate.metamodel.model.domain.MapPersistentAttribute;
import org.hibernate.metamodel.model.domain.PluralPersistentAttribute;
import org.hibernate.query.sqm.SqmPathSource;
import org.hibernate.query.sqm.SemanticQueryWalker;
import org.hibernate.query.hql.spi.SemanticPathPart;
import org.hibernate.query.hql.spi.SqmCreationState;
/**
* @author Steve Ebersole
*/
public class SqmMinIndexPath<T> extends AbstractSqmSpecificPluralPartPath<T> {
public static final String NAVIGABLE_NAME = "{min-index}";
private final SqmPathSource<T> indexPathSource;
public SqmMinIndexPath(SqmPath<?> pluralDomainPath) {
//noinspection unchecked
super(
pluralDomainPath.getNavigablePath().getParent().append( pluralDomainPath.getNavigablePath().getLocalName(), NAVIGABLE_NAME ),
pluralDomainPath,
(PluralPersistentAttribute<?, ?, T>) pluralDomainPath.getReferencedPathSource()
);
if ( getPluralAttribute() instanceof ListPersistentAttribute ) {
//noinspection unchecked
this.indexPathSource = (SqmPathSource<T>) getPluralAttribute().getIndexPathSource();
}
else if ( getPluralAttribute() instanceof MapPersistentAttribute ) {
//noinspection unchecked
this.indexPathSource = ( (MapPersistentAttribute<?, T, ?>) getPluralAttribute() ).getKeyPathSource();
}
else {
throw new UnsupportedOperationException( "Plural attribute [" + getPluralAttribute() + "] is not indexed" );
}
}
@Override
public SqmPath<?> resolvePathPart(
String name,
boolean isTerminal,
SqmCreationState creationState) {
final SqmPath<?> sqmPath = get( name );
creationState.getProcessingStateStack().getCurrent().getPathRegistry().register( sqmPath );
return sqmPath;
}
@Override
public SqmPathSource<T> getReferencedPathSource() {
return indexPathSource;
}
@Override
public <X> X accept(SemanticQueryWalker<X> walker) {
return walker.visitMinIndexPath( this );
}
@Override
public void appendHqlString(StringBuilder sb) {
sb.append( "minindex(" );
getLhs().appendHqlString( sb );
sb.append( ')' );
}
}

View File

@ -43,13 +43,13 @@ public class MapIndexFormulaTest {
session.createQuery( "from Group g join g.users u where g.name = 'something' and index(u) = 'nada'" )
.list();
session.createQuery(
"from Group g join g.users u where g.name = 'something' and minindex(u) = 'nada'" )
"from Group g where g.name = 'something' and minindex(g.users) = 'nada'" )
.list();
session.createQuery(
"from Group g join g.users u where g.name = 'something' and maxindex(u) = 'nada'" )
"from Group g where g.name = 'something' and maxindex(g.users) = 'nada'" )
.list();
session.createQuery(
"from Group g join g.users u where g.name = 'something' and maxindex(u) = 'nada' and maxindex(u) = 'nada'" )
"from Group g where g.name = 'something' and maxindex(g.users) = 'nada' and maxindex(g.users) = 'nada'" )
.list();
}
);

View File

@ -52,7 +52,7 @@ public class PluralAttributeMappingTests {
final MappingMetamodel domainModel = scope.getSessionFactory().getDomainModel();
final EntityMappingType containerEntityDescriptor = domainModel.getEntityDescriptor( EntityOfLists.class );
assertThat( containerEntityDescriptor.getNumberOfAttributeMappings(), is( 7 ) );
assertThat( containerEntityDescriptor.getNumberOfAttributeMappings(), is( 8 ) );
final AttributeMapping listOfBasics = containerEntityDescriptor.findAttributeMapping( "listOfBasics" );
assertThat( listOfBasics, notNullValue() );
@ -116,7 +116,7 @@ public class PluralAttributeMappingTests {
final MappingMetamodel domainModel = scope.getSessionFactory().getDomainModel();
final EntityMappingType containerEntityDescriptor = domainModel.getEntityDescriptor( EntityOfMaps.class );
assertThat( containerEntityDescriptor.getNumberOfAttributeMappings(), is( 16 ) );
assertThat( containerEntityDescriptor.getNumberOfAttributeMappings(), is( 17 ) );
final PluralAttributeMapping basicByBasic = (PluralAttributeMapping) containerEntityDescriptor.findAttributeMapping( "basicByBasic" );
assertThat( basicByBasic, notNullValue() );

View File

@ -67,10 +67,13 @@ public class FunctionTests {
EntityOfLists eol = new EntityOfLists(1,"");
eol.addBasic("hello");
eol.addNumber(1.0);
eol.addNumber(2.0);
em.persist(eol);
EntityOfMaps eom = new EntityOfMaps(2,"");
eom.addBasicByBasic("hello", "world");
eom.addNumberByNumber(1,1.0);
em.persist(eom);
}
);
@ -90,6 +93,45 @@ public class FunctionTests {
);
}
@Test
@RequiresDialect(H2Dialect.class)
@RequiresDialect(HSQLDialect.class)
@RequiresDialect(DerbyDialect.class)
@RequiresDialect(MySQLDialect.class)
@RequiresDialect(SybaseDialect.class)
@RequiresDialect(MariaDBDialect.class)
@RequiresDialect(TiDBDialect.class)
// it's failing on the other dialects due to a bug in query translator
public void testMaxMinSumIndexElement(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
assertThat( session.createQuery("select max(index eol.listOfNumbers) from EntityOfLists eol")
.getSingleResult(), is(1) );
assertThat( session.createQuery("select max(element eol.listOfNumbers) from EntityOfLists eol")
.list().get(0),
// .getSingleResult(),
is(2.0) );
assertThat( session.createQuery("select sum(index eol.listOfNumbers) from EntityOfLists eol")
.getSingleResult(), is(1) );
assertThat( session.createQuery("select sum(element eol.listOfNumbers) from EntityOfLists eol")
.list().get(0),
// .getSingleResult(),
is(3.0) );
assertThat( session.createQuery("select max(index eom.numberByNumber) from EntityOfMaps eom")
.getSingleResult(), is(1) );
assertThat( session.createQuery("select max(element eom.numberByNumber) from EntityOfMaps eom")
.getSingleResult(), is(1.0) );
assertThat( session.createQuery("select sum(index eom.numberByNumber) from EntityOfMaps eom")
.getSingleResult(), is(1) );
assertThat( session.createQuery("select sum(element eom.numberByNumber) from EntityOfMaps eom")
.getSingleResult(), is(1.0) );
}
);
}
@Test
@RequiresDialect(H2Dialect.class)
@RequiresDialect(HSQLDialect.class)
@ -102,19 +144,11 @@ public class FunctionTests {
public void testMaxindexMaxelement(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
assertThat( session.createQuery("select maxindex(l) from EntityOfLists eol join eol.listOfBasics l")
.getSingleResult(), is(0) );
assertThat( session.createQuery("select maxelement(l) from EntityOfLists eol join eol.listOfBasics l")
.getSingleResult(), is("hello") );
assertThat( session.createQuery("select maxindex(eol.listOfBasics) from EntityOfLists eol")
.getSingleResult(), is(0) );
assertThat( session.createQuery("select maxelement(eol.listOfBasics) from EntityOfLists eol")
.getSingleResult(), is("hello") );
assertThat( session.createQuery("select maxindex(m) from EntityOfMaps eom join eom.basicByBasic m")
.getSingleResult(), is("hello") );
assertThat( session.createQuery("select maxelement(m) from EntityOfMaps eom join eom.basicByBasic m")
.getSingleResult(), is("world") );
assertThat( session.createQuery("select maxindex(eom.basicByBasic) from EntityOfMaps eom")
.getSingleResult(), is("hello") );
assertThat( session.createQuery("select maxelement(eom.basicByBasic) from EntityOfMaps eom")

View File

@ -126,9 +126,9 @@ public class TernaryTest extends BaseCoreFunctionalTestCase {
session.beginTransaction();
session.createQuery( "from Employee e join e.managerBySite as m where index(m) is not null" )
.list();
session.createQuery( "from Employee e join e.managerBySite as m where minIndex(m) is not null" )
session.createQuery( "from Employee e where minIndex(e.managerBySite) is not null" )
.list();
session.createQuery( "from Employee e join e.managerBySite as m where maxIndex(m) is not null" )
session.createQuery( "from Employee e where maxIndex(e.managerBySite) is not null" )
.list();
session.getTransaction().commit();
session.close();

View File

@ -29,6 +29,7 @@ public class EntityOfLists {
private String name;
private List<String> listOfBasics;
private List<Double> listOfNumbers;
private List<EnumValue> listOfConvertedEnums;
private List<EnumValue> listOfEnums;
@ -78,6 +79,17 @@ public class EntityOfLists {
this.listOfBasics = listOfBasics;
}
@ElementCollection
@OrderColumn(name="num_indx")
@CollectionTable(name = "EntityOfLists_numbers")
public List<Double> getListOfNumbers() {
return listOfNumbers;
}
public void setListOfNumbers(List<Double> listOfNumbers) {
this.listOfNumbers = listOfNumbers;
}
public void addBasic(String basic) {
if ( listOfBasics == null ) {
listOfBasics = new ArrayList<>();
@ -85,6 +97,13 @@ public class EntityOfLists {
listOfBasics.add( basic );
}
public void addNumber(double number) {
if ( listOfNumbers == null ) {
listOfNumbers = new ArrayList<>();
}
listOfNumbers.add( number );
}
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// listOfConvertedEnums

View File

@ -40,6 +40,7 @@ public class EntityOfMaps {
private String name;
private Map<String, String> basicByBasic;
private Map<Integer, Double> numberByNumber;
private SortedMap<String, String> sortedBasicByBasic;
private SortedMap<String, String> sortedBasicByBasicWithComparator;
@ -109,6 +110,25 @@ public class EntityOfMaps {
basicByBasic.put( key, val );
}
@ElementCollection
@CollectionTable(name = "EntityOfMaps_number_number1")
@MapKeyColumn(name = "number_key")
@Column(name = "number_val")
public Map<Integer, Double> getNumberByNumber() {
return numberByNumber;
}
public void setNumberByNumber(Map<Integer, Double> numberByNumber) {
this.numberByNumber = numberByNumber;
}
public void addNumberByNumber(int key, double val) {
if ( numberByNumber == null ) {
numberByNumber = new HashMap<>();
}
numberByNumber.put( key, val );
}
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// sortedBasicByBasic