diff --git a/documentation/src/main/asciidoc/userguide/appendices/Configurations.adoc b/documentation/src/main/asciidoc/userguide/appendices/Configurations.adoc index 4ed06d895e..9c4a6de9da 100644 --- a/documentation/src/main/asciidoc/userguide/appendices/Configurations.adoc +++ b/documentation/src/main/asciidoc/userguide/appendices/Configurations.adoc @@ -79,6 +79,17 @@ Traditionally, Hibernate has considered the names locally scoped. If enabled, the names used by `@TableGenerator` and `@SequenceGenerator` will be considered global so configuring two different generators with the same name will cause a `java.lang.IllegalArgumentException` to be thrown at boot time. +`*hibernate.jpa.compliance.criteria_copy*` (e.g. `true` (default value) or `false` ):: +The Jakarta Persistence spec says that mutations done to `CriteriaQuery`, `CriteriaUpdate` and `CriteriaDelete` +after such objects were used to create a `jakarta.persistence.Query` may not affect that query. +This requirement makes it necessary to copy these objects because the APIs allow mutations. ++ +If disabled, it is assumed that users do not mutate the criteria query afterwards +and due to that, no copy will be created, which will improve performance. ++ +By default, no copies are created to not hurt performance. When enabled, +criteria query objects are copied, as required by the JPA specification. + [[configurations-database-connection]] === Database connection properties diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/AvailableSettings.java b/hibernate-core/src/main/java/org/hibernate/cfg/AvailableSettings.java index 219576b411..d69c2c2490 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/AvailableSettings.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/AvailableSettings.java @@ -19,6 +19,10 @@ import org.hibernate.query.sqm.NullPrecedence; import org.hibernate.resource.jdbc.spi.PhysicalConnectionHandlingMode; import org.hibernate.resource.jdbc.spi.StatementInspector; +import jakarta.persistence.criteria.CriteriaDelete; +import jakarta.persistence.criteria.CriteriaQuery; +import jakarta.persistence.criteria.CriteriaUpdate; + /** * Enumerates the configuration properties supported by Hibernate, including * properties defined by the JPA specification. @@ -2364,6 +2368,24 @@ public interface AvailableSettings { */ String JPA_LOAD_BY_ID_COMPLIANCE = "hibernate.jpa.compliance.load_by_id"; + /** + * When enabled, specifies that {@linkplain org.hibernate.query.Query queries} + * created through {@link jakarta.persistence.EntityManager#createQuery(CriteriaQuery)}, + * {@link jakarta.persistence.EntityManager#createQuery(CriteriaUpdate)} or + * {@link jakarta.persistence.EntityManager#createQuery(CriteriaDelete)} + * must create a copy of the passed object such that the resulting {@link jakarta.persistence.Query} + * is not affected by any mutations to the original criteria query. + *

+ * If disabled, it is assumed that users do not mutate the criteria query afterwards + * and due to that, no copy will be created, which will improve performance. + *

+ * By default, no copies are created to not hurt performance. When enabled, + * criteria query objects are copied, as required by the JPA specification. + * + * @since 6.0 + */ + String JPA_CRITERIA_COPY_COMPLIANCE = "hibernate.jpa.compliance.criteria_copy"; + /** * Determines if the identifier value stored in the database table backing a * {@linkplain jakarta.persistence.TableGenerator table generator} is the last diff --git a/hibernate-core/src/main/java/org/hibernate/engine/spi/SessionDelegatorBaseImpl.java b/hibernate-core/src/main/java/org/hibernate/engine/spi/SessionDelegatorBaseImpl.java index c330ef5b43..976a902fc6 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/spi/SessionDelegatorBaseImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/spi/SessionDelegatorBaseImpl.java @@ -209,6 +209,16 @@ public class SessionDelegatorBaseImpl implements SessionImplementor { delegate.setCacheMode( cm ); } + @Override + public void setJpaCriteriaCopyComplianceEnabled(boolean jpaCriteriaCopyComplianceEnabled) { + delegate.setJpaCriteriaCopyComplianceEnabled( jpaCriteriaCopyComplianceEnabled ); + } + + @Override + public boolean isJpaCriteriaCopyComplianceEnabled() { + return delegate.isJpaCriteriaCopyComplianceEnabled(); + } + @Override public boolean isOpen() { return delegate.isOpen(); diff --git a/hibernate-core/src/main/java/org/hibernate/engine/spi/SharedSessionContractImplementor.java b/hibernate-core/src/main/java/org/hibernate/engine/spi/SharedSessionContractImplementor.java index 3dd3fcbd83..ef38a6a6f0 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/spi/SharedSessionContractImplementor.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/spi/SharedSessionContractImplementor.java @@ -332,6 +332,10 @@ public interface SharedSessionContractImplementor void setCacheMode(CacheMode cm); + void setJpaCriteriaCopyComplianceEnabled(boolean jpaCriteriaCopyComplianceEnabled); + + boolean isJpaCriteriaCopyComplianceEnabled(); + /** * Set the flush mode for this session. *

diff --git a/hibernate-core/src/main/java/org/hibernate/internal/AbstractSharedSessionContract.java b/hibernate-core/src/main/java/org/hibernate/internal/AbstractSharedSessionContract.java index ad95f7a652..1b38399df7 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/AbstractSharedSessionContract.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/AbstractSharedSessionContract.java @@ -81,6 +81,7 @@ import org.hibernate.query.sql.spi.NativeQueryImplementor; import org.hibernate.query.sqm.SqmSelectionQuery; import org.hibernate.query.sqm.internal.QuerySqmImpl; import org.hibernate.query.sqm.internal.SqmSelectionQueryImpl; +import org.hibernate.query.sqm.internal.SqmUtil; import org.hibernate.query.sqm.tree.SqmDmlStatement; import org.hibernate.query.sqm.tree.SqmStatement; import org.hibernate.query.sqm.tree.delete.SqmDeleteStatement; @@ -141,6 +142,7 @@ public abstract class AbstractSharedSessionContract implements SharedSessionCont private final PhysicalConnectionHandlingMode connectionHandlingMode; private CacheMode cacheMode; + private boolean jpaCriteriaCopyComplianceEnabled; protected boolean closed; protected boolean waitingForAutoClose; @@ -158,7 +160,7 @@ public abstract class AbstractSharedSessionContract implements SharedSessionCont this.factory = factory; this.fastSessionServices = factory.getFastSessionServices(); this.cacheTransactionSync = factory.getCache().getRegionFactory().createTransactionContext( this ); - + setJpaCriteriaCopyComplianceEnabled( factory.getJpaMetamodel().getJpaCompliance().isJpaCriteriaCopyComplianceEnabled() ); this.flushMode = options.getInitialSessionFlushMode(); @@ -643,6 +645,15 @@ public abstract class AbstractSharedSessionContract implements SharedSessionCont this.cacheMode = cacheMode; } + @Override + public void setJpaCriteriaCopyComplianceEnabled(boolean jpaCriteriaCopyComplianceEnabled) { + this.jpaCriteriaCopyComplianceEnabled = jpaCriteriaCopyComplianceEnabled; + } + + @Override + public boolean isJpaCriteriaCopyComplianceEnabled() { + return jpaCriteriaCopyComplianceEnabled; + } // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // dynamic HQL handling @@ -705,6 +716,7 @@ public abstract class AbstractSharedSessionContract implements SharedSessionCont @Override public SelectionQuery createSelectionQuery(CriteriaQuery criteria) { + SqmUtil.verifyIsSelectStatement( (SqmStatement) criteria, null ); return new SqmSelectionQueryImpl<>( (SqmSelectStatement) criteria, this ); } diff --git a/hibernate-core/src/main/java/org/hibernate/internal/SessionImpl.java b/hibernate-core/src/main/java/org/hibernate/internal/SessionImpl.java index 26529256de..a18b808512 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/SessionImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/SessionImpl.java @@ -152,6 +152,7 @@ import static org.hibernate.cfg.AvailableSettings.JAKARTA_LOCK_SCOPE; import static org.hibernate.cfg.AvailableSettings.JAKARTA_LOCK_TIMEOUT; import static org.hibernate.cfg.AvailableSettings.JAKARTA_SHARED_CACHE_RETRIEVE_MODE; import static org.hibernate.cfg.AvailableSettings.JAKARTA_SHARED_CACHE_STORE_MODE; +import static org.hibernate.cfg.AvailableSettings.JPA_CRITERIA_COPY_COMPLIANCE; import static org.hibernate.cfg.AvailableSettings.JPA_LOCK_SCOPE; import static org.hibernate.cfg.AvailableSettings.JPA_LOCK_TIMEOUT; import static org.hibernate.cfg.AvailableSettings.JPA_SHARED_CACHE_RETRIEVE_MODE; @@ -2599,6 +2600,9 @@ public class SessionImpl ) ); break; + case JPA_CRITERIA_COPY_COMPLIANCE: + setJpaCriteriaCopyComplianceEnabled( Boolean.parseBoolean( value.toString() ) ); + break; } } diff --git a/hibernate-core/src/main/java/org/hibernate/jpa/internal/JpaComplianceImpl.java b/hibernate-core/src/main/java/org/hibernate/jpa/internal/JpaComplianceImpl.java index 2078c52c6d..a5785e13bd 100644 --- a/hibernate-core/src/main/java/org/hibernate/jpa/internal/JpaComplianceImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/jpa/internal/JpaComplianceImpl.java @@ -21,6 +21,7 @@ public class JpaComplianceImpl implements JpaCompliance { private final boolean closedCompliance; private final boolean cachingCompliance; private final boolean loadByIdCompliance; + private final boolean criteriaCopyCompliance; public JpaComplianceImpl( boolean listCompliance, @@ -31,7 +32,8 @@ public class JpaComplianceImpl implements JpaCompliance { boolean transactionCompliance, boolean closedCompliance, boolean cachingCompliance, - boolean loadByIdCompliance) { + boolean loadByIdCompliance, + boolean criteriaCopyCompliance) { this.queryCompliance = queryCompliance; this.transactionCompliance = transactionCompliance; this.listCompliance = listCompliance; @@ -41,6 +43,7 @@ public class JpaComplianceImpl implements JpaCompliance { this.globalGeneratorNameScopeCompliance = globalGeneratorNameScopeCompliance; this.orderByMappingCompliance = orderByMappingCompliance; this.loadByIdCompliance = loadByIdCompliance; + this.criteriaCopyCompliance = criteriaCopyCompliance; } @Override @@ -88,6 +91,11 @@ public class JpaComplianceImpl implements JpaCompliance { return loadByIdCompliance; } + @Override + public boolean isJpaCriteriaCopyComplianceEnabled() { + return criteriaCopyCompliance; + } + public static class JpaComplianceBuilder { private boolean queryCompliance; private boolean listCompliance; @@ -98,6 +106,7 @@ public class JpaComplianceImpl implements JpaCompliance { private boolean transactionCompliance; private boolean closedCompliance; private boolean loadByIdCompliance; + private boolean criteriaCopyCompliance; public JpaComplianceBuilder() { } @@ -147,6 +156,11 @@ public class JpaComplianceImpl implements JpaCompliance { return this; } + public JpaComplianceBuilder setJpaCriteriaCopyComplianceEnabled(boolean jpaCriteriaCopyComplianceEnabled) { + this.criteriaCopyCompliance = jpaCriteriaCopyComplianceEnabled; + return this; + } + JpaCompliance createJpaCompliance() { return new JpaComplianceImpl( listCompliance, @@ -157,7 +171,8 @@ public class JpaComplianceImpl implements JpaCompliance { transactionCompliance, closedCompliance, cachingCompliance, - loadByIdCompliance + loadByIdCompliance, + criteriaCopyCompliance ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/jpa/internal/MutableJpaComplianceImpl.java b/hibernate-core/src/main/java/org/hibernate/jpa/internal/MutableJpaComplianceImpl.java index a2d720931d..279a1904af 100644 --- a/hibernate-core/src/main/java/org/hibernate/jpa/internal/MutableJpaComplianceImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/jpa/internal/MutableJpaComplianceImpl.java @@ -26,6 +26,7 @@ public class MutableJpaComplianceImpl implements MutableJpaCompliance { private boolean closedCompliance; private boolean cachingCompliance; private boolean loadByIdCompliance; + private boolean criteriaCopyCompliance; public MutableJpaComplianceImpl(Map configurationSettings) { this( @@ -92,6 +93,12 @@ public class MutableJpaComplianceImpl implements MutableJpaCompliance { configurationSettings, jpaByDefault ); + + criteriaCopyCompliance = ConfigurationHelper.getBoolean( + AvailableSettings.JPA_CRITERIA_COPY_COMPLIANCE, + configurationSettings, + jpaByDefault + ); } @Override @@ -134,6 +141,16 @@ public class MutableJpaComplianceImpl implements MutableJpaCompliance { return orderByMappingCompliance; } + @Override + public boolean isLoadByIdComplianceEnabled() { + return loadByIdCompliance; + } + + @Override + public boolean isJpaCriteriaCopyComplianceEnabled() { + return criteriaCopyCompliance; + } + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Mutators @@ -182,8 +199,8 @@ public class MutableJpaComplianceImpl implements MutableJpaCompliance { } @Override - public boolean isLoadByIdComplianceEnabled() { - return loadByIdCompliance; + public void setJpaCriteriaCopyComplianceEnabled(boolean jpaCriteriaCopyComplianceEnabled) { + this.criteriaCopyCompliance = jpaCriteriaCopyComplianceEnabled; } @Override @@ -197,7 +214,8 @@ public class MutableJpaComplianceImpl implements MutableJpaCompliance { .setTransactionCompliance( transactionCompliance ) .setClosedCompliance( closedCompliance ) .setCachingCompliance( cachingCompliance ) - .setLoadByIdCompliance( loadByIdCompliance ); + .setLoadByIdCompliance( loadByIdCompliance ) + .setJpaCriteriaCopyComplianceEnabled( criteriaCopyCompliance ); return builder.createJpaCompliance(); } } diff --git a/hibernate-core/src/main/java/org/hibernate/jpa/spi/JpaCompliance.java b/hibernate-core/src/main/java/org/hibernate/jpa/spi/JpaCompliance.java index 73d3f1f70b..1b714ea81b 100644 --- a/hibernate-core/src/main/java/org/hibernate/jpa/spi/JpaCompliance.java +++ b/hibernate-core/src/main/java/org/hibernate/jpa/spi/JpaCompliance.java @@ -154,4 +154,22 @@ public interface JpaCompliance { * @since 6.0 */ boolean isLoadByIdComplianceEnabled(); + + /** + * JPA says that mutations done to {@link jakarta.persistence.criteria.CriteriaQuery}, + * {@link jakarta.persistence.criteria.CriteriaUpdate} and {@link jakarta.persistence.criteria.CriteriaDelete} + * after such objects were used to create a {@link jakarta.persistence.Query} may not affect that query. + * This requirement makes it necessary to copy these objects because the APIs allow mutations. + * + * If disabled, it is assumed that users do not mutate the criteria query afterwards + * and due to that, no copy will be created, which will improve performance. + * + * By default, no copies are created to not hurt performance. When enabled, + * criteria query objects are copied, as required by the JPA specification. + * + * @see org.hibernate.cfg.AvailableSettings#JPA_CRITERIA_COPY_COMPLIANCE + * + * @since 6.0 + */ + boolean isJpaCriteriaCopyComplianceEnabled(); } diff --git a/hibernate-core/src/main/java/org/hibernate/jpa/spi/MutableJpaCompliance.java b/hibernate-core/src/main/java/org/hibernate/jpa/spi/MutableJpaCompliance.java index eeac1d5d91..ea4544762a 100644 --- a/hibernate-core/src/main/java/org/hibernate/jpa/spi/MutableJpaCompliance.java +++ b/hibernate-core/src/main/java/org/hibernate/jpa/spi/MutableJpaCompliance.java @@ -28,5 +28,7 @@ public interface MutableJpaCompliance extends JpaCompliance { void setLoadByIdCompliance(boolean enabled); + void setJpaCriteriaCopyComplianceEnabled(boolean jpaCriteriaCopyComplianceEnabled); + JpaCompliance immutableCopy(); } diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/DiscriminatorSqmPath.java b/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/DiscriminatorSqmPath.java index a78d96fcf8..c3c592ea1f 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/DiscriminatorSqmPath.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/DiscriminatorSqmPath.java @@ -21,6 +21,7 @@ import org.hibernate.query.sqm.SqmPathSource; import org.hibernate.query.sqm.sql.internal.DiscriminatorPathInterpretation; import org.hibernate.query.sqm.sql.internal.SelfInterpretingSqmPath; import org.hibernate.query.sqm.sql.internal.SqmPathInterpretation; +import org.hibernate.query.sqm.tree.SqmCopyContext; import org.hibernate.query.sqm.tree.domain.AbstractSqmPath; import org.hibernate.query.sqm.tree.domain.SqmPath; import org.hibernate.query.sqm.tree.domain.SqmTreatedPath; @@ -50,6 +51,18 @@ public class DiscriminatorSqmPath extends AbstractSqmPath implements SelfInterpr this.entityDescriptor = entityDescriptor; } + @Override + public DiscriminatorSqmPath copy(SqmCopyContext context) { + final DiscriminatorSqmPath existing = context.getCopy( this ); + if ( existing != null ) { + return existing; + } + return context.registerCopy( + this, + (DiscriminatorSqmPath) getLhs().copy( context ).type() + ); + } + @Override public X accept(SemanticQueryWalker walker) { if ( ! entityDescriptor.hasSubclasses() ) { diff --git a/hibernate-core/src/main/java/org/hibernate/query/IllegalMutationQueryException.java b/hibernate-core/src/main/java/org/hibernate/query/IllegalMutationQueryException.java index bce011f13b..a2141415db 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/IllegalMutationQueryException.java +++ b/hibernate-core/src/main/java/org/hibernate/query/IllegalMutationQueryException.java @@ -6,8 +6,6 @@ */ package org.hibernate.query; -import org.hibernate.QueryException; - /** * Indicates an attempt to call {@link QueryProducer#createMutationQuery(String)}, * {@link QueryProducer#createNamedMutationQuery(String)} or @@ -16,8 +14,12 @@ import org.hibernate.QueryException; * * @author Steve Ebersole */ -public class IllegalMutationQueryException extends QueryException { +public class IllegalMutationQueryException extends IllegalQueryOperationException { public IllegalMutationQueryException(String message) { super( message ); } + + public IllegalMutationQueryException(String message, String queryString) { + super( message, queryString, null ); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/IllegalSelectQueryException.java b/hibernate-core/src/main/java/org/hibernate/query/IllegalSelectQueryException.java index 4c0c9137bd..912610baa7 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/IllegalSelectQueryException.java +++ b/hibernate-core/src/main/java/org/hibernate/query/IllegalSelectQueryException.java @@ -6,20 +6,18 @@ */ package org.hibernate.query; -import org.hibernate.QueryException; - /** * Indicates an attempt to call {@link QueryProducer#createSelectionQuery(String)} * with a non-selection query (generally a mutation query) * * @author Steve Ebersole */ -public class IllegalSelectQueryException extends QueryException { +public class IllegalSelectQueryException extends IllegalQueryOperationException { public IllegalSelectQueryException(String message) { super( message ); } public IllegalSelectQueryException(String message, String queryString) { - super( message, queryString ); + super( message, queryString, null ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/hql/internal/FullyQualifiedReflectivePathTerminal.java b/hibernate-core/src/main/java/org/hibernate/query/hql/internal/FullyQualifiedReflectivePathTerminal.java index f6937b0c78..787d5be2f6 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/hql/internal/FullyQualifiedReflectivePathTerminal.java +++ b/hibernate-core/src/main/java/org/hibernate/query/hql/internal/FullyQualifiedReflectivePathTerminal.java @@ -12,17 +12,17 @@ import java.math.BigInteger; import java.util.Collection; import java.util.List; import java.util.function.Function; -import jakarta.persistence.criteria.Expression; import org.hibernate.boot.registry.classloading.spi.ClassLoaderService; import org.hibernate.boot.registry.classloading.spi.ClassLoadingException; import org.hibernate.metamodel.model.domain.EntityDomainType; import org.hibernate.query.criteria.JpaSelection; import org.hibernate.query.hql.HqlInterpretationException; +import org.hibernate.query.hql.spi.SqmCreationState; import org.hibernate.query.sqm.NodeBuilder; import org.hibernate.query.sqm.SqmExpressible; import org.hibernate.query.sqm.SemanticQueryWalker; -import org.hibernate.query.hql.spi.SqmCreationState; +import org.hibernate.query.sqm.tree.SqmCopyContext; import org.hibernate.query.sqm.tree.expression.SqmEnumLiteral; import org.hibernate.query.sqm.tree.expression.SqmExpression; import org.hibernate.query.sqm.tree.expression.SqmFieldLiteral; @@ -31,6 +31,8 @@ import org.hibernate.query.sqm.tree.predicate.SqmPredicate; import org.hibernate.type.descriptor.java.EnumJavaType; import org.hibernate.type.descriptor.java.JavaType; +import jakarta.persistence.criteria.Expression; + /** * @author Steve Ebersole */ @@ -55,6 +57,11 @@ public class FullyQualifiedReflectivePathTerminal this.expressibleType = null; } + @Override + public FullyQualifiedReflectivePathTerminal copy(SqmCopyContext context) { + return this; + } + @SuppressWarnings("unchecked") private Function resolveTerminalSemantic() { return semanticQueryWalker -> { diff --git a/hibernate-core/src/main/java/org/hibernate/query/spi/AbstractSelectionQuery.java b/hibernate-core/src/main/java/org/hibernate/query/spi/AbstractSelectionQuery.java index 4b34c10802..8964e0e142 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/spi/AbstractSelectionQuery.java +++ b/hibernate-core/src/main/java/org/hibernate/query/spi/AbstractSelectionQuery.java @@ -6,10 +6,12 @@ */ package org.hibernate.query.spi; +import java.sql.Types; import java.time.Instant; import java.util.Calendar; import java.util.Collection; import java.util.Date; +import java.util.IdentityHashMap; import java.util.List; import java.util.Map; import java.util.Optional; @@ -22,6 +24,9 @@ import jakarta.persistence.LockModeType; import jakarta.persistence.NoResultException; import jakarta.persistence.Parameter; import jakarta.persistence.TemporalType; +import jakarta.persistence.Tuple; +import jakarta.persistence.TupleElement; +import jakarta.persistence.criteria.CompoundSelection; import org.hibernate.CacheMode; import org.hibernate.FlushMode; @@ -31,18 +36,36 @@ import org.hibernate.LockOptions; import org.hibernate.NonUniqueResultException; import org.hibernate.ScrollMode; import org.hibernate.TypeMismatchException; +import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.graph.spi.AppliedGraph; import org.hibernate.jpa.internal.util.LockModeTypeHelper; +import org.hibernate.metamodel.model.domain.BasicDomainType; +import org.hibernate.metamodel.model.domain.DomainType; import org.hibernate.query.BindableType; import org.hibernate.query.IllegalQueryOperationException; import org.hibernate.query.QueryParameter; +import org.hibernate.query.QueryTypeMismatchException; import org.hibernate.query.SelectionQuery; +import org.hibernate.query.criteria.JpaSelection; +import org.hibernate.query.hql.spi.NamedHqlQueryMemento; import org.hibernate.query.internal.ScrollableResultsIterator; import org.hibernate.query.named.NamedQueryMemento; +import org.hibernate.query.sqm.SqmExpressible; +import org.hibernate.query.sqm.SqmPathSource; +import org.hibernate.query.sqm.tree.SqmStatement; +import org.hibernate.query.sqm.tree.expression.SqmParameter; +import org.hibernate.query.sqm.tree.from.SqmRoot; +import org.hibernate.query.sqm.tree.select.SqmQueryGroup; +import org.hibernate.query.sqm.tree.select.SqmQueryPart; +import org.hibernate.query.sqm.tree.select.SqmQuerySpec; import org.hibernate.query.sqm.tree.select.SqmSelectStatement; +import org.hibernate.query.sqm.tree.select.SqmSelection; import org.hibernate.sql.exec.internal.CallbackImpl; import org.hibernate.sql.exec.spi.Callback; +import org.hibernate.sql.results.internal.TupleMetadata; +import org.hibernate.type.BasicType; +import org.hibernate.type.descriptor.jdbc.JdbcType; import static org.hibernate.cfg.AvailableSettings.JAKARTA_SHARED_CACHE_RETRIEVE_MODE; import static org.hibernate.cfg.AvailableSettings.JAKARTA_SHARED_CACHE_STORE_MODE; @@ -68,6 +91,64 @@ public abstract class AbstractSelectionQuery super( session ); } + protected TupleMetadata buildTupleMetadata(SqmStatement statement, Class resultType) { + if ( resultType != null && Tuple.class.isAssignableFrom( resultType ) ) { + final List> selections = ( (SqmSelectStatement) statement ).getQueryPart() + .getFirstQuerySpec() + .getSelectClause() + .getSelections(); + // resultType is Tuple.. + if ( getQueryOptions().getTupleTransformer() == null ) { + final Map, Integer> tupleElementMap; + if ( selections.size() == 1 && selections.get( 0 ).getSelectableNode() instanceof CompoundSelection ) { + final List> selectionItems = selections.get( 0 ) + .getSelectableNode() + .getSelectionItems(); + tupleElementMap = new IdentityHashMap<>( selectionItems.size() ); + for ( int i = 0; i < selectionItems.size(); i++ ) { + tupleElementMap.put( selectionItems.get( i ), i ); + } + } + else { + tupleElementMap = new IdentityHashMap<>( selections.size() ); + for ( int i = 0; i < selections.size(); i++ ) { + final SqmSelection selection = selections.get( i ); + tupleElementMap.put( selection.getSelectableNode(), i ); + } + } + return new TupleMetadata( tupleElementMap ); + } + + throw new IllegalArgumentException( + "Illegal combination of Tuple resultType and (non-JpaTupleBuilder) TupleTransformer : " + + getQueryOptions().getTupleTransformer() + ); + } + return null; + } + + protected void applyOptions(NamedHqlQueryMemento memento) { + applyOptions( (NamedQueryMemento) memento ); + + if ( memento.getFirstResult() != null ) { + setFirstResult( memento.getFirstResult() ); + } + + if ( memento.getMaxResults() != null ) { + setMaxResults( memento.getMaxResults() ); + } + + if ( memento.getParameterTypes() != null ) { + for ( Map.Entry entry : memento.getParameterTypes().entrySet() ) { + final QueryParameterImplementor parameter = getParameterMetadata().getQueryParameter( entry.getKey() ); + final BasicType type = getSessionFactory().getTypeConfiguration() + .getBasicTypeRegistry() + .getRegisteredType( entry.getValue() ); + parameter.applyAnticipatedType( type ); + } + } + } + protected void applyOptions(NamedQueryMemento memento) { if ( memento.getHints() != null ) { memento.getHints().forEach( this::applyHint ); @@ -106,6 +187,143 @@ public abstract class AbstractSelectionQuery } } + protected void visitQueryReturnType( + SqmQueryPart queryPart, + Class resultType, + SessionFactoryImplementor factory) { + if ( queryPart instanceof SqmQuerySpec ) { + final SqmQuerySpec sqmQuerySpec = (SqmQuerySpec) queryPart; + final List> sqmSelections = sqmQuerySpec.getSelectClause().getSelections(); + + if ( sqmSelections == null || sqmSelections.isEmpty() ) { + // make sure there is at least one root + final List> sqmRoots = sqmQuerySpec.getFromClause().getRoots(); + if ( sqmRoots == null || sqmRoots.isEmpty() ) { + throw new IllegalArgumentException( "Criteria did not define any query roots" ); + } + // if there is a single root, use that as the selection + if ( sqmRoots.size() == 1 ) { + final SqmRoot sqmRoot = sqmRoots.get( 0 ); + sqmQuerySpec.getSelectClause().add( sqmRoot, null ); + } + else { + throw new IllegalArgumentException( ); + } + } + + if ( resultType != null ) { + checkQueryReturnType( sqmQuerySpec, resultType, factory ); + } + } + else { + final SqmQueryGroup queryGroup = (SqmQueryGroup) queryPart; + for ( SqmQueryPart sqmQueryPart : queryGroup.getQueryParts() ) { + visitQueryReturnType( sqmQueryPart, resultType, factory ); + } + } + } + + protected static void checkQueryReturnType( + SqmQuerySpec querySpec, + Class resultClass, + SessionFactoryImplementor sessionFactory) { + if ( resultClass == null ) { + // nothing to check + return; + } + + final List> selections = querySpec.getSelectClause().getSelections(); + + if ( resultClass.isArray() ) { + // todo (6.0) : implement + } + else if ( Tuple.class.isAssignableFrom( resultClass ) ) { + // todo (6.0) : implement + } + else { + final boolean jpaQueryComplianceEnabled = sessionFactory.getSessionFactoryOptions() + .getJpaCompliance() + .isJpaQueryComplianceEnabled(); + if ( selections.size() != 1 ) { + final String errorMessage = "Query result-type error - multiple selections: use Tuple or array"; + + if ( jpaQueryComplianceEnabled ) { + throw new IllegalArgumentException( errorMessage ); + } + else { + throw new QueryTypeMismatchException( errorMessage ); + } + } + + final SqmSelection sqmSelection = selections.get( 0 ); + + if ( sqmSelection.getSelectableNode() instanceof SqmParameter ) { + final SqmParameter sqmParameter = (SqmParameter) sqmSelection.getSelectableNode(); + + // we may not yet know a selection type + if ( sqmParameter.getNodeType() == null || sqmParameter.getNodeType().getExpressibleJavaType() == null ) { + // we can't verify the result type up front + return; + } + } + + if ( jpaQueryComplianceEnabled ) { + return; + } + verifyResultType( resultClass, sqmSelection.getNodeType(), sessionFactory ); + } + } + + protected static void verifyResultType( + Class resultClass, + SqmExpressible sqmExpressible, + SessionFactoryImplementor sessionFactory) { + assert sqmExpressible != null; + assert sqmExpressible.getExpressibleJavaType() != null; + final Class javaTypeClass = sqmExpressible.getExpressibleJavaType().getJavaTypeClass(); + if ( !resultClass.isAssignableFrom( javaTypeClass ) ) { + // Special case for date because we always report java.util.Date as expression type + // But the expected resultClass could be a subtype of that, so we need to check the JdbcType + if ( javaTypeClass == Date.class ) { + JdbcType jdbcType = null; + if ( sqmExpressible instanceof BasicDomainType ) { + jdbcType = ( (BasicDomainType) sqmExpressible).getJdbcType(); + } + else if ( sqmExpressible instanceof SqmPathSource ) { + final DomainType domainType = ( (SqmPathSource) sqmExpressible).getSqmPathType(); + if ( domainType instanceof BasicDomainType ) { + jdbcType = ( (BasicDomainType) domainType ).getJdbcType(); + } + } + if ( jdbcType != null ) { + switch ( jdbcType.getJdbcTypeCode() ) { + case Types.DATE: + if ( resultClass.isAssignableFrom( java.sql.Date.class ) ) { + return; + } + break; + case Types.TIME: + if ( resultClass.isAssignableFrom( java.sql.Time.class ) ) { + return; + } + break; + case Types.TIMESTAMP: + if ( resultClass.isAssignableFrom( java.sql.Timestamp.class ) ) { + return; + } + break; + } + } + } + final String errorMessage = String.format( + "Specified result type [%s] did not match Query selection type [%s] - multiple selections: use Tuple or array", + resultClass.getName(), + sqmExpressible.getExpressibleJavaType().getJavaType().getTypeName() + ); + throw new QueryTypeMismatchException( errorMessage ); + } + } + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // execution @@ -686,4 +904,8 @@ public abstract class AbstractSelectionQuery super.setProperties( bean ); return this; } + + public SessionFactoryImplementor getSessionFactory() { + return getSession().getFactory(); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/function/SelfRenderingSqmAggregateFunction.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/function/SelfRenderingSqmAggregateFunction.java index 196d0678cd..2d63cdf00c 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/function/SelfRenderingSqmAggregateFunction.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/function/SelfRenderingSqmAggregateFunction.java @@ -6,6 +6,7 @@ */ package org.hibernate.query.sqm.function; +import java.util.ArrayList; import java.util.List; import org.hibernate.query.ReturnableType; @@ -13,6 +14,7 @@ import org.hibernate.query.sqm.NodeBuilder; import org.hibernate.query.sqm.produce.function.ArgumentsValidator; import org.hibernate.query.sqm.produce.function.FunctionReturnTypeResolver; import org.hibernate.query.sqm.sql.SqmToSqlAstConverter; +import org.hibernate.query.sqm.tree.SqmCopyContext; import org.hibernate.query.sqm.tree.SqmTypedNode; import org.hibernate.query.sqm.tree.expression.SqmAggregateFunction; import org.hibernate.query.sqm.tree.expression.SqmDistinct; @@ -43,6 +45,34 @@ public class SelfRenderingSqmAggregateFunction extends SelfRenderingSqmFuncti this.filter = filter; } + @Override + public SelfRenderingSqmAggregateFunction copy(SqmCopyContext context) { + final SelfRenderingSqmAggregateFunction existing = context.getCopy( this ); + if ( existing != null ) { + return existing; + } + final List> arguments = new ArrayList<>( getArguments().size() ); + for ( SqmTypedNode argument : getArguments() ) { + arguments.add( argument.copy( context ) ); + } + final SelfRenderingSqmAggregateFunction expression = context.registerCopy( + this, + new SelfRenderingSqmAggregateFunction<>( + getFunctionDescriptor(), + getRenderingSupport(), + arguments, + filter == null ? null : filter.copy( context ), + getImpliedResultType(), + getArgumentsValidator(), + getReturnTypeResolver(), + nodeBuilder(), + getFunctionName() + ) + ); + copyTo( expression, context ); + return expression; + } + @Override public SelfRenderingFunctionSqlAstExpression convertToSqlAst(SqmToSqlAstConverter walker) { final ReturnableType resultType = resolveResultType( diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/function/SelfRenderingSqmFunction.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/function/SelfRenderingSqmFunction.java index ebeebd84df..d4d41d7cc1 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/function/SelfRenderingSqmFunction.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/function/SelfRenderingSqmFunction.java @@ -18,6 +18,7 @@ import org.hibernate.query.sqm.SqmExpressible; import org.hibernate.query.sqm.produce.function.ArgumentsValidator; import org.hibernate.query.sqm.produce.function.FunctionReturnTypeResolver; import org.hibernate.query.sqm.sql.SqmToSqlAstConverter; +import org.hibernate.query.sqm.tree.SqmCopyContext; import org.hibernate.query.sqm.tree.SqmTypedNode; import org.hibernate.query.sqm.tree.SqmVisitableNode; import org.hibernate.query.sqm.tree.expression.SqmFunction; @@ -52,14 +53,49 @@ public class SelfRenderingSqmFunction extends SqmFunction { this.returnTypeResolver = returnTypeResolver; } + @Override + public SelfRenderingSqmFunction copy(SqmCopyContext context) { + final SelfRenderingSqmFunction existing = context.getCopy( this ); + if ( existing != null ) { + return existing; + } + final List> arguments = new ArrayList<>( getArguments().size() ); + for ( SqmTypedNode argument : getArguments() ) { + arguments.add( argument.copy( context ) ); + } + final SelfRenderingSqmFunction expression = context.registerCopy( + this, + new SelfRenderingSqmFunction<>( + getFunctionDescriptor(), + getRenderingSupport(), + arguments, + getImpliedResultType(), + getArgumentsValidator(), + getReturnTypeResolver(), + nodeBuilder(), + getFunctionName() + ) + ); + copyTo( expression, context ); + return expression; + } + public FunctionRenderingSupport getRenderingSupport() { return renderingSupport; } + protected ReturnableType getImpliedResultType() { + return impliedResultType; + } + protected ArgumentsValidator getArgumentsValidator() { return argumentsValidator; } + protected FunctionReturnTypeResolver getReturnTypeResolver() { + return returnTypeResolver; + } + protected static List resolveSqlAstArguments(List> sqmArguments, SqmToSqlAstConverter walker) { if ( sqmArguments == null || sqmArguments.isEmpty() ) { return emptyList(); diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/ConcreteSqmSelectQueryPlan.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/ConcreteSqmSelectQueryPlan.java index 3391ab20f4..8f227bcf1c 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/ConcreteSqmSelectQueryPlan.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/ConcreteSqmSelectQueryPlan.java @@ -85,12 +85,13 @@ public class ConcreteSqmSelectQueryPlan implements SelectQueryPlan { String hql, DomainParameterXref domainParameterXref, Class resultType, + TupleMetadata tupleMetadata, QueryOptions queryOptions) { this.sqm = sqm; this.hql = hql; this.domainParameterXref = domainParameterXref; - this.rowTransformer = determineRowTransformer( sqm, resultType, queryOptions ); + this.rowTransformer = determineRowTransformer( sqm, resultType, tupleMetadata, queryOptions ); this.listInterpreter = (unused, executionContext, sqmInterpretation, jdbcParameterBindings) -> { final SharedSessionContractImplementor session = executionContext.getSession(); @@ -178,6 +179,7 @@ public class ConcreteSqmSelectQueryPlan implements SelectQueryPlan { private RowTransformer determineRowTransformer( SqmSelectStatement sqm, Class resultType, + TupleMetadata tupleMetadata, QueryOptions queryOptions) { if ( resultType == null || resultType.isArray() ) { if ( queryOptions.getTupleTransformer() != null ) { @@ -191,27 +193,10 @@ public class ConcreteSqmSelectQueryPlan implements SelectQueryPlan { // NOTE : if we get here, a result-type of some kind (other than Object[].class) was specified final List> selections = sqm.getQueryPart().getFirstQuerySpec().getSelectClause().getSelections(); - if ( Tuple.class.isAssignableFrom( resultType ) ) { + if ( tupleMetadata != null ) { // resultType is Tuple.. if ( queryOptions.getTupleTransformer() == null ) { - final Map, Integer> tupleElementMap; - if ( selections.size() == 1 && selections.get( 0 ).getSelectableNode() instanceof CompoundSelection ) { - final List> selectionItems = selections.get( 0 ) - .getSelectableNode() - .getSelectionItems(); - tupleElementMap = new IdentityHashMap<>( selectionItems.size() ); - for ( int i = 0; i < selectionItems.size(); i++ ) { - tupleElementMap.put( selectionItems.get( i ), i ); - } - } - else { - tupleElementMap = new IdentityHashMap<>( selections.size() ); - for ( int i = 0; i < selections.size(); i++ ) { - final SqmSelection selection = selections.get( i ); - tupleElementMap.put( selection.getSelectableNode(), i ); - } - } - return (RowTransformer) new RowTransformerJpaTupleImpl( new TupleMetadata( tupleElementMap ) ); + return (RowTransformer) new RowTransformerJpaTupleImpl( tupleMetadata ); } throw new IllegalArgumentException( diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/QuerySqmImpl.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/QuerySqmImpl.java index f8223b575a..efb596ea97 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/QuerySqmImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/QuerySqmImpl.java @@ -16,6 +16,7 @@ import java.util.Collection; import java.util.Collections; import java.util.Date; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Optional; import java.util.function.Supplier; @@ -77,7 +78,6 @@ import org.hibernate.query.spi.QueryEngine; import org.hibernate.query.spi.QueryInterpretationCache; import org.hibernate.query.spi.QueryOptions; import org.hibernate.query.spi.QueryParameterBindings; -import org.hibernate.query.spi.QueryParameterImplementor; import org.hibernate.query.spi.ScrollableResultsImplementor; import org.hibernate.query.spi.SelectQueryPlan; import org.hibernate.query.sqm.SqmExpressible; @@ -85,6 +85,7 @@ import org.hibernate.query.sqm.SqmPathSource; import org.hibernate.query.sqm.internal.SqmInterpretationsKey.InterpretationsKeySource; import org.hibernate.query.sqm.mutation.spi.SqmMultiTableInsertStrategy; import org.hibernate.query.sqm.mutation.spi.SqmMultiTableMutationStrategy; +import org.hibernate.query.sqm.tree.SqmCopyContext; import org.hibernate.query.sqm.tree.SqmStatement; import org.hibernate.query.sqm.tree.SqmTypedNode; import org.hibernate.query.sqm.tree.delete.SqmDeleteStatement; @@ -103,7 +104,7 @@ import org.hibernate.query.sqm.tree.select.SqmQuerySpec; import org.hibernate.query.sqm.tree.select.SqmSelectStatement; import org.hibernate.query.sqm.tree.select.SqmSelection; import org.hibernate.query.sqm.tree.update.SqmUpdateStatement; -import org.hibernate.type.BasicType; +import org.hibernate.sql.results.internal.TupleMetadata; import org.hibernate.type.descriptor.jdbc.JdbcType; import static org.hibernate.jpa.HibernateHints.HINT_CACHEABLE; @@ -135,7 +136,7 @@ public class QuerySqmImpl private static final CoreMessageLogger LOG = CoreLogging.messageLogger( QuerySqmImpl.class ); private final String hql; - private final SqmStatement sqm; + private final SqmStatement sqm; private final ParameterMetadataImplementor parameterMetadata; private final DomainParameterXref domainParameterXref; @@ -143,6 +144,7 @@ public class QuerySqmImpl private final QueryParameterBindingsImpl parameterBindings; private final Class resultType; + private final TupleMetadata tupleMetadata; /** * Creates a Query instance from a named HQL memento @@ -171,31 +173,11 @@ public class QuerySqmImpl this.parameterBindings = QueryParameterBindingsImpl.from( parameterMetadata, session.getFactory() ); + validateStatement( sqm, resultType ); setComment( hql ); applyOptions( memento ); - } - - protected void applyOptions(NamedHqlQueryMemento memento) { - super.applyOptions( memento ); - - if ( memento.getFirstResult() != null ) { - setFirstResult( memento.getFirstResult() ); - } - - if ( memento.getMaxResults() != null ) { - setMaxResults( memento.getMaxResults() ); - } - - if ( memento.getParameterTypes() != null ) { - for ( Map.Entry entry : memento.getParameterTypes().entrySet() ) { - final QueryParameterImplementor parameter = getParameterMetadata().getQueryParameter( entry.getKey() ); - final BasicType type = getSessionFactory().getTypeConfiguration() - .getBasicTypeRegistry() - .getRegisteredType( entry.getValue() ); - parameter.applyAnticipatedType( type ); - } - } + this.tupleMetadata = buildTupleMetadata( sqm, resultType ); } /** @@ -218,24 +200,10 @@ public class QuerySqmImpl this.parameterBindings = QueryParameterBindingsImpl.from( parameterMetadata, session.getFactory() ); + validateStatement( sqm, resultType ); setComment( hql ); - //noinspection rawtypes - final SqmStatement sqmStatement = hqlInterpretation.getSqmStatement(); - if ( resultType != null ) { - SqmUtil.verifyIsSelectStatement( sqmStatement, hql ); - visitQueryReturnType( - ( (SqmSelectStatement) sqmStatement ).getQueryPart(), - resultType, - session.getFactory() - ); - } - else if ( sqmStatement instanceof SqmUpdateStatement ) { - verifyImmutableEntityUpdate( hql, (SqmUpdateStatement) sqmStatement, session.getFactory() ); - } - else if ( sqmStatement instanceof SqmInsertStatement ) { - verifyInsertTypesMatch( hql, (SqmInsertStatement) sqmStatement ); - } + this.tupleMetadata = buildTupleMetadata( sqm, resultType ); } /** @@ -247,11 +215,16 @@ public class QuerySqmImpl SharedSessionContractImplementor producer) { super( producer ); this.hql = CRITERIA_HQL_STRING; - this.sqm = criteria; + if ( producer.isJpaCriteriaCopyComplianceEnabled() ) { + this.sqm = criteria.copy( SqmCopyContext.simpleContext() ); + } + else { + this.sqm = criteria; + } setComment( hql ); - this.domainParameterXref = DomainParameterXref.from( criteria ); + this.domainParameterXref = DomainParameterXref.from( this.sqm ); if ( ! domainParameterXref.hasParameters() ) { this.parameterMetadata = ParameterMetadataImpl.EMPTY; } @@ -275,7 +248,7 @@ public class QuerySqmImpl } } - if ( resultType != null ) { + if ( sqm instanceof SqmSelectStatement ) { SqmUtil.verifyIsSelectStatement( sqm, null ); final SqmQueryPart queryPart = ( (SqmSelectStatement) sqm ).getQueryPart(); // For criteria queries, we have to validate the fetch structure here @@ -286,154 +259,56 @@ public class QuerySqmImpl producer.getFactory() ); } - else if ( sqm instanceof SqmUpdateStatement ) { - final SqmUpdateStatement updateStatement = (SqmUpdateStatement) sqm; - verifyImmutableEntityUpdate( CRITERIA_HQL_STRING, updateStatement, producer.getFactory() ); - if ( updateStatement.getSetClause() == null || updateStatement.getSetClause().getAssignments().isEmpty() ) { - throw new IllegalArgumentException( "No assignments specified as part of UPDATE criteria" ); + else { + if ( resultType != null ) { + throw new IllegalQueryOperationException( + "Result type given for a non-SELECT Query", + hql, + null + ); + } + if ( sqm instanceof SqmUpdateStatement ) { + final SqmUpdateStatement updateStatement = (SqmUpdateStatement) sqm; + verifyImmutableEntityUpdate( CRITERIA_HQL_STRING, updateStatement, producer.getFactory() ); + if ( updateStatement.getSetClause() == null || updateStatement.getSetClause() + .getAssignments() + .isEmpty() ) { + throw new IllegalArgumentException( "No assignments specified as part of UPDATE criteria" ); + } + } + else if ( sqm instanceof SqmInsertStatement ) { + verifyInsertTypesMatch( CRITERIA_HQL_STRING, (SqmInsertStatement) sqm ); } - } - else if ( sqm instanceof SqmInsertStatement ) { - verifyInsertTypesMatch( CRITERIA_HQL_STRING, (SqmInsertStatement) sqm ); } this.resultType = resultType; + this.tupleMetadata = buildTupleMetadata( criteria, resultType ); } - private void visitQueryReturnType( - SqmQueryPart queryPart, - Class resultType, - SessionFactoryImplementor factory) { - if ( queryPart instanceof SqmQuerySpec ) { - final SqmQuerySpec sqmQuerySpec = (SqmQuerySpec) queryPart; - final List> sqmSelections = sqmQuerySpec.getSelectClause().getSelections(); - - if ( sqmSelections == null || sqmSelections.isEmpty() ) { - // make sure there is at least one root - final List> sqmRoots = sqmQuerySpec.getFromClause().getRoots(); - if ( sqmRoots == null || sqmRoots.isEmpty() ) { - throw new IllegalArgumentException( "Criteria did not define any query roots" ); - } - // if there is a single root, use that as the selection - if ( sqmRoots.size() == 1 ) { - final SqmRoot sqmRoot = sqmRoots.get( 0 ); - sqmQuerySpec.getSelectClause().add( sqmRoot, null ); - } - else { - throw new IllegalArgumentException( ); - } - } - - if ( resultType != null ) { - checkQueryReturnType( sqmQuerySpec, resultType, factory ); - } - } - else { - final SqmQueryGroup queryGroup = (SqmQueryGroup) queryPart; - for ( SqmQueryPart sqmQueryPart : queryGroup.getQueryParts() ) { - visitQueryReturnType( sqmQueryPart, resultType, factory ); - } - } - } - - private static void checkQueryReturnType( - SqmQuerySpec querySpec, - Class resultClass, - SessionFactoryImplementor sessionFactory) { - if ( resultClass == null ) { - // nothing to check - return; - } - - final List> selections = querySpec.getSelectClause().getSelections(); - - if ( resultClass.isArray() ) { - // todo (6.0) : implement - } - else if ( Tuple.class.isAssignableFrom( resultClass ) ) { - // todo (6.0) : implement - } - else { - final boolean jpaQueryComplianceEnabled = sessionFactory.getSessionFactoryOptions() - .getJpaCompliance() - .isJpaQueryComplianceEnabled(); - if ( selections.size() != 1 ) { - final String errorMessage = "Query result-type error - multiple selections: use Tuple or array"; - - if ( jpaQueryComplianceEnabled ) { - throw new IllegalArgumentException( errorMessage ); - } - else { - throw new QueryTypeMismatchException( errorMessage ); - } - } - - final SqmSelection sqmSelection = selections.get( 0 ); - - if ( sqmSelection.getSelectableNode() instanceof SqmParameter ) { - final SqmParameter sqmParameter = (SqmParameter) sqmSelection.getSelectableNode(); - - // we may not yet know a selection type - if ( sqmParameter.getNodeType() == null || sqmParameter.getNodeType().getExpressibleJavaType() == null ) { - // we can't verify the result type up front - return; - } - } - - if ( jpaQueryComplianceEnabled ) { - return; - } - verifyResultType( resultClass, sqmSelection.getNodeType(), sessionFactory ); - } - } - - private static void verifyResultType( - Class resultClass, - SqmExpressible sqmExpressible, - SessionFactoryImplementor sessionFactory) { - assert sqmExpressible != null; - assert sqmExpressible.getExpressibleJavaType() != null; - final Class javaTypeClass = sqmExpressible.getExpressibleJavaType().getJavaTypeClass(); - if ( !resultClass.isAssignableFrom( javaTypeClass ) ) { - // Special case for date because we always report java.util.Date as expression type - // But the expected resultClass could be a subtype of that, so we need to check the JdbcType - if ( javaTypeClass == Date.class ) { - JdbcType jdbcType = null; - if ( sqmExpressible instanceof BasicDomainType ) { - jdbcType = ( (BasicDomainType) sqmExpressible).getJdbcType(); - } - else if ( sqmExpressible instanceof SqmPathSource ) { - final DomainType domainType = ( (SqmPathSource) sqmExpressible).getSqmPathType(); - if ( domainType instanceof BasicDomainType ) { - jdbcType = ( (BasicDomainType) domainType ).getJdbcType(); - } - } - if ( jdbcType != null ) { - switch ( jdbcType.getJdbcTypeCode() ) { - case Types.DATE: - if ( resultClass.isAssignableFrom( java.sql.Date.class ) ) { - return; - } - break; - case Types.TIME: - if ( resultClass.isAssignableFrom( java.sql.Time.class ) ) { - return; - } - break; - case Types.TIMESTAMP: - if ( resultClass.isAssignableFrom( java.sql.Timestamp.class ) ) { - return; - } - break; - } - } - } - final String errorMessage = String.format( - "Specified result type [%s] did not match Query selection type [%s] - multiple selections: use Tuple or array", - resultClass.getName(), - sqmExpressible.getExpressibleJavaType().getJavaType().getTypeName() + private void validateStatement(SqmStatement sqmStatement, Class resultType) { + if ( sqmStatement instanceof SqmSelectStatement ) { + SqmUtil.verifyIsSelectStatement( sqmStatement, hql ); + visitQueryReturnType( + ( (SqmSelectStatement) sqmStatement ).getQueryPart(), + resultType, + getSessionFactory() ); - throw new QueryTypeMismatchException( errorMessage ); + } + else { + if ( resultType != null ) { + throw new IllegalQueryOperationException( + "Result type given for a non-SELECT Query", + hql, + null + ); + } + if ( sqmStatement instanceof SqmUpdateStatement ) { + SqmUpdateStatement updateStatement = (SqmUpdateStatement) sqmStatement; + verifyImmutableEntityUpdate( hql, updateStatement, getSessionFactory() ); + } + else if ( sqmStatement instanceof SqmInsertStatement ) { + verifyInsertTypesMatch( hql, (SqmInsertStatement) sqmStatement ); + } } } @@ -548,10 +423,6 @@ public class QuerySqmImpl return parameterBindings; } - public SessionFactoryImplementor getSessionFactory() { - return getSession().getFactory(); - } - @Override public QueryParameterBindings getParameterBindings() { return getQueryParameterBindings(); @@ -739,6 +610,7 @@ public class QuerySqmImpl getQueryString(), getDomainParameterXref(), resultType, + tupleMetadata, queryOptions ); } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmSelectionQueryImpl.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmSelectionQueryImpl.java index b4b199b02b..a1ab76ef91 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmSelectionQueryImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmSelectionQueryImpl.java @@ -56,11 +56,13 @@ import org.hibernate.query.spi.ScrollableResultsImplementor; import org.hibernate.query.spi.SelectQueryPlan; import org.hibernate.query.sqm.SqmSelectionQuery; import org.hibernate.query.sqm.internal.SqmInterpretationsKey.InterpretationsKeySource; +import org.hibernate.query.sqm.tree.SqmCopyContext; import org.hibernate.query.sqm.tree.expression.JpaCriteriaParameter; import org.hibernate.query.sqm.tree.expression.SqmJpaCriteriaParameterWrapper; import org.hibernate.query.sqm.tree.expression.SqmParameter; import org.hibernate.query.sqm.tree.select.SqmSelectStatement; import org.hibernate.query.sqm.tree.select.SqmSelection; +import org.hibernate.sql.results.internal.TupleMetadata; import static org.hibernate.jpa.HibernateHints.HINT_CACHEABLE; import static org.hibernate.jpa.HibernateHints.HINT_CACHE_MODE; @@ -81,13 +83,14 @@ public class SqmSelectionQueryImpl extends AbstractSelectionQuery implemen public static final String CRITERIA_HQL_STRING = ""; private final String hql; - private final SqmSelectStatement sqm; + private final SqmSelectStatement sqm; private final ParameterMetadataImplementor parameterMetadata; private final DomainParameterXref domainParameterXref; private final QueryParameterBindingsImpl parameterBindings; private final Class resultType; + private final TupleMetadata tupleMetadata; public SqmSelectionQueryImpl( String hql, @@ -95,16 +98,19 @@ public class SqmSelectionQueryImpl extends AbstractSelectionQuery implemen SharedSessionContractImplementor session) { super( session ); this.hql = hql; - this.sqm = (SqmSelectStatement) hqlInterpretation.getSqmStatement(); + //noinspection unchecked + this.sqm = (SqmSelectStatement) hqlInterpretation.getSqmStatement(); this.parameterMetadata = hqlInterpretation.getParameterMetadata(); this.domainParameterXref = hqlInterpretation.getDomainParameterXref(); this.parameterBindings = QueryParameterBindingsImpl.from( parameterMetadata, session.getFactory() ); - this.resultType = determineResultType( sqm ); + visitQueryReturnType( sqm.getQueryPart(), null, getSessionFactory() ); + this.resultType = null; setComment( hql ); + this.tupleMetadata = null; } public SqmSelectionQueryImpl( @@ -123,51 +129,33 @@ public class SqmSelectionQueryImpl extends AbstractSelectionQuery implemen (s) -> queryEngine.getHqlTranslator().translate( hql ) ); - if ( !( hqlInterpretation.getSqmStatement() instanceof SqmSelectStatement ) ) { - throw new IllegalSelectQueryException( "Expecting a selection query, but found `" + hql + "`", hql ); - } - - this.sqm = (SqmSelectStatement) hqlInterpretation.getSqmStatement(); + SqmUtil.verifyIsSelectStatement( hqlInterpretation.getSqmStatement(), hql ); + //noinspection unchecked + this.sqm = (SqmSelectStatement) hqlInterpretation.getSqmStatement(); this.parameterMetadata = hqlInterpretation.getParameterMetadata(); this.domainParameterXref = hqlInterpretation.getDomainParameterXref(); this.parameterBindings = QueryParameterBindingsImpl.from( parameterMetadata, session.getFactory() ); + visitQueryReturnType( sqm.getQueryPart(), resultType, getSessionFactory() ); + setComment( hql ); - if ( resultType != null ) { - final Class determinedResultType = determineResultType( sqm ); - if ( !resultType.isAssignableFrom( determinedResultType ) ) { - throw new QueryTypeMismatchException( - String.format( - Locale.ROOT, - "SelectionQuery result-type error - expecting `%s`, but found `%s`", - resultType.getName(), - determinedResultType.getName() - ) - ); - } - } - } - - private static Class determineResultType(SqmSelectStatement sqm) { - final List> selections = sqm.getQuerySpec().getSelectClause().getSelections(); - if ( selections.size() == 1 ) { - final SqmSelection sqmSelection = selections.get( 0 ); - //noinspection unchecked - return (Class) sqmSelection.getNodeJavaType().getJavaTypeClass(); - } - - //noinspection unchecked - return (Class) Object[].class; + applyOptions( memento ); + this.tupleMetadata = buildTupleMetadata( sqm, resultType ); } public SqmSelectionQueryImpl( - SqmSelectStatement sqm, + SqmSelectStatement criteria, SharedSessionContractImplementor session) { super( session ); this.hql = CRITERIA_HQL_STRING; - this.sqm = sqm; + if ( session.isJpaCriteriaCopyComplianceEnabled() ) { + this.sqm = criteria.copy( SqmCopyContext.simpleContext() ); + } + else { + this.sqm = criteria; + } this.domainParameterXref = DomainParameterXref.from( sqm ); if ( ! domainParameterXref.hasParameters() ) { @@ -195,7 +183,21 @@ public class SqmSelectionQueryImpl extends AbstractSelectionQuery implemen this.resultType = determineResultType( sqm ); + visitQueryReturnType( sqm.getQueryPart(), resultType, getSessionFactory() ); setComment( hql ); + this.tupleMetadata = buildTupleMetadata( sqm, resultType ); + } + + private static Class determineResultType(SqmSelectStatement sqm) { + final List> selections = sqm.getQuerySpec().getSelectClause().getSelections(); + if ( selections.size() == 1 ) { + final SqmSelection sqmSelection = selections.get( 0 ); + //noinspection unchecked + return (Class) sqmSelection.getNodeJavaType().getJavaTypeClass(); + } + + //noinspection unchecked + return (Class) Object[].class; } @SuppressWarnings("rawtypes") @@ -217,10 +219,6 @@ public class SqmSelectionQueryImpl extends AbstractSelectionQuery implemen return parameterBindings; } - protected SessionFactoryImplementor getSessionFactory() { - return getSession().getSessionFactory(); - } - @Override public String getQueryString() { return hql; @@ -373,6 +371,7 @@ public class SqmSelectionQueryImpl extends AbstractSelectionQuery implemen getQueryString(), getDomainParameterXref(), resultType, + tupleMetadata, queryOptions ); } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmUtil.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmUtil.java index 33421318e4..a6fe262416 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmUtil.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmUtil.java @@ -34,6 +34,7 @@ import org.hibernate.metamodel.mapping.PluralAttributeMapping; import org.hibernate.metamodel.mapping.internal.ToOneAttributeMapping; import org.hibernate.metamodel.model.convert.spi.BasicValueConverter; import org.hibernate.query.IllegalQueryOperationException; +import org.hibernate.query.IllegalSelectQueryException; import org.hibernate.query.spi.NavigablePath; import org.hibernate.query.spi.QueryParameterBinding; import org.hibernate.query.spi.QueryParameterBindings; @@ -78,15 +79,14 @@ public class SqmUtil { public static void verifyIsSelectStatement(SqmStatement sqm, String hqlString) { if ( ! isSelect( sqm ) ) { - throw new IllegalQueryOperationException( + throw new IllegalSelectQueryException( String.format( Locale.ROOT, "Expecting a SELECT Query [%s], but found %s", SqmSelectStatement.class.getName(), sqm.getClass().getName() ), - hqlString, - null + hqlString ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/AbstractSqmDmlStatement.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/AbstractSqmDmlStatement.java index 54ae7b8e20..c12ad48013 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/AbstractSqmDmlStatement.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/AbstractSqmDmlStatement.java @@ -9,10 +9,12 @@ package org.hibernate.query.sqm.tree; import java.util.Collection; import java.util.LinkedHashMap; import java.util.Map; +import java.util.Set; import org.hibernate.query.sqm.NodeBuilder; import org.hibernate.query.sqm.SqmQuerySource; import org.hibernate.query.sqm.tree.cte.SqmCteStatement; +import org.hibernate.query.sqm.tree.expression.SqmParameter; import org.hibernate.query.sqm.tree.from.SqmRoot; import org.hibernate.query.sqm.tree.select.SqmSubQuery; @@ -22,12 +24,13 @@ import org.hibernate.query.sqm.tree.select.SqmSubQuery; public abstract class AbstractSqmDmlStatement extends AbstractSqmStatement implements SqmDmlStatement { - private final Map> cteStatements = new LinkedHashMap<>(); + private final Map> cteStatements; private boolean withRecursiveCte; private SqmRoot target; public AbstractSqmDmlStatement(SqmQuerySource querySource, NodeBuilder nodeBuilder) { super( querySource, nodeBuilder ); + this.cteStatements = new LinkedHashMap<>(); } public AbstractSqmDmlStatement(SqmRoot target, SqmQuerySource querySource, NodeBuilder nodeBuilder) { @@ -35,6 +38,27 @@ public abstract class AbstractSqmDmlStatement this.target = target; } + public AbstractSqmDmlStatement( + NodeBuilder builder, + SqmQuerySource querySource, + Set> parameters, + Map> cteStatements, + boolean withRecursiveCte, + SqmRoot target) { + super( builder, querySource, parameters ); + this.cteStatements = cteStatements; + this.withRecursiveCte = withRecursiveCte; + this.target = target; + } + + protected Map> copyCteStatements(SqmCopyContext context) { + final Map> cteStatements = new LinkedHashMap<>( this.cteStatements.size() ); + for ( Map.Entry> entry : this.cteStatements.entrySet() ) { + cteStatements.put( entry.getKey(), entry.getValue().copy( context ) ); + } + return cteStatements; + } + @Override public boolean isWithRecursive() { return withRecursiveCte; diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/AbstractSqmRestrictedDmlStatement.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/AbstractSqmRestrictedDmlStatement.java index fe3d895863..ccc12462ed 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/AbstractSqmRestrictedDmlStatement.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/AbstractSqmRestrictedDmlStatement.java @@ -6,20 +6,25 @@ */ package org.hibernate.query.sqm.tree; -import jakarta.persistence.criteria.Expression; -import jakarta.persistence.criteria.Predicate; -import jakarta.persistence.criteria.Root; -import jakarta.persistence.metamodel.EntityType; +import java.util.Map; +import java.util.Set; import org.hibernate.metamodel.model.domain.EntityDomainType; import org.hibernate.query.criteria.JpaCriteriaBase; import org.hibernate.query.criteria.JpaPredicate; import org.hibernate.query.sqm.NodeBuilder; import org.hibernate.query.sqm.SqmQuerySource; +import org.hibernate.query.sqm.tree.cte.SqmCteStatement; +import org.hibernate.query.sqm.tree.expression.SqmParameter; import org.hibernate.query.sqm.tree.from.SqmRoot; import org.hibernate.query.sqm.tree.predicate.SqmPredicate; import org.hibernate.query.sqm.tree.predicate.SqmWhereClause; +import jakarta.persistence.criteria.Expression; +import jakarta.persistence.criteria.Predicate; +import jakarta.persistence.criteria.Root; +import jakarta.persistence.metamodel.EntityType; + /** * @author Christian Beikov */ @@ -36,6 +41,27 @@ public abstract class AbstractSqmRestrictedDmlStatement extends AbstractSqmDm super( target, querySource, nodeBuilder ); } + public AbstractSqmRestrictedDmlStatement( + NodeBuilder builder, + SqmQuerySource querySource, + Set> parameters, + Map> cteStatements, + boolean withRecursiveCte, + SqmRoot target) { + super( builder, querySource, parameters, cteStatements, withRecursiveCte, target ); + } + + protected SqmWhereClause copyWhereClause(SqmCopyContext context) { + if ( getWhereClause() == null ) { + return null; + } + else { + final SqmWhereClause whereClause = new SqmWhereClause( nodeBuilder() ); + whereClause.setPredicate( getWhereClause().getPredicate().copy( context ) ); + return whereClause; + } + } + public Root from(Class entityClass) { final EntityDomainType entity = nodeBuilder().getDomainModel().entity( entityClass ); SqmRoot root = new SqmRoot<>( entity, null, false, nodeBuilder() ); diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/AbstractSqmStatement.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/AbstractSqmStatement.java index e50dd95b0e..0be7c2a676 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/AbstractSqmStatement.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/AbstractSqmStatement.java @@ -12,15 +12,16 @@ import java.util.Set; import org.hibernate.query.sqm.NodeBuilder; import org.hibernate.query.sqm.SqmQuerySource; +import org.hibernate.query.sqm.internal.ParameterCollector; import org.hibernate.query.sqm.internal.SqmUtil; import org.hibernate.query.sqm.tree.expression.SqmParameter; -import org.hibernate.query.sqm.internal.ParameterCollector; /** * @author Steve Ebersole */ public abstract class AbstractSqmStatement extends AbstractSqmNode implements SqmStatement, ParameterCollector { private final SqmQuerySource querySource; + private Set> parameters; public AbstractSqmStatement( SqmQuerySource querySource, @@ -29,7 +30,27 @@ public abstract class AbstractSqmStatement extends AbstractSqmNode implements this.querySource = querySource; } - private Set> parameters; + protected AbstractSqmStatement( + NodeBuilder builder, + SqmQuerySource querySource, + Set> parameters) { + super( builder ); + this.querySource = querySource; + this.parameters = parameters; + } + + protected Set> copyParameters(SqmCopyContext context) { + if ( parameters == null ) { + return null; + } + else { + final Set> parameters = new HashSet<>( this.parameters.size() ); + for ( SqmParameter parameter : this.parameters ) { + parameters.add( parameter.copy( context ) ); + } + return parameters; + } + } @Override public SqmQuerySource getQuerySource() { diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/SqmCopyContext.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/SqmCopyContext.java new file mode 100644 index 0000000000..07aea79bf6 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/SqmCopyContext.java @@ -0,0 +1,40 @@ +/* + * 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.tree; + +import java.util.IdentityHashMap; + +/** + * + */ +public interface SqmCopyContext { + + T getCopy(T original); + + T registerCopy(T original, T copy); + + static SqmCopyContext simpleContext() { + final IdentityHashMap map = new IdentityHashMap<>(); + return new SqmCopyContext() { + @Override + public T getCopy(T original) { + //noinspection unchecked + return (T) map.get( original ); + } + + @Override + public T registerCopy(T original, T copy) { + final Object old = map.put( original, copy ); + if ( old != null ) { + throw new IllegalArgumentException( "Already registered a copy: " + old ); + } + return copy; + } + + }; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/SqmNode.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/SqmNode.java index 933f3db33f..2cdde43070 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/SqmNode.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/SqmNode.java @@ -25,4 +25,6 @@ public interface SqmNode extends JpaCriteriaNode { } NodeBuilder nodeBuilder(); + + SqmNode copy(SqmCopyContext context); } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/SqmQuery.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/SqmQuery.java index da43406ea6..ae1010aa09 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/SqmQuery.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/SqmQuery.java @@ -14,4 +14,6 @@ import org.hibernate.query.criteria.JpaCriteriaBase; * @author Steve Ebersole */ public interface SqmQuery extends JpaCriteriaBase, SqmNode { + @Override + SqmQuery copy(SqmCopyContext context); } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/SqmStatement.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/SqmStatement.java index 318e6461fc..2682db3c60 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/SqmStatement.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/SqmStatement.java @@ -33,6 +33,9 @@ public interface SqmStatement extends SqmQuery, JpaQueryableCriteria, S ParameterResolutions resolveParameters(); + @Override + SqmStatement copy(SqmCopyContext context); + interface ParameterResolutions { ParameterResolutions EMPTY = new ParameterResolutions() { @Override diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/SqmTypedNode.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/SqmTypedNode.java index 8cdbe525a0..a95cf0f06a 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/SqmTypedNode.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/SqmTypedNode.java @@ -30,4 +30,7 @@ public interface SqmTypedNode extends SqmNode, SqmExpressibleAccessor { } SqmExpressible getNodeType(); + + @Override + SqmTypedNode copy(SqmCopyContext context); } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/cte/SqmCteStatement.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/cte/SqmCteStatement.java index bdc9236b71..e82dd3475d 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/cte/SqmCteStatement.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/cte/SqmCteStatement.java @@ -15,6 +15,7 @@ import org.hibernate.sql.ast.tree.cte.CteSearchClauseKind; import org.hibernate.query.sqm.NodeBuilder; import org.hibernate.query.sqm.SemanticQueryWalker; import org.hibernate.query.sqm.tree.AbstractSqmNode; +import org.hibernate.query.sqm.tree.SqmCopyContext; import org.hibernate.query.sqm.tree.SqmStatement; import org.hibernate.query.sqm.tree.SqmVisitableNode; @@ -70,6 +71,55 @@ public class SqmCteStatement extends AbstractSqmNode implements SqmVisitableN this.noCycleValue = '\0'; } + private SqmCteStatement( + NodeBuilder builder, + SqmCteContainer cteContainer, + SqmCteTable cteTable, + CteMaterialization materialization, + SqmStatement cteDefinition, + CteSearchClauseKind searchClauseKind, + List searchBySpecifications, + List cycleColumns, + SqmCteTableColumn cycleMarkColumn, + char cycleValue, + char noCycleValue) { + super( builder ); + this.cteContainer = cteContainer; + this.cteTable = cteTable; + this.materialization = materialization; + this.cteDefinition = cteDefinition; + this.searchClauseKind = searchClauseKind; + this.searchBySpecifications = searchBySpecifications; + this.cycleColumns = cycleColumns; + this.cycleMarkColumn = cycleMarkColumn; + this.cycleValue = cycleValue; + this.noCycleValue = noCycleValue; + } + + @Override + public SqmCteStatement copy(SqmCopyContext context) { + final SqmCteStatement existing = context.getCopy( this ); + if ( existing != null ) { + return existing; + } + return context.registerCopy( + this, + new SqmCteStatement<>( + nodeBuilder(), + cteContainer, + cteTable, + materialization, + cteDefinition.copy( context ), + searchClauseKind, + searchBySpecifications, + cycleColumns, + cycleMarkColumn, + cycleValue, + noCycleValue + ) + ); + } + public SqmCteTable getCteTable() { return cteTable; } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/cte/SqmSearchClauseSpecification.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/cte/SqmSearchClauseSpecification.java index 2d11de8ab9..9fdfe26fbe 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/cte/SqmSearchClauseSpecification.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/cte/SqmSearchClauseSpecification.java @@ -10,6 +10,7 @@ import java.io.Serializable; import org.hibernate.query.sqm.NullPrecedence; import org.hibernate.query.sqm.SortOrder; +import org.hibernate.query.sqm.tree.SqmCopyContext; /** * @author Christian Beikov @@ -25,6 +26,14 @@ public class SqmSearchClauseSpecification implements Serializable { this.nullPrecedence = nullPrecedence; } + public SqmSearchClauseSpecification copy(SqmCopyContext context) { + return new SqmSearchClauseSpecification( + cteColumn, + sortOrder, + nullPrecedence + ); + } + public SqmCteTableColumn getCteColumn() { return cteColumn; } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/delete/SqmDeleteStatement.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/delete/SqmDeleteStatement.java index b8772eef79..5a14db0310 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/delete/SqmDeleteStatement.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/delete/SqmDeleteStatement.java @@ -6,16 +6,23 @@ */ package org.hibernate.query.sqm.tree.delete; -import jakarta.persistence.criteria.Expression; -import jakarta.persistence.criteria.Predicate; +import java.util.Map; +import java.util.Set; import org.hibernate.query.criteria.JpaCriteriaDelete; import org.hibernate.query.sqm.NodeBuilder; import org.hibernate.query.sqm.SemanticQueryWalker; import org.hibernate.query.sqm.SqmQuerySource; import org.hibernate.query.sqm.tree.AbstractSqmRestrictedDmlStatement; +import org.hibernate.query.sqm.tree.SqmCopyContext; import org.hibernate.query.sqm.tree.SqmDeleteOrUpdateStatement; +import org.hibernate.query.sqm.tree.cte.SqmCteStatement; +import org.hibernate.query.sqm.tree.expression.SqmParameter; import org.hibernate.query.sqm.tree.from.SqmRoot; +import org.hibernate.query.sqm.tree.predicate.SqmWhereClause; + +import jakarta.persistence.criteria.Expression; +import jakarta.persistence.criteria.Predicate; /** * @author Steve Ebersole @@ -41,6 +48,37 @@ public class SqmDeleteStatement ); } + public SqmDeleteStatement( + NodeBuilder builder, + SqmQuerySource querySource, + Set> parameters, + Map> cteStatements, + boolean withRecursiveCte, + SqmRoot target) { + super( builder, querySource, parameters, cteStatements, withRecursiveCte, target ); + } + + @Override + public SqmDeleteStatement copy(SqmCopyContext context) { + final SqmDeleteStatement existing = context.getCopy( this ); + if ( existing != null ) { + return existing; + } + final SqmDeleteStatement statement = context.registerCopy( + this, + new SqmDeleteStatement<>( + nodeBuilder(), + getQuerySource(), + copyParameters( context ), + copyCteStatements( context ), + isWithRecursive(), + getTarget().copy( context ) + ) + ); + statement.setWhereClause( copyWhereClause( context ) ); + return statement; + } + @Override public SqmDeleteStatement where(Expression restriction) { setWhere( restriction ); diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/AbstractSqmAttributeJoin.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/AbstractSqmAttributeJoin.java index 4934556bb9..4b4d1c0ebb 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/AbstractSqmAttributeJoin.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/AbstractSqmAttributeJoin.java @@ -6,19 +6,16 @@ */ package org.hibernate.query.sqm.tree.domain; -import jakarta.persistence.criteria.Expression; -import jakarta.persistence.criteria.JoinType; -import jakarta.persistence.criteria.Predicate; - import org.hibernate.metamodel.model.domain.PersistentAttribute; import org.hibernate.query.spi.NavigablePath; import org.hibernate.query.criteria.JpaExpression; import org.hibernate.query.criteria.JpaPredicate; import org.hibernate.query.sqm.NodeBuilder; +import org.hibernate.query.sqm.SemanticQueryWalker; import org.hibernate.query.sqm.SqmJoinable; import org.hibernate.query.sqm.SqmPathSource; -import org.hibernate.query.sqm.SemanticQueryWalker; import org.hibernate.query.sqm.spi.SqmCreationHelper; +import org.hibernate.query.sqm.tree.SqmCopyContext; import org.hibernate.query.sqm.tree.SqmJoinType; import org.hibernate.query.sqm.tree.from.SqmAttributeJoin; import org.hibernate.query.sqm.tree.from.SqmFrom; @@ -27,6 +24,10 @@ import org.hibernate.type.descriptor.java.JavaType; import org.jboss.logging.Logger; +import jakarta.persistence.criteria.Expression; +import jakarta.persistence.criteria.JoinType; +import jakarta.persistence.criteria.Predicate; + /** * Models a join based on a mapped attribute reference. * @@ -79,6 +80,11 @@ public abstract class AbstractSqmAttributeJoin this.fetched = fetched; } + protected void copyTo(AbstractSqmAttributeJoin target, SqmCopyContext context) { + super.copyTo( target, context ); + this.onClausePredicate = onClausePredicate == null ? null : onClausePredicate.copy( context ); + } + @Override public SqmFrom getLhs() { //noinspection unchecked diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/AbstractSqmFrom.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/AbstractSqmFrom.java index aecfb7b642..390a767183 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/AbstractSqmFrom.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/AbstractSqmFrom.java @@ -14,15 +14,6 @@ import java.util.Objects; import java.util.Set; import java.util.function.Consumer; import java.util.stream.Collectors; -import jakarta.persistence.criteria.Fetch; -import jakarta.persistence.criteria.Join; -import jakarta.persistence.criteria.JoinType; -import jakarta.persistence.metamodel.CollectionAttribute; -import jakarta.persistence.metamodel.ListAttribute; -import jakarta.persistence.metamodel.MapAttribute; -import jakarta.persistence.metamodel.PluralAttribute; -import jakarta.persistence.metamodel.SetAttribute; -import jakarta.persistence.metamodel.SingularAttribute; import org.hibernate.metamodel.mapping.ModelPartContainer; import org.hibernate.metamodel.model.domain.BagPersistentAttribute; @@ -34,14 +25,16 @@ import org.hibernate.metamodel.model.domain.PluralPersistentAttribute; import org.hibernate.metamodel.model.domain.SetPersistentAttribute; import org.hibernate.metamodel.model.domain.SingularPersistentAttribute; import org.hibernate.query.spi.NavigablePath; +import org.hibernate.query.SemanticException; import org.hibernate.query.criteria.JpaEntityJoin; import org.hibernate.query.criteria.JpaPath; import org.hibernate.query.criteria.JpaSelection; +import org.hibernate.query.hql.spi.SqmCreationState; import org.hibernate.query.sqm.NodeBuilder; -import org.hibernate.query.SemanticException; import org.hibernate.query.sqm.SqmPathSource; import org.hibernate.query.hql.spi.SqmCreationState; import org.hibernate.query.sqm.spi.SqmCreationHelper; +import org.hibernate.query.sqm.tree.SqmCopyContext; import org.hibernate.query.sqm.tree.SqmJoinType; import org.hibernate.query.sqm.tree.from.SqmAttributeJoin; import org.hibernate.query.sqm.tree.from.SqmCrossJoin; @@ -50,6 +43,16 @@ import org.hibernate.query.sqm.tree.from.SqmFrom; import org.hibernate.query.sqm.tree.from.SqmJoin; import org.hibernate.query.sqm.tree.from.SqmRoot; +import jakarta.persistence.criteria.Fetch; +import jakarta.persistence.criteria.Join; +import jakarta.persistence.criteria.JoinType; +import jakarta.persistence.metamodel.CollectionAttribute; +import jakarta.persistence.metamodel.ListAttribute; +import jakarta.persistence.metamodel.MapAttribute; +import jakarta.persistence.metamodel.PluralAttribute; +import jakarta.persistence.metamodel.SetAttribute; +import jakarta.persistence.metamodel.SingularAttribute; + /** * Convenience base class for SqmFrom implementations * @@ -115,6 +118,22 @@ public abstract class AbstractSqmFrom extends AbstractSqmPath implements super( navigablePath, referencedNavigable, null, nodeBuilder ); } + protected void copyTo(AbstractSqmFrom target, SqmCopyContext context) { + super.copyTo( target, context ); + if ( joins != null ) { + target.joins = new ArrayList<>( joins.size() ); + for ( SqmJoin join : joins ) { + target.joins.add( join.copy( context ) ); + } + } + if ( treats != null ) { + target.treats = new ArrayList<>( treats.size() ); + for ( SqmFrom treat : treats ) { + target.treats.add( treat.copy( context ) ); + } + } + } + @Override public String getExplicitAlias() { return alias; diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/AbstractSqmPath.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/AbstractSqmPath.java index 1c2b713950..94b29c5407 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/AbstractSqmPath.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/AbstractSqmPath.java @@ -21,8 +21,11 @@ import org.hibernate.metamodel.model.domain.PersistentAttribute; import org.hibernate.query.spi.NavigablePath; import org.hibernate.query.sqm.NodeBuilder; import org.hibernate.query.sqm.SqmPathSource; +import org.hibernate.query.sqm.tree.SqmCopyContext; import org.hibernate.query.sqm.tree.expression.AbstractSqmExpression; import org.hibernate.query.sqm.tree.expression.SqmExpression; +import org.hibernate.query.sqm.tree.from.SqmFrom; +import org.hibernate.query.sqm.tree.from.SqmJoin; /** * @author Steve Ebersole @@ -48,6 +51,16 @@ public abstract class AbstractSqmPath extends AbstractSqmExpression implem this.lhs = lhs; } + protected void copyTo(AbstractSqmPath target, SqmCopyContext context) { + super.copyTo( target, context ); + if ( reusablePaths != null ) { + target.reusablePaths = new HashMap<>( reusablePaths.size() ); + for ( Map.Entry> entry : reusablePaths.entrySet() ) { + target.reusablePaths.put( entry.getKey(), entry.getValue().copy( context ) ); + } + } + } + @Override public SqmPathSource getNodeType() { return (SqmPathSource) super.getNodeType(); diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/NonAggregatedCompositeSimplePath.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/NonAggregatedCompositeSimplePath.java index d57960c70b..c60b37358d 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/NonAggregatedCompositeSimplePath.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/NonAggregatedCompositeSimplePath.java @@ -6,8 +6,6 @@ */ package org.hibernate.query.sqm.tree.domain; -import jakarta.persistence.metamodel.EntityType; - import org.hibernate.metamodel.model.domain.EntityDomainType; import org.hibernate.query.spi.NavigablePath; import org.hibernate.query.PathException; @@ -15,6 +13,9 @@ import org.hibernate.query.hql.spi.SqmCreationState; import org.hibernate.query.sqm.NodeBuilder; import org.hibernate.query.sqm.SemanticQueryWalker; import org.hibernate.query.sqm.SqmPathSource; +import org.hibernate.query.sqm.tree.SqmCopyContext; + +import jakarta.persistence.metamodel.EntityType; /** * @author Andrea Boriero @@ -30,7 +31,27 @@ public class NonAggregatedCompositeSimplePath extends SqmEntityValuedSimplePa assert referencedPathSource.getSqmPathType() instanceof EntityType; } - + + @Override + public NonAggregatedCompositeSimplePath copy(SqmCopyContext context) { + final NonAggregatedCompositeSimplePath existing = context.getCopy( this ); + if ( existing != null ) { + return existing; + } + + final NonAggregatedCompositeSimplePath path = context.registerCopy( + this, + new NonAggregatedCompositeSimplePath<>( + getNavigablePath(), + getReferencedPathSource(), + getLhs().copy( context ), + nodeBuilder() + ) + ); + copyTo( path, context ); + return path; + } + @Override public SqmPath resolvePathPart( String name, diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmAnyValuedSimplePath.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmAnyValuedSimplePath.java index 6fff3a7349..370c7cf66c 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmAnyValuedSimplePath.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmAnyValuedSimplePath.java @@ -11,10 +11,11 @@ import org.hibernate.metamodel.model.domain.AnyMappingDomainType; import org.hibernate.metamodel.model.domain.EntityDomainType; import org.hibernate.query.spi.NavigablePath; import org.hibernate.query.PathException; -import org.hibernate.query.sqm.NodeBuilder; -import org.hibernate.query.sqm.SqmPathSource; -import org.hibernate.query.sqm.SemanticQueryWalker; import org.hibernate.query.hql.spi.SqmCreationState; +import org.hibernate.query.sqm.NodeBuilder; +import org.hibernate.query.sqm.SemanticQueryWalker; +import org.hibernate.query.sqm.SqmPathSource; +import org.hibernate.query.sqm.tree.SqmCopyContext; /** * @author Steve Ebersole @@ -42,6 +43,27 @@ public class SqmAnyValuedSimplePath extends AbstractSqmSimplePath { assert referencedPathSource.getSqmPathType() instanceof AnyMappingDomainType; } + @Override + public SqmAnyValuedSimplePath copy(SqmCopyContext context) { + final SqmAnyValuedSimplePath existing = context.getCopy( this ); + if ( existing != null ) { + return existing; + } + + final SqmAnyValuedSimplePath path = context.registerCopy( + this, + new SqmAnyValuedSimplePath<>( + getNavigablePath(), + getReferencedPathSource(), + getLhs().copy( context ), + getExplicitAlias(), + nodeBuilder() + ) + ); + copyTo( path, context ); + return path; + } + @Override public SqmTreatedPath treatAs(Class treatJavaType) throws PathException { throw new NotYetImplementedFor6Exception(); diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmBagJoin.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmBagJoin.java index 360d8e37ba..8a4d6c6357 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmBagJoin.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmBagJoin.java @@ -7,8 +7,6 @@ package org.hibernate.query.sqm.tree.domain; import java.util.Collection; -import jakarta.persistence.criteria.Expression; -import jakarta.persistence.criteria.Predicate; import org.hibernate.metamodel.model.domain.BagPersistentAttribute; import org.hibernate.metamodel.model.domain.EntityDomainType; @@ -18,10 +16,14 @@ import org.hibernate.query.criteria.JpaExpression; import org.hibernate.query.criteria.JpaPredicate; import org.hibernate.query.hql.spi.SqmCreationProcessingState; import org.hibernate.query.sqm.NodeBuilder; +import org.hibernate.query.sqm.tree.SqmCopyContext; import org.hibernate.query.sqm.tree.SqmJoinType; import org.hibernate.query.sqm.tree.from.SqmAttributeJoin; import org.hibernate.query.sqm.tree.from.SqmFrom; +import jakarta.persistence.criteria.Expression; +import jakarta.persistence.criteria.Predicate; + /** * @author Steve Ebersole */ @@ -47,6 +49,28 @@ public class SqmBagJoin extends AbstractSqmPluralJoin, E> super( lhs, navigablePath, attribute, alias, joinType, fetched, nodeBuilder ); } + @Override + public SqmBagJoin copy(SqmCopyContext context) { + final SqmBagJoin existing = context.getCopy( this ); + if ( existing != null ) { + return existing; + } + final SqmBagJoin path = context.registerCopy( + this, + new SqmBagJoin<>( + getLhs().copy( context ), + getNavigablePath(), + getReferencedPathSource(), + getExplicitAlias(), + getSqmJoinType(), + isFetched(), + nodeBuilder() + ) + ); + copyTo( path, context ); + return path; + } + @Override public BagPersistentAttribute getReferencedPathSource() { return (BagPersistentAttribute) super.getReferencedPathSource(); diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmBasicValuedEntityTypePath.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmBasicValuedEntityTypePath.java deleted file mode 100644 index eabb544a2e..0000000000 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmBasicValuedEntityTypePath.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * 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.tree.domain; - -import org.hibernate.metamodel.model.domain.EntityDomainType; -import org.hibernate.query.spi.NavigablePath; -import org.hibernate.query.sqm.NodeBuilder; -import org.hibernate.query.sqm.SemanticQueryWalker; -import org.hibernate.query.sqm.tree.expression.SqmLiteralEntityType; - -/** - * A path that is wrapping an entity type literal. - * - * @author Christian Beikov - * @see SqmPath#type() - */ -public class SqmBasicValuedEntityTypePath extends SqmBasicValuedSimplePath { - - private final SqmLiteralEntityType literal; - - public SqmBasicValuedEntityTypePath( - NavigablePath navigablePath, - EntityDomainType referencedPathSource, - SqmPath lhs, - NodeBuilder nodeBuilder) { - this( navigablePath, referencedPathSource, lhs, null, nodeBuilder ); - } - - public SqmBasicValuedEntityTypePath( - NavigablePath navigablePath, - EntityDomainType referencedPathSource, - SqmPath lhs, - String explicitAlias, - NodeBuilder nodeBuilder) { - super( navigablePath, referencedPathSource, lhs, explicitAlias, nodeBuilder ); - this.literal = new SqmLiteralEntityType<>( referencedPathSource, nodeBuilder ); - } - - // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - // Visitation - - @Override - public X accept(SemanticQueryWalker walker) { - return walker.visitEntityTypeLiteralExpression( literal ); - } - - @Override - public void appendHqlString(StringBuilder sb) { - sb.append( "type(" ); - super.appendHqlString( sb ); - sb.append( ')' ); - } -} diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmBasicValuedSimplePath.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmBasicValuedSimplePath.java index 4da4150883..9b8d041b80 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmBasicValuedSimplePath.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmBasicValuedSimplePath.java @@ -16,6 +16,7 @@ import org.hibernate.query.sqm.NodeBuilder; import org.hibernate.query.sqm.SemanticQueryWalker; import org.hibernate.query.sqm.SqmExpressible; import org.hibernate.query.sqm.SqmPathSource; +import org.hibernate.query.sqm.tree.SqmCopyContext; import org.hibernate.type.descriptor.java.BasicJavaType; import org.hibernate.type.descriptor.java.JavaType; @@ -42,6 +43,27 @@ public class SqmBasicValuedSimplePath super( navigablePath, referencedPathSource, lhs, explicitAlias, nodeBuilder ); } + @Override + public SqmBasicValuedSimplePath copy(SqmCopyContext context) { + final SqmBasicValuedSimplePath existing = context.getCopy( this ); + if ( existing != null ) { + return existing; + } + + final SqmBasicValuedSimplePath path = context.registerCopy( + this, + new SqmBasicValuedSimplePath<>( + getNavigablePath(), + getReferencedPathSource(), + getLhs().copy( context ), + getExplicitAlias(), + nodeBuilder() + ) + ); + copyTo( path, context ); + return path; + } + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // SemanticPathPart diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedBagJoin.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedBagJoin.java index 35c8126473..327adf9293 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedBagJoin.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedBagJoin.java @@ -10,6 +10,7 @@ import org.hibernate.metamodel.model.domain.BagPersistentAttribute; import org.hibernate.query.hql.spi.SqmCreationProcessingState; import org.hibernate.query.hql.spi.SqmPathRegistry; import org.hibernate.query.sqm.NodeBuilder; +import org.hibernate.query.sqm.tree.SqmCopyContext; import org.hibernate.query.sqm.tree.SqmJoinType; import org.hibernate.query.sqm.tree.from.SqmFrom; import org.hibernate.query.sqm.tree.from.SqmRoot; @@ -49,6 +50,29 @@ public class SqmCorrelatedBagJoin extends SqmBagJoin implements SqmC this.correlationParent = correlationParent; } + @Override + public SqmCorrelatedBagJoin copy(SqmCopyContext context) { + final SqmCorrelatedBagJoin existing = context.getCopy( this ); + if ( existing != null ) { + return existing; + } + final SqmCorrelatedBagJoin path = context.registerCopy( + this, + new SqmCorrelatedBagJoin<>( + getLhs().copy( context ), + getReferencedPathSource(), + getExplicitAlias(), + getSqmJoinType(), + isFetched(), + nodeBuilder(), + correlatedRootJoin.copy( context ), + correlationParent.copy( context ) + ) + ); + copyTo( path, context ); + return path; + } + @Override public SqmBagJoin getCorrelationParent() { return correlationParent; diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedCrossJoin.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedCrossJoin.java index 77c48c0623..811d78918c 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedCrossJoin.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedCrossJoin.java @@ -9,6 +9,7 @@ package org.hibernate.query.sqm.tree.domain; import org.hibernate.metamodel.model.domain.EntityDomainType; import org.hibernate.query.hql.spi.SqmCreationProcessingState; import org.hibernate.query.hql.spi.SqmPathRegistry; +import org.hibernate.query.sqm.tree.SqmCopyContext; import org.hibernate.query.sqm.tree.from.SqmCrossJoin; import org.hibernate.query.sqm.tree.from.SqmRoot; @@ -41,6 +42,26 @@ public class SqmCorrelatedCrossJoin extends SqmCrossJoin implements SqmCor this.correlationParent = correlationParent; } + @Override + public SqmCorrelatedCrossJoin copy(SqmCopyContext context) { + final SqmCorrelatedCrossJoin existing = context.getCopy( this ); + if ( existing != null ) { + return existing; + } + final SqmCorrelatedCrossJoin path = context.registerCopy( + this, + new SqmCorrelatedCrossJoin<>( + getReferencedPathSource(), + getExplicitAlias(), + getRoot().copy( context ), + correlatedRootJoin.copy( context ), + correlationParent.copy( context ) + ) + ); + copyTo( path, context ); + return path; + } + @Override public SqmCrossJoin getCorrelationParent() { return correlationParent; diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedEntityJoin.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedEntityJoin.java index ed9a9a0fd0..a5820157e0 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedEntityJoin.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedEntityJoin.java @@ -9,6 +9,7 @@ package org.hibernate.query.sqm.tree.domain; import org.hibernate.metamodel.model.domain.EntityDomainType; import org.hibernate.query.hql.spi.SqmCreationProcessingState; import org.hibernate.query.hql.spi.SqmPathRegistry; +import org.hibernate.query.sqm.tree.SqmCopyContext; import org.hibernate.query.sqm.tree.SqmJoinType; import org.hibernate.query.sqm.tree.from.SqmEntityJoin; import org.hibernate.query.sqm.tree.from.SqmRoot; @@ -44,6 +45,27 @@ public class SqmCorrelatedEntityJoin extends SqmEntityJoin implements SqmC this.correlationParent = correlationParent; } + @Override + public SqmCorrelatedEntityJoin copy(SqmCopyContext context) { + final SqmCorrelatedEntityJoin existing = context.getCopy( this ); + if ( existing != null ) { + return existing; + } + final SqmCorrelatedEntityJoin path = context.registerCopy( + this, + new SqmCorrelatedEntityJoin<>( + getReferencedPathSource(), + getExplicitAlias(), + getSqmJoinType(), + getRoot().copy( context ), + correlatedRootJoin.copy( context ), + correlationParent.copy( context ) + ) + ); + copyTo( path, context ); + return path; + } + @Override public SqmEntityJoin getCorrelationParent() { return correlationParent; diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedListJoin.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedListJoin.java index 09a2e5dd58..f94a42ff4e 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedListJoin.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedListJoin.java @@ -10,6 +10,7 @@ import org.hibernate.metamodel.model.domain.ListPersistentAttribute; import org.hibernate.query.hql.spi.SqmCreationProcessingState; import org.hibernate.query.hql.spi.SqmPathRegistry; import org.hibernate.query.sqm.NodeBuilder; +import org.hibernate.query.sqm.tree.SqmCopyContext; import org.hibernate.query.sqm.tree.SqmJoinType; import org.hibernate.query.sqm.tree.from.SqmFrom; import org.hibernate.query.sqm.tree.from.SqmRoot; @@ -49,6 +50,29 @@ public class SqmCorrelatedListJoin extends SqmListJoin implements Sq this.correlationParent = correlationParent; } + @Override + public SqmCorrelatedListJoin copy(SqmCopyContext context) { + final SqmCorrelatedListJoin existing = context.getCopy( this ); + if ( existing != null ) { + return existing; + } + final SqmCorrelatedListJoin path = context.registerCopy( + this, + new SqmCorrelatedListJoin<>( + getLhs().copy( context ), + getReferencedPathSource(), + getExplicitAlias(), + getSqmJoinType(), + isFetched(), + nodeBuilder(), + correlatedRootJoin.copy( context ), + correlationParent.copy( context ) + ) + ); + copyTo( path, context ); + return path; + } + @Override public SqmListJoin getCorrelationParent() { return correlationParent; diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedMapJoin.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedMapJoin.java index 21273ddd02..0a4ccf8246 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedMapJoin.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedMapJoin.java @@ -10,6 +10,7 @@ import org.hibernate.metamodel.model.domain.MapPersistentAttribute; import org.hibernate.query.hql.spi.SqmCreationProcessingState; import org.hibernate.query.hql.spi.SqmPathRegistry; import org.hibernate.query.sqm.NodeBuilder; +import org.hibernate.query.sqm.tree.SqmCopyContext; import org.hibernate.query.sqm.tree.SqmJoinType; import org.hibernate.query.sqm.tree.from.SqmFrom; import org.hibernate.query.sqm.tree.from.SqmRoot; @@ -49,6 +50,29 @@ public class SqmCorrelatedMapJoin extends SqmMapJoin implement this.correlationParent = correlationParent; } + @Override + public SqmCorrelatedMapJoin copy(SqmCopyContext context) { + final SqmCorrelatedMapJoin existing = context.getCopy( this ); + if ( existing != null ) { + return existing; + } + final SqmCorrelatedMapJoin path = context.registerCopy( + this, + new SqmCorrelatedMapJoin<>( + getLhs().copy( context ), + getReferencedPathSource(), + getExplicitAlias(), + getSqmJoinType(), + isFetched(), + nodeBuilder(), + correlatedRootJoin.copy( context ), + correlationParent.copy( context ) + ) + ); + copyTo( path, context ); + return path; + } + @Override public SqmMapJoin getCorrelationParent() { return correlationParent; diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedPluralPartJoin.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedPluralPartJoin.java index e8bb43ade7..cf6afb2a93 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedPluralPartJoin.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedPluralPartJoin.java @@ -6,6 +6,7 @@ */ package org.hibernate.query.sqm.tree.domain; +import org.hibernate.query.sqm.tree.SqmCopyContext; import org.hibernate.query.sqm.tree.SqmJoinType; import org.hibernate.query.sqm.tree.from.SqmFrom; import org.hibernate.query.sqm.tree.from.SqmRoot; @@ -30,6 +31,20 @@ public class SqmCorrelatedPluralPartJoin extends SqmPluralPartJoin i this.correlationParent = correlationParent; } + @Override + public SqmCorrelatedPluralPartJoin copy(SqmCopyContext context) { + final SqmCorrelatedPluralPartJoin existing = context.getCopy( this ); + if ( existing != null ) { + return existing; + } + final SqmCorrelatedPluralPartJoin path = context.registerCopy( + this, + new SqmCorrelatedPluralPartJoin<>( correlationParent.copy( context ) ) + ); + copyTo( path, context ); + return path; + } + @Override public SqmPluralPartJoin getCorrelationParent() { return correlationParent; diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedRoot.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedRoot.java index 6e362bb98b..b74a202a8b 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedRoot.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedRoot.java @@ -8,6 +8,7 @@ package org.hibernate.query.sqm.tree.domain; import org.hibernate.query.criteria.JpaSelection; import org.hibernate.query.sqm.SemanticQueryWalker; +import org.hibernate.query.sqm.tree.SqmCopyContext; import org.hibernate.query.sqm.tree.from.SqmRoot; /** @@ -27,6 +28,20 @@ public class SqmCorrelatedRoot extends SqmRoot implements SqmPathWrapper copy(SqmCopyContext context) { + final SqmCorrelatedRoot existing = context.getCopy( this ); + if ( existing != null ) { + return existing; + } + final SqmCorrelatedRoot path = context.registerCopy( + this, + new SqmCorrelatedRoot<>( correlationParent.copy( context ) ) + ); + copyTo( path, context ); + return path; + } + @Override public SqmRoot getCorrelationParent() { return correlationParent; diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedRootJoin.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedRootJoin.java index 61df2be358..16dd60135e 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedRootJoin.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedRootJoin.java @@ -10,6 +10,7 @@ import org.hibernate.query.spi.NavigablePath; import org.hibernate.query.sqm.NodeBuilder; import org.hibernate.query.sqm.SemanticQueryWalker; import org.hibernate.query.sqm.SqmPathSource; +import org.hibernate.query.sqm.tree.SqmCopyContext; import org.hibernate.query.sqm.tree.from.SqmFrom; import org.hibernate.query.sqm.tree.from.SqmJoin; import org.hibernate.query.sqm.tree.from.SqmRoot; @@ -26,6 +27,24 @@ public class SqmCorrelatedRootJoin extends SqmRoot implements SqmCorrelati super( navigablePath, referencedNavigable, nodeBuilder ); } + @Override + public SqmCorrelatedRootJoin copy(SqmCopyContext context) { + final SqmCorrelatedRootJoin existing = context.getCopy( this ); + if ( existing != null ) { + return existing; + } + final SqmCorrelatedRootJoin path = context.registerCopy( + this, + new SqmCorrelatedRootJoin<>( + getNavigablePath(), + getReferencedPathSource(), + nodeBuilder() + ) + ); + copyTo( path, context ); + return path; + } + @SuppressWarnings("unchecked") public static > SqmCorrelatedRootJoin create(J correlationParent, J correlatedJoin) { final SqmFrom parentPath = (SqmFrom) correlationParent.getParentPath(); diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedSetJoin.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedSetJoin.java index b1d6da5325..973a8abd63 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedSetJoin.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedSetJoin.java @@ -10,6 +10,7 @@ import org.hibernate.metamodel.model.domain.SetPersistentAttribute; import org.hibernate.query.hql.spi.SqmCreationProcessingState; import org.hibernate.query.hql.spi.SqmPathRegistry; import org.hibernate.query.sqm.NodeBuilder; +import org.hibernate.query.sqm.tree.SqmCopyContext; import org.hibernate.query.sqm.tree.SqmJoinType; import org.hibernate.query.sqm.tree.from.SqmFrom; import org.hibernate.query.sqm.tree.from.SqmRoot; @@ -49,6 +50,29 @@ public class SqmCorrelatedSetJoin extends SqmSetJoin implements SqmC this.correlationParent = correlationParent; } + @Override + public SqmCorrelatedSetJoin copy(SqmCopyContext context) { + final SqmCorrelatedSetJoin existing = context.getCopy( this ); + if ( existing != null ) { + return existing; + } + final SqmCorrelatedSetJoin path = context.registerCopy( + this, + new SqmCorrelatedSetJoin<>( + getLhs().copy( context ), + getReferencedPathSource(), + getExplicitAlias(), + getSqmJoinType(), + isFetched(), + nodeBuilder(), + correlatedRootJoin.copy( context ), + correlationParent.copy( context ) + ) + ); + copyTo( path, context ); + return path; + } + @Override public SqmSetJoin getCorrelationParent() { return correlationParent; diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedSingularJoin.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedSingularJoin.java index c11e05eb6a..af1e232fcb 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedSingularJoin.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedSingularJoin.java @@ -10,6 +10,7 @@ import org.hibernate.metamodel.model.domain.SingularPersistentAttribute; import org.hibernate.query.hql.spi.SqmCreationProcessingState; import org.hibernate.query.hql.spi.SqmPathRegistry; import org.hibernate.query.sqm.NodeBuilder; +import org.hibernate.query.sqm.tree.SqmCopyContext; import org.hibernate.query.sqm.tree.SqmJoinType; import org.hibernate.query.sqm.tree.from.SqmFrom; import org.hibernate.query.sqm.tree.from.SqmRoot; @@ -49,6 +50,29 @@ public class SqmCorrelatedSingularJoin extends SqmSingularJoin imple this.correlationParent = correlationParent; } + @Override + public SqmCorrelatedSingularJoin copy(SqmCopyContext context) { + final SqmCorrelatedSingularJoin existing = context.getCopy( this ); + if ( existing != null ) { + return existing; + } + final SqmCorrelatedSingularJoin path = context.registerCopy( + this, + new SqmCorrelatedSingularJoin<>( + getLhs().copy( context ), + getReferencedPathSource(), + getExplicitAlias(), + getSqmJoinType(), + isFetched(), + nodeBuilder(), + correlatedRootJoin.copy( context ), + correlationParent.copy( context ) + ) + ); + copyTo( path, context ); + return path; + } + @Override public SqmSingularJoin getCorrelationParent() { return correlationParent; diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmElementAggregateFunction.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmElementAggregateFunction.java index 09ddc30c61..f7b3b8829c 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmElementAggregateFunction.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmElementAggregateFunction.java @@ -10,25 +10,42 @@ import org.hibernate.metamodel.model.domain.PluralPersistentAttribute; import org.hibernate.query.hql.spi.SqmCreationState; import org.hibernate.query.sqm.SemanticQueryWalker; import org.hibernate.query.sqm.SqmPathSource; +import org.hibernate.query.sqm.tree.SqmCopyContext; /** * @author Steve Ebersole */ public class SqmElementAggregateFunction extends AbstractSqmSpecificPluralPartPath { - public static final String NAVIGABLE_NAME = "{max-element}"; - private final String functionName; public SqmElementAggregateFunction(SqmPath pluralDomainPath, String functionName) { //noinspection unchecked super( - pluralDomainPath.getNavigablePath().getParent().append( pluralDomainPath.getNavigablePath().getLocalName(), NAVIGABLE_NAME ), + pluralDomainPath.getNavigablePath().getParent().append( pluralDomainPath.getNavigablePath().getLocalName(), "{" + functionName + "-element}" ), pluralDomainPath, (PluralPersistentAttribute) pluralDomainPath.getReferencedPathSource() ); this.functionName = functionName; } + @Override + public SqmElementAggregateFunction copy(SqmCopyContext context) { + final SqmElementAggregateFunction existing = context.getCopy( this ); + if ( existing != null ) { + return existing; + } + + final SqmElementAggregateFunction path = context.registerCopy( + this, + new SqmElementAggregateFunction<>( + getLhs().copy( context ), + functionName + ) + ); + copyTo( path, context ); + return path; + } + public String getFunctionName() { return functionName; } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmEmbeddedValuedSimplePath.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmEmbeddedValuedSimplePath.java index 4956177cd4..071b9c205e 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmEmbeddedValuedSimplePath.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmEmbeddedValuedSimplePath.java @@ -16,6 +16,7 @@ import org.hibernate.query.sqm.NodeBuilder; import org.hibernate.query.sqm.SemanticQueryWalker; import org.hibernate.query.sqm.SqmExpressible; import org.hibernate.query.sqm.SqmPathSource; +import org.hibernate.query.sqm.tree.SqmCopyContext; import org.hibernate.type.descriptor.java.JavaType; /** @@ -46,6 +47,27 @@ public class SqmEmbeddedValuedSimplePath assert referencedPathSource.getSqmPathType() instanceof EmbeddableDomainType; } + @Override + public SqmEmbeddedValuedSimplePath copy(SqmCopyContext context) { + final SqmEmbeddedValuedSimplePath existing = context.getCopy( this ); + if ( existing != null ) { + return existing; + } + + final SqmEmbeddedValuedSimplePath path = context.registerCopy( + this, + new SqmEmbeddedValuedSimplePath<>( + getNavigablePath(), + getReferencedPathSource(), + getLhs().copy( context ), + getExplicitAlias(), + nodeBuilder() + ) + ); + copyTo( path, context ); + return path; + } + @Override public SqmPath resolvePathPart( String name, diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmEntityValuedSimplePath.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmEntityValuedSimplePath.java index 4a6884bcee..e9b2a18d37 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmEntityValuedSimplePath.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmEntityValuedSimplePath.java @@ -13,6 +13,7 @@ import org.hibernate.query.hql.spi.SqmCreationState; import org.hibernate.query.sqm.NodeBuilder; import org.hibernate.query.sqm.SemanticQueryWalker; import org.hibernate.query.sqm.SqmPathSource; +import org.hibernate.query.sqm.tree.SqmCopyContext; /** * @author Steve Ebersole @@ -26,6 +27,26 @@ public class SqmEntityValuedSimplePath extends AbstractSqmSimplePath { super( navigablePath, referencedPathSource, lhs, nodeBuilder ); } + @Override + public SqmEntityValuedSimplePath copy(SqmCopyContext context) { + final SqmEntityValuedSimplePath existing = context.getCopy( this ); + if ( existing != null ) { + return existing; + } + + final SqmEntityValuedSimplePath path = context.registerCopy( + this, + new SqmEntityValuedSimplePath<>( + getNavigablePath(), + getReferencedPathSource(), + getLhs().copy( context ), + nodeBuilder() + ) + ); + copyTo( path, context ); + return path; + } + @Override public SqmPath resolvePathPart( String name, diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmIndexAggregateFunction.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmIndexAggregateFunction.java index 7e3eb10be1..e523f7d754 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmIndexAggregateFunction.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmIndexAggregateFunction.java @@ -12,20 +12,19 @@ import org.hibernate.metamodel.model.domain.PluralPersistentAttribute; import org.hibernate.query.sqm.SqmPathSource; import org.hibernate.query.sqm.SemanticQueryWalker; import org.hibernate.query.hql.spi.SqmCreationState; +import org.hibernate.query.sqm.tree.SqmCopyContext; /** * @author Steve Ebersole */ public class SqmIndexAggregateFunction extends AbstractSqmSpecificPluralPartPath { - public static final String NAVIGABLE_NAME = "{max-index}"; - private final SqmPathSource indexPathSource; private final String functionName; public SqmIndexAggregateFunction(SqmPath pluralDomainPath, String functionName) { //noinspection unchecked super( - pluralDomainPath.getNavigablePath().getParent().append( pluralDomainPath.getNavigablePath().getLocalName(), NAVIGABLE_NAME ), + pluralDomainPath.getNavigablePath().getParent().append( pluralDomainPath.getNavigablePath().getLocalName(), "{" + functionName + "-index}" ), pluralDomainPath, (PluralPersistentAttribute) pluralDomainPath.getReferencedPathSource() ); @@ -44,6 +43,24 @@ public class SqmIndexAggregateFunction extends AbstractSqmSpecificPluralPartP } } + @Override + public SqmIndexAggregateFunction copy(SqmCopyContext context) { + final SqmIndexAggregateFunction existing = context.getCopy( this ); + if ( existing != null ) { + return existing; + } + + final SqmIndexAggregateFunction path = context.registerCopy( + this, + new SqmIndexAggregateFunction<>( + getLhs().copy( context ), + functionName + ) + ); + copyTo( path, context ); + return path; + } + public String getFunctionName() { return functionName; } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmIndexedCollectionAccessPath.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmIndexedCollectionAccessPath.java index 35d6e4fd5d..0c53c8c477 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmIndexedCollectionAccessPath.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmIndexedCollectionAccessPath.java @@ -12,6 +12,8 @@ import org.hibernate.query.spi.NavigablePath; import org.hibernate.query.PathException; import org.hibernate.query.sqm.SemanticQueryWalker; import org.hibernate.query.hql.spi.SqmCreationState; +import org.hibernate.query.sqm.SemanticQueryWalker; +import org.hibernate.query.sqm.tree.SqmCopyContext; import org.hibernate.query.sqm.tree.expression.SqmExpression; /** @@ -32,7 +34,25 @@ public class SqmIndexedCollectionAccessPath extends AbstractSqmPath implem pluralDomainPath.nodeBuilder() ); this.selectorExpression = selectorExpression; + } + @Override + public SqmIndexedCollectionAccessPath copy(SqmCopyContext context) { + final SqmIndexedCollectionAccessPath existing = context.getCopy( this ); + if ( existing != null ) { + return existing; + } + + final SqmIndexedCollectionAccessPath path = context.registerCopy( + this, + new SqmIndexedCollectionAccessPath( + getNavigablePath(), + getLhs().copy( context ), + selectorExpression.copy( context ) + ) + ); + copyTo( path, context ); + return path; } public SqmExpression getSelectorExpression() { diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmListJoin.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmListJoin.java index e830862eaa..2a7ecd4f01 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmListJoin.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmListJoin.java @@ -7,8 +7,6 @@ package org.hibernate.query.sqm.tree.domain; import java.util.List; -import jakarta.persistence.criteria.Expression; -import jakarta.persistence.criteria.Predicate; import org.hibernate.metamodel.model.domain.EntityDomainType; import org.hibernate.metamodel.model.domain.ListPersistentAttribute; @@ -19,10 +17,14 @@ import org.hibernate.query.criteria.JpaPredicate; import org.hibernate.query.hql.spi.SqmCreationProcessingState; import org.hibernate.query.sqm.NodeBuilder; import org.hibernate.query.sqm.SqmPathSource; +import org.hibernate.query.sqm.tree.SqmCopyContext; import org.hibernate.query.sqm.tree.SqmJoinType; import org.hibernate.query.sqm.tree.from.SqmAttributeJoin; import org.hibernate.query.sqm.tree.from.SqmFrom; +import jakarta.persistence.criteria.Expression; +import jakarta.persistence.criteria.Predicate; + /** * @author Steve Ebersole */ @@ -50,6 +52,28 @@ public class SqmListJoin super( lhs, navigablePath, listAttribute, alias, joinType, fetched, nodeBuilder ); } + @Override + public SqmListJoin copy(SqmCopyContext context) { + final SqmListJoin existing = context.getCopy( this ); + if ( existing != null ) { + return existing; + } + final SqmListJoin path = context.registerCopy( + this, + new SqmListJoin<>( + getLhs().copy( context ), + getNavigablePath(), + getReferencedPathSource(), + getExplicitAlias(), + getSqmJoinType(), + isFetched(), + nodeBuilder() + ) + ); + copyTo( path, context ); + return path; + } + @Override public ListPersistentAttribute getReferencedPathSource() { return (ListPersistentAttribute) super.getReferencedPathSource(); diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmMapEntryReference.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmMapEntryReference.java index 5165258d95..ec6330504d 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmMapEntryReference.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmMapEntryReference.java @@ -16,6 +16,7 @@ import org.hibernate.query.criteria.JpaSelection; import org.hibernate.query.sqm.NodeBuilder; import org.hibernate.query.sqm.SemanticQueryWalker; import org.hibernate.query.sqm.SqmExpressible; +import org.hibernate.query.sqm.tree.SqmCopyContext; import org.hibernate.query.sqm.tree.select.SqmSelectableNode; import org.hibernate.type.descriptor.java.JavaType; @@ -52,6 +53,19 @@ public class SqmMapEntryReference .getDescriptor( Map.Entry.class ); } + @Override + public SqmMapEntryReference copy(SqmCopyContext context) { + final SqmMapEntryReference existing = context.getCopy( this ); + if ( existing != null ) { + return existing; + } + final SqmMapEntryReference path = context.registerCopy( + this, + new SqmMapEntryReference<>( mapPath.copy( context ), nodeBuilder() ) + ); + return path; + } + @Override public String getAlias() { return explicitAlias; diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmMapJoin.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmMapJoin.java index 0b3fda9649..73f95340cc 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmMapJoin.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmMapJoin.java @@ -20,6 +20,7 @@ import org.hibernate.query.criteria.JpaPredicate; import org.hibernate.query.sqm.NodeBuilder; import org.hibernate.query.sqm.SqmPathSource; import org.hibernate.query.hql.spi.SqmCreationProcessingState; +import org.hibernate.query.sqm.tree.SqmCopyContext; import org.hibernate.query.sqm.tree.SqmJoinType; import org.hibernate.query.sqm.tree.from.SqmFrom; @@ -50,6 +51,28 @@ public class SqmMapJoin super( lhs, navigablePath, pluralValuedNavigable, alias, joinType, fetched, nodeBuilder ); } + @Override + public SqmMapJoin copy(SqmCopyContext context) { + final SqmMapJoin existing = context.getCopy( this ); + if ( existing != null ) { + return existing; + } + final SqmMapJoin path = context.registerCopy( + this, + new SqmMapJoin<>( + getLhs().copy( context ), + getNavigablePath(), + getReferencedPathSource(), + getExplicitAlias(), + getSqmJoinType(), + isFetched(), + nodeBuilder() + ) + ); + copyTo( path, context ); + return path; + } + @Override public MapPersistentAttribute getReferencedPathSource() { return(MapPersistentAttribute) super.getReferencedPathSource(); diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmPath.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmPath.java index 747d5a4cac..f90019f574 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmPath.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmPath.java @@ -24,6 +24,8 @@ import org.hibernate.query.hql.spi.SqmCreationState; import org.hibernate.query.sqm.ParsingException; import org.hibernate.query.sqm.SqmExpressible; import org.hibernate.query.sqm.SqmPathSource; +import org.hibernate.query.sqm.tree.SqmCopyContext; +import org.hibernate.query.sqm.tree.SqmNode; import org.hibernate.query.sqm.tree.expression.SqmExpression; import org.hibernate.query.sqm.tree.from.SqmRoot; import org.hibernate.type.descriptor.java.JavaType; @@ -156,4 +158,7 @@ public interface SqmPath extends SqmExpression, SemanticPathPart, JpaPath< @Override SqmPath get(String attributeName); + + @Override + SqmPath copy(SqmCopyContext context); } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmPluralPartJoin.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmPluralPartJoin.java index fcc5da7c20..294abe3cc3 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmPluralPartJoin.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmPluralPartJoin.java @@ -14,6 +14,7 @@ import org.hibernate.query.sqm.NodeBuilder; import org.hibernate.query.sqm.SemanticQueryWalker; import org.hibernate.query.sqm.SqmPathSource; import org.hibernate.query.sqm.spi.SqmCreationHelper; +import org.hibernate.query.sqm.tree.SqmCopyContext; import org.hibernate.query.sqm.tree.SqmJoinType; import org.hibernate.query.sqm.tree.from.SqmFrom; import org.hibernate.query.sqm.tree.from.SqmQualifiedJoin; @@ -57,6 +58,27 @@ public class SqmPluralPartJoin extends AbstractSqmJoin implements SqmQ ); } + @Override + public SqmPluralPartJoin copy(SqmCopyContext context) { + final SqmPluralPartJoin existing = context.getCopy( this ); + if ( existing != null ) { + return existing; + } + final SqmPluralPartJoin path = context.registerCopy( + this, + new SqmPluralPartJoin<>( + (SqmFrom) getLhs().copy( context ), + getNavigablePath(), + getReferencedPathSource(), + getExplicitAlias(), + getSqmJoinType(), + nodeBuilder() + ) + ); + copyTo( path, context ); + return path; + } + @Override public SqmPredicate getJoinPredicate() { return null; diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmPluralValuedSimplePath.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmPluralValuedSimplePath.java index e57551aec2..37781dad93 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmPluralValuedSimplePath.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmPluralValuedSimplePath.java @@ -18,6 +18,7 @@ import org.hibernate.query.hql.spi.SqmCreationState; import org.hibernate.query.hql.spi.SqmPathRegistry; import org.hibernate.query.sqm.NodeBuilder; import org.hibernate.query.sqm.SemanticQueryWalker; +import org.hibernate.query.sqm.tree.SqmCopyContext; import org.hibernate.query.sqm.tree.SqmJoinType; import org.hibernate.query.sqm.tree.expression.SqmExpression; import org.hibernate.query.sqm.tree.from.SqmFrom; @@ -48,6 +49,27 @@ public class SqmPluralValuedSimplePath extends AbstractSqmSimplePath { super( navigablePath, referencedNavigable, lhs, explicitAlias, nodeBuilder ); } + @Override + public SqmPluralValuedSimplePath copy(SqmCopyContext context) { + final SqmPluralValuedSimplePath existing = context.getCopy( this ); + if ( existing != null ) { + return existing; + } + + final SqmPluralValuedSimplePath path = context.registerCopy( + this, + new SqmPluralValuedSimplePath<>( + getNavigablePath(), + getReferencedPathSource(), + getLhs().copy( context ), + getExplicitAlias(), + nodeBuilder() + ) + ); + copyTo( path, context ); + return path; + } + @Override public PluralPersistentAttribute getReferencedPathSource() { return (PluralPersistentAttribute) super.getReferencedPathSource(); diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmSetJoin.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmSetJoin.java index 4924e9d212..3ef4febd7a 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmSetJoin.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmSetJoin.java @@ -7,9 +7,6 @@ package org.hibernate.query.sqm.tree.domain; import java.util.Set; -import jakarta.persistence.criteria.Expression; -import jakarta.persistence.criteria.JoinType; -import jakarta.persistence.criteria.Predicate; import org.hibernate.metamodel.model.domain.EntityDomainType; import org.hibernate.metamodel.model.domain.SetPersistentAttribute; @@ -19,10 +16,15 @@ import org.hibernate.query.criteria.JpaPredicate; import org.hibernate.query.criteria.JpaSetJoin; import org.hibernate.query.hql.spi.SqmCreationProcessingState; import org.hibernate.query.sqm.NodeBuilder; +import org.hibernate.query.sqm.tree.SqmCopyContext; import org.hibernate.query.sqm.tree.SqmJoinType; import org.hibernate.query.sqm.tree.from.SqmAttributeJoin; import org.hibernate.query.sqm.tree.from.SqmFrom; +import jakarta.persistence.criteria.Expression; +import jakarta.persistence.criteria.JoinType; +import jakarta.persistence.criteria.Predicate; + /** * @author Steve Ebersole */ @@ -49,6 +51,28 @@ public class SqmSetJoin super( lhs, navigablePath, pluralValuedNavigable, alias, joinType, fetched, nodeBuilder ); } + @Override + public SqmSetJoin copy(SqmCopyContext context) { + final SqmSetJoin existing = context.getCopy( this ); + if ( existing != null ) { + return existing; + } + final SqmSetJoin path = context.registerCopy( + this, + new SqmSetJoin<>( + getLhs().copy( context ), + getNavigablePath(), + getReferencedPathSource(), + getExplicitAlias(), + getSqmJoinType(), + isFetched(), + nodeBuilder() + ) + ); + copyTo( path, context ); + return path; + } + @Override public SetPersistentAttribute getReferencedPathSource() { return (SetPersistentAttribute) super.getReferencedPathSource(); diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmSingularJoin.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmSingularJoin.java index a89693db4f..00f1e43268 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmSingularJoin.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmSingularJoin.java @@ -14,6 +14,7 @@ import org.hibernate.query.spi.NavigablePath; import org.hibernate.query.hql.spi.SqmCreationProcessingState; import org.hibernate.query.sqm.NodeBuilder; import org.hibernate.query.sqm.SqmJoinable; +import org.hibernate.query.sqm.tree.SqmCopyContext; import org.hibernate.query.sqm.tree.SqmJoinType; import org.hibernate.query.sqm.tree.from.SqmAttributeJoin; import org.hibernate.query.sqm.tree.from.SqmFrom; @@ -53,6 +54,28 @@ public class SqmSingularJoin extends AbstractSqmAttributeJoin { super( lhs, navigablePath, joinedNavigable, alias, joinType, fetched, nodeBuilder ); } + @Override + public SqmSingularJoin copy(SqmCopyContext context) { + final SqmSingularJoin existing = context.getCopy( this ); + if ( existing != null ) { + return existing; + } + final SqmSingularJoin path = context.registerCopy( + this, + new SqmSingularJoin<>( + getLhs().copy( context ), + getNavigablePath(), + getReferencedPathSource(), + getExplicitAlias(), + getSqmJoinType(), + isFetched(), + nodeBuilder() + ) + ); + copyTo( path, context ); + return path; + } + @Override public SingularPersistentAttribute getReferencedPathSource() { return (SingularPersistentAttribute) super.getReferencedPathSource(); diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmTreatedBagJoin.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmTreatedBagJoin.java index 439dffac74..ae16d1d5e0 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmTreatedBagJoin.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmTreatedBagJoin.java @@ -10,6 +10,7 @@ import org.hibernate.metamodel.model.domain.BagPersistentAttribute; import org.hibernate.metamodel.model.domain.EntityDomainType; import org.hibernate.query.hql.spi.SqmCreationProcessingState; import org.hibernate.query.sqm.SqmPathSource; +import org.hibernate.query.sqm.tree.SqmCopyContext; import org.hibernate.query.sqm.tree.from.SqmAttributeJoin; import org.hibernate.query.sqm.tree.from.SqmJoin; @@ -42,10 +43,21 @@ public class SqmTreatedBagJoin extends SqmBagJoin impleme } @Override - public void addSqmJoin(SqmJoin join) { - super.addSqmJoin( join ); - //noinspection unchecked - wrappedPath.addSqmJoin( (SqmJoin) join ); + public SqmTreatedBagJoin copy(SqmCopyContext context) { + final SqmTreatedBagJoin existing = context.getCopy( this ); + if ( existing != null ) { + return existing; + } + final SqmTreatedBagJoin path = context.registerCopy( + this, + new SqmTreatedBagJoin<>( + wrappedPath.copy( context ), + treatTarget, + getExplicitAlias() + ) + ); + copyTo( path, context ); + return path; } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmTreatedCrossJoin.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmTreatedCrossJoin.java index aa74d952be..2e3beb537f 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmTreatedCrossJoin.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmTreatedCrossJoin.java @@ -8,6 +8,7 @@ package org.hibernate.query.sqm.tree.domain; import org.hibernate.metamodel.model.domain.EntityDomainType; import org.hibernate.query.sqm.SqmPathSource; +import org.hibernate.query.sqm.tree.SqmCopyContext; import org.hibernate.query.sqm.tree.from.SqmCrossJoin; import org.hibernate.query.sqm.tree.from.SqmJoin; @@ -34,10 +35,21 @@ public class SqmTreatedCrossJoin extends SqmCrossJoin impleme } @Override - public void addSqmJoin(SqmJoin join) { - super.addSqmJoin( join ); - //noinspection unchecked - wrappedPath.addSqmJoin( (SqmJoin) join ); + public SqmTreatedCrossJoin copy(SqmCopyContext context) { + final SqmTreatedCrossJoin existing = context.getCopy( this ); + if ( existing != null ) { + return existing; + } + final SqmTreatedCrossJoin path = context.registerCopy( + this, + new SqmTreatedCrossJoin<>( + wrappedPath.copy( context ), + treatTarget, + getExplicitAlias() + ) + ); + copyTo( path, context ); + return path; } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmTreatedEntityJoin.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmTreatedEntityJoin.java index 15c35016cf..827e7de7f0 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmTreatedEntityJoin.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmTreatedEntityJoin.java @@ -8,6 +8,7 @@ package org.hibernate.query.sqm.tree.domain; import org.hibernate.metamodel.model.domain.EntityDomainType; import org.hibernate.query.sqm.SqmPathSource; +import org.hibernate.query.sqm.tree.SqmCopyContext; import org.hibernate.query.sqm.tree.from.SqmEntityJoin; import org.hibernate.query.sqm.tree.from.SqmJoin; @@ -37,10 +38,21 @@ public class SqmTreatedEntityJoin extends SqmEntityJoin imple } @Override - public void addSqmJoin(SqmJoin join) { - super.addSqmJoin( join ); - //noinspection unchecked - wrappedPath.addSqmJoin( (SqmJoin) join ); + public SqmTreatedEntityJoin copy(SqmCopyContext context) { + final SqmTreatedEntityJoin existing = context.getCopy( this ); + if ( existing != null ) { + return existing; + } + final SqmTreatedEntityJoin path = context.registerCopy( + this, + new SqmTreatedEntityJoin<>( + wrappedPath.copy( context ), + treatTarget, + getExplicitAlias() + ) + ); + copyTo( path, context ); + return path; } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmTreatedListJoin.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmTreatedListJoin.java index d9719d6efd..1dd44d6241 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmTreatedListJoin.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmTreatedListJoin.java @@ -11,6 +11,7 @@ import org.hibernate.metamodel.model.domain.ListPersistentAttribute; import org.hibernate.query.hql.spi.SqmCreationProcessingState; import org.hibernate.query.hql.spi.SqmCreationState; import org.hibernate.query.sqm.SqmPathSource; +import org.hibernate.query.sqm.tree.SqmCopyContext; import org.hibernate.query.sqm.tree.expression.SqmExpression; import org.hibernate.query.sqm.tree.from.SqmAttributeJoin; import org.hibernate.query.sqm.tree.from.SqmJoin; @@ -44,10 +45,21 @@ public class SqmTreatedListJoin extends SqmListJoin imple } @Override - public void addSqmJoin(SqmJoin join) { - super.addSqmJoin( join ); - //noinspection unchecked - wrappedPath.addSqmJoin( (SqmJoin) join ); + public SqmTreatedListJoin copy(SqmCopyContext context) { + final SqmTreatedListJoin existing = context.getCopy( this ); + if ( existing != null ) { + return existing; + } + final SqmTreatedListJoin path = context.registerCopy( + this, + new SqmTreatedListJoin<>( + wrappedPath.copy( context ), + treatTarget, + getExplicitAlias() + ) + ); + copyTo( path, context ); + return path; } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmTreatedMapJoin.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmTreatedMapJoin.java index 8a71b42491..75e1391039 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmTreatedMapJoin.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmTreatedMapJoin.java @@ -9,6 +9,7 @@ package org.hibernate.query.sqm.tree.domain; import org.hibernate.metamodel.model.domain.EntityDomainType; import org.hibernate.query.hql.spi.SqmCreationProcessingState; import org.hibernate.query.sqm.SqmPathSource; +import org.hibernate.query.sqm.tree.SqmCopyContext; import org.hibernate.query.sqm.tree.from.SqmJoin; /** @@ -40,10 +41,21 @@ public class SqmTreatedMapJoin extends SqmMapJoin } @Override - public void addSqmJoin(SqmJoin join) { - super.addSqmJoin( join ); - //noinspection unchecked - wrappedPath.addSqmJoin( (SqmJoin) join ); + public SqmTreatedMapJoin copy(SqmCopyContext context) { + final SqmTreatedMapJoin existing = context.getCopy( this ); + if ( existing != null ) { + return existing; + } + final SqmTreatedMapJoin path = context.registerCopy( + this, + new SqmTreatedMapJoin<>( + wrappedPath.copy( context ), + treatTarget, + getExplicitAlias() + ) + ); + copyTo( path, context ); + return path; } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmTreatedPluralPartJoin.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmTreatedPluralPartJoin.java index 1362503717..100ff4c195 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmTreatedPluralPartJoin.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmTreatedPluralPartJoin.java @@ -8,6 +8,7 @@ package org.hibernate.query.sqm.tree.domain; import org.hibernate.metamodel.model.domain.EntityDomainType; import org.hibernate.query.sqm.SqmPathSource; +import org.hibernate.query.sqm.tree.SqmCopyContext; import org.hibernate.query.sqm.tree.from.SqmFrom; import org.hibernate.query.sqm.tree.from.SqmJoin; @@ -39,10 +40,21 @@ public class SqmTreatedPluralPartJoin extends SqmPluralPartJoi } @Override - public void addSqmJoin(SqmJoin join) { - super.addSqmJoin( join ); - //noinspection unchecked - wrappedPath.addSqmJoin( (SqmJoin) join ); + public SqmTreatedPluralPartJoin copy(SqmCopyContext context) { + final SqmTreatedPluralPartJoin existing = context.getCopy( this ); + if ( existing != null ) { + return existing; + } + final SqmTreatedPluralPartJoin path = context.registerCopy( + this, + new SqmTreatedPluralPartJoin<>( + wrappedPath.copy( context ), + treatTarget, + getExplicitAlias() + ) + ); + copyTo( path, context ); + return path; } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmTreatedRoot.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmTreatedRoot.java index ae493dde56..f65e884b5b 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmTreatedRoot.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmTreatedRoot.java @@ -10,6 +10,7 @@ import org.hibernate.metamodel.model.domain.EntityDomainType; import org.hibernate.query.hql.spi.SqmCreationState; import org.hibernate.query.sqm.SemanticQueryWalker; import org.hibernate.query.sqm.SqmPathSource; +import org.hibernate.query.sqm.tree.SqmCopyContext; import org.hibernate.query.sqm.tree.from.SqmJoin; import org.hibernate.query.sqm.tree.from.SqmRoot; @@ -37,10 +38,20 @@ public class SqmTreatedRoot extends SqmRoot implements SqmTre } @Override - public void addSqmJoin(SqmJoin join) { - super.addSqmJoin( join ); - //noinspection unchecked - wrappedPath.addSqmJoin( (SqmJoin) join ); + public SqmRoot copy(SqmCopyContext context) { + final SqmTreatedRoot existing = context.getCopy( this ); + if ( existing != null ) { + return existing; + } + final SqmTreatedRoot path = context.registerCopy( + this, + new SqmTreatedRoot<>( + wrappedPath.copy( context ), + treatTarget + ) + ); + copyTo( path, context ); + return path; } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmTreatedSetJoin.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmTreatedSetJoin.java index 5ce8c3017d..b8c5a11f6e 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmTreatedSetJoin.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmTreatedSetJoin.java @@ -10,6 +10,7 @@ import org.hibernate.metamodel.model.domain.EntityDomainType; import org.hibernate.metamodel.model.domain.SetPersistentAttribute; import org.hibernate.query.hql.spi.SqmCreationProcessingState; import org.hibernate.query.sqm.SqmPathSource; +import org.hibernate.query.sqm.tree.SqmCopyContext; import org.hibernate.query.sqm.tree.from.SqmAttributeJoin; import org.hibernate.query.sqm.tree.from.SqmJoin; @@ -42,10 +43,21 @@ public class SqmTreatedSetJoin extends SqmSetJoin impleme } @Override - public void addSqmJoin(SqmJoin join) { - super.addSqmJoin( join ); - //noinspection unchecked - wrappedPath.addSqmJoin( (SqmJoin) join ); + public SqmTreatedSetJoin copy(SqmCopyContext context) { + final SqmTreatedSetJoin existing = context.getCopy( this ); + if ( existing != null ) { + return existing; + } + final SqmTreatedSetJoin path = context.registerCopy( + this, + new SqmTreatedSetJoin<>( + wrappedPath.copy( context ), + treatTarget, + getExplicitAlias() + ) + ); + copyTo( path, context ); + return path; } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmTreatedSimplePath.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmTreatedSimplePath.java index f79c07968c..b3c9373517 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmTreatedSimplePath.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmTreatedSimplePath.java @@ -10,6 +10,7 @@ import org.hibernate.metamodel.model.domain.EntityDomainType; import org.hibernate.query.PathException; import org.hibernate.query.sqm.NodeBuilder; import org.hibernate.query.sqm.SemanticQueryWalker; +import org.hibernate.query.sqm.tree.SqmCopyContext; /** * @author Steve Ebersole @@ -56,9 +57,22 @@ public class SqmTreatedSimplePath } @Override - public void registerReusablePath(SqmPath path) { - super.registerReusablePath( path ); - wrappedPath.registerReusablePath( path ); + public SqmTreatedSimplePath copy(SqmCopyContext context) { + final SqmTreatedSimplePath existing = context.getCopy( this ); + if ( existing != null ) { + return existing; + } + + final SqmTreatedSimplePath path = context.registerCopy( + this, + new SqmTreatedSimplePath<>( + wrappedPath.copy( context ), + getTreatTarget(), + nodeBuilder() + ) + ); + copyTo( path, context ); + return path; } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmTreatedSingularJoin.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmTreatedSingularJoin.java index 52b41adb73..7002ef898b 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmTreatedSingularJoin.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmTreatedSingularJoin.java @@ -10,6 +10,7 @@ import org.hibernate.metamodel.model.domain.EntityDomainType; import org.hibernate.metamodel.model.domain.SingularPersistentAttribute; import org.hibernate.query.hql.spi.SqmCreationProcessingState; import org.hibernate.query.sqm.SqmPathSource; +import org.hibernate.query.sqm.tree.SqmCopyContext; import org.hibernate.query.sqm.tree.from.SqmAttributeJoin; import org.hibernate.query.sqm.tree.from.SqmJoin; @@ -42,10 +43,21 @@ public class SqmTreatedSingularJoin extends SqmSingularJoin join) { - super.addSqmJoin( join ); - //noinspection unchecked - wrappedPath.addSqmJoin( (SqmJoin) join ); + public SqmTreatedSingularJoin copy(SqmCopyContext context) { + final SqmTreatedSingularJoin existing = context.getCopy( this ); + if ( existing != null ) { + return existing; + } + final SqmTreatedSingularJoin path = context.registerCopy( + this, + new SqmTreatedSingularJoin<>( + wrappedPath.copy( context ), + treatTarget, + getExplicitAlias() + ) + ); + copyTo( path, context ); + return path; } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/AbstractSqmExpression.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/AbstractSqmExpression.java index 47ade510b3..3cb86cdf8f 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/AbstractSqmExpression.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/AbstractSqmExpression.java @@ -9,7 +9,6 @@ package org.hibernate.query.sqm.tree.expression; import java.math.BigDecimal; import java.math.BigInteger; import java.util.Collection; -import jakarta.persistence.criteria.Expression; import org.hibernate.query.criteria.JpaExpression; import org.hibernate.query.criteria.JpaSelection; @@ -22,6 +21,8 @@ import org.hibernate.query.sqm.tree.predicate.SqmInPredicate; import org.hibernate.query.sqm.tree.predicate.SqmPredicate; import org.hibernate.type.descriptor.java.JavaType; +import jakarta.persistence.criteria.Expression; + import static org.hibernate.query.internal.QueryHelper.highestPrecedenceType; /** diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/JpaCriteriaParameter.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/JpaCriteriaParameter.java index a4586b383f..7549f98b48 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/JpaCriteriaParameter.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/JpaCriteriaParameter.java @@ -16,6 +16,7 @@ import org.hibernate.query.sqm.NodeBuilder; import org.hibernate.query.sqm.SemanticQueryWalker; import org.hibernate.query.sqm.SqmExpressible; import org.hibernate.query.sqm.sql.internal.DomainResultProducer; +import org.hibernate.query.sqm.tree.SqmCopyContext; import org.hibernate.sql.results.graph.DomainResult; import org.hibernate.sql.results.graph.DomainResultCreationState; @@ -45,6 +46,12 @@ public class JpaCriteriaParameter this.allowsMultiValuedBinding = allowsMultiValuedBinding; } + protected JpaCriteriaParameter(JpaCriteriaParameter original) { + super( original.getNodeType(), original.nodeBuilder() ); + this.name = original.name; + this.allowsMultiValuedBinding = original.allowsMultiValuedBinding; + } + private static SqmExpressible toSqmType(BindableType type, NodeBuilder nodeBuilder) { if ( type == null ) { return null; @@ -54,6 +61,12 @@ public class JpaCriteriaParameter ); } + @Override + public JpaCriteriaParameter copy(SqmCopyContext context) { + // Don't create a copy of regular parameters because identity is important here + return this; + } + @Override public String getName() { return name; diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmAliasedNodeRef.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmAliasedNodeRef.java index 8fbe4912e4..905dfb6359 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmAliasedNodeRef.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmAliasedNodeRef.java @@ -9,6 +9,7 @@ package org.hibernate.query.sqm.tree.expression; import org.hibernate.query.sqm.NodeBuilder; import org.hibernate.query.sqm.SemanticQueryWalker; import org.hibernate.query.sqm.SqmExpressible; +import org.hibernate.query.sqm.tree.SqmCopyContext; /** * Models a reference to a {@link org.hibernate.query.sqm.tree.select.SqmAliasedNode} @@ -23,6 +24,22 @@ public class SqmAliasedNodeRef extends AbstractSqmExpression { this.position = position; } + private SqmAliasedNodeRef(SqmAliasedNodeRef original) { + super( original.getNodeType(), original.nodeBuilder() ); + this.position = original.position; + } + + @Override + public SqmAliasedNodeRef copy(SqmCopyContext context) { + final SqmAliasedNodeRef existing = context.getCopy( this ); + if ( existing != null ) { + return existing; + } + final SqmAliasedNodeRef expression = context.registerCopy( this, new SqmAliasedNodeRef( this ) ); + copyTo( expression, context ); + return expression; + } + public int getPosition() { return position; } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmAny.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmAny.java index 9379ab6c05..41a3fc35d2 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmAny.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmAny.java @@ -8,6 +8,7 @@ package org.hibernate.query.sqm.tree.expression; import org.hibernate.query.sqm.NodeBuilder; import org.hibernate.query.sqm.SemanticQueryWalker; +import org.hibernate.query.sqm.tree.SqmCopyContext; import org.hibernate.query.sqm.tree.select.SqmSubQuery; /** @@ -22,6 +23,23 @@ public class SqmAny extends AbstractSqmExpression { this.subquery = subquery; } + @Override + public SqmAny copy(SqmCopyContext context) { + final SqmAny existing = context.getCopy( this ); + if ( existing != null ) { + return existing; + } + final SqmAny expression = context.registerCopy( + this, + new SqmAny<>( + subquery.copy( context ), + nodeBuilder() + ) + ); + copyTo( expression, context ); + return expression; + } + public SqmSubQuery getSubquery() { return subquery; } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmBinaryArithmetic.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmBinaryArithmetic.java index 01e670b54b..44999c9d68 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmBinaryArithmetic.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmBinaryArithmetic.java @@ -11,6 +11,7 @@ import org.hibernate.query.sqm.BinaryArithmeticOperator; import org.hibernate.query.sqm.NodeBuilder; import org.hibernate.query.sqm.SemanticQueryWalker; import org.hibernate.query.sqm.SqmExpressible; +import org.hibernate.query.sqm.tree.SqmCopyContext; import org.hibernate.query.sqm.tree.select.SqmSelectableNode; /** @@ -61,6 +62,26 @@ public class SqmBinaryArithmetic extends AbstractSqmExpression implements applyInferableType( expressibleType ); } + @Override + public SqmBinaryArithmetic copy(SqmCopyContext context) { + final SqmBinaryArithmetic existing = context.getCopy( this ); + if ( existing != null ) { + return existing; + } + final SqmBinaryArithmetic expression = context.registerCopy( + this, + new SqmBinaryArithmetic<>( + operator, + lhsOperand.copy( context ), + rhsOperand.copy( context ), + getNodeType(), + nodeBuilder() + ) + ); + copyTo( expression, context ); + return expression; + } + @Override public X accept(SemanticQueryWalker walker) { return walker.visitBinaryArithmeticExpression( this ); diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmByUnit.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmByUnit.java index d37f2da8cf..22a5173ee3 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmByUnit.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmByUnit.java @@ -9,6 +9,7 @@ package org.hibernate.query.sqm.tree.expression; import org.hibernate.query.sqm.NodeBuilder; import org.hibernate.query.sqm.SemanticQueryWalker; import org.hibernate.query.sqm.SqmExpressible; +import org.hibernate.query.sqm.tree.SqmCopyContext; /** * @author Gavin King @@ -27,6 +28,25 @@ public class SqmByUnit extends AbstractSqmExpression { this.duration = duration; } + @Override + public SqmByUnit copy(SqmCopyContext context) { + final SqmByUnit existing = context.getCopy( this ); + if ( existing != null ) { + return existing; + } + final SqmByUnit expression = context.registerCopy( + this, + new SqmByUnit( + unit.copy( context ), + duration.copy( context ), + getNodeType(), + nodeBuilder() + ) + ); + copyTo( expression, context ); + return expression; + } + public SqmDurationUnit getUnit() { return unit; } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmCaseSearched.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmCaseSearched.java index b4a1833787..e63cbe9da4 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmCaseSearched.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmCaseSearched.java @@ -14,6 +14,7 @@ import org.hibernate.query.internal.QueryHelper; import org.hibernate.query.sqm.NodeBuilder; import org.hibernate.query.sqm.SemanticQueryWalker; import org.hibernate.query.sqm.SqmExpressible; +import org.hibernate.query.sqm.tree.SqmCopyContext; import org.hibernate.query.sqm.tree.predicate.SqmPredicate; import jakarta.persistence.criteria.Expression; @@ -41,6 +42,35 @@ public class SqmCaseSearched this.whenFragments = new ArrayList<>( estimateWhenSize ); } + @Override + public SqmCaseSearched copy(SqmCopyContext context) { + final SqmCaseSearched existing = context.getCopy( this ); + if ( existing != null ) { + return existing; + } + final SqmCaseSearched caseSearched = context.registerCopy( + this, + new SqmCaseSearched<>( + getNodeType(), + whenFragments.size(), + nodeBuilder() + ) + ); + for ( WhenFragment whenFragment : whenFragments ) { + caseSearched.whenFragments.add( + new WhenFragment<>( + whenFragment.predicate.copy( context ), + whenFragment.result.copy( context ) + ) + ); + } + if ( otherwise != null ) { + caseSearched.otherwise = otherwise.copy( context ); + } + copyTo( caseSearched, context ); + return caseSearched; + } + public List> getWhenFragments() { return whenFragments; } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmCaseSimple.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmCaseSimple.java index be8499bb40..3291f027ca 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmCaseSimple.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmCaseSimple.java @@ -9,8 +9,6 @@ package org.hibernate.query.sqm.tree.expression; import java.util.ArrayList; import java.util.List; -import jakarta.persistence.criteria.Expression; - import org.hibernate.NotYetImplementedFor6Exception; import org.hibernate.query.criteria.JpaExpression; import org.hibernate.query.criteria.JpaSimpleCase; @@ -19,9 +17,12 @@ import org.hibernate.query.sqm.NodeBuilder; import org.hibernate.query.sqm.SqmExpressible; import org.hibernate.query.sqm.SemanticQueryWalker; import org.hibernate.query.sqm.sql.internal.DomainResultProducer; +import org.hibernate.query.sqm.tree.SqmCopyContext; import org.hibernate.sql.results.graph.DomainResult; import org.hibernate.sql.results.graph.DomainResultCreationState; +import jakarta.persistence.criteria.Expression; + /** * @author Steve Ebersole */ @@ -48,6 +49,36 @@ public class SqmCaseSimple this.fixture = fixture; } + @Override + public SqmCaseSimple copy(SqmCopyContext context) { + final SqmCaseSimple existing = context.getCopy( this ); + if ( existing != null ) { + return existing; + } + final SqmCaseSimple caseSearched = context.registerCopy( + this, + new SqmCaseSimple<>( + fixture.copy( context ), + getNodeType(), + whenFragments.size(), + nodeBuilder() + ) + ); + for ( SqmCaseSimple.WhenFragment whenFragment : whenFragments ) { + caseSearched.whenFragments.add( + new SqmCaseSimple.WhenFragment<>( + whenFragment.checkValue.copy( context ), + whenFragment.result.copy( context ) + ) + ); + } + if ( otherwise != null ) { + caseSearched.otherwise = otherwise.copy( context ); + } + copyTo( caseSearched, context ); + return caseSearched; + } + public SqmExpression getFixture() { return fixture; } @@ -131,6 +162,11 @@ public class SqmCaseSimple this.result = result; } + private WhenFragment(WhenFragment original, SqmCopyContext context) { + this.checkValue = original.checkValue.copy( context ); + this.result = original.result.copy( context ); + } + public SqmExpression getCheckValue() { return checkValue; } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmCastTarget.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmCastTarget.java index 70dc90f784..4c63ecfe57 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmCastTarget.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmCastTarget.java @@ -11,6 +11,7 @@ import org.hibernate.query.sqm.NodeBuilder; import org.hibernate.query.sqm.SemanticQueryWalker; import org.hibernate.query.sqm.SqmExpressible; import org.hibernate.query.sqm.tree.AbstractSqmNode; +import org.hibernate.query.sqm.tree.SqmCopyContext; import org.hibernate.query.sqm.tree.SqmTypedNode; import org.hibernate.query.sqm.tree.SqmVisitableNode; @@ -70,6 +71,11 @@ public class SqmCastTarget extends AbstractSqmNode implements SqmTypedNode this.scale = scale; } + @Override + public SqmCastTarget copy(SqmCopyContext context) { + return this; + } + public ReturnableType getType() { return type; } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmCoalesce.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmCoalesce.java index 8512b64f58..9ffea6b339 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmCoalesce.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmCoalesce.java @@ -6,6 +6,9 @@ */ package org.hibernate.query.sqm.tree.expression; +import java.util.ArrayList; +import java.util.List; + import org.hibernate.internal.util.collections.CollectionHelper; import org.hibernate.query.criteria.JpaCoalesce; import org.hibernate.query.criteria.JpaExpression; @@ -13,10 +16,9 @@ import org.hibernate.query.sqm.NodeBuilder; import org.hibernate.query.sqm.SemanticQueryWalker; import org.hibernate.query.sqm.SqmExpressible; import org.hibernate.query.sqm.function.SqmFunctionDescriptor; +import org.hibernate.query.sqm.tree.SqmCopyContext; import jakarta.persistence.criteria.Expression; -import java.util.ArrayList; -import java.util.List; /** * @author Steve Ebersole @@ -42,6 +44,27 @@ public class SqmCoalesce extends AbstractSqmExpression implements JpaCoale this.arguments = new ArrayList<>( numberOfArguments ); } + @Override + public SqmCoalesce copy(SqmCopyContext context) { + final SqmCoalesce existing = context.getCopy( this ); + if ( existing != null ) { + return existing; + } + final SqmCoalesce coalesce = context.registerCopy( + this, + new SqmCoalesce<>( + getNodeType(), + arguments.size(), + nodeBuilder() + ) + ); + for ( SqmExpression argument : arguments ) { + coalesce.arguments.add( argument.copy( context ) ); + } + copyTo( coalesce, context ); + return coalesce; + } + public SqmFunctionDescriptor getFunctionDescriptor() { return functionDescriptor; } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmCollation.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmCollation.java index 06649726d1..789a1df7af 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmCollation.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmCollation.java @@ -9,6 +9,7 @@ package org.hibernate.query.sqm.tree.expression; import org.hibernate.query.sqm.NodeBuilder; import org.hibernate.query.sqm.SemanticQueryWalker; import org.hibernate.query.sqm.SqmExpressible; +import org.hibernate.query.sqm.tree.SqmCopyContext; /** * @author Christian Beikov @@ -18,6 +19,20 @@ public class SqmCollation extends SqmLiteral { super(value, inherentType, nodeBuilder); } + @Override + public SqmCollation copy(SqmCopyContext context) { + final SqmCollation existing = context.getCopy( this ); + if ( existing != null ) { + return existing; + } + final SqmCollation expression = context.registerCopy( + this, + new SqmCollation( getLiteralValue(), getNodeType(), nodeBuilder() ) + ); + copyTo( expression, context ); + return expression; + } + @Override public R accept(SemanticQueryWalker walker) { return walker.visitCollation( this ); diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmCollectionSize.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmCollectionSize.java index c226525e1e..dcf1268bcc 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmCollectionSize.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmCollectionSize.java @@ -9,6 +9,7 @@ package org.hibernate.query.sqm.tree.expression; import org.hibernate.query.sqm.NodeBuilder; import org.hibernate.query.sqm.SemanticQueryWalker; import org.hibernate.query.sqm.SqmExpressible; +import org.hibernate.query.sqm.tree.SqmCopyContext; import org.hibernate.query.sqm.tree.domain.SqmPath; /** @@ -29,6 +30,24 @@ public class SqmCollectionSize extends AbstractSqmExpression { this.pluralPath = pluralPath; } + @Override + public SqmCollectionSize copy(SqmCopyContext context) { + final SqmCollectionSize existing = context.getCopy( this ); + if ( existing != null ) { + return existing; + } + final SqmCollectionSize expression = context.registerCopy( + this, + new SqmCollectionSize( + pluralPath.copy( context ), + getNodeType(), + nodeBuilder() + ) + ); + copyTo( expression, context ); + return expression; + } + public SqmPath getPluralPath() { return pluralPath; } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmDistinct.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmDistinct.java index 2a2496c643..1f3c3b2906 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmDistinct.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmDistinct.java @@ -10,6 +10,7 @@ import org.hibernate.query.sqm.NodeBuilder; import org.hibernate.query.sqm.SqmExpressible; import org.hibernate.query.sqm.SemanticQueryWalker; import org.hibernate.query.sqm.tree.AbstractSqmNode; +import org.hibernate.query.sqm.tree.SqmCopyContext; import org.hibernate.query.sqm.tree.SqmTypedNode; import org.hibernate.query.sqm.tree.SqmVisitableNode; @@ -21,10 +22,25 @@ public class SqmDistinct extends AbstractSqmNode implements SqmTypedNode, private final SqmExpression expression; public SqmDistinct(SqmExpression expression, NodeBuilder builder) { - super(builder); + super( builder ); this.expression = expression; } + @Override + public SqmDistinct copy(SqmCopyContext context) { + final SqmDistinct existing = context.getCopy( this ); + if ( existing != null ) { + return existing; + } + return context.registerCopy( + this, + new SqmDistinct<>( + expression.copy( context ), + nodeBuilder() + ) + ); + } + public SqmExpression getExpression() { return expression; } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmDurationUnit.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmDurationUnit.java index bfea5df55b..a769cc549b 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmDurationUnit.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmDurationUnit.java @@ -12,6 +12,7 @@ import org.hibernate.query.sqm.NodeBuilder; import org.hibernate.query.sqm.SemanticQueryWalker; import org.hibernate.query.sqm.SqmExpressible; import org.hibernate.query.sqm.tree.AbstractSqmNode; +import org.hibernate.query.sqm.tree.SqmCopyContext; import org.hibernate.query.sqm.tree.SqmTypedNode; import org.hibernate.query.sqm.tree.SqmVisitableNode; @@ -28,6 +29,11 @@ public class SqmDurationUnit extends AbstractSqmNode implements SqmTypedNode< this.unit = unit; } + @Override + public SqmDurationUnit copy(SqmCopyContext context) { + return this; + } + public ReturnableType getType() { return type; } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmEnumLiteral.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmEnumLiteral.java index 48c664c834..8870283b83 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmEnumLiteral.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmEnumLiteral.java @@ -16,6 +16,7 @@ import org.hibernate.query.hql.spi.SqmCreationState; import org.hibernate.query.sqm.NodeBuilder; import org.hibernate.query.sqm.SemanticQueryWalker; import org.hibernate.query.sqm.SqmExpressible; +import org.hibernate.query.sqm.tree.SqmCopyContext; import org.hibernate.query.sqm.tree.domain.SqmPath; import org.hibernate.type.descriptor.java.EnumJavaType; @@ -42,6 +43,25 @@ public class SqmEnumLiteral> extends AbstractSqmExpression setExpressibleType( this ); } + @Override + public SqmEnumLiteral copy(SqmCopyContext context) { + final SqmEnumLiteral existing = context.getCopy( this ); + if ( existing != null ) { + return existing; + } + final SqmEnumLiteral expression = context.registerCopy( + this, + new SqmEnumLiteral<>( + enumValue, + referencedEnumTypeDescriptor, + enumValueName, + nodeBuilder() + ) + ); + copyTo( expression, context ); + return expression; + } + public E getEnumValue() { return enumValue; } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmEvery.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmEvery.java index fed1662f79..e7b6280b12 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmEvery.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmEvery.java @@ -8,6 +8,7 @@ package org.hibernate.query.sqm.tree.expression; import org.hibernate.query.sqm.NodeBuilder; import org.hibernate.query.sqm.SemanticQueryWalker; +import org.hibernate.query.sqm.tree.SqmCopyContext; import org.hibernate.query.sqm.tree.select.SqmSubQuery; /** @@ -22,6 +23,20 @@ public class SqmEvery extends AbstractSqmExpression { this.subquery = subquery; } + @Override + public SqmEvery copy(SqmCopyContext context) { + final SqmEvery existing = context.getCopy( this ); + if ( existing != null ) { + return existing; + } + final SqmEvery expression = context.registerCopy( + this, + new SqmEvery<>( subquery.copy( context ), nodeBuilder() ) + ); + copyTo( expression, context ); + return expression; + } + public SqmSubQuery getSubquery() { return subquery; } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmExpression.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmExpression.java index fa0f217c1c..0523ed6830 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmExpression.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmExpression.java @@ -18,6 +18,7 @@ import org.hibernate.metamodel.model.domain.DomainType; import org.hibernate.query.criteria.JpaExpression; import org.hibernate.query.spi.QueryEngine; import org.hibernate.query.sqm.SqmExpressible; +import org.hibernate.query.sqm.tree.SqmCopyContext; import org.hibernate.query.sqm.tree.predicate.SqmPredicate; import org.hibernate.query.sqm.tree.select.SqmSelectableNode; @@ -102,6 +103,9 @@ public interface SqmExpression extends SqmSelectableNode, JpaExpression @Override SqmPredicate in(Expression> values); + @Override + SqmExpression copy(SqmCopyContext context); + default SqmExpression castAs(DomainType type) { if ( getNodeType() == type ) { return (SqmExpression) this; diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmExtractUnit.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmExtractUnit.java index 7c54007a8e..d68b68ad72 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmExtractUnit.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmExtractUnit.java @@ -12,6 +12,7 @@ import org.hibernate.query.sqm.NodeBuilder; import org.hibernate.query.sqm.SemanticQueryWalker; import org.hibernate.query.sqm.SqmExpressible; import org.hibernate.query.sqm.tree.AbstractSqmNode; +import org.hibernate.query.sqm.tree.SqmCopyContext; import org.hibernate.query.sqm.tree.SqmTypedNode; import org.hibernate.query.sqm.tree.SqmVisitableNode; @@ -28,6 +29,11 @@ public class SqmExtractUnit extends AbstractSqmNode implements SqmTypedNode copy(SqmCopyContext context) { + return this; + } + public TemporalUnit getUnit() { return unit; } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmFieldLiteral.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmFieldLiteral.java index 9eb8edf874..c5e1e75517 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmFieldLiteral.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmFieldLiteral.java @@ -21,6 +21,7 @@ import org.hibernate.query.hql.spi.SqmCreationState; import org.hibernate.query.sqm.NodeBuilder; import org.hibernate.query.sqm.SemanticQueryWalker; import org.hibernate.query.sqm.SqmExpressible; +import org.hibernate.query.sqm.tree.SqmCopyContext; import org.hibernate.query.sqm.tree.domain.SqmPath; import org.hibernate.query.sqm.tree.predicate.SqmPredicate; import org.hibernate.query.sqm.tree.select.SqmSelectableNode; @@ -51,16 +52,6 @@ public class SqmFieldLiteral implements SqmExpression, SqmExpressible, ); } - private static T extractValue(Field field) { - try { - //noinspection unchecked - return (T) field.get( null ); - } - catch (IllegalAccessException e) { - throw new QueryException( "Could not access Field value for SqmFieldLiteral", e ); - } - } - public SqmFieldLiteral( T value, JavaType fieldJavaType, @@ -74,6 +65,33 @@ public class SqmFieldLiteral implements SqmExpression, SqmExpressible, this.expressible = this; } + private static T extractValue(Field field) { + try { + //noinspection unchecked + return (T) field.get( null ); + } + catch (IllegalAccessException e) { + throw new QueryException( "Could not access Field value for SqmFieldLiteral", e ); + } + } + + @Override + public SqmFieldLiteral copy(SqmCopyContext context) { + final SqmFieldLiteral existing = context.getCopy( this ); + if ( existing != null ) { + return existing; + } + return context.registerCopy( + this, + new SqmFieldLiteral<>( + value, + fieldJavaType, + fieldName, + nodeBuilder() + ) + ); + } + public T getValue() { return value; } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmFormat.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmFormat.java index 7ba0c8366f..e54ea355be 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmFormat.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmFormat.java @@ -12,6 +12,7 @@ import org.hibernate.query.SemanticException; import org.hibernate.query.sqm.NodeBuilder; import org.hibernate.query.sqm.SemanticQueryWalker; import org.hibernate.query.sqm.SqmExpressible; +import org.hibernate.query.sqm.tree.SqmCopyContext; /** * Effectively a query-literal but we want to handle it specially in the SQM -> SQL AST conversion @@ -51,6 +52,24 @@ public class SqmFormat extends SqmLiteral { } } + @Override + public SqmFormat copy(SqmCopyContext context) { + final SqmFormat existing = context.getCopy( this ); + if ( existing != null ) { + return existing; + } + final SqmFormat expression = context.registerCopy( + this, + new SqmFormat( + getLiteralValue(), + getNodeType(), + nodeBuilder() + ) + ); + copyTo( expression, context ); + return expression; + } + @Override public R accept(SemanticQueryWalker walker) { return walker.visitFormat( this ); diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmFunction.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmFunction.java index c4b792a12b..20e4f2d434 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmFunction.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmFunction.java @@ -6,6 +6,8 @@ */ package org.hibernate.query.sqm.tree.expression; +import java.util.List; + import org.hibernate.query.criteria.JpaFunction; import org.hibernate.query.hql.spi.SemanticPathPart; import org.hibernate.query.hql.spi.SqmCreationState; @@ -19,8 +21,6 @@ import org.hibernate.query.sqm.tree.domain.SqmPath; import org.hibernate.query.sqm.tree.select.SqmSelectableNode; import org.hibernate.sql.ast.tree.expression.Expression; -import java.util.List; - /** * A SQM function * diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmJpaCriteriaParameterWrapper.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmJpaCriteriaParameterWrapper.java index 6d3cdca151..80b15e8d82 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmJpaCriteriaParameterWrapper.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmJpaCriteriaParameterWrapper.java @@ -11,6 +11,7 @@ import java.util.function.Consumer; import org.hibernate.query.BindableType; import org.hibernate.query.sqm.NodeBuilder; import org.hibernate.query.sqm.SemanticQueryWalker; +import org.hibernate.query.sqm.tree.SqmCopyContext; import org.hibernate.query.sqm.tree.select.SqmSelectableNode; import org.hibernate.sql.ast.tree.expression.JdbcParameter; @@ -35,6 +36,22 @@ public class SqmJpaCriteriaParameterWrapper this.jpaCriteriaParameter = jpaCriteriaParameter; } + @Override + public SqmJpaCriteriaParameterWrapper copy(SqmCopyContext context) { + final SqmJpaCriteriaParameterWrapper existing = context.getCopy( this ); + if ( existing != null ) { + return existing; + } + return context.registerCopy( + this, + new SqmJpaCriteriaParameterWrapper<>( + getNodeType(), + jpaCriteriaParameter.copy( context ), + nodeBuilder() + ) + ); + } + @Override public String getName() { return jpaCriteriaParameter.getName(); diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmLiteral.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmLiteral.java index 29880271ce..f38ea59598 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmLiteral.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmLiteral.java @@ -10,6 +10,7 @@ import org.hibernate.query.internal.QueryLiteralHelper; import org.hibernate.query.sqm.NodeBuilder; import org.hibernate.query.sqm.SemanticQueryWalker; import org.hibernate.query.sqm.SqmExpressible; +import org.hibernate.query.sqm.tree.SqmCopyContext; import org.hibernate.type.descriptor.java.JavaType; /** @@ -36,6 +37,24 @@ public class SqmLiteral extends AbstractSqmExpression { this.value = null; } + @Override + public SqmLiteral copy(SqmCopyContext context) { + final SqmLiteral existing = context.getCopy( this ); + if ( existing != null ) { + return existing; + } + final SqmLiteral expression = context.registerCopy( + this, + new SqmLiteral<>( + getLiteralValue(), + getNodeType(), + nodeBuilder() + ) + ); + copyTo( expression, context ); + return expression; + } + public T getLiteralValue() { return value; } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmLiteralEntityType.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmLiteralEntityType.java index daef99f959..0c76f920b3 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmLiteralEntityType.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmLiteralEntityType.java @@ -13,6 +13,7 @@ import org.hibernate.query.hql.spi.SqmCreationState; import org.hibernate.query.sqm.NodeBuilder; import org.hibernate.query.sqm.SemanticQueryWalker; import org.hibernate.query.sqm.SqmExpressible; +import org.hibernate.query.sqm.tree.SqmCopyContext; import org.hibernate.query.sqm.tree.domain.SqmPath; import org.hibernate.query.sqm.tree.select.SqmSelectableNode; @@ -35,6 +36,23 @@ public class SqmLiteralEntityType this.entityType = entityType; } + @Override + public SqmLiteralEntityType copy(SqmCopyContext context) { + final SqmLiteralEntityType existing = context.getCopy( this ); + if ( existing != null ) { + return existing; + } + final SqmLiteralEntityType expression = context.registerCopy( + this, + new SqmLiteralEntityType<>( + entityType, + nodeBuilder() + ) + ); + copyTo( expression, context ); + return expression; + } + @Override public EntityDomainType getNodeType() { return entityType; diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmLiteralNull.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmLiteralNull.java index b46786ac20..40e9225b1b 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmLiteralNull.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmLiteralNull.java @@ -9,6 +9,7 @@ package org.hibernate.query.sqm.tree.expression; import org.hibernate.query.sqm.NodeBuilder; import org.hibernate.query.sqm.SqmExpressible; import org.hibernate.query.sqm.SemanticQueryWalker; +import org.hibernate.query.sqm.tree.SqmCopyContext; /** * @author Steve Ebersole @@ -26,6 +27,23 @@ public class SqmLiteralNull extends SqmLiteral { super( expressibleType, nodeBuilder ); } + @Override + public SqmLiteralNull copy(SqmCopyContext context) { + final SqmLiteralNull existing = context.getCopy( this ); + if ( existing != null ) { + return existing; + } + final SqmLiteralNull expression = context.registerCopy( + this, + new SqmLiteralNull<>( + getNodeType(), + nodeBuilder() + ) + ); + copyTo( expression, context ); + return expression; + } + @Override public X accept(SemanticQueryWalker walker) { return walker.visitLiteral( this ); diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmModifiedSubQueryExpression.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmModifiedSubQueryExpression.java index f81f4a0ce9..d2679d02fe 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmModifiedSubQueryExpression.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmModifiedSubQueryExpression.java @@ -9,6 +9,7 @@ package org.hibernate.query.sqm.tree.expression; import org.hibernate.query.sqm.NodeBuilder; import org.hibernate.query.sqm.SemanticQueryWalker; import org.hibernate.query.sqm.SqmExpressible; +import org.hibernate.query.sqm.tree.SqmCopyContext; import org.hibernate.query.sqm.tree.select.SqmSubQuery; /** @@ -49,6 +50,25 @@ public class SqmModifiedSubQueryExpression extends AbstractSqmExpression { this.modifier = modifier; } + @Override + public SqmModifiedSubQueryExpression copy(SqmCopyContext context) { + final SqmModifiedSubQueryExpression existing = context.getCopy( this ); + if ( existing != null ) { + return existing; + } + final SqmModifiedSubQueryExpression expression = context.registerCopy( + this, + new SqmModifiedSubQueryExpression<>( + subQuery.copy( context ), + modifier, + getNodeType(), + nodeBuilder() + ) + ); + copyTo( expression, context ); + return expression; + } + public Modifier getModifier() { return modifier; } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmNamedParameter.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmNamedParameter.java index de5baf108a..5b946119f5 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmNamedParameter.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmNamedParameter.java @@ -9,6 +9,7 @@ package org.hibernate.query.sqm.tree.expression; import org.hibernate.query.sqm.NodeBuilder; import org.hibernate.query.sqm.SemanticQueryWalker; import org.hibernate.query.sqm.SqmExpressible; +import org.hibernate.query.sqm.tree.SqmCopyContext; /** * Represents a named query parameter in the SQM tree. @@ -31,6 +32,25 @@ public class SqmNamedParameter extends AbstractSqmParameter { this.name = name; } + @Override + public SqmNamedParameter copy(SqmCopyContext context) { + final SqmNamedParameter existing = context.getCopy( this ); + if ( existing != null ) { + return existing; + } + final SqmNamedParameter expression = context.registerCopy( + this, + new SqmNamedParameter<>( + name, + allowMultiValuedBinding(), + getNodeType(), + nodeBuilder() + ) + ); + copyTo( expression, context ); + return expression; + } + @Override public X accept(SemanticQueryWalker walker) { return walker.visitNamedParameterExpression( this ); diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmParameter.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmParameter.java index 747ce46b0b..f00a21b53c 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmParameter.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmParameter.java @@ -9,6 +9,7 @@ package org.hibernate.query.sqm.tree.expression; import org.hibernate.query.BindableType; import org.hibernate.query.criteria.JpaParameterExpression; import org.hibernate.query.sqm.SqmExpressible; +import org.hibernate.query.sqm.tree.SqmCopyContext; /** * Models a parameter expression declared in the query. @@ -70,4 +71,7 @@ public interface SqmParameter extends SqmExpression, JpaParameterExpressio * Make a copy */ SqmParameter copy(); + + @Override + SqmParameter copy(SqmCopyContext context); } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmParameterizedEntityType.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmParameterizedEntityType.java index 25db81ee09..a2f16cfc9e 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmParameterizedEntityType.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmParameterizedEntityType.java @@ -9,6 +9,7 @@ package org.hibernate.query.sqm.tree.expression; import org.hibernate.query.sqm.NodeBuilder; import org.hibernate.query.sqm.SemanticQueryWalker; import org.hibernate.query.sqm.SqmExpressible; +import org.hibernate.query.sqm.tree.SqmCopyContext; import org.hibernate.query.sqm.tree.select.SqmSelectableNode; /** @@ -17,7 +18,7 @@ import org.hibernate.query.sqm.tree.select.SqmSelectableNode; * @author Steve Ebersole */ public class SqmParameterizedEntityType extends AbstractSqmExpression implements SqmSelectableNode { - private final SqmExpression discriminatorSource; + private final SqmParameter discriminatorSource; public SqmExpression getDiscriminatorSource() { return discriminatorSource; @@ -28,6 +29,23 @@ public class SqmParameterizedEntityType extends AbstractSqmExpression impl this.discriminatorSource = parameterExpression; } + @Override + public SqmParameterizedEntityType copy(SqmCopyContext context) { + final SqmParameterizedEntityType existing = context.getCopy( this ); + if ( existing != null ) { + return existing; + } + final SqmParameterizedEntityType expression = context.registerCopy( + this, + new SqmParameterizedEntityType<>( + discriminatorSource.copy( context ), + nodeBuilder() + ) + ); + copyTo( expression, context ); + return expression; + } + @Override public void internalApplyInferableType(SqmExpressible type) { setExpressibleType( type ); diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmPositionalParameter.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmPositionalParameter.java index 29e6de086b..407b6e180c 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmPositionalParameter.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmPositionalParameter.java @@ -9,6 +9,7 @@ package org.hibernate.query.sqm.tree.expression; import org.hibernate.query.sqm.NodeBuilder; import org.hibernate.query.sqm.SemanticQueryWalker; import org.hibernate.query.sqm.SqmExpressible; +import org.hibernate.query.sqm.tree.SqmCopyContext; /** * Models a positional parameter expression @@ -34,6 +35,25 @@ public class SqmPositionalParameter extends AbstractSqmParameter { this.position = position; } + @Override + public SqmPositionalParameter copy(SqmCopyContext context) { + final SqmPositionalParameter existing = context.getCopy( this ); + if ( existing != null ) { + return existing; + } + final SqmPositionalParameter expression = context.registerCopy( + this, + new SqmPositionalParameter<>( + position, + allowMultiValuedBinding(), + getNodeType(), + nodeBuilder() + ) + ); + copyTo( expression, context ); + return expression; + } + @Override public Integer getPosition() { return position; diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmSelfRenderingExpression.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmSelfRenderingExpression.java index 8727cea20c..122f0bde0d 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmSelfRenderingExpression.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmSelfRenderingExpression.java @@ -11,6 +11,7 @@ import java.util.function.Function; import org.hibernate.query.sqm.NodeBuilder; import org.hibernate.query.sqm.SemanticQueryWalker; import org.hibernate.query.sqm.SqmExpressible; +import org.hibernate.query.sqm.tree.SqmCopyContext; import org.hibernate.sql.ast.tree.expression.Expression; /** @@ -27,6 +28,20 @@ public class SqmSelfRenderingExpression extends AbstractSqmExpression { this.renderer = renderer; } + @Override + public SqmSelfRenderingExpression copy(SqmCopyContext context) { + final SqmSelfRenderingExpression existing = context.getCopy( this ); + if ( existing != null ) { + return existing; + } + final SqmSelfRenderingExpression expression = context.registerCopy( + this, + new SqmSelfRenderingExpression<>( renderer, getNodeType(), nodeBuilder() ) + ); + copyTo( expression, context ); + return expression; + } + @Override public X accept(SemanticQueryWalker walker) { //noinspection unchecked diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmStar.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmStar.java index 90ec7f284b..0aa35a275c 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmStar.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmStar.java @@ -8,6 +8,7 @@ package org.hibernate.query.sqm.tree.expression; import org.hibernate.query.sqm.NodeBuilder; import org.hibernate.query.sqm.SemanticQueryWalker; +import org.hibernate.query.sqm.tree.SqmCopyContext; /** * @author Gavin King @@ -18,6 +19,15 @@ public class SqmStar extends AbstractSqmExpression { super( null, builder ); } + @Override + public SqmStar copy(SqmCopyContext context) { + final SqmStar existing = context.getCopy( this ); + if ( existing != null ) { + return existing; + } + return context.registerCopy( this, new SqmStar( nodeBuilder() ) ); + } + @Override public X accept(SemanticQueryWalker walker) { return walker.visitStar( this ); diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmSummarization.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmSummarization.java index fa8bb7a0af..777061919f 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmSummarization.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmSummarization.java @@ -6,11 +6,12 @@ */ package org.hibernate.query.sqm.tree.expression; +import java.util.ArrayList; import java.util.List; import org.hibernate.query.sqm.NodeBuilder; import org.hibernate.query.sqm.SemanticQueryWalker; -import org.hibernate.query.sqm.tree.select.SqmSubQuery; +import org.hibernate.query.sqm.tree.SqmCopyContext; /** * @author Christian Beikov @@ -26,6 +27,28 @@ public class SqmSummarization extends AbstractSqmExpression { this.groupings = groupings; } + @Override + public SqmSummarization copy(SqmCopyContext context) { + final SqmSummarization existing = context.getCopy( this ); + if ( existing != null ) { + return existing; + } + final List> groupings = new ArrayList<>( this.groupings.size() ); + for ( SqmExpression grouping : this.groupings ) { + groupings.add( grouping.copy( context ) ); + } + final SqmSummarization expression = context.registerCopy( + this, + new SqmSummarization<>( + kind, + groupings, + nodeBuilder() + ) + ); + copyTo( expression, context ); + return expression; + } + public Kind getKind() { return kind; } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmToDuration.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmToDuration.java index e7a0c16eca..26e365534b 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmToDuration.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmToDuration.java @@ -9,6 +9,7 @@ package org.hibernate.query.sqm.tree.expression; import org.hibernate.query.ReturnableType; import org.hibernate.query.sqm.NodeBuilder; import org.hibernate.query.sqm.SemanticQueryWalker; +import org.hibernate.query.sqm.tree.SqmCopyContext; /** * @author Gavin King @@ -27,6 +28,25 @@ public class SqmToDuration extends AbstractSqmExpression { this.unit = unit; } + @Override + public SqmToDuration copy(SqmCopyContext context) { + final SqmToDuration existing = context.getCopy( this ); + if ( existing != null ) { + return existing; + } + final SqmToDuration expression = context.registerCopy( + this, + new SqmToDuration<>( + magnitude.copy( context ), + unit.copy( context ), + (ReturnableType) getNodeType(), + nodeBuilder() + ) + ); + copyTo( expression, context ); + return expression; + } + public SqmExpression getMagnitude() { return magnitude; } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmTrimSpecification.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmTrimSpecification.java index a9beb2fc38..62a57b38b1 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmTrimSpecification.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmTrimSpecification.java @@ -10,7 +10,9 @@ import org.hibernate.query.sqm.TrimSpec; import org.hibernate.query.sqm.NodeBuilder; import org.hibernate.query.sqm.SqmExpressible; import org.hibernate.query.sqm.SemanticQueryWalker; +import org.hibernate.query.sqm.SqmExpressible; import org.hibernate.query.sqm.tree.AbstractSqmNode; +import org.hibernate.query.sqm.tree.SqmCopyContext; import org.hibernate.query.sqm.tree.SqmTypedNode; import org.hibernate.query.sqm.tree.SqmVisitableNode; @@ -28,6 +30,11 @@ public class SqmTrimSpecification extends AbstractSqmNode implements SqmTypedNod this.specification = specification; } + @Override + public SqmTrimSpecification copy(SqmCopyContext context) { + return this; + } + public TrimSpec getSpecification() { return specification; } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmTuple.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmTuple.java index 728aec7ce5..4372f27b90 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmTuple.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmTuple.java @@ -6,6 +6,7 @@ */ package org.hibernate.query.sqm.tree.expression; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -15,6 +16,7 @@ import org.hibernate.query.criteria.JpaSelection; import org.hibernate.query.sqm.NodeBuilder; import org.hibernate.query.sqm.SemanticQueryWalker; import org.hibernate.query.sqm.SqmExpressible; +import org.hibernate.query.sqm.tree.SqmCopyContext; import org.hibernate.query.sqm.tree.select.SqmJpaCompoundSelection; /** @@ -55,6 +57,24 @@ public class SqmTuple } } + @Override + public SqmTuple copy(SqmCopyContext context) { + final SqmTuple existing = context.getCopy( this ); + if ( existing != null ) { + return existing; + } + final List> groupedExpressions = new ArrayList<>( this.groupedExpressions.size() ); + for ( SqmExpression groupedExpression : this.groupedExpressions ) { + groupedExpressions.add( groupedExpression.copy( context ) ); + } + final SqmTuple expression = context.registerCopy( + this, + new SqmTuple<>( groupedExpressions, getNodeType(), nodeBuilder() ) + ); + copyTo( expression, context ); + return expression; + } + public List> getGroupedExpressions() { return groupedExpressions; } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmUnaryOperation.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmUnaryOperation.java index 7105e77527..18838d3d00 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmUnaryOperation.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmUnaryOperation.java @@ -9,6 +9,7 @@ package org.hibernate.query.sqm.tree.expression; import org.hibernate.query.sqm.UnaryArithmeticOperator; import org.hibernate.query.sqm.SemanticQueryWalker; import org.hibernate.query.sqm.SqmExpressible; +import org.hibernate.query.sqm.tree.SqmCopyContext; import org.hibernate.query.sqm.tree.select.SqmSelectableNode; /** @@ -16,7 +17,7 @@ import org.hibernate.query.sqm.tree.select.SqmSelectableNode; */ public class SqmUnaryOperation extends AbstractSqmExpression implements SqmSelectableNode { private final UnaryArithmeticOperator operation; - private final SqmExpression operand; + private final SqmExpression operand; public SqmUnaryOperation( UnaryArithmeticOperator operation, @@ -33,6 +34,24 @@ public class SqmUnaryOperation extends AbstractSqmExpression implements Sq this.operand = operand; } + @Override + public SqmUnaryOperation copy(SqmCopyContext context) { + final SqmUnaryOperation existing = context.getCopy( this ); + if ( existing != null ) { + return existing; + } + final SqmUnaryOperation expression = context.registerCopy( + this, + new SqmUnaryOperation<>( + operation, + operand.copy( context ), + getNodeType() + ) + ); + copyTo( expression, context ); + return expression; + } + public SqmExpression getOperand() { return operand; } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/ValueBindJpaCriteriaParameter.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/ValueBindJpaCriteriaParameter.java index 3da601f996..76f7aae5ec 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/ValueBindJpaCriteriaParameter.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/ValueBindJpaCriteriaParameter.java @@ -8,6 +8,7 @@ package org.hibernate.query.sqm.tree.expression; import org.hibernate.query.BindableType; import org.hibernate.query.sqm.NodeBuilder; +import org.hibernate.query.sqm.tree.SqmCopyContext; /** * It is a JpaCriteriaParameter created from a value when ValueHandlingMode is equal to BIND @@ -25,6 +26,20 @@ public class ValueBindJpaCriteriaParameter extends JpaCriteriaParameter{ this.value = value; } + private ValueBindJpaCriteriaParameter(ValueBindJpaCriteriaParameter original) { + super( original ); + this.value = original.value; + } + + @Override + public ValueBindJpaCriteriaParameter copy(SqmCopyContext context) { + final ValueBindJpaCriteriaParameter existing = context.getCopy( this ); + if ( existing != null ) { + return existing; + } + return context.registerCopy( this, new ValueBindJpaCriteriaParameter<>( this ) ); + } + public T getValue() { return value; } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/from/SqmCrossJoin.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/from/SqmCrossJoin.java index 9437dc8a45..8ff7813c29 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/from/SqmCrossJoin.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/from/SqmCrossJoin.java @@ -12,6 +12,7 @@ import org.hibernate.query.PathException; import org.hibernate.query.hql.spi.SqmCreationProcessingState; import org.hibernate.query.hql.spi.SqmPathRegistry; import org.hibernate.query.sqm.SemanticQueryWalker; +import org.hibernate.query.sqm.tree.SqmCopyContext; import org.hibernate.query.sqm.tree.SqmJoinType; import org.hibernate.query.sqm.tree.domain.AbstractSqmFrom; import org.hibernate.query.sqm.tree.domain.SqmCorrelatedCrossJoin; @@ -53,6 +54,25 @@ public class SqmCrossJoin extends AbstractSqmFrom implements SqmJoin copy(SqmCopyContext context) { + final SqmCrossJoin existing = context.getCopy( this ); + if ( existing != null ) { + return existing; + } + final SqmCrossJoin path = context.registerCopy( + this, + new SqmCrossJoin<>( + getNavigablePath(), + getReferencedPathSource(), + getExplicitAlias(), + sqmRoot.copy( context ) + ) + ); + copyTo( path, context ); + return path; + } + public SqmRoot getRoot() { return sqmRoot; } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/from/SqmEntityJoin.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/from/SqmEntityJoin.java index 57c328820b..da0ebc67c1 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/from/SqmEntityJoin.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/from/SqmEntityJoin.java @@ -15,6 +15,7 @@ import org.hibernate.query.hql.spi.SqmCreationState; import org.hibernate.query.hql.spi.SqmPathRegistry; import org.hibernate.query.sqm.SemanticQueryWalker; import org.hibernate.query.sqm.spi.SqmCreationHelper; +import org.hibernate.query.sqm.tree.SqmCopyContext; import org.hibernate.query.sqm.tree.SqmJoinType; import org.hibernate.query.sqm.tree.domain.AbstractSqmJoin; import org.hibernate.query.sqm.tree.domain.SqmCorrelatedEntityJoin; @@ -54,6 +55,31 @@ public class SqmEntityJoin extends AbstractSqmJoin implements SqmQualif this.sqmRoot = sqmRoot; } + @Override + public SqmEntityJoin copy(SqmCopyContext context) { + final SqmEntityJoin existing = context.getCopy( this ); + if ( existing != null ) { + return existing; + } + final SqmEntityJoin path = context.registerCopy( + this, + new SqmEntityJoin<>( + getNavigablePath(), + getReferencedPathSource(), + getExplicitAlias(), + getSqmJoinType(), + getRoot().copy( context ) + ) + ); + copyTo( path, context ); + return path; + } + + protected void copyTo(SqmEntityJoin target, SqmCopyContext context) { + super.copyTo( target, context ); + target.joinPredicate = joinPredicate == null ? null : joinPredicate.copy( context ); + } + public SqmRoot getRoot() { return sqmRoot; } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/from/SqmFrom.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/from/SqmFrom.java index 8fa1bb021a..865d1122e2 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/from/SqmFrom.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/from/SqmFrom.java @@ -26,6 +26,7 @@ import org.hibernate.query.criteria.JpaJoin; import org.hibernate.query.criteria.JpaPath; import org.hibernate.query.hql.spi.SqmCreationState; import org.hibernate.query.sqm.SqmPathSource; +import org.hibernate.query.sqm.tree.SqmCopyContext; import org.hibernate.query.sqm.tree.SqmVisitableNode; import org.hibernate.query.sqm.tree.domain.SqmBagJoin; import org.hibernate.query.sqm.tree.domain.SqmListJoin; @@ -149,4 +150,7 @@ public interface SqmFrom extends SqmVisitableNode, SqmPath, JpaFrom SqmMapJoin joinMap(String attributeName, JoinType jt); + + @Override + SqmFrom copy(SqmCopyContext context); } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/from/SqmFromClause.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/from/SqmFromClause.java index a6d3aadb2f..c572ff9225 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/from/SqmFromClause.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/from/SqmFromClause.java @@ -13,6 +13,7 @@ import java.util.List; import java.util.function.Consumer; import org.hibernate.internal.util.collections.CollectionHelper; +import org.hibernate.query.sqm.tree.SqmCopyContext; /** * Contract representing a from clause. @@ -32,6 +33,19 @@ public class SqmFromClause implements Serializable { this.domainRoots = CollectionHelper.arrayList( expectedNumberOfRoots ); } + private SqmFromClause(SqmFromClause original, SqmCopyContext context) { + if ( original.domainRoots != null ) { + this.domainRoots = new ArrayList<>( original.domainRoots.size() ); + for ( SqmRoot domainRoot : original.domainRoots ) { + this.domainRoots.add( domainRoot.copy( context ) ); + } + } + } + + public SqmFromClause copy(SqmCopyContext context) { + return new SqmFromClause( this, context ); + } + /** * Immutable view of the domain roots. Use {@link #setRoots} or {@link #addRoot} to * mutate the roots diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/from/SqmJoin.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/from/SqmJoin.java index 06f3fd1ba2..a7b2afa4ee 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/from/SqmJoin.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/from/SqmJoin.java @@ -8,6 +8,7 @@ package org.hibernate.query.sqm.tree.from; import jakarta.persistence.criteria.JoinType; +import org.hibernate.query.sqm.tree.SqmCopyContext; import org.hibernate.query.sqm.tree.SqmJoinType; /** @@ -21,4 +22,7 @@ public interface SqmJoin extends SqmFrom { @Override SqmAttributeJoin join(String attributeName, JoinType jt); + + @Override + SqmJoin copy(SqmCopyContext context); } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/from/SqmRoot.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/from/SqmRoot.java index 368db42348..955dd8e8b8 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/from/SqmRoot.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/from/SqmRoot.java @@ -16,6 +16,7 @@ import org.hibernate.query.criteria.JpaRoot; import org.hibernate.query.sqm.NodeBuilder; import org.hibernate.query.sqm.SemanticQueryWalker; import org.hibernate.query.sqm.SqmPathSource; +import org.hibernate.query.sqm.tree.SqmCopyContext; import org.hibernate.query.sqm.tree.SqmJoinType; import org.hibernate.query.sqm.tree.domain.AbstractSqmFrom; import org.hibernate.query.sqm.tree.domain.SqmCorrelatedRoot; @@ -56,6 +57,46 @@ public class SqmRoot extends AbstractSqmFrom implements JpaRoot { this.allowJoins = true; } + protected SqmRoot( + NavigablePath navigablePath, + EntityDomainType entityType, + String alias, + boolean allowJoins, + NodeBuilder nodeBuilder) { + super( navigablePath, entityType, alias, nodeBuilder ); + this.allowJoins = allowJoins; + } + + @Override + public SqmRoot copy(SqmCopyContext context) { + final SqmRoot existing = context.getCopy( this ); + if ( existing != null ) { + return existing; + } + final SqmRoot path = context.registerCopy( + this, + new SqmRoot<>( + getNavigablePath(), + getReferencedPathSource(), + getExplicitAlias(), + allowJoins, + nodeBuilder() + ) + ); + copyTo( path, context ); + return path; + } + + protected void copyTo(SqmRoot target, SqmCopyContext context) { + super.copyTo( target, context ); + if ( orderedJoins != null ) { + target.orderedJoins = new ArrayList<>( orderedJoins.size() ); + for ( SqmJoin orderedJoin : orderedJoins ) { + target.orderedJoins.add( orderedJoin.copy( context ) ); + } + } + } + @Override public SqmPath getLhs() { // a root has no LHS diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/insert/AbstractSqmInsertStatement.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/insert/AbstractSqmInsertStatement.java index 3993c05aac..c281363484 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/insert/AbstractSqmInsertStatement.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/insert/AbstractSqmInsertStatement.java @@ -9,12 +9,17 @@ package org.hibernate.query.sqm.tree.insert; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Map; +import java.util.Set; import java.util.function.Consumer; import org.hibernate.query.sqm.NodeBuilder; import org.hibernate.query.sqm.SqmQuerySource; import org.hibernate.query.sqm.tree.AbstractSqmDmlStatement; +import org.hibernate.query.sqm.tree.SqmCopyContext; +import org.hibernate.query.sqm.tree.cte.SqmCteStatement; import org.hibernate.query.sqm.tree.domain.SqmPath; +import org.hibernate.query.sqm.tree.expression.SqmParameter; import org.hibernate.query.sqm.tree.from.SqmRoot; /** @@ -33,6 +38,31 @@ public abstract class AbstractSqmInsertStatement extends AbstractSqmDmlStatem super( targetRoot, querySource, nodeBuilder ); } + protected AbstractSqmInsertStatement( + NodeBuilder builder, + SqmQuerySource querySource, + Set> parameters, + Map> cteStatements, + boolean withRecursiveCte, + SqmRoot target, + List> insertionTargetPaths) { + super( builder, querySource, parameters, cteStatements, withRecursiveCte, target ); + this.insertionTargetPaths = insertionTargetPaths; + } + + protected List> copyInsertionTargetPaths(SqmCopyContext context) { + if ( insertionTargetPaths == null ) { + return null; + } + else { + final List> insertionTargetPaths = new ArrayList<>( this.insertionTargetPaths.size() ); + for ( SqmPath insertionTargetPath : this.insertionTargetPaths ) { + insertionTargetPaths.add( insertionTargetPath.copy( context ) ); + } + return insertionTargetPaths; + } + } + @Override public List> getInsertionTargetPaths() { return insertionTargetPaths == null diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/insert/SqmInsertSelectStatement.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/insert/SqmInsertSelectStatement.java index d352934537..9032136acb 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/insert/SqmInsertSelectStatement.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/insert/SqmInsertSelectStatement.java @@ -6,11 +6,19 @@ */ package org.hibernate.query.sqm.tree.insert; +import java.util.List; +import java.util.Map; +import java.util.Set; + import org.hibernate.query.criteria.JpaCriteriaInsertSelect; import org.hibernate.query.criteria.JpaPredicate; import org.hibernate.query.sqm.NodeBuilder; import org.hibernate.query.sqm.SemanticQueryWalker; import org.hibernate.query.sqm.SqmQuerySource; +import org.hibernate.query.sqm.tree.SqmCopyContext; +import org.hibernate.query.sqm.tree.cte.SqmCteStatement; +import org.hibernate.query.sqm.tree.domain.SqmPath; +import org.hibernate.query.sqm.tree.expression.SqmParameter; import org.hibernate.query.sqm.tree.from.SqmRoot; import org.hibernate.query.sqm.tree.select.SqmQueryPart; import org.hibernate.query.sqm.tree.select.SqmQuerySpec; @@ -40,6 +48,40 @@ public class SqmInsertSelectStatement extends AbstractSqmInsertStatement i this.selectQueryPart = new SqmQuerySpec<>( nodeBuilder ); } + private SqmInsertSelectStatement( + NodeBuilder builder, + SqmQuerySource querySource, + Set> parameters, + Map> cteStatements, + boolean withRecursiveCte, + SqmRoot target, + List> insertionTargetPaths, + SqmQueryPart selectQueryPart) { + super( builder, querySource, parameters, cteStatements, withRecursiveCte, target, insertionTargetPaths ); + this.selectQueryPart = selectQueryPart; + } + + @Override + public SqmInsertSelectStatement copy(SqmCopyContext context) { + final SqmInsertSelectStatement existing = context.getCopy( this ); + if ( existing != null ) { + return existing; + } + return context.registerCopy( + this, + new SqmInsertSelectStatement<>( + nodeBuilder(), + getQuerySource(), + copyParameters( context ), + copyCteStatements( context ), + isWithRecursive(), + getTarget().copy( context ), + copyInsertionTargetPaths( context ), + selectQueryPart.copy( context ) + ) + ); + } + public SqmQueryPart getSelectQueryPart() { return selectQueryPart; } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/insert/SqmInsertValuesStatement.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/insert/SqmInsertValuesStatement.java index 0758bb7cbc..86076c30e2 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/insert/SqmInsertValuesStatement.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/insert/SqmInsertValuesStatement.java @@ -6,24 +6,69 @@ */ package org.hibernate.query.sqm.tree.insert; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Set; + import org.hibernate.query.criteria.JpaPredicate; import org.hibernate.query.sqm.NodeBuilder; import org.hibernate.query.sqm.SemanticQueryWalker; import org.hibernate.query.sqm.SqmQuerySource; +import org.hibernate.query.sqm.tree.SqmCopyContext; +import org.hibernate.query.sqm.tree.cte.SqmCteStatement; +import org.hibernate.query.sqm.tree.domain.SqmPath; import org.hibernate.query.sqm.tree.expression.SqmExpression; +import org.hibernate.query.sqm.tree.expression.SqmParameter; import org.hibernate.query.sqm.tree.from.SqmRoot; -import java.util.ArrayList; -import java.util.List; - /** * @author Gavin King */ public class SqmInsertValuesStatement extends AbstractSqmInsertStatement { - private final List valuesList = new ArrayList<>(); + private final List valuesList; public SqmInsertValuesStatement(SqmRoot targetRoot, NodeBuilder nodeBuilder) { super( targetRoot, SqmQuerySource.HQL, nodeBuilder ); + this.valuesList = new ArrayList<>(); + } + + private SqmInsertValuesStatement( + NodeBuilder builder, + SqmQuerySource querySource, + Set> parameters, + Map> cteStatements, + boolean withRecursiveCte, + SqmRoot target, + List> insertionTargetPaths, + List valuesList) { + super( builder, querySource, parameters, cteStatements, withRecursiveCte, target, insertionTargetPaths ); + this.valuesList = valuesList; + } + + @Override + public SqmInsertValuesStatement copy(SqmCopyContext context) { + final SqmInsertValuesStatement existing = context.getCopy( this ); + if ( existing != null ) { + return existing; + } + final List valuesList = new ArrayList<>( this.valuesList.size() ); + for ( SqmValues sqmValues : this.valuesList ) { + valuesList.add( sqmValues.copy( context ) ); + } + return context.registerCopy( + this, + new SqmInsertValuesStatement<>( + nodeBuilder(), + getQuerySource(), + copyParameters( context ), + copyCteStatements( context ), + isWithRecursive(), + getTarget().copy( context ), + copyInsertionTargetPaths( context ), + valuesList + ) + ); } public List getValuesList() { diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/insert/SqmValues.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/insert/SqmValues.java index 3b7ed96111..a6e637c87b 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/insert/SqmValues.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/insert/SqmValues.java @@ -6,6 +6,7 @@ */ package org.hibernate.query.sqm.tree.insert; +import org.hibernate.query.sqm.tree.SqmCopyContext; import org.hibernate.query.sqm.tree.expression.SqmExpression; import java.io.Serializable; @@ -16,7 +17,22 @@ import java.util.List; * @author Gavin King */ public class SqmValues implements Serializable { - private final List> expressions = new ArrayList<>(); + private final List> expressions; + + public SqmValues() { + this.expressions = new ArrayList<>(); + } + + private SqmValues(SqmValues original, SqmCopyContext context) { + this.expressions = new ArrayList<>( original.expressions.size() ); + for ( SqmExpression expression : original.expressions ) { + this.expressions.add( expression.copy( context ) ); + } + } + + public SqmValues copy(SqmCopyContext context) { + return new SqmValues( this, context ); + } public List> getExpressions() { return expressions; diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/jpa/AbstractJpaTupleElement.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/jpa/AbstractJpaTupleElement.java index d05ae216c0..aaf0ff62e7 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/jpa/AbstractJpaTupleElement.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/jpa/AbstractJpaTupleElement.java @@ -10,6 +10,7 @@ import org.hibernate.query.criteria.JpaTupleElement; import org.hibernate.query.sqm.NodeBuilder; import org.hibernate.query.sqm.SqmExpressible; import org.hibernate.query.sqm.tree.AbstractSqmNode; +import org.hibernate.query.sqm.tree.SqmCopyContext; import org.hibernate.query.sqm.tree.SqmVisitableNode; /** @@ -30,6 +31,10 @@ public abstract class AbstractJpaTupleElement setExpressibleType(expressibleType); } + protected void copyTo(AbstractJpaTupleElement target, SqmCopyContext context) { + target.alias = alias; + } + @Override public String getAlias() { return alias; diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/predicate/SqmAndPredicate.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/predicate/SqmAndPredicate.java index ee23557081..f7f2b3fc9a 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/predicate/SqmAndPredicate.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/predicate/SqmAndPredicate.java @@ -8,10 +8,12 @@ package org.hibernate.query.sqm.tree.predicate; import java.util.Arrays; import java.util.List; -import jakarta.persistence.criteria.Expression; import org.hibernate.query.sqm.NodeBuilder; import org.hibernate.query.sqm.SemanticQueryWalker; +import org.hibernate.query.sqm.tree.SqmCopyContext; + +import jakarta.persistence.criteria.Expression; /** * @author Steve Ebersole @@ -29,6 +31,24 @@ public class SqmAndPredicate extends AbstractSqmPredicate implements SqmJunctive this.rightHandPredicate = rightHandPredicate; } + @Override + public SqmAndPredicate copy(SqmCopyContext context) { + final SqmAndPredicate existing = context.getCopy( this ); + if ( existing != null ) { + return existing; + } + final SqmAndPredicate predicate = context.registerCopy( + this, + new SqmAndPredicate( + leftHandPredicate.copy( context ), + rightHandPredicate.copy( context ), + nodeBuilder() + ) + ); + copyTo( predicate, context ); + return predicate; + } + @Override public SqmPredicate getLeftHandPredicate() { return leftHandPredicate; diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/predicate/SqmBetweenPredicate.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/predicate/SqmBetweenPredicate.java index 8d5d25a862..ce32c56f2a 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/predicate/SqmBetweenPredicate.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/predicate/SqmBetweenPredicate.java @@ -10,6 +10,7 @@ import org.hibernate.query.internal.QueryHelper; import org.hibernate.query.sqm.NodeBuilder; import org.hibernate.query.sqm.SemanticQueryWalker; import org.hibernate.query.sqm.SqmExpressible; +import org.hibernate.query.sqm.tree.SqmCopyContext; import org.hibernate.query.sqm.tree.expression.SqmExpression; /** @@ -42,6 +43,26 @@ public class SqmBetweenPredicate extends AbstractNegatableSqmPredicate { upperBound.applyInferableType( expressibleType ); } + @Override + public SqmBetweenPredicate copy(SqmCopyContext context) { + final SqmBetweenPredicate existing = context.getCopy( this ); + if ( existing != null ) { + return existing; + } + final SqmBetweenPredicate predicate = context.registerCopy( + this, + new SqmBetweenPredicate( + expression.copy( context ), + lowerBound.copy( context ), + upperBound.copy( context ), + isNegated(), + nodeBuilder() + ) + ); + copyTo( predicate, context ); + return predicate; + } + public SqmExpression getExpression() { return expression; } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/predicate/SqmBooleanExpressionPredicate.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/predicate/SqmBooleanExpressionPredicate.java index 25e2e8a426..2038322ab7 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/predicate/SqmBooleanExpressionPredicate.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/predicate/SqmBooleanExpressionPredicate.java @@ -8,13 +8,15 @@ package org.hibernate.query.sqm.tree.predicate; import java.util.Collections; import java.util.List; -import jakarta.persistence.criteria.Expression; import org.hibernate.query.sqm.NodeBuilder; import org.hibernate.query.sqm.SemanticQueryWalker; +import org.hibernate.query.sqm.tree.SqmCopyContext; import org.hibernate.query.sqm.tree.expression.SqmExpression; import org.hibernate.sql.ast.tree.predicate.NegatedPredicate; +import jakarta.persistence.criteria.Expression; + /** * Represents an expression whose type is boolean, and can therefore be used as a predicate. * @@ -42,6 +44,24 @@ public class SqmBooleanExpressionPredicate extends AbstractNegatableSqmPredicate this.booleanExpression = booleanExpression; } + @Override + public SqmBooleanExpressionPredicate copy(SqmCopyContext context) { + final SqmBooleanExpressionPredicate existing = context.getCopy( this ); + if ( existing != null ) { + return existing; + } + final SqmBooleanExpressionPredicate predicate = context.registerCopy( + this, + new SqmBooleanExpressionPredicate( + booleanExpression.copy( context ), + isNegated(), + nodeBuilder() + ) + ); + copyTo( predicate, context ); + return predicate; + } + public SqmExpression getBooleanExpression() { return booleanExpression; } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/predicate/SqmComparisonPredicate.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/predicate/SqmComparisonPredicate.java index c2953821f3..7cd0eecf70 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/predicate/SqmComparisonPredicate.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/predicate/SqmComparisonPredicate.java @@ -11,6 +11,7 @@ import org.hibernate.query.internal.QueryHelper; import org.hibernate.query.sqm.NodeBuilder; import org.hibernate.query.sqm.SqmExpressible; import org.hibernate.query.sqm.SemanticQueryWalker; +import org.hibernate.query.sqm.tree.SqmCopyContext; import org.hibernate.query.sqm.tree.expression.SqmExpression; /** @@ -26,7 +27,16 @@ public class SqmComparisonPredicate extends AbstractNegatableSqmPredicate { ComparisonOperator operator, SqmExpression rightHandExpression, NodeBuilder nodeBuilder) { - super( nodeBuilder ); + this( leftHandExpression, operator, rightHandExpression, false, nodeBuilder ); + } + + private SqmComparisonPredicate( + SqmExpression leftHandExpression, + ComparisonOperator operator, + SqmExpression rightHandExpression, + boolean negated, + NodeBuilder nodeBuilder) { + super( negated, nodeBuilder ); this.leftHandExpression = leftHandExpression; this.rightHandExpression = rightHandExpression; this.operator = operator; @@ -47,6 +57,26 @@ public class SqmComparisonPredicate extends AbstractNegatableSqmPredicate { this.operator = affirmativeForm.operator; } + @Override + public SqmComparisonPredicate copy(SqmCopyContext context) { + final SqmComparisonPredicate existing = context.getCopy( this ); + if ( existing != null ) { + return existing; + } + final SqmComparisonPredicate predicate = context.registerCopy( + this, + new SqmComparisonPredicate( + leftHandExpression.copy( context ), + operator, + rightHandExpression.copy( context ), + isNegated(), + nodeBuilder() + ) + ); + copyTo( predicate, context ); + return predicate; + } + public SqmExpression getLeftHandExpression() { return leftHandExpression; } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/predicate/SqmEmptinessPredicate.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/predicate/SqmEmptinessPredicate.java index d066e412d8..9c5554c8cd 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/predicate/SqmEmptinessPredicate.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/predicate/SqmEmptinessPredicate.java @@ -8,6 +8,7 @@ package org.hibernate.query.sqm.tree.predicate; import org.hibernate.query.sqm.NodeBuilder; import org.hibernate.query.sqm.SemanticQueryWalker; +import org.hibernate.query.sqm.tree.SqmCopyContext; import org.hibernate.query.sqm.tree.domain.SqmPluralValuedSimplePath; /** @@ -24,6 +25,24 @@ public class SqmEmptinessPredicate extends AbstractNegatableSqmPredicate { this.pluralPath = pluralPath; } + @Override + public SqmEmptinessPredicate copy(SqmCopyContext context) { + final SqmEmptinessPredicate existing = context.getCopy( this ); + if ( existing != null ) { + return existing; + } + final SqmEmptinessPredicate predicate = context.registerCopy( + this, + new SqmEmptinessPredicate( + pluralPath.copy( context ), + isNegated(), + nodeBuilder() + ) + ); + copyTo( predicate, context ); + return predicate; + } + public SqmPluralValuedSimplePath getPluralPath() { return pluralPath; } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/predicate/SqmExistsPredicate.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/predicate/SqmExistsPredicate.java index 71939b01fd..d8710906a5 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/predicate/SqmExistsPredicate.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/predicate/SqmExistsPredicate.java @@ -8,6 +8,7 @@ package org.hibernate.query.sqm.tree.predicate; import org.hibernate.query.sqm.NodeBuilder; import org.hibernate.query.sqm.SemanticQueryWalker; +import org.hibernate.query.sqm.tree.SqmCopyContext; import org.hibernate.query.sqm.tree.expression.SqmExpression; /** @@ -32,6 +33,24 @@ public class SqmExistsPredicate extends AbstractNegatableSqmPredicate { expression.applyInferableType( expression.getNodeType() ); } + @Override + public SqmExistsPredicate copy(SqmCopyContext context) { + final SqmExistsPredicate existing = context.getCopy( this ); + if ( existing != null ) { + return existing; + } + final SqmExistsPredicate predicate = context.registerCopy( + this, + new SqmExistsPredicate( + expression.copy( context ), + isNegated(), + nodeBuilder() + ) + ); + copyTo( predicate, context ); + return predicate; + } + public SqmExpression getExpression() { return expression; } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/predicate/SqmGroupedPredicate.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/predicate/SqmGroupedPredicate.java index c94ab41add..af4706e88c 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/predicate/SqmGroupedPredicate.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/predicate/SqmGroupedPredicate.java @@ -8,10 +8,12 @@ package org.hibernate.query.sqm.tree.predicate; import java.util.Collections; import java.util.List; -import jakarta.persistence.criteria.Expression; import org.hibernate.query.sqm.NodeBuilder; import org.hibernate.query.sqm.SemanticQueryWalker; +import org.hibernate.query.sqm.tree.SqmCopyContext; + +import jakarta.persistence.criteria.Expression; /** * @author Steve Ebersole @@ -24,6 +26,23 @@ public class SqmGroupedPredicate extends AbstractSqmPredicate { this.subPredicate = subPredicate; } + @Override + public SqmGroupedPredicate copy(SqmCopyContext context) { + final SqmGroupedPredicate existing = context.getCopy( this ); + if ( existing != null ) { + return existing; + } + final SqmGroupedPredicate predicate = context.registerCopy( + this, + new SqmGroupedPredicate( + subPredicate.copy( context ), + nodeBuilder() + ) + ); + copyTo( predicate, context ); + return predicate; + } + public SqmPredicate getSubPredicate() { return subPredicate; } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/predicate/SqmInListPredicate.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/predicate/SqmInListPredicate.java index c4a763b6ac..84d52f5a9a 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/predicate/SqmInListPredicate.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/predicate/SqmInListPredicate.java @@ -10,15 +10,16 @@ import java.util.ArrayList; import java.util.Collection; import java.util.List; -import jakarta.persistence.criteria.Expression; - import org.hibernate.internal.util.collections.ArrayHelper; import org.hibernate.query.criteria.JpaExpression; import org.hibernate.query.internal.QueryHelper; import org.hibernate.query.sqm.NodeBuilder; import org.hibernate.query.sqm.SemanticQueryWalker; +import org.hibernate.query.sqm.tree.SqmCopyContext; import org.hibernate.query.sqm.tree.expression.SqmExpression; +import jakarta.persistence.criteria.Expression; + /** * @author Steve Ebersole */ @@ -57,7 +58,29 @@ public class SqmInListPredicate extends AbstractNegatableSqmPredicate impleme for ( SqmExpression listExpression : listExpressions ) { implyListElementType( listExpression ); } + } + @Override + public SqmInListPredicate copy(SqmCopyContext context) { + final SqmInListPredicate existing = context.getCopy( this ); + if ( existing != null ) { + return existing; + } + List> listExpressions = new ArrayList<>( this.listExpressions.size() ); + for ( SqmExpression listExpression : this.listExpressions ) { + listExpressions.add( listExpression.copy( context ) ); + } + final SqmInListPredicate predicate = context.registerCopy( + this, + new SqmInListPredicate<>( + testExpression.copy( context ), + listExpressions, + isNegated(), + nodeBuilder() + ) + ); + copyTo( predicate, context ); + return predicate; } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/predicate/SqmInSubQueryPredicate.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/predicate/SqmInSubQueryPredicate.java index 28c3c4b715..178162a7f5 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/predicate/SqmInSubQueryPredicate.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/predicate/SqmInSubQueryPredicate.java @@ -6,16 +6,17 @@ */ package org.hibernate.query.sqm.tree.predicate; -import jakarta.persistence.criteria.Expression; - import org.hibernate.query.criteria.JpaExpression; import org.hibernate.query.internal.QueryHelper; import org.hibernate.query.sqm.NodeBuilder; import org.hibernate.query.sqm.SqmExpressible; import org.hibernate.query.sqm.SemanticQueryWalker; +import org.hibernate.query.sqm.tree.SqmCopyContext; import org.hibernate.query.sqm.tree.expression.SqmExpression; import org.hibernate.query.sqm.tree.select.SqmSubQuery; +import jakarta.persistence.criteria.Expression; + /** * @author Steve Ebersole */ @@ -48,6 +49,25 @@ public class SqmInSubQueryPredicate extends AbstractNegatableSqmPredicate imp subQueryExpression.applyInferableType( expressibleType ); } + @Override + public SqmInSubQueryPredicate copy(SqmCopyContext context) { + final SqmInSubQueryPredicate existing = context.getCopy( this ); + if ( existing != null ) { + return existing; + } + final SqmInSubQueryPredicate predicate = context.registerCopy( + this, + new SqmInSubQueryPredicate( + testExpression.copy( context ), + subQueryExpression.copy( context ), + isNegated(), + nodeBuilder() + ) + ); + copyTo( predicate, context ); + return predicate; + } + @Override public SqmExpression getTestExpression() { return testExpression; diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/predicate/SqmLikePredicate.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/predicate/SqmLikePredicate.java index cb584d048e..296f58e83a 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/predicate/SqmLikePredicate.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/predicate/SqmLikePredicate.java @@ -8,6 +8,7 @@ package org.hibernate.query.sqm.tree.predicate; import org.hibernate.query.sqm.NodeBuilder; import org.hibernate.query.sqm.SemanticQueryWalker; +import org.hibernate.query.sqm.tree.SqmCopyContext; import org.hibernate.query.sqm.tree.expression.SqmExpression; /** @@ -66,6 +67,27 @@ public class SqmLikePredicate extends AbstractNegatableSqmPredicate { this( matchExpression, pattern, null, negated, isCaseSensitive, nodeBuilder ); } + @Override + public SqmLikePredicate copy(SqmCopyContext context) { + final SqmLikePredicate existing = context.getCopy( this ); + if ( existing != null ) { + return existing; + } + final SqmLikePredicate predicate = context.registerCopy( + this, + new SqmLikePredicate( + matchExpression.copy( context ), + pattern.copy( context ), + escapeCharacter == null ? null : escapeCharacter.copy( context ), + isNegated(), + isCaseSensitive, + nodeBuilder() + ) + ); + copyTo( predicate, context ); + return predicate; + } + public SqmExpression getMatchExpression() { return matchExpression; } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/predicate/SqmMemberOfPredicate.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/predicate/SqmMemberOfPredicate.java index 23f1618e7c..d65a0c78d1 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/predicate/SqmMemberOfPredicate.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/predicate/SqmMemberOfPredicate.java @@ -8,6 +8,7 @@ package org.hibernate.query.sqm.tree.predicate; import org.hibernate.query.sqm.NodeBuilder; import org.hibernate.query.sqm.SemanticQueryWalker; +import org.hibernate.query.sqm.tree.SqmCopyContext; import org.hibernate.query.sqm.tree.domain.SqmPath; import org.hibernate.query.sqm.tree.expression.SqmExpression; @@ -33,6 +34,25 @@ public class SqmMemberOfPredicate extends AbstractNegatableSqmPredicate { this.leftHandExpression = leftHandExpression; } + @Override + public SqmMemberOfPredicate copy(SqmCopyContext context) { + final SqmMemberOfPredicate existing = context.getCopy( this ); + if ( existing != null ) { + return existing; + } + final SqmMemberOfPredicate predicate = context.registerCopy( + this, + new SqmMemberOfPredicate( + leftHandExpression.copy( context ), + pluralPath.copy( context ), + isNegated(), + nodeBuilder() + ) + ); + copyTo( predicate, context ); + return predicate; + } + public SqmExpression getLeftHandExpression() { return leftHandExpression; } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/predicate/SqmNegatedPredicate.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/predicate/SqmNegatedPredicate.java index 077bffbfc0..e2bc15e51c 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/predicate/SqmNegatedPredicate.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/predicate/SqmNegatedPredicate.java @@ -8,10 +8,12 @@ package org.hibernate.query.sqm.tree.predicate; import java.util.Collections; import java.util.List; -import jakarta.persistence.criteria.Expression; import org.hibernate.query.sqm.NodeBuilder; import org.hibernate.query.sqm.SemanticQueryWalker; +import org.hibernate.query.sqm.tree.SqmCopyContext; + +import jakarta.persistence.criteria.Expression; /** * @author Steve Ebersole @@ -24,6 +26,32 @@ public class SqmNegatedPredicate extends AbstractNegatableSqmPredicate { this.wrappedPredicate = wrappedPredicate; } + public SqmNegatedPredicate( + SqmPredicate wrappedPredicate, + boolean negated, + NodeBuilder nodeBuilder) { + super( negated, nodeBuilder ); + this.wrappedPredicate = wrappedPredicate; + } + + @Override + public SqmNegatedPredicate copy(SqmCopyContext context) { + final SqmNegatedPredicate existing = context.getCopy( this ); + if ( existing != null ) { + return existing; + } + final SqmNegatedPredicate predicate = context.registerCopy( + this, + new SqmNegatedPredicate( + wrappedPredicate.copy( context ), + isNegated(), + nodeBuilder() + ) + ); + copyTo( predicate, context ); + return predicate; + } + public SqmPredicate getWrappedPredicate() { return wrappedPredicate; } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/predicate/SqmNullnessPredicate.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/predicate/SqmNullnessPredicate.java index 9f9d939ff7..d8607a9546 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/predicate/SqmNullnessPredicate.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/predicate/SqmNullnessPredicate.java @@ -8,6 +8,7 @@ package org.hibernate.query.sqm.tree.predicate; import org.hibernate.query.sqm.NodeBuilder; import org.hibernate.query.sqm.SemanticQueryWalker; +import org.hibernate.query.sqm.tree.SqmCopyContext; import org.hibernate.query.sqm.tree.expression.SqmExpression; /** @@ -25,6 +26,24 @@ public class SqmNullnessPredicate extends AbstractNegatableSqmPredicate { this.expression = expression; } + @Override + public SqmNullnessPredicate copy(SqmCopyContext context) { + final SqmNullnessPredicate existing = context.getCopy( this ); + if ( existing != null ) { + return existing; + } + final SqmNullnessPredicate predicate = context.registerCopy( + this, + new SqmNullnessPredicate( + expression.copy( context ), + isNegated(), + nodeBuilder() + ) + ); + copyTo( predicate, context ); + return predicate; + } + public SqmExpression getExpression() { return expression; } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/predicate/SqmOrPredicate.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/predicate/SqmOrPredicate.java index fb2f012d6a..0eb72e4673 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/predicate/SqmOrPredicate.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/predicate/SqmOrPredicate.java @@ -8,12 +8,14 @@ package org.hibernate.query.sqm.tree.predicate; import java.util.Arrays; import java.util.List; -import jakarta.persistence.criteria.Expression; import org.hibernate.query.sqm.NodeBuilder; import org.hibernate.query.sqm.SemanticQueryWalker; +import org.hibernate.query.sqm.tree.SqmCopyContext; import org.hibernate.query.sqm.tree.expression.AbstractSqmExpression; +import jakarta.persistence.criteria.Expression; + /** * @author Steve Ebersole */ @@ -30,6 +32,24 @@ public class SqmOrPredicate extends AbstractSqmExpression implements Sq this.rightHandPredicate = rightHandPredicate; } + @Override + public SqmOrPredicate copy(SqmCopyContext context) { + final SqmOrPredicate existing = context.getCopy( this ); + if ( existing != null ) { + return existing; + } + final SqmOrPredicate predicate = context.registerCopy( + this, + new SqmOrPredicate( + leftHandPredicate.copy( context ), + rightHandPredicate.copy( context ), + nodeBuilder() + ) + ); + copyTo( predicate, context ); + return predicate; + } + @Override public SqmPredicate getLeftHandPredicate() { return leftHandPredicate; diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/predicate/SqmPredicate.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/predicate/SqmPredicate.java index f92e0ec553..b56dfba10e 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/predicate/SqmPredicate.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/predicate/SqmPredicate.java @@ -7,6 +7,7 @@ package org.hibernate.query.sqm.tree.predicate; import org.hibernate.query.criteria.JpaPredicate; +import org.hibernate.query.sqm.tree.SqmCopyContext; import org.hibernate.query.sqm.tree.SqmVisitableNode; import org.hibernate.query.sqm.tree.expression.SqmExpression; import org.hibernate.type.descriptor.java.BooleanJavaType; @@ -25,4 +26,6 @@ public interface SqmPredicate @Override SqmPredicate not(); + @Override + SqmPredicate copy(SqmCopyContext context); } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/predicate/SqmWhereClause.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/predicate/SqmWhereClause.java index fa271cd365..7e329ae269 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/predicate/SqmWhereClause.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/predicate/SqmWhereClause.java @@ -9,6 +9,7 @@ package org.hibernate.query.sqm.tree.predicate; import java.util.Collection; import org.hibernate.query.sqm.NodeBuilder; +import org.hibernate.query.sqm.tree.SqmCopyContext; /** * @author Steve Ebersole @@ -27,6 +28,13 @@ public class SqmWhereClause { this.predicate = predicate; } + public SqmWhereClause copy(SqmCopyContext context) { + return new SqmWhereClause( + predicate == null ? null : predicate.copy( context ), + nodeBuilder + ); + } + public SqmPredicate getPredicate() { return predicate; } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/AbstractSqmSelectQuery.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/AbstractSqmSelectQuery.java index 92f505ead1..37ef9cf881 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/AbstractSqmSelectQuery.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/AbstractSqmSelectQuery.java @@ -12,20 +12,22 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; -import jakarta.persistence.criteria.Expression; -import jakarta.persistence.criteria.Predicate; -import jakarta.persistence.criteria.Root; -import jakarta.persistence.metamodel.EntityType; import org.hibernate.metamodel.model.domain.EntityDomainType; import org.hibernate.query.criteria.JpaSelection; import org.hibernate.query.sqm.NodeBuilder; import org.hibernate.query.sqm.tree.AbstractSqmNode; +import org.hibernate.query.sqm.tree.SqmCopyContext; import org.hibernate.query.sqm.tree.cte.SqmCteContainer; import org.hibernate.query.sqm.tree.cte.SqmCteStatement; import org.hibernate.query.sqm.tree.from.SqmRoot; import org.hibernate.query.sqm.tree.predicate.SqmPredicate; +import jakarta.persistence.criteria.Expression; +import jakarta.persistence.criteria.Predicate; +import jakarta.persistence.criteria.Root; +import jakarta.persistence.metamodel.EntityType; + /** * @author Steve Ebersole */ @@ -33,23 +35,41 @@ import org.hibernate.query.sqm.tree.predicate.SqmPredicate; public abstract class AbstractSqmSelectQuery extends AbstractSqmNode implements SqmSelectQuery, SqmCteContainer { - private final Map> cteStatements = new LinkedHashMap<>(); + private final Map> cteStatements; private boolean withRecursive; private SqmQueryPart sqmQueryPart; private Class resultType; public AbstractSqmSelectQuery(Class resultType, NodeBuilder builder) { - super( builder ); - this.sqmQueryPart = new SqmQuerySpec<>( builder ); - this.resultType = resultType; + this( new SqmQuerySpec<>( builder ), resultType, builder ); } public AbstractSqmSelectQuery(SqmQueryPart queryPart, Class resultType, NodeBuilder builder) { super( builder ); + this.cteStatements = new LinkedHashMap<>(); this.resultType = resultType; setQueryPart( queryPart ); } + protected AbstractSqmSelectQuery( + NodeBuilder builder, + Map> cteStatements, + boolean withRecursive, + Class resultType) { + super( builder ); + this.cteStatements = cteStatements; + this.withRecursive = withRecursive; + this.resultType = resultType; + } + + protected Map> copyCteStatements(SqmCopyContext context) { + final Map> cteStatements = new LinkedHashMap<>( this.cteStatements.size() ); + for ( Map.Entry> entry : this.cteStatements.entrySet() ) { + cteStatements.put( entry.getKey(), entry.getValue().copy( context ) ); + } + return cteStatements; + } + @Override public boolean isWithRecursive() { return withRecursive; diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/SqmDynamicInstantiation.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/SqmDynamicInstantiation.java index 265e1f99fd..5585ea9f36 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/SqmDynamicInstantiation.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/SqmDynamicInstantiation.java @@ -15,6 +15,8 @@ import org.hibernate.query.sqm.DynamicInstantiationNature; import org.hibernate.query.criteria.JpaCompoundSelection; import org.hibernate.query.sqm.NodeBuilder; import org.hibernate.query.sqm.SemanticQueryWalker; +import org.hibernate.query.sqm.SqmExpressible; +import org.hibernate.query.sqm.tree.SqmCopyContext; import org.hibernate.query.sqm.tree.expression.SqmExpression; import org.hibernate.query.sqm.tree.jpa.AbstractJpaSelection; import org.hibernate.type.descriptor.java.JavaType; @@ -98,6 +100,45 @@ public class SqmDynamicInstantiation this.instantiationTarget = instantiationTarget; } + private SqmDynamicInstantiation( + SqmExpressible sqmExpressible, + NodeBuilder criteriaBuilder, + SqmDynamicInstantiationTarget instantiationTarget, + List> arguments) { + super( sqmExpressible, criteriaBuilder ); + this.instantiationTarget = instantiationTarget; + this.arguments = arguments; + } + + @Override + public SqmDynamicInstantiation copy(SqmCopyContext context) { + final SqmDynamicInstantiation existing = context.getCopy( this ); + if ( existing != null ) { + return existing; + } + List> arguments; + if ( this.arguments == null ) { + arguments = null; + } + else { + arguments = new ArrayList<>( this.arguments.size() ); + for ( SqmDynamicInstantiationArgument argument : this.arguments ) { + arguments.add( argument.copy( context ) ); + } + } + final SqmDynamicInstantiation instantiation = context.registerCopy( + this, + new SqmDynamicInstantiation<>( + getExpressible(), + nodeBuilder(), + instantiationTarget, + arguments + ) + ); + copyTo( instantiation, context ); + return instantiation; + } + public SqmDynamicInstantiationTarget getInstantiationTarget() { return instantiationTarget; } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/SqmDynamicInstantiationArgument.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/SqmDynamicInstantiationArgument.java index a52a9870c7..7db9d91b39 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/SqmDynamicInstantiationArgument.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/SqmDynamicInstantiationArgument.java @@ -7,6 +7,7 @@ package org.hibernate.query.sqm.tree.select; import org.hibernate.query.sqm.NodeBuilder; +import org.hibernate.query.sqm.tree.SqmCopyContext; /** * Represents an individual argument to a dynamic instantiation. @@ -27,6 +28,15 @@ public class SqmDynamicInstantiationArgument implements SqmAliasedNode { this.nodeBuilder = nodeBuilder; } + @Override + public SqmDynamicInstantiationArgument copy(SqmCopyContext context) { + return new SqmDynamicInstantiationArgument<>( + selectableNode.copy( context ), + alias, + nodeBuilder + ); + } + @Override public SqmSelectableNode getSelectableNode() { return selectableNode; diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/SqmJpaCompoundSelection.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/SqmJpaCompoundSelection.java index cc77105330..b5568f9924 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/SqmJpaCompoundSelection.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/SqmJpaCompoundSelection.java @@ -6,6 +6,7 @@ */ package org.hibernate.query.sqm.tree.select; +import java.util.ArrayList; import java.util.List; import java.util.function.Consumer; @@ -14,6 +15,7 @@ import org.hibernate.query.criteria.JpaSelection; import org.hibernate.query.sqm.NodeBuilder; import org.hibernate.query.sqm.SemanticQueryWalker; import org.hibernate.query.sqm.SqmExpressible; +import org.hibernate.query.sqm.tree.SqmCopyContext; import org.hibernate.query.sqm.tree.expression.AbstractSqmExpression; import org.hibernate.type.descriptor.java.JavaType; @@ -69,6 +71,26 @@ public class SqmJpaCompoundSelection setExpressibleType( this ); } + @Override + public SqmJpaCompoundSelection copy(SqmCopyContext context) { + final SqmJpaCompoundSelection existing = context.getCopy( this ); + if ( existing != null ) { + return existing; + } + final List> selectableNodes = new ArrayList<>( this.selectableNodes.size() ); + for ( SqmSelectableNode selectableNode : this.selectableNodes ) { + selectableNodes.add( selectableNode.copy( context ) ); + } + return context.registerCopy( + this, + new SqmJpaCompoundSelection<>( + selectableNodes, + javaType, + nodeBuilder() + ) + ); + } + @Override public JavaType getJavaTypeDescriptor() { return javaType; diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/SqmOrderByClause.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/SqmOrderByClause.java index 8bc36e435d..adb5301a63 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/SqmOrderByClause.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/SqmOrderByClause.java @@ -11,6 +11,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; +import org.hibernate.query.sqm.tree.SqmCopyContext; import org.hibernate.query.sqm.tree.expression.SqmAliasedNodeRef; import org.hibernate.query.sqm.tree.expression.SqmExpression; @@ -28,6 +29,25 @@ public class SqmOrderByClause implements Serializable { this.sortSpecifications = new ArrayList<>( estimateSize ); } + private SqmOrderByClause(boolean hasPositionalSortItem, List sortSpecifications) { + this.hasPositionalSortItem = hasPositionalSortItem; + this.sortSpecifications = sortSpecifications; + } + + public SqmOrderByClause copy(SqmCopyContext context) { + final List sortSpecifications; + if ( this.sortSpecifications == null ) { + sortSpecifications = null; + } + else { + sortSpecifications = new ArrayList<>( this.sortSpecifications.size() ); + for ( SqmSortSpecification sortSpecification : this.sortSpecifications ) { + sortSpecifications.add( sortSpecification.copy( context ) ); + } + } + return new SqmOrderByClause( hasPositionalSortItem, sortSpecifications ); + } + public boolean hasPositionalSortItem() { return hasPositionalSortItem; } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/SqmQueryGroup.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/SqmQueryGroup.java index 00bdbe19d8..dc319df2c9 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/SqmQueryGroup.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/SqmQueryGroup.java @@ -11,15 +11,16 @@ import java.util.Collections; import java.util.Iterator; import java.util.List; +import org.hibernate.internal.util.collections.CollectionHelper; import org.hibernate.query.sqm.FetchClauseType; import org.hibernate.query.SemanticException; import org.hibernate.query.sqm.SetOperator; -import org.hibernate.internal.util.collections.CollectionHelper; import org.hibernate.query.criteria.JpaExpression; import org.hibernate.query.criteria.JpaOrder; import org.hibernate.query.criteria.JpaQueryGroup; import org.hibernate.query.sqm.NodeBuilder; import org.hibernate.query.sqm.SemanticQueryWalker; +import org.hibernate.query.sqm.tree.SqmCopyContext; import org.hibernate.query.sqm.tree.SqmTypedNode; import org.hibernate.query.sqm.tree.from.SqmAttributeJoin; import org.hibernate.query.sqm.tree.from.SqmFrom; @@ -49,6 +50,28 @@ public class SqmQueryGroup extends SqmQueryPart implements JpaQueryGroup copy(SqmCopyContext context) { + final SqmQueryGroup existing = context.getCopy( this ); + if ( existing != null ) { + return existing; + } + final List> queryParts = new ArrayList<>( this.queryParts.size() ); + for ( SqmQueryPart queryPart : this.queryParts ) { + queryParts.add( queryPart.copy( context ) ); + } + final SqmQueryGroup queryGroup = context.registerCopy( + this, + new SqmQueryGroup<>( + nodeBuilder(), + setOperator, + queryParts + ) + ); + copyTo( queryGroup, context ); + return queryGroup; + } + public List> queryParts() { return queryParts; } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/SqmQueryPart.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/SqmQueryPart.java index 2e23bdaaea..0679071828 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/SqmQueryPart.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/SqmQueryPart.java @@ -14,6 +14,7 @@ import org.hibernate.query.criteria.JpaExpression; import org.hibernate.query.criteria.JpaOrder; import org.hibernate.query.criteria.JpaQueryPart; import org.hibernate.query.sqm.NodeBuilder; +import org.hibernate.query.sqm.tree.SqmCopyContext; import org.hibernate.query.sqm.tree.SqmVisitableNode; import org.hibernate.query.sqm.tree.expression.SqmExpression; @@ -35,6 +36,36 @@ public abstract class SqmQueryPart implements SqmVisitableNode, JpaQueryPart< this.nodeBuilder = nodeBuilder; } + public SqmQueryPart(SqmQueryPart original, SqmCopyContext context) { + this.nodeBuilder = original.nodeBuilder; + if ( original.orderByClause != null ) { + this.orderByClause = original.orderByClause.copy( context ); + } + if ( original.offsetExpression != null ) { + this.offsetExpression = original.offsetExpression.copy( context ); + } + if ( original.fetchExpression != null ) { + this.fetchExpression = original.fetchExpression.copy( context ); + } + this.fetchClauseType = original.fetchClauseType; + } + + protected void copyTo(SqmQueryPart target, SqmCopyContext context) { + if ( orderByClause != null ) { + target.orderByClause = orderByClause.copy( context ); + } + if ( offsetExpression != null ) { + target.offsetExpression = offsetExpression.copy( context ); + } + if ( fetchExpression != null ) { + target.fetchExpression = fetchExpression.copy( context ); + } + target.fetchClauseType = fetchClauseType; + } + + @Override + public abstract SqmQueryPart copy(SqmCopyContext context); + public abstract SqmQuerySpec getFirstQuerySpec(); public abstract SqmQuerySpec getLastQuerySpec(); diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/SqmQuerySpec.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/SqmQuerySpec.java index 7a17933c24..f4a9535fdb 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/SqmQuerySpec.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/SqmQuerySpec.java @@ -11,8 +11,6 @@ import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; -import jakarta.persistence.criteria.Expression; -import jakarta.persistence.criteria.Predicate; import org.hibernate.metamodel.mapping.CollectionPart; import org.hibernate.metamodel.model.domain.EmbeddableDomainType; @@ -26,6 +24,7 @@ import org.hibernate.query.criteria.JpaRoot; import org.hibernate.query.criteria.JpaSelection; import org.hibernate.query.sqm.NodeBuilder; import org.hibernate.query.sqm.SemanticQueryWalker; +import org.hibernate.query.sqm.tree.SqmCopyContext; import org.hibernate.query.sqm.tree.SqmNode; import org.hibernate.query.sqm.tree.domain.SqmEntityValuedSimplePath; import org.hibernate.query.sqm.tree.expression.SqmAliasedNodeRef; @@ -42,6 +41,9 @@ import org.hibernate.query.sqm.tree.predicate.SqmPredicate; import org.hibernate.query.sqm.tree.predicate.SqmWhereClause; import org.hibernate.query.sqm.tree.predicate.SqmWhereClauseContainer; +import jakarta.persistence.criteria.Expression; +import jakarta.persistence.criteria.Predicate; + /** * Defines the commonality between a root query and a subquery. * @@ -61,6 +63,59 @@ public class SqmQuerySpec extends SqmQueryPart super( nodeBuilder ); } + public SqmQuerySpec(SqmQuerySpec original, SqmCopyContext context) { + super( original, context ); + if ( original.fromClause != null ) { + this.fromClause = original.fromClause.copy( context ); + } + if ( original.selectClause != null ) { + this.selectClause = original.selectClause.copy( context ); + } + if ( original.whereClause != null ) { + this.whereClause = original.whereClause.copy( context ); + } + this.hasPositionalGroupItem = original.hasPositionalGroupItem; + if ( !original.groupByClauseExpressions.isEmpty() ) { + this.groupByClauseExpressions = new ArrayList<>( original.groupByClauseExpressions.size() ); + for ( SqmExpression groupByClauseExpression : original.groupByClauseExpressions ) { + this.groupByClauseExpressions.add( groupByClauseExpression.copy( context ) ); + } + } + if ( original.havingClausePredicate != null ) { + this.havingClausePredicate = original.havingClausePredicate.copy( context ); + } + } + + @Override + public SqmQuerySpec copy(SqmCopyContext context) { + final SqmQuerySpec existing = context.getCopy( this ); + if ( existing != null ) { + return existing; + } + final SqmQuerySpec querySpec = context.registerCopy( this, new SqmQuerySpec<>( nodeBuilder() ) ); + if ( fromClause != null ) { + querySpec.fromClause = fromClause.copy( context ); + } + if ( selectClause != null ) { + querySpec.selectClause = selectClause.copy( context ); + } + if ( whereClause != null ) { + querySpec.whereClause = whereClause.copy( context ); + } + querySpec.hasPositionalGroupItem = hasPositionalGroupItem; + if ( !groupByClauseExpressions.isEmpty() ) { + querySpec.groupByClauseExpressions = new ArrayList<>( groupByClauseExpressions.size() ); + for ( SqmExpression groupByClauseExpression : groupByClauseExpressions ) { + querySpec.groupByClauseExpressions.add( groupByClauseExpression.copy( context ) ); + } + } + if ( havingClausePredicate != null ) { + querySpec.havingClausePredicate = havingClausePredicate.copy( context ); + } + copyTo( querySpec, context ); + return querySpec; + } + @Override public X accept(SemanticQueryWalker walker) { return walker.visitQuerySpec( this ); diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/SqmSelectClause.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/SqmSelectClause.java index 325adcbc5f..34f56356a5 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/SqmSelectClause.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/SqmSelectClause.java @@ -14,6 +14,7 @@ import org.hibernate.internal.util.collections.CollectionHelper; import org.hibernate.query.criteria.JpaSelection; import org.hibernate.query.sqm.NodeBuilder; import org.hibernate.query.sqm.tree.AbstractSqmNode; +import org.hibernate.query.sqm.tree.SqmCopyContext; import org.hibernate.query.sqm.tree.expression.SqmExpression; import org.hibernate.type.descriptor.java.JavaType; @@ -42,6 +43,18 @@ public class SqmSelectClause extends AbstractSqmNode implements SqmAliasedExpres this.selections = CollectionHelper.arrayList( expectedNumberOfSelections ); } + @Override + public SqmSelectClause copy(SqmCopyContext context) { + final SqmSelectClause selectClause = new SqmSelectClause( distinct, nodeBuilder() ); + if ( selections != null ) { + selectClause.selections = new ArrayList<>( selections.size() ); + for ( SqmSelection selection : selections ) { + selectClause.selections.add( selection.copy( context ) ); + } + } + return selectClause; + } + public boolean isDistinct() { return distinct; } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/SqmSelectStatement.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/SqmSelectStatement.java index e4cb6a67aa..3753bf2905 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/SqmSelectStatement.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/SqmSelectStatement.java @@ -9,6 +9,7 @@ package org.hibernate.query.sqm.tree.select; import java.util.Collections; import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Set; import java.util.stream.Collectors; @@ -27,7 +28,9 @@ import org.hibernate.query.sqm.NodeBuilder; import org.hibernate.query.sqm.SemanticQueryWalker; import org.hibernate.query.sqm.SqmQuerySource; import org.hibernate.query.sqm.internal.SqmUtil; +import org.hibernate.query.sqm.tree.SqmCopyContext; import org.hibernate.query.sqm.tree.SqmStatement; +import org.hibernate.query.sqm.tree.cte.SqmCteStatement; import org.hibernate.query.sqm.tree.expression.ValueBindJpaCriteriaParameter; import org.hibernate.query.sqm.tree.expression.SqmParameter; import org.hibernate.query.sqm.tree.from.SqmFromClause; @@ -77,6 +80,49 @@ public class SqmSelectStatement extends AbstractSqmSelectQuery implements getQuerySpec().setFromClause( new SqmFromClause() ); } + private SqmSelectStatement( + NodeBuilder builder, + Map> cteStatements, + boolean withRecursive, + Class resultType, + SqmQuerySource querySource, + Set> parameters) { + super( builder, cteStatements, withRecursive, resultType ); + this.querySource = querySource; + this.parameters = parameters; + } + + @Override + public SqmSelectStatement copy(SqmCopyContext context) { + final SqmSelectStatement existing = context.getCopy( this ); + if ( existing != null ) { + return existing; + } + final Set> parameters; + if ( this.parameters == null ) { + parameters = null; + } + else { + parameters = new HashSet<>( this.parameters.size() ); + for ( SqmParameter parameter : this.parameters ) { + parameters.add( parameter.copy( context ) ); + } + } + final SqmSelectStatement statement = context.registerCopy( + this, + new SqmSelectStatement<>( + nodeBuilder(), + copyCteStatements( context ), + isWithRecursive(), + getResultType(), + getQuerySource(), + parameters + ) + ); + statement.setQueryPart( getQueryPart().copy( context ) ); + return statement; + } + @Override public SqmQuerySource getQuerySource() { return querySource; diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/SqmSelectableNode.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/SqmSelectableNode.java index 7b601cf58b..26c8bc952c 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/SqmSelectableNode.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/SqmSelectableNode.java @@ -10,6 +10,7 @@ import java.util.function.Consumer; import jakarta.persistence.criteria.Selection; import org.hibernate.query.criteria.JpaSelection; +import org.hibernate.query.sqm.tree.SqmCopyContext; import org.hibernate.query.sqm.tree.SqmTypedNode; import org.hibernate.query.sqm.tree.SqmVisitableNode; @@ -29,4 +30,7 @@ public interface SqmSelectableNode extends JpaSelection, SqmTypedNode, * @see Selection#getCompoundSelectionItems() */ void visitSubSelectableNodes(Consumer> jpaSelectionConsumer); + + @Override + SqmSelectableNode copy(SqmCopyContext context); } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/SqmSelection.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/SqmSelection.java index e03652f896..691633c59d 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/SqmSelection.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/SqmSelection.java @@ -9,6 +9,7 @@ package org.hibernate.query.sqm.tree.select; import org.hibernate.query.sqm.NodeBuilder; import org.hibernate.query.sqm.SemanticQueryWalker; import org.hibernate.query.sqm.tree.AbstractSqmNode; +import org.hibernate.query.sqm.tree.SqmCopyContext; import org.hibernate.query.sqm.tree.SqmVisitableNode; /** @@ -39,6 +40,11 @@ public class SqmSelection extends AbstractSqmNode implements SqmAliasedNode copy(SqmCopyContext context) { + return new SqmSelection<>( selectableNode.copy( context ), nodeBuilder() ); + } + @Override public SqmSelectableNode getSelectableNode() { return selectableNode; diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/SqmSortSpecification.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/SqmSortSpecification.java index 5cf5322b0f..ee32d1644d 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/SqmSortSpecification.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/SqmSortSpecification.java @@ -10,6 +10,7 @@ import org.hibernate.query.sqm.NullPrecedence; import org.hibernate.query.sqm.SortOrder; import org.hibernate.query.criteria.JpaExpression; import org.hibernate.query.criteria.JpaOrder; +import org.hibernate.query.sqm.tree.SqmCopyContext; import org.hibernate.query.sqm.tree.expression.SqmExpression; /** @@ -38,6 +39,10 @@ public class SqmSortSpecification implements JpaOrder { this( sortExpression, sortOrder, null ); } + public SqmSortSpecification copy(SqmCopyContext context) { + return new SqmSortSpecification( sortExpression.copy( context ), sortOrder, nullPrecedence ); + } + public SqmExpression getSortExpression() { return sortExpression; } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/SqmSubQuery.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/SqmSubQuery.java index fd0492fd27..7c508884f3 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/SqmSubQuery.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/SqmSubQuery.java @@ -11,25 +11,17 @@ import java.math.BigInteger; import java.util.Collection; import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Set; -import jakarta.persistence.criteria.AbstractQuery; -import jakarta.persistence.criteria.CollectionJoin; -import jakarta.persistence.criteria.Expression; -import jakarta.persistence.criteria.Join; -import jakarta.persistence.criteria.ListJoin; -import jakarta.persistence.criteria.MapJoin; -import jakarta.persistence.criteria.PluralJoin; -import jakarta.persistence.criteria.Predicate; -import jakarta.persistence.criteria.Root; -import jakarta.persistence.criteria.Selection; -import jakarta.persistence.criteria.SetJoin; import org.hibernate.query.criteria.JpaSelection; import org.hibernate.query.criteria.JpaSubQuery; import org.hibernate.query.sqm.NodeBuilder; import org.hibernate.query.sqm.SemanticQueryWalker; import org.hibernate.query.sqm.SqmExpressible; +import org.hibernate.query.sqm.tree.SqmCopyContext; import org.hibernate.query.sqm.tree.SqmQuery; +import org.hibernate.query.sqm.tree.cte.SqmCteStatement; import org.hibernate.query.sqm.tree.domain.SqmBagJoin; import org.hibernate.query.sqm.tree.domain.SqmCorrelatedBagJoin; import org.hibernate.query.sqm.tree.domain.SqmCorrelatedCrossJoin; @@ -55,6 +47,18 @@ import org.hibernate.query.sqm.tree.predicate.SqmInPredicate; import org.hibernate.query.sqm.tree.predicate.SqmPredicate; import org.hibernate.type.descriptor.java.JavaType; +import jakarta.persistence.criteria.AbstractQuery; +import jakarta.persistence.criteria.CollectionJoin; +import jakarta.persistence.criteria.Expression; +import jakarta.persistence.criteria.Join; +import jakarta.persistence.criteria.ListJoin; +import jakarta.persistence.criteria.MapJoin; +import jakarta.persistence.criteria.PluralJoin; +import jakarta.persistence.criteria.Predicate; +import jakarta.persistence.criteria.Root; +import jakarta.persistence.criteria.Selection; +import jakarta.persistence.criteria.SetJoin; + /** * @author Steve Ebersole */ @@ -88,6 +92,42 @@ public class SqmSubQuery extends AbstractSqmSelectQuery implements SqmSele this.parent = parent; } + private SqmSubQuery( + NodeBuilder builder, + Map> cteStatements, + boolean withRecursive, + Class resultType, + SqmQuery parent, + SqmExpressible expressibleType, + String alias) { + super( builder, cteStatements, withRecursive, resultType ); + this.parent = parent; + this.expressibleType = expressibleType; + this.alias = alias; + } + + @Override + public SqmSubQuery copy(SqmCopyContext context) { + final SqmSubQuery existing = context.getCopy( this ); + if ( existing != null ) { + return existing; + } + final SqmSubQuery statement = context.registerCopy( + this, + new SqmSubQuery<>( + nodeBuilder(), + copyCteStatements( context ), + isWithRecursive(), + getResultType(), + parent.copy( context ), + getExpressible(), + getAlias() + ) + ); + statement.setQueryPart( getQueryPart().copy( context ) ); + return statement; + } + @Override public SqmQuery getContainingQuery() { return parent; diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/update/SqmAssignment.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/update/SqmAssignment.java index 791ceb4455..072cc56935 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/update/SqmAssignment.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/update/SqmAssignment.java @@ -6,6 +6,7 @@ */ package org.hibernate.query.sqm.tree.update; +import org.hibernate.query.sqm.tree.SqmCopyContext; import org.hibernate.query.sqm.tree.domain.SqmPath; import org.hibernate.query.sqm.tree.expression.SqmExpression; @@ -24,6 +25,10 @@ public class SqmAssignment { this.value.applyInferableType( targetPath.getNodeType() ); } + public SqmAssignment copy(SqmCopyContext context) { + return new SqmAssignment( targetPath.copy( context ), value.copy( context ) ); + } + /** * The attribute/path to be updated */ diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/update/SqmSetClause.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/update/SqmSetClause.java index 40aaba1e36..df3e4f9a61 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/update/SqmSetClause.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/update/SqmSetClause.java @@ -10,6 +10,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; +import org.hibernate.query.sqm.tree.SqmCopyContext; import org.hibernate.query.sqm.tree.domain.SqmPath; import org.hibernate.query.sqm.tree.expression.SqmExpression; @@ -17,7 +18,23 @@ import org.hibernate.query.sqm.tree.expression.SqmExpression; * @author Steve Ebersole */ public class SqmSetClause { - private List assignments = new ArrayList<>(); + private final List assignments; + + public SqmSetClause() { + this.assignments = new ArrayList<>(); + } + + private SqmSetClause(List assignments) { + this.assignments = assignments; + } + + public SqmSetClause copy(SqmCopyContext context) { + final List assignments = new ArrayList<>( this.assignments.size() ); + for ( SqmAssignment assignment : this.assignments ) { + assignments.add( assignment.copy( context ) ); + } + return new SqmSetClause( assignments ); + } public List getAssignments() { return Collections.unmodifiableList( assignments ); diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/update/SqmUpdateStatement.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/update/SqmUpdateStatement.java index 2fbfe2e83c..c45c1a8ca2 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/update/SqmUpdateStatement.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/update/SqmUpdateStatement.java @@ -7,10 +7,8 @@ package org.hibernate.query.sqm.tree.update; import java.util.List; -import jakarta.persistence.criteria.Expression; -import jakarta.persistence.criteria.Path; -import jakarta.persistence.criteria.Predicate; -import jakarta.persistence.metamodel.SingularAttribute; +import java.util.Map; +import java.util.Set; import org.hibernate.query.criteria.JpaCriteriaUpdate; import org.hibernate.query.sqm.NodeBuilder; @@ -18,10 +16,19 @@ import org.hibernate.query.sqm.SemanticQueryWalker; import org.hibernate.query.sqm.SqmQuerySource; import org.hibernate.query.sqm.internal.SqmCriteriaNodeBuilder; import org.hibernate.query.sqm.tree.AbstractSqmRestrictedDmlStatement; +import org.hibernate.query.sqm.tree.SqmCopyContext; import org.hibernate.query.sqm.tree.SqmDeleteOrUpdateStatement; +import org.hibernate.query.sqm.tree.cte.SqmCteStatement; import org.hibernate.query.sqm.tree.domain.SqmPath; import org.hibernate.query.sqm.tree.expression.SqmExpression; +import org.hibernate.query.sqm.tree.expression.SqmParameter; import org.hibernate.query.sqm.tree.from.SqmRoot; +import org.hibernate.query.sqm.tree.predicate.SqmWhereClause; + +import jakarta.persistence.criteria.Expression; +import jakarta.persistence.criteria.Path; +import jakarta.persistence.criteria.Predicate; +import jakarta.persistence.metamodel.SingularAttribute; /** * @author Steve Ebersole @@ -53,6 +60,41 @@ public class SqmUpdateStatement ); } + public SqmUpdateStatement( + NodeBuilder builder, + SqmQuerySource querySource, + Set> parameters, + Map> cteStatements, + boolean withRecursiveCte, + SqmRoot target) { + super( builder, querySource, parameters, cteStatements, withRecursiveCte, target ); + } + + @Override + public SqmUpdateStatement copy(SqmCopyContext context) { + final SqmUpdateStatement existing = context.getCopy( this ); + if ( existing != null ) { + return existing; + } + final SqmUpdateStatement statement = context.registerCopy( + this, + new SqmUpdateStatement( + nodeBuilder(), + getQuerySource(), + copyParameters( context ), + copyCteStatements( context ), + isWithRecursive(), + getTarget().copy( context ) + ) + ); + statement.setWhereClause( copyWhereClause( context ) ); + statement.versioned = versioned; + if ( setClause != null ) { + statement.setClause = setClause.copy( context ); + } + return statement; + } + public SqmSetClause getSetClause() { return setClause; } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/JpaComplianceStub.java b/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/JpaComplianceStub.java index 73cd63a1ba..987262d42b 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/JpaComplianceStub.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/JpaComplianceStub.java @@ -58,4 +58,9 @@ public class JpaComplianceStub implements JpaCompliance { public boolean isLoadByIdComplianceEnabled() { return false; } + + @Override + public boolean isJpaCriteriaCopyComplianceEnabled() { + return false; + } } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/compliance/CriteriaDeleteTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/compliance/CriteriaDeleteTest.java new file mode 100644 index 0000000000..86003a5220 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/compliance/CriteriaDeleteTest.java @@ -0,0 +1,87 @@ +/* + * 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.orm.test.jpa.compliance; + +import org.hibernate.testing.orm.junit.EntityManagerFactoryScope; +import org.hibernate.testing.orm.junit.Jpa; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.Query; +import jakarta.persistence.Table; +import jakarta.persistence.criteria.CriteriaBuilder; +import jakarta.persistence.criteria.CriteriaDelete; +import jakarta.persistence.criteria.Root; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +@Jpa( + annotatedClasses = CriteriaDeleteTest.Person.class, + criteriaCopyComplianceEnabled = true +) +public class CriteriaDeleteTest { + + @BeforeEach + public void setUp(EntityManagerFactoryScope scope) { + scope.inTransaction( + entityManager -> { + entityManager.persist( new Person( 1, "Andrea", 5 ) ); + entityManager.persist( new Person( 2, "Fab", 40 ) ); + } + ); + } + + @Test + public void testModifyingDeleteQueryWhere(EntityManagerFactoryScope scope) { + scope.inTransaction( + entityManager -> { + final CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder(); + final CriteriaDelete criteriaDelete = criteriaBuilder + .createCriteriaDelete( Person.class ); + final Root Person = criteriaDelete.from( Person.class ); + criteriaDelete.where( criteriaBuilder.lt( Person.get( "age" ), 35 ) ); + + final Query q = entityManager.createQuery( criteriaDelete ); + criteriaDelete.where( criteriaBuilder.lt( Person.get( "age" ), 500 ) ); + + assertEquals( 1, q.executeUpdate() ); + } + ); + } + + @Entity(name = "Person") + @Table(name = "PERSON_TABLE") + public static class Person { + + @Id + private Integer id; + + private String name; + + private Integer age; + + Person() { + } + + public Person(Integer id, String name, Integer age) { + this.id = id; + this.name = name; + this.age = age; + } + + public Integer getId() { + return id; + } + + public String getName() { + return name; + } + } + +} \ No newline at end of file diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/function/OrderByFragmentFunction.java b/hibernate-envers/src/main/java/org/hibernate/envers/function/OrderByFragmentFunction.java index f3561931d6..59f0d7213c 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/function/OrderByFragmentFunction.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/function/OrderByFragmentFunction.java @@ -7,6 +7,7 @@ package org.hibernate.envers.function; +import java.util.ArrayList; import java.util.List; import org.hibernate.metamodel.mapping.PluralAttributeMapping; @@ -16,14 +17,21 @@ import org.hibernate.persister.collection.QueryableCollection; import org.hibernate.persister.entity.Joinable; import org.hibernate.query.spi.NavigablePath; import org.hibernate.query.spi.QueryEngine; +import org.hibernate.query.sqm.NodeBuilder; import org.hibernate.query.sqm.function.AbstractSqmFunctionDescriptor; +import org.hibernate.query.sqm.function.FunctionRenderingSupport; import org.hibernate.query.sqm.function.SelfRenderingFunctionSqlAstExpression; import org.hibernate.query.sqm.function.SelfRenderingSqmFunction; +import org.hibernate.query.sqm.function.SqmFunctionDescriptor; +import org.hibernate.query.sqm.produce.function.ArgumentsValidator; +import org.hibernate.query.sqm.produce.function.FunctionReturnTypeResolver; import org.hibernate.query.sqm.produce.function.StandardArgumentsValidators; import org.hibernate.query.sqm.produce.function.StandardFunctionReturnTypeResolvers; import org.hibernate.query.sqm.sql.FromClauseIndex; import org.hibernate.query.sqm.sql.SqmToSqlAstConverter; +import org.hibernate.query.sqm.tree.SqmCopyContext; import org.hibernate.query.sqm.tree.SqmTypedNode; +import org.hibernate.query.sqm.tree.expression.SqmAny; import org.hibernate.query.sqm.tree.expression.SqmLiteral; import org.hibernate.sql.ast.spi.SqlAstQueryPartProcessingState; import org.hibernate.sql.ast.tree.from.DelegatingTableGroup; @@ -56,57 +64,7 @@ public class OrderByFragmentFunction extends AbstractSqmFunctionDescriptor { ReturnableType impliedResultType, QueryEngine queryEngine, TypeConfiguration typeConfiguration) { - return new SelfRenderingSqmFunction( - this, - null, - arguments, - impliedResultType, - getArgumentsValidator(), - getReturnTypeResolver(), - queryEngine.getCriteriaBuilder(), - getName() - ) { - - @Override - public SelfRenderingFunctionSqlAstExpression convertToSqlAst(SqmToSqlAstConverter walker) { - final ReturnableType resultType = resolveResultType( - walker.getCreationContext().getMappingMetamodel().getTypeConfiguration() - ); - final String sqmAlias = ( (SqmLiteral) getArguments().get( 0 ) ).getLiteralValue(); - final String attributeRole = ( (SqmLiteral) getArguments().get( 1 ) ).getLiteralValue(); - final TableGroup tableGroup = ( (FromClauseIndex) walker.getFromClauseAccess() ).findTableGroup( - sqmAlias - ); - final QueryableCollection collectionDescriptor = (QueryableCollection) walker.getCreationContext() - .getSessionFactory() - .getRuntimeMetamodels() - .getMappingMetamodel() - .findCollectionDescriptor( attributeRole ); - final PluralAttributeMapping pluralAttribute = collectionDescriptor.getAttributeMapping(); - final QuerySpec queryPart = (QuerySpec) ( (SqlAstQueryPartProcessingState) walker.getCurrentProcessingState() ).getInflightQueryPart(); - final OrderByFragment fragment; - if ( pluralAttribute.getOrderByFragment() != null ) { - fragment = pluralAttribute.getOrderByFragment(); - } - else { - fragment = pluralAttribute.getManyToManyOrderByFragment(); - } - final String targetTableExpression; - if ( collectionDescriptor.getElementPersister() == null ) { - targetTableExpression = collectionDescriptor.getTableName(); - } - else { - targetTableExpression = ( (Joinable) collectionDescriptor.getElementPersister() ).getTableName(); - } - // We apply the fragment here and return null to signal that this is a no-op - fragment.apply( - queryPart, - new AuditingTableGroup( tableGroup, targetTableExpression ), - walker - ); - return null; - } - }; + return new OrderByFragmentSelfRenderingSqmFunction<>( this, arguments, impliedResultType, queryEngine ); } private static class AuditingTableGroup extends DelegatingTableGroup { @@ -149,4 +107,110 @@ public class OrderByFragmentFunction extends AbstractSqmFunctionDescriptor { return super.getTableReference( navigablePath, tableExpression, allowFkOptimization, resolve ); } } + + private static class OrderByFragmentSelfRenderingSqmFunction extends SelfRenderingSqmFunction { + + public OrderByFragmentSelfRenderingSqmFunction( + OrderByFragmentFunction orderByFragmentFunction, + List> arguments, + ReturnableType impliedResultType, + QueryEngine queryEngine) { + super( + orderByFragmentFunction, + null, + arguments, + impliedResultType, + orderByFragmentFunction.getArgumentsValidator(), + orderByFragmentFunction.getReturnTypeResolver(), + queryEngine.getCriteriaBuilder(), + orderByFragmentFunction.getName() + ); + } + + private OrderByFragmentSelfRenderingSqmFunction( + SqmFunctionDescriptor descriptor, + FunctionRenderingSupport renderingSupport, + List> arguments, + ReturnableType impliedResultType, + ArgumentsValidator argumentsValidator, + FunctionReturnTypeResolver returnTypeResolver, + NodeBuilder nodeBuilder, + String name) { + super( + descriptor, + renderingSupport, + arguments, + impliedResultType, + argumentsValidator, + returnTypeResolver, + nodeBuilder, + name + ); + } + + @Override + public OrderByFragmentSelfRenderingSqmFunction copy(SqmCopyContext context) { + final OrderByFragmentSelfRenderingSqmFunction existing = context.getCopy( this ); + if ( existing != null ) { + return existing; + } + final List> arguments = new ArrayList<>( getArguments().size() ); + for ( SqmTypedNode argument : getArguments() ) { + arguments.add( argument.copy( context ) ); + } + return context.registerCopy( + this, + new OrderByFragmentSelfRenderingSqmFunction<>( + getFunctionDescriptor(), + getRenderingSupport(), + arguments, + getImpliedResultType(), + getArgumentsValidator(), + getReturnTypeResolver(), + nodeBuilder(), + getFunctionName() + ) + ); + } + + @Override + public SelfRenderingFunctionSqlAstExpression convertToSqlAst(SqmToSqlAstConverter walker) { + final ReturnableType resultType = resolveResultType( + walker.getCreationContext().getMappingMetamodel().getTypeConfiguration() + ); + final String sqmAlias = ( (SqmLiteral) getArguments().get( 0 ) ).getLiteralValue(); + final String attributeRole = ( (SqmLiteral) getArguments().get( 1 ) ).getLiteralValue(); + final TableGroup tableGroup = ( (FromClauseIndex) walker.getFromClauseAccess() ).findTableGroup( + sqmAlias + ); + final QueryableCollection collectionDescriptor = (QueryableCollection) walker.getCreationContext() + .getSessionFactory() + .getRuntimeMetamodels() + .getMappingMetamodel() + .findCollectionDescriptor( attributeRole ); + final PluralAttributeMapping pluralAttribute = collectionDescriptor.getAttributeMapping(); + final QuerySpec queryPart = (QuerySpec) ( (SqlAstQueryPartProcessingState) walker.getCurrentProcessingState() ).getInflightQueryPart(); + final OrderByFragment fragment; + if ( pluralAttribute.getOrderByFragment() != null ) { + fragment = pluralAttribute.getOrderByFragment(); + } + else { + fragment = pluralAttribute.getManyToManyOrderByFragment(); + } + final String targetTableExpression; + if ( collectionDescriptor.getElementPersister() == null ) { + targetTableExpression = collectionDescriptor.getTableName(); + } + else { + targetTableExpression = ( (Joinable) collectionDescriptor.getElementPersister() ).getTableName(); + } + // We apply the fragment here and return null to signal that this is a no-op + fragment.apply( + queryPart, + new AuditingTableGroup( tableGroup, targetTableExpression ), + walker + ); + return null; + } + } } diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/EntityManagerFactoryExtension.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/EntityManagerFactoryExtension.java index ed14d690c7..5b5f6729c2 100644 --- a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/EntityManagerFactoryExtension.java +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/EntityManagerFactoryExtension.java @@ -98,6 +98,7 @@ public class EntityManagerFactoryExtension pui.getProperties().put( AvailableSettings.JPA_ID_GENERATOR_GLOBAL_SCOPE_COMPLIANCE, emfAnn.generatorScopeComplianceEnabled() ); pui.getProperties().put( AvailableSettings.JPA_ORDER_BY_MAPPING_COMPLIANCE, emfAnn.orderByMappingComplianceEnabled() ); pui.getProperties().put( AvailableSettings.JPA_LOAD_BY_ID_COMPLIANCE, emfAnn.loadByIdComplianceEnabled() ); + pui.getProperties().put( AvailableSettings.JPA_CRITERIA_COPY_COMPLIANCE, emfAnn.criteriaCopyComplianceEnabled() ); final Setting[] properties = emfAnn.properties(); for ( int i = 0; i < properties.length; i++ ) { diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/Jpa.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/Jpa.java index aeb814fc96..1baa0f87bf 100644 --- a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/Jpa.java +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/Jpa.java @@ -109,6 +109,11 @@ public @interface Jpa { */ boolean loadByIdComplianceEnabled() default false; + /** + * @see JpaCompliance#isLoadByIdComplianceEnabled() + */ + boolean criteriaCopyComplianceEnabled() default false; + boolean excludeUnlistedClasses() default false; StandardDomainModel[] standardModels() default {}; diff --git a/migration-guide.adoc b/migration-guide.adoc index 4d5a94e504..cc82c144f9 100644 --- a/migration-guide.adoc +++ b/migration-guide.adoc @@ -317,6 +317,17 @@ todo (6.0) - cover new syntaxes todo (6.0) - cover bulk manipulation query handling +[[query-criteria-copy]] +== JPA Criteria behavior change + +Without JPA compliance (`hibernate.jpa.compliance`) or when the newly introduced `hibernate.jpa.compliance.criteria_copy` configuration property +is disabled, it is expected that criteria queries passed to `jakarta.persistence.EntityManager#createQuery(CriteriaQuery)`, +`jakarta.persistence.EntityManager#createQuery(CriteriaUpdate)` or `jakarta.persistence.EntityManager#createQuery(CriteriaDelete)` +are not mutated afterwards to avoid the need for copying the criteria query. + +Prior to 6.0, mutations to criteria queries didn't affect `Query` instances created from that. +To retain backwards compatibility, enable the `hibernate.jpa.compliance.criteria_copy` configuration property. + [[query-sqm-rows]] ==== Result "rows"