Java tutorial
/* * Hibernate, Relational Persistence for Idiomatic Java * * License: Apache License, Version 2.0 * See the LICENSE file in the root directory or visit http://www.apache.org/licenses/LICENSE-2.0 */ package org.hibernate.sqm.parser.hql.internal; 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 java.util.Map; import org.hibernate.sqm.domain.BasicType; import org.hibernate.sqm.domain.EntityType; import org.hibernate.sqm.domain.PluralAttribute; import org.hibernate.sqm.domain.PolymorphicEntityType; import org.hibernate.sqm.domain.Type; import org.hibernate.sqm.parser.LiteralNumberFormatException; import org.hibernate.sqm.parser.ParsingException; import org.hibernate.sqm.parser.SemanticException; import org.hibernate.sqm.StrictJpaComplianceViolation; import org.hibernate.sqm.parser.common.FromElementLocator; import org.hibernate.sqm.parser.common.ParameterDeclarationContext; import org.hibernate.sqm.parser.common.QuerySpecProcessingState; import org.hibernate.sqm.parser.common.QuerySpecProcessingStateDmlImpl; import org.hibernate.sqm.parser.common.QuerySpecProcessingStateStandardImpl; import org.hibernate.sqm.parser.common.Stack; import org.hibernate.sqm.parser.hql.internal.path.PathHelper; import org.hibernate.sqm.parser.hql.internal.path.PathResolver; import org.hibernate.sqm.parser.hql.internal.path.PathResolverBasicImpl; import org.hibernate.sqm.parser.hql.internal.path.PathResolverJoinAttributeImpl; import org.hibernate.sqm.parser.hql.internal.path.PathResolverJoinPredicateImpl; import org.hibernate.sqm.parser.common.ResolutionContext; import org.hibernate.sqm.parser.common.ExpressionTypeHelper; import org.hibernate.sqm.parser.common.FromElementBuilder; import org.hibernate.sqm.parser.common.ImplicitAliasGenerator; import org.hibernate.sqm.parser.common.ParsingContext; import org.hibernate.sqm.parser.hql.internal.antlr.HqlParser; import org.hibernate.sqm.parser.hql.internal.antlr.HqlParserBaseVisitor; import org.hibernate.sqm.path.AttributeBinding; import org.hibernate.sqm.path.AttributeBindingSource; import org.hibernate.sqm.path.Binding; import org.hibernate.sqm.query.SqmQuerySpec; import org.hibernate.sqm.query.SqmDeleteStatement; import org.hibernate.sqm.query.SqmInsertSelectStatement; import org.hibernate.sqm.query.JoinType; import org.hibernate.sqm.query.SqmSelectStatement; import org.hibernate.sqm.query.SqmStatement; import org.hibernate.sqm.query.SqmUpdateStatement; import org.hibernate.sqm.query.expression.function.AggregateFunctionSqmExpression; import org.hibernate.sqm.query.expression.AttributeReferenceSqmExpression; import org.hibernate.sqm.query.expression.function.AvgFunctionSqmExpression; import org.hibernate.sqm.query.expression.BinaryArithmeticSqmExpression; import org.hibernate.sqm.query.expression.CaseSearchedSqmExpression; import org.hibernate.sqm.query.expression.CoalesceSqmExpression; import org.hibernate.sqm.query.expression.CollectionIndexSqmExpression; import org.hibernate.sqm.query.expression.CollectionSizeSqmExpression; import org.hibernate.sqm.query.expression.CollectionValuePathSqmExpression; import org.hibernate.sqm.query.expression.ConcatSqmExpression; import org.hibernate.sqm.query.expression.ConstantEnumSqmExpression; import org.hibernate.sqm.query.expression.ConstantSqmExpression; import org.hibernate.sqm.query.expression.ConstantFieldSqmExpression; import org.hibernate.sqm.query.expression.function.CastFunctionSqmExpression; import org.hibernate.sqm.query.expression.function.ConcatFunctionSqmExpression; import org.hibernate.sqm.query.expression.function.CountFunctionSqmExpression; import org.hibernate.sqm.query.expression.function.CountStarFunctionSqmExpression; import org.hibernate.sqm.query.expression.EntityTypeSqmExpression; import org.hibernate.sqm.query.expression.SqmExpression; import org.hibernate.sqm.query.expression.function.GenericFunctionSqmExpression; import org.hibernate.sqm.query.expression.ImpliedTypeSqmExpression; import org.hibernate.sqm.query.expression.LiteralBigDecimalSqmExpression; import org.hibernate.sqm.query.expression.LiteralBigIntegerSqmExpression; import org.hibernate.sqm.query.expression.LiteralCharacterSqmExpression; import org.hibernate.sqm.query.expression.LiteralDoubleSqmExpression; import org.hibernate.sqm.query.expression.LiteralSqmExpression; import org.hibernate.sqm.query.expression.LiteralFalseSqmExpression; import org.hibernate.sqm.query.expression.LiteralFloatSqmExpression; import org.hibernate.sqm.query.expression.LiteralIntegerSqmExpression; import org.hibernate.sqm.query.expression.LiteralLongSqmExpression; import org.hibernate.sqm.query.expression.LiteralNullSqmExpression; import org.hibernate.sqm.query.expression.LiteralStringSqmExpression; import org.hibernate.sqm.query.expression.LiteralTrueSqmExpression; import org.hibernate.sqm.query.expression.MapEntrySqmExpression; import org.hibernate.sqm.query.expression.MapKeyPathSqmExpression; import org.hibernate.sqm.query.expression.MaxElementSqmExpression; import org.hibernate.sqm.query.expression.function.LowerFunctionSqmExpression; import org.hibernate.sqm.query.expression.function.MaxFunctionSqmExpression; import org.hibernate.sqm.query.expression.MaxIndexSqmExpression; import org.hibernate.sqm.query.expression.MinElementSqmExpression; import org.hibernate.sqm.query.expression.function.MinFunctionSqmExpression; import org.hibernate.sqm.query.expression.MinIndexSqmExpression; import org.hibernate.sqm.query.expression.NamedParameterSqmExpression; import org.hibernate.sqm.query.expression.NullifSqmExpression; import org.hibernate.sqm.query.expression.PluralAttributeIndexedReference; import org.hibernate.sqm.query.expression.PositionalParameterSqmExpression; import org.hibernate.sqm.query.expression.CaseSimpleSqmExpression; import org.hibernate.sqm.query.expression.SubQuerySqmExpression; import org.hibernate.sqm.query.expression.function.SubstringFunctionSqmExpression; import org.hibernate.sqm.query.expression.function.SumFunctionSqmExpression; import org.hibernate.sqm.query.expression.UnaryOperationSqmExpression; import org.hibernate.sqm.query.expression.function.TrimFunctionSqmExpression; import org.hibernate.sqm.query.expression.function.UpperFunctionSqmExpression; import org.hibernate.sqm.query.from.CrossJoinedFromElement; import org.hibernate.sqm.query.from.SqmFromClause; import org.hibernate.sqm.query.from.FromElement; import org.hibernate.sqm.query.from.FromElementSpace; import org.hibernate.sqm.query.from.JoinedFromElement; import org.hibernate.sqm.query.from.QualifiedAttributeJoinFromElement; import org.hibernate.sqm.query.from.QualifiedJoinedFromElement; import org.hibernate.sqm.query.from.RootEntityFromElement; import org.hibernate.sqm.query.internal.ParameterCollector; import org.hibernate.sqm.query.internal.SqmDeleteStatementImpl; import org.hibernate.sqm.query.internal.SqmInsertSelectStatementImpl; import org.hibernate.sqm.query.internal.SqmSelectStatementImpl; import org.hibernate.sqm.query.internal.SqmUpdateStatementImpl; import org.hibernate.sqm.query.order.OrderByClause; import org.hibernate.sqm.query.order.SortOrder; import org.hibernate.sqm.query.order.SortSpecification; import org.hibernate.sqm.query.predicate.AndSqmPredicate; import org.hibernate.sqm.query.predicate.BetweenSqmPredicate; import org.hibernate.sqm.query.predicate.EmptinessSqmPredicate; import org.hibernate.sqm.query.predicate.GroupedSqmPredicate; import org.hibernate.sqm.query.predicate.InSubQuerySqmPredicate; import org.hibernate.sqm.query.predicate.InListSqmPredicate; import org.hibernate.sqm.query.predicate.LikeSqmPredicate; import org.hibernate.sqm.query.predicate.MemberOfSqmPredicate; import org.hibernate.sqm.query.predicate.NegatableSqmPredicate; import org.hibernate.sqm.query.predicate.NegatedSqmPredicate; import org.hibernate.sqm.query.predicate.NullnessSqmPredicate; import org.hibernate.sqm.query.predicate.OrSqmPredicate; import org.hibernate.sqm.query.predicate.SqmPredicate; import org.hibernate.sqm.query.predicate.RelationalSqmPredicate; import org.hibernate.sqm.query.predicate.SqmWhereClause; import org.hibernate.sqm.query.select.SqmDynamicInstantiation; import org.hibernate.sqm.query.select.SqmDynamicInstantiationArgument; import org.hibernate.sqm.query.select.SqmSelectClause; import org.hibernate.sqm.query.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 { 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<PathResolver> pathResolverStack = new Stack<>(); private final Stack<ParameterDeclarationContext> parameterDeclarationContextStack = new Stack<>(); private boolean inWhereClause; private QuerySpecProcessingState currentQuerySpecProcessingState; 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.getConsumerContext().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())); if (ctx.orderByClause() != null) { pathResolverStack.push( new PathResolverBasicImpl(new OrderByResolutionContext(parsingContext, selectStatement))); try { selectStatement.applyOrderByClause(visitOrderByClause(ctx.orderByClause())); } finally { pathResolverStack.pop(); } } } finally { selectStatement.wrapUp(); } return selectStatement; } @Override public SqmQuerySpec visitQuerySpec(HqlParser.QuerySpecContext ctx) { currentQuerySpecProcessingState = new QuerySpecProcessingStateStandardImpl(parsingContext, currentQuerySpecProcessingState); pathResolverStack.push(new PathResolverBasicImpl(currentQuerySpecProcessingState)); 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(currentQuerySpecProcessingState.getFromClause()); } final SqmWhereClause whereClause; if (ctx.whereClause() != null) { whereClause = visitWhereClause(ctx.whereClause()); } else { whereClause = null; } return new SqmQuerySpec(currentQuerySpecProcessingState.getFromClause(), selectClause, whereClause); } finally { pathResolverStack.pop(); currentQuerySpecProcessingState = currentQuerySpecProcessingState.getParent(); } } 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(true); final FromElement root = fromClause.getFromElementSpaces().get(0).getRoot(); selectClause.addSelection(new SqmSelection(root)); return selectClause; } @Override public SqmSelectClause visitSelectClause(HqlParser.SelectClauseContext ctx) { final SqmSelectClause selectClause = new SqmSelectClause(ctx.DISTINCT() != null); for (HqlParser.SelectionContext selectionContext : ctx.selectionList().selection()) { selectClause.addSelection(visitSelection(selectionContext)); } return selectClause; } @Override public SqmSelection visitSelection(HqlParser.SelectionContext ctx) { final SqmSelection selection = new SqmSelection(visitSelectExpression(ctx.selectExpression()), interpretResultIdentifier(ctx.resultIdentifier())); currentQuerySpecProcessingState.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.getConsumerContext().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) { final BasicType<Map> mapType = parsingContext.getConsumerContext().getDomainMetamodel() .getBasicType(Map.class); dynamicInstantiation = SqmDynamicInstantiation.forMapInstantiation(); } else if (ctx.dynamicInstantiationTarget().LIST() != null) { final BasicType<List> listType = parsingContext.getConsumerContext().getDomainMetamodel() .getBasicType(List.class); dynamicInstantiation = SqmDynamicInstantiation.forListInstantiation(); } else { final String className = ctx.dynamicInstantiationTarget().dotIdentifierSequence().getText(); try { final Class targetJavaType = parsingContext.getConsumerContext().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 FromElement visitJpaSelectObjectSyntax(HqlParser.JpaSelectObjectSyntaxContext ctx) { final String alias = ctx.identifier().getText(); final FromElement fromElement = currentQuerySpecProcessingState.getFromElementBuilder().getAliasRegistry() .findFromElementByAlias(alias); if (fromElement == null) { throw new SemanticException( "Unable to resolve alias [" + alias + "] in selection [" + ctx.getText() + "]"); } return fromElement; } @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)); } private static class OrderByResolutionContext implements ResolutionContext, FromElementLocator { private final ParsingContext parsingContext; private final SqmSelectStatement selectStatement; public OrderByResolutionContext(ParsingContext parsingContext, SqmSelectStatement selectStatement) { this.parsingContext = parsingContext; this.selectStatement = selectStatement; } @Override public FromElement findFromElementByIdentificationVariable(String identificationVariable) { for (FromElementSpace fromElementSpace : selectStatement.getQuerySpec().getFromClause() .getFromElementSpaces()) { if (fromElementSpace.getRoot().getIdentificationVariable().equals(identificationVariable)) { return fromElementSpace.getRoot(); } for (JoinedFromElement joinedFromElement : fromElementSpace.getJoins()) { if (joinedFromElement.getIdentificationVariable().equals(identificationVariable)) { return joinedFromElement; } } } // otherwise there is none return null; } @Override public FromElement findFromElementExposingAttribute(String attributeName) { for (FromElementSpace fromElementSpace : selectStatement.getQuerySpec().getFromClause() .getFromElementSpaces()) { if (fromElementSpace.getRoot().resolveAttribute(attributeName) != null) { return fromElementSpace.getRoot(); } for (JoinedFromElement joinedFromElement : fromElementSpace.getJoins()) { if (joinedFromElement.resolveAttribute(attributeName) != null) { return joinedFromElement; } } } // otherwise there is none return null; } @Override public FromElementLocator getFromElementLocator() { return this; } @Override public FromElementBuilder getFromElementBuilder() { throw new SemanticException("order-by clause cannot define implicit joins"); } @Override public ParsingContext getParsingContext() { return parsingContext; } } @Override public OrderByClause visitOrderByClause(HqlParser.OrderByClauseContext ctx) { final OrderByClause orderByClause = new OrderByClause(); for (HqlParser.SortSpecificationContext sortSpecificationContext : ctx.sortSpecification()) { orderByClause.addSortSpecification(visitSortSpecification(sortSpecificationContext)); } return orderByClause; } @Override public SortSpecification 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 SortOrder 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 SortSpecification(sortExpression, collation, sortOrder); } private SortOrder interpretSortOrder(String value) { if (value == null) { return null; } if (value.equalsIgnoreCase("ascending") || value.equalsIgnoreCase("asc")) { return SortOrder.ASCENDING; } if (value.equalsIgnoreCase("descending") || value.equalsIgnoreCase("desc")) { return SortOrder.DESCENDING; } throw new SemanticException("Unknown sort order : " + value); } @Override public SqmDeleteStatement visitDeleteStatement(HqlParser.DeleteStatementContext ctx) { currentQuerySpecProcessingState = new QuerySpecProcessingStateDmlImpl(parsingContext); try { final RootEntityFromElement root = resolveDmlRootEntityReference(ctx.mainEntityPersisterReference()); final SqmDeleteStatementImpl deleteStatement = new SqmDeleteStatementImpl(root); parameterCollector = deleteStatement; pathResolverStack.push(new PathResolverBasicImpl(currentQuerySpecProcessingState)); try { deleteStatement.getWhereClause() .setPredicate((SqmPredicate) ctx.whereClause().predicate().accept(this)); } finally { pathResolverStack.pop(); deleteStatement.wrapUp(); } return deleteStatement; } finally { currentQuerySpecProcessingState = null; } } protected RootEntityFromElement resolveDmlRootEntityReference( HqlParser.MainEntityPersisterReferenceContext rootEntityContext) { final EntityType entityType = resolveEntityReference(rootEntityContext.dotIdentifierSequence()); 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, entityType.getName()); } final RootEntityFromElement root = new RootEntityFromElement(null, parsingContext.makeUniqueIdentifier(), alias, entityType); parsingContext.registerFromElementByUniqueId(root); currentQuerySpecProcessingState.getFromElementBuilder().getAliasRegistry().registerAlias(root); currentQuerySpecProcessingState.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.getConsumerContext().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) { currentQuerySpecProcessingState = new QuerySpecProcessingStateDmlImpl(parsingContext); try { final RootEntityFromElement root = resolveDmlRootEntityReference(ctx.mainEntityPersisterReference()); final SqmUpdateStatementImpl updateStatement = new SqmUpdateStatementImpl(root); pathResolverStack.push(new PathResolverBasicImpl(currentQuerySpecProcessingState)); parameterCollector = updateStatement; try { updateStatement.getWhereClause() .setPredicate((SqmPredicate) ctx.whereClause().predicate().accept(this)); for (HqlParser.AssignmentContext assignmentContext : ctx.setClause().assignment()) { final AttributeReferenceSqmExpression stateField = (AttributeReferenceSqmExpression) 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 { currentQuerySpecProcessingState = null; } } 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) { currentQuerySpecProcessingState = new QuerySpecProcessingStateDmlImpl(parsingContext); try { final EntityType entityType = resolveEntityReference( ctx.insertSpec().intoSpec().dotIdentifierSequence()); String alias = parsingContext.getImplicitAliasGenerator().buildUniqueImplicitAlias(); log.debugf("Generated implicit alias [%s] for INSERT target [%s]", alias, entityType.getName()); RootEntityFromElement root = new RootEntityFromElement(null, parsingContext.makeUniqueIdentifier(), alias, entityType); parsingContext.registerFromElementByUniqueId(root); currentQuerySpecProcessingState.getFromElementBuilder().getAliasRegistry().registerAlias(root); currentQuerySpecProcessingState.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(currentQuerySpecProcessingState)); try { insertStatement.setSelectQuery(visitQuerySpec(ctx.querySpec())); for (HqlParser.DotIdentifierSequenceContext stateFieldCtx : ctx.insertSpec().targetFieldsSpec() .dotIdentifierSequence()) { final AttributeReferenceSqmExpression stateField = (AttributeReferenceSqmExpression) pathResolverStack .getCurrent().resolvePath(splitPathParts(stateFieldCtx)); // todo : validate each resolved stateField... insertStatement.addInsertTargetStateField(stateField); } } finally { pathResolverStack.pop(); insertStatement.wrapUp(); } return insertStatement; } finally { currentQuerySpecProcessingState = null; } } private FromElementSpace currentFromElementSpace; @Override public Object visitFromElementSpace(HqlParser.FromElementSpaceContext ctx) { currentFromElementSpace = currentQuerySpecProcessingState.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); } FromElementSpace rtn = currentFromElementSpace; currentFromElementSpace = null; return rtn; } @Override public RootEntityFromElement visitFromElementSpaceRoot(HqlParser.FromElementSpaceRootContext ctx) { final EntityType entityType = resolveEntityReference( ctx.mainEntityPersisterReference().dotIdentifierSequence()); if (PolymorphicEntityType.class.isInstance(entityType)) { if (parsingContext.getConsumerContext().useStrictJpaCompliance()) { throw new StrictJpaComplianceViolation( "Encountered unmapped polymorphic reference [" + entityType.getName() + "], but strict JPQL compliance was requested", StrictJpaComplianceViolation.Type.UNMAPPED_POLYMORPHISM); } // todo : disallow in subqueries as well } return currentQuerySpecProcessingState.getFromElementBuilder().makeRootEntityFromElement( currentFromElementSpace, entityType, interpretIdentificationVariable(ctx.mainEntityPersisterReference().identificationVariableDef())); } private EntityType resolveEntityReference(HqlParser.DotIdentifierSequenceContext dotIdentifierSequenceContext) { final String entityName = dotIdentifierSequenceContext.getText(); final EntityType entityTypeDescriptor = parsingContext.getConsumerContext().getDomainMetamodel() .resolveEntityType(entityName); if (entityTypeDescriptor == null) { throw new SemanticException("Unresolved entity name : " + entityName); } return entityTypeDescriptor; } @Override public CrossJoinedFromElement visitCrossJoin(HqlParser.CrossJoinContext ctx) { final EntityType entityType = resolveEntityReference( ctx.mainEntityPersisterReference().dotIdentifierSequence()); if (PolymorphicEntityType.class.isInstance(entityType)) { throw new SemanticException( "Unmapped polymorphic references are only valid as sqm root, not in cross join : " + entityType.getName()); } return currentQuerySpecProcessingState.getFromElementBuilder().makeCrossJoinedFromElement( currentFromElementSpace, parsingContext.makeUniqueIdentifier(), entityType, interpretIdentificationVariable(ctx.mainEntityPersisterReference().identificationVariableDef())); } @Override public QualifiedJoinedFromElement visitJpaCollectionJoin(HqlParser.JpaCollectionJoinContext ctx) { pathResolverStack .push(new PathResolverJoinAttributeImpl(currentQuerySpecProcessingState, currentFromElementSpace, JoinType.INNER, interpretIdentificationVariable(ctx.identificationVariableDef()), false)); try { QualifiedJoinedFromElement joinedPath = (QualifiedJoinedFromElement) ctx.path().accept(this); if (joinedPath == null) { throw new ParsingException("Could not resolve JPA collection join path : " + ctx.getText()); } return joinedPath; } finally { pathResolverStack.pop(); } } @Override public QualifiedJoinedFromElement visitQualifiedJoin(HqlParser.QualifiedJoinContext ctx) { final JoinType 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("FULL OUTER joins are not yet supported : " + ctx.getText()); } joinType = JoinType.LEFT; } else { joinType = JoinType.INNER; } final String identificationVariable = interpretIdentificationVariable( ctx.qualifiedJoinRhs().identificationVariableDef()); pathResolverStack.push(new PathResolverJoinAttributeImpl(currentQuerySpecProcessingState, currentFromElementSpace, joinType, identificationVariable, ctx.FETCH() != null)); try { final QualifiedJoinedFromElement joinedFromElement; // Object because join-target might be either an EntityTypeExpression (... join Address a on ...) // or an attribute-join (... from p.address a on ...) final Object joinedPath = ctx.qualifiedJoinRhs().path().accept(this); if (joinedPath instanceof QualifiedJoinedFromElement) { joinedFromElement = (QualifiedJoinedFromElement) joinedPath; } else if (joinedPath instanceof EntityTypeSqmExpression) { joinedFromElement = currentQuerySpecProcessingState.getFromElementBuilder().buildEntityJoin( currentFromElementSpace, identificationVariable, ((EntityTypeSqmExpression) joinedPath).getExpressionType(), joinType); } else { throw new ParsingException("Unexpected qualifiedJoin.path resolution type : " + joinedPath); } currentJoinRhs = joinedFromElement; if (joinedPath == null) { throw new ParsingException("Could not resolve join path : " + ctx.qualifiedJoinRhs().getText()); } if (parsingContext.getConsumerContext().useStrictJpaCompliance()) { if (!ImplicitAliasGenerator.isImplicitAlias(joinedFromElement.getIdentificationVariable())) { if (QualifiedAttributeJoinFromElement.class.isInstance(joinedPath)) { if (QualifiedAttributeJoinFromElement.class.cast(joinedPath).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 QualifiedJoinedFromElement currentJoinRhs; @Override public SqmPredicate visitQualifiedJoinPredicate(HqlParser.QualifiedJoinPredicateContext ctx) { if (currentJoinRhs == null) { throw new ParsingException("Expecting join RHS to be set"); } pathResolverStack.push(new PathResolverJoinPredicateImpl(currentQuerySpecProcessingState, 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((SqmExpression) 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(RelationalSqmPredicate.Operator.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(RelationalSqmPredicate.Operator.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(RelationalSqmPredicate.Operator.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(RelationalSqmPredicate.Operator.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(RelationalSqmPredicate.Operator.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(RelationalSqmPredicate.Operator.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 Object 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 Object visitMemberOfPredicate(HqlParser.MemberOfPredicateContext ctx) { final Object pathResolution = ctx.path().accept(this); if (!AttributeReferenceSqmExpression.class.isInstance(pathResolution)) { throw new SemanticException( "Could not resolve path [" + ctx.path().getText() + "] as an attribute reference"); } final AttributeReferenceSqmExpression attributeReference = (AttributeReferenceSqmExpression) pathResolution; if (!PluralAttribute.class.isInstance(attributeReference.getBoundAttribute())) { throw new SemanticException("Path argument to MEMBER OF must be a collection"); } return new MemberOfSqmPredicate(attributeReference); } @Override public Object 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<SqmExpression>( 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 visitSimplePath(HqlParser.SimplePathContext ctx) { // SimplePath might represent any number of things final Binding binding = pathResolverStack.getCurrent() .resolvePath(splitPathParts(ctx.dotIdentifierSequence())); if (binding != null) { return binding; } final String pathText = ctx.getText(); try { final EntityType entityType = parsingContext.getConsumerContext().getDomainMetamodel() .resolveEntityType(pathText); if (entityType != null) { return new EntityTypeSqmExpression(entityType); } } catch (IllegalArgumentException ignore) { } // 5th level precedence : constant reference 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); } @Override public MapEntrySqmExpression visitMapEntryPath(HqlParser.MapEntryPathContext ctx) { final Binding pathResolution = (Binding) ctx.mapReference().path().accept(this); if (inWhereClause) { throw new SemanticException("entry() function may only be used in SELECT clauses; specified " + "path [" + ctx.mapReference().path().getText() + "] is used in WHERE clause"); } if (PluralAttribute.class.isInstance(pathResolution.getBoundModelType())) { final PluralAttribute pluralAttribute = (PluralAttribute) pathResolution.getBoundModelType(); if (pluralAttribute.getCollectionClassification() == PluralAttribute.CollectionClassification.MAP) { return new MapEntrySqmExpression(pathResolution.getBoundFromElementBinding().getFromElement(), pluralAttribute.getIndexType(), pluralAttribute.getElementType()); } } throw new SemanticException( "entry() function can only be applied to path expressions which resolve to a persistent Map; specified " + "path [" + ctx.mapReference().path().getText() + "] resolved to " + pathResolution.getBoundModelType()); } @Override public Binding visitIndexedPath(HqlParser.IndexedPathContext ctx) { final AttributeBinding pluralAttributeBinding = (AttributeBinding) ctx.path().accept(this); final SqmExpression indexExpression = (SqmExpression) ctx.expression().accept(this); // the source TypeDescriptor needs to be an indexed collection for this to be valid... if (!PluralAttribute.class.isInstance(pluralAttributeBinding.getBoundModelType())) { throw new SemanticException("Index operator only valid for indexed collections (maps, lists, arrays) : " + pluralAttributeBinding.getBoundModelType()); } final PluralAttribute pluralAttribute = (PluralAttribute) pluralAttributeBinding.getBoundModelType(); // todo : would be nice to validate the index's type against the Collection-index's type // but that requires "compatible type checking" rather than TypeDescriptor sameness (long versus int, e.g) final PluralAttributeIndexedReference indexedReference = new PluralAttributeIndexedReference( pluralAttributeBinding, indexExpression, // Ultimately the Type for this Expression is the same as the elements of the collection... pluralAttribute.getElementType()); 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 (indexedReference.getBoundModelType() == null) { throw new SemanticException(String.format(Locale.ROOT, "path root [%s] did not resolve to an attribute source", ctx.path().getText())); } return pathResolverStack.getCurrent().resolvePath(indexedReference, PathHelper.split(ctx.pathTerminal().getText())); } @Override public Binding visitCompoundPath(HqlParser.CompoundPathContext ctx) { final Binding root = (Binding) ctx.pathRoot().accept(this); log.debugf("Resolved CompoundPath.pathRoot [%s] : %s", ctx.pathRoot().getText(), root.asLoggableText()); if (ctx.pathTerminal() == null) { return root; } // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // 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 (!AttributeBindingSource.class.isInstance(root) || root.getBoundModelType() == null) { throw new SemanticException(String.format(Locale.ROOT, "path root [%s] did not resolve to an attribute source", ctx.pathRoot().getText())); } final AttributeBindingSource attributeBindingSource = (AttributeBindingSource) root; return pathResolverStack.getCurrent().resolvePath(attributeBindingSource, PathHelper.split(ctx.pathTerminal().getText())); } @Override public Object visitMapKeyPathRoot(HqlParser.MapKeyPathRootContext ctx) { final Binding pathResolution = (Binding) ctx.mapReference().path().accept(this); final PluralAttribute pluralAttribute = (PluralAttribute) pathResolution.getBoundModelType(); if (pluralAttribute.getCollectionClassification() != PluralAttribute.CollectionClassification.MAP) { throw new SemanticException( "key() function can only be applied to path expressions which resolve to a persistent Map; " + "specified path [" + ctx.mapReference().path().getText() + "] resolved to " + pathResolution.getBoundModelType()); } return new MapKeyPathSqmExpression(pathResolution.getBoundFromElementBinding().getFromElement(), pluralAttribute.getIndexType()); } @Override public CollectionValuePathSqmExpression visitCollectionValuePathRoot( HqlParser.CollectionValuePathRootContext ctx) { final Binding pathResolution = visitCollectionReference(ctx.collectionReference()); if (!QualifiedAttributeJoinFromElement.class.isInstance(pathResolution)) { throw new SemanticException( "value() function can only be applied to path expressions which resolve to a plural attribute; specified " + "path [" + ctx.collectionReference().path().getText() + "] resolved to " + pathResolution.getClass().getName()); } final QualifiedAttributeJoinFromElement attributeBinding = (QualifiedAttributeJoinFromElement) pathResolution; if (!PluralAttribute.class.isInstance(attributeBinding.getBoundAttribute())) { throw new SemanticException( "value() function can only be applied to path expressions which resolve to a collection; specified " + "path [" + ctx.collectionReference().path().getText() + "] resolved to " + pathResolution); } final PluralAttribute collectionReference = (PluralAttribute) attributeBinding.getBoundAttribute(); if (parsingContext.getConsumerContext().useStrictJpaCompliance()) { if (collectionReference.getCollectionClassification() != PluralAttribute.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 " + pathResolution, StrictJpaComplianceViolation.Type.VALUE_FUNCTION_ON_NON_MAP); } } return new CollectionValuePathSqmExpression(attributeBinding, collectionReference.getElementType()); } @Override public Binding visitCollectionReference(HqlParser.CollectionReferenceContext ctx) { return toPluralAttributeBinding(ctx.path()); } protected Binding toPluralAttributeBinding(HqlParser.PathContext ctx) { final Binding binding = (Binding) ctx.accept(this); if (!PluralAttribute.class.isInstance(binding.getBoundModelType())) { throw new SemanticException("Expecting a collection (plural attribute) reference, but specified path [" + ctx.getText() + "] resolved to " + binding); } return binding; } @Override public Binding visitMapReference(HqlParser.MapReferenceContext ctx) { final Binding pathResolution = toPluralAttributeBinding(ctx.path()); final PluralAttribute pluralAttribute = (PluralAttribute) pathResolution.getBoundModelType(); if (pluralAttribute.getCollectionClassification() != PluralAttribute.CollectionClassification.MAP) { throw new SemanticException( "Expecting a persistent-Map (plural attribute) reference, but specified path [" + ctx.path().getText() + "] resolved to " + pathResolution); } return pathResolution; } @Override public Binding visitTreatedPathRoot(HqlParser.TreatedPathRootContext ctx) { final String treatAsName = ctx.dotIdentifierSequence().get(1).getText(); final EntityType treatAsTypeDescriptor = parsingContext.getConsumerContext().getDomainMetamodel() .resolveEntityType(treatAsName); if (treatAsTypeDescriptor == null) { throw new SemanticException("TREAT-AS target type [" + treatAsName + "] did not reference an entity"); } return pathResolverStack.getCurrent().resolvePath(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.getConsumerContext().classByName(className); if (clazz.isEnum()) { try { return new ConstantEnumSqmExpression(Enum.valueOf(clazz, fieldName), parsingContext.getConsumerContext().getDomainMetamodel().getBasicType(clazz)); } 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.get(null), parsingContext.getConsumerContext().getDomainMetamodel().getBasicType(field.getType())); } 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, ExpressionTypeHelper.resolveArithmeticType((BasicType) firstOperand.getExpressionType(), (BasicType) secondOperand.getExpressionType(), parsingContext.getConsumerContext(), false)); } @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, ExpressionTypeHelper.resolveArithmeticType((BasicType) firstOperand.getExpressionType(), (BasicType) secondOperand.getExpressionType(), parsingContext.getConsumerContext(), false)); } @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, ExpressionTypeHelper.resolveArithmeticType((BasicType) firstOperand.getExpressionType(), (BasicType) secondOperand.getExpressionType(), parsingContext.getConsumerContext(), false)); } @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, ExpressionTypeHelper.resolveArithmeticType((BasicType) firstOperand.getExpressionType(), (BasicType) secondOperand.getExpressionType(), parsingContext.getConsumerContext(), true)); } @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, ExpressionTypeHelper.resolveArithmeticType((BasicType) firstOperand.getExpressionType(), (BasicType) secondOperand.getExpressionType(), parsingContext.getConsumerContext(), false)); } @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(0).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 BasicType<Boolean> type = parsingContext.getConsumerContext().getDomainMetamodel() .getBasicType(Boolean.class); return value ? new LiteralTrueSqmExpression(type) : new LiteralFalseSqmExpression(type); } 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), parsingContext.getConsumerContext().getDomainMetamodel().getBasicType(Character.class)); } private LiteralSqmExpression stringLiteral(String text) { return new LiteralStringSqmExpression(text, parsingContext.getConsumerContext().getDomainMetamodel().getBasicType(String.class)); } protected LiteralIntegerSqmExpression integerLiteral(String text) { try { final Integer value = Integer.valueOf(text); return new LiteralIntegerSqmExpression(value, parsingContext.getConsumerContext().getDomainMetamodel().getBasicType(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, parsingContext.getConsumerContext().getDomainMetamodel().getBasicType(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), parsingContext.getConsumerContext().getDomainMetamodel().getBasicType(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), parsingContext.getConsumerContext().getDomainMetamodel().getBasicType(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), parsingContext.getConsumerContext().getDomainMetamodel().getBasicType(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), parsingContext.getConsumerContext().getDomainMetamodel().getBasicType(BigDecimal.class)); } catch (NumberFormatException e) { throw new LiteralNumberFormatException( "Unable to convert sqm literal [" + originalText + "] to BigDecimal", e); } } @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) { 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.getConsumerContext().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.getConsumerContext().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, (BasicType) expr.getExpressionType()); } @Override public CastFunctionSqmExpression visitCastFunction(HqlParser.CastFunctionContext ctx) { return new CastFunctionSqmExpression((SqmExpression) ctx.expression().accept(this), parsingContext.getConsumerContext().getDomainMetamodel() .resolveCastTargetType(ctx.dataType().IDENTIFIER().getText())); } @Override public ConcatFunctionSqmExpression visitConcatFunction(HqlParser.ConcatFunctionContext ctx) { final List<SqmExpression> arguments = new ArrayList<SqmExpression>(); for (HqlParser.ExpressionContext argument : ctx.expression()) { arguments.add((SqmExpression) argument.accept(this)); } return new ConcatFunctionSqmExpression((BasicType) arguments.get(0).getExpressionType(), arguments); } @Override public AggregateFunctionSqmExpression visitCountFunction(HqlParser.CountFunctionContext ctx) { final BasicType longType = parsingContext.getConsumerContext().getDomainMetamodel() .getBasicType(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, (BasicType) expr.getExpressionType()); } @Override public MinFunctionSqmExpression visitMinFunction(HqlParser.MinFunctionContext ctx) { final SqmExpression expr = (SqmExpression) ctx.expression().accept(this); return new MinFunctionSqmExpression(expr, ctx.DISTINCT() != null, (BasicType) 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((BasicType) 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, ExpressionTypeHelper.resolveSingleNumericType((BasicType) expr.getExpressionType(), parsingContext.getConsumerContext())); } @Override public TrimFunctionSqmExpression visitTrimFunction(HqlParser.TrimFunctionContext ctx) { final SqmExpression source = (SqmExpression) ctx.expression().accept(this); return new TrimFunctionSqmExpression((BasicType) 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), parsingContext.getConsumerContext().getDomainMetamodel().getBasicType(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), parsingContext.getConsumerContext().getDomainMetamodel().getBasicType(Character.class)); } // JPA says space is the default return new LiteralCharacterSqmExpression(' ', parsingContext.getConsumerContext().getDomainMetamodel().getBasicType(Character.class)); } @Override public UpperFunctionSqmExpression visitUpperFunction(HqlParser.UpperFunctionContext ctx) { final SqmExpression expression = (SqmExpression) ctx.expression().accept(this); return new UpperFunctionSqmExpression((BasicType) expression.getExpressionType(), expression); } @Override public LowerFunctionSqmExpression visitLowerFunction(HqlParser.LowerFunctionContext ctx) { final SqmExpression expression = (SqmExpression) ctx.expression().accept(this); return new LowerFunctionSqmExpression((BasicType) expression.getExpressionType(), expression); } @Override public CollectionSizeSqmExpression visitCollectionSizeFunction(HqlParser.CollectionSizeFunctionContext ctx) { final Binding pathResolution = (Binding) ctx.path().accept(this); if (!AttributeBinding.class.isInstance(pathResolution)) { throw new SemanticException( "size() function can only be applied to path expressions which resolve to an attribute; specified " + "path [" + ctx.path().getText() + "] resolved to " + pathResolution.getClass().getName()); } final AttributeBinding attributeBinding = (AttributeBinding) pathResolution; if (!PluralAttribute.class.isInstance(attributeBinding.getBoundModelType())) { throw new SemanticException( "size() function can only be applied to path expressions which resolve to a collection; specified " + "path [" + ctx.path().getText() + "] resolved to " + pathResolution); } return new CollectionSizeSqmExpression(attributeBinding, parsingContext.getConsumerContext().getDomainMetamodel().getBasicType(Long.class)); } @Override public CollectionIndexSqmExpression visitCollectionIndexFunction(HqlParser.CollectionIndexFunctionContext ctx) { final String alias = ctx.identifier().getText(); final FromElement fromElement = currentQuerySpecProcessingState.getFromElementBuilder().getAliasRegistry() .findFromElementByAlias(alias); if (!PluralAttribute.class.isInstance(fromElement.getBoundModelType())) { throw new SemanticException( "index() function can only be applied to identification variables which resolve to a collection; specified " + "identification variable [" + alias + "] resolved to " + fromElement.getBoundModelType()); } final PluralAttribute collectionDescriptor = (PluralAttribute) fromElement.getBoundModelType(); if (collectionDescriptor.getCollectionClassification() != PluralAttribute.CollectionClassification.MAP && collectionDescriptor .getCollectionClassification() != PluralAttribute.CollectionClassification.LIST) { 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 " + collectionDescriptor); } return new CollectionIndexSqmExpression(fromElement, collectionDescriptor.getIndexType()); } @Override public MaxElementSqmExpression visitMaxElementFunction(HqlParser.MaxElementFunctionContext ctx) { if (parsingContext.getConsumerContext().useStrictJpaCompliance()) { throw new StrictJpaComplianceViolation(StrictJpaComplianceViolation.Type.HQL_COLLECTION_FUNCTION); } final Binding pathResolution = (Binding) ctx.path().accept(this); if (!PluralAttribute.class.isInstance(pathResolution.getBoundModelType())) { throw new SemanticException( "maxelement() function can only be applied to path expressions which resolve to a " + "collection; specified path [" + ctx.path().getText() + "] resolved to " + pathResolution.getBoundModelType()); } final PluralAttribute pluralAttribute = (PluralAttribute) pathResolution.getBoundModelType(); return new MaxElementSqmExpression(pathResolution.getBoundFromElementBinding().getFromElement(), pluralAttribute.getElementType()); } @Override public MinElementSqmExpression visitMinElementFunction(HqlParser.MinElementFunctionContext ctx) { if (parsingContext.getConsumerContext().useStrictJpaCompliance()) { throw new StrictJpaComplianceViolation(StrictJpaComplianceViolation.Type.HQL_COLLECTION_FUNCTION); } final Binding pathResolution = (Binding) ctx.path().accept(this); if (!PluralAttribute.class.isInstance(pathResolution.getBoundModelType())) { throw new SemanticException( "minelement() function can only be applied to path expressions which resolve to a " + "collection; specified path [" + ctx.path().getText() + "] resolved to " + pathResolution.getBoundModelType()); } final PluralAttribute pluralAttribute = (PluralAttribute) pathResolution.getBoundModelType(); return new MinElementSqmExpression(pathResolution.getBoundFromElementBinding().getFromElement(), pluralAttribute.getElementType()); } @Override public MaxIndexSqmExpression visitMaxIndexFunction(HqlParser.MaxIndexFunctionContext ctx) { if (parsingContext.getConsumerContext().useStrictJpaCompliance()) { throw new StrictJpaComplianceViolation(StrictJpaComplianceViolation.Type.HQL_COLLECTION_FUNCTION); } final Binding pathResolution = (Binding) ctx.path().accept(this); if (PluralAttribute.class.isInstance(pathResolution.getBoundModelType())) { final PluralAttribute pluralAttribute = (PluralAttribute) pathResolution.getBoundModelType(); if (pluralAttribute.getCollectionClassification() == PluralAttribute.CollectionClassification.LIST) { return new MaxIndexSqmExpression(pathResolution.getBoundFromElementBinding().getFromElement(), parsingContext.getConsumerContext().getDomainMetamodel().getBasicType(Integer.class)); } else if (pluralAttribute .getCollectionClassification() == PluralAttribute.CollectionClassification.MAP) { return new MaxIndexSqmExpression(pathResolution.getBoundFromElementBinding().getFromElement(), pluralAttribute.getIndexType()); } } 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 " + pathResolution.getBoundModelType()); } @Override public MinIndexSqmExpression visitMinIndexFunction(HqlParser.MinIndexFunctionContext ctx) { if (parsingContext.getConsumerContext().useStrictJpaCompliance()) { throw new StrictJpaComplianceViolation(StrictJpaComplianceViolation.Type.HQL_COLLECTION_FUNCTION); } final Binding pathResolution = (Binding) ctx.path().accept(this); if (PluralAttribute.class.isInstance(pathResolution.getBoundModelType())) { final PluralAttribute pluralAttribute = (PluralAttribute) pathResolution.getBoundModelType(); if (pluralAttribute.getCollectionClassification() == PluralAttribute.CollectionClassification.LIST) { return new MinIndexSqmExpression(pathResolution.getBoundFromElementBinding().getFromElement(), parsingContext.getConsumerContext().getDomainMetamodel().getBasicType(Integer.class)); } else if (pluralAttribute .getCollectionClassification() == PluralAttribute.CollectionClassification.MAP) { return new MinIndexSqmExpression(pathResolution.getBoundFromElementBinding().getFromElement(), pluralAttribute.getIndexType()); } } 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 " + pathResolution.getBoundModelType()); } @Override public SubQuerySqmExpression visitSubQueryExpression(HqlParser.SubQueryExpressionContext ctx) { final SqmQuerySpec querySpec = visitQuerySpec(ctx.querySpec()); return new SubQuerySqmExpression(querySpec, determineTypeDescriptor(querySpec.getSelectClause())); } private static Type determineTypeDescriptor(SqmSelectClause selectClause) { if (selectClause.getSelections().size() != 0) { return null; } final SqmSelection selection = selectClause.getSelections().get(0); return selection.getExpression().getExpressionType(); } }