/*
* 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.produce.internal.hql;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import org.hibernate.internal.util.collections.Stack;
import org.hibernate.persister.collection.spi.CollectionElement;
import org.hibernate.persister.collection.spi.CollectionPersister.CollectionClassification;
import org.hibernate.persister.common.spi.NavigableSource;
import org.hibernate.persister.common.spi.PluralPersistentAttribute;
import org.hibernate.persister.queryable.spi.BasicValuedExpressableType;
import org.hibernate.persister.queryable.spi.EntityValuedExpressableType;
import org.hibernate.persister.queryable.spi.ExpressableType;
import org.hibernate.persister.queryable.spi.PolymorphicEntityValuedExpressableType;
import org.hibernate.query.sqm.LiteralNumberFormatException;
import org.hibernate.query.sqm.NotYetImplementedException;
import org.hibernate.query.sqm.ParsingException;
import org.hibernate.query.sqm.SemanticException;
import org.hibernate.query.sqm.StrictJpaComplianceViolation;
import org.hibernate.query.sqm.UnknownEntityException;
import org.hibernate.query.sqm.hql.internal.antlr.HqlParser;
import org.hibernate.query.sqm.hql.internal.antlr.HqlParserBaseVisitor;
import org.hibernate.query.sqm.produce.internal.NavigableBindingHelper;
import org.hibernate.query.sqm.produce.internal.QuerySpecProcessingStateDmlImpl;
import org.hibernate.query.sqm.produce.internal.QuerySpecProcessingStateStandardImpl;
import org.hibernate.query.sqm.produce.internal.hql.navigable.NavigableBindingResolver;
import org.hibernate.query.sqm.produce.internal.hql.navigable.PathHelper;
import org.hibernate.query.sqm.produce.internal.hql.navigable.PathResolverBasicImpl;
import org.hibernate.query.sqm.produce.internal.hql.navigable.PathResolverJoinAttributeImpl;
import org.hibernate.query.sqm.produce.internal.hql.navigable.PathResolverJoinPredicateImpl;
import org.hibernate.query.sqm.produce.internal.hql.navigable.PathResolverSelectClauseImpl;
import org.hibernate.query.sqm.produce.spi.ImplicitAliasGenerator;
import org.hibernate.query.sqm.produce.spi.ParameterDeclarationContext;
import org.hibernate.query.sqm.produce.spi.ParsingContext;
import org.hibernate.query.sqm.produce.spi.QuerySpecProcessingState;
import org.hibernate.query.sqm.tree.SqmDeleteStatement;
import org.hibernate.query.sqm.tree.SqmInsertSelectStatement;
import org.hibernate.query.sqm.tree.SqmJoinType;
import org.hibernate.query.sqm.tree.SqmQuerySpec;
import org.hibernate.query.sqm.tree.SqmSelectStatement;
import org.hibernate.query.sqm.tree.SqmStatement;
import org.hibernate.query.sqm.tree.SqmUpdateStatement;
import org.hibernate.query.sqm.tree.expression.BinaryArithmeticSqmExpression;
import org.hibernate.query.sqm.tree.expression.CaseSearchedSqmExpression;
import org.hibernate.query.sqm.tree.expression.CaseSimpleSqmExpression;
import org.hibernate.query.sqm.tree.expression.CoalesceSqmExpression;
import org.hibernate.query.sqm.tree.expression.CollectionSizeSqmExpression;
import org.hibernate.query.sqm.tree.expression.ConcatSqmExpression;
import org.hibernate.query.sqm.tree.expression.ConstantEnumSqmExpression;
import org.hibernate.query.sqm.tree.expression.ConstantFieldSqmExpression;
import org.hibernate.query.sqm.tree.expression.ConstantSqmExpression;
import org.hibernate.query.sqm.tree.expression.EntityTypeLiteralSqmExpression;
import org.hibernate.query.sqm.tree.expression.ImpliedTypeSqmExpression;
import org.hibernate.query.sqm.tree.expression.LiteralBigDecimalSqmExpression;
import org.hibernate.query.sqm.tree.expression.LiteralBigIntegerSqmExpression;
import org.hibernate.query.sqm.tree.expression.LiteralCharacterSqmExpression;
import org.hibernate.query.sqm.tree.expression.LiteralDoubleSqmExpression;
import org.hibernate.query.sqm.tree.expression.LiteralFalseSqmExpression;
import org.hibernate.query.sqm.tree.expression.LiteralFloatSqmExpression;
import org.hibernate.query.sqm.tree.expression.LiteralIntegerSqmExpression;
import org.hibernate.query.sqm.tree.expression.LiteralLongSqmExpression;
import org.hibernate.query.sqm.tree.expression.LiteralNullSqmExpression;
import org.hibernate.query.sqm.tree.expression.LiteralSqmExpression;
import org.hibernate.query.sqm.tree.expression.LiteralStringSqmExpression;
import org.hibernate.query.sqm.tree.expression.LiteralTrueSqmExpression;
import org.hibernate.query.sqm.tree.expression.NamedParameterSqmExpression;
import org.hibernate.query.sqm.tree.expression.NullifSqmExpression;
import org.hibernate.query.sqm.tree.expression.ParameterSqmExpression;
import org.hibernate.query.sqm.tree.expression.ParameterizedEntityTypeSqmExpression;
import org.hibernate.query.sqm.tree.expression.PositionalParameterSqmExpression;
import org.hibernate.query.sqm.tree.expression.SqmExpression;
import org.hibernate.query.sqm.tree.expression.SubQuerySqmExpression;
import org.hibernate.query.sqm.tree.expression.UnaryOperationSqmExpression;
import org.hibernate.query.sqm.tree.expression.domain.SqmAttributeReference;
import org.hibernate.query.sqm.tree.expression.domain.SqmCollectionElementReference;
import org.hibernate.query.sqm.tree.expression.domain.SqmCollectionElementReferenceBasic;
import org.hibernate.query.sqm.tree.expression.domain.SqmCollectionElementReferenceEmbedded;
import org.hibernate.query.sqm.tree.expression.domain.SqmCollectionElementReferenceEntity;
import org.hibernate.query.sqm.tree.expression.domain.SqmCollectionIndexReference;
import org.hibernate.query.sqm.tree.expression.domain.SqmEmbeddableTypedReference;
import org.hibernate.query.sqm.tree.expression.domain.SqmEntityReference;
import org.hibernate.query.sqm.tree.expression.domain.SqmEntityTypeSqmExpression;
import org.hibernate.query.sqm.tree.expression.domain.SqmEntityTypedReference;
import org.hibernate.query.sqm.tree.expression.domain.SqmIndexedElementReferenceBasic;
import org.hibernate.query.sqm.tree.expression.domain.SqmIndexedElementReferenceEmbedded;
import org.hibernate.query.sqm.tree.expression.domain.SqmIndexedElementReferenceEntity;
import org.hibernate.query.sqm.tree.expression.domain.SqmMapEntryBinding;
import org.hibernate.query.sqm.tree.expression.domain.SqmMaxElementReferenceBasic;
import org.hibernate.query.sqm.tree.expression.domain.SqmMaxIndexReference;
import org.hibernate.query.sqm.tree.expression.domain.SqmMaxIndexReferenceBasic;
import org.hibernate.query.sqm.tree.expression.domain.SqmMaxIndexReferenceEmbedded;
import org.hibernate.query.sqm.tree.expression.domain.SqmMaxIndexReferenceEntity;
import org.hibernate.query.sqm.tree.expression.domain.SqmMinElementReference;
import org.hibernate.query.sqm.tree.expression.domain.SqmMinElementReferenceBasic;
import org.hibernate.query.sqm.tree.expression.domain.SqmMinElementReferenceEmbedded;
import org.hibernate.query.sqm.tree.expression.domain.SqmMinElementReferenceEntity;
import org.hibernate.query.sqm.tree.expression.domain.SqmMinIndexReference;
import org.hibernate.query.sqm.tree.expression.domain.SqmMinIndexReferenceBasic;
import org.hibernate.query.sqm.tree.expression.domain.SqmMinIndexReferenceEmbeddable;
import org.hibernate.query.sqm.tree.expression.domain.SqmMinIndexReferenceEntity;
import org.hibernate.query.sqm.tree.expression.domain.SqmNavigableReference;
import org.hibernate.query.sqm.tree.expression.domain.SqmNavigableResolutionContext;
import org.hibernate.query.sqm.tree.expression.domain.SqmNavigableSourceReference;
import org.hibernate.query.sqm.tree.expression.domain.SqmPluralAttributeReference;
import org.hibernate.query.sqm.tree.expression.domain.SqmRestrictedCollectionElementReference;
import org.hibernate.query.sqm.tree.expression.domain.SqmSingularAttributeReference;
import org.hibernate.query.sqm.tree.expression.function.AggregateFunctionSqmExpression;
import org.hibernate.query.sqm.tree.expression.function.AvgFunctionSqmExpression;
import org.hibernate.query.sqm.tree.expression.function.CastFunctionSqmExpression;
import org.hibernate.query.sqm.tree.expression.function.ConcatFunctionSqmExpression;
import org.hibernate.query.sqm.tree.expression.function.CountFunctionSqmExpression;
import org.hibernate.query.sqm.tree.expression.function.CountStarFunctionSqmExpression;
import org.hibernate.query.sqm.tree.expression.function.GenericFunctionSqmExpression;
import org.hibernate.query.sqm.tree.expression.function.LowerFunctionSqmExpression;
import org.hibernate.query.sqm.tree.expression.function.MaxFunctionSqmExpression;
import org.hibernate.query.sqm.tree.expression.function.MinFunctionSqmExpression;
import org.hibernate.query.sqm.tree.expression.function.SubstringFunctionSqmExpression;
import org.hibernate.query.sqm.tree.expression.function.SumFunctionSqmExpression;
import org.hibernate.query.sqm.tree.expression.function.TrimFunctionSqmExpression;
import org.hibernate.query.sqm.tree.expression.function.UpperFunctionSqmExpression;
import org.hibernate.query.sqm.tree.from.SqmAttributeJoin;
import org.hibernate.query.sqm.tree.from.SqmCrossJoin;
import org.hibernate.query.sqm.tree.from.SqmFrom;
import org.hibernate.query.sqm.tree.from.SqmFromClause;
import org.hibernate.query.sqm.tree.from.SqmFromElementSpace;
import org.hibernate.query.sqm.tree.from.SqmFromExporter;
import org.hibernate.query.sqm.tree.from.SqmQualifiedJoin;
import org.hibernate.query.sqm.tree.from.SqmRoot;
import org.hibernate.query.sqm.tree.internal.ParameterCollector;
import org.hibernate.query.sqm.tree.internal.SqmDeleteStatementImpl;
import org.hibernate.query.sqm.tree.internal.SqmInsertSelectStatementImpl;
import org.hibernate.query.sqm.tree.internal.SqmSelectStatementImpl;
import org.hibernate.query.sqm.tree.internal.SqmUpdateStatementImpl;
import org.hibernate.query.sqm.tree.order.SqmOrderByClause;
import org.hibernate.query.sqm.tree.order.SqmSortOrder;
import org.hibernate.query.sqm.tree.order.SqmSortSpecification;
import org.hibernate.query.sqm.tree.paging.SqmLimitOffsetClause;
import org.hibernate.query.sqm.tree.predicate.AndSqmPredicate;
import org.hibernate.query.sqm.tree.predicate.BetweenSqmPredicate;
import org.hibernate.query.sqm.tree.predicate.EmptinessSqmPredicate;
import org.hibernate.query.sqm.tree.predicate.GroupedSqmPredicate;
import org.hibernate.query.sqm.tree.predicate.InListSqmPredicate;
import org.hibernate.query.sqm.tree.predicate.InSubQuerySqmPredicate;
import org.hibernate.query.sqm.tree.predicate.LikeSqmPredicate;
import org.hibernate.query.sqm.tree.predicate.MemberOfSqmPredicate;
import org.hibernate.query.sqm.tree.predicate.NegatableSqmPredicate;
import org.hibernate.query.sqm.tree.predicate.NegatedSqmPredicate;
import org.hibernate.query.sqm.tree.predicate.NullnessSqmPredicate;
import org.hibernate.query.sqm.tree.predicate.OrSqmPredicate;
import org.hibernate.query.sqm.tree.predicate.RelationalPredicateOperator;
import org.hibernate.query.sqm.tree.predicate.RelationalSqmPredicate;
import org.hibernate.query.sqm.tree.predicate.SqmPredicate;
import org.hibernate.query.sqm.tree.predicate.SqmWhereClause;
import org.hibernate.query.sqm.tree.select.SqmDynamicInstantiation;
import org.hibernate.query.sqm.tree.select.SqmDynamicInstantiationArgument;
import org.hibernate.query.sqm.tree.select.SqmSelectClause;
import org.hibernate.query.sqm.tree.select.SqmSelection;
import org.jboss.logging.Logger;
import org.antlr.v4.runtime.Token;
import org.antlr.v4.runtime.tree.TerminalNode;
/**
* @author Steve Ebersole
*/
public class SemanticQueryBuilder extends HqlParserBaseVisitor implements SqmNavigableResolutionContext {
private static final Logger log = Logger.getLogger( SemanticQueryBuilder.class );
/**
* Main entry point into analysis of HQL/JPQL parse tree - producing a semantic model of the
* query.
*
* @param statement The statement to analyze.
* @param parsingContext Access to things needed to perform the analysis
*
* @return The semantic query model
*/
public static SqmStatement buildSemanticModel(HqlParser.StatementContext statement, ParsingContext parsingContext) {
return new SemanticQueryBuilder( parsingContext ).visitStatement( statement );
}
private final ParsingContext parsingContext;
private final Stack<NavigableBindingResolver> pathResolverStack = new Stack<>();
private final Stack<ParameterDeclarationContext> parameterDeclarationContextStack = new Stack<>();
private final Stack<QuerySpecProcessingState> querySpecProcessingStateStack = new Stack<>();
private boolean inWhereClause;
private ParameterCollector parameterCollector;
private SemanticQueryBuilder(ParsingContext parsingContext) {
this.parsingContext = parsingContext;
}
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Grammar rules
@Override
public SqmStatement visitStatement(HqlParser.StatementContext ctx) {
// parameters allow multi-valued bindings only in very limited cases, so for
// the base case here we say false
parameterDeclarationContextStack.push( () -> false );
try {
if ( ctx.insertStatement() != null ) {
return visitInsertStatement( ctx.insertStatement() );
}
else if ( ctx.updateStatement() != null ) {
return visitUpdateStatement( ctx.updateStatement() );
}
else if ( ctx.deleteStatement() != null ) {
return visitDeleteStatement( ctx.deleteStatement() );
}
else if ( ctx.selectStatement() != null ) {
return visitSelectStatement( ctx.selectStatement() );
}
}
finally {
parameterDeclarationContextStack.pop();
}
throw new ParsingException( "Unexpected statement type [not INSERT, UPDATE, DELETE or SELECT] : " + ctx.getText() );
}
@Override
public SqmSelectStatement visitSelectStatement(HqlParser.SelectStatementContext ctx) {
if ( parsingContext.getSessionFactory().useStrictJpaCompliance() ) {
if ( ctx.querySpec().selectClause() == null ) {
throw new StrictJpaComplianceViolation(
"Encountered implicit select-clause, but strict JPQL compliance was requested",
StrictJpaComplianceViolation.Type.IMPLICIT_SELECT
);
}
}
final SqmSelectStatementImpl selectStatement = new SqmSelectStatementImpl();
parameterCollector = selectStatement;
try {
selectStatement.applyQuerySpec( visitQuerySpec( ctx.querySpec() ) );
}
finally {
selectStatement.wrapUp();
}
return selectStatement;
}
@Override
public SqmQuerySpec visitQuerySpec(HqlParser.QuerySpecContext ctx) {
querySpecProcessingStateStack.push(
new QuerySpecProcessingStateStandardImpl(
parsingContext,
querySpecProcessingStateStack.getCurrent()
)
);
pathResolverStack.push( new PathResolverBasicImpl( querySpecProcessingStateStack.getCurrent() ) );
try {
// visit from-clause first!!!
visitFromClause( ctx.fromClause() );
final SqmSelectClause selectClause;
if ( ctx.selectClause() != null ) {
selectClause = visitSelectClause( ctx.selectClause() );
}
else {
log.info( "Encountered implicit select clause which is a deprecated feature : " + ctx.getText() );
selectClause = buildInferredSelectClause( querySpecProcessingStateStack.getCurrent().getFromClause() );
}
final SqmWhereClause whereClause;
if ( ctx.whereClause() != null ) {
whereClause = visitWhereClause( ctx.whereClause() );
}
else {
whereClause = null;
}
final SqmOrderByClause orderByClause;
if ( ctx.orderByClause() != null ) {
if ( parsingContext.getSessionFactory().useStrictJpaCompliance()
&& querySpecProcessingStateStack.getCurrent().getContainingQueryState() != null ) {
throw new StrictJpaComplianceViolation(
StrictJpaComplianceViolation.Type.SUBQUERY_ORDER_BY
);
}
pathResolverStack.push(
new PathResolverBasicImpl(
new OrderByResolutionContext(
parsingContext,
querySpecProcessingStateStack.getCurrent().getFromClause(),
selectClause
)
)
);
try {
orderByClause = visitOrderByClause( ctx.orderByClause() );
}
finally {
pathResolverStack.pop();
}
}
else {
orderByClause = null;
}
final SqmLimitOffsetClause limitOffsetClause;
if ( ctx.limitClause() != null || ctx.offsetClause() != null ) {
if ( parsingContext.getSessionFactory().useStrictJpaCompliance() ) {
throw new StrictJpaComplianceViolation(
StrictJpaComplianceViolation.Type.LIMIT_OFFSET_CLAUSE
);
}
if ( querySpecProcessingStateStack.getCurrent().getContainingQueryState() != null
&& orderByClause == null ) {
throw new SemanticException( "limit and offset clause require an order-by clause" );
}
final SqmExpression limitExpression;
if ( ctx.limitClause() != null ) {
limitExpression = visitLimitClause( ctx.limitClause() );
} else {
limitExpression = null;
}
final SqmExpression offsetExpression;
if ( ctx.offsetClause() != null ) {
offsetExpression = visitOffsetClause( ctx.offsetClause() );
} else {
offsetExpression = null;
}
limitOffsetClause = new SqmLimitOffsetClause( limitExpression, offsetExpression );
}
else {
limitOffsetClause = null;
}
return new SqmQuerySpec( querySpecProcessingStateStack.getCurrent().getFromClause(), selectClause, whereClause, orderByClause, limitOffsetClause );
}
finally {
pathResolverStack.pop();
querySpecProcessingStateStack.pop();
}
}
protected SqmSelectClause buildInferredSelectClause(SqmFromClause fromClause) {
// for now, this is slightly different than the legacy behavior where
// the root and each non-fetched-join was selected. For now, here, we simply
// select the root
final SqmSelectClause selectClause = new SqmSelectClause( false );
final SqmFrom root = fromClause.getFromElementSpaces().get( 0 ).getRoot();
selectClause.addSelection( new SqmSelection( root.getBinding() ) );
return selectClause;
}
@Override
public SqmSelectClause visitSelectClause(HqlParser.SelectClauseContext ctx) {
pathResolverStack.push( new PathResolverSelectClauseImpl( querySpecProcessingStateStack.getCurrent() ) );
try {
final SqmSelectClause selectClause = new SqmSelectClause( ctx.DISTINCT() != null );
for ( HqlParser.SelectionContext selectionContext : ctx.selectionList().selection() ) {
selectClause.addSelection( visitSelection( selectionContext ) );
}
return selectClause;
}
finally {
pathResolverStack.pop();
}
}
@Override
public SqmSelection visitSelection(HqlParser.SelectionContext ctx) {
SqmExpression selectExpression = visitSelectExpression( ctx.selectExpression() );
if ( selectExpression instanceof SqmPluralAttributeReference ) {
final SqmPluralAttributeReference pluralAttributeBinding = (SqmPluralAttributeReference) selectExpression;
final CollectionElement elementReference = pluralAttributeBinding.getReferencedNavigable().getCollectionPersister().getElementDescriptor();
switch ( elementReference.getClassification() ) {
case ANY: {
throw new NotYetImplementedException( );
}
case BASIC: {
selectExpression = new SqmCollectionElementReferenceBasic( pluralAttributeBinding );
break;
}
case EMBEDDABLE: {
selectExpression = new SqmCollectionElementReferenceEmbedded( pluralAttributeBinding );
break;
}
case ONE_TO_MANY:
case MANY_TO_MANY: {
selectExpression = new SqmCollectionElementReferenceEntity( pluralAttributeBinding );
break;
}
}
}
final SqmSelection selection = new SqmSelection(
selectExpression,
interpretResultIdentifier( ctx.resultIdentifier() )
);
querySpecProcessingStateStack.getCurrent().getFromElementBuilder().getAliasRegistry().registerAlias( selection );
return selection;
}
private String interpretResultIdentifier(HqlParser.ResultIdentifierContext resultIdentifierContext) {
if ( resultIdentifierContext != null ) {
final String explicitAlias;
if ( resultIdentifierContext.AS() != null ) {
final Token aliasToken = resultIdentifierContext.identifier().getStart();
explicitAlias = aliasToken.getText();
if ( aliasToken.getType() != HqlParser.IDENTIFIER ) {
// we have a reserved word used as an identification variable.
if ( parsingContext.getSessionFactory().useStrictJpaCompliance() ) {
throw new StrictJpaComplianceViolation(
String.format(
Locale.ROOT,
"Strict JPQL compliance was violated : %s [%s]",
StrictJpaComplianceViolation.Type.RESERVED_WORD_USED_AS_ALIAS.description(),
explicitAlias
),
StrictJpaComplianceViolation.Type.RESERVED_WORD_USED_AS_ALIAS
);
}
}
}
else {
explicitAlias = resultIdentifierContext.getText();
}
return explicitAlias;
}
return parsingContext.getImplicitAliasGenerator().buildUniqueImplicitAlias();
}
private String interpretAlias(HqlParser.IdentifierContext identifier) {
if ( identifier == null || identifier.getText() == null ) {
return parsingContext.getImplicitAliasGenerator().buildUniqueImplicitAlias();
}
return identifier.getText();
}
private String interpretAlias(TerminalNode aliasNode) {
if ( aliasNode == null ) {
return parsingContext.getImplicitAliasGenerator().buildUniqueImplicitAlias();
}
// todo : not sure I like asserts for this kind of thing. They are generally disable in runtime environments.
// either the thing is important to check or it isn't.
assert aliasNode.getSymbol().getType() == HqlParser.IDENTIFIER;
return aliasNode.getText();
}
@Override
public SqmExpression visitSelectExpression(HqlParser.SelectExpressionContext ctx) {
if ( ctx.dynamicInstantiation() != null ) {
return visitDynamicInstantiation( ctx.dynamicInstantiation() );
}
else if ( ctx.jpaSelectObjectSyntax() != null ) {
return visitJpaSelectObjectSyntax( ctx.jpaSelectObjectSyntax() );
}
else if ( ctx.expression() != null ) {
return (SqmExpression) ctx.expression().accept( this );
}
throw new ParsingException( "Unexpected selection rule type : " + ctx.getText() );
}
@Override
public SqmDynamicInstantiation visitDynamicInstantiation(HqlParser.DynamicInstantiationContext ctx) {
final SqmDynamicInstantiation dynamicInstantiation;
if ( ctx.dynamicInstantiationTarget().MAP() != null ) {
dynamicInstantiation = SqmDynamicInstantiation.forMapInstantiation();
}
else if ( ctx.dynamicInstantiationTarget().LIST() != null ) {
dynamicInstantiation = SqmDynamicInstantiation.forListInstantiation();
}
else {
final String className = ctx.dynamicInstantiationTarget().dotIdentifierSequence().getText();
try {
final Class targetJavaType = parsingContext.getSessionFactory().classByName( className );
dynamicInstantiation = SqmDynamicInstantiation.forClassInstantiation( targetJavaType );
}
catch (ClassNotFoundException e) {
throw new SemanticException( "Unable to resolve class named for dynamic instantiation : " + className );
}
}
for ( HqlParser.DynamicInstantiationArgContext arg : ctx.dynamicInstantiationArgs().dynamicInstantiationArg() ) {
dynamicInstantiation.addArgument( visitDynamicInstantiationArg( arg ) );
}
return dynamicInstantiation;
}
@Override
public SqmDynamicInstantiationArgument visitDynamicInstantiationArg(HqlParser.DynamicInstantiationArgContext ctx) {
return new SqmDynamicInstantiationArgument(
visitDynamicInstantiationArgExpression( ctx.dynamicInstantiationArgExpression() ),
ctx.identifier() == null ? null : ctx.identifier().getText()
);
}
@Override
public SqmExpression visitDynamicInstantiationArgExpression(HqlParser.DynamicInstantiationArgExpressionContext ctx) {
if ( ctx.dynamicInstantiation() != null ) {
return visitDynamicInstantiation( ctx.dynamicInstantiation() );
}
else if ( ctx.expression() != null ) {
return (SqmExpression) ctx.expression().accept( this );
}
throw new ParsingException( "Unexpected dynamic-instantiation-argument rule type : " + ctx.getText() );
}
@Override
public SqmNavigableReference visitJpaSelectObjectSyntax(HqlParser.JpaSelectObjectSyntaxContext ctx) {
final String alias = ctx.identifier().getText();
final SqmNavigableReference binding = querySpecProcessingStateStack.getCurrent().getFromElementBuilder().getAliasRegistry().findFromElementByAlias( alias );
if ( binding == null ) {
throw new SemanticException( "Unable to resolve alias [" + alias + "] in selection [" + ctx.getText() + "]" );
}
return binding;
}
@Override
public SqmWhereClause visitWhereClause(HqlParser.WhereClauseContext ctx) {
inWhereClause = true;
try {
return new SqmWhereClause( (SqmPredicate) ctx.predicate().accept( this ) );
}
finally {
inWhereClause = false;
}
}
@Override
public Object visitGroupByClause(HqlParser.GroupByClauseContext ctx) {
return super.visitGroupByClause( ctx );
}
@Override
public Object visitHavingClause(HqlParser.HavingClauseContext ctx) {
return super.visitHavingClause( ctx );
}
@Override
public GroupedSqmPredicate visitGroupedPredicate(HqlParser.GroupedPredicateContext ctx) {
return new GroupedSqmPredicate( (SqmPredicate) ctx.predicate().accept( this ) );
}
@Override
public SqmOrderByClause visitOrderByClause(HqlParser.OrderByClauseContext ctx) {
final SqmOrderByClause orderByClause = new SqmOrderByClause();
for ( HqlParser.SortSpecificationContext sortSpecificationContext : ctx.sortSpecification() ) {
orderByClause.addSortSpecification( visitSortSpecification( sortSpecificationContext ) );
}
return orderByClause;
}
@Override
public SqmSortSpecification visitSortSpecification(HqlParser.SortSpecificationContext ctx) {
final SqmExpression sortExpression = (SqmExpression) ctx.expression().accept( this );
final String collation;
if ( ctx.collationSpecification() != null && ctx.collationSpecification().collateName() != null ) {
collation = ctx.collationSpecification().collateName().dotIdentifierSequence().getText();
}
else {
collation = null;
}
final SqmSortOrder sortOrder;
if ( ctx.orderingSpecification() != null ) {
final String ordering = ctx.orderingSpecification().getText();
try {
sortOrder = interpretSortOrder( ordering );
}
catch (IllegalArgumentException e) {
throw new SemanticException( "Unrecognized sort ordering: " + ordering, e );
}
}
else {
sortOrder = null;
}
return new SqmSortSpecification( sortExpression, collation, sortOrder );
}
@Override
public SqmExpression visitLimitClause(HqlParser.LimitClauseContext ctx) {
return (SqmExpression) ctx.parameterOrNumberLiteral().accept( this );
}
@Override
public SqmExpression visitOffsetClause(HqlParser.OffsetClauseContext ctx) {
return (SqmExpression) ctx.parameterOrNumberLiteral().accept( this );
}
@Override
public SqmExpression visitParameterOrNumberLiteral(HqlParser.ParameterOrNumberLiteralContext ctx) {
if ( ctx.INTEGER_LITERAL() != null ) {
return integerLiteral( ctx.INTEGER_LITERAL().getText() );
}
if ( ctx.parameter() != null ) {
return (SqmExpression) ctx.parameter().accept( this );
}
return null;
}
private SqmSortOrder interpretSortOrder(String value) {
if ( value == null ) {
return null;
}
if ( value.equalsIgnoreCase( "ascending" ) || value.equalsIgnoreCase( "asc" ) ) {
return SqmSortOrder.ASCENDING;
}
if ( value.equalsIgnoreCase( "descending" ) || value.equalsIgnoreCase( "desc" ) ) {
return SqmSortOrder.DESCENDING;
}
throw new SemanticException( "Unknown sort order : " + value );
}
@Override
public SqmDeleteStatement visitDeleteStatement(HqlParser.DeleteStatementContext ctx) {
querySpecProcessingStateStack.push( new QuerySpecProcessingStateDmlImpl( parsingContext ) );
try {
final SqmRoot root = resolveDmlRootEntityReference( ctx.mainEntityPersisterReference() );
final SqmDeleteStatementImpl deleteStatement = new SqmDeleteStatementImpl( root );
parameterCollector = deleteStatement;
pathResolverStack.push( new PathResolverBasicImpl( querySpecProcessingStateStack.getCurrent() ) );
try {
deleteStatement.getWhereClause().setPredicate( (SqmPredicate) ctx.whereClause()
.predicate()
.accept( this ) );
}
finally {
pathResolverStack.pop();
deleteStatement.wrapUp();
}
return deleteStatement;
}
finally {
querySpecProcessingStateStack.pop();
}
}
protected SqmRoot resolveDmlRootEntityReference(HqlParser.MainEntityPersisterReferenceContext rootEntityContext) {
final String entityName = rootEntityContext.dotIdentifierSequence().getText();
final EntityValuedExpressableType entityReference = resolveEntityReference( entityName );
if ( entityReference == null ) {
throw new UnknownEntityException( "Could not resolve entity name [" + entityName + "] as DML target", entityName );
}
String alias = interpretIdentificationVariable( rootEntityContext.identificationVariableDef() );
if ( alias == null ) {
alias = parsingContext.getImplicitAliasGenerator().buildUniqueImplicitAlias();
log.debugf(
"Generated implicit alias [%s] for DML root entity reference [%s]",
alias,
entityReference.getEntityName()
);
}
final SqmRoot root = new SqmRoot( null, parsingContext.makeUniqueIdentifier(), alias, entityReference );
parsingContext.registerFromElementByUniqueId( root );
querySpecProcessingStateStack.getCurrent().getFromElementBuilder().getAliasRegistry().registerAlias( root.getBinding() );
querySpecProcessingStateStack.getCurrent().getFromClause().getFromElementSpaces().get( 0 ).setRoot( root );
return root;
}
private String interpretIdentificationVariable(HqlParser.IdentificationVariableDefContext identificationVariableDef) {
if ( identificationVariableDef != null ) {
final String explicitAlias;
if ( identificationVariableDef.AS() != null ) {
final Token identificationVariableToken = identificationVariableDef.identificationVariable().identifier().getStart();
if ( identificationVariableToken.getType() != HqlParser.IDENTIFIER ) {
// we have a reserved word used as an identification variable.
if ( parsingContext.getSessionFactory().useStrictJpaCompliance() ) {
throw new StrictJpaComplianceViolation(
String.format(
Locale.ROOT,
"Strict JPQL compliance was violated : %s [%s]",
StrictJpaComplianceViolation.Type.RESERVED_WORD_USED_AS_ALIAS.description(),
identificationVariableToken.getText()
),
StrictJpaComplianceViolation.Type.RESERVED_WORD_USED_AS_ALIAS
);
}
}
explicitAlias = identificationVariableToken.getText();
}
else {
explicitAlias = identificationVariableDef.IDENTIFIER().getText();
}
return explicitAlias;
}
return parsingContext.getImplicitAliasGenerator().buildUniqueImplicitAlias();
}
@Override
public SqmUpdateStatement visitUpdateStatement(HqlParser.UpdateStatementContext ctx) {
querySpecProcessingStateStack.push( new QuerySpecProcessingStateDmlImpl( parsingContext ) );
try {
final SqmRoot root = resolveDmlRootEntityReference( ctx.mainEntityPersisterReference() );
final SqmUpdateStatementImpl updateStatement = new SqmUpdateStatementImpl( root );
pathResolverStack.push( new PathResolverBasicImpl( querySpecProcessingStateStack.getCurrent() ) );
parameterCollector = updateStatement;
try {
updateStatement.getWhereClause().setPredicate(
(SqmPredicate) ctx.whereClause().predicate().accept( this )
);
for ( HqlParser.AssignmentContext assignmentContext : ctx.setClause().assignment() ) {
final SqmSingularAttributeReference stateField = (SqmSingularAttributeReference) pathResolverStack.getCurrent().resolvePath(
splitPathParts( assignmentContext.dotIdentifierSequence() )
);
// todo : validate "state field" expression
updateStatement.getSetClause().addAssignment(
stateField,
(SqmExpression) assignmentContext.expression().accept( this )
);
}
}
finally {
pathResolverStack.pop();
updateStatement.wrapUp();
}
return updateStatement;
}
finally {
querySpecProcessingStateStack.pop();
}
}
private String[] splitPathParts(HqlParser.DotIdentifierSequenceContext path) {
final String pathText = path.getText();
log.debugf( "Splitting dotIdentifierSequence into path parts : %s", pathText );
return PathHelper.split( pathText );
}
@Override
public SqmInsertSelectStatement visitInsertStatement(HqlParser.InsertStatementContext ctx) {
querySpecProcessingStateStack.push( new QuerySpecProcessingStateDmlImpl( parsingContext ) );
try {
final String entityName = ctx.insertSpec().intoSpec().dotIdentifierSequence().getText();
final EntityValuedExpressableType entityReference = resolveEntityReference( entityName );
if ( entityReference == null ) {
throw new UnknownEntityException( "Could not resolve entity name [" + entityName + "] as INSERT target", entityName );
}
String alias = parsingContext.getImplicitAliasGenerator().buildUniqueImplicitAlias();
log.debugf(
"Generated implicit alias [%s] for INSERT target [%s]",
alias,
entityReference.getEntityName()
);
SqmRoot root = new SqmRoot( null, parsingContext.makeUniqueIdentifier(), alias, entityReference );
parsingContext.registerFromElementByUniqueId( root );
querySpecProcessingStateStack.getCurrent().getFromElementBuilder().getAliasRegistry().registerAlias( root.getBinding() );
querySpecProcessingStateStack.getCurrent().getFromClause().getFromElementSpaces().get( 0 ).setRoot( root );
// for now we only support the INSERT-SELECT form
final SqmInsertSelectStatementImpl insertStatement = new SqmInsertSelectStatementImpl( root );
parameterCollector = insertStatement;
pathResolverStack.push( new PathResolverBasicImpl( querySpecProcessingStateStack.getCurrent() ) );
try {
insertStatement.setSelectQuery( visitQuerySpec( ctx.querySpec() ) );
for ( HqlParser.DotIdentifierSequenceContext stateFieldCtx : ctx.insertSpec().targetFieldsSpec().dotIdentifierSequence() ) {
final SqmSingularAttributeReference stateField = (SqmSingularAttributeReference) pathResolverStack.getCurrent().resolvePath(
splitPathParts( stateFieldCtx )
);
// todo : validate each resolved stateField...
insertStatement.addInsertTargetStateField( stateField );
}
}
finally {
pathResolverStack.pop();
insertStatement.wrapUp();
}
return insertStatement;
}
finally {
querySpecProcessingStateStack.pop();
}
}
private SqmFromElementSpace currentFromElementSpace;
@Override
public Object visitFromElementSpace(HqlParser.FromElementSpaceContext ctx) {
currentFromElementSpace = querySpecProcessingStateStack.getCurrent().getFromClause().makeFromElementSpace();
// adding root and joins to the FromElementSpace is currently handled in FromElementBuilder
// it is very questionable whether this should be done there, but for now keep it
// todo : revisit ^^
visitFromElementSpaceRoot( ctx.fromElementSpaceRoot() );
for ( HqlParser.CrossJoinContext crossJoinContext : ctx.crossJoin() ) {
visitCrossJoin( crossJoinContext );
}
for ( HqlParser.QualifiedJoinContext qualifiedJoinContext : ctx.qualifiedJoin() ) {
visitQualifiedJoin( qualifiedJoinContext );
}
for ( HqlParser.JpaCollectionJoinContext jpaCollectionJoinContext : ctx.jpaCollectionJoin() ) {
visitJpaCollectionJoin( jpaCollectionJoinContext );
}
SqmFromElementSpace rtn = currentFromElementSpace;
currentFromElementSpace = null;
return rtn;
}
@Override
public SqmRoot visitFromElementSpaceRoot(HqlParser.FromElementSpaceRootContext ctx) {
final String entityName = ctx.mainEntityPersisterReference().dotIdentifierSequence().getText();
final EntityValuedExpressableType entityReference = resolveEntityReference( entityName );
if ( entityReference == null ) {
throw new UnknownEntityException( "Could not resolve entity name [" + entityName + "] used as root", entityName );
}
if ( PolymorphicEntityValuedExpressableType.class.isInstance( entityReference ) ) {
if ( parsingContext.getSessionFactory().useStrictJpaCompliance() ) {
throw new StrictJpaComplianceViolation(
"Encountered unmapped polymorphic reference [" + entityReference.getEntityName()
+ "], but strict JPQL compliance was requested",
StrictJpaComplianceViolation.Type.UNMAPPED_POLYMORPHISM
);
}
// todo : disallow in subqueries as well
}
return querySpecProcessingStateStack.getCurrent().getFromElementBuilder().makeRootEntityFromElement(
currentFromElementSpace,
entityReference,
interpretIdentificationVariable( ctx.mainEntityPersisterReference().identificationVariableDef() )
);
}
private EntityValuedExpressableType resolveEntityReference(String entityName) {
log.debugf( "Attempting to resolve path [%s] as entity reference...", entityName );
EntityValuedExpressableType reference = null;
try {
reference = parsingContext.getSessionFactory()
.getDomainMetamodel()
.resolveEntityReference( entityName );
}
catch (Exception ignore) {
}
return reference;
}
@Override
public SqmCrossJoin visitCrossJoin(HqlParser.CrossJoinContext ctx) {
final String entityName = ctx.mainEntityPersisterReference().dotIdentifierSequence().getText();
final EntityValuedExpressableType entityReference = resolveEntityReference( entityName );
if ( entityReference == null ) {
throw new UnknownEntityException( "Could not resolve entity name [" + entityName + "] used as CROSS JOIN target", entityName );
}
if ( PolymorphicEntityValuedExpressableType.class.isInstance( entityReference ) ) {
throw new SemanticException(
"Unmapped polymorphic references are only valid as sqm root, not in cross join : " +
entityReference.getEntityName()
);
}
return querySpecProcessingStateStack.getCurrent().getFromElementBuilder().makeCrossJoinedFromElement(
currentFromElementSpace,
parsingContext.makeUniqueIdentifier(),
entityReference,
interpretIdentificationVariable( ctx.mainEntityPersisterReference().identificationVariableDef() )
);
}
@Override
public SqmQualifiedJoin visitJpaCollectionJoin(HqlParser.JpaCollectionJoinContext ctx) {
pathResolverStack.push(
new PathResolverJoinAttributeImpl(
querySpecProcessingStateStack.getCurrent(),
currentFromElementSpace,
SqmJoinType.INNER,
interpretIdentificationVariable( ctx.identificationVariableDef() ),
false
)
);
try {
SqmPluralAttributeReference attributeBinding = asPluralAttribute( (SqmNavigableReference) ctx.path().accept( this ) );
return attributeBinding.getExportedFromElement();
}
finally {
pathResolverStack.pop();
}
}
@Override
public SqmQualifiedJoin visitQualifiedJoin(HqlParser.QualifiedJoinContext ctx) {
final SqmJoinType joinType;
if ( ctx.OUTER() != null ) {
// for outer joins, only left outer joins are currently supported
if ( ctx.FULL() != null ) {
throw new SemanticException( "FULL OUTER joins are not yet supported : " + ctx.getText() );
}
if ( ctx.RIGHT() != null ) {
throw new SemanticException( "RIGHT OUTER joins are not yet supported : " + ctx.getText() );
}
joinType = SqmJoinType.LEFT;
}
else {
joinType = SqmJoinType.INNER;
}
final String identificationVariable = interpretIdentificationVariable(
ctx.qualifiedJoinRhs().identificationVariableDef()
);
pathResolverStack.push(
new PathResolverJoinAttributeImpl(
querySpecProcessingStateStack.getCurrent(),
currentFromElementSpace,
joinType,
identificationVariable,
ctx.FETCH() != null
)
);
try {
final SqmQualifiedJoin joinedFromElement;
// Object because join-target might be either an Entity join (... join Address a on ...)
// or an attribute-join (... from p.address a on ...)
final Object joinRhsResolution = ctx.qualifiedJoinRhs().path().accept( this );
final SqmNavigableReference navigableBinding;
if ( joinRhsResolution instanceof EntityTypeLiteralSqmExpression ) {
// convert the EntityTypeLiteralSqmExpression into an EntityBinding
final EntityTypeLiteralSqmExpression entityReference = (EntityTypeLiteralSqmExpression) joinRhsResolution;
navigableBinding = new SqmEntityReference( entityReference.getExpressionType() );
}
else {
navigableBinding = (SqmNavigableReference) joinRhsResolution;
}
if ( navigableBinding instanceof SqmAttributeReference ) {
resolveAttributeJoinIfNot( (SqmAttributeReference) navigableBinding, identificationVariable );
}
else if ( navigableBinding instanceof SqmCollectionElementReference ) {
resolveCollectionElementJoinIfNot( (SqmCollectionElementReference) navigableBinding, identificationVariable );
}
else if ( navigableBinding instanceof SqmCollectionIndexReference ) {
resolveCollectionIndexJoinIfNot( (SqmCollectionIndexReference) navigableBinding, identificationVariable );
}
else if ( navigableBinding instanceof SqmEntityTypedReference ) {
// should be an EntityBindingImpl
assert navigableBinding instanceof SqmEntityReference;
final SqmEntityTypedReference entityBinding = (SqmEntityTypedReference) navigableBinding;
if ( entityBinding.getExportedFromElement() == null ) {
entityBinding.injectExportedFromElement(
querySpecProcessingStateStack.getCurrent().getFromElementBuilder().buildEntityJoin(
currentFromElementSpace,
identificationVariable,
entityBinding.getReferencedNavigable(),
SqmJoinType.INNER
)
);
}
}
else if ( navigableBinding instanceof SqmEmbeddableTypedReference ) {
final SqmEmbeddableTypedReference compositeBinding = (SqmEmbeddableTypedReference) navigableBinding;
if ( compositeBinding.getExportedFromElement() == null ) {
// todo : can we create a SqmFrom element specific to a CompositeBinding?
throw new NotYetImplementedException( "Support for SqmFrom generation specific to CompositeBinding not yet implemented" );
}
}
else {
throw new ParsingException( "Unexpected qualifiedJoin.path resolution type : " + navigableBinding );
}
joinedFromElement = (SqmQualifiedJoin) ( (SqmFromExporter) navigableBinding ).getExportedFromElement();
currentJoinRhs = joinedFromElement;
if ( parsingContext.getSessionFactory().useStrictJpaCompliance() ) {
if ( !ImplicitAliasGenerator.isImplicitAlias( joinedFromElement.getIdentificationVariable() ) ) {
if ( SqmSingularAttributeReference.class.isInstance( joinedFromElement.getBinding() )
&& SqmFromExporter.class.isInstance( joinedFromElement.getBinding() ) ) {
final SqmAttributeJoin attributeJoin = (SqmAttributeJoin) joinedFromElement;
if ( attributeJoin.isFetched() ) {
throw new StrictJpaComplianceViolation(
"Encountered aliased fetch join, but strict JPQL compliance was requested",
StrictJpaComplianceViolation.Type.ALIASED_FETCH_JOIN
);
}
}
}
}
if ( ctx.qualifiedJoinPredicate() != null ) {
joinedFromElement.setOnClausePredicate( visitQualifiedJoinPredicate( ctx.qualifiedJoinPredicate() ) );
}
return joinedFromElement;
}
finally {
currentJoinRhs = null;
pathResolverStack.pop();
}
}
private void resolveCollectionElementJoinIfNot(SqmCollectionElementReference binding, String identificationVariable) {
throw new NotYetImplementedException( );
}
private void resolveCollectionIndexJoinIfNot(SqmCollectionIndexReference binding, String identificationVariable) {
throw new NotYetImplementedException( );
}
private SqmQualifiedJoin currentJoinRhs;
@Override
public SqmPredicate visitQualifiedJoinPredicate(HqlParser.QualifiedJoinPredicateContext ctx) {
if ( currentJoinRhs == null ) {
throw new ParsingException( "Expecting join RHS to be set" );
}
pathResolverStack.push(
new PathResolverJoinPredicateImpl( querySpecProcessingStateStack.getCurrent(), currentJoinRhs )
);
try {
return (SqmPredicate) ctx.predicate().accept( this );
}
finally {
pathResolverStack.pop();
}
}
@Override
public SqmPredicate visitAndPredicate(HqlParser.AndPredicateContext ctx) {
return new AndSqmPredicate(
(SqmPredicate) ctx.predicate( 0 ).accept( this ),
(SqmPredicate) ctx.predicate( 1 ).accept( this )
);
}
@Override
public SqmPredicate visitOrPredicate(HqlParser.OrPredicateContext ctx) {
return new OrSqmPredicate(
(SqmPredicate) ctx.predicate( 0 ).accept( this ),
(SqmPredicate) ctx.predicate( 1 ).accept( this )
);
}
@Override
public SqmPredicate visitNegatedPredicate(HqlParser.NegatedPredicateContext ctx) {
SqmPredicate predicate = (SqmPredicate) ctx.predicate().accept( this );
if ( predicate instanceof NegatableSqmPredicate ) {
( (NegatableSqmPredicate) predicate ).negate();
return predicate;
}
else {
return new NegatedSqmPredicate( predicate );
}
}
@Override
public NullnessSqmPredicate visitIsNullPredicate(HqlParser.IsNullPredicateContext ctx) {
return new NullnessSqmPredicate(
(SqmExpression) ctx.expression().accept( this ),
ctx.NOT() != null
);
}
@Override
public EmptinessSqmPredicate visitIsEmptyPredicate(HqlParser.IsEmptyPredicateContext ctx) {
return new EmptinessSqmPredicate(
(SqmPluralAttributeReference) ctx.expression().accept( this ),
ctx.NOT() != null
);
}
@Override
public RelationalSqmPredicate visitEqualityPredicate(HqlParser.EqualityPredicateContext ctx) {
final SqmExpression lhs = (SqmExpression) ctx.expression().get( 0 ).accept( this );
final SqmExpression rhs = (SqmExpression) ctx.expression().get( 1 ).accept( this );
if ( lhs.getInferableType() != null ) {
if ( rhs instanceof ImpliedTypeSqmExpression ) {
( (ImpliedTypeSqmExpression) rhs ).impliedType( lhs.getInferableType() );
}
}
if ( rhs.getInferableType() != null ) {
if ( lhs instanceof ImpliedTypeSqmExpression ) {
( (ImpliedTypeSqmExpression) lhs ).impliedType( rhs.getInferableType() );
}
}
return new RelationalSqmPredicate( RelationalPredicateOperator.EQUAL, lhs, rhs );
}
@Override
public Object visitInequalityPredicate(HqlParser.InequalityPredicateContext ctx) {
final SqmExpression lhs = (SqmExpression) ctx.expression().get( 0 ).accept( this );
final SqmExpression rhs = (SqmExpression) ctx.expression().get( 1 ).accept( this );
if ( lhs.getInferableType() != null ) {
if ( rhs instanceof ImpliedTypeSqmExpression ) {
( (ImpliedTypeSqmExpression) rhs ).impliedType( lhs.getInferableType() );
}
}
if ( rhs.getInferableType() != null ) {
if ( lhs instanceof ImpliedTypeSqmExpression ) {
( (ImpliedTypeSqmExpression) lhs ).impliedType( rhs.getInferableType() );
}
}
return new RelationalSqmPredicate( RelationalPredicateOperator.NOT_EQUAL, lhs, rhs );
}
@Override
public Object visitGreaterThanPredicate(HqlParser.GreaterThanPredicateContext ctx) {
final SqmExpression lhs = (SqmExpression) ctx.expression().get( 0 ).accept( this );
final SqmExpression rhs = (SqmExpression) ctx.expression().get( 1 ).accept( this );
if ( lhs.getInferableType() != null ) {
if ( rhs instanceof ImpliedTypeSqmExpression ) {
( (ImpliedTypeSqmExpression) rhs ).impliedType( lhs.getInferableType() );
}
}
if ( rhs.getInferableType() != null ) {
if ( lhs instanceof ImpliedTypeSqmExpression ) {
( (ImpliedTypeSqmExpression) lhs ).impliedType( rhs.getInferableType() );
}
}
return new RelationalSqmPredicate( RelationalPredicateOperator.GREATER_THAN, lhs, rhs );
}
@Override
public Object visitGreaterThanOrEqualPredicate(HqlParser.GreaterThanOrEqualPredicateContext ctx) {
final SqmExpression lhs = (SqmExpression) ctx.expression().get( 0 ).accept( this );
final SqmExpression rhs = (SqmExpression) ctx.expression().get( 1 ).accept( this );
if ( lhs.getInferableType() != null ) {
if ( rhs instanceof ImpliedTypeSqmExpression ) {
( (ImpliedTypeSqmExpression) rhs ).impliedType( lhs.getInferableType() );
}
}
if ( rhs.getInferableType() != null ) {
if ( lhs instanceof ImpliedTypeSqmExpression ) {
( (ImpliedTypeSqmExpression) lhs ).impliedType( rhs.getInferableType() );
}
}
return new RelationalSqmPredicate( RelationalPredicateOperator.GREATER_THAN_OR_EQUAL, lhs, rhs );
}
@Override
public Object visitLessThanPredicate(HqlParser.LessThanPredicateContext ctx) {
final SqmExpression lhs = (SqmExpression) ctx.expression().get( 0 ).accept( this );
final SqmExpression rhs = (SqmExpression) ctx.expression().get( 1 ).accept( this );
if ( lhs.getInferableType() != null ) {
if ( rhs instanceof ImpliedTypeSqmExpression ) {
( (ImpliedTypeSqmExpression) rhs ).impliedType( lhs.getInferableType() );
}
}
if ( rhs.getInferableType() != null ) {
if ( lhs instanceof ImpliedTypeSqmExpression ) {
( (ImpliedTypeSqmExpression) lhs ).impliedType( rhs.getInferableType() );
}
}
return new RelationalSqmPredicate( RelationalPredicateOperator.LESS_THAN, lhs, rhs );
}
@Override
public Object visitLessThanOrEqualPredicate(HqlParser.LessThanOrEqualPredicateContext ctx) {
final SqmExpression lhs = (SqmExpression) ctx.expression().get( 0 ).accept( this );
final SqmExpression rhs = (SqmExpression) ctx.expression().get( 1 ).accept( this );
if ( lhs.getInferableType() != null ) {
if ( rhs instanceof ImpliedTypeSqmExpression ) {
( (ImpliedTypeSqmExpression) rhs ).impliedType( lhs.getInferableType() );
}
}
if ( rhs.getInferableType() != null ) {
if ( lhs instanceof ImpliedTypeSqmExpression ) {
( (ImpliedTypeSqmExpression) lhs ).impliedType( rhs.getInferableType() );
}
}
return new RelationalSqmPredicate( RelationalPredicateOperator.LESS_THAN_OR_EQUAL, lhs, rhs );
}
@Override
public Object visitBetweenPredicate(HqlParser.BetweenPredicateContext ctx) {
final SqmExpression expression = (SqmExpression) ctx.expression().get( 0 ).accept( this );
final SqmExpression lowerBound = (SqmExpression) ctx.expression().get( 1 ).accept( this );
final SqmExpression upperBound = (SqmExpression) ctx.expression().get( 2 ).accept( this );
if ( expression.getInferableType() != null ) {
if ( lowerBound instanceof ImpliedTypeSqmExpression ) {
( (ImpliedTypeSqmExpression) lowerBound ).impliedType( expression.getInferableType() );
}
if ( upperBound instanceof ImpliedTypeSqmExpression ) {
( (ImpliedTypeSqmExpression) upperBound ).impliedType( expression.getInferableType() );
}
}
else if ( lowerBound.getInferableType() != null ) {
if ( expression instanceof ImpliedTypeSqmExpression ) {
( (ImpliedTypeSqmExpression) expression ).impliedType( lowerBound.getInferableType() );
}
if ( upperBound instanceof ImpliedTypeSqmExpression ) {
( (ImpliedTypeSqmExpression) upperBound ).impliedType( lowerBound.getInferableType() );
}
}
else if ( upperBound.getInferableType() != null ) {
if ( expression instanceof ImpliedTypeSqmExpression ) {
( (ImpliedTypeSqmExpression) expression ).impliedType( upperBound.getInferableType() );
}
if ( lowerBound instanceof ImpliedTypeSqmExpression ) {
( (ImpliedTypeSqmExpression) lowerBound ).impliedType( upperBound.getInferableType() );
}
}
return new BetweenSqmPredicate(
expression,
lowerBound,
upperBound,
false
);
}
@Override
public SqmPredicate visitLikePredicate(HqlParser.LikePredicateContext ctx) {
if ( ctx.likeEscape() != null ) {
return new LikeSqmPredicate(
(SqmExpression) ctx.expression().get( 0 ).accept( this ),
(SqmExpression) ctx.expression().get( 1 ).accept( this ),
(SqmExpression) ctx.likeEscape().expression().accept( this )
);
}
else {
return new LikeSqmPredicate(
(SqmExpression) ctx.expression().get( 0 ).accept( this ),
(SqmExpression) ctx.expression().get( 1 ).accept( this )
);
}
}
@Override
public SqmPredicate visitMemberOfPredicate(HqlParser.MemberOfPredicateContext ctx) {
final SqmNavigableReference pathResolution = (SqmNavigableReference) ctx.path().accept( this );
if ( pathResolution == null ) {
throw new SemanticException( "Could not resolve path [" + ctx.path().getText() + "] as a plural attribute reference" );
}
if ( !SqmPluralAttributeReference.class.isInstance( pathResolution ) ) {
throw new SemanticException( "Path argument to MEMBER OF must be a plural attribute" );
}
return new MemberOfSqmPredicate( (SqmPluralAttributeReference) pathResolution );
}
@Override
public SqmPredicate visitInPredicate(HqlParser.InPredicateContext ctx) {
final SqmExpression testExpression = (SqmExpression) ctx.expression().accept( this );
if ( HqlParser.ExplicitTupleInListContext.class.isInstance( ctx.inList() ) ) {
final HqlParser.ExplicitTupleInListContext tupleExpressionListContext = (HqlParser.ExplicitTupleInListContext) ctx.inList();
parameterDeclarationContextStack.push( () -> tupleExpressionListContext.expression().size() == 1 );
try {
final List<SqmExpression> listExpressions = new ArrayList<>( tupleExpressionListContext.expression().size() );
for ( HqlParser.ExpressionContext expressionContext : tupleExpressionListContext.expression() ) {
final SqmExpression listItemExpression = (SqmExpression) expressionContext.accept( this );
if ( testExpression.getInferableType() != null ) {
if ( listItemExpression instanceof ImpliedTypeSqmExpression ) {
( (ImpliedTypeSqmExpression) listItemExpression ).impliedType( testExpression.getInferableType() );
}
}
listExpressions.add( listItemExpression );
}
return new InListSqmPredicate( testExpression, listExpressions );
}
finally {
parameterDeclarationContextStack.pop();
}
}
else if ( HqlParser.SubQueryInListContext.class.isInstance( ctx.inList() ) ) {
final HqlParser.SubQueryInListContext subQueryContext = (HqlParser.SubQueryInListContext) ctx.inList();
final SqmExpression subQueryExpression = (SqmExpression) subQueryContext.expression().accept( this );
if ( !SubQuerySqmExpression.class.isInstance( subQueryExpression ) ) {
throw new ParsingException(
"Was expecting a SubQueryExpression, but found " + subQueryExpression.getClass().getSimpleName()
+ " : " + subQueryContext.expression().toString()
);
}
return new InSubQuerySqmPredicate( testExpression, (SubQuerySqmExpression) subQueryExpression );
}
// todo : handle PersistentCollectionReferenceInList labeled branch
throw new ParsingException( "Unexpected IN predicate type [" + ctx.getClass().getSimpleName() + "] : " + ctx.getText() );
}
@Override
public Object visitEntityTypeExpression(HqlParser.EntityTypeExpressionContext ctx) {
// can be one of 2 forms:
// 1) TYPE( some.path )
// 2) TYPE( :someParam )
if ( ctx.entityTypeReference().parameter() != null ) {
// we have form (2)
return new ParameterizedEntityTypeSqmExpression(
(ParameterSqmExpression) ctx.entityTypeReference().parameter().accept( this )
);
}
else if ( ctx.entityTypeReference().path() != null ) {
final SqmNavigableReference binding = (SqmNavigableReference) ctx.entityTypeReference().path().accept( this );
validateBindingAsEntityTypeExpression( binding );
return new SqmEntityTypeSqmExpression( binding );
}
throw new ParsingException( "Could not interpret grammar context as 'entity type' expression : " + ctx.getText() );
}
private void validateBindingAsEntityTypeExpression(SqmNavigableReference binding) {
if ( binding instanceof SqmEntityTypedReference ) {
// its ok
return;
}
throw new SemanticException(
"Path used in TYPE() resolved to a non-EntityBinding : " + binding
);
}
@Override
public SqmExpression visitSimplePath(HqlParser.SimplePathContext ctx) {
// SimplePath might represent any number of things
final SqmNavigableReference binding = pathAsNavigableBinding( splitPathParts( ctx.dotIdentifierSequence() ) );
if ( binding != null ) {
return binding;
}
final String pathText = ctx.getText();
final EntityValuedExpressableType entityReference = resolveEntityReference( pathText );
if ( entityReference != null ) {
// todo : how this should be handled depends on the parent context...
// inside a FROM-CLAUSE (although be careful of treats) -> EntityBindingImpl
// otherwise -> EntityTypeLiteralSqmExpression
//
// an example of the warning about treats...
// from Person p cross join (treat Person as Person)...
return new EntityTypeLiteralSqmExpression( entityReference );
// return new EntityBindingImpl( entityReference );
}
try {
return resolveConstantExpression( pathText );
}
catch (SemanticException e) {
log.debug( e.getMessage() );
}
// if we get here we had a problem interpreting the dot-ident sequence
throw new SemanticException( "Could not interpret token : " + pathText );
}
protected SqmNavigableReference pathAsNavigableBinding(String[] pathParts) {
return pathResolverStack.getCurrent().resolvePath( pathParts );
}
@Override
public SqmMapEntryBinding visitMapEntryPath(HqlParser.MapEntryPathContext ctx) {
if ( inWhereClause ) {
throw new SemanticException(
"entry() function may only be used in SELECT clauses; specified "
+ "path [" + ctx.pathAsMap().path().getText() + "] is used in WHERE clause" );
}
final SqmPluralAttributeReference pathResolution = asMap( (SqmNavigableReference) ctx.pathAsMap().path().accept( this ) );
resolveAttributeJoinIfNot( pathResolution );
return new SqmMapEntryBinding( pathResolution );
}
private void resolveAttributeJoinIfNot(SqmAttributeReference binding) {
resolveAttributeJoinIfNot( binding, null );
}
private void resolveAttributeJoinIfNot(SqmAttributeReference attributeBinding, String alias) {
if ( attributeBinding.getExportedFromElement() != null ) {
return;
}
attributeBinding.injectExportedFromElement(
querySpecProcessingStateStack.getCurrent().getFromElementBuilder().buildAttributeJoin(
attributeBinding,
alias,
null,
SqmJoinType.INNER,
false,
true
)
);
}
@Override
public SqmNavigableReference visitIndexedPath(HqlParser.IndexedPathContext ctx) {
final Object pathResolution = ctx.path().accept( this );
if ( !SqmPluralAttributeReference.class.isInstance( pathResolution ) ) {
// assume it is a semantic problem...
throw new SemanticException(
"Expecting reference to a plural-attribute, but path [" + ctx.path().getText() +
"] resolved to : " + pathResolution
);
}
final SqmPluralAttributeReference attributeBinding = (SqmPluralAttributeReference) pathResolution;
if ( !PluralPersistentAttribute.class.isInstance( attributeBinding.getReferencedNavigable() )
|| attributeBinding.getReferencedNavigable().getCollectionPersister().getIndexDescriptor() == null ) {
throw new SemanticException(
"Index operator only valid for indexed collections (maps, lists, arrays) : " +
attributeBinding.getReferencedNavigable()
);
}
final PluralPersistentAttribute attRef = attributeBinding.getReferencedNavigable();
final CollectionElement elementReference = attributeBinding.getReferencedNavigable().getCollectionPersister().getElementDescriptor();
final SqmExpression indexExpression = (SqmExpression) ctx.expression().accept( this );
// todo : would be nice to validate the index's type against the Collection-index's type
assertAreSame( attributeBinding.getReferencedNavigable().getCollectionPersister().getIndexDescriptor(), indexExpression.getExpressionType() );
// determine the kind of IndexedElementBinding to create
final SqmRestrictedCollectionElementReference indexedReference;
switch ( elementReference.getClassification() ) {
case ANY: {
throw new NotYetImplementedException( );
}
case BASIC: {
indexedReference = new SqmIndexedElementReferenceBasic( attributeBinding, indexExpression );
break;
}
case EMBEDDABLE: {
indexedReference = new SqmIndexedElementReferenceEmbedded( attributeBinding, indexExpression );
break;
}
case ONE_TO_MANY:
case MANY_TO_MANY: {
indexedReference = new SqmIndexedElementReferenceEntity(
attributeBinding,
indexExpression
);
break;
}
default: {
throw new UnsupportedOperationException(
"Unrecognized plural-attribute element classification : " +
elementReference.getClassification().name()
);
}
}
if ( ctx.pathTerminal() == null ) {
return indexedReference;
}
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// otherwise, we have a dereference of the pathRoot (as a pathTerminal)
// the binding would additionally need to be an AttributeBindingSource
// and expose a Bindable
if ( ! NavigableSource.class.isInstance( elementReference ) ) {
throw new SemanticException(
String.format(
Locale.ROOT,
"Plural attribute elements [%s.%s - as resolved from %s] cannot be dereferenced - [%s]",
attributeBinding.getSourceReference().asLoggableText(),
attributeBinding.getReferencedNavigable().getAttributeName(),
ctx.path().getText(),
attRef.getCollectionPersister().getElementDescriptor().getClassification().name()
)
);
}
return pathResolverStack.getCurrent().resolvePath(
(SqmNavigableSourceReference) indexedReference,
PathHelper.split( ctx.pathTerminal().getText() )
);
}
private void assertAreSame(ExpressableType expected, ExpressableType actual) {
if ( !expected.equals( actual ) ) {
log.debugf( "Expected domain type [%s] but found [%s]", expected, actual );
}
}
@Override
public SqmNavigableReference visitCompoundPath(HqlParser.CompoundPathContext ctx) {
SqmNavigableReference rootPath = (SqmNavigableReference) ctx.pathRoot().accept( this );
if ( ctx.pathTerminal() == null ) {
// can happen for non-dereferenced KEY() and VALUE() references...
return rootPath;
// todo : a better solution is to better structure the grammar rules.
}
final SqmNavigableSourceReference root = (SqmNavigableSourceReference) rootPath ;
log.debugf(
"Resolved CompoundPath pathRoot [%s] : %s",
ctx.pathRoot().getText(),
root
);
if ( ctx.pathTerminal() == null ) {
return root;
}
return pathResolverStack.getCurrent().resolvePath(
root,
PathHelper.split( ctx.pathTerminal().getText() )
);
}
@Override
public SqmNavigableReference visitMapKeyPathRoot(HqlParser.MapKeyPathRootContext ctx) {
final SqmPluralAttributeReference pathResolution = visitPathAsMap( ctx.pathAsMap() );
resolveAttributeJoinIfNot( pathResolution );
return NavigableBindingHelper.createCollectionIndexBinding(
pathResolution,
pathResolution.getReferencedNavigable().getCollectionPersister().getIndexDescriptor()
);
}
private SqmPluralAttributeReference asMap(SqmNavigableReference binding) {
SqmPluralAttributeReference attributeBinding = asPluralAttribute( binding );
if ( attributeBinding.getReferencedNavigable().getCollectionPersister().getCollectionClassification() != CollectionClassification.MAP ) {
throw new SemanticException( "Expecting persistent Map reference, but found : " + binding );
}
return attributeBinding;
}
@Override
public SqmCollectionElementReference visitCollectionValuePathRoot(HqlParser.CollectionValuePathRootContext ctx) {
final SqmPluralAttributeReference attributeBinding = asPluralAttribute( (SqmNavigableReference) ctx.collectionReference().accept( this ) );
if ( parsingContext.getSessionFactory().useStrictJpaCompliance() ) {
if ( attributeBinding.getReferencedNavigable().getCollectionPersister().getCollectionClassification() != CollectionClassification.MAP ) {
throw new StrictJpaComplianceViolation(
"Encountered application of value() function to path expression which does not " +
"resolve to a persistent Map, but strict JPQL compliance was requested. specified "
+ "path [" + ctx.collectionReference().path().getText() + "] resolved to " + attributeBinding,
StrictJpaComplianceViolation.Type.VALUE_FUNCTION_ON_NON_MAP
);
}
}
resolveAttributeJoinIfNot( attributeBinding );
return NavigableBindingHelper.createCollectionElementBinding(
attributeBinding,
attributeBinding.getReferencedNavigable().getCollectionPersister().getElementDescriptor()
);
}
@SuppressWarnings("RedundantIfStatement")
private SqmPluralAttributeReference asPluralAttribute(SqmNavigableReference attributeBinding) {
if ( !SqmPluralAttributeReference.class.isInstance( attributeBinding.getReferencedNavigable() ) ) {
throw new SemanticException( "Expecting plural-attribute, but found : " + attributeBinding );
}
return (SqmPluralAttributeReference) attributeBinding;
}
@Override
public SqmPluralAttributeReference visitCollectionReference(HqlParser.CollectionReferenceContext ctx) {
return asPluralAttribute( (SqmNavigableReference) ctx.path().accept( this ) );
}
@Override
public SqmPluralAttributeReference visitPathAsMap(HqlParser.PathAsMapContext ctx) {
final SqmNavigableReference pathResolution = (SqmNavigableReference) ctx.path().accept( this );
return asMap( pathResolution );
}
@Override
public SqmNavigableReference visitTreatedPathRoot(HqlParser.TreatedPathRootContext ctx) {
final String treatAsName = ctx.dotIdentifierSequence().get( 1 ).getText();
final EntityValuedExpressableType treatAsTypeDescriptor = resolveEntityReference( treatAsName );
if ( treatAsTypeDescriptor == null ) {
throw new UnknownEntityException( "Could not resolve entity name [" + treatAsName + "] as TREAT target", treatAsName );
}
return pathResolverStack.getCurrent().resolveTreatedPath(
treatAsTypeDescriptor,
splitPathParts( ctx.dotIdentifierSequence().get( 0 ) )
);
}
@SuppressWarnings("unchecked")
protected ConstantSqmExpression resolveConstantExpression(String reference) {
// todo : hook in "import" resolution using the ParsingContext
final int dotPosition = reference.lastIndexOf( '.' );
final String className = reference.substring( 0, dotPosition - 1 );
final String fieldName = reference.substring( dotPosition+1, reference.length() );
try {
final Class clazz = parsingContext.getSessionFactory().classByName( className );
if ( clazz.isEnum() ) {
try {
return new ConstantEnumSqmExpression( Enum.valueOf( clazz, fieldName ) );
}
catch (IllegalArgumentException e) {
throw new SemanticException( "Name [" + fieldName + "] does not represent an enum constant on enum class [" + className + "]" );
}
}
else {
try {
final Field field = clazz.getField( fieldName );
if ( !Modifier.isStatic( field.getModifiers() ) ) {
throw new SemanticException( "Field [" + fieldName + "] is not static on class [" + className + "]" );
}
field.setAccessible( true );
return new ConstantFieldSqmExpression( field, field.get( null ) );
}
catch (NoSuchFieldException e) {
throw new SemanticException( "Name [" + fieldName + "] does not represent a field on class [" + className + "]", e );
}
catch (SecurityException e) {
throw new SemanticException( "Field [" + fieldName + "] is not accessible on class [" + className + "]", e );
}
catch (IllegalAccessException e) {
throw new SemanticException( "Unable to access field [" + fieldName + "] on class [" + className + "]", e );
}
}
}
catch (ClassNotFoundException e) {
throw new SemanticException( "Cannot resolve class for sqm constant [" + reference + "]" );
}
}
@Override
public ConcatSqmExpression visitConcatenationExpression(HqlParser.ConcatenationExpressionContext ctx) {
if ( ctx.expression().size() != 2 ) {
throw new ParsingException( "Expecting 2 operands to the concat operator" );
}
return new ConcatSqmExpression(
(SqmExpression) ctx.expression( 0 ).accept( this ),
(SqmExpression) ctx.expression( 1 ).accept( this )
);
}
@Override
public Object visitAdditionExpression(HqlParser.AdditionExpressionContext ctx) {
if ( ctx.expression().size() != 2 ) {
throw new ParsingException( "Expecting 2 operands to the + operator" );
}
final SqmExpression firstOperand = (SqmExpression) ctx.expression( 0 ).accept( this );
final SqmExpression secondOperand = (SqmExpression) ctx.expression( 1 ).accept( this );
return new BinaryArithmeticSqmExpression(
BinaryArithmeticSqmExpression.Operation.ADD,
firstOperand,
secondOperand,
parsingContext.getSessionFactory().getDomainMetamodel().resolveArithmeticType(
(BasicValuedExpressableType) firstOperand.getExpressionType(),
(BasicValuedExpressableType) secondOperand.getExpressionType(),
BinaryArithmeticSqmExpression.Operation.ADD
)
);
}
@Override
public Object visitSubtractionExpression(HqlParser.SubtractionExpressionContext ctx) {
if ( ctx.expression().size() != 2 ) {
throw new ParsingException( "Expecting 2 operands to the - operator" );
}
final SqmExpression firstOperand = (SqmExpression) ctx.expression( 0 ).accept( this );
final SqmExpression secondOperand = (SqmExpression) ctx.expression( 1 ).accept( this );
return new BinaryArithmeticSqmExpression(
BinaryArithmeticSqmExpression.Operation.SUBTRACT,
firstOperand,
secondOperand,
parsingContext.getSessionFactory().getDomainMetamodel().resolveArithmeticType(
(BasicValuedExpressableType) firstOperand.getExpressionType(),
(BasicValuedExpressableType) secondOperand.getExpressionType(),
BinaryArithmeticSqmExpression.Operation.SUBTRACT
)
);
}
@Override
public Object visitMultiplicationExpression(HqlParser.MultiplicationExpressionContext ctx) {
if ( ctx.expression().size() != 2 ) {
throw new ParsingException( "Expecting 2 operands to the * operator" );
}
final SqmExpression firstOperand = (SqmExpression) ctx.expression( 0 ).accept( this );
final SqmExpression secondOperand = (SqmExpression) ctx.expression( 1 ).accept( this );
return new BinaryArithmeticSqmExpression(
BinaryArithmeticSqmExpression.Operation.MULTIPLY,
firstOperand,
secondOperand,
parsingContext.getSessionFactory().getDomainMetamodel().resolveArithmeticType(
(BasicValuedExpressableType) firstOperand.getExpressionType(),
(BasicValuedExpressableType) secondOperand.getExpressionType(),
BinaryArithmeticSqmExpression.Operation.MULTIPLY
)
);
}
@Override
public Object visitDivisionExpression(HqlParser.DivisionExpressionContext ctx) {
if ( ctx.expression().size() != 2 ) {
throw new ParsingException( "Expecting 2 operands to the / operator" );
}
final SqmExpression firstOperand = (SqmExpression) ctx.expression( 0 ).accept( this );
final SqmExpression secondOperand = (SqmExpression) ctx.expression( 1 ).accept( this );
return new BinaryArithmeticSqmExpression(
BinaryArithmeticSqmExpression.Operation.DIVIDE,
firstOperand,
secondOperand,
parsingContext.getSessionFactory().getDomainMetamodel().resolveArithmeticType(
(BasicValuedExpressableType) firstOperand.getExpressionType(),
(BasicValuedExpressableType) secondOperand.getExpressionType(),
BinaryArithmeticSqmExpression.Operation.DIVIDE
)
);
}
@Override
public Object visitModuloExpression(HqlParser.ModuloExpressionContext ctx) {
if ( ctx.expression().size() != 2 ) {
throw new ParsingException( "Expecting 2 operands to the % operator" );
}
final SqmExpression firstOperand = (SqmExpression) ctx.expression( 0 ).accept( this );
final SqmExpression secondOperand = (SqmExpression) ctx.expression( 1 ).accept( this );
return new BinaryArithmeticSqmExpression(
BinaryArithmeticSqmExpression.Operation.MODULO,
firstOperand,
secondOperand,
parsingContext.getSessionFactory().getDomainMetamodel().resolveArithmeticType(
(BasicValuedExpressableType) firstOperand.getExpressionType(),
(BasicValuedExpressableType) secondOperand.getExpressionType(),
BinaryArithmeticSqmExpression.Operation.MODULO
)
);
}
@Override
public Object visitUnaryPlusExpression(HqlParser.UnaryPlusExpressionContext ctx) {
return new UnaryOperationSqmExpression(
UnaryOperationSqmExpression.Operation.PLUS,
(SqmExpression) ctx.expression().accept( this )
);
}
@Override
public Object visitUnaryMinusExpression(HqlParser.UnaryMinusExpressionContext ctx) {
return new UnaryOperationSqmExpression(
UnaryOperationSqmExpression.Operation.MINUS,
(SqmExpression) ctx.expression().accept( this )
);
}
@Override
public CaseSimpleSqmExpression visitSimpleCaseStatement(HqlParser.SimpleCaseStatementContext ctx) {
final CaseSimpleSqmExpression caseExpression = new CaseSimpleSqmExpression(
(SqmExpression) ctx.expression().accept( this )
);
for ( HqlParser.SimpleCaseWhenContext simpleCaseWhen : ctx.simpleCaseWhen() ) {
caseExpression.when(
(SqmExpression) simpleCaseWhen.expression( 0 ).accept( this ),
(SqmExpression) simpleCaseWhen.expression( 1 ).accept( this )
);
}
if ( ctx.caseOtherwise() != null ) {
caseExpression.otherwise( (SqmExpression) ctx.caseOtherwise().expression().accept( this ) );
}
return caseExpression;
}
@Override
public CaseSearchedSqmExpression visitSearchedCaseStatement(HqlParser.SearchedCaseStatementContext ctx) {
final CaseSearchedSqmExpression caseExpression = new CaseSearchedSqmExpression();
for ( HqlParser.SearchedCaseWhenContext whenFragment : ctx.searchedCaseWhen() ) {
caseExpression.when(
(SqmPredicate) whenFragment.predicate().accept( this ),
(SqmExpression) whenFragment.expression().accept( this )
);
}
if ( ctx.caseOtherwise() != null ) {
caseExpression.otherwise( (SqmExpression) ctx.caseOtherwise().expression().accept( this ) );
}
return caseExpression;
}
@Override
public CoalesceSqmExpression visitCoalesceExpression(HqlParser.CoalesceExpressionContext ctx) {
CoalesceSqmExpression coalesceExpression = new CoalesceSqmExpression();
for ( HqlParser.ExpressionContext expressionContext : ctx.coalesce().expression() ) {
coalesceExpression.value( (SqmExpression) expressionContext.accept( this ) );
}
return coalesceExpression;
}
@Override
public NullifSqmExpression visitNullIfExpression(HqlParser.NullIfExpressionContext ctx) {
return new NullifSqmExpression(
(SqmExpression) ctx.nullIf().expression( 0 ).accept( this ),
(SqmExpression) ctx.nullIf().expression( 1 ).accept( this )
);
}
@Override
@SuppressWarnings("UnnecessaryBoxing")
public LiteralSqmExpression visitLiteralExpression(HqlParser.LiteralExpressionContext ctx) {
if ( ctx.literal().CHARACTER_LITERAL() != null ) {
return characterLiteral( ctx.literal().CHARACTER_LITERAL().getText() );
}
else if ( ctx.literal().STRING_LITERAL() != null ) {
return stringLiteral( ctx.literal().STRING_LITERAL().getText() );
}
else if ( ctx.literal().INTEGER_LITERAL() != null ) {
return integerLiteral( ctx.literal().INTEGER_LITERAL().getText() );
}
else if ( ctx.literal().LONG_LITERAL() != null ) {
return longLiteral( ctx.literal().LONG_LITERAL().getText() );
}
else if ( ctx.literal().BIG_INTEGER_LITERAL() != null ) {
return bigIntegerLiteral( ctx.literal().BIG_INTEGER_LITERAL().getText() );
}
else if ( ctx.literal().HEX_LITERAL() != null ) {
final String text = ctx.literal().HEX_LITERAL().getText();
if ( text.endsWith( "l" ) || text.endsWith( "L" ) ) {
return longLiteral( text );
}
else {
return integerLiteral( text );
}
}
else if ( ctx.literal().OCTAL_LITERAL() != null ) {
final String text = ctx.literal().OCTAL_LITERAL().getText();
if ( text.endsWith( "l" ) || text.endsWith( "L" ) ) {
return longLiteral( text );
}
else {
return integerLiteral( text );
}
}
else if ( ctx.literal().FLOAT_LITERAL() != null ) {
return floatLiteral( ctx.literal().FLOAT_LITERAL().getText() );
}
else if ( ctx.literal().DOUBLE_LITERAL() != null ) {
return doubleLiteral( ctx.literal().DOUBLE_LITERAL().getText() );
}
else if ( ctx.literal().BIG_DECIMAL_LITERAL() != null ) {
return bigDecimalLiteral( ctx.literal().BIG_DECIMAL_LITERAL().getText() );
}
else if ( ctx.literal().FALSE() != null ) {
booleanLiteral( false );
}
else if ( ctx.literal().TRUE() != null ) {
booleanLiteral( true );
}
else if ( ctx.literal().NULL() != null ) {
return new LiteralNullSqmExpression();
}
// otherwise we have a problem
throw new ParsingException( "Unexpected literal expression type [" + ctx.getText() + "]" );
}
private LiteralSqmExpression<Boolean> booleanLiteral(boolean value) {
final BasicValuedExpressableType expressionType = resolveExpressableTypeBasic( Boolean.class );
return value
? new LiteralTrueSqmExpression( expressionType )
: new LiteralFalseSqmExpression( expressionType );
}
private LiteralCharacterSqmExpression characterLiteral(String text) {
if ( text.length() > 1 ) {
// todo : or just treat it as a String literal?
throw new ParsingException( "Value for CHARACTER_LITERAL token was more than 1 character" );
}
return new LiteralCharacterSqmExpression(
text.charAt( 0 ),
resolveExpressableTypeBasic( Character.class )
);
}
private LiteralSqmExpression stringLiteral(String text) {
return new LiteralStringSqmExpression(
text,
resolveExpressableTypeBasic( String.class )
);
}
protected LiteralIntegerSqmExpression integerLiteral(String text) {
try {
final Integer value = Integer.valueOf( text );
return new LiteralIntegerSqmExpression(
value,
resolveExpressableTypeBasic( Integer.class )
);
}
catch (NumberFormatException e) {
throw new LiteralNumberFormatException(
"Unable to convert sqm literal [" + text + "] to Integer",
e
);
}
}
protected LiteralLongSqmExpression longLiteral(String text) {
final String originalText = text;
try {
if ( text.endsWith( "l" ) || text.endsWith( "L" ) ) {
text = text.substring( 0, text.length() - 1 );
}
final Long value = Long.valueOf( text );
return new LiteralLongSqmExpression(
value,
resolveExpressableTypeBasic( Long.class )
);
}
catch (NumberFormatException e) {
throw new LiteralNumberFormatException(
"Unable to convert sqm literal [" + originalText + "] to Long",
e
);
}
}
protected LiteralBigIntegerSqmExpression bigIntegerLiteral(String text) {
final String originalText = text;
try {
if ( text.endsWith( "bi" ) || text.endsWith( "BI" ) ) {
text = text.substring( 0, text.length() - 2 );
}
return new LiteralBigIntegerSqmExpression(
new BigInteger( text ),
resolveExpressableTypeBasic( BigInteger.class )
);
}
catch (NumberFormatException e) {
throw new LiteralNumberFormatException(
"Unable to convert sqm literal [" + originalText + "] to BigInteger",
e
);
}
}
protected LiteralFloatSqmExpression floatLiteral(String text) {
try {
return new LiteralFloatSqmExpression(
Float.valueOf( text ),
resolveExpressableTypeBasic( Float.class )
);
}
catch (NumberFormatException e) {
throw new LiteralNumberFormatException(
"Unable to convert sqm literal [" + text + "] to Float",
e
);
}
}
protected LiteralDoubleSqmExpression doubleLiteral(String text) {
try {
return new LiteralDoubleSqmExpression(
Double.valueOf( text ),
resolveExpressableTypeBasic( Double.class )
);
}
catch (NumberFormatException e) {
throw new LiteralNumberFormatException(
"Unable to convert sqm literal [" + text + "] to Double",
e
);
}
}
protected LiteralBigDecimalSqmExpression bigDecimalLiteral(String text) {
final String originalText = text;
try {
if ( text.endsWith( "bd" ) || text.endsWith( "BD" ) ) {
text = text.substring( 0, text.length() - 2 );
}
return new LiteralBigDecimalSqmExpression(
new BigDecimal( text ),
resolveExpressableTypeBasic( BigDecimal.class )
);
}
catch (NumberFormatException e) {
throw new LiteralNumberFormatException(
"Unable to convert sqm literal [" + originalText + "] to BigDecimal",
e
);
}
}
private BasicValuedExpressableType resolveExpressableTypeBasic(Class javaType) {
return parsingContext.getSessionFactory().getDomainMetamodel().resolveBasicType( javaType );
}
@Override
public Object visitParameterExpression(HqlParser.ParameterExpressionContext ctx) {
return ctx.parameter().accept( this );
}
@Override
public NamedParameterSqmExpression visitNamedParameter(HqlParser.NamedParameterContext ctx) {
final NamedParameterSqmExpression param = new NamedParameterSqmExpression(
ctx.identifier().getText(),
parameterDeclarationContextStack.getCurrent().isMultiValuedBindingAllowed()
);
parameterCollector.addParameter( param );
return param;
}
@Override
public PositionalParameterSqmExpression visitPositionalParameter(HqlParser.PositionalParameterContext ctx) {
if ( ctx.INTEGER_LITERAL() == null ) {
throw new SemanticException( "Encountered positional parameter which did not declare position (? instead of, e.g., ?1)" );
}
final PositionalParameterSqmExpression param = new PositionalParameterSqmExpression(
Integer.valueOf( ctx.INTEGER_LITERAL().getText() ),
parameterDeclarationContextStack.getCurrent().isMultiValuedBindingAllowed()
);
parameterCollector.addParameter( param );
return param;
}
@Override
public GenericFunctionSqmExpression visitJpaNonStandardFunction(HqlParser.JpaNonStandardFunctionContext ctx) {
final String functionName = ctx.nonStandardFunctionName().getText();
final List<SqmExpression> functionArguments = visitNonStandardFunctionArguments( ctx.nonStandardFunctionArguments() );
// todo : integrate some form of SqlFunction look-up using the ParsingContext so we can resolve the "type"
return new GenericFunctionSqmExpression( functionName, null, functionArguments );
}
@Override
public GenericFunctionSqmExpression visitNonStandardFunction(HqlParser.NonStandardFunctionContext ctx) {
if ( parsingContext.getSessionFactory().useStrictJpaCompliance() ) {
throw new StrictJpaComplianceViolation(
"Encountered non-compliant non-standard function call [" +
ctx.nonStandardFunctionName() + "], but strict JPQL compliance was requested; use JPA's FUNCTION(functionName[,...]) syntax name instead",
StrictJpaComplianceViolation.Type.FUNCTION_CALL
);
}
final String functionName = ctx.nonStandardFunctionName().getText();
final List<SqmExpression> functionArguments = visitNonStandardFunctionArguments( ctx.nonStandardFunctionArguments() );
// todo : integrate some form of SqlFunction look-up using the ParsingContext so we can resolve the "type"
return new GenericFunctionSqmExpression( functionName, null, functionArguments );
}
@Override
public List<SqmExpression> visitNonStandardFunctionArguments(HqlParser.NonStandardFunctionArgumentsContext ctx) {
final List<SqmExpression> arguments = new ArrayList<SqmExpression>();
for ( int i=0, x=ctx.expression().size(); i<x; i++ ) {
// we handle the final argument differently...
if ( i == x-1 ) {
arguments.add( visitFinalFunctionArgument( ctx.expression( i ) ) );
}
else {
arguments.add( (SqmExpression) ctx.expression( i ).accept( this ) );
}
}
return arguments;
}
private SqmExpression visitFinalFunctionArgument(HqlParser.ExpressionContext expression) {
// the final argument to a function may accept multi-value parameter (varargs),
// but only if we are operating in non-strict JPA mode
parameterDeclarationContextStack.push( () -> parsingContext.getSessionFactory().useStrictJpaCompliance() );
try {
return (SqmExpression) expression.accept( this );
}
finally {
parameterDeclarationContextStack.pop();
}
}
@Override
public AggregateFunctionSqmExpression visitAggregateFunction(HqlParser.AggregateFunctionContext ctx) {
return (AggregateFunctionSqmExpression) super.visitAggregateFunction( ctx );
}
@Override
public AvgFunctionSqmExpression visitAvgFunction(HqlParser.AvgFunctionContext ctx) {
final SqmExpression expr = (SqmExpression) ctx.expression().accept( this );
return new AvgFunctionSqmExpression(
expr,
ctx.DISTINCT() != null,
(BasicValuedExpressableType) expr.getExpressionType()
);
}
@Override
public CastFunctionSqmExpression visitCastFunction(HqlParser.CastFunctionContext ctx) {
return new CastFunctionSqmExpression(
(SqmExpression) ctx.expression().accept( this ),
parsingContext.getSessionFactory().getDomainMetamodel().resolveCastTargetType( ctx.dataType().IDENTIFIER().getText() )
);
}
@Override
public ConcatFunctionSqmExpression visitConcatFunction(HqlParser.ConcatFunctionContext ctx) {
final List<SqmExpression> arguments = new ArrayList<>();
for ( HqlParser.ExpressionContext argument : ctx.expression() ) {
arguments.add( (SqmExpression) argument.accept( this ) );
}
return new ConcatFunctionSqmExpression( (BasicValuedExpressableType) arguments.get( 0 ).getExpressionType(), arguments );
}
@Override
public AggregateFunctionSqmExpression visitCountFunction(HqlParser.CountFunctionContext ctx) {
final BasicValuedExpressableType longType = resolveExpressableTypeBasic( Long.class );
if ( ctx.ASTERISK() != null ) {
return new CountStarFunctionSqmExpression( ctx.DISTINCT() != null, longType );
}
else {
return new CountFunctionSqmExpression(
(SqmExpression) ctx.expression().accept( this ),
ctx.DISTINCT() != null,
longType
);
}
}
@Override
public MaxFunctionSqmExpression visitMaxFunction(HqlParser.MaxFunctionContext ctx) {
final SqmExpression expr = (SqmExpression) ctx.expression().accept( this );
return new MaxFunctionSqmExpression(
expr,
ctx.DISTINCT() != null,
(BasicValuedExpressableType) expr.getExpressionType()
);
}
@Override
public MinFunctionSqmExpression visitMinFunction(HqlParser.MinFunctionContext ctx) {
final SqmExpression expr = (SqmExpression) ctx.expression().accept( this );
return new MinFunctionSqmExpression(
expr,
ctx.DISTINCT() != null,
(BasicValuedExpressableType) expr.getExpressionType()
);
}
@Override
public SubstringFunctionSqmExpression visitSubstringFunction(HqlParser.SubstringFunctionContext ctx) {
final SqmExpression source = (SqmExpression) ctx.expression().accept( this );
final SqmExpression start = (SqmExpression) ctx.substringFunctionStartArgument().accept( this );
final SqmExpression length = ctx.substringFunctionLengthArgument() == null
? null
: (SqmExpression) ctx.substringFunctionLengthArgument().accept( this );
return new SubstringFunctionSqmExpression( (BasicValuedExpressableType) source.getExpressionType(), source, start, length );
}
@Override
public SumFunctionSqmExpression visitSumFunction(HqlParser.SumFunctionContext ctx) {
final SqmExpression expr = (SqmExpression) ctx.expression().accept( this );
return new SumFunctionSqmExpression(
expr,
ctx.DISTINCT() != null,
parsingContext.getSessionFactory()
.getDomainMetamodel()
.resolveSumFunctionType( (BasicValuedExpressableType) expr.getExpressionType() )
);
}
@Override
public TrimFunctionSqmExpression visitTrimFunction(HqlParser.TrimFunctionContext ctx) {
final SqmExpression source = (SqmExpression) ctx.expression().accept( this );
return new TrimFunctionSqmExpression(
(BasicValuedExpressableType) source.getExpressionType(),
visitTrimSpecification( ctx.trimSpecification() ),
visitTrimCharacter( ctx.trimCharacter() ),
source
);
}
@Override
public TrimFunctionSqmExpression.Specification visitTrimSpecification(HqlParser.TrimSpecificationContext ctx) {
if ( ctx.LEADING() != null ) {
return TrimFunctionSqmExpression.Specification.LEADING;
}
else if ( ctx.TRAILING() != null ) {
return TrimFunctionSqmExpression.Specification.TRAILING;
}
// JPA says the default is BOTH
return TrimFunctionSqmExpression.Specification.BOTH;
}
@Override
public LiteralCharacterSqmExpression visitTrimCharacter(HqlParser.TrimCharacterContext ctx) {
if ( ctx.CHARACTER_LITERAL() != null ) {
final String trimCharText = ctx.CHARACTER_LITERAL().getText();
if ( trimCharText.length() != 1 ) {
throw new SemanticException( "Expecting [trim character] for TRIM function to be single character, found : " + trimCharText );
}
return new LiteralCharacterSqmExpression(
trimCharText.charAt( 0 ),
resolveExpressableTypeBasic( Character.class )
);
}
if ( ctx.STRING_LITERAL() != null ) {
final String trimCharText = ctx.STRING_LITERAL().getText();
if ( trimCharText.length() != 1 ) {
throw new SemanticException( "Expecting [trim character] for TRIM function to be single character, found : " + trimCharText );
}
return new LiteralCharacterSqmExpression(
trimCharText.charAt( 0 ),
resolveExpressableTypeBasic( Character.class ));
}
// JPA says space is the default
return new LiteralCharacterSqmExpression(
' ',
resolveExpressableTypeBasic( Character.class )
);
}
@Override
public UpperFunctionSqmExpression visitUpperFunction(HqlParser.UpperFunctionContext ctx) {
final SqmExpression expression = (SqmExpression) ctx.expression().accept( this );
return new UpperFunctionSqmExpression(
(BasicValuedExpressableType) expression.getExpressionType(),
expression
);
}
@Override
public LowerFunctionSqmExpression visitLowerFunction(HqlParser.LowerFunctionContext ctx) {
// todo (6.0) : why pass both the expression and its expression-type?
// can't we just pass the expression?
final SqmExpression expression = (SqmExpression) ctx.expression().accept( this );
return new LowerFunctionSqmExpression(
(BasicValuedExpressableType) expression.getExpressionType(),
expression
);
}
@Override
public CollectionSizeSqmExpression visitCollectionSizeFunction(HqlParser.CollectionSizeFunctionContext ctx) {
final SqmPluralAttributeReference attributeBinding = asPluralAttribute( (SqmNavigableReference) ctx.path().accept( this ) );
return new CollectionSizeSqmExpression(
attributeBinding,
resolveExpressableTypeBasic( Integer.class )
);
}
@Override
public SqmCollectionIndexReference visitCollectionIndexFunction(HqlParser.CollectionIndexFunctionContext ctx) {
final String alias = ctx.identifier().getText();
final SqmNavigableReference binding = querySpecProcessingStateStack.getCurrent().getFromElementBuilder().getAliasRegistry().findFromElementByAlias( alias );
if ( binding == null || !SqmPluralAttributeReference.class.isInstance( binding ) ) {
// most likely a semantic problem, but not necessarily...
throw new ParsingException( "Could not resolve identification variable [" + alias + "] as plural-attribute, encountered : " + binding );
}
final SqmPluralAttributeReference attributeBinding = (SqmPluralAttributeReference) binding;
if ( !isIndexedPluralAttribute( attributeBinding ) ) {
throw new SemanticException(
"index() function can only be applied to identification variables which resolve to an " +
"indexed collection (map,list); specified identification variable [" + alias +
"] resolved to " + attributeBinding
);
}
return NavigableBindingHelper.createCollectionIndexBinding(
attributeBinding,
attributeBinding.getReferencedNavigable().getCollectionPersister().getIndexDescriptor()
);
}
private boolean isIndexedPluralAttribute(SqmPluralAttributeReference attributeBinding) {
return attributeBinding.getReferencedNavigable().getCollectionPersister().getCollectionClassification() == CollectionClassification.MAP
|| attributeBinding.getReferencedNavigable().getCollectionPersister().getCollectionClassification() == CollectionClassification.LIST;
}
private boolean isList(SqmPluralAttributeReference attributeBinding) {
return attributeBinding.getReferencedNavigable().getCollectionPersister().getCollectionClassification() == CollectionClassification.LIST;
}
@Override
public SqmMaxElementReferenceBasic visitMaxElementFunction(HqlParser.MaxElementFunctionContext ctx) {
if ( parsingContext.getSessionFactory().useStrictJpaCompliance() ) {
throw new StrictJpaComplianceViolation( StrictJpaComplianceViolation.Type.HQL_COLLECTION_FUNCTION );
}
return new SqmMaxElementReferenceBasic( asPluralAttribute( (SqmNavigableReference) ctx.path().accept( this ) ) );
}
@Override
public SqmMinElementReference visitMinElementFunction(HqlParser.MinElementFunctionContext ctx) {
if ( parsingContext.getSessionFactory().useStrictJpaCompliance() ) {
throw new StrictJpaComplianceViolation( StrictJpaComplianceViolation.Type.HQL_COLLECTION_FUNCTION );
}
final SqmPluralAttributeReference pluralAttributeBinding = asPluralAttribute( (SqmNavigableReference) ctx.path().accept( this ) );
switch ( pluralAttributeBinding.getReferencedNavigable().getCollectionPersister().getElementDescriptor().getClassification() ) {
case BASIC: {
return new SqmMinElementReferenceBasic( pluralAttributeBinding );
}
case EMBEDDABLE: {
return new SqmMinElementReferenceEmbedded( pluralAttributeBinding );
}
case ONE_TO_MANY:
case MANY_TO_MANY: {
return new SqmMinElementReferenceEntity( pluralAttributeBinding );
}
default: {
throw new NotYetImplementedException( "min-element function not yet supported for ANY elements" );
}
}
}
@Override
public SqmMaxIndexReference visitMaxIndexFunction(HqlParser.MaxIndexFunctionContext ctx) {
if ( parsingContext.getSessionFactory().useStrictJpaCompliance() ) {
throw new StrictJpaComplianceViolation( StrictJpaComplianceViolation.Type.HQL_COLLECTION_FUNCTION );
}
final SqmPluralAttributeReference attributeBinding = asPluralAttribute( (SqmNavigableReference) ctx.path().accept( this ) );
if ( !isIndexedPluralAttribute( attributeBinding ) ) {
throw new SemanticException(
"maxindex() function can only be applied to path expressions which resolve to an " +
"indexed collection (list,map); specified path [" + ctx.path().getText() +
"] resolved to " + attributeBinding.getReferencedNavigable()
);
}
switch ( attributeBinding.getReferencedNavigable().getCollectionPersister().getIndexDescriptor().getClassification() ) {
case BASIC: {
return new SqmMaxIndexReferenceBasic( attributeBinding );
}
case EMBEDDABLE: {
return new SqmMaxIndexReferenceEmbedded( attributeBinding );
}
case ONE_TO_MANY:
case MANY_TO_MANY: {
return new SqmMaxIndexReferenceEntity( attributeBinding );
}
default: {
throw new NotYetImplementedException( );
}
}
}
@Override
public SqmMinIndexReference visitMinIndexFunction(HqlParser.MinIndexFunctionContext ctx) {
if ( parsingContext.getSessionFactory().useStrictJpaCompliance() ) {
throw new StrictJpaComplianceViolation( StrictJpaComplianceViolation.Type.HQL_COLLECTION_FUNCTION );
}
final SqmPluralAttributeReference attributeBinding = asPluralAttribute( (SqmNavigableReference) ctx.path().accept( this ) );
if ( !isIndexedPluralAttribute( attributeBinding ) ) {
throw new SemanticException(
"minindex() function can only be applied to path expressions which resolve to an " +
"indexed collection (list,map); specified path [" + ctx.path().getText() +
"] resolved to " + attributeBinding.getReferencedNavigable()
);
}
switch ( attributeBinding.getReferencedNavigable().getCollectionPersister().getIndexDescriptor().getClassification() ) {
case BASIC: {
return new SqmMinIndexReferenceBasic( attributeBinding );
}
case EMBEDDABLE: {
return new SqmMinIndexReferenceEmbeddable( attributeBinding );
}
case ONE_TO_MANY:
case MANY_TO_MANY: {
return new SqmMinIndexReferenceEntity( attributeBinding );
}
default: {
throw new NotYetImplementedException( );
}
}
}
@Override
public SubQuerySqmExpression visitSubQueryExpression(HqlParser.SubQueryExpressionContext ctx) {
final SqmQuerySpec querySpec = visitQuerySpec( ctx.querySpec() );
return new SubQuerySqmExpression( querySpec, determineTypeDescriptor( querySpec.getSelectClause() ) );
}
private static ExpressableType determineTypeDescriptor(SqmSelectClause selectClause) {
if ( selectClause.getSelections().size() != 0 ) {
return null;
}
final SqmSelection selection = selectClause.getSelections().get( 0 );
return selection.getExpression().getExpressionType();
}
}