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 extends JpaSelection>> 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 extends SqmTypedNode>> 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 extends JpaSelection>> 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