Fix some issues with collection-path related HQL functions minindex/maxindex/minelement/maxelement/elements/indices and group by item rendering problems

This commit is contained in:
Christian Beikov 2021-08-03 09:56:49 +02:00
parent 4acdf1ab3e
commit ca22db14bb
23 changed files with 378 additions and 116 deletions

View File

@ -2006,7 +2006,7 @@ public class HQLTest extends BaseEntityManagerFunctionalTestCase {
"where p.person = ph.person ", Object[].class )
.getResultList();
//end::hql-relational-comparisons-example[]
assertEquals(3, phonePayments.size());
assertEquals(2, phonePayments.size());
});
}

View File

@ -197,6 +197,7 @@ HOUR : [hH] [oO] [uU] [rR];
IFNULL : [iI] [fF] [nN] [uU] [lL] [lL];
IN : [iI] [nN];
INDEX : [iI] [nN] [dD] [eE] [xX];
INDICES : [iI] [nN] [dD] [iI] [cC] [eE] [sS];
INNER : [iI] [nN] [nN] [eE] [rR];
INSERT : [iI] [nN] [sS] [eE] [rR] [tT];
INSTANT : [iI] [nN] [sS] [tT] [aA] [nN] [tT];

View File

@ -255,6 +255,7 @@ pathContinuation
*
* * TREAT( path )
* * ELEMENTS( path )
* * INDICES( path )
* * VALUE( path )
* * KEY( path )
* * path[ selector ]
@ -262,6 +263,7 @@ pathContinuation
syntacticDomainPath
: treatedNavigablePath
| collectionElementNavigablePath
| collectionIndexNavigablePath
| mapKeyNavigablePath
| dotIdentifierSequence indexedPathAccessFragment
;
@ -289,6 +291,10 @@ collectionElementNavigablePath
: (VALUE | ELEMENTS) LEFT_PAREN path RIGHT_PAREN pathContinuation?
;
collectionIndexNavigablePath
: INDICES LEFT_PAREN path RIGHT_PAREN
;
mapKeyNavigablePath
: KEY LEFT_PAREN path RIGHT_PAREN pathContinuation?
;
@ -401,6 +407,7 @@ predicate
| expression (NOT)? BETWEEN expression AND expression # BetweenPredicate
| expression (NOT)? LIKE expression (likeEscape)? # LikePredicate
| expression comparisonOperator expression # ComparisonPredicate
| EXISTS (ELEMENTS|INDICES) LEFT_PAREN dotIdentifierSequence RIGHT_PAREN # ExistsCollectionPartPredicate
| EXISTS expression # ExistsPredicate
| expression (NOT)? MEMBER OF path # MemberOfPredicate
| NOT predicate # NegatedPredicate
@ -421,7 +428,7 @@ comparisonOperator
;
inList
: ELEMENTS? LEFT_PAREN dotIdentifierSequence RIGHT_PAREN # PersistentCollectionReferenceInList
: (ELEMENTS|INDICES) LEFT_PAREN dotIdentifierSequence RIGHT_PAREN # PersistentCollectionReferenceInList
| LEFT_PAREN expression (COMMA expression)* RIGHT_PAREN # ExplicitTupleInList
| LEFT_PAREN subQuery RIGHT_PAREN # SubQueryInList
| parameter # ParamInList
@ -713,11 +720,13 @@ countFunction
everyFunction
: (EVERY|ALL) LEFT_PAREN predicate RIGHT_PAREN filterClause?
| (EVERY|ALL) LEFT_PAREN subQuery RIGHT_PAREN
| (EVERY|ALL) (ELEMENTS|INDICES) LEFT_PAREN dotIdentifierSequence RIGHT_PAREN
;
anyFunction
: (ANY|SOME) LEFT_PAREN predicate RIGHT_PAREN filterClause?
| (ANY|SOME) LEFT_PAREN subQuery RIGHT_PAREN
| (ANY|SOME) (ELEMENTS|INDICES) LEFT_PAREN dotIdentifierSequence RIGHT_PAREN
;
filterClause
@ -1170,6 +1179,7 @@ identifier
| IFNULL
| IN
| INDEX
| INDICES
| INNER
| INSERT
| INSTANT

View File

@ -3810,8 +3810,8 @@ public abstract class Dialect implements ConversionContext {
return null;
}
public boolean supportsSelectAliasInGroupByClause() {
return false;
public SelectItemReferenceStrategy getGroupBySelectItemReferenceStrategy() {
return SelectItemReferenceStrategy.EXPRESSION;
}
public SizeStrategy getSizeStrategy() {

View File

@ -429,8 +429,8 @@ public class H2Dialect extends Dialect {
}
@Override
public boolean supportsSelectAliasInGroupByClause() {
return true;
public SelectItemReferenceStrategy getGroupBySelectItemReferenceStrategy() {
return SelectItemReferenceStrategy.ALIAS;
}
@Override

View File

@ -499,8 +499,8 @@ public class MySQLDialect extends Dialect {
}
@Override
public boolean supportsSelectAliasInGroupByClause() {
return true;
public SelectItemReferenceStrategy getGroupBySelectItemReferenceStrategy() {
return SelectItemReferenceStrategy.POSITION;
}
@Override

View File

@ -646,8 +646,8 @@ public class PostgreSQLDialect extends Dialect {
}
@Override
public boolean supportsSelectAliasInGroupByClause() {
return true;
public SelectItemReferenceStrategy getGroupBySelectItemReferenceStrategy() {
return SelectItemReferenceStrategy.POSITION;
}
@Override

View File

@ -0,0 +1,27 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.dialect;
/**
* Strategies for referring to a select item.
*
* @author Christian Beikov
*/
public enum SelectItemReferenceStrategy {
/**
* The default strategy i.e. render the expression again.
*/
EXPRESSION,
/**
* Refer to the item via its alias.
*/
ALIAS,
/**
* Refer to the item via its position.
*/
POSITION;
}

View File

@ -29,7 +29,6 @@ class ListAttributeImpl<X, E> extends AbstractPluralAttribute<X, List<E>, E> imp
//noinspection unchecked
this.indexPathSource = (SqmPathSource<Integer>) SqmMappingModelHelper.resolveSqmKeyPathSource(
getName(),
builder.getListIndexOrMapKeyType(),
BindableType.PLURAL_ATTRIBUTE
);

View File

@ -30,7 +30,6 @@ class MapAttributeImpl<X, K, V> extends AbstractPluralAttribute<X, Map<K, V>, V>
super( xceBuilder, metadataContext );
this.keyPathSource = SqmMappingModelHelper.resolveSqmKeyPathSource(
CollectionPart.Nature.INDEX.getName(),
xceBuilder.getListIndexOrMapKeyType(),
BindableType.PLURAL_ATTRIBUTE
);

View File

@ -747,6 +747,10 @@ public class MappingMetamodelImpl implements MappingMetamodel, MetamodelImplemen
return (MappingModelExpressable) sqmExpressable;
}
if ( sqmExpressable instanceof EntityDomainType<?> ) {
return getEntityDescriptor( ( (EntityDomainType<?>) sqmExpressable ).getHibernateEntityName() );
}
throw new UnsupportedOperationException( "Cannot determine proper mapping model expressable for " + sqmExpressable );
}

View File

@ -1693,8 +1693,8 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> implem
HqlParser.JpaCollectionJoinContext ctx,
SqmRoot<?> sqmRoot) {
final HqlParser.IdentificationVariableDefContext identificationVariableDefContext;
if ( ctx.getChildCount() > 1 ) {
identificationVariableDefContext = (HqlParser.IdentificationVariableDefContext) ctx.getChild( 1 );
if ( ctx.getChildCount() > 5 ) {
identificationVariableDefContext = (HqlParser.IdentificationVariableDefContext) ctx.getChild( 5 );
}
else {
identificationVariableDefContext = null;
@ -2047,13 +2047,35 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> implem
creationContext.getNodeBuilder()
);
}
else if ( inListContext instanceof HqlParser.PersistentCollectionReferenceInListContext ) {
if ( getCreationOptions().useStrictJpaCompliance() ) {
throw new StrictJpaComplianceViolation( StrictJpaComplianceViolation.Type.HQL_COLLECTION_FUNCTION );
}
final HqlParser.PersistentCollectionReferenceInListContext collectionReferenceInListContext = (HqlParser.PersistentCollectionReferenceInListContext) inListContext;
return new SqmInSubQueryPredicate<>(
testExpression,
createCollectionReferenceSubQuery(
(HqlParser.DotIdentifierSequenceContext) collectionReferenceInListContext.getChild( 2 ),
(TerminalNode) collectionReferenceInListContext.getChild( 0 )
),
negated,
creationContext.getNodeBuilder()
);
}
else {
// todo : handle PersistentCollectionReferenceInList labeled branch
throw new ParsingException( "Unexpected IN predicate type [" + ctx.getClass().getSimpleName() + "] : " + ctx.getText() );
}
}
@Override
public SqmPredicate visitExistsCollectionPartPredicate(HqlParser.ExistsCollectionPartPredicateContext ctx) {
final SqmSubQuery<Object> subQuery = createCollectionReferenceSubQuery(
(HqlParser.DotIdentifierSequenceContext) ctx.getChild( 3 ),
null
);
return new SqmExistsPredicate( subQuery, creationContext.getNodeBuilder() );
}
@Override
public SqmPredicate visitExistsPredicate(HqlParser.ExistsPredicateContext ctx) {
final SqmExpression<?> expression = (SqmExpression<?>) ctx.getChild( 1 ).accept( this );
@ -3764,15 +3786,14 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> implem
final SqmPredicate filterExpression = getFilterExpression( ctx );
final ParseTree argumentChild = ctx.getChild( 2 );
if ( argumentChild instanceof HqlParser.SubQueryContext ) {
SqmSubQuery<?> subquery = (SqmSubQuery<?>) argumentChild.accept(this);
final SqmSubQuery<?> subquery = (SqmSubQuery<?>) argumentChild.accept( this );
return new SqmEvery<>( subquery, creationContext.getNodeBuilder() );
}
final SqmExpression<?> argument = (SqmExpression<?>) argumentChild.accept( this );
if ( argument instanceof SqmSubQuery<?> && ctx.getChild( ctx.getChildCount() - 1) instanceof HqlParser.FilterClauseContext ) {
throw new SemanticException( "Quantified expression cannot have a filter clause!" );
else if ( argumentChild instanceof HqlParser.PredicateContext ) {
if ( getCreationOptions().useStrictJpaCompliance() ) {
throw new StrictJpaComplianceViolation( StrictJpaComplianceViolation.Type.FUNCTION_CALL );
}
final SqmExpression<?> argument = (SqmExpression<?>) argumentChild.accept( this );
return getFunctionDescriptor( "every" ).generateAggregateSqmExpression(
singletonList( argument ),
@ -3782,21 +3803,33 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> implem
creationContext.getJpaMetamodel().getTypeConfiguration()
);
}
else {
if ( getCreationOptions().useStrictJpaCompliance() ) {
throw new StrictJpaComplianceViolation( StrictJpaComplianceViolation.Type.HQL_COLLECTION_FUNCTION );
}
return new SqmEvery<>(
createCollectionReferenceSubQuery(
(HqlParser.DotIdentifierSequenceContext) ctx.getChild( 3 ),
(TerminalNode) ctx.getChild( 1 )
),
creationContext.getNodeBuilder()
);
}
}
@Override
public SqmExpression<?> visitAnyFunction(HqlParser.AnyFunctionContext ctx) {
final SqmPredicate filterExpression = getFilterExpression( ctx );
final ParseTree argumentChild = ctx.getChild( 2 );
if ( argumentChild instanceof HqlParser.SubQueryContext ) {
SqmSubQuery<?> subquery = (SqmSubQuery<?>) argumentChild.accept(this);
final SqmSubQuery<?> subquery = (SqmSubQuery<?>) argumentChild.accept( this );
return new SqmAny<>( subquery, creationContext.getNodeBuilder() );
}
final SqmExpression<?> argument = (SqmExpression<?>) argumentChild.accept( this );
if ( argument instanceof SqmSubQuery<?> && ctx.getChild( ctx.getChildCount() - 1) instanceof HqlParser.FilterClauseContext ) {
throw new SemanticException( "Quantified expression cannot have a filter clause!" );
else if ( argumentChild instanceof HqlParser.PredicateContext ) {
if ( getCreationOptions().useStrictJpaCompliance() ) {
throw new StrictJpaComplianceViolation( StrictJpaComplianceViolation.Type.FUNCTION_CALL );
}
final SqmExpression<?> argument = (SqmExpression<?>) argumentChild.accept( this );
return getFunctionDescriptor( "any" ).generateAggregateSqmExpression(
singletonList( argument ),
@ -3806,6 +3839,71 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> implem
creationContext.getJpaMetamodel().getTypeConfiguration()
);
}
else {
if ( getCreationOptions().useStrictJpaCompliance() ) {
throw new StrictJpaComplianceViolation( StrictJpaComplianceViolation.Type.HQL_COLLECTION_FUNCTION );
}
return new SqmAny<>(
createCollectionReferenceSubQuery(
(HqlParser.DotIdentifierSequenceContext) ctx.getChild( 3 ),
(TerminalNode) ctx.getChild( 1 )
),
creationContext.getNodeBuilder()
);
}
}
private <X> SqmSubQuery<X> createCollectionReferenceSubQuery(
HqlParser.DotIdentifierSequenceContext pathCtx,
TerminalNode collectionReferenceCtx) {
final SqmPath<?> pluralAttributePath = consumeDomainPath( pathCtx );
final SqmPathSource<?> referencedPathSource = pluralAttributePath.getReferencedPathSource();
if ( !(referencedPathSource instanceof PluralPersistentAttribute ) ) {
throw new PathException(
"Illegal attempt to treat non-plural path as a plural path : " + pluralAttributePath.getNavigablePath()
);
}
final PluralPersistentAttribute<?, ?, ?> attribute = (PluralPersistentAttribute<?, ?, ?>) referencedPathSource;
final SqmSubQuery<?> subQuery = new SqmSubQuery<>(
processingStateStack.getCurrent().getProcessingQuery(),
creationContext.getNodeBuilder()
);
final SqmSelectClause selectClause = new SqmSelectClause( false, 1, creationContext.getNodeBuilder() );
final SqmFromClause fromClause = new SqmFromClause( 1 );
final SqmCorrelation<?, ?> correlation = ( (AbstractSqmFrom<?, ?>) pluralAttributePath.getLhs() ).createCorrelation();
final SqmAttributeJoin<?, ?> collectionJoin = correlation.join( pluralAttributePath.getNavigablePath().getUnaliasedLocalName() );
fromClause.addRoot( correlation.getCorrelatedRoot() );
if ( collectionReferenceCtx == null ) {
final SqmLiteral<Integer> literal = new SqmLiteral<>(
1,
StandardBasicTypes.INTEGER,
creationContext.getNodeBuilder()
);
subQuery.applyInferableType( literal.getNodeType() );
selectClause.setSelection( literal );
}
else {
final SqmPathSource<?> pathSource;
switch ( collectionReferenceCtx.getSymbol().getType() ) {
case HqlParser.ELEMENTS:
pathSource = attribute.getElementPathSource();
break;
case HqlParser.INDICES:
pathSource = attribute.getIndexPathSource();
break;
default:
throw new ParsingException( "Unexpected collection reference : " + collectionReferenceCtx.getText() );
}
subQuery.applyInferableType( pathSource.getSqmPathType() );
selectClause.setSelection( pathSource.createSqmPath( collectionJoin ) );
}
final SqmQuerySpec<?> querySpec = subQuery.getQuerySpec();
querySpec.setFromClause( fromClause );
querySpec.setSelectClause( selectClause );
//noinspection unchecked
return (SqmSubQuery<X>) subQuery;
}
@Override
public SqmExpression<?> visitAvgFunction(HqlParser.AvgFunctionContext ctx) {
@ -4216,6 +4314,9 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> implem
else if ( firstChild instanceof HqlParser.CollectionElementNavigablePathContext ) {
return visitCollectionElementNavigablePath( (HqlParser.CollectionElementNavigablePathContext) firstChild );
}
else if ( firstChild instanceof HqlParser.CollectionIndexNavigablePathContext ) {
return visitCollectionIndexNavigablePath( (HqlParser.CollectionIndexNavigablePathContext) firstChild );
}
else if ( firstChild instanceof HqlParser.MapKeyNavigablePathContext ) {
return visitMapKeyNavigablePath( (HqlParser.MapKeyNavigablePathContext) firstChild );
}
@ -4384,6 +4485,30 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> implem
}
@Override
public SqmPath<?> visitCollectionIndexNavigablePath(HqlParser.CollectionIndexNavigablePathContext ctx) {
final SqmPath<?> pluralAttributePath = consumeDomainPath( (HqlParser.PathContext) ctx.getChild( 2 ) );
final SqmPathSource<?> referencedPathSource = pluralAttributePath.getReferencedPathSource();
if ( !(referencedPathSource instanceof PluralPersistentAttribute ) ) {
throw new PathException(
"Illegal attempt to treat non-plural path as a plural path : " + pluralAttributePath.getNavigablePath()
);
}
final PluralPersistentAttribute<?, ?, ?> attribute = (PluralPersistentAttribute<?, ?, ?>) referencedPathSource;
if ( getCreationOptions().useStrictJpaCompliance() ) {
if ( attribute.getCollectionClassification() != CollectionClassification.MAP ) {
throw new StrictJpaComplianceViolation( StrictJpaComplianceViolation.Type.VALUE_FUNCTION_ON_NON_MAP );
}
}
return attribute.getIndexPathSource().createSqmPath(
pluralAttributePath
);
}
@Override
@SuppressWarnings({ "rawtypes" })
public SqmPath visitMapKeyNavigablePath(HqlParser.MapKeyNavigablePathContext ctx) {

View File

@ -19,7 +19,7 @@ public class StrictJpaComplianceViolation extends SemanticException {
ALIASED_FETCH_JOIN( "aliased fetch join" ),
UNMAPPED_POLYMORPHISM( "unmapped polymorphic reference" ),
FUNCTION_CALL( "improper non-standard function call" ),
HQL_COLLECTION_FUNCTION( "use of HQL collection functions (maxelement, minelement, maxindex, minindex)"),
HQL_COLLECTION_FUNCTION( "use of HQL collection functions (maxelement, minelement, maxindex, minindex, elements, indices)"),
VALUE_FUNCTION_ON_NON_MAP( "use of value() function for non-Map type" ),
RESERVED_WORD_USED_AS_ALIAS( "use of reserved word as alias (identification variable or result variable)" ),
INDEXED_ELEMENT_REFERENCE( "use of HQL indexed element reference syntax" ),

View File

@ -11,9 +11,11 @@ import javax.persistence.metamodel.Bindable;
import org.hibernate.NotYetImplementedFor6Exception;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.metamodel.mapping.CollectionPart;
import org.hibernate.metamodel.mapping.EntityMappingType;
import org.hibernate.metamodel.mapping.MappingModelExpressable;
import org.hibernate.metamodel.mapping.ModelPart;
import org.hibernate.metamodel.mapping.ModelPartContainer;
import org.hibernate.metamodel.model.domain.AnyMappingDomainType;
import org.hibernate.metamodel.model.domain.BasicDomainType;
import org.hibernate.metamodel.model.domain.DomainType;
@ -61,11 +63,13 @@ public class SqmMappingModelHelper {
}
public static <J> SqmPathSource<J> resolveSqmKeyPathSource(
String name,
DomainType<J> valueDomainType,
Bindable.BindableType jpaBindableType) {
// todo (6.0): the key path source must create a special path for the key
return resolveSqmPathSource( name, valueDomainType, jpaBindableType );
return resolveSqmPathSource(
CollectionPart.Nature.INDEX.getName(),
valueDomainType,
jpaBindableType
);
}
public static <J> SqmPathSource<J> resolveSqmPathSource(
@ -149,7 +153,11 @@ public class SqmMappingModelHelper {
// Plural path parts are not joined and thus also have no table group
if ( sqmPath instanceof AbstractSqmSpecificPluralPartPath<?> ) {
final TableGroup lhsTableGroup = tableGroupLocator.apply( sqmPath.getLhs().getLhs().getNavigablePath() );
return lhsTableGroup.getModelPart().findSubPart( sqmPath.getReferencedPathSource().getPathName(), null );
final ModelPartContainer pluralPart = (ModelPartContainer) lhsTableGroup.getModelPart().findSubPart(
sqmPath.getLhs().getReferencedPathSource().getPathName(),
null
);
return pluralPart.findSubPart( sqmPath.getReferencedPathSource().getPathName(), null );
}
final TableGroup lhsTableGroup = tableGroupLocator.apply( sqmPath.getLhs().getNavigablePath() );

View File

@ -20,6 +20,7 @@ import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.metamodel.MappingMetamodel;
import org.hibernate.metamodel.mapping.Bindable;
import org.hibernate.metamodel.mapping.CollectionPart;
import org.hibernate.metamodel.mapping.ConvertibleModelPart;
import org.hibernate.metamodel.mapping.EntityIdentifierMapping;
import org.hibernate.metamodel.mapping.EntityMappingType;
@ -328,6 +329,9 @@ public class SqmUtil {
if ( parameterType == null ) {
throw new SqlTreeCreationException( "Unable to interpret mapping-model type for Query parameter : " + domainParam );
}
if ( parameterType instanceof CollectionPart && ( (CollectionPart) parameterType ).getPartMappingType() instanceof Bindable ) {
parameterType = (Bindable) ( (CollectionPart) parameterType ).getPartMappingType();
}
if ( parameterType instanceof EntityIdentifierMapping ) {
final EntityIdentifierMapping identifierMapping = (EntityIdentifierMapping) parameterType;
@ -336,6 +340,14 @@ public class SqmUtil {
bindValue = identifierMapping.getIdentifier( bindValue, session );
}
}
else if ( parameterType instanceof EntityMappingType ) {
final EntityIdentifierMapping identifierMapping = ( (EntityMappingType) parameterType ).getIdentifierMapping();
final EntityMappingType entityMapping = identifierMapping.findContainingEntityMapping();
parameterType = identifierMapping;
if ( entityMapping.getRepresentationStrategy().getInstantiator().isInstance( bindValue, session.getFactory() ) ) {
bindValue = identifierMapping.getIdentifier( bindValue, session );
}
}
else if ( parameterType instanceof ToOneAttributeMapping ) {
ToOneAttributeMapping association = (ToOneAttributeMapping) parameterType;
bindValue = association.getForeignKeyDescriptor().getAssociationKeyFromSide(

View File

@ -45,6 +45,7 @@ import org.hibernate.metamodel.MappingMetamodel;
import org.hibernate.metamodel.mapping.Association;
import org.hibernate.metamodel.mapping.AttributeMapping;
import org.hibernate.metamodel.mapping.BasicValuedMapping;
import org.hibernate.metamodel.mapping.Bindable;
import org.hibernate.metamodel.mapping.CollectionPart;
import org.hibernate.metamodel.mapping.ConvertibleModelPart;
import org.hibernate.metamodel.mapping.EmbeddableMappingType;
@ -52,6 +53,7 @@ import org.hibernate.metamodel.mapping.EmbeddableValuedModelPart;
import org.hibernate.metamodel.mapping.EntityAssociationMapping;
import org.hibernate.metamodel.mapping.EntityDiscriminatorMapping;
import org.hibernate.metamodel.mapping.EntityIdentifierMapping;
import org.hibernate.metamodel.mapping.EntityMappingType;
import org.hibernate.metamodel.mapping.EntityValuedModelPart;
import org.hibernate.metamodel.mapping.EntityVersionMapping;
import org.hibernate.metamodel.mapping.ForeignKeyDescriptor;
@ -2747,18 +2749,20 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
MappingModelExpressable valueMapping,
BiConsumer<Integer,JdbcParameter> jdbcParameterConsumer) {
sqmParameterMappingModelTypes.put( expression, valueMapping );
final Bindable bindable;
if ( valueMapping instanceof Association ) {
( (Association) valueMapping ).getForeignKeyDescriptor().forEachJdbcType(
(index, jdbcMapping) -> jdbcParameterConsumer.accept( index, new JdbcParameterImpl( jdbcMapping ) )
);
bindable = ( (Association) valueMapping ).getForeignKeyDescriptor();
}
else if ( valueMapping instanceof EntityMappingType ) {
bindable = ( (EntityMappingType) valueMapping ).getIdentifierMapping();
}
else {
valueMapping.forEachJdbcType(
bindable = valueMapping;
}
bindable.forEachJdbcType(
(index, jdbcMapping) -> jdbcParameterConsumer.accept( index, new JdbcParameterImpl( jdbcMapping ) )
);
}
}
@Override
public Object visitPositionalParameterExpression(SqmPositionalParameter expression) {
@ -4098,26 +4102,47 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
final CollectionPart collectionPart = index
? mappingModelExpressable.getIndexDescriptor()
: mappingModelExpressable.getElementDescriptor();
final List<SqlAstNode> arguments = new ArrayList<>( 1 );
final ModelPart modelPart;
if ( collectionPart instanceof EntityAssociationMapping ) {
modelPart = ( (EntityAssociationMapping) collectionPart ).getKeyTargetMatchPart();
}
else {
modelPart = collectionPart;
}
final List<Expression> arguments = new ArrayList<>( 1 );
final NavigablePath navigablePath = pluralPartPath.getNavigablePath();
collectionPart.forEachSelectable(
final int jdbcTypeCount = modelPart.getJdbcTypeCount();
final List<Expression> tupleElements;
if ( jdbcTypeCount == 1 ) {
tupleElements = arguments;
}
else {
tupleElements = new ArrayList<>( jdbcTypeCount );
}
modelPart.forEachSelectable(
(selectionIndex, selectionMapping) -> {
arguments.add(
tupleElements.add(
new ColumnReference(
tableGroup.getTableReference( navigablePath, selectionMapping.getContainingTableExpression() ),
tableGroup.getTableReference(
navigablePath,
selectionMapping.getContainingTableExpression()
),
selectionMapping,
creationContext.getSessionFactory()
)
);
}
);
if ( jdbcTypeCount != 1 ) {
arguments.add( new SqlTuple( tupleElements, modelPart ) );
}
final Expression expression = new SelfRenderingAggregateFunctionSqlAstExpression(
functionDescriptor.getName(),
functionDescriptor::render,
arguments,
(List<SqlAstNode>) (List<?>) arguments,
null,
(AllowableFunctionReturnType<?>) collectionPart.getJdbcMappings().get( 0 ),
collectionPart
(AllowableFunctionReturnType<?>) modelPart.getJdbcMappings().get( 0 ),
modelPart
);
subQuerySpec.getSelectClause().addSqlSelection( new SqlSelectionImpl( 1, 0, expression ) );

View File

@ -85,7 +85,7 @@ public class EntityValuedPathInterpretation<T> extends AbstractSqmPathInterpreta
final EntityIdentifierMapping identifierMapping = entityMappingType.getIdentifierMapping();
final EntityDiscriminatorMapping discriminatorMapping = entityMappingType.getDiscriminatorMapping();
final List<Expression> expressions = new ArrayList<>(
mapping.getJdbcTypeCount() + identifierMapping.getJdbcTypeCount()
entityMappingType.getJdbcTypeCount() + identifierMapping.getJdbcTypeCount()
+ ( discriminatorMapping == null ? 0 : 1 )
);
final SelectableConsumer selectableConsumer = (selectionIndex, selectableMapping) -> {
@ -111,8 +111,8 @@ public class EntityValuedPathInterpretation<T> extends AbstractSqmPathInterpreta
if ( discriminatorMapping != null ) {
discriminatorMapping.forEachSelectable( selectableConsumer );
}
mapping.forEachSelectable( selectableConsumer );
sqlExpression = new SqlTuple( expressions, mapping );
entityMappingType.forEachSelectable( selectableConsumer );
sqlExpression = new SqlTuple( expressions, entityMappingType );
}
else if ( mapping instanceof EntityAssociationMapping ) {
final EntityAssociationMapping associationMapping = (EntityAssociationMapping) mapping;

View File

@ -14,7 +14,6 @@ import org.hibernate.query.sqm.SqmPathSource;
* @author Steve Ebersole
*/
public abstract class AbstractSqmSimplePath<T> extends AbstractSqmPath<T> implements SqmSimplePath<T> {
private final NavigablePath navigablePath;
@SuppressWarnings("WeakerAccess")
public AbstractSqmSimplePath(
@ -32,17 +31,11 @@ public abstract class AbstractSqmSimplePath<T> extends AbstractSqmPath<T> implem
SqmPath lhs,
String explicitAlias,
NodeBuilder nodeBuilder) {
super( referencedPathSource, lhs, nodeBuilder );
this.navigablePath = navigablePath;
super( navigablePath, referencedPathSource, lhs, nodeBuilder );
setExplicitAlias( explicitAlias );
}
@Override
public NavigablePath getNavigablePath() {
return navigablePath;
}
@Override
public void appendHqlString(StringBuilder sb) {
if ( getLhs() != null ) {

View File

@ -16,7 +16,6 @@ import org.hibernate.query.PathException;
* @author Steve Ebersole
*/
public abstract class AbstractSqmSpecificPluralPartPath<T> extends AbstractSqmPath<T> implements SqmPath<T> {
private final NavigablePath navigablePath;
private final SqmPath pluralDomainPath;
private final PluralPersistentAttribute<?,?,T> pluralAttribute;
@ -33,7 +32,6 @@ public abstract class AbstractSqmSpecificPluralPartPath<T> extends AbstractSqmPa
pluralDomainPath,
pluralDomainPath.nodeBuilder()
);
this.navigablePath = navigablePath;
this.pluralDomainPath = pluralDomainPath;
this.pluralAttribute = referencedAttribute;
}
@ -48,11 +46,6 @@ public abstract class AbstractSqmSpecificPluralPartPath<T> extends AbstractSqmPa
return pluralAttribute;
}
@Override
public NavigablePath getNavigablePath() {
return navigablePath;
}
@Override
public SqmPath getLhs() {
return pluralDomainPath;

View File

@ -17,7 +17,12 @@ public class SqmCorrelatedRoot<T> extends SqmRoot<T> implements SqmPathWrapper<T
private final SqmRoot<T> correlationParent;
public SqmCorrelatedRoot(SqmRoot<T> correlationParent) {
super( correlationParent.getReferencedPathSource(), null, correlationParent.nodeBuilder() );
super(
correlationParent.getNavigablePath(),
correlationParent.getReferencedPathSource(),
null,
correlationParent.nodeBuilder()
);
this.correlationParent = correlationParent;
}

View File

@ -24,6 +24,7 @@ import java.util.function.Function;
import org.hibernate.LockMode;
import org.hibernate.QueryException;
import org.hibernate.dialect.RowLockStrategy;
import org.hibernate.dialect.SelectItemReferenceStrategy;
import org.hibernate.metamodel.mapping.EntityAssociationMapping;
import org.hibernate.metamodel.mapping.ModelPart;
import org.hibernate.persister.entity.EntityPersister;
@ -1480,7 +1481,7 @@ public abstract class AbstractSqlAstTranslator<T extends JdbcOperation> implemen
visitSelectClause( querySpec.getSelectClause() );
visitFromClause( querySpec.getFromClause() );
visitWhereClause( querySpec );
visitGroupByClause( querySpec, dialect.supportsSelectAliasInGroupByClause() );
visitGroupByClause( querySpec, dialect.getGroupBySelectItemReferenceStrategy() );
visitHavingClause( querySpec );
visitOrderBy( querySpec.getSortSpecifications() );
visitOffsetFetchClause( querySpec );
@ -1556,13 +1557,38 @@ public abstract class AbstractSqlAstTranslator<T extends JdbcOperation> implemen
return expression;
}
protected final void visitGroupByClause(QuerySpec querySpec, boolean supportsSelectAliases) {
protected Expression resolveExpressionToAlias(Expression expression) {
int index = -1;
if ( expression instanceof SqlSelectionExpression ) {
index = ( (SqlSelectionExpression) expression ).getSelection().getValuesArrayPosition();
}
else if ( expression instanceof SqmPathInterpretation<?> ) {
final Expression sqlExpression = ( (SqmPathInterpretation<?>) expression ).getSqlExpression();
if ( sqlExpression instanceof SqlSelectionExpression ) {
index = ( (SqlSelectionExpression) sqlExpression ).getSelection().getValuesArrayPosition();
}
}
if ( index == -1 ) {
return expression;
}
return new ColumnReference(
(String) null,
"c" + index,
false,
null,
null,
expression.getExpressionType().getJdbcMappings().get( 0 ),
sessionFactory
);
}
protected final void visitGroupByClause(QuerySpec querySpec, SelectItemReferenceStrategy referenceStrategy) {
final List<Expression> partitionExpressions = querySpec.getGroupByClauseExpressions();
if ( !partitionExpressions.isEmpty() ) {
try {
clauseStack.push( Clause.GROUP );
appendSql( " group by " );
visitPartitionExpressions( partitionExpressions, supportsSelectAliases );
visitPartitionExpressions( partitionExpressions, referenceStrategy );
}
finally {
clauseStack.pop();
@ -1575,7 +1601,7 @@ public abstract class AbstractSqlAstTranslator<T extends JdbcOperation> implemen
try {
clauseStack.push( Clause.PARTITION );
appendSql( "partition by " );
visitPartitionExpressions( partitionExpressions, false );
visitPartitionExpressions( partitionExpressions, SelectItemReferenceStrategy.EXPRESSION );
}
finally {
clauseStack.pop();
@ -1585,13 +1611,21 @@ public abstract class AbstractSqlAstTranslator<T extends JdbcOperation> implemen
protected final void visitPartitionExpressions(
List<Expression> partitionExpressions,
boolean supportsSelectAliases) {
if ( supportsSelectAliases ) {
visitPartitionExpressions( partitionExpressions, expression -> expression );
}
else {
visitPartitionExpressions( partitionExpressions, expression -> resolveAliasedExpression( expression ) );
SelectItemReferenceStrategy referenceStrategy) {
final Function<Expression, Expression> resolveAliasExpression;
switch ( referenceStrategy ) {
case POSITION:
resolveAliasExpression = Function.identity();
break;
case ALIAS:
resolveAliasExpression = this::resolveExpressionToAlias;
break;
case EXPRESSION:
default:
resolveAliasExpression = this::resolveAliasedExpression;
break;
}
visitPartitionExpressions( partitionExpressions, resolveAliasExpression );
}
protected final void visitPartitionExpressions(
@ -2862,7 +2896,7 @@ public abstract class AbstractSqlAstTranslator<T extends JdbcOperation> implemen
protected void visitSqlSelections(SelectClause selectClause) {
final List<SqlSelection> sqlSelections = selectClause.getSqlSelections();
final int size = sqlSelections.size();
if ( needsSelectAliases ) {
if ( needsSelectAliases || needsSelectAliasesForGroupOrOrderByClause() ) {
String separator = NO_SEPARATOR;
for ( int i = 0; i < size; i++ ) {
final SqlSelection sqlSelection = sqlSelections.get( i );
@ -2887,6 +2921,40 @@ public abstract class AbstractSqlAstTranslator<T extends JdbcOperation> implemen
}
}
private boolean needsSelectAliasesForGroupOrOrderByClause() {
if ( dialect.getGroupBySelectItemReferenceStrategy() == SelectItemReferenceStrategy.ALIAS ) {
final QuerySpec querySpec = (QuerySpec) getQueryPartStack().getCurrent();
for ( Expression groupByClauseExpression : querySpec.getGroupByClauseExpressions() ) {
if ( isSelectItemReference( groupByClauseExpression ) ) {
return true;
}
}
if ( querySpec.hasSortSpecifications() ) {
for ( SortSpecification sortSpecification : querySpec.getSortSpecifications() ) {
if ( isSelectItemReference( sortSpecification.getSortExpression() ) ) {
return true;
}
}
}
}
return false;
}
protected final boolean isSelectItemReference(Expression expression) {
final SqlTuple sqlTuple = SqlTupleContainer.getSqlTuple( expression );
if ( sqlTuple != null ) {
for ( Expression e : sqlTuple.getExpressions() ) {
if ( e instanceof SqlSelectionExpression ) {
return true;
}
}
}
else if ( expression instanceof SqlSelectionExpression ) {
return true;
}
return false;
}
protected void renderRowNumberingSelectItems(SelectClause selectClause, QueryPart queryPart) {
final FetchClauseType fetchClauseType = getFetchClauseTypeForRowNumbering( queryPart );
if ( fetchClauseType != null ) {
@ -4329,7 +4397,7 @@ public abstract class AbstractSqlAstTranslator<T extends JdbcOperation> implemen
.isEmpty() || subQuery.getHavingClauseRestrictions() != null ) {
// If we have a group by or having clause, we have to move the tuple comparison emulation to the HAVING clause
visitWhereClause( subQuery );
visitGroupByClause( subQuery, false );
visitGroupByClause( subQuery, SelectItemReferenceStrategy.EXPRESSION );
appendSql( " having " );
clauseStack.push( Clause.HAVING );
@ -4421,7 +4489,7 @@ public abstract class AbstractSqlAstTranslator<T extends JdbcOperation> implemen
visitSelectClause( subQuery.getSelectClause() );
visitFromClause( subQuery.getFromClause() );
visitWhereClause( subQuery );
visitGroupByClause( subQuery, dialect.supportsSelectAliasInGroupByClause() );
visitGroupByClause( subQuery, dialect.getGroupBySelectItemReferenceStrategy() );
visitHavingClause( subQuery );
appendSql( " order by " );

View File

@ -7,8 +7,7 @@
package org.hibernate.query;
import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase;
import org.hibernate.testing.DialectChecks;
import org.hibernate.testing.RequiresDialectFeature;
import org.hibernate.testing.TestForIssue;
import org.hibernate.testing.jdbc.SQLStatementInterceptor;
import org.junit.Test;
@ -30,7 +29,7 @@ import static org.junit.Assert.assertNotNull;
* @author Jan-Willem Gmelig Meyling
* @author Sayra Ranjha
*/
@RequiresDialectFeature(value = DialectChecks.SupportsSelectAliasInGroupByClause.class, jiraKey = "HHH-9301")
@TestForIssue( jiraKey = "HHH-9301" )
public class GroupByAliasTest extends BaseEntityManagerFunctionalTestCase {
public static final int MAX_COUNT = 15;

View File

@ -281,12 +281,6 @@ abstract public class DialectChecks {
}
}
public static class SupportsSelectAliasInGroupByClause implements DialectCheck {
public boolean isMatch(Dialect dialect) {
return dialect.supportsSelectAliasInGroupByClause();
}
}
public static class SupportsNClob implements DialectCheck {
@Override
public boolean isMatch(Dialect dialect) {