/* * 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.criteria; import java.math.BigDecimal; import java.math.BigInteger; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.persistence.criteria.CriteriaDelete; import javax.persistence.criteria.CriteriaQuery; import javax.persistence.criteria.CriteriaUpdate; import javax.persistence.criteria.Fetch; import javax.persistence.criteria.FetchParent; import javax.persistence.criteria.Join; import javax.persistence.criteria.JoinType; import org.hibernate.internal.util.collections.Stack; import org.hibernate.persister.queryable.spi.BasicValuedExpressableType; import org.hibernate.persister.queryable.spi.ExpressableType; import org.hibernate.query.sqm.NotYetImplementedException; import org.hibernate.query.sqm.ParsingException; import org.hibernate.query.sqm.QueryException; import org.hibernate.query.sqm.produce.internal.QuerySpecProcessingStateDmlImpl; import org.hibernate.query.sqm.produce.internal.QuerySpecProcessingStateStandardImpl; import org.hibernate.query.sqm.produce.spi.ParsingContext; import org.hibernate.query.sqm.produce.spi.QuerySpecProcessingState; import org.hibernate.query.sqm.produce.spi.criteria.CriteriaVisitor; import org.hibernate.query.sqm.produce.spi.criteria.JpaCriteriaDelete; import org.hibernate.query.sqm.produce.spi.criteria.JpaCriteriaQuery; import org.hibernate.query.sqm.produce.spi.criteria.JpaCriteriaUpdate; import org.hibernate.query.sqm.produce.spi.criteria.JpaExpression; import org.hibernate.query.sqm.produce.spi.criteria.JpaOrder; import org.hibernate.query.sqm.produce.spi.criteria.JpaPredicate; import org.hibernate.query.sqm.produce.spi.criteria.JpaQuerySpec; import org.hibernate.query.sqm.produce.spi.criteria.JpaSubquery; import org.hibernate.query.sqm.produce.spi.criteria.JpaUpdateAssignment; import org.hibernate.query.sqm.produce.spi.criteria.from.JpaAttributeJoin; import org.hibernate.query.sqm.produce.spi.criteria.from.JpaFetch; import org.hibernate.query.sqm.produce.spi.criteria.from.JpaFrom; import org.hibernate.query.sqm.produce.spi.criteria.from.JpaRoot; import org.hibernate.query.sqm.produce.spi.criteria.path.JpaAttributePath; import org.hibernate.query.sqm.produce.spi.criteria.path.JpaPath; import org.hibernate.query.sqm.produce.spi.criteria.path.JpaPluralAttributePath; import org.hibernate.query.sqm.produce.spi.criteria.path.JpaSingularAttributePath; import org.hibernate.query.sqm.tree.SqmDeleteStatement; 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.SqmUpdateStatement; import org.hibernate.query.sqm.tree.expression.BinaryArithmeticSqmExpression; import org.hibernate.query.sqm.tree.expression.CoalesceSqmExpression; import org.hibernate.query.sqm.tree.expression.ConcatSqmExpression; import org.hibernate.query.sqm.tree.expression.ConstantEnumSqmExpression; import org.hibernate.query.sqm.tree.expression.EntityTypeLiteralSqmExpression; 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.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.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.SqmNavigableReference; 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.SqmSingularAttributeReference; 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.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.MaxFunctionSqmExpression; import org.hibernate.query.sqm.tree.expression.function.MinFunctionSqmExpression; import org.hibernate.query.sqm.tree.expression.function.SumFunctionSqmExpression; import org.hibernate.query.sqm.tree.from.SqmAttributeJoin; import org.hibernate.query.sqm.tree.from.SqmFromClause; import org.hibernate.query.sqm.tree.from.SqmFromElementSpace; import org.hibernate.query.sqm.tree.from.SqmRoot; import org.hibernate.query.sqm.tree.internal.SqmDeleteStatementImpl; 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.BooleanExpressionSqmPredicate; import org.hibernate.query.sqm.tree.predicate.EmptinessSqmPredicate; 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.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.SqmWhereClause; import org.hibernate.query.sqm.tree.select.SqmDynamicInstantiation; import org.hibernate.query.sqm.tree.select.SqmSelectClause; import org.hibernate.query.sqm.tree.select.SqmSelection; /** * @author Steve Ebersole */ public class CriteriaInterpreter implements CriteriaVisitor { // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // top level statement visitation public static SqmSelectStatement interpretSelectCriteria(CriteriaQuery criteria, ParsingContext parsingContext) { if ( !JpaCriteriaQuery.class.isInstance( criteria ) ) { throw new IllegalArgumentException( "CriteriaQuery to interpret must implement JpaCriteriaQuery" ); } final JpaCriteriaQuery jpaCriteriaQuery = (JpaCriteriaQuery) criteria; final CriteriaInterpreter interpreter = new CriteriaInterpreter( parsingContext ); final SqmSelectStatementImpl selectStatement = new SqmSelectStatementImpl(); selectStatement.applyQuerySpec( interpreter.visitQuerySpec( jpaCriteriaQuery.getQuerySpec() ) ); return selectStatement; } public static <E> SqmDeleteStatement interpretDeleteCriteria(CriteriaDelete<E> criteria, ParsingContext parsingContext) { if ( !JpaCriteriaDelete.class.isInstance( criteria ) ) { throw new IllegalArgumentException( "CriteriaDelete to interpret must implement SqmDeleteStatement" ); } final CriteriaInterpreter interpreter = new CriteriaInterpreter( parsingContext ); return interpreter.visitDeleteCriteria( (JpaCriteriaDelete<E>) criteria ); } public static <E> SqmUpdateStatement interpretUpdateCriteria(CriteriaUpdate<E> criteria, ParsingContext parsingContext) { if ( !JpaCriteriaUpdate.class.isInstance( criteria ) ) { throw new IllegalArgumentException( "CriteriaUpdate to interpret must implement org.hibernate.sqm.query.JpaCriteriaUpdate" ); } final CriteriaInterpreter interpreter = new CriteriaInterpreter( parsingContext ); return interpreter.visitUpdateCriteria( (JpaCriteriaUpdate<E>) criteria ); } // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // visitation private final ParsingContext parsingContext; private final Stack<QuerySpecProcessingState> querySpecProcessingStateStack = new Stack<>(); private CriteriaInterpreter(ParsingContext parsingContext) { this.parsingContext = parsingContext; } public ParsingContext getParsingContext() { return parsingContext; } private <E> SqmDeleteStatement visitDeleteCriteria(JpaCriteriaDelete<E> jpaCriteria) { final QuerySpecProcessingStateDmlImpl dmlProcessingState = new QuerySpecProcessingStateDmlImpl( parsingContext ); querySpecProcessingStateStack.push( dmlProcessingState ); try { final SqmRoot entityToDelete = dmlProcessingState.getFromElementBuilder().makeRootEntityFromElement( dmlProcessingState.getDmlFromElementSpace(), jpaCriteria.getRoot().getEntityType(), interpretAlias( jpaCriteria.getRoot().getAlias() ) ); final SqmDeleteStatementImpl sqmStatement = new SqmDeleteStatementImpl( entityToDelete ); if ( jpaCriteria.getRestriction() != null ) { sqmStatement.getWhereClause().setPredicate( jpaCriteria.getRestriction().visitPredicate( this ) ); } return sqmStatement; } finally { querySpecProcessingStateStack.pop(); } } private <E> SqmUpdateStatement visitUpdateCriteria(JpaCriteriaUpdate<E> jpaCriteria) { final QuerySpecProcessingStateDmlImpl dmlProcessingState = new QuerySpecProcessingStateDmlImpl( parsingContext ); querySpecProcessingStateStack.push( dmlProcessingState ); try { final SqmRoot entityToUpdate = dmlProcessingState.getFromElementBuilder().makeRootEntityFromElement( dmlProcessingState.getDmlFromElementSpace(), jpaCriteria.getRoot().getEntityType(), interpretAlias( jpaCriteria.getRoot().getAlias() ) ); final SqmUpdateStatementImpl sqmStatement = new SqmUpdateStatementImpl( entityToUpdate ); for ( JpaUpdateAssignment assignment : jpaCriteria.getAssignments() ) { sqmStatement.getSetClause().addAssignment( (SqmSingularAttributeReference) resolveNavigableBinding( entityToUpdate.getBinding(), assignment.getTargetAttributePath() ), assignment.getUpdatedValue().visitExpression( this ) ); } if ( jpaCriteria.getRestriction() != null ) { sqmStatement.getWhereClause().setPredicate( jpaCriteria.getRestriction().visitPredicate( this ) ); } return sqmStatement; } finally { querySpecProcessingStateStack.pop(); } } private final Map<JpaPath,SqmNavigableReference> jpaPathResolutionMap = new HashMap<>(); private SqmNavigableReference resolvePath(JpaPath jpaPath) { final SqmNavigableReference existing = jpaPathResolutionMap.get( jpaPath ); if ( existing != null ) { return existing; } if ( jpaPath instanceof JpaFrom ) { return resolveJpaFrom0( (JpaFrom) jpaPath ); } if ( jpaPath instanceof JpaAttributePath ) { return resolveJpaAttributePath0( (JpaAttributePath) jpaPath ); } throw new ParsingException( "Could not determine how to resolve JpaPath : " + jpaPath ); } private SqmNavigableReference resolveJpaFrom(JpaFrom jpaFrom) { final SqmNavigableReference existing = jpaPathResolutionMap.get( jpaFrom ); if ( existing != null ) { return existing; } return resolveJpaFrom0( jpaFrom ); } private SqmNavigableReference resolveJpaFrom0(JpaFrom jpaFrom) { if ( jpaFrom instanceof JpaRoot ) { return makeSqmRoot( querySpecProcessingStateStack.getCurrent().getFromClause(), (JpaRoot<?>) jpaFrom ).getBinding(); } else if ( jpaFrom instanceof JpaAttributeJoin ) { final JpaAttributeJoin jpaAttributeJoin = (JpaAttributeJoin) jpaFrom; final SqmNavigableReference parentPathBinding = resolvePath( jpaAttributeJoin.getParentPath() ); return makeSqmAttributeJoin( parentPathBinding.getSourceReference(), parentPathBinding.getSourceReference().getExportedFromElement().getContainingSpace(), jpaAttributeJoin ).getBinding(); } throw new ParsingException( "Could not determine how to resolve JpaFrom : " + jpaFrom ); } private SqmNavigableReference resolveJpaAttributePath(JpaAttributePath jpaPath) { final SqmNavigableReference existing = jpaPathResolutionMap.get( jpaPath ); if ( existing != null ) { return existing; } return resolveJpaAttributePath0( jpaPath ); } private SqmNavigableReference resolveJpaAttributePath0(JpaAttributePath jpaPath) { if ( jpaPath instanceof JpaSingularAttributePath ) { return resolveSingularAttributePath0( (JpaSingularAttributePath) jpaPath ); } if ( jpaPath instanceof JpaPluralAttributePath ) { return resolvePluralAttributePath0( (JpaPluralAttributePath) jpaPath ); } throw new ParsingException( "Could not determine how to resolve JpaAttributePath : " + jpaPath ); } private SqmSingularAttributeReference resolveSingularAttributePath(JpaSingularAttributePath jpaAttributePath) { final SqmNavigableReference existing = jpaPathResolutionMap.get( jpaAttributePath ); if ( existing != null ) { return (SqmSingularAttributeReference) existing; } return resolveSingularAttributePath0( jpaAttributePath ); } private SqmSingularAttributeReference resolveSingularAttributePath0(JpaSingularAttributePath jpaAttributePath) { final SqmNavigableSourceReference attributeSourceBinding = (SqmNavigableSourceReference) resolvePath( jpaAttributePath.getParentPath() ); final SqmSingularAttributeReference attributeBinding = (SqmSingularAttributeReference) parsingContext.findOrCreateNavigableBinding( attributeSourceBinding, jpaAttributePath.getNavigable().getAttributeName() ); jpaPathResolutionMap.put( jpaAttributePath, attributeBinding ); return attributeBinding; } private SqmPluralAttributeReference resolvePluralAttributePath(JpaPluralAttributePath jpaAttributePath) { final SqmNavigableSourceReference existing = (SqmNavigableSourceReference) jpaPathResolutionMap.get( jpaAttributePath ); if ( existing != null ) { return (SqmPluralAttributeReference) existing; } return resolvePluralAttributePath0( jpaAttributePath ); } private SqmPluralAttributeReference resolvePluralAttributePath0(JpaPluralAttributePath jpaAttributePath) { final SqmNavigableSourceReference attributeSourceBinding = (SqmNavigableSourceReference) resolvePath( jpaAttributePath.getParentPath() ); final SqmPluralAttributeReference attributeBinding = (SqmPluralAttributeReference) parsingContext.findOrCreateNavigableBinding( attributeSourceBinding, jpaAttributePath.getNavigable().getAttributeName() ); jpaPathResolutionMap.put( jpaAttributePath, attributeBinding ); return attributeBinding; } private SqmNavigableReference resolveNavigableBinding(SqmNavigableSourceReference sqmFrom, JpaAttributePath attributePath) { return parsingContext.findOrCreateNavigableBinding( sqmFrom, attributePath.getNavigable().getAttributeName() ); } private SqmQuerySpec visitQuerySpec(JpaQuerySpec jpaQuerySpec) { querySpecProcessingStateStack.push( new QuerySpecProcessingStateStandardImpl( parsingContext, querySpecProcessingStateStack.getCurrent() ) ); try { return new SqmQuerySpec( visitFromClause( jpaQuerySpec ), visitSelectClause( jpaQuerySpec ), visitWhereClause( jpaQuerySpec ), visitOrderBy( jpaQuerySpec ), visitLimitOffset( jpaQuerySpec ) ); } finally { querySpecProcessingStateStack.pop(); } } private SqmLimitOffsetClause visitLimitOffset(JpaQuerySpec<?> jpaQuerySpec) { // not yet supported in criteria return null; } private SqmOrderByClause visitOrderBy(JpaQuerySpec<?> jpaQuerySpec) { if ( jpaQuerySpec.getOrderList() == null || jpaQuerySpec.getOrderList().isEmpty() ) { return null; } final SqmOrderByClause sqmOrderByClause = new SqmOrderByClause(); for ( JpaOrder jpaOrder : jpaQuerySpec.getOrderList() ) { sqmOrderByClause.addSortSpecification( new SqmSortSpecification( jpaOrder.getExpression().visitExpression( this ), jpaOrder.isAscending() ? SqmSortOrder.ASCENDING : SqmSortOrder.DESCENDING ) ); } return sqmOrderByClause; } private SqmFromClause visitFromClause(JpaQuerySpec<?> jpaQuerySpec) { final SqmFromClause fromClause = new SqmFromClause(); for ( JpaRoot<?> jpaRoot : jpaQuerySpec.getFromClause().getRoots() ) { makeSqmRoot( fromClause, jpaRoot ); } return fromClause; } private SqmRoot makeSqmRoot(SqmFromClause fromClause, JpaRoot<?> jpaRoot) { final SqmFromElementSpace space = fromClause.makeFromElementSpace(); final SqmRoot sqmRoot = querySpecProcessingStateStack.getCurrent().getFromElementBuilder().makeRootEntityFromElement( space, jpaRoot.getEntityType(), interpretAlias( jpaRoot.getAlias() ) ); space.setRoot( sqmRoot ); bindJoins( jpaRoot, sqmRoot.getBinding(), space ); bindFetches( jpaRoot, sqmRoot.getBinding(), space ); jpaPathResolutionMap.put( jpaRoot, sqmRoot.getBinding() ); return sqmRoot; } private void bindJoins(JpaFrom<?,?> lhs, SqmNavigableReference lhsBinding, SqmFromElementSpace space) { if ( !SqmNavigableSourceReference.class.isInstance( lhsBinding ) ) { if ( !lhs.getJoins().isEmpty() ) { throw new ParsingException( "Attempt to bind joins against a NavigableBinding that is not also a NavigableSourceBinding " ); } else { return; } } for ( Join<?, ?> join : lhs.getJoins() ) { makeSqmAttributeJoin( (SqmNavigableSourceReference) lhsBinding, space, join ); } } private SqmAttributeJoin makeSqmAttributeJoin(SqmNavigableSourceReference sourceBinding, SqmFromElementSpace space, Join<?, ?> join) { final JpaAttributeJoin<?,?> jpaAttributeJoin = (JpaAttributeJoin<?, ?>) join; final String alias = jpaAttributeJoin.getAlias(); final SqmAttributeReference attributeBinding = (SqmAttributeReference) parsingContext.findOrCreateNavigableBinding( sourceBinding, jpaAttributeJoin.getAttribute().getName() ); // todo : handle treats final SqmAttributeJoin sqmJoin = querySpecProcessingStateStack.getCurrent().getFromElementBuilder().buildAttributeJoin( attributeBinding, alias, // todo : this is where treat would be applied null, convert( join.getJoinType() ), false, false ); space.addJoin( sqmJoin ); bindJoins( jpaAttributeJoin, sqmJoin.getBinding(), space ); jpaPathResolutionMap.put( jpaAttributeJoin, sqmJoin.getBinding() ); return sqmJoin; } private void bindFetches(FetchParent<?, ?> lhs, SqmNavigableReference lhsBinding, SqmFromElementSpace space) { if ( !SqmNavigableSourceReference.class.isInstance( lhsBinding ) ) { if ( !lhs.getFetches().isEmpty() ) { throw new ParsingException( "Attempt to bind fetches against a NavigableBinding that is not also a NavigableSourceBinding " ); } else { return; } } final SqmNavigableSourceReference sourceBinding = (SqmNavigableSourceReference) lhsBinding; for ( Fetch<?, ?> fetch : lhs.getFetches() ) { final JpaFetch<?,?> jpaFetch = (JpaFetch<?, ?>) fetch; final SqmAttributeReference attrBinding = (SqmAttributeReference) parsingContext.findOrCreateNavigableBinding( sourceBinding, fetch.getAttribute().getName() ); // todo : handle treats final SqmAttributeJoin sqmFetch = querySpecProcessingStateStack.getCurrent().getFromElementBuilder().buildAttributeJoin( attrBinding, interpretAlias( jpaFetch.getAlias() ), // todo : this is where treat would be applied null, convert( fetch.getJoinType() ), true, false ); space.addJoin( sqmFetch ); bindFetches( fetch, sqmFetch.getBinding(), space ); jpaPathResolutionMap.put( jpaFetch, sqmFetch.getBinding() ); } } private SqmJoinType convert(JoinType joinType) { switch ( joinType ) { case INNER: { return SqmJoinType.INNER; } case LEFT: { return SqmJoinType.LEFT; } case RIGHT: { return SqmJoinType.RIGHT; } } throw new ParsingException( "Unrecognized JPA JoinType : " + joinType ); } private SqmSelectClause visitSelectClause(JpaQuerySpec<?> jpaQuerySpec) { final SqmSelectClause sqmSelectClause = new SqmSelectClause( jpaQuerySpec.getSelectClause().isDistinct() ); jpaQuerySpec.getSelectClause().getSelection().visitSelections( this, sqmSelectClause ); return sqmSelectClause; } @Override public void visitDynamicInstantiation(Class target, List<JpaExpression<?>> arguments) { final SqmDynamicInstantiation dynamicInstantiation; if ( List.class.equals( target ) ) { dynamicInstantiation = SqmDynamicInstantiation.forListInstantiation(); } else if ( Map.class.equals( target ) ) { dynamicInstantiation = SqmDynamicInstantiation.forMapInstantiation(); } else { dynamicInstantiation = SqmDynamicInstantiation.forClassInstantiation( target ); } for ( JpaExpression<?> argument : arguments ) { dynamicInstantiation.add( argument.visitExpression( this ), argument.getAlias() ); } } private String interpretAlias(String explicitAlias) { return isNotEmpty( explicitAlias ) ? explicitAlias : parsingContext.getImplicitAliasGenerator().buildUniqueImplicitAlias(); } private static boolean isNotEmpty(String string) { return !isEmpty( string ); } private static boolean isEmpty(String string) { return string == null || string.isEmpty(); } // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Expressions @Override @SuppressWarnings("unchecked") public <T extends Enum> ConstantEnumSqmExpression<T> visitEnumConstant(T value) { return new ConstantEnumSqmExpression<T>( value, parsingContext.getSessionFactory().getTypeConfiguration().getBasicTypeRegistry().getBasicType( value.getClass() ) ); } @Override @SuppressWarnings("unchecked") public <T> LiteralSqmExpression<T> visitConstant(T value) { if ( value == null ) { throw new NullPointerException( "Value passed as `constant value` cannot be null" ); } return visitConstant( value, (Class<T>) value.getClass() ); } @Override @SuppressWarnings("unchecked") public <T> LiteralSqmExpression<T> visitConstant(T value, Class<T> javaType) { if ( Boolean.class.isAssignableFrom( javaType ) ) { if ( (Boolean) value ) { return (LiteralSqmExpression<T>) new LiteralTrueSqmExpression( resolveBasicExpressionType( Boolean.class ) ); } else { return (LiteralSqmExpression<T>) new LiteralFalseSqmExpression( resolveBasicExpressionType( Boolean.class ) ); } } else if ( Integer.class.isAssignableFrom( javaType ) ) { return (LiteralSqmExpression<T>) new LiteralIntegerSqmExpression( (Integer) value, resolveBasicExpressionType( Integer.class ) ); } else if ( Long.class.isAssignableFrom( javaType ) ) { return (LiteralSqmExpression<T>) new LiteralLongSqmExpression( (Long) value, resolveBasicExpressionType( Long.class ) ); } else if ( Float.class.isAssignableFrom( javaType ) ) { return (LiteralSqmExpression<T>) new LiteralFloatSqmExpression( (Float) value, resolveBasicExpressionType( Float.class ) ); } else if ( Double.class.isAssignableFrom( javaType ) ) { return (LiteralSqmExpression<T>) new LiteralDoubleSqmExpression( (Double) value, resolveBasicExpressionType( Double.class ) ); } else if ( BigInteger.class.isAssignableFrom( javaType ) ) { return (LiteralSqmExpression<T>) new LiteralBigIntegerSqmExpression( (BigInteger) value, resolveBasicExpressionType( BigInteger.class ) ); } else if ( BigDecimal.class.isAssignableFrom( javaType ) ) { return (LiteralSqmExpression<T>) new LiteralBigDecimalSqmExpression( (BigDecimal) value, resolveBasicExpressionType( BigDecimal.class ) ); } else if ( Character.class.isAssignableFrom( javaType ) ) { return (LiteralSqmExpression<T>) new LiteralCharacterSqmExpression( (Character) value, resolveBasicExpressionType( Character.class ) ); } else if ( String.class.isAssignableFrom( javaType ) ) { return (LiteralSqmExpression<T>) new LiteralStringSqmExpression( (String) value, resolveBasicExpressionType( String.class ) ); } throw new QueryException( "Unexpected literal expression [value=" + value + ", javaType=" + javaType.getName() + "]; expecting boolean, int, long, float, double, BigInteger, BigDecimal, char, or String" ); } private <T> BasicValuedExpressableType<T> resolveBasicExpressionType(Class<T> typeClass) { return parsingContext.getSessionFactory().getTypeConfiguration().getBasicTypeRegistry().getBasicType( typeClass ); } @Override public UnaryOperationSqmExpression visitUnaryOperation( UnaryOperationSqmExpression.Operation operation, JpaExpression<?> expression) { return new UnaryOperationSqmExpression( operation, expression.visitExpression( this ) ); } @Override public UnaryOperationSqmExpression visitUnaryOperation( UnaryOperationSqmExpression.Operation operation, JpaExpression<?> expression, BasicValuedExpressableType resultType) { return new UnaryOperationSqmExpression( operation, expression.visitExpression( this ), resultType ); } @Override public BinaryArithmeticSqmExpression visitArithmetic( BinaryArithmeticSqmExpression.Operation operation, JpaExpression<?> expression1, JpaExpression<?> expression2) { final SqmExpression firstOperand = expression1.visitExpression( this ); final SqmExpression secondOperand = expression2.visitExpression( this ); return new BinaryArithmeticSqmExpression( operation, firstOperand, secondOperand, parsingContext.getSessionFactory().getTypeConfiguration().resolveArithmeticType( (BasicValuedExpressableType) firstOperand.getExpressionType(), (BasicValuedExpressableType) secondOperand.getExpressionType(), operation == BinaryArithmeticSqmExpression.Operation.DIVIDE ) ); } @Override public BinaryArithmeticSqmExpression visitArithmetic( BinaryArithmeticSqmExpression.Operation operation, JpaExpression<?> expression1, JpaExpression<?> expression2, BasicValuedExpressableType resultType) { return new BinaryArithmeticSqmExpression( operation, expression1.visitExpression( this ), expression2.visitExpression( this ), resultType ); } @Override public SqmSingularAttributeReference visitAttributeReference(JpaFrom<?, ?> attributeSource, String attributeName) { // todo : see // todo : implement (especially leveraging the new pathToDomainBindingXref map) throw new NotYetImplementedException(); // final DomainReferenceBinding source = currentQuerySpecProcessingState.findNavigableBindingByIdentificationVariable( attributeSource.getAlias() ); // final Attribute attributeDescriptor = source.resolveAttribute( attributeName ); // final Type type; // if ( attributeDescriptor instanceof SingularAttribute ) { // type = ( (SingularAttribute) attributeDescriptor ).getType(); // } // else if ( attributeDescriptor instanceof PluralAttribute ) { // type = ( (PluralAttribute) attributeDescriptor ).getElementType(); // } // else { // throw new ParsingException( "Resolved attribute was neither javax.persistence.metamodel.SingularAttribute nor javax.persistence.metamodel.PluralAttribute" ); // } // return new AttributeReferenceSqmExpression( source, attributeDescriptor, null ); } @Override public GenericFunctionSqmExpression visitFunction( String name, BasicValuedExpressableType resultTypeDescriptor, List<JpaExpression<?>> arguments) { final List<SqmExpression> sqmExpressions = new ArrayList<>(); for ( JpaExpression<?> argument : arguments ) { sqmExpressions.add( argument.visitExpression( this ) ); } return new GenericFunctionSqmExpression( name, resultTypeDescriptor, sqmExpressions ); } @Override public GenericFunctionSqmExpression visitFunction( String name, BasicValuedExpressableType resultTypeDescriptor, JpaExpression<?>[] arguments) { // todo : handle the standard function calls specially... // for now always use the generic expression final List<SqmExpression> sqmArguments = new ArrayList<>(); if ( arguments != null ) { for ( JpaExpression<?> expression : arguments ) { sqmArguments.add( expression.visitExpression( this ) ); } } return new GenericFunctionSqmExpression( name, resultTypeDescriptor, sqmArguments ); } @Override public AvgFunctionSqmExpression visitAvgFunction(JpaExpression<?> expression, boolean distinct) { final SqmExpression sqmExpression = expression.visitExpression( this ); return new AvgFunctionSqmExpression( sqmExpression, distinct, (BasicValuedExpressableType) sqmExpression.getExpressionType() ); } @Override public AvgFunctionSqmExpression visitAvgFunction( JpaExpression<?> expression, boolean distinct, BasicValuedExpressableType resultType) { return new AvgFunctionSqmExpression( expression.visitExpression( this ), distinct, resultType ); } @Override public CountFunctionSqmExpression visitCountFunction(JpaExpression<?> expression, boolean distinct) { final SqmExpression sqmExpression = expression.visitExpression( this ); return new CountFunctionSqmExpression( sqmExpression, distinct, (BasicValuedExpressableType) sqmExpression.getExpressionType() ); } @Override public CountFunctionSqmExpression visitCountFunction( JpaExpression<?> expression, boolean distinct, BasicValuedExpressableType resultType) { return new CountFunctionSqmExpression( expression.visitExpression( this ), distinct, resultType ); } @Override public CountStarFunctionSqmExpression visitCountStarFunction(boolean distinct) { return new CountStarFunctionSqmExpression( distinct, parsingContext.getSessionFactory().getTypeConfiguration().getBasicTypeRegistry().getBasicType( Long.class ) ); } @Override public CountStarFunctionSqmExpression visitCountStarFunction(boolean distinct, BasicValuedExpressableType resultType) { return new CountStarFunctionSqmExpression( distinct, resultType ); } @Override public MaxFunctionSqmExpression visitMaxFunction(JpaExpression<?> expression, boolean distinct) { final SqmExpression sqmExpression = expression.visitExpression( this ); return new MaxFunctionSqmExpression( sqmExpression, distinct, (BasicValuedExpressableType) sqmExpression.getExpressionType() ); } @Override public MaxFunctionSqmExpression visitMaxFunction( JpaExpression<?> expression, boolean distinct, BasicValuedExpressableType resultType) { return new MaxFunctionSqmExpression( expression.visitExpression( this ), distinct, resultType ); } @Override public MinFunctionSqmExpression visitMinFunction(JpaExpression<?> expression, boolean distinct) { final SqmExpression sqmExpression = expression.visitExpression( this ); return new MinFunctionSqmExpression( sqmExpression, distinct, (BasicValuedExpressableType) sqmExpression.getExpressionType() ); } @Override public MinFunctionSqmExpression visitMinFunction( JpaExpression<?> expression, boolean distinct, BasicValuedExpressableType resultType) { return new MinFunctionSqmExpression( expression.visitExpression( this ), distinct, resultType ); } @Override public SumFunctionSqmExpression visitSumFunction(JpaExpression<?> expression, boolean distinct) { final SqmExpression sqmExpression = expression.visitExpression( this ); return new SumFunctionSqmExpression( sqmExpression, distinct, parsingContext.getSessionFactory().getTypeConfiguration().resolveSumFunctionType( (BasicValuedExpressableType) sqmExpression.getExpressionType() ) ); } @Override public SumFunctionSqmExpression visitSumFunction( JpaExpression<?> expression, boolean distinct, BasicValuedExpressableType resultType) { return new SumFunctionSqmExpression( expression.visitExpression( this ), distinct, resultType ); } @Override public ConcatSqmExpression visitConcat(JpaExpression<?> expression1, JpaExpression<?> expression2) { return new ConcatSqmExpression( expression1.visitExpression( this ), expression2.visitExpression( this ) ); } @Override public ConcatSqmExpression visitConcat( JpaExpression<?> expression1, JpaExpression<?> expression2, BasicValuedExpressableType resultType) { return new ConcatSqmExpression( expression1.visitExpression( this ), expression2.visitExpression( this ), resultType ); } @Override public CoalesceSqmExpression visitCoalesce(List<JpaExpression<?>> arguments) { final CoalesceSqmExpression coalesce = new CoalesceSqmExpression(); arguments.forEach( argument -> coalesce.value( argument.visitExpression( this ) ) ); return coalesce; } @Override public EntityTypeLiteralSqmExpression visitEntityType(String identificationVariable) { // todo : implement (especially leveraging the new pathToDomainBindingXref map) throw new NotYetImplementedException(); // final SqmFrom fromElement = currentQuerySpecProcessingState.findNavigableBindingByIdentificationVariable( identificationVariable ); // return new EntityTypeSqmExpression( (EntityType) fromElement.getBoundDomainReference() ); } @Override public EntityTypeLiteralSqmExpression visitEntityType(String identificationVariable, String attributeName) { // todo : implement (especially leveraging the new pathToDomainBindingXref map) throw new NotYetImplementedException(); // final SqmFrom fromElement = currentQuerySpecProcessingState.findNavigableBindingByIdentificationVariable( identificationVariable ); // return new EntityTypeSqmExpression( (EntityType) fromElement.resolveAttribute( attributeName ) ); } @Override public SubQuerySqmExpression visitSubQuery(JpaSubquery jpaSubquery) { final SqmQuerySpec subQuerySpec = visitQuerySpec( jpaSubquery.getQuerySpec() ); return new SubQuerySqmExpression( subQuerySpec, determineSelectedExpressableType( subQuerySpec.getSelectClause() ) ); } private static ExpressableType determineSelectedExpressableType(SqmSelectClause selectClause) { if ( selectClause.getSelections().size() != 0 ) { return null; } final SqmSelection selection = selectClause.getSelections().get( 0 ); return selection.getExpression().getExpressionType(); } // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Predicates private SqmWhereClause visitWhereClause(JpaQuerySpec<?> jpaQuerySpec) { final SqmWhereClause whereClause = new SqmWhereClause(); if ( jpaQuerySpec.getRestriction() != null ) { whereClause.setPredicate( jpaQuerySpec.getRestriction().visitPredicate( this ) ); } return whereClause; } @Override public AndSqmPredicate visitAndPredicate(List<JpaPredicate> predicates) { final int predicateCount = predicates.size(); if ( predicateCount < 2 ) { throw new QueryException( "Expecting 2 or more predicate expressions to form conjunction (AND), but found [" + predicateCount + "]" ); } AndSqmPredicate result = new AndSqmPredicate( predicates.get( 0 ).visitPredicate( this ), predicates.get( 1 ).visitPredicate( this ) ); if ( predicateCount > 2 ) { for ( int i = 2; i < predicateCount; i++ ) { result = new AndSqmPredicate( result, predicates.get( i ).visitPredicate( this ) ); } } return result; } @Override public OrSqmPredicate visitOrPredicate(List<JpaPredicate> predicates) { final int predicateCount = predicates.size(); if ( predicateCount < 2 ) { throw new QueryException( "Expecting 2 or more predicate expressions to form disjunction (OR), but found [" + predicateCount + "]" ); } OrSqmPredicate result = new OrSqmPredicate( predicates.get( 0 ).visitPredicate( this ), predicates.get( 1 ).visitPredicate( this ) ); if ( predicateCount > 2 ) { for ( int i = 2; i < predicateCount; i++ ) { result = new OrSqmPredicate( result, predicates.get( i ).visitPredicate( this ) ); } } return result; } @Override public EmptinessSqmPredicate visitEmptinessPredicate(JpaPluralAttributePath pluralAttributePath, boolean negated) { // resolve the plural attribute binding final SqmNavigableSourceReference lhs = (SqmNavigableSourceReference) resolvePath( pluralAttributePath.getParentPath() ); final SqmAttributeReference attributeBinding = (SqmAttributeReference) parsingContext.findOrCreateNavigableBinding( lhs, pluralAttributePath.getNavigable().getAttributeName() ); if ( !SqmPluralAttributeReference.class.isInstance( attributeBinding ) ) { throw new ParsingException( "JpaPluralAttributePath resolved to non-PluralAttributeBinding : " + attributeBinding ); } return new EmptinessSqmPredicate( (SqmPluralAttributeReference) attributeBinding, negated ); } @Override public MemberOfSqmPredicate visitMemberOfPredicate(JpaPluralAttributePath pluralAttributePath, boolean negated) { throw new NotYetImplementedException(); } @Override public BetweenSqmPredicate visitBetweenPredicate( JpaExpression<?> expression, JpaExpression<?> lowerBound, JpaExpression<?> upperBound, boolean negated) { return new BetweenSqmPredicate( expression.visitExpression( this ), lowerBound.visitExpression( this ), upperBound.visitExpression( this ), negated ); } @Override public LikeSqmPredicate visitLikePredicate( JpaExpression<String> matchExpression, JpaExpression<String> pattern, JpaExpression<Character> escapeCharacter, boolean negated) { return new LikeSqmPredicate( matchExpression.visitExpression( this ), pattern.visitExpression( this ), escapeCharacter.visitExpression( this ), negated ); } @Override public InSubQuerySqmPredicate visitInSubQueryPredicate( JpaExpression<?> testExpression, JpaSubquery<?> subquery, boolean negated) { return new InSubQuerySqmPredicate( testExpression.visitExpression( this ), visitSubQuery( subquery ), negated ); } @Override public InListSqmPredicate visitInTupleListPredicate( JpaExpression<?> testExpression, List<JpaExpression<?>> expressionsList, boolean negated) { final List<SqmExpression> expressions = new ArrayList<>(); for ( JpaExpression<?> expression : expressionsList ) { expressions.add( expression.visitExpression( this ) ); } return new InListSqmPredicate( testExpression.visitExpression( this ), expressions, negated ); } @Override public BooleanExpressionSqmPredicate visitBooleanExpressionPredicate( JpaExpression<Boolean> testExpression, Boolean assertValue) { // for now we only support TRUE assertions assert assertValue == Boolean.TRUE; return new BooleanExpressionSqmPredicate( testExpression.visitExpression( this ) ); } @Override public SqmExpression visitRoot(JpaRoot root) { return querySpecProcessingStateStack.getCurrent().findNavigableBindingByIdentificationVariable( root.getAlias() ); } // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // New sigs @Override public SqmExpression visitParameter(String name, int position, Class javaType) { // todo : add hooks for knowing when we are in a context that allows "multi-valued parameter bindings" // for now assume no... boolean canBeMultiValued = false; if ( isNotEmpty( name ) ) { return new NamedParameterSqmExpression( name, canBeMultiValued ); } else { assert position >= 0; return new PositionalParameterSqmExpression( position, canBeMultiValued ); } } @Override public <T, C> CastFunctionSqmExpression visitCastFunction( JpaExpression<T> expressionToCast, Class<C> castTarget) { return new CastFunctionSqmExpression( expressionToCast.visitExpression( this ), // ugh // todo : decide how we want to handle basic types in SQM parsingContext.getSessionFactory().getTypeConfiguration().resolveCastTargetType( castTarget.getName() ) ); } @Override public GenericFunctionSqmExpression visitGenericFunction( String functionName, BasicValuedExpressableType resultType, List<JpaExpression<?>> jpaArguments) { final List<SqmExpression> arguments; if ( jpaArguments != null && !jpaArguments.isEmpty() ) { arguments = new ArrayList<>(); for ( JpaExpression<?> argument : jpaArguments ) { arguments.add( argument.visitExpression( this ) ); } } else { arguments = Collections.emptyList(); } return new GenericFunctionSqmExpression( functionName, resultType, arguments ); } @Override public RelationalSqmPredicate visitRelationalPredicate( RelationalPredicateOperator operator, JpaExpression<?> lhs, JpaExpression<?> rhs) { return new RelationalSqmPredicate( operator, lhs.visitExpression( this ), rhs.visitExpression( this ) ); } @Override public NegatedSqmPredicate visitNegatedPredicate(JpaPredicate affirmativePredicate) { return new NegatedSqmPredicate( affirmativePredicate.visitPredicate( this ) ); } @Override public NullnessSqmPredicate visitNullnessPredicate(JpaExpression<?> testExpression) { return new NullnessSqmPredicate( testExpression.visitExpression( this ) ); } }