diff --git a/hibernate-core/src/main/antlr/org/hibernate/grammars/hql/HqlParser.g4 b/hibernate-core/src/main/antlr/org/hibernate/grammars/hql/HqlParser.g4 index 1795137007..164bf5bb19 100644 --- a/hibernate-core/src/main/antlr/org/hibernate/grammars/hql/HqlParser.g4 +++ b/hibernate-core/src/main/antlr/org/hibernate/grammars/hql/HqlParser.g4 @@ -152,6 +152,7 @@ queryExpression orderedQuery : query queryOrder? # QuerySpecExpression | LEFT_PAREN queryExpression RIGHT_PAREN queryOrder? # NestedQueryExpression + | queryOrder # QueryOrderExpression ; /** @@ -180,6 +181,7 @@ query // TODO: add with clause : selectClause fromClause? whereClause? (groupByClause havingClause?)? | fromClause whereClause? (groupByClause havingClause?)? selectClause? + | whereClause ; diff --git a/hibernate-core/src/main/java/org/hibernate/query/hql/internal/SemanticQueryBuilder.java b/hibernate-core/src/main/java/org/hibernate/query/hql/internal/SemanticQueryBuilder.java index a10d1fb07e..c0ff63280a 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/hql/internal/SemanticQueryBuilder.java +++ b/hibernate-core/src/main/java/org/hibernate/query/hql/internal/SemanticQueryBuilder.java @@ -213,9 +213,11 @@ import org.hibernate.type.descriptor.java.JavaType; import org.hibernate.type.descriptor.java.PrimitiveByteArrayJavaType; +import org.hibernate.type.descriptor.java.spi.JavaTypeRegistry; import org.hibernate.type.descriptor.java.spi.UnknownBasicJavaType; import org.hibernate.type.descriptor.jdbc.ObjectJdbcType; import org.hibernate.type.internal.BasicTypeImpl; +import org.hibernate.type.spi.TypeConfiguration; import org.jboss.logging.Logger; import jakarta.persistence.criteria.Predicate; @@ -345,20 +347,11 @@ public SemanticQueryBuilder( ? ParameterStyle.UNKNOWN : ParameterStyle.MIXED; - this.integerDomainType = creationContext - .getNodeBuilder() - .getTypeConfiguration() - .standardBasicTypeForJavaType( Integer.class ); - this.listJavaType = creationContext - .getNodeBuilder() - .getTypeConfiguration() - .getJavaTypeRegistry() - .resolveDescriptor( List.class ); - this.mapJavaType = creationContext - .getNodeBuilder() - .getTypeConfiguration() - .getJavaTypeRegistry() - .resolveDescriptor( Map.class ); + final TypeConfiguration typeConfiguration = creationContext.getNodeBuilder().getTypeConfiguration(); + final JavaTypeRegistry javaTypeRegistry = typeConfiguration.getJavaTypeRegistry(); + this.integerDomainType = typeConfiguration.standardBasicTypeForJavaType( Integer.class ); + this.listJavaType = javaTypeRegistry.resolveDescriptor( List.class ); + this.mapJavaType = javaTypeRegistry.resolveDescriptor( Map.class ); } @Override @@ -439,11 +432,10 @@ public SqmSelectStatement visitSelectStatement(HqlParser.SelectStatementConte @Override public SqmRoot visitTargetEntity(HqlParser.TargetEntityContext dmlTargetContext) { - final HqlParser.EntityNameContext entityNameContext = dmlTargetContext.entityName(); final HqlParser.VariableContext variable = dmlTargetContext.variable(); //noinspection unchecked return new SqmRoot<>( - (EntityDomainType) visitEntityName( entityNameContext ), + (EntityDomainType) visitEntityName( dmlTargetContext.entityName() ), variable == null ? null : applyJpaCompliance( visitVariable( variable ) ), false, creationContext.getNodeBuilder() @@ -908,31 +900,39 @@ private void applySearchClause(JpaCteCriteria cteDefinition, HqlParser.Search } @Override - public SqmQueryPart visitSimpleQueryGroup(HqlParser.SimpleQueryGroupContext ctx) { + public SqmQueryPart visitSimpleQueryGroup(HqlParser.SimpleQueryGroupContext ctx) { final int lastChild = ctx.getChildCount() - 1; if ( lastChild != 0 ) { ctx.getChild( 0 ).accept( this ); } - //noinspection unchecked - return (SqmQueryPart) ctx.getChild( lastChild ).accept( this ); + return (SqmQueryPart) ctx.getChild( lastChild ).accept( this ); + } + + @Override + public SqmQueryPart visitQueryOrderExpression(HqlParser.QueryOrderExpressionContext ctx) { + final SqmQuerySpec sqmQuerySpec = currentQuerySpec(); + final SqmFromClause fromClause = buildInferredFromClause(null); + sqmQuerySpec.setFromClause(fromClause); + sqmQuerySpec.setSelectClause( buildInferredSelectClause( fromClause ) ); + visitQueryOrder( sqmQuerySpec, ctx.queryOrder() ); + return sqmQuerySpec; } @Override public SqmQueryPart visitQuerySpecExpression(HqlParser.QuerySpecExpressionContext ctx) { - final List children = ctx.children; - final SqmQueryPart queryPart = visitQuery( (HqlParser.QueryContext) children.get( 0 ) ); - if ( children.size() > 1 ) { - visitQueryOrder( queryPart, (HqlParser.QueryOrderContext) children.get( 1 ) ); + final SqmQueryPart queryPart = visitQuery( ctx.query() ); + final HqlParser.QueryOrderContext queryOrderContext = ctx.queryOrder(); + if ( queryOrderContext != null ) { + visitQueryOrder( queryPart, queryOrderContext ); } return queryPart; } @Override - public SqmQueryPart visitNestedQueryExpression(HqlParser.NestedQueryExpressionContext ctx) { - final List children = ctx.children; - //noinspection unchecked - final SqmQueryPart queryPart = (SqmQueryPart) children.get( 1 ).accept( this ); - if ( children.size() > 3 ) { + public SqmQueryPart visitNestedQueryExpression(HqlParser.NestedQueryExpressionContext ctx) { + final SqmQueryPart queryPart = (SqmQueryPart) ctx.queryExpression().accept( this ); + final HqlParser.QueryOrderContext queryOrderContext = ctx.queryOrder(); + if ( queryOrderContext != null ) { final SqmCreationProcessingState firstProcessingState = processingStateStack.pop(); processingStateStack.push( new SqmQueryPartCreationProcessingStateStandardImpl( @@ -941,13 +941,13 @@ public SqmQueryPart visitNestedQueryExpression(HqlParser.NestedQueryExpr this ) ); - visitQueryOrder( queryPart, (HqlParser.QueryOrderContext) children.get( 3 ) ); + visitQueryOrder( queryPart, queryOrderContext); } return queryPart; } @Override - public SqmQueryGroup visitSetQueryGroup(HqlParser.SetQueryGroupContext ctx) { + public SqmQueryGroup visitSetQueryGroup(HqlParser.SetQueryGroupContext ctx) { final List children = ctx.children; final int firstIndex; if ( children.get( 0 ) instanceof HqlParser.WithClauseContext ) { @@ -962,11 +962,10 @@ public SqmQueryGroup visitSetQueryGroup(HqlParser.SetQueryGroupContext c StrictJpaComplianceViolation.Type.SET_OPERATIONS ); } - //noinspection unchecked - final SqmQueryPart firstQueryPart = (SqmQueryPart) children.get( firstIndex ).accept( this ); - SqmQueryGroup queryGroup; + final SqmQueryPart firstQueryPart = (SqmQueryPart) children.get( firstIndex ).accept( this ); + SqmQueryGroup queryGroup; if ( firstQueryPart instanceof SqmQueryGroup) { - queryGroup = (SqmQueryGroup) firstQueryPart; + queryGroup = (SqmQueryGroup) firstQueryPart; } else { queryGroup = new SqmQueryGroup<>( firstQueryPart ); @@ -975,67 +974,77 @@ public SqmQueryGroup visitSetQueryGroup(HqlParser.SetQueryGroupContext c final int size = children.size(); final SqmCreationProcessingState firstProcessingState = processingStateStack.pop(); for ( int i = firstIndex + 1; i < size; i += 2 ) { - final SetOperator operator = visitSetOperator( (HqlParser.SetOperatorContext) children.get( i ) ); + final SetOperator operator = visitSetOperator( (HqlParser.SetOperatorContext) children.get(i) ); final HqlParser.OrderedQueryContext simpleQueryCtx = (HqlParser.OrderedQueryContext) children.get( i + 1 ); - final List> queryParts; - processingStateStack.push( - new SqmQueryPartCreationProcessingStateStandardImpl( - processingStateStack.getCurrent(), - firstProcessingState.getProcessingQuery(), - this - ) - ); - if ( queryGroup.getSetOperator() == null || queryGroup.getSetOperator() == operator ) { - queryGroup.setSetOperator( operator ); - queryParts = queryGroup.queryParts(); - } - else { - queryParts = new ArrayList<>( size - ( i >> 1 ) ); - queryParts.add( queryGroup ); - queryGroup = new SqmQueryGroup<>( - creationContext.getNodeBuilder(), - operator, - queryParts - ); - setCurrentQueryPart( queryGroup ); - } - - final SqmQueryPart queryPart; - try { - final List subChildren = simpleQueryCtx.children; - if ( subChildren.get( 0 ) instanceof HqlParser.QueryContext ) { - final SqmQuerySpec querySpec = new SqmQuerySpec<>( creationContext.getNodeBuilder() ); - queryParts.add( querySpec ); - visitQuerySpecExpression( (HqlParser.QuerySpecExpressionContext) simpleQueryCtx ); - } - else { - try { - final SqmSelectStatement selectStatement = new SqmSelectStatement<>( creationContext.getNodeBuilder() ); - processingStateStack.push( - new SqmQueryPartCreationProcessingStateStandardImpl( - processingStateStack.getCurrent(), - selectStatement, - this - ) - ); - queryPart = visitNestedQueryExpression( (HqlParser.NestedQueryExpressionContext) simpleQueryCtx ); - queryParts.add( queryPart ); - } - finally { - processingStateStack.pop(); - } - } - } - finally { - processingStateStack.pop(); - } + queryGroup = getSqmQueryGroup( operator, simpleQueryCtx, queryGroup, size, firstProcessingState, i ); } processingStateStack.push( firstProcessingState ); return queryGroup; } + private SqmQueryGroup getSqmQueryGroup( + SetOperator operator, + HqlParser.OrderedQueryContext simpleQueryCtx, + SqmQueryGroup queryGroup, + int size, + SqmCreationProcessingState firstProcessingState, + int i) { + + final List> queryParts; + processingStateStack.push( + new SqmQueryPartCreationProcessingStateStandardImpl( + processingStateStack.getCurrent(), + firstProcessingState.getProcessingQuery(), + this + ) + ); + if ( queryGroup.getSetOperator() == null || queryGroup.getSetOperator() == operator ) { + queryGroup.setSetOperator( operator ); + queryParts = queryGroup.queryParts(); + } + else { + queryParts = new ArrayList<>( size - ( i >> 1 ) ); + queryParts.add( queryGroup ); + queryGroup = new SqmQueryGroup<>( creationContext.getNodeBuilder(), operator, queryParts ); + setCurrentQueryPart( queryGroup ); + } + + try { + final List subChildren = simpleQueryCtx.children; + if ( subChildren.get( 0 ) instanceof HqlParser.QueryContext ) { + final SqmQuerySpec querySpec = new SqmQuerySpec<>( creationContext.getNodeBuilder() ); + queryParts.add( querySpec ); + visitQuerySpecExpression( (HqlParser.QuerySpecExpressionContext) simpleQueryCtx ); + } + else { + try { + final SqmSelectStatement selectStatement = + new SqmSelectStatement<>( creationContext.getNodeBuilder() ); + processingStateStack.push( + new SqmQueryPartCreationProcessingStateStandardImpl( + processingStateStack.getCurrent(), + selectStatement, + this + ) + ); + @SuppressWarnings("unchecked") + final SqmQueryPart queryPart = (SqmQueryPart) + visitNestedQueryExpression( (HqlParser.NestedQueryExpressionContext) simpleQueryCtx ); + queryParts.add( queryPart ); + } + finally { + processingStateStack.pop(); + } + } + } + finally { + processingStateStack.pop(); + } + return queryGroup; + } + @Override public SetOperator visitSetOperator(HqlParser.SetOperatorContext ctx) { final Token token = ( (TerminalNode) ctx.getChild( 0 ) ).getSymbol(); @@ -1114,23 +1123,17 @@ else if ( fetchClauseContext == null ) { public SqmQuerySpec visitQuery(HqlParser.QueryContext ctx) { final SqmQuerySpec sqmQuerySpec = currentQuerySpec(); - // visit from-clause first!!! - visitFromClause( ctx.fromClause() ); + // visit from clause first!!! + final SqmFromClause fromClause = + ctx.fromClause() == null + ? buildInferredFromClause( ctx.selectClause() ) + : visitFromClause( ctx.fromClause() ); + sqmQuerySpec.setFromClause( fromClause ); - final SqmSelectClause selectClause; - if ( ctx.selectClause() == null ) { - if ( creationOptions.useStrictJpaCompliance() ) { - throw new StrictJpaComplianceViolation( - "Encountered implicit select-clause, but strict JPQL compliance was requested", - StrictJpaComplianceViolation.Type.IMPLICIT_SELECT - ); - } - log.debugf( "Encountered implicit select clause : %s", ctx.getText() ); - selectClause = buildInferredSelectClause( sqmQuerySpec.getFromClause() ); - } - else { - selectClause = visitSelectClause( ctx.selectClause() ); - } + final SqmSelectClause selectClause = + ctx.selectClause() == null + ? buildInferredSelectClause( fromClause ) + : visitSelectClause( ctx.selectClause() ); sqmQuerySpec.setSelectClause( selectClause ); final SqmWhereClause whereClause = new SqmWhereClause( creationContext.getNodeBuilder() ); @@ -1149,10 +1152,47 @@ public SqmQuerySpec visitQuery(HqlParser.QueryContext ctx) { return sqmQuerySpec; } + private SqmFromClause buildInferredFromClause(HqlParser.SelectClauseContext selectClauseContext) { + if ( selectClauseContext != null ) { + // when there's an explicit 'select', we never infer the 'from' + return new SqmFromClause(); + } + else { + if ( creationOptions.useStrictJpaCompliance() ) { + throw new StrictJpaComplianceViolation( + "Encountered implicit 'from' clause, but strict JPQL compliance was requested", + StrictJpaComplianceViolation.Type.IMPLICIT_FROM + ); + } + + final SqmFromClause fromClause = new SqmFromClause(); + if ( expectedResultType != null && processingStateStack.depth() <= 1 ) { + final EntityDomainType entityDescriptor = + creationContext.getJpaMetamodel().findEntityType( expectedResultType ); + if ( entityDescriptor == null ) { + throw new SemanticException( "Query has no 'from' clause, and the result type '" + + expectedResultType.getName() + "' is not an entity type" ); + } + final SqmRoot sqmRoot = + new SqmRoot<>( entityDescriptor, null, false, creationContext.getNodeBuilder() ); + processingStateStack.getCurrent().getPathRegistry().register( sqmRoot ); + fromClause.addRoot( sqmRoot ); + } + return fromClause; + } + } + protected SqmSelectClause buildInferredSelectClause(SqmFromClause fromClause) { + if ( creationOptions.useStrictJpaCompliance() ) { + throw new StrictJpaComplianceViolation( + "Encountered implicit 'select' clause, but strict JPQL compliance was requested", + StrictJpaComplianceViolation.Type.IMPLICIT_SELECT + ); + } + if ( fromClause.getNumberOfRoots() == 0 ) { - // should be impossible to get here - throw new ParsingException( "query has no 'select' clause, and no root entities"); + throw new SemanticException( "query has no 'select' clause, and no root entities" + + " (every selection query must have an explicit 'select', an explicit 'from', or an explicit entity result type)"); } final NodeBuilder nodeBuilder = creationContext.getNodeBuilder(); @@ -1219,40 +1259,19 @@ private void applyJoinsToInferredSelectClause(SqmFrom sqm, SqmSelectClause @Override public SqmSelectClause visitSelectClause(HqlParser.SelectClauseContext ctx) { // todo (6.0) : primer a select-clause-specific SemanticPathPart into the stack - final int selectionListIndex; - if ( ctx.getChild( 1 ) instanceof HqlParser.SelectionListContext ) { - selectionListIndex = 1; - } - else { - selectionListIndex = 2; - } - - final SqmSelectClause selectClause = new SqmSelectClause( - selectionListIndex == 2, - creationContext.getNodeBuilder() - ); - final HqlParser.SelectionListContext selectionListContext = (HqlParser.SelectionListContext) ctx.getChild( - selectionListIndex - ); - for ( ParseTree subCtx : selectionListContext.children ) { - if ( subCtx instanceof HqlParser.SelectionContext ) { - selectClause.addSelection( visitSelection( (HqlParser.SelectionContext) subCtx ) ); - } + final SqmSelectClause selectClause = + new SqmSelectClause(ctx.DISTINCT() != null, creationContext.getNodeBuilder() ); + final HqlParser.SelectionListContext selectionListContext = ctx.selectionList(); + for ( HqlParser.SelectionContext selectionContext : selectionListContext.selection() ) { + selectClause.addSelection( visitSelection( selectionContext ) ); } return selectClause; } @Override public SqmSelection visitSelection(HqlParser.SelectionContext ctx) { - final String resultIdentifier; - if ( ctx.getChildCount() == 1 ) { - resultIdentifier = null; - } - else { - resultIdentifier = applyJpaCompliance( - visitVariable( (HqlParser.VariableContext) ctx.getChild( 1 ) ) - ); - } + final String resultIdentifier = ctx.variable() == null ? null + : applyJpaCompliance( visitVariable( ctx.variable() ) ); final SqmSelectableNode selectableNode = visitSelectableNode( ctx ); final SqmSelection selection = new SqmSelection<>( @@ -1266,7 +1285,7 @@ public SqmSelection visitSelection(HqlParser.SelectionContext ctx) { // if the node is not a dynamic-instantiation, register it with // the path-registry if ( !(selectableNode instanceof SqmDynamicInstantiation) ) { - getCurrentProcessingState().getPathRegistry().register( selection ); + processingStateStack.getCurrent().getPathRegistry().register( selection ); } return selection; @@ -1378,7 +1397,7 @@ public SqmDynamicInstantiationArgument visitInstantiationArgument(HqlParser.I ); if ( !(argExpression instanceof SqmDynamicInstantiation) ) { - getCurrentProcessingState().getPathRegistry().register( argument ); + processingStateStack.getCurrent().getPathRegistry().register( argument ); } return argument; @@ -1416,7 +1435,8 @@ private SqmExpression resolveOrderByOrGroupByExpression( ParseTree child, boolean definedCollate, boolean allowPositionalOrAliases) { - final SqmCreationProcessingState processingState = getCurrentProcessingState(); + final NodeBuilder nodeBuilder = creationContext.getNodeBuilder(); + final SqmCreationProcessingState processingState = processingStateStack.getCurrent(); final SqmQuery processingQuery = processingState.getProcessingQuery(); final SqmQueryPart queryPart; if ( processingQuery instanceof SqmInsertSelectStatement ) { @@ -1453,7 +1473,7 @@ else if ( !allowPositionalOrAliases ) { throw new SemanticException( "Numeric literal '" + position + "' used in 'group by' does not match a registered select item" ); } - return new SqmAliasedNodeRef( position, integerDomainType, creationContext.getNodeBuilder() ); + return new SqmAliasedNodeRef( position, integerDomainType, nodeBuilder); } else if ( child instanceof HqlParser.IdentifierContext ) { final String identifierText = visitIdentifier( (HqlParser.IdentifierContext) child ); @@ -1468,8 +1488,8 @@ else if ( child instanceof HqlParser.IdentifierContext ) { if ( identifierText.equals( sqmSelection.getAlias() ) ) { return new SqmAliasedNodeRef( i + 1, - creationContext.getNodeBuilder().getIntegerType(), - creationContext.getNodeBuilder() + nodeBuilder.getIntegerType(), + nodeBuilder ); } final SqmSelectableNode selectableNode = sqmSelection.getSelectableNode(); @@ -1479,7 +1499,7 @@ else if ( child instanceof HqlParser.IdentifierContext ) { if ( pathSource.findSubPathSource( identifierText ) != null ) { if ( sqmPosition != 0 ) { throw new IllegalStateException( - "Multiple from-elements expose unqualified attribute : " + identifierText ); + "Multiple from elements expose unqualified attribute: " + identifierText ); } found = fromElement; sqmPosition = i + 1; @@ -1490,7 +1510,7 @@ else if ( selectableNode instanceof SqmPath ) { if ( identifierText.equals( path.getReferencedPathSource().getPathName() ) ) { if ( sqmPosition != 0 ) { throw new IllegalStateException( - "Multiple from-elements expose unqualified attribute : " + identifierText ); + "Multiple from elements expose unqualified attribute: " + identifierText ); } sqmPosition = i + 1; } @@ -1500,38 +1520,31 @@ else if ( selectableNode instanceof SqmPath ) { return new SqmAliasedNodeRef( sqmPosition, found.get( identifierText ).getNavigablePath(), - creationContext.getNodeBuilder().getIntegerType(), - creationContext.getNodeBuilder() + nodeBuilder.getIntegerType(), + nodeBuilder ); } else if ( sqmPosition != 0 ) { - return new SqmAliasedNodeRef( - sqmPosition, - creationContext.getNodeBuilder().getIntegerType(), - creationContext.getNodeBuilder() - ); + return new SqmAliasedNodeRef( sqmPosition, nodeBuilder.getIntegerType(), nodeBuilder ); } } else { - final Integer correspondingPosition = allowPositionalOrAliases ? - processingState.getPathRegistry().findAliasedNodePosition( identifierText ) : - null; + final Integer correspondingPosition = + allowPositionalOrAliases + ? processingState.getPathRegistry() + .findAliasedNodePosition( identifierText ) + : null; if ( correspondingPosition != null ) { if ( definedCollate ) { // This is syntactically disallowed throw new SyntaxException( "'collate' is not allowed for alias-based 'order by' or 'group by' items" ); } - return new SqmAliasedNodeRef( - correspondingPosition, - integerDomainType, - creationContext.getNodeBuilder() - ); + return new SqmAliasedNodeRef( correspondingPosition, integerDomainType, nodeBuilder ); } - final SqmFrom sqmFrom = processingState.getPathRegistry().findFromByAlias( - identifierText, - true - ); + final SqmFrom sqmFrom = + processingState.getPathRegistry() + .findFromByAlias( identifierText, true ); if ( sqmFrom != null ) { if ( definedCollate ) { // This is syntactically disallowed @@ -1634,7 +1647,7 @@ public SqmExpression visitSortExpression(HqlParser.SortExpressionContext ctx, } private SqmQuerySpec currentQuerySpec() { - SqmQuery processingQuery = processingStateStack.getCurrent().getProcessingQuery(); + final SqmQuery processingQuery = processingStateStack.getCurrent().getProcessingQuery(); if ( processingQuery instanceof SqmInsertSelectStatement ) { return ( (SqmInsertSelectStatement) processingQuery ).getSelectQueryPart().getLastQuerySpec(); } @@ -1729,7 +1742,9 @@ public Object visitGeneralPathExpression(HqlParser.GeneralPathExpressionContext if ( part instanceof DomainPathPart ) { return ( (DomainPathPart) part ).getSqmExpression(); } - return part; + else { + return part; + } } @Override @@ -1791,19 +1806,18 @@ public String getEntityName(HqlParser.EntityNameContext parserEntityName) { @Override public String visitIdentifier(HqlParser.IdentifierContext ctx) { final ParseTree child = ctx.getChild( 0 ); - if ( child instanceof TerminalNode ) { - return child.getText(); - } - return visitNakedIdentifier( (HqlParser.NakedIdentifierContext) child ); + return child instanceof TerminalNode + ? child.getText() + : visitNakedIdentifier( (HqlParser.NakedIdentifierContext) child ); } @Override public String visitNakedIdentifier(HqlParser.NakedIdentifierContext ctx) { final TerminalNode node = (TerminalNode) ctx.getChild( 0 ); - if ( node.getSymbol().getType() == HqlParser.QUOTED_IDENTIFIER ) { - return QuotingHelper.unquoteIdentifier( node.getText() ); - } - return node.getText(); + final String text = node.getText(); + return node.getSymbol().getType() == HqlParser.QUOTED_IDENTIFIER + ? QuotingHelper.unquoteIdentifier( text ) + : text; } @Override @@ -1816,7 +1830,8 @@ public EntityDomainType visitEntityName(HqlParser.EntityNameContext parserEnt throw new UnknownEntityException( "Could not resolve target entity '" + entityName + "'", entityName ); } checkFQNEntityNameJpaComplianceViolationIfNeeded( entityName, entityReference ); - if ( entityReference instanceof SqmPolymorphicRootDescriptor && getCreationOptions().useStrictJpaCompliance() ) { + if ( entityReference instanceof SqmPolymorphicRootDescriptor + && getCreationOptions().useStrictJpaCompliance() ) { throw new StrictJpaComplianceViolation( "Encountered the use of a non entity name [" + entityName + "], " + "but strict JPQL compliance was requested which doesn't allow this", @@ -1828,22 +1843,14 @@ public EntityDomainType visitEntityName(HqlParser.EntityNameContext parserEnt @Override public SqmFromClause visitFromClause(HqlParser.FromClauseContext parserFromClause) { - final SqmFromClause fromClause; - if ( parserFromClause == null ) { - fromClause = new SqmFromClause(); - currentQuerySpec().setFromClause( fromClause ); - } - else { - final int size = parserFromClause.getChildCount(); - // Shift 1 bit instead of division by 2 - final int estimatedSize = size >> 1; - fromClause = new SqmFromClause( estimatedSize ); - currentQuerySpec().setFromClause( fromClause ); - for ( int i = 0; i < size; i++ ) { - final ParseTree parseTree = parserFromClause.getChild( i ); - if ( parseTree instanceof HqlParser.EntityWithJoinsContext ) { - visitEntityWithJoins( (HqlParser.EntityWithJoinsContext) parseTree ); - } + final List joins = parserFromClause.entityWithJoins(); + final SqmFromClause fromClause = new SqmFromClause( joins.size() ); + currentQuerySpec().setFromClause( fromClause ); //have to do this here because visitEntityWithJoins() needs it + for ( HqlParser.EntityWithJoinsContext join : joins ) { + final SqmRoot sqmRoot = visitEntityWithJoins( join ); + // Correlations are implicitly added to the from clause + if ( !( sqmRoot instanceof SqmCorrelation ) ) { + fromClause.addRoot( sqmRoot ); } } return fromClause; @@ -1851,12 +1858,7 @@ public SqmFromClause visitFromClause(HqlParser.FromClauseContext parserFromClaus @Override public SqmRoot visitEntityWithJoins(HqlParser.EntityWithJoinsContext parserSpace) { - final SqmRoot sqmRoot = (SqmRoot) parserSpace.getChild( 0 ).accept( this ); - final SqmFromClause fromClause = currentQuerySpec().getFromClause(); - // Correlations are implicitly added to the from clause - if ( !( sqmRoot instanceof SqmCorrelation ) ) { - fromClause.addRoot( sqmRoot ); - } + final SqmRoot sqmRoot = (SqmRoot) parserSpace.fromRoot().accept( this ); final int size = parserSpace.getChildCount(); for ( int i = 1; i < size; i++ ) { final ParseTree parseTree = parserSpace.getChild( i ); @@ -1875,69 +1877,18 @@ else if ( parseTree instanceof HqlParser.JpaCollectionJoinContext ) { } @Override - @SuppressWarnings( { "rawtypes", "unchecked" } ) public SqmRoot visitRootEntity(HqlParser.RootEntityContext ctx) { - final HqlParser.EntityNameContext entityNameContext = (HqlParser.EntityNameContext) ctx.getChild( 0 ); - final List entityNameParseTreeChildren = entityNameContext.children; + final HqlParser.EntityNameContext entityNameContext = ctx.entityName(); final String name = getEntityName( entityNameContext ); - log.debugf( "Handling root path - %s", name ); - final EntityDomainType entityDescriptor = getCreationContext() - .getJpaMetamodel() - .getHqlEntityReference( name ); + final EntityDomainType entityDescriptor = creationContext.getJpaMetamodel().getHqlEntityReference( name ); - final HqlParser.VariableContext identificationVariableDefContext; - if ( ctx.getChildCount() > 1 ) { - identificationVariableDefContext = (HqlParser.VariableContext) ctx.getChild( 1 ); - } - else { - identificationVariableDefContext = null; - } - final String alias = applyJpaCompliance( - visitVariable( identificationVariableDefContext ) - ); + final String alias = applyJpaCompliance( visitVariable( ctx.variable() ) ); final SqmCreationProcessingState processingState = processingStateStack.getCurrent(); final SqmPathRegistry pathRegistry = processingState.getPathRegistry(); if ( entityDescriptor == null ) { - final int size = entityNameParseTreeChildren.size(); - // Handle the use of a correlation path in subqueries - if ( processingStateStack.depth() > 1 && size > 2 ) { - final String parentAlias = entityNameParseTreeChildren.get( 0 ).getText(); - final AbstractSqmFrom correlation = processingState.getPathRegistry() - .findFromByAlias( parentAlias, true ); - if ( correlation instanceof SqmCorrelation ) { - final DotIdentifierConsumer dotIdentifierConsumer = new QualifiedJoinPathConsumer( - correlation, - SqmJoinType.INNER, - false, - alias, - this - ); - final int lastIdx = size - 1; - for ( int i = 2; i != lastIdx; i += 2 ) { - dotIdentifierConsumer.consumeIdentifier( - entityNameParseTreeChildren.get( i ).getText(), - false, - false - ); - } - dotIdentifierConsumer.consumeIdentifier( - entityNameParseTreeChildren.get( lastIdx ).getText(), - false, - true - ); - return ( (SqmCorrelation) correlation ).getCorrelatedRoot(); - } - throw new SemanticException( "Could not resolve entity or correlation path '" + name + "'" ); - } - final SqmCteStatement cteStatement = findCteStatement( name ); - if ( cteStatement != null ) { - final SqmCteRoot root = new SqmCteRoot<>( cteStatement, alias ); - pathRegistry.register( root ); - return root; - } - throw new UnknownEntityException( "Could not resolve root entity '" + name + "'", name ); + return resolveRootEntity( entityNameContext, name, alias, processingState, pathRegistry ); } checkFQNEntityNameJpaComplianceViolationIfNeeded( name, entityDescriptor ); @@ -1957,13 +1908,58 @@ public SqmRoot visitRootEntity(HqlParser.RootEntityContext ctx) { } } - final SqmRoot sqmRoot = new SqmRoot<>( entityDescriptor, alias, true, creationContext.getNodeBuilder() ); - + final SqmRoot sqmRoot = + new SqmRoot<>( entityDescriptor, alias, true, creationContext.getNodeBuilder() ); pathRegistry.register( sqmRoot ); - return sqmRoot; } + private SqmRoot resolveRootEntity( + HqlParser.EntityNameContext entityNameContext, + String name, String alias, + SqmCreationProcessingState processingState, + SqmPathRegistry pathRegistry) { + final List entityNameParseTreeChildren = entityNameContext.children; + final int size = entityNameParseTreeChildren.size(); + // Handle the use of a correlation path in subqueries + if ( processingStateStack.depth() > 1 && size > 2 ) { + final String parentAlias = entityNameParseTreeChildren.get( 0 ).getText(); + final AbstractSqmFrom correlation = + processingState.getPathRegistry().findFromByAlias( parentAlias, true ); + if ( correlation instanceof SqmCorrelation ) { + final DotIdentifierConsumer dotIdentifierConsumer = new QualifiedJoinPathConsumer( + correlation, + SqmJoinType.INNER, + false, + alias, + this + ); + final int lastIdx = size - 1; + for ( int i = 2; i != lastIdx; i += 2 ) { + dotIdentifierConsumer.consumeIdentifier( + entityNameParseTreeChildren.get( i ).getText(), + false, + false + ); + } + dotIdentifierConsumer.consumeIdentifier( + entityNameParseTreeChildren.get( lastIdx ).getText(), + false, + true + ); + return ((SqmCorrelation) correlation).getCorrelatedRoot(); + } + throw new SemanticException( "Could not resolve entity or correlation path '" + name + "'" ); + } + final SqmCteStatement cteStatement = findCteStatement( name ); + if ( cteStatement != null ) { + final SqmCteRoot root = new SqmCteRoot<>( cteStatement, alias); + pathRegistry.register( root ); + return root; + } + throw new UnknownEntityException( "Could not resolve root entity '" + name + "'", name); + } + @Override public SqmCteStatement findCteStatement(String name) { if ( currentPotentialRecursiveCte != null && name.equals( currentPotentialRecursiveCte.getName() ) ) { @@ -1990,26 +1986,10 @@ public SqmRoot visitRootSubquery(HqlParser.RootSubqueryContext ctx) { ); } - final SqmSubQuery subQuery = (SqmSubQuery) ctx.getChild(1).accept( this ); - - final ParseTree lastChild = ctx.getChild( ctx.getChildCount() - 1 ); - final HqlParser.VariableContext identificationVariableDefContext; - if ( lastChild instanceof HqlParser.VariableContext ) { - identificationVariableDefContext = (HqlParser.VariableContext) lastChild; - } - else { - identificationVariableDefContext = null; - } - final String alias = applyJpaCompliance( - visitVariable( identificationVariableDefContext ) - ); - - final SqmCreationProcessingState processingState = processingStateStack.getCurrent(); - final SqmPathRegistry pathRegistry = processingState.getPathRegistry(); + final SqmSubQuery subQuery = (SqmSubQuery) ctx.subquery().accept( this ); + final String alias = applyJpaCompliance( visitVariable( ctx.variable() ) ); final SqmRoot sqmRoot = new SqmDerivedRoot<>( subQuery, alias ); - - pathRegistry.register( sqmRoot ); - + processingStateStack.getCurrent().getPathRegistry().register( sqmRoot ); return sqmRoot; } @@ -2079,8 +2059,7 @@ public final SqmCrossJoin visitCrossJoin(HqlParser.CrossJoinContext ctx) { } private void consumeCrossJoin(HqlParser.CrossJoinContext parserJoin, SqmRoot sqmRoot) { - final HqlParser.EntityNameContext entityNameContext = (HqlParser.EntityNameContext) parserJoin.getChild( 2 ); - final String name = getEntityName( entityNameContext ); + final String name = getEntityName( parserJoin.entityName() ); SqmTreeCreationLogger.LOGGER.debugf( "Handling root path - %s", name ); @@ -2090,19 +2069,9 @@ private void consumeCrossJoin(HqlParser.CrossJoinContext parserJoin, SqmRoot if ( entityDescriptor instanceof SqmPolymorphicRootDescriptor ) { throw new SemanticException( "Unmapped polymorphic reference cannot be used as a target of 'cross join'" ); } - final HqlParser.VariableContext identificationVariableDefContext; - if ( parserJoin.getChildCount() > 3 ) { - identificationVariableDefContext = (HqlParser.VariableContext) parserJoin.getChild( 3 ); - } - else { - identificationVariableDefContext = null; - } - final SqmCrossJoin join = new SqmCrossJoin<>( - entityDescriptor, - visitVariable( identificationVariableDefContext ), - sqmRoot - ); - + final SqmCrossJoin join = + new SqmCrossJoin<>( entityDescriptor, visitVariable( parserJoin.variable() ), sqmRoot ); + processingStateStack.getCurrent().getPathRegistry().register( join ); // CROSS joins are always added to the root @@ -2115,110 +2084,41 @@ private void consumeCrossJoin(HqlParser.CrossJoinContext parserJoin, SqmRoot } protected void consumeJoin(HqlParser.JoinContext parserJoin, SqmRoot sqmRoot) { - final SqmJoinType joinType; - final int firstJoinTypeSymbolType; - if ( parserJoin.getChild( 0 ) instanceof HqlParser.JoinTypeContext - && parserJoin.getChild( 0 ).getChildCount() != 0 ) { - firstJoinTypeSymbolType = ( (TerminalNode) parserJoin.getChild( 0 ).getChild( 0 ) ).getSymbol().getType(); - } - else { - firstJoinTypeSymbolType = HqlParser.INNER; - } - switch ( firstJoinTypeSymbolType ) { - case HqlParser.FULL: - joinType = SqmJoinType.FULL; - break; - case HqlParser.RIGHT: - joinType = SqmJoinType.RIGHT; - break; - // For some reason, we also support `outer join` syntax.. - case HqlParser.OUTER: - case HqlParser.LEFT: - joinType = SqmJoinType.LEFT; - break; - default: - joinType = SqmJoinType.INNER; - break; - } - + final SqmJoinType joinType = getSqmJoinType( parserJoin.joinType() ); final HqlParser.JoinTargetContext qualifiedJoinTargetContext = parserJoin.joinTarget(); - final ParseTree lastChild = qualifiedJoinTargetContext.getChild( qualifiedJoinTargetContext.getChildCount() - 1 ); - final HqlParser.VariableContext identificationVariableDefContext; - if ( lastChild instanceof HqlParser.VariableContext ) { - identificationVariableDefContext = (HqlParser.VariableContext) lastChild; - } - else { - identificationVariableDefContext = null; - } - final String alias = visitVariable( identificationVariableDefContext ); - final boolean fetch = parserJoin.getChild( 2 ) instanceof TerminalNode; + final String alias = visitVariable( getVariable( qualifiedJoinTargetContext ) ); + final boolean fetch = parserJoin.FETCH() != null; if ( fetch && processingStateStack.depth() > 1 ) { throw new SemanticException( "The 'from' clause of a subquery has a 'fetch'" ); } - dotIdentifierConsumerStack.push( - new QualifiedJoinPathConsumer( - sqmRoot, - joinType, - fetch, - alias, - this - ) - ); + dotIdentifierConsumerStack.push( new QualifiedJoinPathConsumer( sqmRoot, joinType, fetch, alias, this ) ); try { - final SqmQualifiedJoin join; - if ( qualifiedJoinTargetContext instanceof HqlParser.JoinPathContext ) { - //noinspection unchecked - join = (SqmQualifiedJoin) qualifiedJoinTargetContext.getChild( 0 ).accept( this ); - } - else { - if ( fetch ) { - throw new SemanticException( "The 'from' clause of a subquery has a 'fetch' join" ); - } - if ( getCreationOptions().useStrictJpaCompliance() ) { - throw new StrictJpaComplianceViolation( - "The JPA specification does not support subqueries in the from clause. " + - "Please disable the JPA query compliance if you want to use this feature.", - StrictJpaComplianceViolation.Type.FROM_SUBQUERY - ); - } - final TerminalNode terminalNode = (TerminalNode) qualifiedJoinTargetContext.getChild( 0 ); - final boolean lateral = terminalNode.getSymbol().getType() == HqlParser.LATERAL; - final int subqueryIndex = lateral ? 2 : 1; - final DotIdentifierConsumer identifierConsumer = dotIdentifierConsumerStack.pop(); - final SqmSubQuery subQuery = (SqmSubQuery) qualifiedJoinTargetContext.getChild( subqueryIndex ).accept( this ); - dotIdentifierConsumerStack.push( identifierConsumer ); - //noinspection unchecked,rawtypes - join = new SqmDerivedJoin( subQuery, alias, joinType, lateral, sqmRoot ); - processingStateStack.getCurrent().getPathRegistry().register( join ); - } - - final HqlParser.JoinRestrictionContext qualifiedJoinRestrictionContext = parserJoin.joinRestriction(); + final SqmQualifiedJoin join = getJoin( sqmRoot, joinType, qualifiedJoinTargetContext, alias, fetch ); + final HqlParser.JoinRestrictionContext joinRestrictionContext = parserJoin.joinRestriction(); if ( join instanceof SqmEntityJoin || join instanceof SqmDerivedJoin || join instanceof SqmCteJoin ) { sqmRoot.addSqmJoin( join ); } else if ( join instanceof SqmAttributeJoin ) { final SqmAttributeJoin attributeJoin = (SqmAttributeJoin) join; if ( getCreationOptions().useStrictJpaCompliance() ) { - if ( join.getExplicitAlias() != null ) { - if ( attributeJoin.isFetched() ) { - throw new StrictJpaComplianceViolation( - "Encountered aliased fetch join, but strict JPQL compliance was requested", - StrictJpaComplianceViolation.Type.ALIASED_FETCH_JOIN - ); - } + if ( join.getExplicitAlias() != null && attributeJoin.isFetched() ) { + throw new StrictJpaComplianceViolation( + "Encountered aliased fetch join, but strict JPQL compliance was requested", + StrictJpaComplianceViolation.Type.ALIASED_FETCH_JOIN + ); } } - if ( qualifiedJoinRestrictionContext != null && attributeJoin.isFetched() ) { + if ( joinRestrictionContext != null && attributeJoin.isFetched() ) { throw new SemanticException( "Fetch join has a 'with' clause (use a filter instead)" ); } } - if ( qualifiedJoinRestrictionContext != null ) { + if ( joinRestrictionContext != null ) { dotIdentifierConsumerStack.push( new QualifiedJoinPredicatePathConsumer( join, this ) ); try { - join.setJoinPredicate( (SqmPredicate) qualifiedJoinRestrictionContext.getChild( 1 ).accept( this ) ); + join.setJoinPredicate( (SqmPredicate) joinRestrictionContext.getChild( 1 ).accept( this ) ); } finally { dotIdentifierConsumerStack.pop(); @@ -2230,35 +2130,93 @@ else if ( join instanceof SqmAttributeJoin ) { } } + private static HqlParser.VariableContext getVariable(HqlParser.JoinTargetContext joinTargetContext) { + if ( joinTargetContext instanceof HqlParser.JoinPathContext ) { + return ((HqlParser.JoinPathContext) joinTargetContext).variable(); + } + else if ( joinTargetContext instanceof HqlParser.JoinSubqueryContext ) { + return ((HqlParser.JoinSubqueryContext) joinTargetContext).variable(); + } + else { + throw new ParsingException( "unexpected join type" ); + } + } + + @SuppressWarnings("unchecked") + private SqmQualifiedJoin getJoin( + SqmRoot sqmRoot, + SqmJoinType joinType, + HqlParser.JoinTargetContext joinTargetContext, + String alias, + boolean fetch) { + if ( joinTargetContext instanceof HqlParser.JoinPathContext ) { + final HqlParser.JoinPathContext joinPathContext = (HqlParser.JoinPathContext) joinTargetContext; + return (SqmQualifiedJoin) joinPathContext.path().accept( this ); + } + else if ( joinTargetContext instanceof HqlParser.JoinSubqueryContext ) { + if ( fetch ) { + throw new SemanticException( "The 'from' clause of a subquery has a 'fetch' join" ); + } + if ( getCreationOptions().useStrictJpaCompliance() ) { + throw new StrictJpaComplianceViolation( + "The JPA specification does not support subqueries in the from clause. " + + "Please disable the JPA query compliance if you want to use this feature.", + StrictJpaComplianceViolation.Type.FROM_SUBQUERY + ); + } + + final HqlParser.JoinSubqueryContext joinSubqueryContext = (HqlParser.JoinSubqueryContext) joinTargetContext; + final boolean lateral = joinSubqueryContext.LATERAL() != null; + final DotIdentifierConsumer identifierConsumer = dotIdentifierConsumerStack.pop(); + final SqmSubQuery subQuery = (SqmSubQuery) joinSubqueryContext.subquery().accept( this ); + dotIdentifierConsumerStack.push( identifierConsumer ); + final SqmQualifiedJoin join = new SqmDerivedJoin<>( subQuery, alias, joinType, lateral, sqmRoot ); + processingStateStack.getCurrent().getPathRegistry().register( join ); + return join; + } + else { + throw new ParsingException( "unexpected join type" ); + } + } + + private static SqmJoinType getSqmJoinType(HqlParser.JoinTypeContext joinTypeContext) { + if ( joinTypeContext == null || joinTypeContext.getChildCount() == 0 ) { + return SqmJoinType.INNER; + } + else { + final TerminalNode firstChild = (TerminalNode) joinTypeContext.getChild(0); + switch ( firstChild.getSymbol().getType() ) { + case HqlParser.FULL: + return SqmJoinType.FULL; + case HqlParser.RIGHT: + return SqmJoinType.RIGHT; + // For some reason, we also support `outer join` syntax.. + case HqlParser.OUTER: + case HqlParser.LEFT: + return SqmJoinType.LEFT; + default: + return SqmJoinType.INNER; + } + } + } + @Override public SqmJoin visitJpaCollectionJoin(HqlParser.JpaCollectionJoinContext ctx) { throw new UnsupportedOperationException(); } - protected void consumeJpaCollectionJoin( - HqlParser.JpaCollectionJoinContext ctx, - SqmRoot sqmRoot) { - final HqlParser.VariableContext identificationVariableDefContext; - if ( ctx.getChildCount() > 5 ) { - identificationVariableDefContext = (HqlParser.VariableContext) ctx.getChild( 5 ); - } - else { - identificationVariableDefContext = null; - } - final String alias = visitVariable( identificationVariableDefContext ); + /** + * Deprecated syntax dating back to EJB-QL prior to EJB 3, required by JPA, never documented in Hibernate + */ + protected void consumeJpaCollectionJoin(HqlParser.JpaCollectionJoinContext ctx, SqmRoot sqmRoot) { + final String alias = visitVariable( ctx.variable() ); dotIdentifierConsumerStack.push( - new QualifiedJoinPathConsumer( - sqmRoot, - // According to JPA spec 4.4.6 this is an inner join - SqmJoinType.INNER, - false, - alias, - this - ) + // According to JPA spec 4.4.6 this is an inner join + new QualifiedJoinPathConsumer( sqmRoot, SqmJoinType.INNER, false, alias, this ) ); try { - consumePluralAttributeReference( (HqlParser.PathContext) ctx.getChild( 3 ) ); + consumePluralAttributeReference( ctx.path() ); } finally { dotIdentifierConsumerStack.pop(); @@ -2270,11 +2228,12 @@ protected void consumeJpaCollectionJoin( @Override public SqmPredicate visitWhereClause(HqlParser.WhereClauseContext ctx) { - if ( ctx == null || ctx.getChildCount() != 2 ) { + if ( ctx == null || ctx.predicate() == null ) { return null; } - - return (SqmPredicate) ctx.getChild( 1 ).accept( this ); + else { + return (SqmPredicate) ctx.predicate().accept( this ); + } } @@ -2767,7 +2726,7 @@ public SqmPath visitEntityIdReference(HqlParser.EntityIdReferenceContext ctx) return sqmPath.get( identifierDescriptor.getPathName() ); } else if ( sqmPath instanceof SqmAnyValuedSimplePath ) { - return sqmPath.resolvePathPart( AnyKeyPart.KEY_NAME, true, getCurrentProcessingState().getCreationState() ); + return sqmPath.resolvePathPart( AnyKeyPart.KEY_NAME, true, processingStateStack.getCurrent().getCreationState() ); } else { throw new FunctionArgumentException( "Argument '" + sqmPath.getNavigablePath() diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/StrictJpaComplianceViolation.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/StrictJpaComplianceViolation.java index 9914872c3a..a44d41ac60 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/StrictJpaComplianceViolation.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/StrictJpaComplianceViolation.java @@ -16,6 +16,7 @@ public class StrictJpaComplianceViolation extends SemanticException { public enum Type { IMPLICIT_SELECT( "implicit select clause" ), + IMPLICIT_FROM( "implicit from clause" ), ALIASED_FETCH_JOIN( "aliased fetch join" ), UNMAPPED_POLYMORPHISM( "unmapped polymorphic reference" ), FUNCTION_CALL( "improper non-standard function call" ), diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/hql/nofrom/NoFromTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/hql/nofrom/NoFromTest.java new file mode 100644 index 0000000000..c1a8fd346a --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/hql/nofrom/NoFromTest.java @@ -0,0 +1,66 @@ +package org.hibernate.orm.test.hql.nofrom; + +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +import org.hibernate.query.SemanticException; +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.fail; + +@SessionFactory +@DomainModel(annotatedClasses = NoFromTest.Thing.class) +public class NoFromTest { + + @Test + public void test(SessionFactoryScope scope) { + scope.inTransaction( s -> { + s.persist(new Thing()); + s.persist(new Thing()); + }); + scope.inSession( s -> { + List things = s.createSelectionQuery("where id = 1", Thing.class).getResultList(); + assertEquals(1, things.size()); + assertEquals(1, things.get(0).id); + Thing thing = s.createSelectionQuery("where id = 1", Thing.class).getSingleResultOrNull(); + assertNotNull(thing); + assertEquals(1, thing.id); + }); + scope.inSession( s -> { + List things = s.createSelectionQuery("order by id desc", Thing.class).getResultList(); + assertEquals(2, things.size()); + assertEquals(2, things.get(0).id); + assertEquals(1, things.get(1).id); + }); + } + + @Test + public void testError(SessionFactoryScope scope) { + scope.inSession( s -> { + try { + s.createSelectionQuery("where id = 1").getResultList(); + fail(); + } + catch (SemanticException se) {} + }); + scope.inSession( s -> { + try { + s.createSelectionQuery("order by id desc").getResultList(); + fail(); + } + catch (SemanticException se) {} + }); + } + + @Entity(name = "Thing") + static class Thing { + @Id @GeneratedValue Long id; + } +}