From 2a6d688c564279774f159d861c192a18cec7957c Mon Sep 17 00:00:00 2001 From: Christian Beikov Date: Wed, 23 Jul 2025 09:52:49 +0200 Subject: [PATCH 1/5] HHH-18311 Allow constructing SqmMultiTableInsertStrategy/SqmMultiTableMutationStrategy with EntityDomainType and MappingModelCreationProcess --- .../SessionFactoryOptionsBuilder.java | 119 +++++++++++++++--- ...stractDelegatingSessionFactoryOptions.java | 12 ++ .../query/spi/QueryEngineOptions.java | 18 +++ ...TableMutationStrategyProviderStandard.java | 11 +- 4 files changed, 144 insertions(+), 16 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/boot/internal/SessionFactoryOptionsBuilder.java b/hibernate-core/src/main/java/org/hibernate/boot/internal/SessionFactoryOptionsBuilder.java index f59777d834fa..43f693ffb806 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/internal/SessionFactoryOptionsBuilder.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/internal/SessionFactoryOptionsBuilder.java @@ -29,6 +29,8 @@ import org.hibernate.SessionEventListener; import org.hibernate.SessionFactoryObserver; import org.hibernate.context.spi.TenantSchemaMapper; +import org.hibernate.metamodel.mapping.EntityMappingType; +import org.hibernate.metamodel.mapping.internal.MappingModelCreationProcess; import org.hibernate.type.TimeZoneStorageStrategy; import org.hibernate.annotations.CacheLayout; import org.hibernate.boot.SchemaAutoTooling; @@ -179,6 +181,8 @@ public class SessionFactoryOptionsBuilder implements SessionFactoryOptions { private final HqlTranslator hqlTranslator; private final SqmMultiTableMutationStrategy sqmMultiTableMutationStrategy; private final SqmMultiTableInsertStrategy sqmMultiTableInsertStrategy; + private final Constructor sqmMultiTableMutationStrategyConstructor; + private final Constructor sqmMultiTableInsertStrategyConstructor; private final SqmTranslatorFactory sqmTranslatorFactory; private final Boolean useOfJdbcNamedParametersEnabled; private boolean namedQueryStartupCheckingEnabled; @@ -388,10 +392,14 @@ public SessionFactoryOptionsBuilder(StandardServiceRegistry serviceRegistry, Boo extractPropertyValue( QUERY_MULTI_TABLE_MUTATION_STRATEGY, settings ); sqmMultiTableMutationStrategy = resolveSqmMutationStrategy( sqmMutationStrategyImplName, serviceRegistry, strategySelector ); + sqmMultiTableMutationStrategyConstructor = + resolveSqmMutationStrategyConstructor( sqmMutationStrategyImplName, strategySelector ); final String sqmInsertStrategyImplName = extractPropertyValue( QUERY_MULTI_TABLE_INSERT_STRATEGY, settings ); sqmMultiTableInsertStrategy = resolveSqmInsertStrategy( sqmInsertStrategyImplName, serviceRegistry, strategySelector ); + sqmMultiTableInsertStrategyConstructor = + resolveSqmInsertStrategyConstructor( sqmInsertStrategyImplName, strategySelector ); useOfJdbcNamedParametersEnabled = configurationService.getSetting( CALLABLE_NAMED_PARAMS_ENABLED, BOOLEAN, true ); @@ -609,6 +617,7 @@ private SqmMultiTableMutationStrategy resolveSqmMutationStrategy( strategyClass -> { Constructor dialectConstructor = null; Constructor emptyConstructor = null; + Constructor entityBasedConstructor = null; // todo (6.0) : formalize the allowed constructor parameterizations for ( var declaredConstructor : strategyClass.getDeclaredConstructors() ) { final var parameterTypes = declaredConstructor.getParameterTypes(); @@ -622,31 +631,59 @@ private SqmMultiTableMutationStrategy resolveSqmMutationStrategy( else if ( parameterTypes.length == 0 ) { emptyConstructor = constructor; } + else if ( parameterTypes.length == 2 && parameterTypes[0] == EntityMappingType.class && parameterTypes[1] == MappingModelCreationProcess.class ) { + entityBasedConstructor = (Constructor) declaredConstructor; + } } - try { - if ( dialectConstructor != null ) { - return dialectConstructor.newInstance( - serviceRegistry.requireService( JdbcServices.class ).getDialect() - ); + if ( entityBasedConstructor == null ) { + try { + if ( dialectConstructor != null ) { + return dialectConstructor.newInstance( + serviceRegistry.requireService( JdbcServices.class ).getDialect() + ); + } + else if ( emptyConstructor != null ) { + return emptyConstructor.newInstance(); + } } - else if ( emptyConstructor != null ) { - return emptyConstructor.newInstance(); + catch (Exception e) { + throw new StrategySelectionException( + "Could not instantiate named strategy class [" + + strategyClass.getName() + "]", + e + ); } + throw new IllegalArgumentException( + "Cannot instantiate the class [" + strategyClass.getName() + "] because it does not have a constructor that accepts a dialect or an empty constructor" ); } - catch (Exception e) { - throw new StrategySelectionException( - "Could not instantiate named strategy class [" + strategyClass.getName() + "]", - e - ); + else { + return null; } - throw new IllegalArgumentException( "Cannot instantiate the class [" - + strategyClass.getName() - + "] because it does not have a constructor that accepts a dialect or an empty constructor" ); } ); } + @SuppressWarnings("unchecked") + private Constructor resolveSqmMutationStrategyConstructor( + String strategyName, + StrategySelector strategySelector) { + if ( strategyName == null ) { + return null; + } + + Class strategyClass = + strategySelector.selectStrategyImplementor( SqmMultiTableMutationStrategy.class, strategyName ); + for ( Constructor declaredConstructor : strategyClass.getDeclaredConstructors() ) { + final Class[] parameterTypes = declaredConstructor.getParameterTypes(); + if ( parameterTypes.length == 2 && parameterTypes[0] == EntityMappingType.class && parameterTypes[1] == MappingModelCreationProcess.class ) { + return (Constructor) declaredConstructor; + } + } + + return null; + } + @SuppressWarnings("unchecked") private SqmMultiTableInsertStrategy resolveSqmInsertStrategy( String strategyName, @@ -701,6 +738,26 @@ else if ( emptyConstructor != null ) { ); } + @SuppressWarnings("unchecked") + private Constructor resolveSqmInsertStrategyConstructor( + String strategyName, + StrategySelector strategySelector) { + if ( strategyName == null ) { + return null; + } + + Class strategyClass = + strategySelector.selectStrategyImplementor( SqmMultiTableInsertStrategy.class, strategyName ); + for ( Constructor declaredConstructor : strategyClass.getDeclaredConstructors() ) { + final Class[] parameterTypes = declaredConstructor.getParameterTypes(); + if ( parameterTypes.length == 2 && parameterTypes[0] == EntityMappingType.class && parameterTypes[1] == MappingModelCreationProcess.class ) { + return (Constructor) declaredConstructor; + } + } + + return null; + } + private HqlTranslator resolveHqlTranslator( String producerName, StandardServiceRegistry serviceRegistry, @@ -921,6 +978,38 @@ public SqmMultiTableInsertStrategy getCustomSqmMultiTableInsertStrategy() { return sqmMultiTableInsertStrategy; } + @Override + public SqmMultiTableMutationStrategy resolveCustomSqmMultiTableMutationStrategy(EntityMappingType rootEntityDescriptor, MappingModelCreationProcess creationProcess) { + if ( sqmMultiTableMutationStrategyConstructor != null ) { + try { + return sqmMultiTableMutationStrategyConstructor.newInstance( rootEntityDescriptor, creationProcess ); + } + catch (Exception e) { + throw new StrategySelectionException( + String.format( "Could not instantiate named strategy class [%s]", sqmMultiTableMutationStrategyConstructor.getDeclaringClass().getName() ), + e + ); + } + } + return null; + } + + @Override + public SqmMultiTableInsertStrategy resolveCustomSqmMultiTableInsertStrategy(EntityMappingType rootEntityDescriptor, MappingModelCreationProcess creationProcess) { + if ( sqmMultiTableInsertStrategyConstructor != null ) { + try { + return sqmMultiTableInsertStrategyConstructor.newInstance( rootEntityDescriptor, creationProcess ); + } + catch (Exception e) { + throw new StrategySelectionException( + String.format( "Could not instantiate named strategy class [%s]", sqmMultiTableInsertStrategyConstructor.getDeclaringClass().getName() ), + e + ); + } + } + return null; + } + @Override public boolean isUseOfJdbcNamedParametersEnabled() { return useOfJdbcNamedParametersEnabled; diff --git a/hibernate-core/src/main/java/org/hibernate/boot/spi/AbstractDelegatingSessionFactoryOptions.java b/hibernate-core/src/main/java/org/hibernate/boot/spi/AbstractDelegatingSessionFactoryOptions.java index 3ec439e03428..09deec93d2ea 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/spi/AbstractDelegatingSessionFactoryOptions.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/spi/AbstractDelegatingSessionFactoryOptions.java @@ -18,6 +18,8 @@ import org.hibernate.LockOptions; import org.hibernate.SessionFactoryObserver; import org.hibernate.context.spi.TenantSchemaMapper; +import org.hibernate.metamodel.mapping.EntityMappingType; +import org.hibernate.metamodel.mapping.internal.MappingModelCreationProcess; import org.hibernate.type.TimeZoneStorageStrategy; import org.hibernate.annotations.CacheLayout; import org.hibernate.boot.SchemaAutoTooling; @@ -145,6 +147,16 @@ public SqmMultiTableInsertStrategy getCustomSqmMultiTableInsertStrategy() { return delegate.getCustomSqmMultiTableInsertStrategy(); } + @Override + public SqmMultiTableMutationStrategy resolveCustomSqmMultiTableMutationStrategy(EntityMappingType rootEntityDescriptor, MappingModelCreationProcess creationProcess) { + return delegate.resolveCustomSqmMultiTableMutationStrategy( rootEntityDescriptor, creationProcess ); + } + + @Override + public SqmMultiTableInsertStrategy resolveCustomSqmMultiTableInsertStrategy(EntityMappingType rootEntityDescriptor, MappingModelCreationProcess creationProcess) { + return delegate.resolveCustomSqmMultiTableInsertStrategy( rootEntityDescriptor, creationProcess ); + } + @Override public StatementInspector getStatementInspector() { return delegate.getStatementInspector(); diff --git a/hibernate-core/src/main/java/org/hibernate/query/spi/QueryEngineOptions.java b/hibernate-core/src/main/java/org/hibernate/query/spi/QueryEngineOptions.java index 73ff0f42b562..3b5d47e47783 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/spi/QueryEngineOptions.java +++ b/hibernate-core/src/main/java/org/hibernate/query/spi/QueryEngineOptions.java @@ -7,6 +7,8 @@ import java.util.Map; import org.hibernate.jpa.spi.JpaCompliance; +import org.hibernate.metamodel.mapping.EntityMappingType; +import org.hibernate.metamodel.mapping.internal.MappingModelCreationProcess; import org.hibernate.query.criteria.ValueHandlingMode; import org.hibernate.query.hql.HqlTranslator; import org.hibernate.query.sqm.function.SqmFunctionDescriptor; @@ -75,6 +77,22 @@ public interface QueryEngineOptions { */ SqmMultiTableInsertStrategy getCustomSqmMultiTableInsertStrategy(); + /** + * Contract for handling SQM trees representing mutation (UPDATE or DELETE) queries + * where the target of the mutation is a multi-table entity. + * + * @see org.hibernate.cfg.QuerySettings#QUERY_MULTI_TABLE_MUTATION_STRATEGY + */ + SqmMultiTableMutationStrategy resolveCustomSqmMultiTableMutationStrategy(EntityMappingType rootEntityDescriptor, MappingModelCreationProcess creationProcess); + + /** + * Contract for handling SQM trees representing insertion (INSERT) queries where the + * target of the mutation is a multi-table entity. + * + * @see org.hibernate.cfg.QuerySettings#QUERY_MULTI_TABLE_INSERT_STRATEGY + */ + SqmMultiTableInsertStrategy resolveCustomSqmMultiTableInsertStrategy(EntityMappingType rootEntityDescriptor, MappingModelCreationProcess creationProcess); + /** * @see org.hibernate.cfg.JpaComplianceSettings */ diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/SqmMultiTableMutationStrategyProviderStandard.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/SqmMultiTableMutationStrategyProviderStandard.java index 2b39ce021d1b..abfec8836bad 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/SqmMultiTableMutationStrategyProviderStandard.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/SqmMultiTableMutationStrategyProviderStandard.java @@ -32,7 +32,11 @@ public SqmMultiTableMutationStrategy createMutationStrategy( if ( specifiedStrategy != null ) { return specifiedStrategy; } - + final SqmMultiTableMutationStrategy specifiedEntityBaseStrategy = + options.resolveCustomSqmMultiTableMutationStrategy( rootEntityDescriptor, creationProcess ); + if ( specifiedEntityBaseStrategy != null ) { + return specifiedEntityBaseStrategy; + } return creationContext.getDialect().getFallbackSqmMutationStrategy( rootEntityDescriptor, creationContext ); } @@ -47,6 +51,11 @@ public SqmMultiTableInsertStrategy createInsertStrategy( if ( specifiedStrategy != null ) { return specifiedStrategy; } + final SqmMultiTableInsertStrategy specifiedEntityBaseStrategy = + options.resolveCustomSqmMultiTableInsertStrategy( rootEntityDescriptor, creationProcess ); + if ( specifiedEntityBaseStrategy != null ) { + return specifiedEntityBaseStrategy; + } return creationContext.getDialect().getFallbackSqmInsertStrategy( rootEntityDescriptor, creationContext ); } From c560c2eeb1215454b547ca0781a79d87784ccdd4 Mon Sep 17 00:00:00 2001 From: Christian Beikov Date: Wed, 23 Jul 2025 12:25:22 +0200 Subject: [PATCH 2/5] HHH-19521 Expose different TemporaryTableStrategy from Dialect to allow configuring other mutation/insert strategies --- .../community/dialect/DB2LegacyDialect.java | 19 + .../community/dialect/DerbyDialect.java | 55 +-- .../community/dialect/DerbyLegacyDialect.java | 55 +-- .../community/dialect/FirebirdDialect.java | 30 +- .../community/dialect/H2LegacyDialect.java | 30 +- .../community/dialect/HANALegacyDialect.java | 34 +- .../community/dialect/HSQLLegacyDialect.java | 69 +-- .../community/dialect/InformixDialect.java | 36 +- .../community/dialect/IngresDialect.java | 36 +- .../community/dialect/MaxDBDialect.java | 34 +- .../community/dialect/MySQLLegacyDialect.java | 36 +- .../dialect/OracleLegacyDialect.java | 58 +-- .../dialect/PostgreSQLLegacyDialect.java | 7 + .../community/dialect/SingleStoreDialect.java | 40 +- .../community/dialect/TeradataDialect.java | 30 +- .../community/dialect/TimesTenDialect.java | 30 +- .../DB2LegacyLocalTemporaryTableStrategy.java | 36 ++ .../DerbyLocalTemporaryTableStrategy.java | 36 ++ .../InformixLocalTemporaryTableStrategy.java | 26 ++ .../IngresGlobalTemporaryTableStrategy.java | 30 ++ .../MaxDBLocalTemporaryTableStrategy.java | 31 ++ ...ingleStoreLocalTemporaryTableStrategy.java | 33 ++ .../TeradataGlobalTemporaryTableStrategy.java | 21 + .../SessionFactoryOptionsBuilder.java | 55 ++- .../boot/model/relational/Database.java | 5 + ...stractDelegatingSessionFactoryOptions.java | 10 +- .../dialect/AbstractTransactSQLDialect.java | 47 +- .../org/hibernate/dialect/DB2Dialect.java | 7 + .../java/org/hibernate/dialect/Dialect.java | 56 ++- .../java/org/hibernate/dialect/H2Dialect.java | 36 +- .../org/hibernate/dialect/HANADialect.java | 34 +- .../org/hibernate/dialect/HSQLDialect.java | 64 +-- .../org/hibernate/dialect/MySQLDialect.java | 36 +- .../org/hibernate/dialect/OracleDialect.java | 36 +- .../hibernate/dialect/PostgreSQLDialect.java | 7 + .../DB2GlobalTemporaryTableStrategy.java | 23 + .../H2GlobalTemporaryTableStrategy.java | 18 + .../HANAGlobalTemporaryTableStrategy.java | 21 + .../HSQLLocalTemporaryTableStrategy.java | 32 ++ .../LegacyTemporaryTableStrategy.java | 72 ++++ .../MySQLLocalTemporaryTableStrategy.java | 31 ++ .../OracleLocalTemporaryTableStrategy.java | 38 ++ .../PersistentTemporaryTableStrategy.java | 62 +++ .../StandardGlobalTemporaryTableStrategy.java | 61 +++ .../StandardLocalTemporaryTableStrategy.java | 62 +++ .../StandardTemporaryTableExporter.java | 74 +++- .../dialect/temptable/TemporaryTable.java | 318 +++++++++++--- .../temptable/TemporaryTableStrategy.java | 85 ++++ ...ransactSQLLocalTemporaryTableStrategy.java | 32 ++ .../id/insert/AbstractReturningDelegate.java | 2 + .../entity/AbstractEntityPersister.java | 11 +- .../query/spi/QueryEngineOptions.java | 6 +- ...TableMutationStrategyProviderStandard.java | 4 +- .../internal/SqmMutationStrategyHelper.java | 102 +++++ .../internal/cte/CteInsertHandler.java | 71 ++-- .../AbstractDeleteExecutionDelegate.java | 16 +- .../ExecuteWithTemporaryTableHelper.java | 26 +- .../GlobalTemporaryTableInsertStrategy.java | 31 +- .../GlobalTemporaryTableMutationStrategy.java | 34 +- .../GlobalTemporaryTableStrategy.java | 17 +- .../temptable/InsertExecutionDelegate.java | 402 +++++++++++------- .../LocalTemporaryTableInsertStrategy.java | 34 +- .../LocalTemporaryTableMutationStrategy.java | 39 +- .../LocalTemporaryTableStrategy.java | 15 + .../PersistentTableInsertStrategy.java | 32 +- .../PersistentTableMutationStrategy.java | 35 +- .../temptable/PersistentTableStrategy.java | 9 +- .../RestrictedDeleteExecutionDelegate.java | 9 +- .../SoftDeleteExecutionDelegate.java | 9 +- .../temptable/TableBasedDeleteHandler.java | 26 +- .../temptable/TableBasedInsertHandler.java | 36 +- .../temptable/TableBasedUpdateHandler.java | 20 +- .../temptable/UpdateExecutionDelegate.java | 15 +- .../hibernate/sql/ast/tree/cte/CteTable.java | 98 ++--- .../tree/insert/InsertSelectStatement.java | 7 + ...stractMutationStrategyCompositeIdTest.java | 36 +- ...stractMutationStrategyGeneratedIdTest.java | 142 +++++++ ...nStrategyGeneratedIdWithOptimizerTest.java | 140 ++++++ ...MutationStrategyGeneratedIdentityTest.java | 143 +++++++ .../AbstractMutationStrategyIdTest.java | 45 +- ...efaultMutationStrategyCompositeIdTest.java | 8 +- ...efaultMutationStrategyGeneratedIdTest.java | 15 + ...nStrategyGeneratedIdWithOptimizerTest.java | 15 + ...MutationStrategyGeneratedIdentityTest.java | 18 + .../bulkid/DefaultMutationStrategyIdTest.java | 8 +- ...yTableMutationStrategyCompositeIdTest.java | 26 ++ ...yTableMutationStrategyGeneratedIdTest.java | 19 + ...nStrategyGeneratedIdWithOptimizerTest.java | 19 + ...MutationStrategyGeneratedIdentityTest.java | 20 + ...lTemporaryTableMutationStrategyIdTest.java | 26 ++ ...InlineMutationStrategyCompositeIdTest.java | 9 +- .../bulkid/InlineMutationStrategyIdTest.java | 9 +- ...yTableMutationStrategyCompositeIdTest.java | 26 ++ ...yTableMutationStrategyGeneratedIdTest.java | 19 + ...nStrategyGeneratedIdWithOptimizerTest.java | 19 + ...MutationStrategyGeneratedIdentityTest.java | 20 + ...lTemporaryTableMutationStrategyIdTest.java | 26 ++ ...tTableMutationStrategyCompositeIdTest.java | 23 + ...tTableMutationStrategyGeneratedIdTest.java | 16 + ...nStrategyGeneratedIdWithOptimizerTest.java | 16 + ...MutationStrategyGeneratedIdentityTest.java | 19 + ...PersistentTableMutationStrategyIdTest.java | 23 + .../org/hibernate/testing/DialectChecks.java | 10 +- .../org/hibernate/testing/junit4/Helper.java | 4 +- .../orm/junit/DialectFeatureChecks.java | 30 +- 105 files changed, 3193 insertions(+), 996 deletions(-) create mode 100644 hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/temptable/DB2LegacyLocalTemporaryTableStrategy.java create mode 100644 hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/temptable/DerbyLocalTemporaryTableStrategy.java create mode 100644 hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/temptable/InformixLocalTemporaryTableStrategy.java create mode 100644 hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/temptable/IngresGlobalTemporaryTableStrategy.java create mode 100644 hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/temptable/MaxDBLocalTemporaryTableStrategy.java create mode 100644 hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/temptable/SingleStoreLocalTemporaryTableStrategy.java create mode 100644 hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/temptable/TeradataGlobalTemporaryTableStrategy.java create mode 100644 hibernate-core/src/main/java/org/hibernate/dialect/temptable/DB2GlobalTemporaryTableStrategy.java create mode 100644 hibernate-core/src/main/java/org/hibernate/dialect/temptable/H2GlobalTemporaryTableStrategy.java create mode 100644 hibernate-core/src/main/java/org/hibernate/dialect/temptable/HANAGlobalTemporaryTableStrategy.java create mode 100644 hibernate-core/src/main/java/org/hibernate/dialect/temptable/HSQLLocalTemporaryTableStrategy.java create mode 100644 hibernate-core/src/main/java/org/hibernate/dialect/temptable/LegacyTemporaryTableStrategy.java create mode 100644 hibernate-core/src/main/java/org/hibernate/dialect/temptable/MySQLLocalTemporaryTableStrategy.java create mode 100644 hibernate-core/src/main/java/org/hibernate/dialect/temptable/OracleLocalTemporaryTableStrategy.java create mode 100644 hibernate-core/src/main/java/org/hibernate/dialect/temptable/PersistentTemporaryTableStrategy.java create mode 100644 hibernate-core/src/main/java/org/hibernate/dialect/temptable/StandardGlobalTemporaryTableStrategy.java create mode 100644 hibernate-core/src/main/java/org/hibernate/dialect/temptable/StandardLocalTemporaryTableStrategy.java create mode 100644 hibernate-core/src/main/java/org/hibernate/dialect/temptable/TemporaryTableStrategy.java create mode 100644 hibernate-core/src/main/java/org/hibernate/dialect/temptable/TransactSQLLocalTemporaryTableStrategy.java create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/bulkid/AbstractMutationStrategyGeneratedIdTest.java create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/bulkid/AbstractMutationStrategyGeneratedIdWithOptimizerTest.java create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/bulkid/AbstractMutationStrategyGeneratedIdentityTest.java create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/bulkid/DefaultMutationStrategyGeneratedIdTest.java create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/bulkid/DefaultMutationStrategyGeneratedIdWithOptimizerTest.java create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/bulkid/DefaultMutationStrategyGeneratedIdentityTest.java create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/bulkid/GlobalTemporaryTableMutationStrategyCompositeIdTest.java create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/bulkid/GlobalTemporaryTableMutationStrategyGeneratedIdTest.java create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/bulkid/GlobalTemporaryTableMutationStrategyGeneratedIdWithOptimizerTest.java create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/bulkid/GlobalTemporaryTableMutationStrategyGeneratedIdentityTest.java create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/bulkid/GlobalTemporaryTableMutationStrategyIdTest.java create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/bulkid/LocalTemporaryTableMutationStrategyCompositeIdTest.java create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/bulkid/LocalTemporaryTableMutationStrategyGeneratedIdTest.java create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/bulkid/LocalTemporaryTableMutationStrategyGeneratedIdWithOptimizerTest.java create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/bulkid/LocalTemporaryTableMutationStrategyGeneratedIdentityTest.java create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/bulkid/LocalTemporaryTableMutationStrategyIdTest.java create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/bulkid/PersistentTableMutationStrategyCompositeIdTest.java create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/bulkid/PersistentTableMutationStrategyGeneratedIdTest.java create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/bulkid/PersistentTableMutationStrategyGeneratedIdWithOptimizerTest.java create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/bulkid/PersistentTableMutationStrategyGeneratedIdentityTest.java create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/bulkid/PersistentTableMutationStrategyIdTest.java diff --git a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/DB2LegacyDialect.java b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/DB2LegacyDialect.java index 66aac5b68bba..7b165775a200 100644 --- a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/DB2LegacyDialect.java +++ b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/DB2LegacyDialect.java @@ -6,10 +6,12 @@ import jakarta.persistence.TemporalType; import jakarta.persistence.Timeout; +import org.checkerframework.checker.nullness.qual.Nullable; import org.hibernate.Timeouts; import org.hibernate.boot.model.FunctionContributions; import org.hibernate.boot.model.TypeContributions; import org.hibernate.community.dialect.sequence.LegacyDB2SequenceSupport; +import org.hibernate.community.dialect.temptable.DB2LegacyLocalTemporaryTableStrategy; import org.hibernate.dialect.DB2GetObjectExtractor; import org.hibernate.dialect.DatabaseVersion; import org.hibernate.dialect.Dialect; @@ -33,6 +35,8 @@ import org.hibernate.dialect.pagination.LimitHandler; import org.hibernate.dialect.sequence.DB2SequenceSupport; import org.hibernate.dialect.sequence.SequenceSupport; +import org.hibernate.dialect.temptable.DB2GlobalTemporaryTableStrategy; +import org.hibernate.dialect.temptable.TemporaryTableStrategy; import org.hibernate.dialect.type.DB2StructJdbcType; import org.hibernate.dialect.unique.AlterTableUniqueIndexDelegate; import org.hibernate.dialect.unique.SkipNullableUniqueDelegate; @@ -1041,6 +1045,21 @@ public SqmMultiTableInsertStrategy getFallbackSqmInsertStrategy( return new CteInsertStrategy( rootEntityDescriptor, runtimeModelCreationContext ); } + @Override + public @Nullable TemporaryTableStrategy getGlobalTemporaryTableStrategy() { + // Starting in DB2 9.7, "real" global temporary tables that can be shared between sessions + // are supported; (obviously) data is not shared between sessions. + return getDB2Version().isBefore( 9, 7 ) ? null : DB2GlobalTemporaryTableStrategy.INSTANCE; + } + + @Override + public @Nullable TemporaryTableStrategy getLocalTemporaryTableStrategy() { + // Prior to DB2 9.7, "real" global temporary tables that can be shared between sessions + // are *not* supported; even though the DB2 command says to declare a "global" temp table + // Hibernate treats it as a "local" temp table. + return getDB2Version().isBefore( 9, 7 ) ? DB2LegacyLocalTemporaryTableStrategy.INSTANCE : null; + } + @Override public boolean supportsCurrentTimestampSelection() { return true; diff --git a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/DerbyDialect.java b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/DerbyDialect.java index f3db4eca8012..f886a1016e47 100644 --- a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/DerbyDialect.java +++ b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/DerbyDialect.java @@ -32,8 +32,9 @@ import org.hibernate.dialect.lock.spi.LockingSupport; import org.hibernate.dialect.pagination.LimitHandler; import org.hibernate.dialect.sequence.SequenceSupport; -import org.hibernate.dialect.temptable.TemporaryTable; +import org.hibernate.community.dialect.temptable.DerbyLocalTemporaryTableStrategy; import org.hibernate.dialect.temptable.TemporaryTableKind; +import org.hibernate.dialect.temptable.TemporaryTableStrategy; import org.hibernate.dialect.unique.CreateTableUniqueDelegate; import org.hibernate.dialect.unique.UniqueDelegate; import org.hibernate.engine.jdbc.Size; @@ -52,10 +53,9 @@ import org.hibernate.query.common.TemporalUnit; import org.hibernate.query.sqm.CastType; import org.hibernate.query.sqm.IntervalType; -import org.hibernate.query.sqm.mutation.internal.temptable.GlobalTemporaryTableMutationStrategy; +import org.hibernate.query.sqm.mutation.spi.BeforeUseAction; import org.hibernate.query.sqm.mutation.internal.temptable.LocalTemporaryTableInsertStrategy; import org.hibernate.query.sqm.mutation.internal.temptable.LocalTemporaryTableMutationStrategy; -import org.hibernate.query.sqm.mutation.spi.BeforeUseAction; import org.hibernate.query.sqm.mutation.spi.SqmMultiTableInsertStrategy; import org.hibernate.query.sqm.mutation.spi.SqmMultiTableMutationStrategy; import org.hibernate.service.ServiceRegistry; @@ -975,46 +975,18 @@ protected void registerDefaultKeywords() { registerKeyword( "YEAR" ); } - /** - * {@inheritDoc} - *

- * From Derby docs: - *

-	 *     The DECLARE GLOBAL TEMPORARY TABLE statement defines a temporary table for the current connection.
-	 * 
- *

- * {@link DB2Dialect} returns a {@link GlobalTemporaryTableMutationStrategy} that - * will make temporary tables created at startup and hence unavailable for subsequent connections.
- * see HHH-10238. - */ @Override public SqmMultiTableMutationStrategy getFallbackSqmMutationStrategy( EntityMappingType rootEntityDescriptor, RuntimeModelCreationContext runtimeModelCreationContext) { - return new LocalTemporaryTableMutationStrategy( - TemporaryTable.createIdTable( - rootEntityDescriptor, - basename -> "session." + TemporaryTable.ID_TABLE_PREFIX + basename, - this, - runtimeModelCreationContext - ), - runtimeModelCreationContext.getSessionFactory() - ); + return new LocalTemporaryTableMutationStrategy( rootEntityDescriptor, runtimeModelCreationContext ); } @Override public SqmMultiTableInsertStrategy getFallbackSqmInsertStrategy( EntityMappingType rootEntityDescriptor, RuntimeModelCreationContext runtimeModelCreationContext) { - return new LocalTemporaryTableInsertStrategy( - TemporaryTable.createEntityTable( - rootEntityDescriptor, - name -> "session." + TemporaryTable.ENTITY_TABLE_PREFIX + name, - this, - runtimeModelCreationContext - ), - runtimeModelCreationContext.getSessionFactory() - ); + return new LocalTemporaryTableInsertStrategy( rootEntityDescriptor, runtimeModelCreationContext ); } @Override @@ -1023,23 +995,28 @@ public TemporaryTableKind getSupportedTemporaryTableKind() { } @Override - public String getTemporaryTableCreateOptions() { - return "not logged"; + public TemporaryTableStrategy getLocalTemporaryTableStrategy() { + return DerbyLocalTemporaryTableStrategy.INSTANCE; } @Override - public boolean supportsTemporaryTablePrimaryKey() { - return false; + public String getTemporaryTableCreateOptions() { + return DerbyLocalTemporaryTableStrategy.INSTANCE.getTemporaryTableCreateOptions(); } @Override public String getTemporaryTableCreateCommand() { - return "declare global temporary table"; + return DerbyLocalTemporaryTableStrategy.INSTANCE.getTemporaryTableCreateCommand(); } @Override public BeforeUseAction getTemporaryTableBeforeUseAction() { - return BeforeUseAction.CREATE; + return DerbyLocalTemporaryTableStrategy.INSTANCE.getTemporaryTableBeforeUseAction(); + } + + @Override + public boolean supportsTemporaryTablePrimaryKey() { + return DerbyLocalTemporaryTableStrategy.INSTANCE.supportsTemporaryTablePrimaryKey(); } @Override diff --git a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/DerbyLegacyDialect.java b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/DerbyLegacyDialect.java index 05d52946e343..241a88b84df9 100644 --- a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/DerbyLegacyDialect.java +++ b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/DerbyLegacyDialect.java @@ -33,8 +33,9 @@ import org.hibernate.dialect.pagination.AbstractLimitHandler; import org.hibernate.dialect.pagination.LimitHandler; import org.hibernate.dialect.sequence.SequenceSupport; -import org.hibernate.dialect.temptable.TemporaryTable; +import org.hibernate.community.dialect.temptable.DerbyLocalTemporaryTableStrategy; import org.hibernate.dialect.temptable.TemporaryTableKind; +import org.hibernate.dialect.temptable.TemporaryTableStrategy; import org.hibernate.engine.jdbc.Size; import org.hibernate.engine.jdbc.dialect.spi.DialectResolutionInfo; import org.hibernate.engine.jdbc.env.spi.IdentifierHelper; @@ -51,10 +52,9 @@ import org.hibernate.query.common.TemporalUnit; import org.hibernate.query.sqm.CastType; import org.hibernate.query.sqm.IntervalType; -import org.hibernate.query.sqm.mutation.internal.temptable.GlobalTemporaryTableMutationStrategy; +import org.hibernate.query.sqm.mutation.spi.BeforeUseAction; import org.hibernate.query.sqm.mutation.internal.temptable.LocalTemporaryTableInsertStrategy; import org.hibernate.query.sqm.mutation.internal.temptable.LocalTemporaryTableMutationStrategy; -import org.hibernate.query.sqm.mutation.spi.BeforeUseAction; import org.hibernate.query.sqm.mutation.spi.SqmMultiTableInsertStrategy; import org.hibernate.query.sqm.mutation.spi.SqmMultiTableMutationStrategy; import org.hibernate.service.ServiceRegistry; @@ -978,46 +978,18 @@ protected void registerDefaultKeywords() { registerKeyword( "YEAR" ); } - /** - * {@inheritDoc} - *

- * From Derby docs: - *

-	 *     The DECLARE GLOBAL TEMPORARY TABLE statement defines a temporary table for the current connection.
-	 * 
- * - * {@link DB2Dialect} returns a {@link GlobalTemporaryTableMutationStrategy} that - * will make temporary tables created at startup and hence unavailable for subsequent connections.
- * see HHH-10238. - */ @Override public SqmMultiTableMutationStrategy getFallbackSqmMutationStrategy( EntityMappingType rootEntityDescriptor, RuntimeModelCreationContext runtimeModelCreationContext) { - return new LocalTemporaryTableMutationStrategy( - TemporaryTable.createIdTable( - rootEntityDescriptor, - basename -> "session." + TemporaryTable.ID_TABLE_PREFIX + basename, - this, - runtimeModelCreationContext - ), - runtimeModelCreationContext.getSessionFactory() - ); + return new LocalTemporaryTableMutationStrategy( rootEntityDescriptor, runtimeModelCreationContext ); } @Override public SqmMultiTableInsertStrategy getFallbackSqmInsertStrategy( EntityMappingType rootEntityDescriptor, RuntimeModelCreationContext runtimeModelCreationContext) { - return new LocalTemporaryTableInsertStrategy( - TemporaryTable.createEntityTable( - rootEntityDescriptor, - name -> "session." + TemporaryTable.ENTITY_TABLE_PREFIX + name, - this, - runtimeModelCreationContext - ), - runtimeModelCreationContext.getSessionFactory() - ); + return new LocalTemporaryTableInsertStrategy( rootEntityDescriptor, runtimeModelCreationContext ); } @Override @@ -1026,23 +998,28 @@ public TemporaryTableKind getSupportedTemporaryTableKind() { } @Override - public String getTemporaryTableCreateOptions() { - return "not logged"; + public TemporaryTableStrategy getLocalTemporaryTableStrategy() { + return DerbyLocalTemporaryTableStrategy.INSTANCE; } @Override - public boolean supportsTemporaryTablePrimaryKey() { - return false; + public String getTemporaryTableCreateOptions() { + return DerbyLocalTemporaryTableStrategy.INSTANCE.getTemporaryTableCreateOptions(); } @Override public String getTemporaryTableCreateCommand() { - return "declare global temporary table"; + return DerbyLocalTemporaryTableStrategy.INSTANCE.getTemporaryTableCreateCommand(); } @Override public BeforeUseAction getTemporaryTableBeforeUseAction() { - return BeforeUseAction.CREATE; + return DerbyLocalTemporaryTableStrategy.INSTANCE.getTemporaryTableBeforeUseAction(); + } + + @Override + public boolean supportsTemporaryTablePrimaryKey() { + return DerbyLocalTemporaryTableStrategy.INSTANCE.supportsTemporaryTablePrimaryKey(); } @Override diff --git a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/FirebirdDialect.java b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/FirebirdDialect.java index a94e25fae86e..2368fd3e7e82 100644 --- a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/FirebirdDialect.java +++ b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/FirebirdDialect.java @@ -44,8 +44,9 @@ import org.hibernate.dialect.pagination.LimitHandler; import org.hibernate.dialect.pagination.OffsetFetchLimitHandler; import org.hibernate.dialect.sequence.SequenceSupport; -import org.hibernate.dialect.temptable.TemporaryTable; +import org.hibernate.dialect.temptable.StandardGlobalTemporaryTableStrategy; import org.hibernate.dialect.temptable.TemporaryTableKind; +import org.hibernate.dialect.temptable.TemporaryTableStrategy; import org.hibernate.engine.jdbc.dialect.spi.DialectResolutionInfo; import org.hibernate.engine.jdbc.env.spi.IdentifierHelper; import org.hibernate.engine.jdbc.env.spi.IdentifierHelperBuilder; @@ -1023,30 +1024,14 @@ public SQLExceptionConversionDelegate buildSQLExceptionConversionDelegate() { public SqmMultiTableMutationStrategy getFallbackSqmMutationStrategy(EntityMappingType entityDescriptor, RuntimeModelCreationContext runtimeModelCreationContext) { return getVersion().isBefore( 2,1 ) ? super.getFallbackSqmMutationStrategy( entityDescriptor, runtimeModelCreationContext ) - : new GlobalTemporaryTableMutationStrategy( - TemporaryTable.createIdTable( - entityDescriptor, - name -> TemporaryTable.ID_TABLE_PREFIX + name, - this, - runtimeModelCreationContext - ), - runtimeModelCreationContext.getSessionFactory() - ); + : new GlobalTemporaryTableMutationStrategy( entityDescriptor, runtimeModelCreationContext ); } @Override public SqmMultiTableInsertStrategy getFallbackSqmInsertStrategy(EntityMappingType entityDescriptor, RuntimeModelCreationContext runtimeModelCreationContext) { return getVersion().isBefore( 2, 1 ) ? super.getFallbackSqmInsertStrategy( entityDescriptor, runtimeModelCreationContext ) - : new GlobalTemporaryTableInsertStrategy( - TemporaryTable.createEntityTable( - entityDescriptor, - name -> TemporaryTable.ENTITY_TABLE_PREFIX + name, - this, - runtimeModelCreationContext - ), - runtimeModelCreationContext.getSessionFactory() - ); + : new GlobalTemporaryTableInsertStrategy( entityDescriptor, runtimeModelCreationContext ); } @Override @@ -1054,9 +1039,14 @@ public TemporaryTableKind getSupportedTemporaryTableKind() { return TemporaryTableKind.GLOBAL; } + @Override + public TemporaryTableStrategy getGlobalTemporaryTableStrategy() { + return StandardGlobalTemporaryTableStrategy.INSTANCE; + } + @Override public String getTemporaryTableCreateOptions() { - return "on commit delete rows"; + return StandardGlobalTemporaryTableStrategy.INSTANCE.getTemporaryTableCreateOptions(); } private final FirebirdIndexExporter indexExporter = new FirebirdIndexExporter( this ); diff --git a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/H2LegacyDialect.java b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/H2LegacyDialect.java index f4346d7ce937..091295f07581 100644 --- a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/H2LegacyDialect.java +++ b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/H2LegacyDialect.java @@ -34,8 +34,9 @@ import org.hibernate.dialect.sequence.H2V1SequenceSupport; import org.hibernate.dialect.sequence.H2V2SequenceSupport; import org.hibernate.dialect.sequence.SequenceSupport; -import org.hibernate.dialect.temptable.TemporaryTable; +import org.hibernate.dialect.temptable.StandardLocalTemporaryTableStrategy; import org.hibernate.dialect.temptable.TemporaryTableKind; +import org.hibernate.dialect.temptable.TemporaryTableStrategy; import org.hibernate.dialect.type.H2DurationIntervalSecondJdbcType; import org.hibernate.dialect.type.H2JsonArrayJdbcTypeConstructor; import org.hibernate.dialect.type.H2JsonJdbcType; @@ -787,30 +788,14 @@ public NullOrdering getNullOrdering() { public SqmMultiTableMutationStrategy getFallbackSqmMutationStrategy( EntityMappingType entityDescriptor, RuntimeModelCreationContext runtimeModelCreationContext) { - return new LocalTemporaryTableMutationStrategy( - TemporaryTable.createIdTable( - entityDescriptor, - basename -> TemporaryTable.ID_TABLE_PREFIX + basename, - this, - runtimeModelCreationContext - ), - runtimeModelCreationContext.getSessionFactory() - ); + return new LocalTemporaryTableMutationStrategy( entityDescriptor, runtimeModelCreationContext ); } @Override public SqmMultiTableInsertStrategy getFallbackSqmInsertStrategy( EntityMappingType entityDescriptor, RuntimeModelCreationContext runtimeModelCreationContext) { - return new LocalTemporaryTableInsertStrategy( - TemporaryTable.createEntityTable( - entityDescriptor, - name -> TemporaryTable.ENTITY_TABLE_PREFIX + name, - this, - runtimeModelCreationContext - ), - runtimeModelCreationContext.getSessionFactory() - ); + return new LocalTemporaryTableInsertStrategy( entityDescriptor, runtimeModelCreationContext ); } @Override @@ -818,9 +803,14 @@ public TemporaryTableKind getSupportedTemporaryTableKind() { return TemporaryTableKind.LOCAL; } + @Override + public TemporaryTableStrategy getLocalTemporaryTableStrategy() { + return StandardLocalTemporaryTableStrategy.INSTANCE; + } + @Override public BeforeUseAction getTemporaryTableBeforeUseAction() { - return BeforeUseAction.CREATE; + return StandardLocalTemporaryTableStrategy.INSTANCE.getTemporaryTableBeforeUseAction(); } @Override diff --git a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/HANALegacyDialect.java b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/HANALegacyDialect.java index b348faf80eab..a213bca84000 100644 --- a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/HANALegacyDialect.java +++ b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/HANALegacyDialect.java @@ -34,8 +34,9 @@ import org.hibernate.dialect.sequence.HANASequenceSupport; import org.hibernate.dialect.sequence.SequenceSupport; import org.hibernate.dialect.sql.ast.HANASqlAstTranslator; -import org.hibernate.dialect.temptable.TemporaryTable; +import org.hibernate.dialect.temptable.HANAGlobalTemporaryTableStrategy; import org.hibernate.dialect.temptable.TemporaryTableKind; +import org.hibernate.dialect.temptable.TemporaryTableStrategy; import org.hibernate.engine.config.spi.ConfigurationService; import org.hibernate.engine.config.spi.StandardConverters; import org.hibernate.engine.jdbc.BinaryStream; @@ -1965,30 +1966,14 @@ public int getMaxLobPrefetchSize() { public SqmMultiTableMutationStrategy getFallbackSqmMutationStrategy( EntityMappingType entityDescriptor, RuntimeModelCreationContext runtimeModelCreationContext) { - return new GlobalTemporaryTableMutationStrategy( - TemporaryTable.createIdTable( - entityDescriptor, - basename -> TemporaryTable.ID_TABLE_PREFIX + basename, - this, - runtimeModelCreationContext - ), - runtimeModelCreationContext.getSessionFactory() - ); + return new GlobalTemporaryTableMutationStrategy( entityDescriptor, runtimeModelCreationContext ); } @Override public SqmMultiTableInsertStrategy getFallbackSqmInsertStrategy( EntityMappingType entityDescriptor, RuntimeModelCreationContext runtimeModelCreationContext) { - return new GlobalTemporaryTableInsertStrategy( - TemporaryTable.createEntityTable( - entityDescriptor, - name -> TemporaryTable.ENTITY_TABLE_PREFIX + name, - this, - runtimeModelCreationContext - ), - runtimeModelCreationContext.getSessionFactory() - ); + return new GlobalTemporaryTableInsertStrategy( entityDescriptor, runtimeModelCreationContext ); } @Override @@ -1996,19 +1981,24 @@ public TemporaryTableKind getSupportedTemporaryTableKind() { return TemporaryTableKind.GLOBAL; } + @Override + public TemporaryTableStrategy getGlobalTemporaryTableStrategy() { + return HANAGlobalTemporaryTableStrategy.INSTANCE; + } + @Override public String getTemporaryTableCreateOptions() { - return "on commit delete rows"; + return HANAGlobalTemporaryTableStrategy.INSTANCE.getTemporaryTableCreateOptions(); } @Override public String getTemporaryTableCreateCommand() { - return "create global temporary row table"; + return HANAGlobalTemporaryTableStrategy.INSTANCE.getTemporaryTableCreateCommand(); } @Override public String getTemporaryTableTruncateCommand() { - return "truncate table"; + return HANAGlobalTemporaryTableStrategy.INSTANCE.getTemporaryTableTruncateCommand(); } @Override diff --git a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/HSQLLegacyDialect.java b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/HSQLLegacyDialect.java index dbd97c16d383..737bf0a109e6 100644 --- a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/HSQLLegacyDialect.java +++ b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/HSQLLegacyDialect.java @@ -34,8 +34,10 @@ import org.hibernate.dialect.pagination.OffsetFetchLimitHandler; import org.hibernate.dialect.sequence.HSQLSequenceSupport; import org.hibernate.dialect.sequence.SequenceSupport; -import org.hibernate.dialect.temptable.TemporaryTable; +import org.hibernate.dialect.temptable.HSQLLocalTemporaryTableStrategy; +import org.hibernate.dialect.temptable.StandardGlobalTemporaryTableStrategy; import org.hibernate.dialect.temptable.TemporaryTableKind; +import org.hibernate.dialect.temptable.TemporaryTableStrategy; import org.hibernate.dialect.unique.CreateTableUniqueDelegate; import org.hibernate.dialect.unique.UniqueDelegate; import org.hibernate.engine.jdbc.dialect.spi.DialectResolutionInfo; @@ -648,28 +650,10 @@ public SqmMultiTableMutationStrategy getFallbackSqmMutationStrategy( // can happen in the middle of a transaction if ( getVersion().isBefore( 2 ) ) { - return new GlobalTemporaryTableMutationStrategy( - TemporaryTable.createIdTable( - rootEntityDescriptor, - basename -> TemporaryTable.ID_TABLE_PREFIX + basename, - this, - runtimeModelCreationContext - ), - runtimeModelCreationContext.getSessionFactory() - ); + return new GlobalTemporaryTableMutationStrategy( rootEntityDescriptor, runtimeModelCreationContext ); } else { - return new LocalTemporaryTableMutationStrategy( - // With HSQLDB 2.0, the table name is qualified with MODULE to assist the drop - // statement (in-case there is a global name beginning with HT_) - TemporaryTable.createIdTable( - rootEntityDescriptor, - basename -> "MODULE." + TemporaryTable.ID_TABLE_PREFIX + basename, - this, - runtimeModelCreationContext - ), - runtimeModelCreationContext.getSessionFactory() - ); + return new LocalTemporaryTableMutationStrategy( rootEntityDescriptor, runtimeModelCreationContext ); } } @@ -688,28 +672,10 @@ public SqmMultiTableInsertStrategy getFallbackSqmInsertStrategy( // can happen in the middle of a transaction if ( getVersion().isBefore( 2 ) ) { - return new GlobalTemporaryTableInsertStrategy( - TemporaryTable.createEntityTable( - rootEntityDescriptor, - name -> TemporaryTable.ENTITY_TABLE_PREFIX + name, - this, - runtimeModelCreationContext - ), - runtimeModelCreationContext.getSessionFactory() - ); + return new GlobalTemporaryTableInsertStrategy( rootEntityDescriptor, runtimeModelCreationContext ); } else { - return new LocalTemporaryTableInsertStrategy( - // With HSQLDB 2.0, the table name is qualified with MODULE to assist the drop - // statement (in-case there is a global name beginning with HT_) - TemporaryTable.createEntityTable( - rootEntityDescriptor, - name -> "MODULE." + TemporaryTable.ENTITY_TABLE_PREFIX + name, - this, - runtimeModelCreationContext - ), - runtimeModelCreationContext.getSessionFactory() - ); + return new LocalTemporaryTableInsertStrategy( rootEntityDescriptor, runtimeModelCreationContext ); } } @@ -718,21 +684,32 @@ public TemporaryTableKind getSupportedTemporaryTableKind() { return getVersion().isBefore( 2 ) ? TemporaryTableKind.GLOBAL : TemporaryTableKind.LOCAL; } + @Override + public TemporaryTableStrategy getGlobalTemporaryTableStrategy() { + return StandardGlobalTemporaryTableStrategy.INSTANCE; + } + + @Override + public TemporaryTableStrategy getLocalTemporaryTableStrategy() { + return HSQLLocalTemporaryTableStrategy.INSTANCE; + } + @Override public String getTemporaryTableCreateCommand() { - return getVersion().isBefore( 2 ) ? super.getTemporaryTableCreateCommand() : "declare local temporary table"; + return (getVersion().isBefore( 2 ) ? StandardGlobalTemporaryTableStrategy.INSTANCE + : HSQLLocalTemporaryTableStrategy.INSTANCE).getTemporaryTableCreateCommand(); } @Override public AfterUseAction getTemporaryTableAfterUseAction() { - // Version 1.8 GLOBAL TEMPORARY table definitions persist beyond the end - // of the session (by default, data is cleared at commit). - return getVersion().isBefore( 2 ) ? AfterUseAction.CLEAN : AfterUseAction.DROP; + return (getVersion().isBefore( 2 ) ? StandardGlobalTemporaryTableStrategy.INSTANCE + : HSQLLocalTemporaryTableStrategy.INSTANCE).getTemporaryTableAfterUseAction(); } @Override public BeforeUseAction getTemporaryTableBeforeUseAction() { - return getVersion().isBefore( 2 ) ? BeforeUseAction.NONE : BeforeUseAction.CREATE; + return (getVersion().isBefore( 2 ) ? StandardGlobalTemporaryTableStrategy.INSTANCE + : HSQLLocalTemporaryTableStrategy.INSTANCE).getTemporaryTableBeforeUseAction(); } // current timestamp support ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/InformixDialect.java b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/InformixDialect.java index bdf69569c39a..57d5231c4e04 100644 --- a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/InformixDialect.java +++ b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/InformixDialect.java @@ -30,6 +30,8 @@ import org.hibernate.dialect.SelectItemReferenceStrategy; import org.hibernate.dialect.function.InsertSubstringOverlayEmulation; import org.hibernate.dialect.function.TrimFunction; +import org.hibernate.community.dialect.temptable.InformixLocalTemporaryTableStrategy; +import org.hibernate.dialect.temptable.TemporaryTableStrategy; import org.hibernate.engine.jdbc.env.spi.IdentifierCaseStrategy; import org.hibernate.engine.jdbc.env.spi.IdentifierHelper; import org.hibernate.engine.jdbc.env.spi.IdentifierHelperBuilder; @@ -49,7 +51,6 @@ import org.hibernate.dialect.identity.IdentityColumnSupport; import org.hibernate.dialect.pagination.LimitHandler; import org.hibernate.dialect.sequence.SequenceSupport; -import org.hibernate.dialect.temptable.TemporaryTable; import org.hibernate.dialect.temptable.TemporaryTableKind; import org.hibernate.dialect.unique.UniqueDelegate; import org.hibernate.engine.jdbc.Size; @@ -846,30 +847,14 @@ public String getCatalogSeparator() { public SqmMultiTableMutationStrategy getFallbackSqmMutationStrategy( EntityMappingType rootEntityDescriptor, RuntimeModelCreationContext runtimeModelCreationContext) { - return new LocalTemporaryTableMutationStrategy( - TemporaryTable.createIdTable( - rootEntityDescriptor, - basename -> TemporaryTable.ID_TABLE_PREFIX + basename, - this, - runtimeModelCreationContext - ), - runtimeModelCreationContext.getSessionFactory() - ); + return new LocalTemporaryTableMutationStrategy( rootEntityDescriptor, runtimeModelCreationContext ); } @Override public SqmMultiTableInsertStrategy getFallbackSqmInsertStrategy( EntityMappingType rootEntityDescriptor, RuntimeModelCreationContext runtimeModelCreationContext) { - return new LocalTemporaryTableInsertStrategy( - TemporaryTable.createEntityTable( - rootEntityDescriptor, - name -> TemporaryTable.ENTITY_TABLE_PREFIX + name, - this, - runtimeModelCreationContext - ), - runtimeModelCreationContext.getSessionFactory() - ); + return new LocalTemporaryTableInsertStrategy( rootEntityDescriptor, runtimeModelCreationContext ); } @Override @@ -877,24 +862,29 @@ public TemporaryTableKind getSupportedTemporaryTableKind() { return TemporaryTableKind.LOCAL; } + @Override + public TemporaryTableStrategy getLocalTemporaryTableStrategy() { + return InformixLocalTemporaryTableStrategy.INSTANCE; + } + @Override public String getTemporaryTableCreateOptions() { - return "with no log"; + return InformixLocalTemporaryTableStrategy.INSTANCE.getTemporaryTableCreateOptions(); } @Override public String getTemporaryTableCreateCommand() { - return "create temp table"; + return InformixLocalTemporaryTableStrategy.INSTANCE.getTemporaryTableCreateCommand(); } @Override public AfterUseAction getTemporaryTableAfterUseAction() { - return AfterUseAction.NONE; + return InformixLocalTemporaryTableStrategy.INSTANCE.getTemporaryTableAfterUseAction(); } @Override public BeforeUseAction getTemporaryTableBeforeUseAction() { - return BeforeUseAction.CREATE; + return InformixLocalTemporaryTableStrategy.INSTANCE.getTemporaryTableBeforeUseAction(); } @Override diff --git a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/IngresDialect.java b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/IngresDialect.java index 039d6dc1cae7..163323cf63e8 100644 --- a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/IngresDialect.java +++ b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/IngresDialect.java @@ -4,6 +4,8 @@ */ package org.hibernate.community.dialect; +import java.sql.Types; + import jakarta.persistence.TemporalType; import org.hibernate.LockOptions; import org.hibernate.boot.model.FunctionContributions; @@ -23,8 +25,9 @@ import org.hibernate.dialect.pagination.LimitHandler; import org.hibernate.dialect.sequence.ANSISequenceSupport; import org.hibernate.dialect.sequence.SequenceSupport; -import org.hibernate.dialect.temptable.TemporaryTable; +import org.hibernate.community.dialect.temptable.IngresGlobalTemporaryTableStrategy; import org.hibernate.dialect.temptable.TemporaryTableKind; +import org.hibernate.dialect.temptable.TemporaryTableStrategy; import org.hibernate.engine.jdbc.dialect.spi.DialectResolutionInfo; import org.hibernate.engine.spi.LoadQueryInfluencers; import org.hibernate.engine.spi.SessionFactoryImplementor; @@ -62,8 +65,6 @@ import org.hibernate.type.descriptor.jdbc.JdbcType; import org.hibernate.type.descriptor.jdbc.spi.JdbcTypeRegistry; -import java.sql.Types; - import static org.hibernate.query.sqm.produce.function.FunctionParameterType.INTEGER; import static org.hibernate.query.sqm.produce.function.FunctionParameterType.STRING; import static org.hibernate.sql.ast.internal.NonLockingClauseStrategy.NON_CLAUSE_STRATEGY; @@ -454,30 +455,14 @@ public boolean supportsCurrentTimestampSelection() { public SqmMultiTableMutationStrategy getFallbackSqmMutationStrategy( EntityMappingType rootEntityDescriptor, RuntimeModelCreationContext runtimeModelCreationContext) { - return new GlobalTemporaryTableMutationStrategy( - TemporaryTable.createIdTable( - rootEntityDescriptor, - name -> "session." + TemporaryTable.ID_TABLE_PREFIX + name, - this, - runtimeModelCreationContext - ), - runtimeModelCreationContext.getSessionFactory() - ); + return new GlobalTemporaryTableMutationStrategy( rootEntityDescriptor, runtimeModelCreationContext ); } @Override public SqmMultiTableInsertStrategy getFallbackSqmInsertStrategy( EntityMappingType rootEntityDescriptor, RuntimeModelCreationContext runtimeModelCreationContext) { - return new GlobalTemporaryTableInsertStrategy( - TemporaryTable.createEntityTable( - rootEntityDescriptor, - name -> "session." + TemporaryTable.ENTITY_TABLE_PREFIX + name, - this, - runtimeModelCreationContext - ), - runtimeModelCreationContext.getSessionFactory() - ); + return new GlobalTemporaryTableInsertStrategy( rootEntityDescriptor, runtimeModelCreationContext ); } @Override @@ -485,14 +470,19 @@ public TemporaryTableKind getSupportedTemporaryTableKind() { return TemporaryTableKind.GLOBAL; } + @Override + public TemporaryTableStrategy getGlobalTemporaryTableStrategy() { + return IngresGlobalTemporaryTableStrategy.INSTANCE; + } + @Override public String getTemporaryTableCreateOptions() { - return "on commit preserve rows with norecovery"; + return IngresGlobalTemporaryTableStrategy.INSTANCE.getTemporaryTableCreateOptions(); } @Override public String getTemporaryTableCreateCommand() { - return "declare global temporary table"; + return IngresGlobalTemporaryTableStrategy.INSTANCE.getTemporaryTableCreateCommand(); } // union subclass support ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/MaxDBDialect.java b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/MaxDBDialect.java index 466adcbbfe39..3f5fe8b6de6e 100644 --- a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/MaxDBDialect.java +++ b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/MaxDBDialect.java @@ -18,8 +18,9 @@ import org.hibernate.dialect.pagination.LimitHandler; import org.hibernate.dialect.pagination.LimitLimitHandler; import org.hibernate.dialect.sequence.SequenceSupport; -import org.hibernate.dialect.temptable.TemporaryTable; +import org.hibernate.community.dialect.temptable.MaxDBLocalTemporaryTableStrategy; import org.hibernate.dialect.temptable.TemporaryTableKind; +import org.hibernate.dialect.temptable.TemporaryTableStrategy; import org.hibernate.engine.jdbc.dialect.spi.DialectResolutionInfo; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.metamodel.mapping.EntityMappingType; @@ -285,40 +286,29 @@ public boolean supportsOffsetInSubquery() { public SqmMultiTableMutationStrategy getFallbackSqmMutationStrategy( EntityMappingType rootEntityDescriptor, RuntimeModelCreationContext runtimeModelCreationContext) { - return new LocalTemporaryTableMutationStrategy( - TemporaryTable.createIdTable( - rootEntityDescriptor, - basename -> "temp." + TemporaryTable.ID_TABLE_PREFIX + basename, - this, - runtimeModelCreationContext - ), - runtimeModelCreationContext.getSessionFactory() - ); + return new LocalTemporaryTableMutationStrategy( rootEntityDescriptor, runtimeModelCreationContext ); } @Override public SqmMultiTableInsertStrategy getFallbackSqmInsertStrategy( EntityMappingType rootEntityDescriptor, RuntimeModelCreationContext runtimeModelCreationContext) { - return new LocalTemporaryTableInsertStrategy( - TemporaryTable.createEntityTable( - rootEntityDescriptor, - name -> "temp." + TemporaryTable.ENTITY_TABLE_PREFIX + name, - this, - runtimeModelCreationContext - ), - runtimeModelCreationContext.getSessionFactory() - ); + return new LocalTemporaryTableInsertStrategy( rootEntityDescriptor, runtimeModelCreationContext ); + } + + @Override + public TemporaryTableStrategy getLocalTemporaryTableStrategy() { + return MaxDBLocalTemporaryTableStrategy.INSTANCE; } @Override public BeforeUseAction getTemporaryTableBeforeUseAction() { - return BeforeUseAction.CREATE; + return MaxDBLocalTemporaryTableStrategy.INSTANCE.getTemporaryTableBeforeUseAction(); } @Override public AfterUseAction getTemporaryTableAfterUseAction() { - return AfterUseAction.DROP; + return MaxDBLocalTemporaryTableStrategy.INSTANCE.getTemporaryTableAfterUseAction(); } @Override @@ -328,7 +318,7 @@ public TemporaryTableKind getSupportedTemporaryTableKind() { @Override public String getTemporaryTableCreateOptions() { - return "ignore rollback"; + return MaxDBLocalTemporaryTableStrategy.INSTANCE.getTemporaryTableCreateOptions(); } @Override diff --git a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/MySQLLegacyDialect.java b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/MySQLLegacyDialect.java index a2a9c7480e64..130f86be7bef 100644 --- a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/MySQLLegacyDialect.java +++ b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/MySQLLegacyDialect.java @@ -34,8 +34,9 @@ import org.hibernate.dialect.pagination.LimitLimitHandler; import org.hibernate.dialect.sequence.NoSequenceSupport; import org.hibernate.dialect.sequence.SequenceSupport; -import org.hibernate.dialect.temptable.TemporaryTable; +import org.hibernate.dialect.temptable.MySQLLocalTemporaryTableStrategy; import org.hibernate.dialect.temptable.TemporaryTableKind; +import org.hibernate.dialect.temptable.TemporaryTableStrategy; import org.hibernate.dialect.type.MySQLCastingJsonArrayJdbcTypeConstructor; import org.hibernate.dialect.type.MySQLCastingJsonJdbcType; import org.hibernate.engine.jdbc.Size; @@ -1056,32 +1057,19 @@ public NullOrdering getNullOrdering() { public SqmMultiTableMutationStrategy getFallbackSqmMutationStrategy( EntityMappingType rootEntityDescriptor, RuntimeModelCreationContext runtimeModelCreationContext) { - - return new LocalTemporaryTableMutationStrategy( - TemporaryTable.createIdTable( - rootEntityDescriptor, - basename -> TemporaryTable.ID_TABLE_PREFIX + basename, - this, - runtimeModelCreationContext - ), - runtimeModelCreationContext.getSessionFactory() - ); + return new LocalTemporaryTableMutationStrategy( rootEntityDescriptor, runtimeModelCreationContext ); } @Override public SqmMultiTableInsertStrategy getFallbackSqmInsertStrategy( EntityMappingType rootEntityDescriptor, RuntimeModelCreationContext runtimeModelCreationContext) { + return new LocalTemporaryTableInsertStrategy( rootEntityDescriptor, runtimeModelCreationContext ); + } - return new LocalTemporaryTableInsertStrategy( - TemporaryTable.createEntityTable( - rootEntityDescriptor, - name -> TemporaryTable.ENTITY_TABLE_PREFIX + name, - this, - runtimeModelCreationContext - ), - runtimeModelCreationContext.getSessionFactory() - ); + @Override + public TemporaryTableStrategy getLocalTemporaryTableStrategy() { + return MySQLLocalTemporaryTableStrategy.INSTANCE; } @Override @@ -1091,22 +1079,22 @@ public TemporaryTableKind getSupportedTemporaryTableKind() { @Override public String getTemporaryTableCreateCommand() { - return "create temporary table if not exists"; + return MySQLLocalTemporaryTableStrategy.INSTANCE.getTemporaryTableCreateCommand(); } @Override public String getTemporaryTableDropCommand() { - return "drop temporary table"; + return MySQLLocalTemporaryTableStrategy.INSTANCE.getTemporaryTableDropCommand(); } @Override public AfterUseAction getTemporaryTableAfterUseAction() { - return AfterUseAction.DROP; + return MySQLLocalTemporaryTableStrategy.INSTANCE.getTemporaryTableAfterUseAction(); } @Override public BeforeUseAction getTemporaryTableBeforeUseAction() { - return BeforeUseAction.CREATE; + return MySQLLocalTemporaryTableStrategy.INSTANCE.getTemporaryTableBeforeUseAction(); } @Override diff --git a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/OracleLegacyDialect.java b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/OracleLegacyDialect.java index 7b7a26e62969..91c59b8af375 100644 --- a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/OracleLegacyDialect.java +++ b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/OracleLegacyDialect.java @@ -29,6 +29,14 @@ import org.hibernate.dialect.DatabaseVersion; import org.hibernate.dialect.Dialect; import org.hibernate.dialect.DmlTargetColumnQualifierSupport; +import org.hibernate.dialect.temptable.OracleLocalTemporaryTableStrategy; +import org.hibernate.dialect.temptable.StandardGlobalTemporaryTableStrategy; +import org.hibernate.dialect.temptable.TemporaryTableStrategy; +import org.hibernate.dialect.type.OracleBooleanJdbcType; +import org.hibernate.dialect.type.OracleJdbcHelper; +import org.hibernate.dialect.type.OracleJsonArrayJdbcTypeConstructor; +import org.hibernate.dialect.type.OracleJsonJdbcType; +import org.hibernate.dialect.type.OracleReflectionStructJdbcType; import org.hibernate.dialect.OracleTypes; import org.hibernate.dialect.Replacer; import org.hibernate.dialect.TimeZoneSupport; @@ -46,13 +54,7 @@ import org.hibernate.dialect.pagination.Oracle12LimitHandler; import org.hibernate.dialect.sequence.OracleSequenceSupport; import org.hibernate.dialect.sequence.SequenceSupport; -import org.hibernate.dialect.temptable.TemporaryTable; import org.hibernate.dialect.temptable.TemporaryTableKind; -import org.hibernate.dialect.type.OracleBooleanJdbcType; -import org.hibernate.dialect.type.OracleJdbcHelper; -import org.hibernate.dialect.type.OracleJsonArrayJdbcTypeConstructor; -import org.hibernate.dialect.type.OracleJsonJdbcType; -import org.hibernate.dialect.type.OracleReflectionStructJdbcType; import org.hibernate.dialect.type.OracleUserDefinedTypeExporter; import org.hibernate.dialect.type.OracleXmlJdbcType; import org.hibernate.dialect.unique.CreateTableUniqueDelegate; @@ -120,18 +122,6 @@ import org.hibernate.type.descriptor.sql.spi.DdlTypeRegistry; import org.hibernate.type.spi.TypeConfiguration; -import java.sql.CallableStatement; -import java.sql.DatabaseMetaData; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.sql.Types; -import java.time.temporal.ChronoField; -import java.time.temporal.TemporalAccessor; -import java.util.Locale; -import java.util.TimeZone; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - import static java.lang.String.join; import static java.util.regex.Pattern.CASE_INSENSITIVE; import static org.hibernate.exception.spi.TemplatedViolatedConstraintNameExtractor.extractUsingTemplate; @@ -1229,34 +1219,28 @@ public boolean isEmptyStringTreatedAsNull() { return true; } + @Override + public TemporaryTableStrategy getLocalTemporaryTableStrategy() { + return OracleLocalTemporaryTableStrategy.INSTANCE; + } + + @Override + public TemporaryTableStrategy getGlobalTemporaryTableStrategy() { + return StandardGlobalTemporaryTableStrategy.INSTANCE; + } + @Override public SqmMultiTableMutationStrategy getFallbackSqmMutationStrategy( EntityMappingType rootEntityDescriptor, RuntimeModelCreationContext runtimeModelCreationContext) { - return new GlobalTemporaryTableMutationStrategy( - TemporaryTable.createIdTable( - rootEntityDescriptor, - basename -> TemporaryTable.ID_TABLE_PREFIX + basename, - this, - runtimeModelCreationContext - ), - runtimeModelCreationContext.getSessionFactory() - ); + return new GlobalTemporaryTableMutationStrategy( rootEntityDescriptor, runtimeModelCreationContext ); } @Override public SqmMultiTableInsertStrategy getFallbackSqmInsertStrategy( EntityMappingType rootEntityDescriptor, RuntimeModelCreationContext runtimeModelCreationContext) { - return new GlobalTemporaryTableInsertStrategy( - TemporaryTable.createEntityTable( - rootEntityDescriptor, - name -> TemporaryTable.ENTITY_TABLE_PREFIX + name, - this, - runtimeModelCreationContext - ), - runtimeModelCreationContext.getSessionFactory() - ); + return new GlobalTemporaryTableInsertStrategy( rootEntityDescriptor, runtimeModelCreationContext ); } @Override @@ -1266,7 +1250,7 @@ public TemporaryTableKind getSupportedTemporaryTableKind() { @Override public String getTemporaryTableCreateOptions() { - return "on commit delete rows"; + return StandardGlobalTemporaryTableStrategy.INSTANCE.getTemporaryTableCreateOptions(); } /** diff --git a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/PostgreSQLLegacyDialect.java b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/PostgreSQLLegacyDialect.java index 10a8bd9944f9..91debe50d7dd 100644 --- a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/PostgreSQLLegacyDialect.java +++ b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/PostgreSQLLegacyDialect.java @@ -43,6 +43,8 @@ import org.hibernate.dialect.pagination.OffsetFetchLimitHandler; import org.hibernate.dialect.sequence.PostgreSQLSequenceSupport; import org.hibernate.dialect.sequence.SequenceSupport; +import org.hibernate.dialect.temptable.StandardLocalTemporaryTableStrategy; +import org.hibernate.dialect.temptable.TemporaryTableStrategy; import org.hibernate.dialect.type.PgJdbcHelper; import org.hibernate.dialect.type.PostgreSQLArrayJdbcTypeConstructor; import org.hibernate.dialect.type.PostgreSQLCastingInetJdbcType; @@ -1025,6 +1027,11 @@ public SqmMultiTableInsertStrategy getFallbackSqmInsertStrategy( return new CteInsertStrategy( rootEntityDescriptor, runtimeModelCreationContext ); } + @Override + public TemporaryTableStrategy getLocalTemporaryTableStrategy() { + return StandardLocalTemporaryTableStrategy.INSTANCE; + } + @Override public SqlAstTranslatorFactory getSqlAstTranslatorFactory() { return new StandardSqlAstTranslatorFactory() { diff --git a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/SingleStoreDialect.java b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/SingleStoreDialect.java index 404ee2eeaea0..a1ed90516f95 100644 --- a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/SingleStoreDialect.java +++ b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/SingleStoreDialect.java @@ -45,8 +45,9 @@ import org.hibernate.dialect.lock.spi.OuterJoinLockingType; import org.hibernate.dialect.pagination.LimitHandler; import org.hibernate.dialect.pagination.LimitLimitHandler; -import org.hibernate.dialect.temptable.TemporaryTable; +import org.hibernate.community.dialect.temptable.SingleStoreLocalTemporaryTableStrategy; import org.hibernate.dialect.temptable.TemporaryTableKind; +import org.hibernate.dialect.temptable.TemporaryTableStrategy; import org.hibernate.dialect.unique.UniqueDelegate; import org.hibernate.engine.jdbc.Size; import org.hibernate.engine.jdbc.dialect.spi.DialectResolutionInfo; @@ -1083,26 +1084,16 @@ public NullOrdering getNullOrdering() { @Override public SqmMultiTableMutationStrategy getFallbackSqmMutationStrategy( - EntityMappingType rootEntityDescriptor, RuntimeModelCreationContext runtimeModelCreationContext) { - - return new LocalTemporaryTableMutationStrategy( TemporaryTable.createIdTable( - rootEntityDescriptor, - basename -> TemporaryTable.ID_TABLE_PREFIX + basename, - this, - runtimeModelCreationContext - ), runtimeModelCreationContext.getSessionFactory() ); + EntityMappingType rootEntityDescriptor, + RuntimeModelCreationContext runtimeModelCreationContext) { + return new LocalTemporaryTableMutationStrategy( rootEntityDescriptor, runtimeModelCreationContext ); } @Override public SqmMultiTableInsertStrategy getFallbackSqmInsertStrategy( - EntityMappingType rootEntityDescriptor, RuntimeModelCreationContext runtimeModelCreationContext) { - - return new LocalTemporaryTableInsertStrategy( TemporaryTable.createEntityTable( - rootEntityDescriptor, - name -> TemporaryTable.ENTITY_TABLE_PREFIX + name, - this, - runtimeModelCreationContext - ), runtimeModelCreationContext.getSessionFactory() ); + EntityMappingType rootEntityDescriptor, + RuntimeModelCreationContext runtimeModelCreationContext) { + return new LocalTemporaryTableInsertStrategy( rootEntityDescriptor, runtimeModelCreationContext ); } @Override @@ -1110,26 +1101,29 @@ public TemporaryTableKind getSupportedTemporaryTableKind() { return TemporaryTableKind.LOCAL; } + @Override + public TemporaryTableStrategy getLocalTemporaryTableStrategy() { + return SingleStoreLocalTemporaryTableStrategy.INSTANCE; + } + @Override public String getTemporaryTableCreateCommand() { - return "create temporary table if not exists"; + return SingleStoreLocalTemporaryTableStrategy.INSTANCE.getTemporaryTableCreateCommand(); } - //SingleStore throws an error on drop temporary table if there are uncommited statements within transaction. - //Just 'drop table' statement causes implicit commit, so using 'delete from'. @Override public String getTemporaryTableDropCommand() { - return "delete from"; + return SingleStoreLocalTemporaryTableStrategy.INSTANCE.getTemporaryTableDropCommand(); } @Override public AfterUseAction getTemporaryTableAfterUseAction() { - return AfterUseAction.DROP; + return SingleStoreLocalTemporaryTableStrategy.INSTANCE.getTemporaryTableAfterUseAction(); } @Override public BeforeUseAction getTemporaryTableBeforeUseAction() { - return BeforeUseAction.CREATE; + return SingleStoreLocalTemporaryTableStrategy.INSTANCE.getTemporaryTableBeforeUseAction(); } @Override diff --git a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/TeradataDialect.java b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/TeradataDialect.java index 6a5f02aea8cb..fa76a017e3cd 100644 --- a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/TeradataDialect.java +++ b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/TeradataDialect.java @@ -22,8 +22,9 @@ import org.hibernate.dialect.lock.spi.LockingSupport; import org.hibernate.dialect.pagination.LimitHandler; import org.hibernate.dialect.pagination.TopLimitHandler; -import org.hibernate.dialect.temptable.TemporaryTable; import org.hibernate.dialect.temptable.TemporaryTableKind; +import org.hibernate.dialect.temptable.TemporaryTableStrategy; +import org.hibernate.community.dialect.temptable.TeradataGlobalTemporaryTableStrategy; import org.hibernate.engine.jdbc.dialect.spi.DialectResolutionInfo; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.exception.spi.TemplatedViolatedConstraintNameExtractor; @@ -327,30 +328,14 @@ public String getAddColumnString() { public SqmMultiTableMutationStrategy getFallbackSqmMutationStrategy( EntityMappingType rootEntityDescriptor, RuntimeModelCreationContext runtimeModelCreationContext) { - return new GlobalTemporaryTableMutationStrategy( - TemporaryTable.createIdTable( - rootEntityDescriptor, - basename -> TemporaryTable.ID_TABLE_PREFIX + basename, - this, - runtimeModelCreationContext - ), - runtimeModelCreationContext.getSessionFactory() - ); + return new GlobalTemporaryTableMutationStrategy( rootEntityDescriptor, runtimeModelCreationContext ); } @Override public SqmMultiTableInsertStrategy getFallbackSqmInsertStrategy( EntityMappingType rootEntityDescriptor, RuntimeModelCreationContext runtimeModelCreationContext) { - return new GlobalTemporaryTableInsertStrategy( - TemporaryTable.createEntityTable( - rootEntityDescriptor, - name -> TemporaryTable.ENTITY_TABLE_PREFIX + name, - this, - runtimeModelCreationContext - ), - runtimeModelCreationContext.getSessionFactory() - ); + return new GlobalTemporaryTableInsertStrategy( rootEntityDescriptor, runtimeModelCreationContext ); } @Override @@ -358,9 +343,14 @@ public TemporaryTableKind getSupportedTemporaryTableKind() { return TemporaryTableKind.GLOBAL; } + @Override + public TemporaryTableStrategy getGlobalTemporaryTableStrategy() { + return TeradataGlobalTemporaryTableStrategy.INSTANCE; + } + @Override public String getTemporaryTableCreateOptions() { - return "on commit preserve rows"; + return TeradataGlobalTemporaryTableStrategy.INSTANCE.getTemporaryTableCreateOptions(); } @Override diff --git a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/TimesTenDialect.java b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/TimesTenDialect.java index 9d9df8d5554c..4c5123f651df 100644 --- a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/TimesTenDialect.java +++ b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/TimesTenDialect.java @@ -22,8 +22,9 @@ import org.hibernate.dialect.lock.spi.LockingSupport; import org.hibernate.dialect.pagination.LimitHandler; import org.hibernate.dialect.sequence.SequenceSupport; -import org.hibernate.dialect.temptable.TemporaryTable; +import org.hibernate.dialect.temptable.StandardGlobalTemporaryTableStrategy; import org.hibernate.dialect.temptable.TemporaryTableKind; +import org.hibernate.dialect.temptable.TemporaryTableStrategy; import org.hibernate.engine.jdbc.dialect.spi.DialectResolutionInfo; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.metamodel.mapping.EntityMappingType; @@ -354,30 +355,14 @@ public boolean supportsCrossJoin() { public SqmMultiTableMutationStrategy getFallbackSqmMutationStrategy( EntityMappingType rootEntityDescriptor, RuntimeModelCreationContext runtimeModelCreationContext) { - return new GlobalTemporaryTableMutationStrategy( - TemporaryTable.createIdTable( - rootEntityDescriptor, - name -> TemporaryTable.ID_TABLE_PREFIX + name, - this, - runtimeModelCreationContext - ), - runtimeModelCreationContext.getSessionFactory() - ); + return new GlobalTemporaryTableMutationStrategy( rootEntityDescriptor, runtimeModelCreationContext ); } @Override public SqmMultiTableInsertStrategy getFallbackSqmInsertStrategy( EntityMappingType rootEntityDescriptor, RuntimeModelCreationContext runtimeModelCreationContext) { - return new GlobalTemporaryTableInsertStrategy( - TemporaryTable.createEntityTable( - rootEntityDescriptor, - name -> TemporaryTable.ENTITY_TABLE_PREFIX + name, - this, - runtimeModelCreationContext - ), - runtimeModelCreationContext.getSessionFactory() - ); + return new GlobalTemporaryTableInsertStrategy( rootEntityDescriptor, runtimeModelCreationContext ); } @Override @@ -385,9 +370,14 @@ public TemporaryTableKind getSupportedTemporaryTableKind() { return TemporaryTableKind.GLOBAL; } + @Override + public TemporaryTableStrategy getGlobalTemporaryTableStrategy() { + return StandardGlobalTemporaryTableStrategy.INSTANCE; + } + @Override public String getTemporaryTableCreateOptions() { - return "on commit delete rows"; + return StandardGlobalTemporaryTableStrategy.INSTANCE.getTemporaryTableCreateOptions(); } @Override diff --git a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/temptable/DB2LegacyLocalTemporaryTableStrategy.java b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/temptable/DB2LegacyLocalTemporaryTableStrategy.java new file mode 100644 index 000000000000..b6b8ef9d18eb --- /dev/null +++ b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/temptable/DB2LegacyLocalTemporaryTableStrategy.java @@ -0,0 +1,36 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.community.dialect.temptable; + +import org.hibernate.dialect.temptable.StandardLocalTemporaryTableStrategy; +import org.hibernate.query.sqm.mutation.spi.AfterUseAction; + +/** + * Legacy DB2 specific local temporary table strategy. + */ +public class DB2LegacyLocalTemporaryTableStrategy extends StandardLocalTemporaryTableStrategy { + + public static final DB2LegacyLocalTemporaryTableStrategy INSTANCE = new DB2LegacyLocalTemporaryTableStrategy(); + + @Override + public String adjustTemporaryTableName(String desiredTableName) { + return "session." + desiredTableName; + } + + @Override + public String getTemporaryTableCreateOptions() { + return "not logged"; + } + + @Override + public String getTemporaryTableCreateCommand() { + return "declare global temporary table"; + } + + @Override + public AfterUseAction getTemporaryTableAfterUseAction() { + return AfterUseAction.DROP; + } +} diff --git a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/temptable/DerbyLocalTemporaryTableStrategy.java b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/temptable/DerbyLocalTemporaryTableStrategy.java new file mode 100644 index 000000000000..4d96312fa126 --- /dev/null +++ b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/temptable/DerbyLocalTemporaryTableStrategy.java @@ -0,0 +1,36 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.community.dialect.temptable; + +import org.hibernate.dialect.temptable.StandardLocalTemporaryTableStrategy; + +/** + * Derby specific local temporary table strategy. + */ +public class DerbyLocalTemporaryTableStrategy extends StandardLocalTemporaryTableStrategy { + + public static final DerbyLocalTemporaryTableStrategy INSTANCE = new DerbyLocalTemporaryTableStrategy(); + + @Override + public String adjustTemporaryTableName(String desiredTableName) { + return "session." + desiredTableName; + } + + @Override + public String getTemporaryTableCreateOptions() { + return "not logged"; + } + + @Override + public String getTemporaryTableCreateCommand() { + return "declare global temporary table"; + } + + @Override + public boolean supportsTemporaryTablePrimaryKey() { + return false; + } + +} diff --git a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/temptable/InformixLocalTemporaryTableStrategy.java b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/temptable/InformixLocalTemporaryTableStrategy.java new file mode 100644 index 000000000000..404f0b870d30 --- /dev/null +++ b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/temptable/InformixLocalTemporaryTableStrategy.java @@ -0,0 +1,26 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.community.dialect.temptable; + +import org.hibernate.dialect.temptable.StandardLocalTemporaryTableStrategy; + +/** + * Informix specific local temporary table strategy. + */ +public class InformixLocalTemporaryTableStrategy extends StandardLocalTemporaryTableStrategy { + + public static final InformixLocalTemporaryTableStrategy INSTANCE = new InformixLocalTemporaryTableStrategy(); + + @Override + public String getTemporaryTableCreateOptions() { + return "with no log"; + } + + @Override + public String getTemporaryTableCreateCommand() { + return "create temp table"; + } + +} diff --git a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/temptable/IngresGlobalTemporaryTableStrategy.java b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/temptable/IngresGlobalTemporaryTableStrategy.java new file mode 100644 index 000000000000..41f174e2c973 --- /dev/null +++ b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/temptable/IngresGlobalTemporaryTableStrategy.java @@ -0,0 +1,30 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.community.dialect.temptable; + +import org.hibernate.dialect.temptable.StandardGlobalTemporaryTableStrategy; + +/** + * Ingres specific global temporary table strategy. + */ +public class IngresGlobalTemporaryTableStrategy extends StandardGlobalTemporaryTableStrategy { + + public static final IngresGlobalTemporaryTableStrategy INSTANCE = new IngresGlobalTemporaryTableStrategy(); + + @Override + public String adjustTemporaryTableName(String desiredTableName) { + return "session." + desiredTableName; + } + + @Override + public String getTemporaryTableCreateOptions() { + return "on commit preserve rows with norecovery"; + } + + @Override + public String getTemporaryTableCreateCommand() { + return "declare global temporary table"; + } +} diff --git a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/temptable/MaxDBLocalTemporaryTableStrategy.java b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/temptable/MaxDBLocalTemporaryTableStrategy.java new file mode 100644 index 000000000000..287508a9b805 --- /dev/null +++ b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/temptable/MaxDBLocalTemporaryTableStrategy.java @@ -0,0 +1,31 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.community.dialect.temptable; + +import org.hibernate.dialect.temptable.StandardLocalTemporaryTableStrategy; +import org.hibernate.query.sqm.mutation.spi.AfterUseAction; + +/** + * MaxDB specific local temporary table strategy. + */ +public class MaxDBLocalTemporaryTableStrategy extends StandardLocalTemporaryTableStrategy { + + public static final MaxDBLocalTemporaryTableStrategy INSTANCE = new MaxDBLocalTemporaryTableStrategy(); + + @Override + public String adjustTemporaryTableName(String desiredTableName) { + return "temp." + desiredTableName; + } + + @Override + public AfterUseAction getTemporaryTableAfterUseAction() { + return AfterUseAction.DROP; + } + + @Override + public String getTemporaryTableCreateOptions() { + return "ignore rollback"; + } +} diff --git a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/temptable/SingleStoreLocalTemporaryTableStrategy.java b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/temptable/SingleStoreLocalTemporaryTableStrategy.java new file mode 100644 index 000000000000..f40391922a6a --- /dev/null +++ b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/temptable/SingleStoreLocalTemporaryTableStrategy.java @@ -0,0 +1,33 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.community.dialect.temptable; + +import org.hibernate.dialect.temptable.StandardLocalTemporaryTableStrategy; +import org.hibernate.query.sqm.mutation.spi.AfterUseAction; + +/** + * SingleStore specific local temporary table strategy. + */ +public class SingleStoreLocalTemporaryTableStrategy extends StandardLocalTemporaryTableStrategy { + + public static final SingleStoreLocalTemporaryTableStrategy INSTANCE = new SingleStoreLocalTemporaryTableStrategy(); + + @Override + public String getTemporaryTableCreateCommand() { + return "create temporary table if not exists"; + } + + //SingleStore throws an error on drop temporary table if there are uncommited statements within transaction. + //Just 'drop table' statement causes implicit commit, so using 'delete from'. + @Override + public String getTemporaryTableDropCommand() { + return "delete from"; + } + + @Override + public AfterUseAction getTemporaryTableAfterUseAction() { + return AfterUseAction.DROP; + } +} diff --git a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/temptable/TeradataGlobalTemporaryTableStrategy.java b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/temptable/TeradataGlobalTemporaryTableStrategy.java new file mode 100644 index 000000000000..0cf2e1a75a65 --- /dev/null +++ b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/temptable/TeradataGlobalTemporaryTableStrategy.java @@ -0,0 +1,21 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.community.dialect.temptable; + +import org.hibernate.dialect.temptable.StandardGlobalTemporaryTableStrategy; + +/** + * Teradata specific global temporary table strategy. + */ +public class TeradataGlobalTemporaryTableStrategy extends StandardGlobalTemporaryTableStrategy { + + public static final TeradataGlobalTemporaryTableStrategy INSTANCE = new TeradataGlobalTemporaryTableStrategy(); + + @Override + public String getTemporaryTableCreateOptions() { + return "on commit preserve rows"; + } + +} diff --git a/hibernate-core/src/main/java/org/hibernate/boot/internal/SessionFactoryOptionsBuilder.java b/hibernate-core/src/main/java/org/hibernate/boot/internal/SessionFactoryOptionsBuilder.java index 43f693ffb806..db3031ebeb36 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/internal/SessionFactoryOptionsBuilder.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/internal/SessionFactoryOptionsBuilder.java @@ -30,7 +30,7 @@ import org.hibernate.SessionFactoryObserver; import org.hibernate.context.spi.TenantSchemaMapper; import org.hibernate.metamodel.mapping.EntityMappingType; -import org.hibernate.metamodel.mapping.internal.MappingModelCreationProcess; +import org.hibernate.metamodel.spi.RuntimeModelCreationContext; import org.hibernate.type.TimeZoneStorageStrategy; import org.hibernate.annotations.CacheLayout; import org.hibernate.boot.SchemaAutoTooling; @@ -631,7 +631,7 @@ private SqmMultiTableMutationStrategy resolveSqmMutationStrategy( else if ( parameterTypes.length == 0 ) { emptyConstructor = constructor; } - else if ( parameterTypes.length == 2 && parameterTypes[0] == EntityMappingType.class && parameterTypes[1] == MappingModelCreationProcess.class ) { + else if ( parameterTypes.length == 2 && parameterTypes[0] == EntityMappingType.class && parameterTypes[1] == RuntimeModelCreationContext.class ) { entityBasedConstructor = (Constructor) declaredConstructor; } } @@ -676,7 +676,7 @@ private Constructor resolveSqmMutationStrategyCon strategySelector.selectStrategyImplementor( SqmMultiTableMutationStrategy.class, strategyName ); for ( Constructor declaredConstructor : strategyClass.getDeclaredConstructors() ) { final Class[] parameterTypes = declaredConstructor.getParameterTypes(); - if ( parameterTypes.length == 2 && parameterTypes[0] == EntityMappingType.class && parameterTypes[1] == MappingModelCreationProcess.class ) { + if ( parameterTypes.length == 2 && parameterTypes[0] == EntityMappingType.class && parameterTypes[1] == RuntimeModelCreationContext.class ) { return (Constructor) declaredConstructor; } } @@ -700,6 +700,7 @@ private SqmMultiTableInsertStrategy resolveSqmInsertStrategy( strategyClass -> { Constructor dialectConstructor = null; Constructor emptyConstructor = null; + Constructor entityBasedConstructor = null; // todo (6.0) : formalize the allowed constructor parameterizations for ( var declaredConstructor : strategyClass.getDeclaredConstructors() ) { final var parameterTypes = declaredConstructor.getParameterTypes(); @@ -713,27 +714,35 @@ private SqmMultiTableInsertStrategy resolveSqmInsertStrategy( else if ( parameterTypes.length == 0 ) { emptyConstructor = constructor; } + else if ( parameterTypes.length == 2 && parameterTypes[0] == EntityMappingType.class && parameterTypes[1] == RuntimeModelCreationContext.class ) { + entityBasedConstructor = (Constructor) declaredConstructor; + } } - try { - if ( dialectConstructor != null ) { - return dialectConstructor.newInstance( - serviceRegistry.requireService( JdbcServices.class ).getDialect() - ); + if ( entityBasedConstructor == null ) { + try { + if ( dialectConstructor != null ) { + return dialectConstructor.newInstance( + serviceRegistry.requireService( JdbcServices.class ).getDialect() + ); + } + else if ( emptyConstructor != null ) { + return emptyConstructor.newInstance(); + } } - else if ( emptyConstructor != null ) { - return emptyConstructor.newInstance(); + catch (Exception e) { + throw new StrategySelectionException( + "Could not instantiate named strategy class [" + + strategyClass.getName() + "]", + e + ); } + throw new IllegalArgumentException( + "Cannot instantiate the class [" + strategyClass.getName() + "] because it does not have a constructor that accepts a dialect or an empty constructor" ); } - catch (Exception e) { - throw new StrategySelectionException( - "Could not instantiate named strategy class [" + strategyClass.getName() + "]", - e - ); + else { + return null; } - throw new IllegalArgumentException( "Cannot instantiate the class [" - + strategyClass.getName() - + "] because it does not have a constructor that accepts a dialect or an empty constructor" ); } ); } @@ -750,7 +759,7 @@ private Constructor resolveSqmInsertStrategyConstru strategySelector.selectStrategyImplementor( SqmMultiTableInsertStrategy.class, strategyName ); for ( Constructor declaredConstructor : strategyClass.getDeclaredConstructors() ) { final Class[] parameterTypes = declaredConstructor.getParameterTypes(); - if ( parameterTypes.length == 2 && parameterTypes[0] == EntityMappingType.class && parameterTypes[1] == MappingModelCreationProcess.class ) { + if ( parameterTypes.length == 2 && parameterTypes[0] == EntityMappingType.class && parameterTypes[1] == RuntimeModelCreationContext.class ) { return (Constructor) declaredConstructor; } } @@ -979,10 +988,10 @@ public SqmMultiTableInsertStrategy getCustomSqmMultiTableInsertStrategy() { } @Override - public SqmMultiTableMutationStrategy resolveCustomSqmMultiTableMutationStrategy(EntityMappingType rootEntityDescriptor, MappingModelCreationProcess creationProcess) { + public SqmMultiTableMutationStrategy resolveCustomSqmMultiTableMutationStrategy(EntityMappingType rootEntityDescriptor, RuntimeModelCreationContext creationContext) { if ( sqmMultiTableMutationStrategyConstructor != null ) { try { - return sqmMultiTableMutationStrategyConstructor.newInstance( rootEntityDescriptor, creationProcess ); + return sqmMultiTableMutationStrategyConstructor.newInstance( rootEntityDescriptor, creationContext ); } catch (Exception e) { throw new StrategySelectionException( @@ -995,10 +1004,10 @@ public SqmMultiTableMutationStrategy resolveCustomSqmMultiTableMutationStrategy( } @Override - public SqmMultiTableInsertStrategy resolveCustomSqmMultiTableInsertStrategy(EntityMappingType rootEntityDescriptor, MappingModelCreationProcess creationProcess) { + public SqmMultiTableInsertStrategy resolveCustomSqmMultiTableInsertStrategy(EntityMappingType rootEntityDescriptor, RuntimeModelCreationContext creationContext) { if ( sqmMultiTableInsertStrategyConstructor != null ) { try { - return sqmMultiTableInsertStrategyConstructor.newInstance( rootEntityDescriptor, creationProcess ); + return sqmMultiTableInsertStrategyConstructor.newInstance( rootEntityDescriptor, creationContext ); } catch (Exception e) { throw new StrategySelectionException( diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/relational/Database.java b/hibernate-core/src/main/java/org/hibernate/boot/model/relational/Database.java index b9e69451a41b..1ea1fd57bd25 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/relational/Database.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/relational/Database.java @@ -11,6 +11,7 @@ import java.util.Map; import java.util.TreeMap; +import org.checkerframework.checker.nullness.qual.Nullable; import org.hibernate.boot.model.naming.Identifier; import org.hibernate.boot.model.naming.PhysicalNamingStrategy; import org.hibernate.boot.spi.MetadataBuildingOptions; @@ -132,6 +133,10 @@ public Namespace.Name getPhysicalImplicitNamespaceName() { return physicalImplicitNamespaceName; } + public @Nullable Namespace findNamespace(Identifier catalogName, Identifier schemaName) { + return namespaceMap.get( new Namespace.Name( catalogName, schemaName ) ); + } + public Namespace locateNamespace(Identifier catalogName, Identifier schemaName) { final Namespace.Name name = new Namespace.Name( catalogName, schemaName ); final Namespace namespace = namespaceMap.get( name ); diff --git a/hibernate-core/src/main/java/org/hibernate/boot/spi/AbstractDelegatingSessionFactoryOptions.java b/hibernate-core/src/main/java/org/hibernate/boot/spi/AbstractDelegatingSessionFactoryOptions.java index 09deec93d2ea..016b41807cca 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/spi/AbstractDelegatingSessionFactoryOptions.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/spi/AbstractDelegatingSessionFactoryOptions.java @@ -19,7 +19,7 @@ import org.hibernate.SessionFactoryObserver; import org.hibernate.context.spi.TenantSchemaMapper; import org.hibernate.metamodel.mapping.EntityMappingType; -import org.hibernate.metamodel.mapping.internal.MappingModelCreationProcess; +import org.hibernate.metamodel.spi.RuntimeModelCreationContext; import org.hibernate.type.TimeZoneStorageStrategy; import org.hibernate.annotations.CacheLayout; import org.hibernate.boot.SchemaAutoTooling; @@ -148,13 +148,13 @@ public SqmMultiTableInsertStrategy getCustomSqmMultiTableInsertStrategy() { } @Override - public SqmMultiTableMutationStrategy resolveCustomSqmMultiTableMutationStrategy(EntityMappingType rootEntityDescriptor, MappingModelCreationProcess creationProcess) { - return delegate.resolveCustomSqmMultiTableMutationStrategy( rootEntityDescriptor, creationProcess ); + public SqmMultiTableMutationStrategy resolveCustomSqmMultiTableMutationStrategy(EntityMappingType rootEntityDescriptor, RuntimeModelCreationContext creationContext) { + return delegate.resolveCustomSqmMultiTableMutationStrategy( rootEntityDescriptor, creationContext ); } @Override - public SqmMultiTableInsertStrategy resolveCustomSqmMultiTableInsertStrategy(EntityMappingType rootEntityDescriptor, MappingModelCreationProcess creationProcess) { - return delegate.resolveCustomSqmMultiTableInsertStrategy( rootEntityDescriptor, creationProcess ); + public SqmMultiTableInsertStrategy resolveCustomSqmMultiTableInsertStrategy(EntityMappingType rootEntityDescriptor, RuntimeModelCreationContext creationContext) { + return delegate.resolveCustomSqmMultiTableInsertStrategy( rootEntityDescriptor, creationContext ); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/AbstractTransactSQLDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/AbstractTransactSQLDialect.java index 08cd1553025f..8e1d2dcbcdf7 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/AbstractTransactSQLDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/AbstractTransactSQLDialect.java @@ -9,20 +9,21 @@ import org.hibernate.boot.model.FunctionContributions; import org.hibernate.dialect.function.CaseLeastGreatestEmulation; import org.hibernate.dialect.function.CastingConcatFunction; -import org.hibernate.dialect.function.CommonFunctionFactory; import org.hibernate.dialect.function.TransactSQLStrFunction; +import org.hibernate.dialect.temptable.TemporaryTableStrategy; +import org.hibernate.dialect.temptable.TransactSQLLocalTemporaryTableStrategy; +import org.hibernate.engine.jdbc.dialect.spi.DialectResolutionInfo; +import org.hibernate.dialect.function.CommonFunctionFactory; import org.hibernate.dialect.identity.AbstractTransactSQLIdentityColumnSupport; import org.hibernate.dialect.identity.IdentityColumnSupport; -import org.hibernate.dialect.temptable.TemporaryTable; import org.hibernate.dialect.temptable.TemporaryTableKind; -import org.hibernate.engine.jdbc.dialect.spi.DialectResolutionInfo; import org.hibernate.metamodel.mapping.EntityMappingType; import org.hibernate.metamodel.spi.RuntimeModelCreationContext; import org.hibernate.query.sqm.TrimSpec; -import org.hibernate.query.sqm.mutation.internal.temptable.LocalTemporaryTableInsertStrategy; -import org.hibernate.query.sqm.mutation.internal.temptable.LocalTemporaryTableMutationStrategy; import org.hibernate.query.sqm.mutation.spi.AfterUseAction; import org.hibernate.query.sqm.mutation.spi.BeforeUseAction; +import org.hibernate.query.sqm.mutation.internal.temptable.LocalTemporaryTableInsertStrategy; +import org.hibernate.query.sqm.mutation.internal.temptable.LocalTemporaryTableMutationStrategy; import org.hibernate.query.sqm.mutation.spi.SqmMultiTableInsertStrategy; import org.hibernate.query.sqm.mutation.spi.SqmMultiTableMutationStrategy; import org.hibernate.sql.ast.SqlAstNodeRenderingMode; @@ -295,32 +296,21 @@ public boolean requiresCastForConcatenatingNonStrings() { @Override public SqmMultiTableMutationStrategy getFallbackSqmMutationStrategy( - EntityMappingType entityDescriptor, + EntityMappingType rootEntityDescriptor, RuntimeModelCreationContext runtimeModelCreationContext) { - return new LocalTemporaryTableMutationStrategy( - TemporaryTable.createIdTable( - entityDescriptor, - basename -> '#' + TemporaryTable.ID_TABLE_PREFIX + basename, - this, - runtimeModelCreationContext - ), - runtimeModelCreationContext.getSessionFactory() - ); + return new LocalTemporaryTableMutationStrategy( rootEntityDescriptor, runtimeModelCreationContext ); } @Override public SqmMultiTableInsertStrategy getFallbackSqmInsertStrategy( - EntityMappingType entityDescriptor, + EntityMappingType rootEntityDescriptor, RuntimeModelCreationContext runtimeModelCreationContext) { - return new LocalTemporaryTableInsertStrategy( - TemporaryTable.createEntityTable( - entityDescriptor, - name -> '#' + TemporaryTable.ENTITY_TABLE_PREFIX + name, - this, - runtimeModelCreationContext - ), - runtimeModelCreationContext.getSessionFactory() - ); + return new LocalTemporaryTableInsertStrategy( rootEntityDescriptor, runtimeModelCreationContext ); + } + + @Override + public TemporaryTableStrategy getLocalTemporaryTableStrategy() { + return TransactSQLLocalTemporaryTableStrategy.INSTANCE; } @Override @@ -330,18 +320,17 @@ public TemporaryTableKind getSupportedTemporaryTableKind() { @Override public String getTemporaryTableCreateCommand() { - return "create table"; + return TransactSQLLocalTemporaryTableStrategy.INSTANCE.getTemporaryTableCreateCommand(); } @Override public AfterUseAction getTemporaryTableAfterUseAction() { - // sql-server, at least needed this dropped after use; strange! - return AfterUseAction.DROP; + return TransactSQLLocalTemporaryTableStrategy.INSTANCE.getTemporaryTableAfterUseAction(); } @Override public BeforeUseAction getTemporaryTableBeforeUseAction() { - return BeforeUseAction.CREATE; + return TransactSQLLocalTemporaryTableStrategy.INSTANCE.getTemporaryTableBeforeUseAction(); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/DB2Dialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/DB2Dialect.java index 908c6d38e392..b879a94a7f3c 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/DB2Dialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/DB2Dialect.java @@ -27,6 +27,8 @@ import org.hibernate.dialect.sequence.SequenceSupport; import org.hibernate.dialect.sql.ast.DB2SqlAstTranslator; import org.hibernate.dialect.sql.ast.PostgreSQLSqlAstTranslator; +import org.hibernate.dialect.temptable.DB2GlobalTemporaryTableStrategy; +import org.hibernate.dialect.temptable.TemporaryTableStrategy; import org.hibernate.dialect.type.DB2StructJdbcType; import org.hibernate.dialect.unique.AlterTableUniqueIndexDelegate; import org.hibernate.dialect.unique.UniqueDelegate; @@ -848,6 +850,11 @@ public SqmMultiTableInsertStrategy getFallbackSqmInsertStrategy( return new CteInsertStrategy( rootEntityDescriptor, runtimeModelCreationContext ); } + @Override + public TemporaryTableStrategy getGlobalTemporaryTableStrategy() { + return DB2GlobalTemporaryTableStrategy.INSTANCE; + } + @Override public boolean supportsIsTrue() { return true; diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/Dialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/Dialect.java index 10a862d2775e..7f72938e2f72 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/Dialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/Dialect.java @@ -55,10 +55,13 @@ import org.hibernate.dialect.pagination.LimitHandler; import org.hibernate.dialect.sequence.NoSequenceSupport; import org.hibernate.dialect.sequence.SequenceSupport; +import org.hibernate.dialect.temptable.LegacyTemporaryTableStrategy; +import org.hibernate.dialect.temptable.PersistentTemporaryTableStrategy; import org.hibernate.dialect.temptable.StandardTemporaryTableExporter; import org.hibernate.dialect.temptable.TemporaryTable; import org.hibernate.dialect.temptable.TemporaryTableExporter; import org.hibernate.dialect.temptable.TemporaryTableKind; +import org.hibernate.dialect.temptable.TemporaryTableStrategy; import org.hibernate.dialect.unique.AlterTableUniqueDelegate; import org.hibernate.dialect.unique.UniqueDelegate; import org.hibernate.engine.jdbc.LobCreator; @@ -2190,7 +2193,9 @@ public String getSelectGUIDString() { * Does this database have some sort of support for temporary tables? * * @return true by default, since most do + * @deprecated Use {@link #getLocalTemporaryTableStrategy()} and {@link #getGlobalTemporaryTableStrategy()} to check instead */ + @Deprecated(forRemoval = true, since = "7.1") public boolean supportsTemporaryTables() { // Most databases do return true; @@ -2200,7 +2205,9 @@ public boolean supportsTemporaryTables() { * Does this database support primary keys for temporary tables? * * @return true by default, since most do + * @deprecated Moved to {@link TemporaryTableStrategy#supportsTemporaryTablePrimaryKey()} */ + @Deprecated(forRemoval = true, since = "7.1") public boolean supportsTemporaryTablePrimaryKey() { // Most databases do return true; @@ -3190,15 +3197,7 @@ public boolean requiresColumnListInCreateView() { public SqmMultiTableMutationStrategy getFallbackSqmMutationStrategy( EntityMappingType entityDescriptor, RuntimeModelCreationContext runtimeModelCreationContext) { - return new PersistentTableMutationStrategy( - TemporaryTable.createIdTable( - entityDescriptor, - basename -> TemporaryTable.ID_TABLE_PREFIX + basename, - this, - runtimeModelCreationContext - ), - runtimeModelCreationContext.getSessionFactory() - ); + return new PersistentTableMutationStrategy( entityDescriptor, runtimeModelCreationContext ); } /** @@ -3899,9 +3898,41 @@ public TemporaryTableExporter getTemporaryTableExporter() { return temporaryTableExporter; } + /** + * The strategy to use for persistent temporary tables. + * + * @since 7.1 + */ + public TemporaryTableStrategy getPersistentTemporaryTableStrategy() { + return getSupportedTemporaryTableKind() == TemporaryTableKind.PERSISTENT + ? new LegacyTemporaryTableStrategy( this ) + : PersistentTemporaryTableStrategy.INSTANCE; + } + + /** + * The strategy to use for local temporary tables. + * + * @since 7.1 + */ + public @Nullable TemporaryTableStrategy getLocalTemporaryTableStrategy() { + return getSupportedTemporaryTableKind() == TemporaryTableKind.LOCAL ? new LegacyTemporaryTableStrategy( this ) + : null; + } + + /** + * The strategy to use for global temporary tables. + * + * @since 7.1 + */ + public @Nullable TemporaryTableStrategy getGlobalTemporaryTableStrategy() { + return getSupportedTemporaryTableKind() == TemporaryTableKind.GLOBAL ? new LegacyTemporaryTableStrategy( this ) + : null; + } + /** * The kind of temporary tables that are supported on this database. */ + @Deprecated(forRemoval = true, since = "7.1") public TemporaryTableKind getSupportedTemporaryTableKind() { return TemporaryTableKind.PERSISTENT; } @@ -3911,6 +3942,7 @@ public TemporaryTableKind getSupportedTemporaryTableKind() { * create a temporary table, specifying dialect-specific options, or * {@code null} if there are no options to specify. */ + @Deprecated(forRemoval = true, since = "7.1") public String getTemporaryTableCreateOptions() { return null; } @@ -3918,6 +3950,7 @@ public String getTemporaryTableCreateOptions() { /** * The command to create a temporary table. */ + @Deprecated(forRemoval = true, since = "7.1") public String getTemporaryTableCreateCommand() { return switch ( getSupportedTemporaryTableKind() ) { case PERSISTENT -> "create table"; @@ -3929,6 +3962,7 @@ public String getTemporaryTableCreateCommand() { /** * The command to drop a temporary table. */ + @Deprecated(forRemoval = true, since = "7.1") public String getTemporaryTableDropCommand() { return "drop table"; } @@ -3936,6 +3970,7 @@ public String getTemporaryTableDropCommand() { /** * The command to truncate a temporary table. */ + @Deprecated(forRemoval = true, since = "7.1") public String getTemporaryTableTruncateCommand() { return "delete from"; } @@ -3946,6 +3981,7 @@ public String getTemporaryTableTruncateCommand() { * @param sqlTypeCode The SQL type code * @return The annotation to be appended, for example, {@code COLLATE DATABASE_DEFAULT} in SQL Server */ + @Deprecated(forRemoval = true, since = "7.1") public String getCreateTemporaryTableColumnAnnotation(int sqlTypeCode) { return ""; } @@ -3964,6 +4000,7 @@ public TempTableDdlTransactionHandling getTemporaryTableDdlTransactionHandling() /** * The action to take after finishing use of a temporary table. */ + @Deprecated(forRemoval = true, since = "7.1") public AfterUseAction getTemporaryTableAfterUseAction() { return AfterUseAction.CLEAN; } @@ -3971,6 +4008,7 @@ public AfterUseAction getTemporaryTableAfterUseAction() { /** * The action to take before beginning use of a temporary table. */ + @Deprecated(forRemoval = true, since = "7.1") public BeforeUseAction getTemporaryTableBeforeUseAction() { return BeforeUseAction.NONE; } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/H2Dialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/H2Dialect.java index d81d2841b7eb..ef2f0a9256ac 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/H2Dialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/H2Dialect.java @@ -24,8 +24,10 @@ import org.hibernate.dialect.sequence.H2V2SequenceSupport; import org.hibernate.dialect.sequence.SequenceSupport; import org.hibernate.dialect.sql.ast.H2SqlAstTranslator; -import org.hibernate.dialect.temptable.TemporaryTable; +import org.hibernate.dialect.temptable.H2GlobalTemporaryTableStrategy; +import org.hibernate.dialect.temptable.StandardLocalTemporaryTableStrategy; import org.hibernate.dialect.temptable.TemporaryTableKind; +import org.hibernate.dialect.temptable.TemporaryTableStrategy; import org.hibernate.dialect.type.H2DurationIntervalSecondJdbcType; import org.hibernate.dialect.type.H2JsonArrayJdbcTypeConstructor; import org.hibernate.dialect.type.H2JsonJdbcType; @@ -774,35 +776,19 @@ public NullOrdering getNullOrdering() { public SqmMultiTableMutationStrategy getFallbackSqmMutationStrategy( EntityMappingType entityDescriptor, RuntimeModelCreationContext runtimeModelCreationContext) { - return new GlobalTemporaryTableMutationStrategy( - TemporaryTable.createIdTable( - entityDescriptor, - basename -> TemporaryTable.ID_TABLE_PREFIX + basename, - this, - runtimeModelCreationContext - ), - runtimeModelCreationContext.getSessionFactory() - ); + return new GlobalTemporaryTableMutationStrategy( entityDescriptor, runtimeModelCreationContext ); } @Override public SqmMultiTableInsertStrategy getFallbackSqmInsertStrategy( EntityMappingType entityDescriptor, RuntimeModelCreationContext runtimeModelCreationContext) { - return new GlobalTemporaryTableInsertStrategy( - TemporaryTable.createEntityTable( - entityDescriptor, - name -> TemporaryTable.ENTITY_TABLE_PREFIX + name, - this, - runtimeModelCreationContext - ), - runtimeModelCreationContext.getSessionFactory() - ); + return new GlobalTemporaryTableInsertStrategy( entityDescriptor, runtimeModelCreationContext ); } @Override public String getTemporaryTableCreateOptions() { - return "TRANSACTIONAL"; + return H2GlobalTemporaryTableStrategy.INSTANCE.getTemporaryTableCreateOptions(); } @Override @@ -810,6 +796,16 @@ public TemporaryTableKind getSupportedTemporaryTableKind() { return TemporaryTableKind.GLOBAL; } + @Override + public TemporaryTableStrategy getGlobalTemporaryTableStrategy() { + return H2GlobalTemporaryTableStrategy.INSTANCE; + } + + @Override + public TemporaryTableStrategy getLocalTemporaryTableStrategy() { + return StandardLocalTemporaryTableStrategy.INSTANCE; + } + @Override public ViolatedConstraintNameExtractor getViolatedConstraintNameExtractor() { return EXTRACTOR; diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/HANADialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/HANADialect.java index 924a85e8a9fb..613ea3390a85 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/HANADialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/HANADialect.java @@ -28,8 +28,9 @@ import org.hibernate.dialect.sequence.HANASequenceSupport; import org.hibernate.dialect.sequence.SequenceSupport; import org.hibernate.dialect.sql.ast.HANASqlAstTranslator; -import org.hibernate.dialect.temptable.TemporaryTable; +import org.hibernate.dialect.temptable.HANAGlobalTemporaryTableStrategy; import org.hibernate.dialect.temptable.TemporaryTableKind; +import org.hibernate.dialect.temptable.TemporaryTableStrategy; import org.hibernate.engine.config.spi.ConfigurationService; import org.hibernate.engine.config.spi.StandardConverters; import org.hibernate.engine.jdbc.BinaryStream; @@ -1963,30 +1964,14 @@ public int getMaxLobPrefetchSize() { public SqmMultiTableMutationStrategy getFallbackSqmMutationStrategy( EntityMappingType entityDescriptor, RuntimeModelCreationContext runtimeModelCreationContext) { - return new GlobalTemporaryTableMutationStrategy( - TemporaryTable.createIdTable( - entityDescriptor, - basename -> TemporaryTable.ID_TABLE_PREFIX + basename, - this, - runtimeModelCreationContext - ), - runtimeModelCreationContext.getSessionFactory() - ); + return new GlobalTemporaryTableMutationStrategy( entityDescriptor, runtimeModelCreationContext ); } @Override public SqmMultiTableInsertStrategy getFallbackSqmInsertStrategy( EntityMappingType entityDescriptor, RuntimeModelCreationContext runtimeModelCreationContext) { - return new GlobalTemporaryTableInsertStrategy( - TemporaryTable.createEntityTable( - entityDescriptor, - name -> TemporaryTable.ENTITY_TABLE_PREFIX + name, - this, - runtimeModelCreationContext - ), - runtimeModelCreationContext.getSessionFactory() - ); + return new GlobalTemporaryTableInsertStrategy( entityDescriptor, runtimeModelCreationContext ); } @Override @@ -1994,19 +1979,24 @@ public TemporaryTableKind getSupportedTemporaryTableKind() { return TemporaryTableKind.GLOBAL; } + @Override + public TemporaryTableStrategy getGlobalTemporaryTableStrategy() { + return HANAGlobalTemporaryTableStrategy.INSTANCE; + } + @Override public String getTemporaryTableCreateOptions() { - return "on commit delete rows"; + return HANAGlobalTemporaryTableStrategy.INSTANCE.getTemporaryTableCreateOptions(); } @Override public String getTemporaryTableCreateCommand() { - return "create global temporary row table"; + return HANAGlobalTemporaryTableStrategy.INSTANCE.getTemporaryTableCreateCommand(); } @Override public String getTemporaryTableTruncateCommand() { - return "truncate table"; + return HANAGlobalTemporaryTableStrategy.INSTANCE.getTemporaryTableTruncateCommand(); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/HSQLDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/HSQLDialect.java index da684f1336fa..a97bd8778da0 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/HSQLDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/HSQLDialect.java @@ -19,8 +19,10 @@ import org.hibernate.dialect.sequence.HSQLSequenceSupport; import org.hibernate.dialect.sequence.SequenceSupport; import org.hibernate.dialect.sql.ast.HSQLSqlAstTranslator; -import org.hibernate.dialect.temptable.TemporaryTable; +import org.hibernate.dialect.temptable.HSQLLocalTemporaryTableStrategy; +import org.hibernate.dialect.temptable.StandardGlobalTemporaryTableStrategy; import org.hibernate.dialect.temptable.TemporaryTableKind; +import org.hibernate.dialect.temptable.TemporaryTableStrategy; import org.hibernate.dialect.unique.CreateTableUniqueDelegate; import org.hibernate.dialect.unique.UniqueDelegate; import org.hibernate.engine.jdbc.dialect.spi.DialectResolutionInfo; @@ -524,54 +526,14 @@ public NullOrdering getNullOrdering() { public SqmMultiTableMutationStrategy getFallbackSqmMutationStrategy( EntityMappingType rootEntityDescriptor, RuntimeModelCreationContext runtimeModelCreationContext) { - - // Hibernate uses this information for temporary tables that it uses for its own operations - // therefore the appropriate strategy is taken with different versions of HSQLDB - - // All versions of HSQLDB support GLOBAL TEMPORARY tables where the table - // definition is shared by all users but data is private to the session - // HSQLDB 2.0 also supports session-based LOCAL TEMPORARY tables where - // the definition and data is private to the session and table declaration - // can happen in the middle of a transaction - - return new LocalTemporaryTableMutationStrategy( - // With HSQLDB 2.0, the table name is qualified with SESSION to assist the drop - // statement (in-case there is a global name beginning with HT_) - TemporaryTable.createIdTable( - rootEntityDescriptor, - basename -> "session." + TemporaryTable.ID_TABLE_PREFIX + basename, - this, - runtimeModelCreationContext - ), - runtimeModelCreationContext.getSessionFactory() - ); + return new LocalTemporaryTableMutationStrategy( rootEntityDescriptor, runtimeModelCreationContext ); } @Override public SqmMultiTableInsertStrategy getFallbackSqmInsertStrategy( EntityMappingType rootEntityDescriptor, RuntimeModelCreationContext runtimeModelCreationContext) { - - // Hibernate uses this information for temporary tables that it uses for its own operations - // therefore the appropriate strategy is taken with different versions of HSQLDB - - // All versions of HSQLDB support GLOBAL TEMPORARY tables where the table - // definition is shared by all users but data is private to the session - // HSQLDB 2.0 also supports session-based LOCAL TEMPORARY tables where - // the definition and data is private to the session and table declaration - // can happen in the middle of a transaction - - return new LocalTemporaryTableInsertStrategy( - // With HSQLDB 2.0, the table name is qualified with SESSION to assist the drop - // statement (in-case there is a global name beginning with HT_) - TemporaryTable.createEntityTable( - rootEntityDescriptor, - name -> "session." + TemporaryTable.ENTITY_TABLE_PREFIX + name, - this, - runtimeModelCreationContext - ), - runtimeModelCreationContext.getSessionFactory() - ); + return new LocalTemporaryTableInsertStrategy( rootEntityDescriptor, runtimeModelCreationContext ); } @Override @@ -579,19 +541,29 @@ public TemporaryTableKind getSupportedTemporaryTableKind() { return TemporaryTableKind.LOCAL; } + @Override + public TemporaryTableStrategy getGlobalTemporaryTableStrategy() { + return StandardGlobalTemporaryTableStrategy.INSTANCE; + } + + @Override + public TemporaryTableStrategy getLocalTemporaryTableStrategy() { + return HSQLLocalTemporaryTableStrategy.INSTANCE; + } + @Override public String getTemporaryTableCreateCommand() { - return "declare local temporary table"; + return HSQLLocalTemporaryTableStrategy.INSTANCE.getTemporaryTableCreateCommand(); } @Override public AfterUseAction getTemporaryTableAfterUseAction() { - return AfterUseAction.DROP; + return HSQLLocalTemporaryTableStrategy.INSTANCE.getTemporaryTableAfterUseAction(); } @Override public BeforeUseAction getTemporaryTableBeforeUseAction() { - return BeforeUseAction.CREATE; + return HSQLLocalTemporaryTableStrategy.INSTANCE.getTemporaryTableBeforeUseAction(); } // current timestamp support ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/MySQLDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/MySQLDialect.java index 5ab74110eb8f..2ccd40a23ecf 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/MySQLDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/MySQLDialect.java @@ -25,8 +25,9 @@ import org.hibernate.dialect.sequence.NoSequenceSupport; import org.hibernate.dialect.sequence.SequenceSupport; import org.hibernate.dialect.sql.ast.MySQLSqlAstTranslator; -import org.hibernate.dialect.temptable.TemporaryTable; +import org.hibernate.dialect.temptable.MySQLLocalTemporaryTableStrategy; import org.hibernate.dialect.temptable.TemporaryTableKind; +import org.hibernate.dialect.temptable.TemporaryTableStrategy; import org.hibernate.dialect.type.MySQLCastingJsonArrayJdbcTypeConstructor; import org.hibernate.dialect.type.MySQLCastingJsonJdbcType; import org.hibernate.engine.jdbc.Size; @@ -1136,30 +1137,19 @@ public NullOrdering getNullOrdering() { public SqmMultiTableMutationStrategy getFallbackSqmMutationStrategy( EntityMappingType rootEntityDescriptor, RuntimeModelCreationContext runtimeModelCreationContext) { - return new LocalTemporaryTableMutationStrategy( - TemporaryTable.createIdTable( - rootEntityDescriptor, - basename -> TemporaryTable.ID_TABLE_PREFIX + basename, - this, - runtimeModelCreationContext - ), - runtimeModelCreationContext.getSessionFactory() - ); + return new LocalTemporaryTableMutationStrategy( rootEntityDescriptor, runtimeModelCreationContext ); } @Override public SqmMultiTableInsertStrategy getFallbackSqmInsertStrategy( EntityMappingType rootEntityDescriptor, RuntimeModelCreationContext runtimeModelCreationContext) { - return new LocalTemporaryTableInsertStrategy( - TemporaryTable.createEntityTable( - rootEntityDescriptor, - name -> TemporaryTable.ENTITY_TABLE_PREFIX + name, - this, - runtimeModelCreationContext - ), - runtimeModelCreationContext.getSessionFactory() - ); + return new LocalTemporaryTableInsertStrategy( rootEntityDescriptor, runtimeModelCreationContext ); + } + + @Override + public TemporaryTableStrategy getLocalTemporaryTableStrategy() { + return MySQLLocalTemporaryTableStrategy.INSTANCE; } @Override @@ -1169,22 +1159,22 @@ public TemporaryTableKind getSupportedTemporaryTableKind() { @Override public String getTemporaryTableCreateCommand() { - return "create temporary table if not exists"; + return MySQLLocalTemporaryTableStrategy.INSTANCE.getTemporaryTableCreateCommand(); } @Override public String getTemporaryTableDropCommand() { - return "drop temporary table"; + return MySQLLocalTemporaryTableStrategy.INSTANCE.getTemporaryTableDropCommand(); } @Override public AfterUseAction getTemporaryTableAfterUseAction() { - return AfterUseAction.DROP; + return MySQLLocalTemporaryTableStrategy.INSTANCE.getTemporaryTableAfterUseAction(); } @Override public BeforeUseAction getTemporaryTableBeforeUseAction() { - return BeforeUseAction.CREATE; + return MySQLLocalTemporaryTableStrategy.INSTANCE.getTemporaryTableBeforeUseAction(); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java index cea98f552e71..e9e75adcc0ef 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java @@ -27,8 +27,10 @@ import org.hibernate.dialect.sequence.OracleSequenceSupport; import org.hibernate.dialect.sequence.SequenceSupport; import org.hibernate.dialect.sql.ast.OracleSqlAstTranslator; -import org.hibernate.dialect.temptable.TemporaryTable; +import org.hibernate.dialect.temptable.OracleLocalTemporaryTableStrategy; +import org.hibernate.dialect.temptable.StandardGlobalTemporaryTableStrategy; import org.hibernate.dialect.temptable.TemporaryTableKind; +import org.hibernate.dialect.temptable.TemporaryTableStrategy; import org.hibernate.dialect.type.OracleBooleanJdbcType; import org.hibernate.dialect.type.OracleEnumJdbcType; import org.hibernate.dialect.type.OracleJdbcHelper; @@ -1304,34 +1306,28 @@ public boolean isEmptyStringTreatedAsNull() { return true; } + @Override + public TemporaryTableStrategy getLocalTemporaryTableStrategy() { + return OracleLocalTemporaryTableStrategy.INSTANCE; + } + + @Override + public TemporaryTableStrategy getGlobalTemporaryTableStrategy() { + return StandardGlobalTemporaryTableStrategy.INSTANCE; + } + @Override public SqmMultiTableMutationStrategy getFallbackSqmMutationStrategy( EntityMappingType rootEntityDescriptor, RuntimeModelCreationContext runtimeModelCreationContext) { - return new GlobalTemporaryTableMutationStrategy( - TemporaryTable.createIdTable( - rootEntityDescriptor, - basename -> TemporaryTable.ID_TABLE_PREFIX + basename, - this, - runtimeModelCreationContext - ), - runtimeModelCreationContext.getSessionFactory() - ); + return new GlobalTemporaryTableMutationStrategy( rootEntityDescriptor, runtimeModelCreationContext ); } @Override public SqmMultiTableInsertStrategy getFallbackSqmInsertStrategy( EntityMappingType rootEntityDescriptor, RuntimeModelCreationContext runtimeModelCreationContext) { - return new GlobalTemporaryTableInsertStrategy( - TemporaryTable.createEntityTable( - rootEntityDescriptor, - name -> TemporaryTable.ENTITY_TABLE_PREFIX + name, - this, - runtimeModelCreationContext - ), - runtimeModelCreationContext.getSessionFactory() - ); + return new GlobalTemporaryTableInsertStrategy( rootEntityDescriptor, runtimeModelCreationContext ); } @Override @@ -1341,7 +1337,7 @@ public TemporaryTableKind getSupportedTemporaryTableKind() { @Override public String getTemporaryTableCreateOptions() { - return "on commit delete rows"; + return StandardGlobalTemporaryTableStrategy.INSTANCE.getTemporaryTableCreateOptions(); } /** diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/PostgreSQLDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/PostgreSQLDialect.java index 2ccacf34db3b..54fd00b75940 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/PostgreSQLDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/PostgreSQLDialect.java @@ -30,6 +30,8 @@ import org.hibernate.dialect.sequence.PostgreSQLSequenceSupport; import org.hibernate.dialect.sequence.SequenceSupport; import org.hibernate.dialect.sql.ast.PostgreSQLSqlAstTranslator; +import org.hibernate.dialect.temptable.StandardLocalTemporaryTableStrategy; +import org.hibernate.dialect.temptable.TemporaryTableStrategy; import org.hibernate.dialect.type.PgJdbcHelper; import org.hibernate.dialect.type.PostgreSQLArrayJdbcTypeConstructor; import org.hibernate.dialect.type.PostgreSQLCastingInetJdbcType; @@ -997,6 +999,11 @@ public SqmMultiTableInsertStrategy getFallbackSqmInsertStrategy( return new CteInsertStrategy( rootEntityDescriptor, runtimeModelCreationContext ); } + @Override + public TemporaryTableStrategy getLocalTemporaryTableStrategy() { + return StandardLocalTemporaryTableStrategy.INSTANCE; + } + @Override public SqlAstTranslatorFactory getSqlAstTranslatorFactory() { return new StandardSqlAstTranslatorFactory() { diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/temptable/DB2GlobalTemporaryTableStrategy.java b/hibernate-core/src/main/java/org/hibernate/dialect/temptable/DB2GlobalTemporaryTableStrategy.java new file mode 100644 index 000000000000..51d3096c5383 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/dialect/temptable/DB2GlobalTemporaryTableStrategy.java @@ -0,0 +1,23 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.dialect.temptable; + +/** + * DB2 specific global temporary table strategy. + */ +public class DB2GlobalTemporaryTableStrategy extends StandardGlobalTemporaryTableStrategy { + + public static final DB2GlobalTemporaryTableStrategy INSTANCE = new DB2GlobalTemporaryTableStrategy(); + + @Override + public String getTemporaryTableCreateOptions() { + return "not logged"; + } + + @Override + public String getTemporaryTableCreateCommand() { + return "declare global temporary table"; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/temptable/H2GlobalTemporaryTableStrategy.java b/hibernate-core/src/main/java/org/hibernate/dialect/temptable/H2GlobalTemporaryTableStrategy.java new file mode 100644 index 000000000000..c75bdf6daf2f --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/dialect/temptable/H2GlobalTemporaryTableStrategy.java @@ -0,0 +1,18 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.dialect.temptable; + +/** + * H2 specific global temporary table strategy. + */ +public class H2GlobalTemporaryTableStrategy extends StandardGlobalTemporaryTableStrategy { + + public static final H2GlobalTemporaryTableStrategy INSTANCE = new H2GlobalTemporaryTableStrategy(); + + @Override + public String getTemporaryTableCreateOptions() { + return "transactional"; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/temptable/HANAGlobalTemporaryTableStrategy.java b/hibernate-core/src/main/java/org/hibernate/dialect/temptable/HANAGlobalTemporaryTableStrategy.java new file mode 100644 index 000000000000..02439e99421e --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/dialect/temptable/HANAGlobalTemporaryTableStrategy.java @@ -0,0 +1,21 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.dialect.temptable; + +/** + * HANA specific global temporary table strategy. + */ +public class HANAGlobalTemporaryTableStrategy extends StandardGlobalTemporaryTableStrategy { + + @Override + public String getTemporaryTableCreateCommand() { + return "create global temporary row table"; + } + + @Override + public String getTemporaryTableTruncateCommand() { + return "truncate table"; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/temptable/HSQLLocalTemporaryTableStrategy.java b/hibernate-core/src/main/java/org/hibernate/dialect/temptable/HSQLLocalTemporaryTableStrategy.java new file mode 100644 index 000000000000..9e7db3d823a3 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/dialect/temptable/HSQLLocalTemporaryTableStrategy.java @@ -0,0 +1,32 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.dialect.temptable; + +import org.hibernate.query.sqm.mutation.spi.AfterUseAction; + +/** + * HSQL specific local temporary table strategy. + */ +public class HSQLLocalTemporaryTableStrategy extends StandardLocalTemporaryTableStrategy { + + public static final HSQLLocalTemporaryTableStrategy INSTANCE = new HSQLLocalTemporaryTableStrategy(); + + @Override + public String adjustTemporaryTableName(String desiredTableName) { + // With HSQLDB 2.0, the table name is qualified with session to assist the drop + // statement (in-case there is a global name beginning with HT_) + return "session." + desiredTableName; + } + + @Override + public String getTemporaryTableCreateCommand() { + return "declare local temporary table"; + } + + @Override + public AfterUseAction getTemporaryTableAfterUseAction() { + return AfterUseAction.DROP; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/temptable/LegacyTemporaryTableStrategy.java b/hibernate-core/src/main/java/org/hibernate/dialect/temptable/LegacyTemporaryTableStrategy.java new file mode 100644 index 000000000000..cf9c74218409 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/dialect/temptable/LegacyTemporaryTableStrategy.java @@ -0,0 +1,72 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.dialect.temptable; + +import org.hibernate.dialect.Dialect; +import org.hibernate.query.sqm.mutation.spi.AfterUseAction; +import org.hibernate.query.sqm.mutation.spi.BeforeUseAction; + +/** + * Legacy strategy that delegates to deprecated Dialect methods. + */ +@Deprecated(forRemoval = true, since = "7.1") +public class LegacyTemporaryTableStrategy implements TemporaryTableStrategy { + + private final Dialect dialect; + + public LegacyTemporaryTableStrategy(Dialect dialect) { + this.dialect = dialect; + } + + @Override + public String adjustTemporaryTableName(String desiredTableName) { + return desiredTableName; + } + + @Override + public TemporaryTableKind getTemporaryTableKind() { + return dialect.getSupportedTemporaryTableKind(); + } + + @Override + public String getTemporaryTableCreateOptions() { + return dialect.getTemporaryTableCreateOptions(); + } + + @Override + public String getTemporaryTableCreateCommand() { + return dialect.getTemporaryTableCreateCommand(); + } + + @Override + public String getTemporaryTableDropCommand() { + return dialect.getTemporaryTableDropCommand(); + } + + @Override + public String getTemporaryTableTruncateCommand() { + return dialect.getTemporaryTableTruncateCommand(); + } + + @Override + public String getCreateTemporaryTableColumnAnnotation(int sqlTypeCode) { + return dialect.getCreateTemporaryTableColumnAnnotation( sqlTypeCode ); + } + + @Override + public AfterUseAction getTemporaryTableAfterUseAction() { + return dialect.getTemporaryTableAfterUseAction(); + } + + @Override + public BeforeUseAction getTemporaryTableBeforeUseAction() { + return dialect.getTemporaryTableBeforeUseAction(); + } + + @Override + public boolean supportsTemporaryTablePrimaryKey() { + return dialect.supportsTemporaryTablePrimaryKey(); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/temptable/MySQLLocalTemporaryTableStrategy.java b/hibernate-core/src/main/java/org/hibernate/dialect/temptable/MySQLLocalTemporaryTableStrategy.java new file mode 100644 index 000000000000..37c60f0dfb2e --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/dialect/temptable/MySQLLocalTemporaryTableStrategy.java @@ -0,0 +1,31 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.dialect.temptable; + +import org.hibernate.query.sqm.mutation.spi.AfterUseAction; + +/** + * MySQL specific local temporary table strategy. + */ +public class MySQLLocalTemporaryTableStrategy extends StandardLocalTemporaryTableStrategy { + + public static final MySQLLocalTemporaryTableStrategy INSTANCE = new MySQLLocalTemporaryTableStrategy(); + + @Override + public String getTemporaryTableCreateCommand() { + return "create temporary table if not exists"; + } + + @Override + public String getTemporaryTableDropCommand() { + return "drop temporary table"; + } + + @Override + public AfterUseAction getTemporaryTableAfterUseAction() { + return AfterUseAction.DROP; + } + +} diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/temptable/OracleLocalTemporaryTableStrategy.java b/hibernate-core/src/main/java/org/hibernate/dialect/temptable/OracleLocalTemporaryTableStrategy.java new file mode 100644 index 000000000000..ff2c43816861 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/dialect/temptable/OracleLocalTemporaryTableStrategy.java @@ -0,0 +1,38 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.dialect.temptable; + +/** + * Strategy to interact with Oracle private temporary tables that were introduced in Oracle 18c. + */ +public class OracleLocalTemporaryTableStrategy extends StandardLocalTemporaryTableStrategy { + + public static final OracleLocalTemporaryTableStrategy INSTANCE = new OracleLocalTemporaryTableStrategy(); + + @Override + public String adjustTemporaryTableName(String desiredTableName) { + return "ora$ptt_" + desiredTableName; + } + + @Override + public String getTemporaryTableCreateOptions() { + return "on commit drop definition"; + } + + @Override + public String getTemporaryTableCreateCommand() { + return "create private temporary table"; + } + + @Override + public boolean supportsTemporaryTablePrimaryKey() { + return false; + } + + @Override + public boolean supportsTemporaryTableNullConstraint() { + return false; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/temptable/PersistentTemporaryTableStrategy.java b/hibernate-core/src/main/java/org/hibernate/dialect/temptable/PersistentTemporaryTableStrategy.java new file mode 100644 index 000000000000..e3ec78f2edb1 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/dialect/temptable/PersistentTemporaryTableStrategy.java @@ -0,0 +1,62 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.dialect.temptable; + +import org.checkerframework.checker.nullness.qual.Nullable; +import org.hibernate.query.sqm.mutation.spi.AfterUseAction; +import org.hibernate.query.sqm.mutation.spi.BeforeUseAction; + +/** + * Strategy to interact with persistent temporary tables. + */ +public class PersistentTemporaryTableStrategy implements TemporaryTableStrategy { + + public static final PersistentTemporaryTableStrategy INSTANCE = new PersistentTemporaryTableStrategy(); + + @Override + public String adjustTemporaryTableName(String desiredTableName) { + return desiredTableName; + } + + @Override + public TemporaryTableKind getTemporaryTableKind() { + return TemporaryTableKind.PERSISTENT; + } + + @Override + public @Nullable String getTemporaryTableCreateOptions() { + return null; + } + + @Override + public String getTemporaryTableCreateCommand() { + return "create table"; + } + + @Override + public String getTemporaryTableDropCommand() { + return "drop table"; + } + + @Override + public String getTemporaryTableTruncateCommand() { + return "delete from"; + } + + @Override + public String getCreateTemporaryTableColumnAnnotation(int sqlTypeCode) { + return ""; + } + + @Override + public AfterUseAction getTemporaryTableAfterUseAction() { + return AfterUseAction.CLEAN; + } + + @Override + public BeforeUseAction getTemporaryTableBeforeUseAction() { + return BeforeUseAction.NONE; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/temptable/StandardGlobalTemporaryTableStrategy.java b/hibernate-core/src/main/java/org/hibernate/dialect/temptable/StandardGlobalTemporaryTableStrategy.java new file mode 100644 index 000000000000..103c8792023e --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/dialect/temptable/StandardGlobalTemporaryTableStrategy.java @@ -0,0 +1,61 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.dialect.temptable; + +import org.hibernate.query.sqm.mutation.spi.AfterUseAction; +import org.hibernate.query.sqm.mutation.spi.BeforeUseAction; + +/** + * Strategy to interact with global temporary tables. + */ +public class StandardGlobalTemporaryTableStrategy implements TemporaryTableStrategy { + + public static final StandardGlobalTemporaryTableStrategy INSTANCE = new StandardGlobalTemporaryTableStrategy(); + + @Override + public String adjustTemporaryTableName(String desiredTableName) { + return desiredTableName; + } + + @Override + public TemporaryTableKind getTemporaryTableKind() { + return TemporaryTableKind.GLOBAL; + } + + @Override + public String getTemporaryTableCreateOptions() { + return "on commit delete rows"; + } + + @Override + public String getTemporaryTableCreateCommand() { + return "create global temporary table"; + } + + @Override + public String getTemporaryTableDropCommand() { + return "drop table"; + } + + @Override + public String getTemporaryTableTruncateCommand() { + return "delete from"; + } + + @Override + public String getCreateTemporaryTableColumnAnnotation(int sqlTypeCode) { + return ""; + } + + @Override + public AfterUseAction getTemporaryTableAfterUseAction() { + return AfterUseAction.CLEAN; + } + + @Override + public BeforeUseAction getTemporaryTableBeforeUseAction() { + return BeforeUseAction.NONE; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/temptable/StandardLocalTemporaryTableStrategy.java b/hibernate-core/src/main/java/org/hibernate/dialect/temptable/StandardLocalTemporaryTableStrategy.java new file mode 100644 index 000000000000..a2bf19ca9bc0 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/dialect/temptable/StandardLocalTemporaryTableStrategy.java @@ -0,0 +1,62 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.dialect.temptable; + +import org.checkerframework.checker.nullness.qual.Nullable; +import org.hibernate.query.sqm.mutation.spi.AfterUseAction; +import org.hibernate.query.sqm.mutation.spi.BeforeUseAction; + +/** + * Strategy to interact with local temporary tables. + */ +public class StandardLocalTemporaryTableStrategy implements TemporaryTableStrategy { + + public static final StandardLocalTemporaryTableStrategy INSTANCE = new StandardLocalTemporaryTableStrategy(); + + @Override + public String adjustTemporaryTableName(String desiredTableName) { + return desiredTableName; + } + + @Override + public TemporaryTableKind getTemporaryTableKind() { + return TemporaryTableKind.LOCAL; + } + + @Override + public @Nullable String getTemporaryTableCreateOptions() { + return null; + } + + @Override + public String getTemporaryTableCreateCommand() { + return "create local temporary table"; + } + + @Override + public String getTemporaryTableDropCommand() { + return "drop table"; + } + + @Override + public String getTemporaryTableTruncateCommand() { + return "delete from"; + } + + @Override + public String getCreateTemporaryTableColumnAnnotation(int sqlTypeCode) { + return ""; + } + + @Override + public AfterUseAction getTemporaryTableAfterUseAction() { + return AfterUseAction.NONE; + } + + @Override + public BeforeUseAction getTemporaryTableBeforeUseAction() { + return BeforeUseAction.CREATE; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/temptable/StandardTemporaryTableExporter.java b/hibernate-core/src/main/java/org/hibernate/dialect/temptable/StandardTemporaryTableExporter.java index cd1c7aa588a2..6eb3a86eff5c 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/temptable/StandardTemporaryTableExporter.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/temptable/StandardTemporaryTableExporter.java @@ -20,25 +20,59 @@ public StandardTemporaryTableExporter(Dialect dialect) { this.dialect = dialect; } + @Deprecated(forRemoval = true, since = "7.1") protected String getCreateCommand() { return dialect.getTemporaryTableCreateCommand(); } + protected String getCreateCommand(TemporaryTableStrategy temporaryTableStrategy) { + return temporaryTableStrategy.getTemporaryTableCreateCommand(); + } + + @Deprecated(forRemoval = true, since = "7.1") protected String getCreateOptions() { return dialect.getTemporaryTableCreateOptions(); } + protected String getCreateOptions(TemporaryTableStrategy temporaryTableStrategy) { + return temporaryTableStrategy.getTemporaryTableCreateOptions(); + } + + @Deprecated(forRemoval = true, since = "7.1") protected String getDropCommand() { return dialect.getTemporaryTableDropCommand(); } + protected String getDropCommand(TemporaryTableStrategy temporaryTableStrategy) { + return temporaryTableStrategy.getTemporaryTableDropCommand(); + } + + @Deprecated(forRemoval = true, since = "7.1") protected String getTruncateTableCommand() { return dialect.getTemporaryTableTruncateCommand(); } + protected String getTruncateTableCommand(TemporaryTableStrategy temporaryTableStrategy) { + return temporaryTableStrategy.getTemporaryTableTruncateCommand(); + } + + private TemporaryTableStrategy getDefaultTemporaryTableStrategy(TemporaryTable temporaryTable) { + final TemporaryTableStrategy temporaryTableStrategy = switch ( temporaryTable.getTemporaryTableKind() ) { + case LOCAL -> dialect.getLocalTemporaryTableStrategy(); + case GLOBAL -> dialect.getGlobalTemporaryTableStrategy(); + case PERSISTENT -> dialect.getPersistentTemporaryTableStrategy(); + }; + if ( temporaryTableStrategy == null ) { + throw new IllegalStateException( + "Dialect returns null TemporaryTableStrategy for temporary table " + temporaryTable.getQualifiedTableName() + " of type " + temporaryTable.getTemporaryTableKind() ); + } + return temporaryTableStrategy; + } + @Override public String getSqlCreateCommand(TemporaryTable temporaryTable) { - final StringBuilder buffer = new StringBuilder( getCreateCommand() ).append( ' ' ); + final TemporaryTableStrategy temporaryTableStrategy = getDefaultTemporaryTableStrategy( temporaryTable ); + final StringBuilder buffer = new StringBuilder( getCreateCommand( temporaryTableStrategy ) ).append( ' ' ); buffer.append( temporaryTable.getQualifiedTableName() ); buffer.append( '(' ); @@ -49,23 +83,25 @@ public String getSqlCreateCommand(TemporaryTable temporaryTable) { buffer.append( databaseTypeName ); - final String columnAnnotation = dialect.getCreateTemporaryTableColumnAnnotation( sqlTypeCode ); + final String columnAnnotation = temporaryTableStrategy.getCreateTemporaryTableColumnAnnotation( sqlTypeCode ); if ( !columnAnnotation.isEmpty() ) { buffer.append( ' ' ).append( columnAnnotation ); } - if ( column.isNullable() ) { - final String nullColumnString = dialect.getNullColumnString( databaseTypeName ); - if ( !databaseTypeName.contains( nullColumnString ) ) { - buffer.append( nullColumnString ); + if ( temporaryTableStrategy.supportsTemporaryTableNullConstraint() ) { + if ( column.isNullable() ) { + final String nullColumnString = dialect.getNullColumnString( databaseTypeName ); + if ( !databaseTypeName.contains( nullColumnString ) ) { + buffer.append( nullColumnString ); + } + } + else { + buffer.append( " not null" ); } - } - else { - buffer.append( " not null" ); } buffer.append( ", " ); } - if ( dialect.supportsTemporaryTablePrimaryKey() ) { + if ( temporaryTableStrategy.supportsTemporaryTablePrimaryKey() ) { buffer.append( "primary key (" ); for ( TemporaryTableColumn column : temporaryTable.getColumnsForExport() ) { if ( column.isPrimaryKey() ) { @@ -81,7 +117,7 @@ public String getSqlCreateCommand(TemporaryTable temporaryTable) { } buffer.append( ')' ); - final String createOptions = getCreateOptions(); + final String createOptions = getCreateOptions( temporaryTableStrategy ); if ( createOptions != null ) { buffer.append( ' ' ).append( createOptions ); } @@ -90,24 +126,26 @@ public String getSqlCreateCommand(TemporaryTable temporaryTable) { } @Override - public String getSqlDropCommand(TemporaryTable idTable) { - return getDropCommand() + " " + idTable.getQualifiedTableName(); + public String getSqlDropCommand(TemporaryTable temporaryTable) { + final TemporaryTableStrategy temporaryTableStrategy = getDefaultTemporaryTableStrategy( temporaryTable ); + return getDropCommand( temporaryTableStrategy ) + " " + temporaryTable.getQualifiedTableName(); } @Override public String getSqlTruncateCommand( - TemporaryTable idTable, + TemporaryTable temporaryTable, Function sessionUidAccess, SharedSessionContractImplementor session) { - if ( idTable.getSessionUidColumn() != null ) { + final TemporaryTableStrategy temporaryTableStrategy = getDefaultTemporaryTableStrategy( temporaryTable ); + if ( temporaryTable.getSessionUidColumn() != null ) { final ParameterMarkerStrategy parameterMarkerStrategy = session.getSessionFactory().getParameterMarkerStrategy(); - return getTruncateTableCommand() + " " + idTable.getQualifiedTableName() - + " where " + idTable.getSessionUidColumn().getColumnName() + " = " + return getTruncateTableCommand( temporaryTableStrategy ) + " " + temporaryTable.getQualifiedTableName() + + " where " + temporaryTable.getSessionUidColumn().getColumnName() + " = " + parameterMarkerStrategy.createMarker( 1, null ); } else { - return getTruncateTableCommand() + " " + idTable.getQualifiedTableName(); + return getTruncateTableCommand( temporaryTableStrategy ) + " " + temporaryTable.getQualifiedTableName(); } } } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/temptable/TemporaryTable.java b/hibernate-core/src/main/java/org/hibernate/dialect/temptable/TemporaryTable.java index 63b98b5dff24..9c5cd8c1b58c 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/temptable/TemporaryTable.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/temptable/TemporaryTable.java @@ -9,12 +9,16 @@ import java.util.List; import java.util.UUID; import java.util.function.BiConsumer; +import java.util.function.Consumer; import java.util.function.Function; import org.hibernate.boot.model.naming.Identifier; +import org.hibernate.boot.model.relational.Database; import org.hibernate.boot.model.relational.Exportable; +import org.hibernate.boot.model.relational.Namespace; import org.hibernate.boot.model.relational.QualifiedNameParser; import org.hibernate.boot.model.relational.QualifiedTableName; +import org.hibernate.boot.model.relational.SqlStringGenerationContext; import org.hibernate.dialect.Dialect; import org.hibernate.engine.jdbc.Size; import org.hibernate.generator.Generator; @@ -29,18 +33,27 @@ import org.hibernate.mapping.PersistentClass; import org.hibernate.mapping.Property; import org.hibernate.mapping.Selectable; -import org.hibernate.mapping.SimpleValue; +import org.hibernate.mapping.Table; +import org.hibernate.metamodel.mapping.Association; +import org.hibernate.metamodel.mapping.AttributeMapping; +import org.hibernate.metamodel.mapping.AttributeMappingsList; +import org.hibernate.metamodel.mapping.BasicValuedModelPart; +import org.hibernate.metamodel.mapping.EmbeddableValuedModelPart; import org.hibernate.metamodel.mapping.EntityDiscriminatorMapping; import org.hibernate.metamodel.mapping.EntityIdentifierMapping; import org.hibernate.metamodel.mapping.EntityMappingType; +import org.hibernate.metamodel.mapping.EntityValuedModelPart; import org.hibernate.metamodel.mapping.ForeignKeyDescriptor; import org.hibernate.metamodel.mapping.JdbcMapping; import org.hibernate.metamodel.mapping.ModelPart; import org.hibernate.metamodel.mapping.PluralAttributeMapping; import org.hibernate.metamodel.mapping.internal.EmbeddedAttributeMapping; +import org.hibernate.metamodel.mapping.internal.SingleAttributeIdentifierMapping; import org.hibernate.metamodel.spi.RuntimeModelCreationContext; import org.hibernate.persister.entity.EntityPersister; import org.hibernate.persister.entity.SingleTableEntityPersister; +import org.hibernate.persister.entity.UnionSubclassEntityPersister; +import org.hibernate.query.sqm.mutation.internal.SqmMutationStrategyHelper; import org.hibernate.type.BasicType; import org.hibernate.type.StandardBasicTypes; import org.hibernate.type.spi.TypeConfiguration; @@ -62,6 +75,7 @@ public class TemporaryTable implements Exportable, Contributable { private final EntityMappingType entityDescriptor; private final String qualifiedTableName; + private final TemporaryTableKind temporaryTableKind; private final TemporaryTableSessionUidColumn sessionUidColumn; private final List columns; @@ -72,6 +86,7 @@ public class TemporaryTable implements Exportable, Contributable { private TemporaryTable( EntityMappingType entityDescriptor, Function temporaryTableNameAdjuster, + TemporaryTableKind temporaryTableKind, Dialect dialect, RuntimeModelCreationContext creationContext, Function> columnInitializer) { @@ -119,8 +134,9 @@ private TemporaryTable( tableNameIdentifier ) ); + this.temporaryTableKind = temporaryTableKind; this.dialect = dialect; - if ( dialect.getSupportedTemporaryTableKind() == TemporaryTableKind.PERSISTENT ) { + if ( temporaryTableKind == TemporaryTableKind.PERSISTENT ) { final TypeConfiguration typeConfiguration = entityPersister .getFactory() .getTypeConfiguration(); @@ -165,14 +181,31 @@ private TemporaryTable( } } + @Deprecated(forRemoval = true, since = "7.1") public static TemporaryTable createIdTable( EntityMappingType entityDescriptor, Function temporaryTableNameAdjuster, Dialect dialect, RuntimeModelCreationContext runtimeModelCreationContext) { + return createIdTable( + entityDescriptor, + temporaryTableNameAdjuster, + dialect.getSupportedTemporaryTableKind(), + dialect, + runtimeModelCreationContext + ); + } + + public static TemporaryTable createIdTable( + EntityMappingType entityDescriptor, + Function temporaryTableNameAdjuster, + TemporaryTableKind temporaryTableKind, + Dialect dialect, + RuntimeModelCreationContext runtimeModelCreationContext) { return new TemporaryTable( entityDescriptor, temporaryTableNameAdjuster, + temporaryTableKind, dialect, runtimeModelCreationContext, temporaryTable -> { @@ -292,14 +325,31 @@ else if ( modelPart instanceof EmbeddedAttributeMapping embeddedAttribute ) { ); } + @Deprecated(forRemoval = true, since = "7.1") + public static TemporaryTable createEntityTable( + EntityMappingType entityDescriptor, + Function temporaryTableNameAdjuster, + Dialect dialect, + RuntimeModelCreationContext runtimeModelCreationContext) { + return createIdTable( + entityDescriptor, + temporaryTableNameAdjuster, + dialect.getSupportedTemporaryTableKind(), + dialect, + runtimeModelCreationContext + ); + } + public static TemporaryTable createEntityTable( EntityMappingType entityDescriptor, Function temporaryTableNameAdjuster, + TemporaryTableKind temporaryTableKind, Dialect dialect, RuntimeModelCreationContext runtimeModelCreationContext) { return new TemporaryTable( entityDescriptor, temporaryTableNameAdjuster, + temporaryTableKind, dialect, runtimeModelCreationContext, temporaryTable -> { @@ -348,80 +398,46 @@ public static TemporaryTable createEntityTable( } } final EntityIdentifierMapping identifierMapping = entityDescriptor.getIdentifierMapping(); - int idIdx = 0; - for ( Column column : entityBinding.getKey().getColumns() ) { - final JdbcMapping jdbcMapping = identifierMapping.getJdbcMapping( idIdx++ ); - columns.add( - new TemporaryTableColumn( - temporaryTable, - column.getText( dialect ), - jdbcMapping, - column.getSqlType( - runtimeModelCreationContext.getMetadata() - ), - column.getColumnSize( - dialect, - runtimeModelCreationContext.getMetadata() - ), - // We have to set the identity column after the root table insert - column.isNullable() || identityColumn || hasOptimizer, - !identityColumn && !hasOptimizer - ) - ); + final String idName; + if ( identifierMapping instanceof SingleAttributeIdentifierMapping ) { + idName = identifierMapping.getAttributeName(); } + else { + idName = "id"; + } + forEachTemporaryTableColumn( runtimeModelCreationContext, temporaryTable, idName, identifierMapping, temporaryTableColumn -> { + columns.add( new TemporaryTableColumn( + temporaryTableColumn.getContainingTable(), + temporaryTableColumn.getColumnName(), + temporaryTableColumn.getJdbcMapping(), + temporaryTableColumn.getSqlTypeDefinition(), + temporaryTableColumn.getSize(), + // We have to set the identity column after the root table insert + identityColumn || hasOptimizer, + !identityColumn && !hasOptimizer + ) ); + }); final EntityDiscriminatorMapping discriminatorMapping = entityDescriptor.getDiscriminatorMapping(); - if ( entityBinding.getDiscriminator() != null && !discriminatorMapping.isFormula() ) { - final Column discriminator = entityBinding.getDiscriminator().getColumns().get(0); - columns.add( - new TemporaryTableColumn( - temporaryTable, - discriminator.getText( dialect ), - discriminatorMapping.getJdbcMapping(), - discriminator.getSqlType( - runtimeModelCreationContext.getMetadata() - ), - discriminator.getColumnSize( - dialect, - runtimeModelCreationContext.getMetadata() - ), - // We have to set the identity column after the root table insert - discriminator.isNullable() - ) - ); + if ( discriminatorMapping != null && discriminatorMapping.hasPhysicalColumn() && !discriminatorMapping.isFormula() ) { + forEachTemporaryTableColumn( runtimeModelCreationContext, temporaryTable, "class", discriminatorMapping, temporaryTableColumn -> { + columns.add( new TemporaryTableColumn( + temporaryTableColumn.getContainingTable(), + temporaryTableColumn.getColumnName(), + temporaryTableColumn.getJdbcMapping(), + temporaryTableColumn.getSqlTypeDefinition(), + temporaryTableColumn.getSize(), + // We have to set the identity column after the root table insert + discriminatorMapping.isNullable() + ) ); + } ); } // Collect all columns for all entity subtype attributes entityDescriptor.visitSubTypeAttributeMappings( attribute -> { if ( !( attribute instanceof PluralAttributeMapping ) ) { - final PersistentClass declaringClass = runtimeModelCreationContext.getBootModel() - .getEntityBinding( attribute.findContainingEntityMapping().getEntityName() ); - final SimpleValue value = (SimpleValue) declaringClass.getProperty( attribute.getAttributeName() ).getValue(); - final Iterator columnIterator = value.getVirtualSelectables().iterator(); - attribute.forEachSelectable( - (columnIndex, selection) -> { - final Selectable selectable = columnIterator.next(); - if ( selectable instanceof Column column ) { - columns.add( - new TemporaryTableColumn( - temporaryTable, - selectable.getText( dialect ), - selection.getJdbcMapping(), - column.getSqlType( - runtimeModelCreationContext.getMetadata() - ), - column.getColumnSize( - dialect, - runtimeModelCreationContext.getMetadata() - ), - // Treat regular temporary table columns as nullable for simplicity - true - ) - ); - } - } - ); + forEachTemporaryTableColumn( runtimeModelCreationContext, temporaryTable, attribute.getAttributeName(), attribute, columns::add ); } } ); @@ -488,6 +504,170 @@ else if ( dialect.getIdentityColumnSupport().supportsIdentityColumns() ) { ); } + private static void forEachTemporaryTableColumn(RuntimeModelCreationContext runtimeModelCreationContext, TemporaryTable temporaryTable, String prefix, ModelPart modelPart, Consumer consumer) { + final Dialect dialect = runtimeModelCreationContext.getDialect(); + final EntityPersister declaringPersister = modelPart.findContainingEntityMapping().getEntityPersister(); + SqmMutationStrategyHelper.forEachSelectableMapping( prefix, modelPart, (columnName, selectableMapping) -> { + final String tableExpression = selectableMapping.getContainingTableExpression(); + final String tableName = + tableExpression.charAt( 0 ) == '(' && declaringPersister instanceof UnionSubclassEntityPersister + ? declaringPersister.getRootTableName() : tableExpression; + final Table table = findTable( runtimeModelCreationContext, tableName ); + final Column column = table.getColumn( Identifier.toIdentifier( selectableMapping.getSelectionExpression() ) ); + consumer.accept( + new TemporaryTableColumn( + temporaryTable, + columnName, + selectableMapping.getJdbcMapping(), + column.getSqlType( + runtimeModelCreationContext.getMetadata() + ), + column.getColumnSize( + dialect, + runtimeModelCreationContext.getMetadata() + ), + // Treat regular temporary table columns as nullable for simplicity + true + ) + ); + } ); + } + + private static Table findTable(RuntimeModelCreationContext runtimeModelCreationContext, String tableName) { + final Database database = runtimeModelCreationContext.getMetadata().getDatabase(); + final QualifiedNameParser.NameParts nameParts = QualifiedNameParser.INSTANCE.parse( tableName ); + // Strip off the default catalog and schema names since these are not reflected in the Database#namespaces + final SqlStringGenerationContext sqlContext = runtimeModelCreationContext.getSqlStringGenerationContext(); + final Identifier catalog; + final Identifier schema; + if ( nameParts.getCatalogName() != null && nameParts.getSchemaName() != null ) { + // For some reason, the QualifiedNameParser thinks a.b.c parses a as schema and b as catalog, + // though it renders catalog.schema.object + catalog = nameParts.getSchemaName() == null || nameParts.getSchemaName().equals( sqlContext.getDefaultCatalog() ) + ? null : nameParts.getSchemaName(); + schema = nameParts.getCatalogName() == null || nameParts.getCatalogName().equals( sqlContext.getDefaultSchema() ) + ? null : nameParts.getCatalogName(); + } + else { + catalog = nameParts.getCatalogName() == null || nameParts.getCatalogName().equals( sqlContext.getDefaultCatalog() ) + ? null : nameParts.getCatalogName(); + schema = nameParts.getSchemaName() == null || nameParts.getSchemaName().equals( sqlContext.getDefaultSchema() ) + ? null : nameParts.getSchemaName(); + } + final Identifier tableNameIdentifier = nameParts.getObjectName(); + Namespace namespace = database.findNamespace( catalog, schema ); + if ( namespace == null ) { + // When parsing a name, we assume a qualifier is a schema, but could maybe be a catalog + if ( schema != null && catalog == null ) { + final Identifier alternativeCatalog = schema.equals( sqlContext.getDefaultCatalog() ) ? null : schema; + namespace = database.findNamespace( alternativeCatalog, null ); + } + if ( namespace == null ) { + throw new IllegalArgumentException( "Unable to find namespace for " + tableName ); + } + } + final Table table = namespace.locateTable( tableNameIdentifier ); + // The tableNameIdentifier is a physical name, so double check if it is correct + if ( table != null && table.getNameIdentifier().equals( tableNameIdentifier ) ) { + return table; + } + else { + // Fallback to searching for table by comparing physical names + for ( Table namespaceTable : namespace.getTables() ) { + if ( tableNameIdentifier.equals( namespaceTable.getNameIdentifier() ) ) { + return namespaceTable; + } + } + + throw new IllegalArgumentException( "No table with name [" + nameParts.getObjectName() + "] found in namespace [" + namespace.getName() + "]" ); + } + } + + public List findTemporaryTableColumns(EntityPersister entityDescriptor, ModelPart modelPart) { + final int offset = determineModelPartStartIndex( entityDescriptor, modelPart ); + if ( offset == -1 ) { + throw new IllegalStateException( "Couldn't find matching temporary table columns for: " + modelPart ); + } + final int end = offset + modelPart.getJdbcTypeCount(); + // Find a matching cte table column and set that at the current index + return getColumns().subList( offset, end ); + } + + private static int determineModelPartStartIndex(EntityPersister entityDescriptor, ModelPart modelPart) { + boolean hasIdentity = entityDescriptor.getGenerator().generatedOnExecution(); + // Entity with an identity column get HTE_IDENTITY as first column in the temporary table that we skip + int offset = hasIdentity ? 1 : 0; + final int idResult = determineIdStartIndex( offset, entityDescriptor, modelPart ); + if ( idResult <= 0 ) { + return -idResult; + } + offset = idResult; + final EntityDiscriminatorMapping discriminatorMapping = entityDescriptor.getDiscriminatorMapping(); + if ( discriminatorMapping != null && discriminatorMapping.hasPhysicalColumn() && !discriminatorMapping.isFormula() ) { + if ( modelPart == discriminatorMapping ) { + return offset; + } + offset += discriminatorMapping.getJdbcTypeCount(); + } + final AttributeMappingsList attributeMappings = entityDescriptor.getAttributeMappings(); + for ( int i = 0; i < attributeMappings.size(); i++ ) { + AttributeMapping attribute = attributeMappings.get( i ); + if ( !( attribute instanceof PluralAttributeMapping ) ) { + final int result = determineModelPartStartIndex( offset, attribute, modelPart ); + if ( result <= 0 ) { + return -result; + } + offset = result; + } + } + return -1; + } + + private static int determineIdStartIndex(int offset, EntityPersister entityDescriptor, ModelPart modelPart) { + final int originalOffset = offset; + do { + final EntityIdentifierMapping identifierMapping = entityDescriptor.getIdentifierMapping(); + final int result = determineModelPartStartIndex( originalOffset, identifierMapping, modelPart ); + offset = result; + if ( result <= 0 ) { + break; + } + entityDescriptor = (EntityPersister) entityDescriptor.getSuperMappingType(); + } while ( entityDescriptor != null ); + + return offset; + } + + private static int determineModelPartStartIndex(int offset, ModelPart modelPart, ModelPart modelPartToFind) { + if ( modelPart == modelPartToFind ) { + return -offset; + } + if ( modelPart instanceof EntityValuedModelPart entityValuedModelPart ) { + final ModelPart keyPart = + modelPart instanceof Association association + ? association.getForeignKeyDescriptor() + : entityValuedModelPart.getEntityMappingType().getIdentifierMapping(); + return determineModelPartStartIndex( offset, keyPart, modelPartToFind ); + } + else if ( modelPart instanceof EmbeddableValuedModelPart embeddablePart ) { + final AttributeMappingsList attributeMappings = + embeddablePart.getEmbeddableTypeDescriptor().getAttributeMappings(); + for ( int i = 0; i < attributeMappings.size(); i++ ) { + final AttributeMapping mapping = attributeMappings.get( i ); + final int result = determineModelPartStartIndex( offset, mapping, modelPartToFind ); + if ( result <= 0 ) { + return result; + } + offset = result; + } + return offset; + } + else if ( modelPart instanceof BasicValuedModelPart basicModelPart ) { + return offset + (basicModelPart.isInsertable() ? modelPart.getJdbcTypeCount() : 0); + } + return offset + modelPart.getJdbcTypeCount(); + } + public EntityMappingType getEntityDescriptor() { return entityDescriptor; } @@ -496,6 +676,10 @@ public String getQualifiedTableName() { return qualifiedTableName; } + public TemporaryTableKind getTemporaryTableKind() { + return temporaryTableKind; + } + public List getColumns() { return columns; } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/temptable/TemporaryTableStrategy.java b/hibernate-core/src/main/java/org/hibernate/dialect/temptable/TemporaryTableStrategy.java new file mode 100644 index 000000000000..22af2dce0179 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/dialect/temptable/TemporaryTableStrategy.java @@ -0,0 +1,85 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.dialect.temptable; + +import org.checkerframework.checker.nullness.qual.Nullable; +import org.hibernate.query.sqm.mutation.spi.AfterUseAction; +import org.hibernate.query.sqm.mutation.spi.BeforeUseAction; + +/** + * Defines how to interact with a certain temporary table kind. + * + * @since 7.1 + */ +public interface TemporaryTableStrategy { + + /** + * Returns an adjusted table name that can be used for temporary tables. + */ + String adjustTemporaryTableName(String desiredTableName); + + /** + * The kind of temporary tables that are supported on this database. + */ + TemporaryTableKind getTemporaryTableKind(); + + /** + * An arbitrary SQL fragment appended to the end of the statement to + * create a temporary table, specifying dialect-specific options, or + * {@code null} if there are no options to specify. + */ + @Nullable String getTemporaryTableCreateOptions(); + + /** + * The command to create a temporary table. + */ + String getTemporaryTableCreateCommand(); + + /** + * The command to drop a temporary table. + */ + String getTemporaryTableDropCommand(); + + /** + * The command to truncate a temporary table. + */ + String getTemporaryTableTruncateCommand(); + + /** + * Annotation to be appended to the end of each COLUMN clause for temporary tables. + * + * @param sqlTypeCode The SQL type code + * @return The annotation to be appended, for example, {@code COLLATE DATABASE_DEFAULT} in SQL Server + */ + String getCreateTemporaryTableColumnAnnotation(int sqlTypeCode); + + /** + * The action to take after finishing use of a temporary table. + */ + AfterUseAction getTemporaryTableAfterUseAction(); + + /** + * The action to take before beginning use of a temporary table. + */ + BeforeUseAction getTemporaryTableBeforeUseAction(); + + /** + * Does this database support primary keys for temporary tables for this strategy? + * + * @return true by default, since most do + */ + default boolean supportsTemporaryTablePrimaryKey() { + return true; + } + + /** + * Does this database support null constraints for temporary table columns for this strategy? + * + * @return true by default, since most do + */ + default boolean supportsTemporaryTableNullConstraint() { + return true; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/temptable/TransactSQLLocalTemporaryTableStrategy.java b/hibernate-core/src/main/java/org/hibernate/dialect/temptable/TransactSQLLocalTemporaryTableStrategy.java new file mode 100644 index 000000000000..65d13b5dd7e2 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/dialect/temptable/TransactSQLLocalTemporaryTableStrategy.java @@ -0,0 +1,32 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.dialect.temptable; + +import org.hibernate.query.sqm.mutation.spi.AfterUseAction; + +/** + * Transact-SQL specific local temporary table strategy. + */ +public class TransactSQLLocalTemporaryTableStrategy extends StandardLocalTemporaryTableStrategy { + + public static final TransactSQLLocalTemporaryTableStrategy INSTANCE = new TransactSQLLocalTemporaryTableStrategy(); + + @Override + public String adjustTemporaryTableName(String desiredTableName) { + return '#' + desiredTableName; + } + + @Override + public String getTemporaryTableCreateCommand() { + return "create table"; + } + + @Override + public AfterUseAction getTemporaryTableAfterUseAction() { + // sql-server, at least needed this dropped after use; strange! + return AfterUseAction.DROP; + } + +} diff --git a/hibernate-core/src/main/java/org/hibernate/id/insert/AbstractReturningDelegate.java b/hibernate-core/src/main/java/org/hibernate/id/insert/AbstractReturningDelegate.java index 2f7c3a2503ae..85bd2f068ba4 100644 --- a/hibernate-core/src/main/java/org/hibernate/id/insert/AbstractReturningDelegate.java +++ b/hibernate-core/src/main/java/org/hibernate/id/insert/AbstractReturningDelegate.java @@ -62,6 +62,8 @@ public GeneratedValues performMutation( @Override public final GeneratedValues performInsertReturning(String sql, SharedSessionContractImplementor session, Binder binder) { + session.getJdbcServices().getSqlStatementLogger().logStatement( sql ); + try { // prepare and execute the insert final var insert = prepareStatement( sql, session ); diff --git a/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java b/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java index 58f1959ccc4e..cb3d8a6ac61f 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java @@ -4823,7 +4823,8 @@ private void inheritSupertypeSpecialAttributeMappings() { } private void prepareMultiTableMutationStrategy(MappingModelCreationProcess creationProcess) { - if ( hasMultipleTables() ) { + // No need for multi-table mutation strategy for subselect entity since update/delete don't make sense + if ( !isSubselect() && hasMultipleTables() ) { creationProcess.registerInitializationCallback( "Entity(" + getEntityName() + ") `sqmMultiTableMutationStrategy` interpretation", () -> { @@ -4842,7 +4843,8 @@ private void prepareMultiTableMutationStrategy(MappingModelCreationProcess creat } private void prepareMultiTableInsertStrategy(MappingModelCreationProcess creationProcess) { - if ( hasMultipleTables() || generatorNeedsMultiTableInsert() ) { + // No need for multi-table insert strategy for subselect entity since insert doesn't make sense + if ( !isSubselect() && ( hasMultipleTables() || generatorNeedsMultiTableInsert() ) ) { creationProcess.registerInitializationCallback( "Entity(" + getEntityName() + ") `sqmMultiTableInsertStrategy` interpretation", () -> { @@ -4860,6 +4862,11 @@ private void prepareMultiTableInsertStrategy(MappingModelCreationProcess creatio } } + private boolean isSubselect() { + // For the lack of a + return getRootTableName().charAt( 0 ) == '('; + } + private boolean generatorNeedsMultiTableInsert() { final Generator generator = getGenerator(); if ( generator instanceof BulkInsertionCapableIdentifierGenerator diff --git a/hibernate-core/src/main/java/org/hibernate/query/spi/QueryEngineOptions.java b/hibernate-core/src/main/java/org/hibernate/query/spi/QueryEngineOptions.java index 3b5d47e47783..24d2089a780e 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/spi/QueryEngineOptions.java +++ b/hibernate-core/src/main/java/org/hibernate/query/spi/QueryEngineOptions.java @@ -8,7 +8,7 @@ import org.hibernate.jpa.spi.JpaCompliance; import org.hibernate.metamodel.mapping.EntityMappingType; -import org.hibernate.metamodel.mapping.internal.MappingModelCreationProcess; +import org.hibernate.metamodel.spi.RuntimeModelCreationContext; import org.hibernate.query.criteria.ValueHandlingMode; import org.hibernate.query.hql.HqlTranslator; import org.hibernate.query.sqm.function.SqmFunctionDescriptor; @@ -83,7 +83,7 @@ public interface QueryEngineOptions { * * @see org.hibernate.cfg.QuerySettings#QUERY_MULTI_TABLE_MUTATION_STRATEGY */ - SqmMultiTableMutationStrategy resolveCustomSqmMultiTableMutationStrategy(EntityMappingType rootEntityDescriptor, MappingModelCreationProcess creationProcess); + SqmMultiTableMutationStrategy resolveCustomSqmMultiTableMutationStrategy(EntityMappingType rootEntityDescriptor, RuntimeModelCreationContext creationContext); /** * Contract for handling SQM trees representing insertion (INSERT) queries where the @@ -91,7 +91,7 @@ public interface QueryEngineOptions { * * @see org.hibernate.cfg.QuerySettings#QUERY_MULTI_TABLE_INSERT_STRATEGY */ - SqmMultiTableInsertStrategy resolveCustomSqmMultiTableInsertStrategy(EntityMappingType rootEntityDescriptor, MappingModelCreationProcess creationProcess); + SqmMultiTableInsertStrategy resolveCustomSqmMultiTableInsertStrategy(EntityMappingType rootEntityDescriptor, RuntimeModelCreationContext creationContext); /** * @see org.hibernate.cfg.JpaComplianceSettings diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/SqmMultiTableMutationStrategyProviderStandard.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/SqmMultiTableMutationStrategyProviderStandard.java index abfec8836bad..49d39c559633 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/SqmMultiTableMutationStrategyProviderStandard.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/SqmMultiTableMutationStrategyProviderStandard.java @@ -33,7 +33,7 @@ public SqmMultiTableMutationStrategy createMutationStrategy( return specifiedStrategy; } final SqmMultiTableMutationStrategy specifiedEntityBaseStrategy = - options.resolveCustomSqmMultiTableMutationStrategy( rootEntityDescriptor, creationProcess ); + options.resolveCustomSqmMultiTableMutationStrategy( rootEntityDescriptor, creationContext ); if ( specifiedEntityBaseStrategy != null ) { return specifiedEntityBaseStrategy; } @@ -52,7 +52,7 @@ public SqmMultiTableInsertStrategy createInsertStrategy( return specifiedStrategy; } final SqmMultiTableInsertStrategy specifiedEntityBaseStrategy = - options.resolveCustomSqmMultiTableInsertStrategy( rootEntityDescriptor, creationProcess ); + options.resolveCustomSqmMultiTableInsertStrategy( rootEntityDescriptor, creationContext ); if ( specifiedEntityBaseStrategy != null ) { return specifiedEntityBaseStrategy; } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/SqmMutationStrategyHelper.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/SqmMutationStrategyHelper.java index 01a425698a74..53f2a9bf0d64 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/SqmMutationStrategyHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/SqmMutationStrategyHelper.java @@ -4,14 +4,30 @@ */ package org.hibernate.query.sqm.mutation.internal; +import java.util.function.BiConsumer; import java.util.function.BiFunction; import java.util.function.Consumer; import org.hibernate.engine.jdbc.spi.JdbcServices; import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.metamodel.mapping.Association; +import org.hibernate.metamodel.mapping.AttributeMapping; +import org.hibernate.metamodel.mapping.AttributeMappingsList; +import org.hibernate.metamodel.mapping.BasicValuedModelPart; +import org.hibernate.metamodel.mapping.DiscriminatedAssociationModelPart; +import org.hibernate.metamodel.mapping.EmbeddableMappingType; +import org.hibernate.metamodel.mapping.EmbeddableValuedModelPart; +import org.hibernate.metamodel.mapping.EntityIdentifierMapping; import org.hibernate.metamodel.mapping.EntityMappingType; +import org.hibernate.metamodel.mapping.EntityValuedModelPart; +import org.hibernate.metamodel.mapping.ForeignKeyDescriptor; +import org.hibernate.metamodel.mapping.JdbcMappingContainer; +import org.hibernate.metamodel.mapping.ModelPart; import org.hibernate.metamodel.mapping.PluralAttributeMapping; +import org.hibernate.metamodel.mapping.SelectableMapping; import org.hibernate.metamodel.mapping.internal.EmbeddedAttributeMapping; +import org.hibernate.metamodel.mapping.internal.ToOneAttributeMapping; +import org.hibernate.persister.entity.EntityPersister; import org.hibernate.sql.ast.tree.delete.DeleteStatement; import org.hibernate.sql.ast.tree.from.NamedTableReference; import org.hibernate.sql.ast.tree.from.TableReference; @@ -177,4 +193,90 @@ private static void cleanUpCollectionTable( ); } } + + public static boolean isId(JdbcMappingContainer type) { + return type instanceof EntityIdentifierMapping || type instanceof AttributeMapping attributeMapping + && isPartOfId( attributeMapping ); + } + + public static boolean isPartOfId(AttributeMapping attributeMapping) { + return attributeMapping.getDeclaringType() instanceof EmbeddableMappingType embeddableMappingType + && (embeddableMappingType.getEmbeddedValueMapping().isEntityIdentifierMapping() + || isId( embeddableMappingType.getEmbeddedValueMapping() )); + } + + + public static void forEachSelectableMapping(String prefix, ModelPart modelPart, BiConsumer consumer) { + if ( modelPart instanceof BasicValuedModelPart basicModelPart ) { + if ( basicModelPart.isInsertable() ) { + consumer.accept( prefix, basicModelPart ); + } + } + else if ( modelPart instanceof EntityValuedModelPart entityPart ) { + final Association association = (Association) modelPart; + if ( association.getForeignKeyDescriptor() == null ) { + // This is expected to happen when processing a + // PostInitCallbackEntry because the callbacks + // are not ordered. The exception is caught in + // MappingModelCreationProcess.executePostInitCallbacks() + // and the callback is re-queued. + throw new IllegalStateException( "ForeignKeyDescriptor not ready for [" + association.getPartName() + "] on entity: " + modelPart.findContainingEntityMapping().getEntityName() ); + } + if ( association.getSideNature() != ForeignKeyDescriptor.Nature.KEY ) { + // Inverse one-to-one receives no column + return; + } + if ( association instanceof ToOneAttributeMapping toOneMapping ) { + final EntityPersister declaringEntityPersister = toOneMapping.findContainingEntityMapping() + .getEntityPersister(); + final int tableIndex = findTableIndex( + declaringEntityPersister, + toOneMapping.getIdentifyingColumnsTableExpression() + ); + if ( declaringEntityPersister.isInverseTable( tableIndex ) ) { + // Actually, this is like ForeignKeyDescriptor.Nature.TARGET, + // but for some reason it isn't + return; + } + } + forEachSelectableMapping( + prefix + "_" + entityPart.getPartName(), + association.getForeignKeyDescriptor().getKeyPart(), + consumer + ); + } + else if ( modelPart instanceof DiscriminatedAssociationModelPart discriminatedPart ) { + final String newPrefix = prefix + "_" + discriminatedPart.getPartName() + "_"; + forEachSelectableMapping( + newPrefix + "discriminator", + discriminatedPart.getDiscriminatorPart(), + consumer + ); + forEachSelectableMapping( + newPrefix + "key", + discriminatedPart.getKeyPart(), + consumer + ); + } + else { + final EmbeddableValuedModelPart embeddablePart = ( EmbeddableValuedModelPart ) modelPart; + final AttributeMappingsList attributeMappings = embeddablePart.getEmbeddableTypeDescriptor().getAttributeMappings(); + for ( int i = 0; i < attributeMappings.size(); i++ ) { + AttributeMapping mapping = attributeMappings.get( i ); + if ( !( mapping instanceof PluralAttributeMapping ) ) { + forEachSelectableMapping( prefix + "_" + mapping.getAttributeName(), mapping, consumer ); + } + } + } + } + + private static int findTableIndex(EntityPersister declaringEntityPersister, String tableExpression) { + final String[] tableNames = declaringEntityPersister.getTableNames(); + for ( int i = 0; i < tableNames.length; i++ ) { + if ( tableExpression.equals( tableNames[i] ) ) { + return i; + } + } + throw new IllegalStateException( "Couldn't find table index for [" + tableExpression + "] in: " + declaringEntityPersister.getEntityName() ); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/cte/CteInsertHandler.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/cte/CteInsertHandler.java index ac673b6c8d41..78b20ec6ef0c 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/cte/CteInsertHandler.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/cte/CteInsertHandler.java @@ -4,28 +4,21 @@ */ package org.hibernate.query.sqm.mutation.internal.cte; -import java.util.AbstractMap; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.function.BiConsumer; -import java.util.stream.Collectors; - import org.hibernate.boot.model.naming.Identifier; import org.hibernate.dialect.Dialect; import org.hibernate.engine.jdbc.spi.JdbcServices; import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.generator.Generator; import org.hibernate.id.BulkInsertionCapableIdentifierGenerator; import org.hibernate.id.OptimizableGenerator; import org.hibernate.id.enhanced.Optimizer; import org.hibernate.internal.util.collections.CollectionHelper; import org.hibernate.internal.util.collections.Stack; +import org.hibernate.metamodel.mapping.AttributeMapping; import org.hibernate.metamodel.mapping.BasicValuedMapping; -import org.hibernate.metamodel.mapping.EntityIdentifierMapping; import org.hibernate.metamodel.mapping.EntityMappingType; import org.hibernate.metamodel.mapping.MappingModelExpressible; +import org.hibernate.metamodel.mapping.ModelPart; import org.hibernate.metamodel.mapping.SqlExpressible; import org.hibernate.persister.entity.EntityPersister; import org.hibernate.query.SemanticException; @@ -42,6 +35,7 @@ import org.hibernate.query.sqm.mutation.internal.InsertHandler; import org.hibernate.query.sqm.mutation.internal.MultiTableSqmMutationConverter; import org.hibernate.query.sqm.mutation.internal.SqmInsertStrategyHelper; +import org.hibernate.query.sqm.mutation.internal.SqmMutationStrategyHelper; import org.hibernate.query.sqm.spi.SqmParameterMappingModelResolutionAccess; import org.hibernate.query.sqm.sql.BaseSqmToSqlAstConverter; import org.hibernate.query.sqm.sql.internal.SqmPathInterpretation; @@ -104,9 +98,17 @@ import org.hibernate.sql.results.internal.RowTransformerSingularReturnImpl; import org.hibernate.sql.results.internal.SqlSelectionImpl; import org.hibernate.sql.results.spi.ListResultsConsumer; -import org.hibernate.generator.Generator; import org.hibernate.type.BasicType; +import java.util.AbstractMap; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.function.BiConsumer; +import java.util.stream.Collectors; + /** * * @author Christian Beikov @@ -200,16 +202,8 @@ public int execute(DomainQueryExecutionContext executionContext) { final BaseSqmToSqlAstConverter.AdditionalInsertValues additionalInsertValues = sqmConverter.visitInsertionTargetPaths( (assignable, columnReferences) -> { final SqmPathInterpretation pathInterpretation = (SqmPathInterpretation) assignable; - final int offset = CteTable.determineModelPartStartIndex( - entityDescriptor, - pathInterpretation.getExpressionType() - ); - if ( offset == -1 ) { - throw new IllegalStateException( "Couldn't find matching cte column for: " + ( (Expression) assignable ).getExpressionType() ); - } - final int end = offset + pathInterpretation.getExpressionType().getJdbcTypeCount(); - // Find a matching cte table column and set that at the current index - final List columns = cteTable.getCteColumns().subList( offset, end ); + final List columns = + cteTable.findCteColumns( entityDescriptor, pathInterpretation.getExpressionType() ); targetPathCteColumns.addAll( columns ); targetPathColumns.add( new AbstractMap.SimpleEntry<>( @@ -294,6 +288,15 @@ public int execute(DomainQueryExecutionContext executionContext) { ); } } + querySpec.getSelectClause().addSqlSelection( + new SqlSelectionImpl( + 0, + SqmInsertStrategyHelper.createRowNumberingExpression( + querySpec, + sessionFactory + ) + ) + ); final ValuesTableGroup valuesTableGroup = new ValuesTableGroup( navigablePath, entityDescriptor.getEntityPersister(), @@ -706,8 +709,7 @@ protected String addDmlCtes( final ConflictClause conflictClause = sqmConverter.visitConflictClause( sqmStatement.getConflictClause() ); final int tableSpan = persister.getTableSpan(); - final String[] rootKeyColumns = persister.getKeyColumns( 0 ); - final List keyCteColumns = queryCte.getCteTable().getCteColumns().subList( 0, rootKeyColumns.length ); + final List keyCteColumns = findCteColumns( persister.getIdentifierMapping(), queryCte.getCteTable().getCteColumns() ); for ( int tableIndex = 0; tableIndex < tableSpan; tableIndex++ ) { final String tableExpression = persister.getTableName( tableIndex ); final TableReference updatingTableReference = updatingTableGroup.getTableReference( @@ -918,7 +920,7 @@ protected String addDmlCtes( new SqlSelectionImpl( new ColumnReference( "e", - rootKeyColumns[j], + keyCteColumns.get( j ).getColumnExpression(), false, null, null @@ -938,7 +940,7 @@ protected String addDmlCtes( for ( Map.Entry, Assignment> entry : assignmentList ) { final Assignment assignment = entry.getValue(); // Skip the id mapping here as we handled that already - if ( assignment.getAssignedValue().getExpressionType() instanceof EntityIdentifierMapping ) { + if ( SqmMutationStrategyHelper.isId( assignment.getAssignedValue().getExpressionType() ) ) { continue; } final List assignmentReferences = assignment.getAssignable().getColumnReferences(); @@ -1019,6 +1021,25 @@ else if ( targetColumnsContainAllConstraintColumns( dmlStatement, conflictClause return getCteTableName( rootTableName ); } + private List findCteColumns(ModelPart modelPart, List cteColumns) { + final int numberOfColumns = modelPart.getJdbcTypeCount(); + final List columns = new ArrayList<>( numberOfColumns ); + final String prefix; + if ( modelPart instanceof AttributeMapping attributeMapping ) { + prefix = attributeMapping.getAttributeName(); + } + else { + prefix = "id"; + } + CteTable.forEachCteColumn( prefix, modelPart, e -> { + columns.add( cteColumns.stream() + .filter( cteColumn -> cteColumn.getColumnExpression().equals( e.getColumnExpression() ) ) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException( "Column reference not found: " + e.getColumnExpression() ) ) ); + } ); + return columns; + } + private void handleConflictClause( CteTable dmlResultCte, InsertSelectStatement insertStatement, diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/AbstractDeleteExecutionDelegate.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/AbstractDeleteExecutionDelegate.java index 539f698fd099..20e27d00055d 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/AbstractDeleteExecutionDelegate.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/AbstractDeleteExecutionDelegate.java @@ -7,6 +7,7 @@ import java.util.function.Function; import org.hibernate.dialect.temptable.TemporaryTable; +import org.hibernate.dialect.temptable.TemporaryTableStrategy; import org.hibernate.engine.spi.LoadQueryInfluencers; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; @@ -24,7 +25,8 @@ public abstract class AbstractDeleteExecutionDelegate implements TableBasedDeleteHandler.ExecutionDelegate { private final EntityMappingType entityDescriptor; private final TemporaryTable idTable; - private final AfterUseAction afterUseAction; + private final TemporaryTableStrategy temporaryTableStrategy; + private final boolean forceDropAfterUse; private final SqmDeleteStatement sqmDelete; private final DomainParameterXref domainParameterXref; private final SessionFactoryImplementor sessionFactory; @@ -35,7 +37,8 @@ public abstract class AbstractDeleteExecutionDelegate implements TableBasedDelet public AbstractDeleteExecutionDelegate( EntityMappingType entityDescriptor, TemporaryTable idTable, - AfterUseAction afterUseAction, + TemporaryTableStrategy temporaryTableStrategy, + boolean forceDropAfterUse, SqmDeleteStatement sqmDelete, DomainParameterXref domainParameterXref, QueryOptions queryOptions, @@ -45,7 +48,8 @@ public AbstractDeleteExecutionDelegate( SessionFactoryImplementor sessionFactory) { this.entityDescriptor = entityDescriptor; this.idTable = idTable; - this.afterUseAction = afterUseAction; + this.temporaryTableStrategy = temporaryTableStrategy; + this.forceDropAfterUse = forceDropAfterUse; this.sqmDelete = sqmDelete; this.domainParameterXref = domainParameterXref; this.sessionFactory = sessionFactory; @@ -71,8 +75,12 @@ public TemporaryTable getIdTable() { return idTable; } + public TemporaryTableStrategy getTemporaryTableStrategy() { + return temporaryTableStrategy; + } + public AfterUseAction getAfterUseAction() { - return afterUseAction; + return forceDropAfterUse ? AfterUseAction.DROP : temporaryTableStrategy.getTemporaryTableAfterUseAction(); } public SqmDeleteStatement getSqmDelete() { diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/ExecuteWithTemporaryTableHelper.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/ExecuteWithTemporaryTableHelper.java index 8459f100879d..a4d74fc863fc 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/ExecuteWithTemporaryTableHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/ExecuteWithTemporaryTableHelper.java @@ -12,6 +12,7 @@ import org.hibernate.dialect.temptable.TemporaryTableHelper; import org.hibernate.dialect.temptable.TemporaryTableHelper.TemporaryTableCreationWork; import org.hibernate.dialect.temptable.TemporaryTableHelper.TemporaryTableDropWork; +import org.hibernate.dialect.temptable.TemporaryTableStrategy; import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment; import org.hibernate.engine.jdbc.spi.JdbcServices; import org.hibernate.engine.spi.SharedSessionContractImplementor; @@ -275,12 +276,35 @@ private static void applyIdTableRestrictions( } } + @Deprecated(forRemoval = true, since = "7.1") public static void performBeforeTemporaryTableUseActions( TemporaryTable temporaryTable, ExecutionContext executionContext) { + performBeforeTemporaryTableUseActions( + temporaryTable, + executionContext.getSession().getDialect().getTemporaryTableBeforeUseAction(), + executionContext + ); + } + + public static void performBeforeTemporaryTableUseActions( + TemporaryTable temporaryTable, + TemporaryTableStrategy temporaryTableStrategy, + ExecutionContext executionContext) { + performBeforeTemporaryTableUseActions( + temporaryTable, + temporaryTableStrategy.getTemporaryTableBeforeUseAction(), + executionContext + ); + } + + private static void performBeforeTemporaryTableUseActions( + TemporaryTable temporaryTable, + BeforeUseAction beforeUseAction, + ExecutionContext executionContext) { final var factory = executionContext.getSession().getFactory(); final Dialect dialect = factory.getJdbcServices().getDialect(); - if ( dialect.getTemporaryTableBeforeUseAction() == BeforeUseAction.CREATE ) { + if ( beforeUseAction == BeforeUseAction.CREATE ) { final var temporaryTableCreationWork = new TemporaryTableCreationWork( temporaryTable, factory ); final var ddlTransactionHandling = dialect.getTemporaryTableDdlTransactionHandling(); diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/GlobalTemporaryTableInsertStrategy.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/GlobalTemporaryTableInsertStrategy.java index 6283932b4b10..253fec77acb4 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/GlobalTemporaryTableInsertStrategy.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/GlobalTemporaryTableInsertStrategy.java @@ -5,7 +5,11 @@ package org.hibernate.query.sqm.mutation.internal.temptable; import org.hibernate.dialect.temptable.TemporaryTable; +import org.hibernate.dialect.temptable.TemporaryTableKind; +import org.hibernate.dialect.temptable.TemporaryTableStrategy; import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.metamodel.mapping.EntityMappingType; +import org.hibernate.metamodel.spi.RuntimeModelCreationContext; import org.hibernate.query.spi.DomainQueryExecutionContext; import org.hibernate.query.sqm.internal.DomainParameterXref; import org.hibernate.query.sqm.mutation.spi.SqmMultiTableInsertStrategy; @@ -18,6 +22,30 @@ */ public class GlobalTemporaryTableInsertStrategy extends GlobalTemporaryTableStrategy implements SqmMultiTableInsertStrategy { + public GlobalTemporaryTableInsertStrategy(EntityMappingType rootEntityDescriptor, RuntimeModelCreationContext runtimeModelCreationContext) { + this( + rootEntityDescriptor, + requireGlobalTemporaryTableStrategy( runtimeModelCreationContext.getDialect() ), + runtimeModelCreationContext + ); + } + + private GlobalTemporaryTableInsertStrategy( + EntityMappingType rootEntityDescriptor, + TemporaryTableStrategy temporaryTableStrategy, + RuntimeModelCreationContext runtimeModelCreationContext) { + this( + TemporaryTable.createEntityTable( + rootEntityDescriptor, + basename -> temporaryTableStrategy.adjustTemporaryTableName( TemporaryTable.ENTITY_TABLE_PREFIX + basename ), + TemporaryTableKind.GLOBAL, + runtimeModelCreationContext.getDialect(), + runtimeModelCreationContext + ), + runtimeModelCreationContext.getSessionFactory() + ); + } + public GlobalTemporaryTableInsertStrategy( TemporaryTable entityTable, SessionFactoryImplementor sessionFactory) { @@ -33,7 +61,8 @@ public int executeInsert( sqmInsertStatement, domainParameterXref, getTemporaryTable(), - getSessionFactory().getJdbcServices().getDialect().getTemporaryTableAfterUseAction(), + getTemporaryTableStrategy(), + false, // generally a global temp table should already track a Connection-specific uid, // but just in case a particular env needs it... session -> session.getSessionIdentifier().toString(), diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/GlobalTemporaryTableMutationStrategy.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/GlobalTemporaryTableMutationStrategy.java index b0aa56938da6..ae865e451520 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/GlobalTemporaryTableMutationStrategy.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/GlobalTemporaryTableMutationStrategy.java @@ -5,7 +5,11 @@ package org.hibernate.query.sqm.mutation.internal.temptable; import org.hibernate.dialect.temptable.TemporaryTable; +import org.hibernate.dialect.temptable.TemporaryTableKind; +import org.hibernate.dialect.temptable.TemporaryTableStrategy; import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.metamodel.mapping.EntityMappingType; +import org.hibernate.metamodel.spi.RuntimeModelCreationContext; import org.hibernate.query.spi.DomainQueryExecutionContext; import org.hibernate.query.sqm.internal.DomainParameterXref; import org.hibernate.query.sqm.mutation.spi.SqmMultiTableMutationStrategy; @@ -19,6 +23,30 @@ */ public class GlobalTemporaryTableMutationStrategy extends GlobalTemporaryTableStrategy implements SqmMultiTableMutationStrategy { + public GlobalTemporaryTableMutationStrategy(EntityMappingType rootEntityDescriptor, RuntimeModelCreationContext runtimeModelCreationContext) { + this( + rootEntityDescriptor, + requireGlobalTemporaryTableStrategy( runtimeModelCreationContext.getDialect() ), + runtimeModelCreationContext + ); + } + + private GlobalTemporaryTableMutationStrategy( + EntityMappingType rootEntityDescriptor, + TemporaryTableStrategy temporaryTableStrategy, + RuntimeModelCreationContext runtimeModelCreationContext) { + this( + TemporaryTable.createIdTable( + rootEntityDescriptor, + basename -> temporaryTableStrategy.adjustTemporaryTableName( TemporaryTable.ID_TABLE_PREFIX + basename ), + TemporaryTableKind.GLOBAL, + runtimeModelCreationContext.getDialect(), + runtimeModelCreationContext + ), + runtimeModelCreationContext.getSessionFactory() + ); + } + public GlobalTemporaryTableMutationStrategy( TemporaryTable idTable, SessionFactoryImplementor sessionFactory) { @@ -34,7 +62,8 @@ public int executeUpdate( sqmUpdate, domainParameterXref, getTemporaryTable(), - getSessionFactory().getJdbcServices().getDialect().getTemporaryTableAfterUseAction(), + getTemporaryTableStrategy(), + false, // generally a global temp table should already track a Connection-specific uid, // but just in case a particular env needs it... session -> session.getSessionIdentifier().toString(), @@ -51,7 +80,8 @@ public int executeDelete( sqmDelete, domainParameterXref, getTemporaryTable(), - getSessionFactory().getJdbcServices().getDialect().getTemporaryTableAfterUseAction(), + getTemporaryTableStrategy(), + false, // generally a global temp table should already track a Connection-specific uid, // but just in case a particular env needs it... session -> session.getSessionIdentifier().toString(), diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/GlobalTemporaryTableStrategy.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/GlobalTemporaryTableStrategy.java index c0dbf6734bac..fca049808b5d 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/GlobalTemporaryTableStrategy.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/GlobalTemporaryTableStrategy.java @@ -6,9 +6,12 @@ import java.sql.Connection; import java.sql.SQLException; +import java.util.Objects; +import org.hibernate.dialect.Dialect; import org.hibernate.dialect.temptable.TemporaryTable; import org.hibernate.dialect.temptable.TemporaryTableHelper; +import org.hibernate.dialect.temptable.TemporaryTableStrategy; import org.hibernate.engine.config.spi.ConfigurationService; import org.hibernate.engine.config.spi.StandardConverters; import org.hibernate.engine.jdbc.connections.spi.JdbcConnectionAccess; @@ -21,6 +24,8 @@ import static org.hibernate.engine.jdbc.JdbcLogging.JDBC_MESSAGE_LOGGER; +import static org.hibernate.internal.util.NullnessUtil.castNonNull; + /** * Strategy based on ANSI SQL's definition of a "global temporary table". * @@ -43,13 +48,23 @@ public class GlobalTemporaryTableStrategy { public GlobalTemporaryTableStrategy(TemporaryTable temporaryTable, SessionFactoryImplementor sessionFactory) { this.temporaryTable = temporaryTable; this.sessionFactory = sessionFactory; + final TemporaryTableStrategy temporaryTableStrategy = requireGlobalTemporaryTableStrategy( sessionFactory.getJdbcServices().getDialect() ); - if ( sessionFactory.getJdbcServices().getDialect().getTemporaryTableAfterUseAction() == AfterUseAction.DROP ) { + if ( temporaryTableStrategy.getTemporaryTableAfterUseAction() == AfterUseAction.DROP ) { throw new IllegalArgumentException( "Global-temp ID tables cannot use AfterUseAction.DROP : " + temporaryTable.getTableExpression() ); } } + protected static TemporaryTableStrategy requireGlobalTemporaryTableStrategy(Dialect dialect) { + return Objects.requireNonNull( dialect.getGlobalTemporaryTableStrategy(), + "Dialect does not define a global temporary table strategy: " + dialect.getClass().getSimpleName() ); + } + + public TemporaryTableStrategy getTemporaryTableStrategy() { + return castNonNull( sessionFactory.getJdbcServices().getDialect().getGlobalTemporaryTableStrategy() ); + } + public EntityMappingType getEntityDescriptor() { return temporaryTable.getEntityDescriptor(); } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/InsertExecutionDelegate.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/InsertExecutionDelegate.java index 915a21405563..680d5ae6a3ab 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/InsertExecutionDelegate.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/InsertExecutionDelegate.java @@ -16,6 +16,7 @@ import org.hibernate.dialect.temptable.TemporaryTable; import org.hibernate.dialect.temptable.TemporaryTableColumn; +import org.hibernate.dialect.temptable.TemporaryTableStrategy; import org.hibernate.engine.FetchTiming; import org.hibernate.engine.jdbc.spi.JdbcServices; import org.hibernate.engine.spi.SessionFactoryImplementor; @@ -32,6 +33,7 @@ import org.hibernate.internal.util.collections.ArrayHelper; import org.hibernate.internal.util.collections.CollectionHelper; import org.hibernate.metamodel.mapping.BasicEntityIdentifierMapping; +import org.hibernate.metamodel.mapping.EntityIdentifierMapping; import org.hibernate.metamodel.mapping.EntityMappingType; import org.hibernate.metamodel.mapping.JdbcMapping; import org.hibernate.metamodel.mapping.MappingModelExpressible; @@ -48,6 +50,7 @@ import org.hibernate.query.sqm.mutation.internal.MultiTableSqmMutationConverter; import org.hibernate.query.sqm.mutation.spi.AfterUseAction; import org.hibernate.query.sqm.spi.SqmParameterMappingModelResolutionAccess; +import org.hibernate.query.sqm.sql.internal.SqmPathInterpretation; import org.hibernate.query.sqm.tree.expression.SqmParameter; import org.hibernate.sql.ast.tree.expression.ColumnReference; import org.hibernate.sql.ast.tree.expression.JdbcParameter; @@ -79,6 +82,7 @@ import org.hibernate.type.descriptor.ValueBinder; import static org.hibernate.generator.EventType.INSERT; +import static org.hibernate.query.sqm.mutation.internal.SqmMutationStrategyHelper.isId; /** * @author Christian Beikov @@ -86,7 +90,8 @@ */ public class InsertExecutionDelegate implements TableBasedInsertHandler.ExecutionDelegate { private final TemporaryTable entityTable; - private final AfterUseAction afterUseAction; + private final TemporaryTableStrategy temporaryTableStrategy; + private final boolean forceDropAfterUse; private final Function sessionUidAccess; private final TableGroup updatingTableGroup; private final InsertSelectStatement insertStatement; @@ -97,13 +102,15 @@ public class InsertExecutionDelegate implements TableBasedInsertHandler.Executio private final JdbcParameterBindings jdbcParameterBindings; private final JdbcParameter sessionUidParameter; - private final Map> assignmentsByTable; + private final boolean assignsId; + private final Map> assignmentsByTable; private final SessionFactoryImplementor sessionFactory; public InsertExecutionDelegate( MultiTableSqmMutationConverter sqmConverter, TemporaryTable entityTable, - AfterUseAction afterUseAction, + TemporaryTableStrategy temporaryTableStrategy, + boolean forceDropAfterUse, Function sessionUidAccess, DomainParameterXref domainParameterXref, TableGroup insertingTableGroup, @@ -114,7 +121,8 @@ public InsertExecutionDelegate( JdbcParameter sessionUidParameter, DomainQueryExecutionContext executionContext) { this.entityTable = entityTable; - this.afterUseAction = afterUseAction; + this.temporaryTableStrategy = temporaryTableStrategy; + this.forceDropAfterUse = forceDropAfterUse; this.sessionUidAccess = sessionUidAccess; this.updatingTableGroup = insertingTableGroup; this.conflictClause = conflictClause; @@ -148,6 +156,7 @@ public MappingModelExpressible getResolvedMappingModelType(SqmParameter MappingModelExpressible getResolvedMappingModelType(SqmParameter new ArrayList<>() ) - .add( assignment ); + assignsId = assignsId || assignable instanceof SqmPathInterpretation pathInterpretation + && isId( pathInterpretation.getExpressionType() ); + + assignmentsByTable.computeIfAbsent( + assignmentTableReference == null ? null : assignmentTableReference.getTableId(), + k -> new ArrayList<>() ).add( assignment ); } + + this.assignsId = assignsId; + } + + private List getPrimaryKeyTableColumns(EntityPersister entityPersister, TemporaryTable entityTable) { + final boolean identityColumn = entityPersister.getGenerator().generatedOnExecution(); + final int startIndex = identityColumn ? 1 : 0; + final int endIndex = startIndex + entityPersister.getIdentifierMapping().getJdbcTypeCount(); + return entityTable.getColumns().subList( startIndex, endIndex ); } @Override @@ -177,6 +199,7 @@ public int execute(ExecutionContext executionContext) { // as we need to split out individual inserts if we have a non-bulk capable optimizer ExecuteWithTemporaryTableHelper.performBeforeTemporaryTableUseActions( entityTable, + temporaryTableStrategy, executionContext ); @@ -246,7 +269,7 @@ public int execute(ExecutionContext executionContext) { ExecuteWithTemporaryTableHelper.performAfterTemporaryTableUseActions( entityTable, sessionUidAccess, - afterUseAction, + forceDropAfterUse ? AfterUseAction.DROP : temporaryTableStrategy.getTemporaryTableAfterUseAction(), executionContext ); } @@ -293,7 +316,7 @@ private int insertRootTable( final EntityPersister entityPersister = entityDescriptor.getEntityPersister(); final Generator generator = entityPersister.getGenerator(); - final List assignments = assignmentsByTable.get( updatingTableReference ); + final List assignments = assignmentsByTable.get( tableExpression ); if ( ( assignments == null || assignments.isEmpty() ) && !generator.generatedOnExecution() && ( !( generator instanceof BulkInsertionCapableIdentifierGenerator ) @@ -317,7 +340,7 @@ private int insertRootTable( entityDescriptor ); querySpec.getFromClause().addRoot( temporaryTableGroup ); - if ( insertStatement.getValuesList().size() == 1 ) { + if ( insertStatement.getValuesList().size() == 1 && conflictClause != null ) { // Potentially apply a limit 1 to allow the use of the conflict clause emulation querySpec.setFetchClauseExpression( new QueryLiteral<>( @@ -330,29 +353,11 @@ private int insertRootTable( final InsertSelectStatement insertStatement = new InsertSelectStatement( dmlTableReference ); insertStatement.setConflictClause( conflictClause ); insertStatement.setSourceSelectStatement( querySpec ); - if ( assignments != null ) { - for ( Assignment assignment : assignments ) { - final Assignable assignable = assignment.getAssignable(); - insertStatement.addTargetColumnReferences( assignable.getColumnReferences() ); - for ( ColumnReference columnReference : assignable.getColumnReferences() ) { - querySpec.getSelectClause().addSqlSelection( - new SqlSelectionImpl( - new ColumnReference( - temporaryTableReference.getIdentificationVariable(), - columnReference.getColumnExpression(), - false, - null, - columnReference.getJdbcMapping() - ) - ) - ); - } - } - } + applyAssignments( assignments, insertStatement, temporaryTableReference ); final JdbcServices jdbcServices = sessionFactory.getJdbcServices(); final Map entityTableToRootIdentity; final SharedSessionContractImplementor session = executionContext.getSession(); - if ( generator.generatedOnExecution() ) { + if ( !assignsId && generator.generatedOnExecution() ) { final BasicEntityIdentifierMapping identifierMapping = (BasicEntityIdentifierMapping) entityDescriptor.getIdentifierMapping(); final QuerySpec idSelectQuerySpec = new QuerySpec( true ); @@ -372,6 +377,20 @@ private int insertRootTable( SortDirection.ASCENDING ) ); + if ( entityTable.getSessionUidColumn() != null ) { + final TemporaryTableColumn sessionUidColumn = entityTable.getSessionUidColumn(); + idSelectQuerySpec.applyPredicate( new ComparisonPredicate( + new ColumnReference( + temporaryTableReference, + sessionUidColumn.getColumnName(), + false, + null, + sessionUidColumn.getJdbcMapping() + ), + ComparisonOperator.EQUAL, + sessionUidParameter + ) ); + } final SelectStatement selectStatement = new SelectStatement( idSelectQuerySpec, Collections.singletonList( @@ -392,7 +411,7 @@ private int insertRootTable( .translate( null, executionContext.getQueryOptions() ); final List list = jdbcServices.getJdbcSelectExecutor().list( jdbcSelect, - JdbcParameterBindings.NO_BINDINGS, + jdbcParameterBindings, executionContext, null, null, @@ -417,30 +436,19 @@ private int insertRootTable( // if the target paths don't already contain the id, and we need identifier generation, // then we load update rows from the temporary table with the generated identifiers, // to then insert into the target tables in once statement - if ( needsIdentifierGeneration( generator ) - && insertStatement.getTargetColumns().stream() + if ( insertStatement.getTargetColumns().stream() .noneMatch( c -> keyColumns[0].equals( c.getColumnExpression() ) ) ) { - final BasicEntityIdentifierMapping identifierMapping = - (BasicEntityIdentifierMapping) entityDescriptor.getIdentifierMapping(); - final JdbcParameter rowNumber = new JdbcParameterImpl( identifierMapping.getJdbcMapping() ); - final JdbcParameter rootIdentity = new JdbcParameterImpl( identifierMapping.getJdbcMapping() ); - final List temporaryTableAssignments = new ArrayList<>( 1 ); - final ColumnReference idColumnReference = new ColumnReference( (String) null, identifierMapping ); - temporaryTableAssignments.add( new Assignment( idColumnReference, rootIdentity ) ); - final TemporaryTableColumn rowNumberColumn; + final EntityIdentifierMapping identifierMapping = entityDescriptor.getIdentifierMapping(); + final List primaryKeyTableColumns = + getPrimaryKeyTableColumns( entityPersister, entityTable ); + final TemporaryTableColumn sessionUidColumn; final Predicate sessionUidPredicate; if ( entityTable.getSessionUidColumn() == null ) { - rowNumberColumn = entityTable.getColumns().get( - entityTable.getColumns().size() - 1 - ); sessionUidColumn = null; sessionUidPredicate = null; } else { - rowNumberColumn = entityTable.getColumns().get( - entityTable.getColumns().size() - 2 - ); sessionUidColumn = entityTable.getSessionUidColumn(); sessionUidPredicate = new ComparisonPredicate( new ColumnReference( @@ -454,97 +462,127 @@ private int insertRootTable( sessionUidParameter ); } - final UpdateStatement updateStatement = new UpdateStatement( - temporaryTableReference, - temporaryTableAssignments, - Predicate.combinePredicates( - new ComparisonPredicate( - new ColumnReference( - (String) null, - rowNumberColumn.getColumnName(), - false, - null, - rowNumberColumn.getJdbcMapping() + if ( needsIdentifierGeneration( generator ) ) { + final BasicEntityIdentifierMapping basicIdentifierMapping = (BasicEntityIdentifierMapping) identifierMapping; + final JdbcParameter rootIdentity = new JdbcParameterImpl( basicIdentifierMapping.getJdbcMapping() ); + final List temporaryTableAssignments = new ArrayList<>( 1 ); + final ColumnReference idColumnReference = new ColumnReference( (String) null, basicIdentifierMapping ); + temporaryTableAssignments.add( new Assignment( idColumnReference, rootIdentity ) ); + + final JdbcParameter rowNumber = new JdbcParameterImpl( basicIdentifierMapping.getJdbcMapping() ); + final int rowNumberIndex = + entityTable.getColumns().size() - (entityTable.getSessionUidColumn() == null ? 1 : 2); + final TemporaryTableColumn rowNumberColumn = entityTable.getColumns().get( rowNumberIndex ); + + final UpdateStatement updateStatement = new UpdateStatement( + temporaryTableReference, + temporaryTableAssignments, + Predicate.combinePredicates( + new ComparisonPredicate( + new ColumnReference( + (String) null, + rowNumberColumn.getColumnName(), + false, + null, + rowNumberColumn.getJdbcMapping() + ), + ComparisonOperator.EQUAL, + rowNumber ), - ComparisonOperator.EQUAL, - rowNumber - ), - sessionUidPredicate - ) - ); - - final JdbcOperationQueryMutation jdbcUpdate = jdbcServices.getJdbcEnvironment() - .getSqlAstTranslatorFactory() - .buildMutationTranslator( sessionFactory, updateStatement ) - .translate( null, executionContext.getQueryOptions() ); - final JdbcParameterBindings updateBindings = new JdbcParameterBindingsImpl( 2 ); - if ( sessionUidColumn != null ) { - updateBindings.addBinding( - sessionUidParameter, - new JdbcParameterBindingImpl( - sessionUidColumn.getJdbcMapping(), - UUID.fromString( sessionUidAccess.apply(session) ) + sessionUidPredicate ) ); + + final JdbcOperationQueryMutation jdbcUpdate = jdbcServices.getJdbcEnvironment() + .getSqlAstTranslatorFactory() + .buildMutationTranslator( sessionFactory, updateStatement ) + .translate( null, executionContext.getQueryOptions() ); + final JdbcParameterBindings updateBindings = new JdbcParameterBindingsImpl( 2 ); + if ( sessionUidColumn != null ) { + updateBindings.addBinding( + sessionUidParameter, + new JdbcParameterBindingImpl( + sessionUidColumn.getJdbcMapping(), + UUID.fromString( sessionUidAccess.apply( session ) ) + ) + ); + } + + final BeforeExecutionGenerator beforeExecutionGenerator = (BeforeExecutionGenerator) generator; + for ( int i = 0; i < rows; i++ ) { + updateBindings.addBinding( + rowNumber, + new JdbcParameterBindingImpl( + rowNumberColumn.getJdbcMapping(), + i + 1 + ) + ); + updateBindings.addBinding( + rootIdentity, + new JdbcParameterBindingImpl( + basicIdentifierMapping.getJdbcMapping(), + beforeExecutionGenerator.generate( session, null, null, INSERT ) + ) + ); + jdbcServices.getJdbcMutationExecutor().execute( + jdbcUpdate, + updateBindings, + sql -> session + .getJdbcCoordinator() + .getStatementPreparer() + .prepareStatement( sql ), + (integer, preparedStatement) -> { + }, + executionContext + ); + } } - final BeforeExecutionGenerator beforeExecutionGenerator = (BeforeExecutionGenerator) generator; - for ( int i = 0; i < rows; i++ ) { - updateBindings.addBinding( - rowNumber, - new JdbcParameterBindingImpl( - rowNumberColumn.getJdbcMapping(), - i + 1 + identifierMapping.forEachSelectable( 0, (selectionIndex, selectableMapping) -> { + insertStatement.addTargetColumnReferences( + new ColumnReference( + (String) null, + keyColumns[selectionIndex], + false, + null, + selectableMapping.getJdbcMapping() ) ); - updateBindings.addBinding( - rootIdentity, - new JdbcParameterBindingImpl( - identifierMapping.getJdbcMapping(), - beforeExecutionGenerator.generate( session, null, null, INSERT ) + querySpec.getSelectClause().addSqlSelection( + new SqlSelectionImpl( + new ColumnReference( + temporaryTableReference.getIdentificationVariable(), + primaryKeyTableColumns.get( selectionIndex ).getColumnName(), + false, + null, + selectableMapping.getJdbcMapping() + ) ) ); - jdbcServices.getJdbcMutationExecutor().execute( - jdbcUpdate, - updateBindings, - sql -> session - .getJdbcCoordinator() - .getStatementPreparer() - .prepareStatement( sql ), - (integer, preparedStatement) -> {}, - executionContext - ); - } - - insertStatement.addTargetColumnReferences( - new ColumnReference( - (String) null, - keyColumns[0], - false, - null, - identifierMapping.getJdbcMapping() - ) - ); - querySpec.getSelectClause().addSqlSelection( - new SqlSelectionImpl( - new ColumnReference( - temporaryTableReference.getIdentificationVariable(), - idColumnReference.getColumnExpression(), - false, - null, - idColumnReference.getJdbcMapping() - ) - ) - ); + } ); } } + if ( entityTable.getSessionUidColumn() != null ) { + final TemporaryTableColumn sessionUidColumn = entityTable.getSessionUidColumn(); + querySpec.applyPredicate( new ComparisonPredicate( + new ColumnReference( + temporaryTableReference, + sessionUidColumn.getColumnName(), + false, + null, + sessionUidColumn.getJdbcMapping() + ), + ComparisonOperator.EQUAL, + sessionUidParameter + ) ); + } final JdbcOperationQueryMutation jdbcInsert = jdbcServices.getJdbcEnvironment() .getSqlAstTranslatorFactory() .buildMutationTranslator( sessionFactory, insertStatement ) .translate( null, executionContext.getQueryOptions() ); - if ( generator.generatedOnExecution() ) { + if ( !assignsId && generator.generatedOnExecution() ) { final GeneratedValuesMutationDelegate insertDelegate = entityDescriptor.getEntityPersister().getInsertDelegate(); // todo 7.0 : InsertGeneratedIdentifierDelegate will be removed once we're going to handle // generated values within the jdbc insert operaetion itself @@ -561,6 +599,10 @@ private int insertRootTable( @Override public void bindValues(PreparedStatement ps) throws SQLException { jdbcValueBinder.bind( ps, entry.getKey(), 1, session ); + if ( sessionUidParameter != null ) { + sessionUidParameter.getParameterBinder() + .bindParameterValue( ps, 2, jdbcParameterBindings, executionContext ); + } } @Override public Object getEntity() { @@ -572,12 +614,22 @@ public Object getEntity() { entry.setValue( rootIdentity ); } + final List primaryKeyTableColumns = + getPrimaryKeyTableColumns( entityPersister, entityTable ); + assert primaryKeyTableColumns.size() == 1; + final JdbcParameter entityIdentity = new JdbcParameterImpl( identifierMapping.getJdbcMapping() ); final JdbcParameter rootIdentity = new JdbcParameterImpl( identifierMapping.getJdbcMapping() ); final List temporaryTableAssignments = new ArrayList<>( 1 ); temporaryTableAssignments.add( new Assignment( - new ColumnReference( (String) null, identifierMapping ), + new ColumnReference( + (String) null, + primaryKeyTableColumns.get( 0 ).getColumnName(), + false, + null, + primaryKeyTableColumns.get( 0 ).getJdbcMapping() + ), rootIdentity ) ); @@ -625,7 +677,7 @@ public Object getEntity() { else { return jdbcServices.getJdbcMutationExecutor().execute( jdbcInsert, - JdbcParameterBindings.NO_BINDINGS, + jdbcParameterBindings, sql -> session .getJdbcCoordinator() .getStatementPreparer() @@ -638,7 +690,7 @@ public Object getEntity() { } private boolean needsIdentifierGeneration(Generator identifierGenerator) { - if (identifierGenerator instanceof OptimizableGenerator) { + if ( !assignsId && identifierGenerator instanceof OptimizableGenerator ) { // If the generator uses an optimizer or is not bulk insertion capable, // we have to generate identifiers for the new rows, as that couldn't // have been done via a SQL expression @@ -664,7 +716,7 @@ private void insertTable( true ); - final List assignments = assignmentsByTable.get( updatingTableReference ); + final List assignments = assignmentsByTable.get( tableExpression ); if ( nullableTable && ( assignments == null || assignments.isEmpty() ) ) { // no assignments for this table - skip it return; @@ -685,25 +737,7 @@ private void insertTable( querySpec.getFromClause().addRoot( temporaryTableGroup ); final InsertSelectStatement insertStatement = new InsertSelectStatement( dmlTargetTableReference ); insertStatement.setSourceSelectStatement( querySpec ); - if ( assignments != null && !assignments.isEmpty() ) { - for ( Assignment assignment : assignments ) { - insertStatement.addTargetColumnReferences( assignment.getAssignable().getColumnReferences() ); - for ( ColumnReference columnReference : assignment.getAssignable().getColumnReferences() ) { - querySpec.getSelectClause().addSqlSelection( - new SqlSelectionImpl( - new ColumnReference( - temporaryTableReference.getIdentificationVariable(), - columnReference.getColumnExpression(), - false, - null, - columnReference.getJdbcMapping() - ) - ) - ); - } - } - } - final String targetKeyColumnName = keyColumns[0]; + applyAssignments( assignments, insertStatement, temporaryTableReference ); final EntityPersister entityPersister = entityDescriptor.getEntityPersister(); final Generator identifierGenerator = entityPersister.getGenerator(); final boolean needsKeyInsert; @@ -720,26 +754,46 @@ else if ( identifierGenerator instanceof OptimizableGenerator ) { } if ( needsKeyInsert && insertStatement.getTargetColumns() .stream() - .noneMatch( c -> targetKeyColumnName.equals( c.getColumnExpression() ) ) ) { - final BasicEntityIdentifierMapping identifierMapping = - (BasicEntityIdentifierMapping) entityDescriptor.getIdentifierMapping(); - insertStatement.addTargetColumnReferences( + .noneMatch( c -> keyColumns[0].equals( c.getColumnExpression() ) ) ) { + final List primaryKeyTableColumns = + getPrimaryKeyTableColumns( entityPersister, entityTable ); + entityDescriptor.getIdentifierMapping().forEachSelectable( 0, (selectionIndex, selectableMapping) -> { + insertStatement.addTargetColumnReferences( + new ColumnReference( + (String) null, + keyColumns[selectionIndex], + false, + null, + selectableMapping.getJdbcMapping() + ) + ); + querySpec.getSelectClause().addSqlSelection( + new SqlSelectionImpl( + new ColumnReference( + temporaryTableReference.getIdentificationVariable(), + primaryKeyTableColumns.get( selectionIndex ).getColumnName(), + false, + null, + selectableMapping.getJdbcMapping() + ) + ) + ); + } ); + } + + if ( entityTable.getSessionUidColumn() != null ) { + final TemporaryTableColumn sessionUidColumn = entityTable.getSessionUidColumn(); + querySpec.applyPredicate( new ComparisonPredicate( new ColumnReference( - dmlTargetTableReference.getIdentificationVariable(), - targetKeyColumnName, + temporaryTableReference, + sessionUidColumn.getColumnName(), false, null, - identifierMapping.getJdbcMapping() - ) - ); - querySpec.getSelectClause().addSqlSelection( - new SqlSelectionImpl( - new ColumnReference( - temporaryTableReference.getIdentificationVariable(), - identifierMapping - ) - ) - ); + sessionUidColumn.getJdbcMapping() + ), + ComparisonOperator.EQUAL, + sessionUidParameter + ) ); } final JdbcServices jdbcServices = sessionFactory.getJdbcServices(); final JdbcOperationQueryMutation jdbcInsert = jdbcServices.getJdbcEnvironment() @@ -749,7 +803,7 @@ else if ( identifierGenerator instanceof OptimizableGenerator ) { jdbcServices.getJdbcMutationExecutor().execute( jdbcInsert, - JdbcParameterBindings.NO_BINDINGS, + jdbcParameterBindings, sql -> executionContext.getSession() .getJdbcCoordinator() .getStatementPreparer() @@ -759,4 +813,30 @@ else if ( identifierGenerator instanceof OptimizableGenerator ) { executionContext ); } + + private void applyAssignments(List assignments, InsertSelectStatement insertStatement, NamedTableReference temporaryTableReference) { + if ( assignments != null && !assignments.isEmpty() ) { + for ( Assignment assignment : assignments ) { + final Assignable assignable = assignment.getAssignable(); + insertStatement.addTargetColumnReferences( assignable.getColumnReferences() ); + final List columns = entityTable.findTemporaryTableColumns( + entityDescriptor.getEntityPersister(), + ((SqmPathInterpretation) assignable).getExpressionType() + ); + for ( TemporaryTableColumn temporaryTableColumn : columns ) { + insertStatement.getSourceSelectStatement().getFirstQuerySpec().getSelectClause().addSqlSelection( + new SqlSelectionImpl( + new ColumnReference( + temporaryTableReference.getIdentificationVariable(), + temporaryTableColumn.getColumnName(), + false, + null, + temporaryTableColumn.getJdbcMapping() + ) + ) + ); + } + } + } + } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/LocalTemporaryTableInsertStrategy.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/LocalTemporaryTableInsertStrategy.java index f81dcce16128..9d9279c5ccc5 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/LocalTemporaryTableInsertStrategy.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/LocalTemporaryTableInsertStrategy.java @@ -5,10 +5,13 @@ package org.hibernate.query.sqm.mutation.internal.temptable; import org.hibernate.dialect.temptable.TemporaryTable; +import org.hibernate.dialect.temptable.TemporaryTableKind; +import org.hibernate.dialect.temptable.TemporaryTableStrategy; import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.metamodel.mapping.EntityMappingType; +import org.hibernate.metamodel.spi.RuntimeModelCreationContext; import org.hibernate.query.spi.DomainQueryExecutionContext; import org.hibernate.query.sqm.internal.DomainParameterXref; -import org.hibernate.query.sqm.mutation.spi.AfterUseAction; import org.hibernate.query.sqm.mutation.spi.SqmMultiTableInsertStrategy; import org.hibernate.query.sqm.tree.insert.SqmInsertStatement; @@ -19,6 +22,30 @@ */ public class LocalTemporaryTableInsertStrategy extends LocalTemporaryTableStrategy implements SqmMultiTableInsertStrategy { + public LocalTemporaryTableInsertStrategy(EntityMappingType rootEntityDescriptor, RuntimeModelCreationContext runtimeModelCreationContext) { + this( + rootEntityDescriptor, + requireLocalTemporaryTableStrategy( runtimeModelCreationContext.getDialect() ), + runtimeModelCreationContext + ); + } + + private LocalTemporaryTableInsertStrategy( + EntityMappingType rootEntityDescriptor, + TemporaryTableStrategy temporaryTableStrategy, + RuntimeModelCreationContext runtimeModelCreationContext) { + this( + TemporaryTable.createEntityTable( + rootEntityDescriptor, + basename -> temporaryTableStrategy.adjustTemporaryTableName( TemporaryTable.ENTITY_TABLE_PREFIX + basename ), + TemporaryTableKind.LOCAL, + runtimeModelCreationContext.getDialect(), + runtimeModelCreationContext + ), + runtimeModelCreationContext.getSessionFactory() + ); + } + public LocalTemporaryTableInsertStrategy( TemporaryTable entityTable, SessionFactoryImplementor sessionFactory) { @@ -34,9 +61,8 @@ public int executeInsert( sqmInsertStatement, domainParameterXref, getTemporaryTable(), - isDropIdTables() - ? AfterUseAction.DROP - : getSessionFactory().getJdbcServices().getDialect().getTemporaryTableAfterUseAction(), + getTemporaryTableStrategy(), + isDropIdTables(), session -> { throw new UnsupportedOperationException( "Unexpected call to access Session uid" ); }, diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/LocalTemporaryTableMutationStrategy.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/LocalTemporaryTableMutationStrategy.java index 9f65733fc26b..16b3af4e4c12 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/LocalTemporaryTableMutationStrategy.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/LocalTemporaryTableMutationStrategy.java @@ -5,10 +5,13 @@ package org.hibernate.query.sqm.mutation.internal.temptable; import org.hibernate.dialect.temptable.TemporaryTable; +import org.hibernate.dialect.temptable.TemporaryTableKind; +import org.hibernate.dialect.temptable.TemporaryTableStrategy; import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.metamodel.mapping.EntityMappingType; +import org.hibernate.metamodel.spi.RuntimeModelCreationContext; import org.hibernate.query.spi.DomainQueryExecutionContext; import org.hibernate.query.sqm.internal.DomainParameterXref; -import org.hibernate.query.sqm.mutation.spi.AfterUseAction; import org.hibernate.query.sqm.mutation.spi.SqmMultiTableMutationStrategy; import org.hibernate.query.sqm.tree.delete.SqmDeleteStatement; import org.hibernate.query.sqm.tree.update.SqmUpdateStatement; @@ -20,6 +23,30 @@ */ public class LocalTemporaryTableMutationStrategy extends LocalTemporaryTableStrategy implements SqmMultiTableMutationStrategy { + public LocalTemporaryTableMutationStrategy(EntityMappingType rootEntityDescriptor, RuntimeModelCreationContext runtimeModelCreationContext) { + this( + rootEntityDescriptor, + requireLocalTemporaryTableStrategy( runtimeModelCreationContext.getDialect() ), + runtimeModelCreationContext + ); + } + + private LocalTemporaryTableMutationStrategy( + EntityMappingType rootEntityDescriptor, + TemporaryTableStrategy temporaryTableStrategy, + RuntimeModelCreationContext runtimeModelCreationContext) { + this( + TemporaryTable.createIdTable( + rootEntityDescriptor, + basename -> temporaryTableStrategy.adjustTemporaryTableName( TemporaryTable.ID_TABLE_PREFIX + basename ), + TemporaryTableKind.LOCAL, + runtimeModelCreationContext.getDialect(), + runtimeModelCreationContext + ), + runtimeModelCreationContext.getSessionFactory() + ); + } + public LocalTemporaryTableMutationStrategy( TemporaryTable idTable, SessionFactoryImplementor sessionFactory) { @@ -35,9 +62,8 @@ public int executeUpdate( sqmUpdate, domainParameterXref, getTemporaryTable(), - isDropIdTables() - ? AfterUseAction.DROP - : getSessionFactory().getJdbcServices().getDialect().getTemporaryTableAfterUseAction(), + getTemporaryTableStrategy(), + isDropIdTables(), session -> { throw new UnsupportedOperationException( "Unexpected call to access Session uid" ); }, @@ -54,9 +80,8 @@ public int executeDelete( sqmDelete, domainParameterXref, getTemporaryTable(), - isDropIdTables() - ? AfterUseAction.DROP - : getSessionFactory().getJdbcServices().getDialect().getTemporaryTableAfterUseAction(), + getTemporaryTableStrategy(), + isDropIdTables(), session -> { throw new UnsupportedOperationException( "Unexpected call to access Session uid" ); }, diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/LocalTemporaryTableStrategy.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/LocalTemporaryTableStrategy.java index 85bd6dc72c22..77bd993aa35a 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/LocalTemporaryTableStrategy.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/LocalTemporaryTableStrategy.java @@ -4,7 +4,9 @@ */ package org.hibernate.query.sqm.mutation.internal.temptable; +import org.hibernate.dialect.Dialect; import org.hibernate.dialect.temptable.TemporaryTable; +import org.hibernate.dialect.temptable.TemporaryTableStrategy; import org.hibernate.engine.config.spi.ConfigurationService; import org.hibernate.engine.config.spi.StandardConverters; import org.hibernate.engine.jdbc.connections.spi.JdbcConnectionAccess; @@ -12,6 +14,10 @@ import org.hibernate.metamodel.mapping.EntityMappingType; import org.hibernate.metamodel.mapping.internal.MappingModelCreationProcess; +import java.util.Objects; + +import static org.hibernate.internal.util.NullnessUtil.castNonNull; + /** * Strategy based on ANSI SQL's definition of a "local temporary table" (local to each db session). * @@ -32,6 +38,15 @@ public LocalTemporaryTableStrategy(TemporaryTable temporaryTable, SessionFactory this.sessionFactory = sessionFactory; } + protected static TemporaryTableStrategy requireLocalTemporaryTableStrategy(Dialect dialect) { + return Objects.requireNonNull( dialect.getLocalTemporaryTableStrategy(), + "Dialect does not define a local temporary table strategy: " + dialect.getClass().getSimpleName() ); + } + + public TemporaryTableStrategy getTemporaryTableStrategy() { + return castNonNull( sessionFactory.getJdbcServices().getDialect().getLocalTemporaryTableStrategy() ); + } + public void prepare(MappingModelCreationProcess mappingModelCreationProcess, JdbcConnectionAccess connectionAccess) { final ConfigurationService configService = mappingModelCreationProcess.getCreationContext() diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/PersistentTableInsertStrategy.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/PersistentTableInsertStrategy.java index 4853e7f95013..6b9bd90ad463 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/PersistentTableInsertStrategy.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/PersistentTableInsertStrategy.java @@ -5,12 +5,17 @@ package org.hibernate.query.sqm.mutation.internal.temptable; import org.hibernate.dialect.temptable.TemporaryTable; +import org.hibernate.dialect.temptable.TemporaryTableKind; +import org.hibernate.dialect.temptable.TemporaryTableStrategy; import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.metamodel.mapping.EntityMappingType; +import org.hibernate.metamodel.spi.RuntimeModelCreationContext; import org.hibernate.query.spi.DomainQueryExecutionContext; import org.hibernate.query.sqm.internal.DomainParameterXref; import org.hibernate.query.sqm.mutation.spi.SqmMultiTableInsertStrategy; import org.hibernate.query.sqm.tree.insert.SqmInsertStatement; + /** * This is a strategy that mimics temporary tables for databases which do not support * temporary tables. It follows a pattern similar to the ANSI SQL definition of global @@ -20,6 +25,30 @@ */ public class PersistentTableInsertStrategy extends PersistentTableStrategy implements SqmMultiTableInsertStrategy { + public PersistentTableInsertStrategy(EntityMappingType rootEntityDescriptor, RuntimeModelCreationContext runtimeModelCreationContext) { + this( + rootEntityDescriptor, + runtimeModelCreationContext.getDialect().getPersistentTemporaryTableStrategy(), + runtimeModelCreationContext + ); + } + + private PersistentTableInsertStrategy( + EntityMappingType rootEntityDescriptor, + TemporaryTableStrategy temporaryTableStrategy, + RuntimeModelCreationContext runtimeModelCreationContext) { + this( + TemporaryTable.createEntityTable( + rootEntityDescriptor, + basename -> temporaryTableStrategy.adjustTemporaryTableName( TemporaryTable.ENTITY_TABLE_PREFIX + basename ), + TemporaryTableKind.PERSISTENT, + runtimeModelCreationContext.getDialect(), + runtimeModelCreationContext + ), + runtimeModelCreationContext.getSessionFactory() + ); + } + public PersistentTableInsertStrategy( TemporaryTable entityTable, SessionFactoryImplementor sessionFactory) { @@ -35,7 +64,8 @@ public int executeInsert( sqmInsertStatement, domainParameterXref, getTemporaryTable(), - getSessionFactory().getJdbcServices().getDialect().getTemporaryTableAfterUseAction(), + getTemporaryTableStrategy(), + false, session -> session.getSessionIdentifier().toString(), getSessionFactory() ).execute( context ); diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/PersistentTableMutationStrategy.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/PersistentTableMutationStrategy.java index 502283f761b8..4e5d087cb7a6 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/PersistentTableMutationStrategy.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/PersistentTableMutationStrategy.java @@ -5,13 +5,18 @@ package org.hibernate.query.sqm.mutation.internal.temptable; import org.hibernate.dialect.temptable.TemporaryTable; +import org.hibernate.dialect.temptable.TemporaryTableKind; +import org.hibernate.dialect.temptable.TemporaryTableStrategy; import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.metamodel.mapping.EntityMappingType; +import org.hibernate.metamodel.spi.RuntimeModelCreationContext; import org.hibernate.query.spi.DomainQueryExecutionContext; import org.hibernate.query.sqm.internal.DomainParameterXref; import org.hibernate.query.sqm.mutation.spi.SqmMultiTableMutationStrategy; import org.hibernate.query.sqm.tree.delete.SqmDeleteStatement; import org.hibernate.query.sqm.tree.update.SqmUpdateStatement; + /** * This is a strategy that mimics temporary tables for databases which do not support * temporary tables. It follows a pattern similar to the ANSI SQL definition of global @@ -21,6 +26,30 @@ */ public class PersistentTableMutationStrategy extends PersistentTableStrategy implements SqmMultiTableMutationStrategy { + public PersistentTableMutationStrategy(EntityMappingType rootEntityDescriptor, RuntimeModelCreationContext runtimeModelCreationContext) { + this( + rootEntityDescriptor, + runtimeModelCreationContext.getDialect().getPersistentTemporaryTableStrategy(), + runtimeModelCreationContext + ); + } + + private PersistentTableMutationStrategy( + EntityMappingType rootEntityDescriptor, + TemporaryTableStrategy temporaryTableStrategy, + RuntimeModelCreationContext runtimeModelCreationContext) { + this( + TemporaryTable.createIdTable( + rootEntityDescriptor, + basename -> temporaryTableStrategy.adjustTemporaryTableName( TemporaryTable.ID_TABLE_PREFIX + basename ), + TemporaryTableKind.PERSISTENT, + runtimeModelCreationContext.getDialect(), + runtimeModelCreationContext + ), + runtimeModelCreationContext.getSessionFactory() + ); + } + public PersistentTableMutationStrategy( TemporaryTable idTable, SessionFactoryImplementor sessionFactory) { @@ -36,7 +65,8 @@ public int executeUpdate( sqmUpdate, domainParameterXref, getTemporaryTable(), - getSessionFactory().getJdbcServices().getDialect().getTemporaryTableAfterUseAction(), + getTemporaryTableStrategy(), + false, session -> session.getSessionIdentifier().toString(), getSessionFactory() ).execute( context ); @@ -51,7 +81,8 @@ public int executeDelete( sqmDelete, domainParameterXref, getTemporaryTable(), - getSessionFactory().getJdbcServices().getDialect().getTemporaryTableAfterUseAction(), + getTemporaryTableStrategy(), + false, session -> session.getSessionIdentifier().toString(), getSessionFactory() ).execute( context ); diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/PersistentTableStrategy.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/PersistentTableStrategy.java index a25984bec40b..1b1f5d9c4404 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/PersistentTableStrategy.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/PersistentTableStrategy.java @@ -9,6 +9,7 @@ import org.hibernate.dialect.temptable.TemporaryTable; import org.hibernate.dialect.temptable.TemporaryTableHelper; +import org.hibernate.dialect.temptable.TemporaryTableStrategy; import org.hibernate.engine.config.spi.ConfigurationService; import org.hibernate.engine.config.spi.StandardConverters; import org.hibernate.engine.jdbc.connections.spi.JdbcConnectionAccess; @@ -21,6 +22,8 @@ import static org.hibernate.engine.jdbc.JdbcLogging.JDBC_MESSAGE_LOGGER; +import static org.hibernate.internal.util.NullnessUtil.castNonNull; + /** * This is a strategy that mimics temporary tables for databases which do not support * temporary tables. It follows a pattern similar to the ANSI SQL definition of global @@ -54,11 +57,15 @@ public PersistentTableStrategy( this.temporaryTable = temporaryTable; this.sessionFactory = sessionFactory; - if ( sessionFactory.getJdbcServices().getDialect().getTemporaryTableAfterUseAction() == AfterUseAction.DROP ) { + if ( sessionFactory.getJdbcServices().getDialect().getPersistentTemporaryTableStrategy().getTemporaryTableAfterUseAction() == AfterUseAction.DROP ) { throw new IllegalArgumentException( "Persistent ID tables cannot use AfterUseAction.DROP : " + temporaryTable.getTableExpression() ); } } + public TemporaryTableStrategy getTemporaryTableStrategy() { + return castNonNull( sessionFactory.getJdbcServices().getDialect().getPersistentTemporaryTableStrategy() ); + } + public EntityMappingType getEntityDescriptor() { return getTemporaryTable().getEntityDescriptor(); } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/RestrictedDeleteExecutionDelegate.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/RestrictedDeleteExecutionDelegate.java index fafe02327b44..268c3015276c 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/RestrictedDeleteExecutionDelegate.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/RestrictedDeleteExecutionDelegate.java @@ -12,6 +12,7 @@ import java.util.function.Supplier; import org.hibernate.dialect.temptable.TemporaryTable; +import org.hibernate.dialect.temptable.TemporaryTableStrategy; import org.hibernate.engine.jdbc.spi.JdbcServices; import org.hibernate.engine.spi.LoadQueryInfluencers; import org.hibernate.engine.spi.SessionFactoryImplementor; @@ -31,7 +32,6 @@ import org.hibernate.query.sqm.internal.SqmUtil; import org.hibernate.query.sqm.mutation.internal.SqmMutationStrategyHelper; import org.hibernate.query.sqm.mutation.internal.TableKeyExpressionCollector; -import org.hibernate.query.sqm.mutation.spi.AfterUseAction; import org.hibernate.query.sqm.spi.SqmParameterMappingModelResolutionAccess; import org.hibernate.query.sqm.tree.delete.SqmDeleteStatement; import org.hibernate.query.sqm.tree.expression.SqmParameter; @@ -64,7 +64,8 @@ public class RestrictedDeleteExecutionDelegate extends AbstractDeleteExecutionDe public RestrictedDeleteExecutionDelegate( EntityMappingType entityDescriptor, TemporaryTable idTable, - AfterUseAction afterUseAction, + TemporaryTableStrategy temporaryTableStrategy, + boolean forceDropAfterUse, SqmDeleteStatement sqmDelete, DomainParameterXref domainParameterXref, QueryOptions queryOptions, @@ -75,7 +76,8 @@ public RestrictedDeleteExecutionDelegate( super( entityDescriptor, idTable, - afterUseAction, + temporaryTableStrategy, + forceDropAfterUse, sqmDelete, domainParameterXref, queryOptions, @@ -442,6 +444,7 @@ public MappingModelExpressible getResolvedMappingModelType(SqmParameter sqmDelete, DomainParameterXref domainParameterXref, QueryOptions queryOptions, @@ -71,7 +72,8 @@ public SoftDeleteExecutionDelegate( super( entityDescriptor, idTable, - afterUseAction, + temporaryTableStrategy, + forceDropAfterUse, sqmDelete, domainParameterXref, queryOptions, @@ -183,6 +185,7 @@ private int performDeleteWithIdTable( SqmJdbcExecutionContextAdapter executionContext) { ExecuteWithTemporaryTableHelper.performBeforeTemporaryTableUseActions( getIdTable(), + getTemporaryTableStrategy(), executionContext ); diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/TableBasedDeleteHandler.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/TableBasedDeleteHandler.java index bee7aeecd05d..0745d197f29c 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/TableBasedDeleteHandler.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/TableBasedDeleteHandler.java @@ -7,6 +7,7 @@ import java.util.function.Function; import org.hibernate.dialect.temptable.TemporaryTable; +import org.hibernate.dialect.temptable.TemporaryTableStrategy; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.query.spi.DomainQueryExecutionContext; @@ -31,7 +32,8 @@ public interface ExecutionDelegate { } private final TemporaryTable idTable; - private final AfterUseAction afterUseAction; + private final TemporaryTableStrategy temporaryTableStrategy; + private final boolean forceDropAfterUse; private final Function sessionUidAccess; private final DomainParameterXref domainParameterXref; @@ -40,14 +42,16 @@ public TableBasedDeleteHandler( SqmDeleteStatement sqmDeleteStatement, DomainParameterXref domainParameterXref, TemporaryTable idTable, - AfterUseAction afterUseAction, + TemporaryTableStrategy temporaryTableStrategy, + boolean forceDropAfterUse, Function sessionUidAccess, SessionFactoryImplementor sessionFactory) { super( sqmDeleteStatement, sessionFactory ); this.idTable = idTable; this.domainParameterXref = domainParameterXref; - this.afterUseAction = afterUseAction; + this.temporaryTableStrategy = temporaryTableStrategy; + this.forceDropAfterUse = forceDropAfterUse; this.sessionUidAccess = sessionUidAccess; } @@ -68,7 +72,8 @@ protected ExecutionDelegate resolveDelegate(DomainQueryExecutionContext executio return new SoftDeleteExecutionDelegate( getEntityDescriptor(), idTable, - afterUseAction, + temporaryTableStrategy, + forceDropAfterUse, getSqmDeleteOrUpdateStatement(), domainParameterXref, executionContext.getQueryOptions(), @@ -82,7 +87,8 @@ protected ExecutionDelegate resolveDelegate(DomainQueryExecutionContext executio return new RestrictedDeleteExecutionDelegate( getEntityDescriptor(), idTable, - afterUseAction, + temporaryTableStrategy, + forceDropAfterUse, getSqmDeleteOrUpdateStatement(), domainParameterXref, executionContext.getQueryOptions(), @@ -99,7 +105,15 @@ protected TemporaryTable getIdTable() { } protected AfterUseAction getAfterUseAction() { - return afterUseAction; + return forceDropAfterUse ? AfterUseAction.DROP : temporaryTableStrategy.getTemporaryTableAfterUseAction(); + } + + protected TemporaryTableStrategy getTemporaryTableStrategy() { + return temporaryTableStrategy; + } + + protected boolean isForceDropAfterUse() { + return forceDropAfterUse; } protected Function getSessionUidAccess() { diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/TableBasedInsertHandler.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/TableBasedInsertHandler.java index 2201c90f03fe..0909ea371a68 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/TableBasedInsertHandler.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/TableBasedInsertHandler.java @@ -13,6 +13,7 @@ import org.hibernate.dialect.temptable.TemporaryTable; import org.hibernate.dialect.temptable.TemporaryTableColumn; import org.hibernate.dialect.temptable.TemporaryTableSessionUidColumn; +import org.hibernate.dialect.temptable.TemporaryTableStrategy; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.generator.Generator; @@ -26,8 +27,8 @@ import org.hibernate.query.sqm.mutation.internal.InsertHandler; import org.hibernate.query.sqm.mutation.internal.MultiTableSqmMutationConverter; import org.hibernate.query.sqm.mutation.internal.SqmInsertStrategyHelper; -import org.hibernate.query.sqm.mutation.spi.AfterUseAction; import org.hibernate.query.sqm.sql.BaseSqmToSqlAstConverter; +import org.hibernate.query.sqm.sql.internal.SqmPathInterpretation; import org.hibernate.query.sqm.tree.insert.SqmInsertSelectStatement; import org.hibernate.query.sqm.tree.insert.SqmInsertStatement; import org.hibernate.query.sqm.tree.insert.SqmInsertValuesStatement; @@ -66,7 +67,8 @@ public interface ExecutionDelegate { private final SessionFactoryImplementor sessionFactory; private final TemporaryTable entityTable; - private final AfterUseAction afterUseAction; + private final TemporaryTableStrategy temporaryTableStrategy; + private final boolean forceDropAfterUse; private final Function sessionUidAccess; private final DomainParameterXref domainParameterXref; private final JdbcParameter sessionUidParameter; @@ -75,11 +77,13 @@ public TableBasedInsertHandler( SqmInsertStatement sqmInsert, DomainParameterXref domainParameterXref, TemporaryTable entityTable, - AfterUseAction afterUseAction, + TemporaryTableStrategy temporaryTableStrategy, + boolean forceDropAfterUse, Function sessionUidAccess, SessionFactoryImplementor sessionFactory) { this.sqmInsertStatement = sqmInsert; - this.afterUseAction = afterUseAction; + this.temporaryTableStrategy = temporaryTableStrategy; + this.forceDropAfterUse = forceDropAfterUse; this.sessionFactory = sessionFactory; this.entityTable = entityTable; this.sessionUidAccess = sessionUidAccess; @@ -142,9 +146,18 @@ protected ExecutionDelegate resolveDelegate(DomainQueryExecutionContext executio final InsertSelectStatement insertStatement = new InsertSelectStatement( entityTableReference ); final BaseSqmToSqlAstConverter.AdditionalInsertValues additionalInsertValues = converterDelegate.visitInsertionTargetPaths( - (assigable, columnReferences) -> { - insertStatement.addTargetColumnReferences( columnReferences ); - targetPathColumns.add( new Assignment( assigable, (Expression) assigable ) ); + (assignable, columnReferences) -> { + final SqmPathInterpretation pathInterpretation = (SqmPathInterpretation) assignable; + final List columns = + entityTable.findTemporaryTableColumns( entityDescriptor, pathInterpretation.getExpressionType() ); + for ( TemporaryTableColumn column : columns ) { + insertStatement.addTargetColumnReference( new ColumnReference( + entityTableReference, + column.getColumnName(), + column.getJdbcMapping() + ) ); + } + targetPathColumns.add( new Assignment( assignable, (Expression) assignable ) ); }, sqmInsertStatement, entityDescriptor, @@ -302,7 +315,8 @@ else if ( entityDescriptor.getGenerator() instanceof OptimizableGenerator ) { sqmInsertStatement, converterDelegate, entityTable, - afterUseAction, + temporaryTableStrategy, + forceDropAfterUse, sessionUidAccess, domainParameterXref, insertingTableGroup, @@ -322,7 +336,8 @@ protected ExecutionDelegate buildExecutionDelegate( SqmInsertStatement sqmInsert, MultiTableSqmMutationConverter sqmConverter, TemporaryTable entityTable, - AfterUseAction afterUseAction, + TemporaryTableStrategy temporaryTableStrategy, + boolean forceDropAfterUse, Function sessionUidAccess, DomainParameterXref domainParameterXref, TableGroup insertingTableGroup, @@ -335,7 +350,8 @@ protected ExecutionDelegate buildExecutionDelegate( return new InsertExecutionDelegate( sqmConverter, entityTable, - afterUseAction, + temporaryTableStrategy, + forceDropAfterUse, sessionUidAccess, domainParameterXref, insertingTableGroup, diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/TableBasedUpdateHandler.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/TableBasedUpdateHandler.java index e893e686a1da..533dd49954dd 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/TableBasedUpdateHandler.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/TableBasedUpdateHandler.java @@ -5,6 +5,7 @@ package org.hibernate.query.sqm.mutation.internal.temptable; import org.hibernate.dialect.temptable.TemporaryTable; +import org.hibernate.dialect.temptable.TemporaryTableStrategy; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.internal.util.collections.CollectionHelper; @@ -16,7 +17,6 @@ import org.hibernate.query.sqm.mutation.internal.MultiTableSqmMutationConverter; import org.hibernate.query.sqm.mutation.internal.UpdateHandler; import org.hibernate.query.sqm.mutation.spi.AbstractMutationHandler; -import org.hibernate.query.sqm.mutation.spi.AfterUseAction; import org.hibernate.query.sqm.tree.update.SqmUpdateStatement; import org.hibernate.sql.ast.tree.from.TableGroup; import org.hibernate.sql.ast.tree.from.TableReference; @@ -45,7 +45,8 @@ public interface ExecutionDelegate { } private final TemporaryTable idTable; - private final AfterUseAction afterUseAction; + private final TemporaryTableStrategy temporaryTableStrategy; + private final boolean forceDropAfterUse; private final Function sessionUidAccess; private final DomainParameterXref domainParameterXref; @@ -53,12 +54,14 @@ public TableBasedUpdateHandler( SqmUpdateStatement sqmUpdate, DomainParameterXref domainParameterXref, TemporaryTable idTable, - AfterUseAction afterUseAction, + TemporaryTableStrategy temporaryTableStrategy, + boolean forceDropAfterUse, Function sessionUidAccess, SessionFactoryImplementor sessionFactory) { super( sqmUpdate, sessionFactory ); this.idTable = idTable; - this.afterUseAction = afterUseAction; + this.temporaryTableStrategy = temporaryTableStrategy; + this.forceDropAfterUse = forceDropAfterUse; this.sessionUidAccess = sessionUidAccess; this.domainParameterXref = domainParameterXref; } @@ -156,7 +159,8 @@ protected ExecutionDelegate resolveDelegate(DomainQueryExecutionContext executio return buildExecutionDelegate( converterDelegate, idTable, - afterUseAction, + temporaryTableStrategy, + forceDropAfterUse, sessionUidAccess, domainParameterXref, updatingTableGroup, @@ -170,7 +174,8 @@ protected ExecutionDelegate resolveDelegate(DomainQueryExecutionContext executio protected UpdateExecutionDelegate buildExecutionDelegate( MultiTableSqmMutationConverter sqmConverter, TemporaryTable idTable, - AfterUseAction afterUseAction, + TemporaryTableStrategy temporaryTableStrategy, + boolean forceDropAfterUse, Function sessionUidAccess, DomainParameterXref domainParameterXref, TableGroup updatingTableGroup, @@ -181,7 +186,8 @@ protected UpdateExecutionDelegate buildExecutionDelegate( return new UpdateExecutionDelegate( sqmConverter, idTable, - afterUseAction, + temporaryTableStrategy, + forceDropAfterUse, sessionUidAccess, domainParameterXref, updatingTableGroup, diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/UpdateExecutionDelegate.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/UpdateExecutionDelegate.java index db273c47fcd2..d2e5460ab903 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/UpdateExecutionDelegate.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/UpdateExecutionDelegate.java @@ -5,6 +5,7 @@ package org.hibernate.query.sqm.mutation.internal.temptable; import org.hibernate.dialect.temptable.TemporaryTable; +import org.hibernate.dialect.temptable.TemporaryTableStrategy; import org.hibernate.engine.jdbc.spi.JdbcServices; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; @@ -66,7 +67,8 @@ public class UpdateExecutionDelegate implements TableBasedUpdateHandler.ExecutionDelegate { private final MultiTableSqmMutationConverter sqmConverter; private final TemporaryTable idTable; - private final AfterUseAction afterUseAction; + private final TemporaryTableStrategy temporaryTableStrategy; + private final boolean forceDropAfterUse; private final Function sessionUidAccess; private final TableGroup updatingTableGroup; private final Predicate suppliedPredicate; @@ -81,7 +83,8 @@ public class UpdateExecutionDelegate implements TableBasedUpdateHandler.Executio public UpdateExecutionDelegate( MultiTableSqmMutationConverter sqmConverter, TemporaryTable idTable, - AfterUseAction afterUseAction, + TemporaryTableStrategy temporaryTableStrategy, + boolean forceDropAfterUse, Function sessionUidAccess, DomainParameterXref domainParameterXref, TableGroup updatingTableGroup, @@ -91,7 +94,8 @@ public UpdateExecutionDelegate( DomainQueryExecutionContext executionContext) { this.sqmConverter = sqmConverter; this.idTable = idTable; - this.afterUseAction = afterUseAction; + this.temporaryTableStrategy = temporaryTableStrategy; + this.forceDropAfterUse = forceDropAfterUse; this.sessionUidAccess = sessionUidAccess; this.updatingTableGroup = updatingTableGroup; this.sessionFactory = executionContext.getSession().getFactory(); @@ -169,6 +173,7 @@ public MappingModelExpressible getResolvedMappingModelType(SqmParameter consumer) { - if ( modelPart instanceof BasicValuedMapping basicValuedMapping ) { - consumer.accept( new CteColumn( prefix, basicValuedMapping.getJdbcMapping() ) ); - } - else if ( modelPart instanceof EntityValuedModelPart entityPart ) { - final ModelPart targetPart; - if ( modelPart instanceof Association association ) { - if ( association.getForeignKeyDescriptor() == null ) { - // This is expected to happen when processing a - // PostInitCallbackEntry because the callbacks - // are not ordered. The exception is caught in - // MappingModelCreationProcess.executePostInitCallbacks() - // and the callback is re-queued. - throw new IllegalStateException( "ForeignKeyDescriptor not ready for [" + association.getPartName() + "] on entity: " + modelPart.findContainingEntityMapping().getEntityName() ); - } - if ( association.getSideNature() != ForeignKeyDescriptor.Nature.KEY ) { - // Inverse one-to-one receives no column - return; - } - targetPart = association.getForeignKeyDescriptor().getTargetPart(); - } - else { - targetPart = entityPart.getEntityMappingType().getIdentifierMapping(); - } - forEachCteColumn( prefix + "_" + entityPart.getPartName(), targetPart, consumer ); - } - else if ( modelPart instanceof DiscriminatedAssociationModelPart discriminatedPart ) { - final String newPrefix = prefix + "_" + discriminatedPart.getPartName() + "_"; - forEachCteColumn( - newPrefix + "discriminator", - discriminatedPart.getDiscriminatorPart(), - consumer - ); - forEachCteColumn( - newPrefix + "key", - discriminatedPart.getKeyPart(), - consumer - ); - } - else { - final EmbeddableValuedModelPart embeddablePart = ( EmbeddableValuedModelPart ) modelPart; - final AttributeMappingsList attributeMappings = embeddablePart.getEmbeddableTypeDescriptor().getAttributeMappings(); - for ( int i = 0; i < attributeMappings.size(); i++ ) { - AttributeMapping mapping = attributeMappings.get( i ); - if ( !( mapping instanceof PluralAttributeMapping ) ) { - forEachCteColumn( prefix + "_" + mapping.getAttributeName(), mapping, consumer ); - } - } + SqmMutationStrategyHelper.forEachSelectableMapping( prefix, modelPart, (s, selectableMapping) -> { + consumer.accept( new CteColumn( s, selectableMapping.getJdbcMapping() ) ); + } ); + } + + public List findCteColumns(EntityPersister entityDescriptor, ModelPart modelPart) { + final int offset = determineModelPartStartIndex( entityDescriptor, modelPart ); + if ( offset == -1 ) { + throw new IllegalStateException( "Couldn't find matching cte columns for: " + modelPart ); } + final int end = offset + modelPart.getJdbcTypeCount(); + // Find a matching cte table column and set that at the current index + return getCteColumns().subList( offset, end ); } - public static int determineModelPartStartIndex(EntityPersister entityDescriptor, ModelPart modelPart) { + private static int determineModelPartStartIndex(EntityPersister entityDescriptor, ModelPart modelPart) { int offset = 0; - final EntityIdentifierMapping identifierMapping = entityDescriptor.getIdentifierMapping(); - if ( modelPart == identifierMapping ) { - return offset; + final int idResult = determineIdStartIndex( offset, entityDescriptor, modelPart ); + if ( idResult <= 0 ) { + return -idResult; } - offset += identifierMapping.getJdbcTypeCount(); + offset = idResult; final EntityDiscriminatorMapping discriminatorMapping = entityDescriptor.getDiscriminatorMapping(); - if ( discriminatorMapping != null ) { + if ( discriminatorMapping != null && discriminatorMapping.hasPhysicalColumn() && !discriminatorMapping.isFormula() ) { if ( modelPart == discriminatorMapping ) { return offset; } @@ -188,7 +152,7 @@ public static int determineModelPartStartIndex(EntityPersister entityDescriptor, AttributeMapping attribute = attributeMappings.get( i ); if ( !( attribute instanceof PluralAttributeMapping ) ) { final int result = determineModelPartStartIndex( offset, attribute, modelPart ); - if ( result < 0 ) { + if ( result <= 0 ) { return -result; } offset = result; @@ -197,6 +161,21 @@ public static int determineModelPartStartIndex(EntityPersister entityDescriptor, return -1; } + private static int determineIdStartIndex(int offset, EntityPersister entityDescriptor, ModelPart modelPart) { + final int originalOffset = offset; + do { + final EntityIdentifierMapping identifierMapping = entityDescriptor.getIdentifierMapping(); + final int result = determineModelPartStartIndex( originalOffset, identifierMapping, modelPart ); + offset = result; + if ( result <= 0 ) { + break; + } + entityDescriptor = (EntityPersister) entityDescriptor.getSuperMappingType(); + } while ( entityDescriptor != null ); + + return offset; + } + private static int determineModelPartStartIndex(int offset, ModelPart modelPart, ModelPart modelPartToFind) { if ( modelPart == modelPartToFind ) { return -offset; @@ -214,13 +193,16 @@ else if ( modelPart instanceof EmbeddableValuedModelPart embeddablePart ) { for ( int i = 0; i < attributeMappings.size(); i++ ) { final AttributeMapping mapping = attributeMappings.get( i ); final int result = determineModelPartStartIndex( offset, mapping, modelPartToFind ); - if ( result < 0 ) { + if ( result <= 0 ) { return result; } offset = result; } return offset; } + else if ( modelPart instanceof BasicValuedModelPart basicModelPart ) { + return offset + (basicModelPart.isInsertable() ? modelPart.getJdbcTypeCount() : 0); + } return offset + modelPart.getJdbcTypeCount(); } } diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/insert/InsertSelectStatement.java b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/insert/InsertSelectStatement.java index 9fbc3c30abb2..307d7db588d9 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/insert/InsertSelectStatement.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/insert/InsertSelectStatement.java @@ -64,6 +64,13 @@ public void forEachTargetColumn(BiConsumer consumer) { } } + public void addTargetColumnReference(ColumnReference reference) { + if ( targetColumnReferences == null ) { + targetColumnReferences = new ArrayList<>(); + } + targetColumnReferences.add( reference ); + } + public void addTargetColumnReferences(ColumnReference... references) { if ( targetColumnReferences == null ) { targetColumnReferences = new ArrayList<>(); diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/bulkid/AbstractMutationStrategyCompositeIdTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/bulkid/AbstractMutationStrategyCompositeIdTest.java index 0eca7fbcc2c5..fcae1171781e 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/bulkid/AbstractMutationStrategyCompositeIdTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/bulkid/AbstractMutationStrategyCompositeIdTest.java @@ -13,6 +13,7 @@ import org.hibernate.cfg.AvailableSettings; import org.hibernate.cfg.Configuration; +import org.hibernate.query.sqm.mutation.spi.SqmMultiTableInsertStrategy; import org.hibernate.query.sqm.mutation.spi.SqmMultiTableMutationStrategy; import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; @@ -21,6 +22,8 @@ import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; /** * @author Vlad Mihalcea @@ -39,13 +42,19 @@ protected Class[] getAnnotatedClasses() { @Override protected void configure(Configuration configuration) { super.configure( configuration ); - Class strategyClass = getMultiTableBulkIdStrategyClass(); - if ( strategyClass != null ) { - configuration.setProperty( AvailableSettings.QUERY_MULTI_TABLE_MUTATION_STRATEGY, strategyClass ); + Class mutationStrategyClass = getMultiTableMutationStrategyClass(); + if ( mutationStrategyClass != null ) { + configuration.setProperty( AvailableSettings.QUERY_MULTI_TABLE_MUTATION_STRATEGY, mutationStrategyClass ); + } + Class insertStrategyClass = getMultiTableInsertStrategyClass(); + if ( insertStrategyClass != null ) { + configuration.setProperty( AvailableSettings.QUERY_MULTI_TABLE_INSERT_STRATEGY, insertStrategyClass ); } } - protected abstract Class getMultiTableBulkIdStrategyClass(); + protected abstract Class getMultiTableMutationStrategyClass(); + + protected abstract Class getMultiTableInsertStrategyClass(); @Override protected boolean isCleanupTestDataRequired() { @@ -62,7 +71,7 @@ public void setUp() { doInHibernate( this::sessionFactory, session -> { for ( int i = 0; i < entityCount(); i++ ) { Doctor doctor = new Doctor(); - doctor.setId( i ); + doctor.setId( i + 1 ); doctor.setCompanyName( "Red Hat USA" ); doctor.setEmployed( ( i % 2 ) == 0 ); session.persist( doctor ); @@ -70,7 +79,7 @@ public void setUp() { for ( int i = 0; i < entityCount(); i++ ) { Engineer engineer = new Engineer(); - engineer.setId( i ); + engineer.setId( i + 1 + entityCount() ); engineer.setCompanyName( "Red Hat Europe" ); engineer.setEmployed( ( i % 2 ) == 0 ); engineer.setFellow( ( i % 2 ) == 1 ); @@ -118,6 +127,21 @@ public void testDeleteFromEngineer() { }); } + @Test + public void testInsert() { + doInHibernate( this::sessionFactory, session -> { + session.createQuery( "insert into Engineer(id, companyName, name, employed, fellow) values (0, 'Red Hat', :name, :employed, false)" ) + .setParameter( "name", "John Doe" ) + .setParameter( "employed", true ) + .executeUpdate(); + final Engineer engineer = session.find( Engineer.class, + new AbstractMutationStrategyCompositeIdTest_.Person_.Id( 0, "Red Hat" ) ); + assertEquals( "John Doe", engineer.getName() ); + assertTrue( engineer.isEmployed() ); + assertFalse( engineer.isFellow() ); + }); + } + //tag::batch-bulk-hql-temp-table-base-class-example[] @Entity(name = "Person") @Inheritance(strategy = InheritanceType.JOINED) diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/bulkid/AbstractMutationStrategyGeneratedIdTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/bulkid/AbstractMutationStrategyGeneratedIdTest.java new file mode 100644 index 000000000000..f603dac9ea38 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/bulkid/AbstractMutationStrategyGeneratedIdTest.java @@ -0,0 +1,142 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.bulkid; + +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Inheritance; +import jakarta.persistence.InheritanceType; +import jakarta.persistence.SequenceGenerator; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.cfg.Configuration; +import org.hibernate.query.sqm.mutation.spi.SqmMultiTableInsertStrategy; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +public abstract class AbstractMutationStrategyGeneratedIdTest extends BaseCoreFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Person.class, + Doctor.class, + Engineer.class + }; + } + + @Override + protected void configure(Configuration configuration) { + super.configure( configuration ); + Class insertStrategyClass = getMultiTableInsertStrategyClass(); + if ( insertStrategyClass != null ) { + configuration.setProperty( AvailableSettings.QUERY_MULTI_TABLE_INSERT_STRATEGY, insertStrategyClass ); + } + } + + protected abstract Class getMultiTableInsertStrategyClass(); + + @Override + protected boolean isCleanupTestDataRequired() { + return true; + } + + @Override + protected boolean isCleanupTestDataUsingBulkDelete() { + return true; + } + + @Test + public void testInsertStatic() { + doInHibernate( this::sessionFactory, session -> { + session.createQuery( "insert into Engineer(id, name, employed, fellow) values (0, :name, :employed, false)" ) + .setParameter( "name", "John Doe" ) + .setParameter( "employed", true ) + .executeUpdate(); + + final Engineer engineer = session.find( Engineer.class, 0 ); + assertEquals( "John Doe", engineer.getName() ); + assertTrue( engineer.isEmployed() ); + assertFalse( engineer.isFellow() ); + }); + } + + @Test + public void testInsertGenerated() { + doInHibernate( this::sessionFactory, session -> { + session.createQuery( "insert into Engineer(name, employed, fellow) values (:name, :employed, false)" ) + .setParameter( "name", "John Doe" ) + .setParameter( "employed", true ) + .executeUpdate(); + final Engineer engineer = session.createQuery( "from Engineer e where e.name = 'John Doe'", Engineer.class ) + .getSingleResult(); + assertEquals( "John Doe", engineer.getName() ); + assertTrue( engineer.isEmployed() ); + assertFalse( engineer.isFellow() ); + }); + } + + @Entity(name = "Person") + @Inheritance(strategy = InheritanceType.JOINED) + public static class Person { + + @Id + @GeneratedValue(strategy = GenerationType.SEQUENCE) + @SequenceGenerator(allocationSize = 1) + private Integer id; + + private String name; + + private boolean employed; + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public boolean isEmployed() { + return employed; + } + + public void setEmployed(boolean employed) { + this.employed = employed; + } + } + @Entity(name = "Doctor") + public static class Doctor extends Person { + } + + @Entity(name = "Engineer") + public static class Engineer extends Person { + + private boolean fellow; + + public boolean isFellow() { + return fellow; + } + + public void setFellow(boolean fellow) { + this.fellow = fellow; + } + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/bulkid/AbstractMutationStrategyGeneratedIdWithOptimizerTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/bulkid/AbstractMutationStrategyGeneratedIdWithOptimizerTest.java new file mode 100644 index 000000000000..8798cf8484e2 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/bulkid/AbstractMutationStrategyGeneratedIdWithOptimizerTest.java @@ -0,0 +1,140 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.bulkid; + +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Inheritance; +import jakarta.persistence.InheritanceType; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.cfg.Configuration; +import org.hibernate.query.sqm.mutation.spi.SqmMultiTableInsertStrategy; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +public abstract class AbstractMutationStrategyGeneratedIdWithOptimizerTest extends BaseCoreFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Person.class, + Doctor.class, + Engineer.class + }; + } + + @Override + protected void configure(Configuration configuration) { + super.configure( configuration ); + Class insertStrategyClass = getMultiTableInsertStrategyClass(); + if ( insertStrategyClass != null ) { + configuration.setProperty( AvailableSettings.QUERY_MULTI_TABLE_INSERT_STRATEGY, insertStrategyClass ); + } + } + + protected abstract Class getMultiTableInsertStrategyClass(); + + @Override + protected boolean isCleanupTestDataRequired() { + return true; + } + + @Override + protected boolean isCleanupTestDataUsingBulkDelete() { + return true; + } + + @Test + public void testInsertStatic() { + doInHibernate( this::sessionFactory, session -> { + session.createQuery( "insert into Engineer(id, name, employed, fellow) values (0, :name, :employed, false)" ) + .setParameter( "name", "John Doe" ) + .setParameter( "employed", true ) + .executeUpdate(); + + final Engineer engineer = session.find( Engineer.class, 0 ); + assertEquals( "John Doe", engineer.getName() ); + assertTrue( engineer.isEmployed() ); + assertFalse( engineer.isFellow() ); + }); + } + + @Test + public void testInsertGenerated() { + doInHibernate( this::sessionFactory, session -> { + session.createQuery( "insert into Engineer(name, employed, fellow) values (:name, :employed, false)" ) + .setParameter( "name", "John Doe" ) + .setParameter( "employed", true ) + .executeUpdate(); + final Engineer engineer = session.createQuery( "from Engineer e where e.name = 'John Doe'", Engineer.class ) + .getSingleResult(); + assertEquals( "John Doe", engineer.getName() ); + assertTrue( engineer.isEmployed() ); + assertFalse( engineer.isFellow() ); + }); + } + + @Entity(name = "Person") + @Inheritance(strategy = InheritanceType.JOINED) + public static class Person { + + @Id + @GeneratedValue(strategy = GenerationType.SEQUENCE) + private Integer id; + + private String name; + + private boolean employed; + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public boolean isEmployed() { + return employed; + } + + public void setEmployed(boolean employed) { + this.employed = employed; + } + } + @Entity(name = "Doctor") + public static class Doctor extends Person { + } + + @Entity(name = "Engineer") + public static class Engineer extends Person { + + private boolean fellow; + + public boolean isFellow() { + return fellow; + } + + public void setFellow(boolean fellow) { + this.fellow = fellow; + } + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/bulkid/AbstractMutationStrategyGeneratedIdentityTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/bulkid/AbstractMutationStrategyGeneratedIdentityTest.java new file mode 100644 index 000000000000..5a7e3e921da7 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/bulkid/AbstractMutationStrategyGeneratedIdentityTest.java @@ -0,0 +1,143 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.bulkid; + +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Inheritance; +import jakarta.persistence.InheritanceType; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.cfg.Configuration; +import org.hibernate.dialect.OracleDialect; +import org.hibernate.query.sqm.mutation.spi.SqmMultiTableInsertStrategy; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.hibernate.testing.orm.junit.SkipForDialect; +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +public abstract class AbstractMutationStrategyGeneratedIdentityTest extends BaseCoreFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Person.class, + Doctor.class, + Engineer.class + }; + } + + @Override + protected void configure(Configuration configuration) { + super.configure( configuration ); + Class insertStrategyClass = getMultiTableInsertStrategyClass(); + if ( insertStrategyClass != null ) { + configuration.setProperty( AvailableSettings.QUERY_MULTI_TABLE_INSERT_STRATEGY, insertStrategyClass ); + } + } + + protected abstract Class getMultiTableInsertStrategyClass(); + + @Override + protected boolean isCleanupTestDataRequired() { + return true; + } + + @Override + protected boolean isCleanupTestDataUsingBulkDelete() { + return true; + } + + @Test + public void testInsertStatic() { + doInHibernate( this::sessionFactory, session -> { + session.createQuery( "insert into Engineer(id, name, employed, fellow) values (0, :name, :employed, false)" ) + .setParameter( "name", "John Doe" ) + .setParameter( "employed", true ) + .executeUpdate(); + + final Engineer engineer = session.find( Engineer.class, 0 ); + assertEquals( "John Doe", engineer.getName() ); + assertTrue( engineer.isEmployed() ); + assertFalse( engineer.isFellow() ); + }); + } + + @Test + @SkipForDialect(dialectClass = OracleDialect.class, reason = "Oracle doesn't support insert-select with a returning clause") + public void testInsertGenerated() { + doInHibernate( this::sessionFactory, session -> { + session.createQuery( "insert into Engineer(name, employed, fellow) values (:name, :employed, false)" ) + .setParameter( "name", "John Doe" ) + .setParameter( "employed", true ) + .executeUpdate(); + final Engineer engineer = session.createQuery( "from Engineer e where e.name = 'John Doe'", Engineer.class ) + .getSingleResult(); + assertEquals( "John Doe", engineer.getName() ); + assertTrue( engineer.isEmployed() ); + assertFalse( engineer.isFellow() ); + }); + } + + @Entity(name = "Person") + @Inheritance(strategy = InheritanceType.JOINED) + public static class Person { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Integer id; + + private String name; + + private boolean employed; + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public boolean isEmployed() { + return employed; + } + + public void setEmployed(boolean employed) { + this.employed = employed; + } + } + @Entity(name = "Doctor") + public static class Doctor extends Person { + } + + @Entity(name = "Engineer") + public static class Engineer extends Person { + + private boolean fellow; + + public boolean isFellow() { + return fellow; + } + + public void setFellow(boolean fellow) { + this.fellow = fellow; + } + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/bulkid/AbstractMutationStrategyIdTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/bulkid/AbstractMutationStrategyIdTest.java index 742a7cea52c3..712532487316 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/bulkid/AbstractMutationStrategyIdTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/bulkid/AbstractMutationStrategyIdTest.java @@ -6,7 +6,6 @@ import jakarta.persistence.Entity; import jakarta.persistence.EntityManager; -import jakarta.persistence.GeneratedValue; import jakarta.persistence.Id; import jakarta.persistence.Inheritance; import jakarta.persistence.InheritanceType; @@ -16,6 +15,7 @@ import org.hibernate.cfg.AvailableSettings; import org.hibernate.cfg.Configuration; +import org.hibernate.query.sqm.mutation.spi.SqmMultiTableInsertStrategy; import org.hibernate.query.sqm.mutation.spi.SqmMultiTableMutationStrategy; import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; @@ -25,6 +25,8 @@ import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; /** * @author Vlad Mihalcea @@ -43,17 +45,20 @@ protected Class[] getAnnotatedClasses() { @Override protected void configure(Configuration configuration) { super.configure( configuration ); - final Class multiTableBulkIdStrategyClass = getMultiTableBulkIdStrategyClass(); - if ( multiTableBulkIdStrategyClass != null ) { - configuration.setProperty( - AvailableSettings.QUERY_MULTI_TABLE_MUTATION_STRATEGY, - multiTableBulkIdStrategyClass - ); + final Class mutationStrategyClass = getMultiTableMutationStrategyClass(); + if ( mutationStrategyClass != null ) { + configuration.setProperty( AvailableSettings.QUERY_MULTI_TABLE_MUTATION_STRATEGY, mutationStrategyClass ); + } + Class insertStrategyClass = getMultiTableInsertStrategyClass(); + if ( insertStrategyClass != null ) { + configuration.setProperty( AvailableSettings.QUERY_MULTI_TABLE_INSERT_STRATEGY, insertStrategyClass ); } } - protected abstract Class getMultiTableBulkIdStrategyClass(); + protected abstract Class getMultiTableMutationStrategyClass(); + + protected abstract Class getMultiTableInsertStrategyClass(); @Override protected boolean isCleanupTestDataRequired() { @@ -70,12 +75,14 @@ public void setUp() { doInHibernate( this::sessionFactory, session -> { for ( int i = 0; i < entityCount(); i++ ) { Doctor doctor = new Doctor(); + doctor.setId( i + 1 ); doctor.setEmployed( ( i % 2 ) == 0 ); session.persist( doctor ); } for ( int i = 0; i < entityCount(); i++ ) { Engineer engineer = new Engineer(); + engineer.setId( i + 1 + entityCount() ); engineer.setEmployed( ( i % 2 ) == 0 ); engineer.setFellow( ( i % 2 ) == 1 ); session.persist( engineer ); @@ -136,23 +143,37 @@ public void testDeleteFromEngineer() { }); } + @Test + public void testInsert() { + doInHibernate( this::sessionFactory, session -> { + session.createQuery( "insert into Engineer(id, name, employed, fellow) values (0, :name, :employed, false)" ) + .setParameter( "name", "John Doe" ) + .setParameter( "employed", true ) + .executeUpdate(); + + final Engineer engineer = session.find( Engineer.class, 0 ); + assertEquals( "John Doe", engineer.getName() ); + assertTrue( engineer.isEmployed() ); + assertFalse( engineer.isFellow() ); + }); + } + @Entity(name = "Person") @Inheritance(strategy = InheritanceType.JOINED) public static class Person { @Id - @GeneratedValue - private Long id; + private Integer id; private String name; private boolean employed; - public Long getId() { + public Integer getId() { return id; } - public void setId(Long id) { + public void setId(Integer id) { this.id = id; } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/bulkid/DefaultMutationStrategyCompositeIdTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/bulkid/DefaultMutationStrategyCompositeIdTest.java index 87652f1098af..0e2aa0fe2d88 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/bulkid/DefaultMutationStrategyCompositeIdTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/bulkid/DefaultMutationStrategyCompositeIdTest.java @@ -4,6 +4,7 @@ */ package org.hibernate.orm.test.bulkid; +import org.hibernate.query.sqm.mutation.spi.SqmMultiTableInsertStrategy; import org.hibernate.query.sqm.mutation.spi.SqmMultiTableMutationStrategy; /** @@ -12,7 +13,12 @@ public class DefaultMutationStrategyCompositeIdTest extends AbstractMutationStrategyCompositeIdTest { @Override - protected Class getMultiTableBulkIdStrategyClass() { + protected Class getMultiTableMutationStrategyClass() { + return null; + } + + @Override + protected Class getMultiTableInsertStrategyClass() { return null; } } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/bulkid/DefaultMutationStrategyGeneratedIdTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/bulkid/DefaultMutationStrategyGeneratedIdTest.java new file mode 100644 index 000000000000..cb65394ac588 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/bulkid/DefaultMutationStrategyGeneratedIdTest.java @@ -0,0 +1,15 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.bulkid; + +import org.hibernate.query.sqm.mutation.spi.SqmMultiTableInsertStrategy; + +public class DefaultMutationStrategyGeneratedIdTest extends AbstractMutationStrategyGeneratedIdTest { + + @Override + protected Class getMultiTableInsertStrategyClass() { + return null; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/bulkid/DefaultMutationStrategyGeneratedIdWithOptimizerTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/bulkid/DefaultMutationStrategyGeneratedIdWithOptimizerTest.java new file mode 100644 index 000000000000..ab9dca1c80af --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/bulkid/DefaultMutationStrategyGeneratedIdWithOptimizerTest.java @@ -0,0 +1,15 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.bulkid; + +import org.hibernate.query.sqm.mutation.spi.SqmMultiTableInsertStrategy; + +public class DefaultMutationStrategyGeneratedIdWithOptimizerTest extends AbstractMutationStrategyGeneratedIdWithOptimizerTest { + + @Override + protected Class getMultiTableInsertStrategyClass() { + return null; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/bulkid/DefaultMutationStrategyGeneratedIdentityTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/bulkid/DefaultMutationStrategyGeneratedIdentityTest.java new file mode 100644 index 000000000000..c101cfa5cb35 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/bulkid/DefaultMutationStrategyGeneratedIdentityTest.java @@ -0,0 +1,18 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.bulkid; + +import org.hibernate.query.sqm.mutation.spi.SqmMultiTableInsertStrategy; +import org.hibernate.testing.orm.junit.DialectFeatureChecks; +import org.hibernate.testing.orm.junit.RequiresDialectFeature; + +@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsIdentityColumns.class) +public class DefaultMutationStrategyGeneratedIdentityTest extends AbstractMutationStrategyGeneratedIdentityTest { + + @Override + protected Class getMultiTableInsertStrategyClass() { + return null; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/bulkid/DefaultMutationStrategyIdTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/bulkid/DefaultMutationStrategyIdTest.java index b0d79fc18595..017832ab0b6f 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/bulkid/DefaultMutationStrategyIdTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/bulkid/DefaultMutationStrategyIdTest.java @@ -4,6 +4,7 @@ */ package org.hibernate.orm.test.bulkid; +import org.hibernate.query.sqm.mutation.spi.SqmMultiTableInsertStrategy; import org.hibernate.query.sqm.mutation.spi.SqmMultiTableMutationStrategy; /** @@ -12,7 +13,12 @@ public class DefaultMutationStrategyIdTest extends AbstractMutationStrategyIdTest { @Override - protected Class getMultiTableBulkIdStrategyClass() { + protected Class getMultiTableMutationStrategyClass() { + return null; + } + + @Override + protected Class getMultiTableInsertStrategyClass() { return null; } } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/bulkid/GlobalTemporaryTableMutationStrategyCompositeIdTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/bulkid/GlobalTemporaryTableMutationStrategyCompositeIdTest.java new file mode 100644 index 000000000000..6427198a71c6 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/bulkid/GlobalTemporaryTableMutationStrategyCompositeIdTest.java @@ -0,0 +1,26 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.bulkid; + +import org.hibernate.query.sqm.mutation.internal.temptable.GlobalTemporaryTableInsertStrategy; +import org.hibernate.query.sqm.mutation.internal.temptable.GlobalTemporaryTableMutationStrategy; +import org.hibernate.query.sqm.mutation.spi.SqmMultiTableInsertStrategy; +import org.hibernate.query.sqm.mutation.spi.SqmMultiTableMutationStrategy; +import org.hibernate.testing.orm.junit.DialectFeatureChecks; +import org.hibernate.testing.orm.junit.RequiresDialectFeature; + +@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsGlobalTemporaryTable.class) +public class GlobalTemporaryTableMutationStrategyCompositeIdTest extends AbstractMutationStrategyCompositeIdTest { + + @Override + protected Class getMultiTableMutationStrategyClass() { + return GlobalTemporaryTableMutationStrategy.class; + } + + @Override + protected Class getMultiTableInsertStrategyClass() { + return GlobalTemporaryTableInsertStrategy.class; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/bulkid/GlobalTemporaryTableMutationStrategyGeneratedIdTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/bulkid/GlobalTemporaryTableMutationStrategyGeneratedIdTest.java new file mode 100644 index 000000000000..9e59892d23bc --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/bulkid/GlobalTemporaryTableMutationStrategyGeneratedIdTest.java @@ -0,0 +1,19 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.bulkid; + +import org.hibernate.query.sqm.mutation.internal.temptable.GlobalTemporaryTableInsertStrategy; +import org.hibernate.query.sqm.mutation.spi.SqmMultiTableInsertStrategy; +import org.hibernate.testing.orm.junit.DialectFeatureChecks; +import org.hibernate.testing.orm.junit.RequiresDialectFeature; + +@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsGlobalTemporaryTable.class) +public class GlobalTemporaryTableMutationStrategyGeneratedIdTest extends AbstractMutationStrategyGeneratedIdTest { + + @Override + protected Class getMultiTableInsertStrategyClass() { + return GlobalTemporaryTableInsertStrategy.class; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/bulkid/GlobalTemporaryTableMutationStrategyGeneratedIdWithOptimizerTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/bulkid/GlobalTemporaryTableMutationStrategyGeneratedIdWithOptimizerTest.java new file mode 100644 index 000000000000..1b340369d524 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/bulkid/GlobalTemporaryTableMutationStrategyGeneratedIdWithOptimizerTest.java @@ -0,0 +1,19 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.bulkid; + +import org.hibernate.query.sqm.mutation.internal.temptable.GlobalTemporaryTableInsertStrategy; +import org.hibernate.query.sqm.mutation.spi.SqmMultiTableInsertStrategy; +import org.hibernate.testing.orm.junit.DialectFeatureChecks; +import org.hibernate.testing.orm.junit.RequiresDialectFeature; + +@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsGlobalTemporaryTable.class) +public class GlobalTemporaryTableMutationStrategyGeneratedIdWithOptimizerTest extends AbstractMutationStrategyGeneratedIdWithOptimizerTest { + + @Override + protected Class getMultiTableInsertStrategyClass() { + return GlobalTemporaryTableInsertStrategy.class; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/bulkid/GlobalTemporaryTableMutationStrategyGeneratedIdentityTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/bulkid/GlobalTemporaryTableMutationStrategyGeneratedIdentityTest.java new file mode 100644 index 000000000000..f6a23fc9290b --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/bulkid/GlobalTemporaryTableMutationStrategyGeneratedIdentityTest.java @@ -0,0 +1,20 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.bulkid; + +import org.hibernate.query.sqm.mutation.internal.temptable.GlobalTemporaryTableInsertStrategy; +import org.hibernate.query.sqm.mutation.spi.SqmMultiTableInsertStrategy; +import org.hibernate.testing.orm.junit.DialectFeatureChecks; +import org.hibernate.testing.orm.junit.RequiresDialectFeature; + +@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsGlobalTemporaryTable.class) +@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsGlobalTemporaryTableIdentity.class) +public class GlobalTemporaryTableMutationStrategyGeneratedIdentityTest extends AbstractMutationStrategyGeneratedIdentityTest { + + @Override + protected Class getMultiTableInsertStrategyClass() { + return GlobalTemporaryTableInsertStrategy.class; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/bulkid/GlobalTemporaryTableMutationStrategyIdTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/bulkid/GlobalTemporaryTableMutationStrategyIdTest.java new file mode 100644 index 000000000000..a4ee11741f38 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/bulkid/GlobalTemporaryTableMutationStrategyIdTest.java @@ -0,0 +1,26 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.bulkid; + +import org.hibernate.query.sqm.mutation.internal.temptable.GlobalTemporaryTableInsertStrategy; +import org.hibernate.query.sqm.mutation.internal.temptable.GlobalTemporaryTableMutationStrategy; +import org.hibernate.query.sqm.mutation.spi.SqmMultiTableInsertStrategy; +import org.hibernate.query.sqm.mutation.spi.SqmMultiTableMutationStrategy; +import org.hibernate.testing.orm.junit.DialectFeatureChecks; +import org.hibernate.testing.orm.junit.RequiresDialectFeature; + +@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsGlobalTemporaryTable.class) +public class GlobalTemporaryTableMutationStrategyIdTest extends AbstractMutationStrategyIdTest { + + @Override + protected Class getMultiTableMutationStrategyClass() { + return GlobalTemporaryTableMutationStrategy.class; + } + + @Override + protected Class getMultiTableInsertStrategyClass() { + return GlobalTemporaryTableInsertStrategy.class; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/bulkid/InlineMutationStrategyCompositeIdTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/bulkid/InlineMutationStrategyCompositeIdTest.java index f921d5a46bc5..557a8dff7d48 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/bulkid/InlineMutationStrategyCompositeIdTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/bulkid/InlineMutationStrategyCompositeIdTest.java @@ -5,6 +5,7 @@ package org.hibernate.orm.test.bulkid; import org.hibernate.query.sqm.mutation.internal.inline.InlineMutationStrategy; +import org.hibernate.query.sqm.mutation.spi.SqmMultiTableInsertStrategy; import org.hibernate.query.sqm.mutation.spi.SqmMultiTableMutationStrategy; /** @@ -13,7 +14,13 @@ public class InlineMutationStrategyCompositeIdTest extends AbstractMutationStrategyCompositeIdTest { @Override - protected Class getMultiTableBulkIdStrategyClass() { + protected Class getMultiTableMutationStrategyClass() { return InlineMutationStrategy.class; } + + @Override + protected Class getMultiTableInsertStrategyClass() { + // No inline strategy for insert + return null; + } } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/bulkid/InlineMutationStrategyIdTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/bulkid/InlineMutationStrategyIdTest.java index b4b7604cb23a..6eba68db499a 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/bulkid/InlineMutationStrategyIdTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/bulkid/InlineMutationStrategyIdTest.java @@ -5,6 +5,7 @@ package org.hibernate.orm.test.bulkid; import org.hibernate.query.sqm.mutation.internal.inline.InlineMutationStrategy; +import org.hibernate.query.sqm.mutation.spi.SqmMultiTableInsertStrategy; import org.hibernate.query.sqm.mutation.spi.SqmMultiTableMutationStrategy; /** @@ -13,7 +14,13 @@ public class InlineMutationStrategyIdTest extends AbstractMutationStrategyIdTest { @Override - protected Class getMultiTableBulkIdStrategyClass() { + protected Class getMultiTableMutationStrategyClass() { return InlineMutationStrategy.class; } + + @Override + protected Class getMultiTableInsertStrategyClass() { + // No inline strategy for insert + return null; + } } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/bulkid/LocalTemporaryTableMutationStrategyCompositeIdTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/bulkid/LocalTemporaryTableMutationStrategyCompositeIdTest.java new file mode 100644 index 000000000000..20d7040bda96 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/bulkid/LocalTemporaryTableMutationStrategyCompositeIdTest.java @@ -0,0 +1,26 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.bulkid; + +import org.hibernate.query.sqm.mutation.internal.temptable.LocalTemporaryTableInsertStrategy; +import org.hibernate.query.sqm.mutation.internal.temptable.LocalTemporaryTableMutationStrategy; +import org.hibernate.query.sqm.mutation.spi.SqmMultiTableInsertStrategy; +import org.hibernate.query.sqm.mutation.spi.SqmMultiTableMutationStrategy; +import org.hibernate.testing.orm.junit.DialectFeatureChecks; +import org.hibernate.testing.orm.junit.RequiresDialectFeature; + +@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsLocalTemporaryTable.class) +public class LocalTemporaryTableMutationStrategyCompositeIdTest extends AbstractMutationStrategyCompositeIdTest { + + @Override + protected Class getMultiTableMutationStrategyClass() { + return LocalTemporaryTableMutationStrategy.class; + } + + @Override + protected Class getMultiTableInsertStrategyClass() { + return LocalTemporaryTableInsertStrategy.class; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/bulkid/LocalTemporaryTableMutationStrategyGeneratedIdTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/bulkid/LocalTemporaryTableMutationStrategyGeneratedIdTest.java new file mode 100644 index 000000000000..cbed391044a6 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/bulkid/LocalTemporaryTableMutationStrategyGeneratedIdTest.java @@ -0,0 +1,19 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.bulkid; + +import org.hibernate.query.sqm.mutation.internal.temptable.LocalTemporaryTableInsertStrategy; +import org.hibernate.query.sqm.mutation.spi.SqmMultiTableInsertStrategy; +import org.hibernate.testing.orm.junit.DialectFeatureChecks; +import org.hibernate.testing.orm.junit.RequiresDialectFeature; + +@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsLocalTemporaryTable.class) +public class LocalTemporaryTableMutationStrategyGeneratedIdTest extends AbstractMutationStrategyGeneratedIdTest { + + @Override + protected Class getMultiTableInsertStrategyClass() { + return LocalTemporaryTableInsertStrategy.class; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/bulkid/LocalTemporaryTableMutationStrategyGeneratedIdWithOptimizerTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/bulkid/LocalTemporaryTableMutationStrategyGeneratedIdWithOptimizerTest.java new file mode 100644 index 000000000000..36cc58ee164e --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/bulkid/LocalTemporaryTableMutationStrategyGeneratedIdWithOptimizerTest.java @@ -0,0 +1,19 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.bulkid; + +import org.hibernate.query.sqm.mutation.internal.temptable.LocalTemporaryTableInsertStrategy; +import org.hibernate.query.sqm.mutation.spi.SqmMultiTableInsertStrategy; +import org.hibernate.testing.orm.junit.DialectFeatureChecks; +import org.hibernate.testing.orm.junit.RequiresDialectFeature; + +@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsLocalTemporaryTable.class) +public class LocalTemporaryTableMutationStrategyGeneratedIdWithOptimizerTest extends AbstractMutationStrategyGeneratedIdWithOptimizerTest { + + @Override + protected Class getMultiTableInsertStrategyClass() { + return LocalTemporaryTableInsertStrategy.class; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/bulkid/LocalTemporaryTableMutationStrategyGeneratedIdentityTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/bulkid/LocalTemporaryTableMutationStrategyGeneratedIdentityTest.java new file mode 100644 index 000000000000..0909b1ddb01e --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/bulkid/LocalTemporaryTableMutationStrategyGeneratedIdentityTest.java @@ -0,0 +1,20 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.bulkid; + +import org.hibernate.query.sqm.mutation.internal.temptable.LocalTemporaryTableInsertStrategy; +import org.hibernate.query.sqm.mutation.spi.SqmMultiTableInsertStrategy; +import org.hibernate.testing.orm.junit.DialectFeatureChecks; +import org.hibernate.testing.orm.junit.RequiresDialectFeature; + +@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsLocalTemporaryTable.class) +@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsLocalTemporaryTableIdentity.class) +public class LocalTemporaryTableMutationStrategyGeneratedIdentityTest extends AbstractMutationStrategyGeneratedIdentityTest { + + @Override + protected Class getMultiTableInsertStrategyClass() { + return LocalTemporaryTableInsertStrategy.class; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/bulkid/LocalTemporaryTableMutationStrategyIdTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/bulkid/LocalTemporaryTableMutationStrategyIdTest.java new file mode 100644 index 000000000000..cb0c05f1bb83 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/bulkid/LocalTemporaryTableMutationStrategyIdTest.java @@ -0,0 +1,26 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.bulkid; + +import org.hibernate.query.sqm.mutation.internal.temptable.LocalTemporaryTableInsertStrategy; +import org.hibernate.query.sqm.mutation.internal.temptable.LocalTemporaryTableMutationStrategy; +import org.hibernate.query.sqm.mutation.spi.SqmMultiTableInsertStrategy; +import org.hibernate.query.sqm.mutation.spi.SqmMultiTableMutationStrategy; +import org.hibernate.testing.orm.junit.DialectFeatureChecks; +import org.hibernate.testing.orm.junit.RequiresDialectFeature; + +@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsLocalTemporaryTable.class) +public class LocalTemporaryTableMutationStrategyIdTest extends AbstractMutationStrategyIdTest { + + @Override + protected Class getMultiTableMutationStrategyClass() { + return LocalTemporaryTableMutationStrategy.class; + } + + @Override + protected Class getMultiTableInsertStrategyClass() { + return LocalTemporaryTableInsertStrategy.class; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/bulkid/PersistentTableMutationStrategyCompositeIdTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/bulkid/PersistentTableMutationStrategyCompositeIdTest.java new file mode 100644 index 000000000000..7506ed4d7b02 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/bulkid/PersistentTableMutationStrategyCompositeIdTest.java @@ -0,0 +1,23 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.bulkid; + +import org.hibernate.query.sqm.mutation.internal.temptable.PersistentTableInsertStrategy; +import org.hibernate.query.sqm.mutation.internal.temptable.PersistentTableMutationStrategy; +import org.hibernate.query.sqm.mutation.spi.SqmMultiTableInsertStrategy; +import org.hibernate.query.sqm.mutation.spi.SqmMultiTableMutationStrategy; + +public class PersistentTableMutationStrategyCompositeIdTest extends AbstractMutationStrategyCompositeIdTest { + + @Override + protected Class getMultiTableMutationStrategyClass() { + return PersistentTableMutationStrategy.class; + } + + @Override + protected Class getMultiTableInsertStrategyClass() { + return PersistentTableInsertStrategy.class; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/bulkid/PersistentTableMutationStrategyGeneratedIdTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/bulkid/PersistentTableMutationStrategyGeneratedIdTest.java new file mode 100644 index 000000000000..4ab9a01e48d1 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/bulkid/PersistentTableMutationStrategyGeneratedIdTest.java @@ -0,0 +1,16 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.bulkid; + +import org.hibernate.query.sqm.mutation.internal.temptable.PersistentTableInsertStrategy; +import org.hibernate.query.sqm.mutation.spi.SqmMultiTableInsertStrategy; + +public class PersistentTableMutationStrategyGeneratedIdTest extends AbstractMutationStrategyGeneratedIdTest { + + @Override + protected Class getMultiTableInsertStrategyClass() { + return PersistentTableInsertStrategy.class; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/bulkid/PersistentTableMutationStrategyGeneratedIdWithOptimizerTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/bulkid/PersistentTableMutationStrategyGeneratedIdWithOptimizerTest.java new file mode 100644 index 000000000000..c1597a0f84c4 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/bulkid/PersistentTableMutationStrategyGeneratedIdWithOptimizerTest.java @@ -0,0 +1,16 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.bulkid; + +import org.hibernate.query.sqm.mutation.internal.temptable.PersistentTableInsertStrategy; +import org.hibernate.query.sqm.mutation.spi.SqmMultiTableInsertStrategy; + +public class PersistentTableMutationStrategyGeneratedIdWithOptimizerTest extends AbstractMutationStrategyGeneratedIdWithOptimizerTest { + + @Override + protected Class getMultiTableInsertStrategyClass() { + return PersistentTableInsertStrategy.class; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/bulkid/PersistentTableMutationStrategyGeneratedIdentityTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/bulkid/PersistentTableMutationStrategyGeneratedIdentityTest.java new file mode 100644 index 000000000000..cd8b7c0f18d0 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/bulkid/PersistentTableMutationStrategyGeneratedIdentityTest.java @@ -0,0 +1,19 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.bulkid; + +import org.hibernate.query.sqm.mutation.internal.temptable.PersistentTableInsertStrategy; +import org.hibernate.query.sqm.mutation.spi.SqmMultiTableInsertStrategy; +import org.hibernate.testing.orm.junit.DialectFeatureChecks; +import org.hibernate.testing.orm.junit.RequiresDialectFeature; + +@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsIdentityColumns.class) +public class PersistentTableMutationStrategyGeneratedIdentityTest extends AbstractMutationStrategyGeneratedIdentityTest { + + @Override + protected Class getMultiTableInsertStrategyClass() { + return PersistentTableInsertStrategy.class; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/bulkid/PersistentTableMutationStrategyIdTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/bulkid/PersistentTableMutationStrategyIdTest.java new file mode 100644 index 000000000000..a6b5c769240a --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/bulkid/PersistentTableMutationStrategyIdTest.java @@ -0,0 +1,23 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.bulkid; + +import org.hibernate.query.sqm.mutation.internal.temptable.PersistentTableInsertStrategy; +import org.hibernate.query.sqm.mutation.internal.temptable.PersistentTableMutationStrategy; +import org.hibernate.query.sqm.mutation.spi.SqmMultiTableInsertStrategy; +import org.hibernate.query.sqm.mutation.spi.SqmMultiTableMutationStrategy; + +public class PersistentTableMutationStrategyIdTest extends AbstractMutationStrategyIdTest { + + @Override + protected Class getMultiTableMutationStrategyClass() { + return PersistentTableMutationStrategy.class; + } + + @Override + protected Class getMultiTableInsertStrategyClass() { + return PersistentTableInsertStrategy.class; + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/DialectChecks.java b/hibernate-testing/src/main/java/org/hibernate/testing/DialectChecks.java index 0ad2fb4b22be..140161e73ddf 100644 --- a/hibernate-testing/src/main/java/org/hibernate/testing/DialectChecks.java +++ b/hibernate-testing/src/main/java/org/hibernate/testing/DialectChecks.java @@ -242,7 +242,7 @@ public boolean isMatch(Dialect dialect) { public static class SupportsTemporaryTable implements DialectCheck { public boolean isMatch(Dialect dialect) { - return dialect.supportsTemporaryTables(); + return dialect.getLocalTemporaryTableStrategy() != null || dialect.getGlobalTemporaryTableStrategy() != null; } } @@ -260,7 +260,13 @@ public boolean isMatch(Dialect dialect) { public static class SupportsTemporaryTableIdentity implements DialectCheck { public boolean isMatch(Dialect dialect) { - return dialect.supportsTemporaryTablePrimaryKey(); + return dialect.getLocalTemporaryTableStrategy() != null + && dialect.getLocalTemporaryTableStrategy().supportsTemporaryTablePrimaryKey() + || dialect.getGlobalTemporaryTableStrategy() != null + && dialect.getGlobalTemporaryTableStrategy().supportsTemporaryTablePrimaryKey() + // Persistent tables definitely support identity + || dialect.getLocalTemporaryTableStrategy() == null + && dialect.getGlobalTemporaryTableStrategy() == null; } } diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/junit4/Helper.java b/hibernate-testing/src/main/java/org/hibernate/testing/junit4/Helper.java index eb1117388123..e74be83c2f19 100644 --- a/hibernate-testing/src/main/java/org/hibernate/testing/junit4/Helper.java +++ b/hibernate-testing/src/main/java/org/hibernate/testing/junit4/Helper.java @@ -162,7 +162,7 @@ public static Collection collect final S[] classAnnotations; if ( methodPluralAnn != null ) { try { - methodAnnotations = (S[]) pluralAnnotationClass.getDeclaredMethods()[0].invoke( methodPluralAnn ); + methodAnnotations = (S[]) pluralAnnotationClass.getDeclaredMethod("value").invoke( methodPluralAnn ); } catch (Exception e) { throw new RuntimeException( e ); @@ -173,7 +173,7 @@ public static Collection collect } if ( classPluralAnn != null ) { try { - classAnnotations = (S[]) pluralAnnotationClass.getDeclaredMethods()[0].invoke( classPluralAnn ); + classAnnotations = (S[]) pluralAnnotationClass.getDeclaredMethod("value").invoke( classPluralAnn ); } catch (Exception e) { throw new RuntimeException( e ); diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/DialectFeatureChecks.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/DialectFeatureChecks.java index 934c270c2f2f..7bcf4e0599ee 100644 --- a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/DialectFeatureChecks.java +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/DialectFeatureChecks.java @@ -451,7 +451,35 @@ public boolean apply(Dialect dialect) { public static class SupportsTemporaryTable implements DialectFeatureCheck { public boolean apply(Dialect dialect) { - return dialect.supportsTemporaryTables(); + return dialect.getLocalTemporaryTableStrategy() != null || dialect.getGlobalTemporaryTableStrategy() != null; + } + } + + public static class SupportsLocalTemporaryTable implements DialectFeatureCheck { + public boolean apply(Dialect dialect) { + return dialect.getLocalTemporaryTableStrategy() != null; + } + } + + public static class SupportsGlobalTemporaryTable implements DialectFeatureCheck { + public boolean apply(Dialect dialect) { + return dialect.getGlobalTemporaryTableStrategy() != null; + } + } + + public static class SupportsLocalTemporaryTableIdentity implements DialectFeatureCheck { + public boolean apply(Dialect dialect) { + return dialect.getIdentityColumnSupport().supportsIdentityColumns() + && dialect.getLocalTemporaryTableStrategy() != null + && dialect.getLocalTemporaryTableStrategy().supportsTemporaryTablePrimaryKey(); + } + } + + public static class SupportsGlobalTemporaryTableIdentity implements DialectFeatureCheck { + public boolean apply(Dialect dialect) { + return dialect.getIdentityColumnSupport().supportsIdentityColumns() + && dialect.getGlobalTemporaryTableStrategy() != null + && dialect.getGlobalTemporaryTableStrategy().supportsTemporaryTablePrimaryKey(); } } From 053e87477817394ee510ee064587e6667f05dcfb Mon Sep 17 00:00:00 2001 From: Christian Beikov Date: Thu, 24 Jul 2025 18:36:40 +0200 Subject: [PATCH 3/5] HHH-19649 Fix QualifiedNameParser parsing for three part names --- .../model/relational/QualifiedNameParser.java | 4 ++-- .../dialect/temptable/TemporaryTable.java | 22 +++++-------------- .../relational/QualifiedNameParserTest.java | 2 +- .../test/naming/QualifiedNameParserTests.java | 2 +- 4 files changed, 10 insertions(+), 20 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/relational/QualifiedNameParser.java b/hibernate-core/src/main/java/org/hibernate/boot/model/relational/QualifiedNameParser.java index 23320bb203db..2031523a93f6 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/relational/QualifiedNameParser.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/relational/QualifiedNameParser.java @@ -135,8 +135,8 @@ else if ( tokens.length == 2 ) { name = tokens[1]; } else if ( tokens.length == 3 ) { - schemaName = tokens[0]; - catalogName = tokens[1]; + catalogName = tokens[0]; + schemaName = tokens[1]; name = tokens[2]; } else { diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/temptable/TemporaryTable.java b/hibernate-core/src/main/java/org/hibernate/dialect/temptable/TemporaryTable.java index 9c5cd8c1b58c..f47dfe734411 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/temptable/TemporaryTable.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/temptable/TemporaryTable.java @@ -538,22 +538,12 @@ private static Table findTable(RuntimeModelCreationContext runtimeModelCreationC final QualifiedNameParser.NameParts nameParts = QualifiedNameParser.INSTANCE.parse( tableName ); // Strip off the default catalog and schema names since these are not reflected in the Database#namespaces final SqlStringGenerationContext sqlContext = runtimeModelCreationContext.getSqlStringGenerationContext(); - final Identifier catalog; - final Identifier schema; - if ( nameParts.getCatalogName() != null && nameParts.getSchemaName() != null ) { - // For some reason, the QualifiedNameParser thinks a.b.c parses a as schema and b as catalog, - // though it renders catalog.schema.object - catalog = nameParts.getSchemaName() == null || nameParts.getSchemaName().equals( sqlContext.getDefaultCatalog() ) - ? null : nameParts.getSchemaName(); - schema = nameParts.getCatalogName() == null || nameParts.getCatalogName().equals( sqlContext.getDefaultSchema() ) - ? null : nameParts.getCatalogName(); - } - else { - catalog = nameParts.getCatalogName() == null || nameParts.getCatalogName().equals( sqlContext.getDefaultCatalog() ) - ? null : nameParts.getCatalogName(); - schema = nameParts.getSchemaName() == null || nameParts.getSchemaName().equals( sqlContext.getDefaultSchema() ) - ? null : nameParts.getSchemaName(); - } + final Identifier catalog = + nameParts.getCatalogName() == null || nameParts.getCatalogName().equals( sqlContext.getDefaultCatalog() ) + ? null : nameParts.getCatalogName(); + final Identifier schema = + nameParts.getSchemaName() == null || nameParts.getSchemaName().equals( sqlContext.getDefaultSchema() ) + ? null : nameParts.getSchemaName(); final Identifier tableNameIdentifier = nameParts.getObjectName(); Namespace namespace = database.findNamespace( catalog, schema ); if ( namespace == null ) { diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/boot/model/relational/QualifiedNameParserTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/boot/model/relational/QualifiedNameParserTest.java index b8eb7ad3458d..8f8afaa6fef3 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/boot/model/relational/QualifiedNameParserTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/boot/model/relational/QualifiedNameParserTest.java @@ -38,7 +38,7 @@ public void testStringSplittingWithSchema() { @Test public void testStringSplittingWithCatalogAndSchema() { QualifiedNameParser.NameParts nameParts = PARSER.parse( - "schema.catalog.MyEntity", + "catalog.schema.MyEntity", DEFAULT_CATALOG, DEFAULT_SCHEMA ); diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/naming/QualifiedNameParserTests.java b/hibernate-core/src/test/java/org/hibernate/orm/test/naming/QualifiedNameParserTests.java index 928a45846346..7f8226cbb8ec 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/naming/QualifiedNameParserTests.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/naming/QualifiedNameParserTests.java @@ -75,7 +75,7 @@ void testSimpleQuoted() { @Test void testIndividualQuotes() { - final String name = "`schema2`.`catalog2`.`tbl`"; + final String name = "`catalog2`.`schema2`.`tbl`"; test( name, NO_CATALOG, From 10ee919b65063b390240d94a4095e18e8be70c41 Mon Sep 17 00:00:00 2001 From: Christian Beikov Date: Thu, 24 Jul 2025 19:15:44 +0200 Subject: [PATCH 4/5] HHH-19521 Expose different TemporaryTableStrategy from Dialect to allow configuring other mutation/insert strategies --- .../dialect/temptable/TemporaryTable.java | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/temptable/TemporaryTable.java b/hibernate-core/src/main/java/org/hibernate/dialect/temptable/TemporaryTable.java index f47dfe734411..8c0c553fb717 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/temptable/TemporaryTable.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/temptable/TemporaryTable.java @@ -21,6 +21,7 @@ import org.hibernate.boot.model.relational.SqlStringGenerationContext; import org.hibernate.dialect.Dialect; import org.hibernate.engine.jdbc.Size; +import org.hibernate.engine.jdbc.env.spi.NameQualifierSupport; import org.hibernate.generator.Generator; import org.hibernate.id.OptimizableGenerator; import org.hibernate.id.enhanced.Optimizer; @@ -534,6 +535,8 @@ private static void forEachTemporaryTableColumn(RuntimeModelCreationContext runt } private static Table findTable(RuntimeModelCreationContext runtimeModelCreationContext, String tableName) { + final NameQualifierSupport nameQualifierSupport = runtimeModelCreationContext.getJdbcServices() + .getJdbcEnvironment().getNameQualifierSupport(); final Database database = runtimeModelCreationContext.getMetadata().getDatabase(); final QualifiedNameParser.NameParts nameParts = QualifiedNameParser.INSTANCE.parse( tableName ); // Strip off the default catalog and schema names since these are not reflected in the Database#namespaces @@ -551,6 +554,41 @@ private static Table findTable(RuntimeModelCreationContext runtimeModelCreationC if ( schema != null && catalog == null ) { final Identifier alternativeCatalog = schema.equals( sqlContext.getDefaultCatalog() ) ? null : schema; namespace = database.findNamespace( alternativeCatalog, null ); + + if ( namespace == null && nameQualifierSupport == NameQualifierSupport.CATALOG ) { + Namespace candidateNamespace = null; + for ( Namespace databaseNamespace : database.getNamespaces() ) { + if ( schema.equals( databaseNamespace.getName().catalog() ) ) { + if ( candidateNamespace != null ) { + // Two namespaces with the same catalog, but different schema names + candidateNamespace = null; + break; + } + candidateNamespace = databaseNamespace; + } + } + if ( candidateNamespace != null ) { + namespace = candidateNamespace; + } + } + } + if ( namespace == null ) { + if ( nameQualifierSupport == NameQualifierSupport.SCHEMA && schema != null ) { + Namespace candidateNamespace = null; + for ( Namespace databaseNamespace : database.getNamespaces() ) { + if ( schema.equals( databaseNamespace.getName().schema() ) ) { + if ( candidateNamespace != null ) { + // Two namespaces with the same schema, but different catalog names + candidateNamespace = null; + break; + } + candidateNamespace = databaseNamespace; + } + } + if ( candidateNamespace != null ) { + namespace = candidateNamespace; + } + } } if ( namespace == null ) { throw new IllegalArgumentException( "Unable to find namespace for " + tableName ); From ebf80a5f2b294d41a412196a0e04f24d92c7181e Mon Sep 17 00:00:00 2001 From: Christian Beikov Date: Fri, 25 Jul 2025 15:04:08 +0200 Subject: [PATCH 5/5] HHH-19521 Implement temporary and cte table derivation based on boot model --- .../dialect/SQLServerLegacyDialect.java | 19 +- .../java/org/hibernate/dialect/Dialect.java | 11 +- .../hibernate/dialect/SQLServerDialect.java | 13 +- .../DB2GlobalTemporaryTableStrategy.java | 4 +- .../SQLServerLocalTemporaryTableStrategy.java | 25 + .../dialect/temptable/TemporaryTable.java | 458 ++++++------------ .../temptable/TemporaryTableHelper.java | 7 +- .../spi/RuntimeModelCreationContext.java | 3 +- .../internal/SqmMutationStrategyHelper.java | 91 +++- .../internal/cte/CteInsertHandler.java | 46 +- .../internal/cte/CteInsertStrategy.java | 33 +- .../internal/cte/CteMutationStrategy.java | 3 +- .../ExecuteWithTemporaryTableHelper.java | 88 +++- .../GlobalTemporaryTableInsertStrategy.java | 3 +- .../GlobalTemporaryTableMutationStrategy.java | 3 +- .../GlobalTemporaryTableStrategy.java | 5 - .../temptable/InsertExecutionDelegate.java | 41 +- .../LocalTemporaryTableInsertStrategy.java | 3 +- .../LocalTemporaryTableMutationStrategy.java | 3 +- .../LocalTemporaryTableStrategy.java | 5 - .../PersistentTableInsertStrategy.java | 3 +- .../PersistentTableMutationStrategy.java | 3 +- .../temptable/PersistentTableStrategy.java | 5 - .../temptable/TableBasedInsertHandler.java | 100 ++-- .../sqm/sql/BaseSqmToSqlAstConverter.java | 121 ++++- .../hibernate/sql/ast/tree/cte/CteTable.java | 167 +++---- ...poraryTableMutationStrategyNoDropTest.java | 79 +-- ...stractMutationStrategyCompositeIdTest.java | 15 + ...stractMutationStrategyGeneratedIdTest.java | 42 ++ ...nStrategyGeneratedIdWithOptimizerTest.java | 42 ++ ...MutationStrategyGeneratedIdentityTest.java | 49 ++ .../AbstractMutationStrategyIdTest.java | 15 + .../orm/test/hql/BulkManipulationTest.java | 2 + 33 files changed, 862 insertions(+), 645 deletions(-) create mode 100644 hibernate-core/src/main/java/org/hibernate/dialect/temptable/SQLServerLocalTemporaryTableStrategy.java diff --git a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/SQLServerLegacyDialect.java b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/SQLServerLegacyDialect.java index 1766ce16a18c..8f9a692afded 100644 --- a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/SQLServerLegacyDialect.java +++ b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/SQLServerLegacyDialect.java @@ -44,6 +44,8 @@ import org.hibernate.dialect.sequence.SQLServer16SequenceSupport; import org.hibernate.dialect.sequence.SQLServerSequenceSupport; import org.hibernate.dialect.sequence.SequenceSupport; +import org.hibernate.dialect.temptable.SQLServerLocalTemporaryTableStrategy; +import org.hibernate.dialect.temptable.TemporaryTableStrategy; import org.hibernate.dialect.type.SQLServerCastingXmlArrayJdbcTypeConstructor; import org.hibernate.dialect.type.SQLServerCastingXmlJdbcType; import org.hibernate.dialect.unique.AlterTableUniqueIndexDelegate; @@ -1149,19 +1151,14 @@ public void appendDateTimeLiteral( } } + @Override + public TemporaryTableStrategy getLocalTemporaryTableStrategy() { + return SQLServerLocalTemporaryTableStrategy.INSTANCE; + } + @Override public String getCreateTemporaryTableColumnAnnotation(int sqlTypeCode) { - switch (sqlTypeCode) { - case Types.CHAR: - case Types.NCHAR: - case Types.VARCHAR: - case Types.NVARCHAR: - case Types.LONGVARCHAR: - case Types.LONGNVARCHAR: - return "collate database_default"; - default: - return ""; - } + return SQLServerLocalTemporaryTableStrategy.INSTANCE.getCreateTemporaryTableColumnAnnotation( sqlTypeCode ); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/Dialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/Dialect.java index 7f72938e2f72..1a2e5d1a0832 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/Dialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/Dialect.java @@ -58,7 +58,6 @@ import org.hibernate.dialect.temptable.LegacyTemporaryTableStrategy; import org.hibernate.dialect.temptable.PersistentTemporaryTableStrategy; import org.hibernate.dialect.temptable.StandardTemporaryTableExporter; -import org.hibernate.dialect.temptable.TemporaryTable; import org.hibernate.dialect.temptable.TemporaryTableExporter; import org.hibernate.dialect.temptable.TemporaryTableKind; import org.hibernate.dialect.temptable.TemporaryTableStrategy; @@ -3209,15 +3208,7 @@ public SqmMultiTableMutationStrategy getFallbackSqmMutationStrategy( public SqmMultiTableInsertStrategy getFallbackSqmInsertStrategy( EntityMappingType entityDescriptor, RuntimeModelCreationContext runtimeModelCreationContext) { - return new PersistentTableInsertStrategy( - TemporaryTable.createEntityTable( - entityDescriptor, - name -> TemporaryTable.ENTITY_TABLE_PREFIX + name, - this, - runtimeModelCreationContext - ), - runtimeModelCreationContext.getSessionFactory() - ); + return new PersistentTableInsertStrategy( entityDescriptor, runtimeModelCreationContext ); } // UDT support ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/SQLServerDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/SQLServerDialect.java index 65032316329c..f89f53f5d2bb 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/SQLServerDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/SQLServerDialect.java @@ -32,6 +32,8 @@ import org.hibernate.dialect.sequence.SQLServerSequenceSupport; import org.hibernate.dialect.sequence.SequenceSupport; import org.hibernate.dialect.sql.ast.SQLServerSqlAstTranslator; +import org.hibernate.dialect.temptable.SQLServerLocalTemporaryTableStrategy; +import org.hibernate.dialect.temptable.TemporaryTableStrategy; import org.hibernate.dialect.type.SQLServerCastingXmlArrayJdbcTypeConstructor; import org.hibernate.dialect.type.SQLServerCastingXmlJdbcType; import org.hibernate.dialect.unique.AlterTableUniqueIndexDelegate; @@ -1068,13 +1070,14 @@ public void appendDateTimeLiteral( } } + @Override + public TemporaryTableStrategy getLocalTemporaryTableStrategy() { + return SQLServerLocalTemporaryTableStrategy.INSTANCE; + } + @Override public String getCreateTemporaryTableColumnAnnotation(int sqlTypeCode) { - return switch (sqlTypeCode) { - case Types.CHAR, Types.NCHAR, Types.VARCHAR, Types.NVARCHAR, Types.LONGVARCHAR, Types.LONGNVARCHAR -> - "collate database_default"; - default -> ""; - }; + return SQLServerLocalTemporaryTableStrategy.INSTANCE.getCreateTemporaryTableColumnAnnotation( sqlTypeCode ); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/temptable/DB2GlobalTemporaryTableStrategy.java b/hibernate-core/src/main/java/org/hibernate/dialect/temptable/DB2GlobalTemporaryTableStrategy.java index 51d3096c5383..5055a096f449 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/temptable/DB2GlobalTemporaryTableStrategy.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/temptable/DB2GlobalTemporaryTableStrategy.java @@ -17,7 +17,7 @@ public String getTemporaryTableCreateOptions() { } @Override - public String getTemporaryTableCreateCommand() { - return "declare global temporary table"; + public boolean supportsTemporaryTablePrimaryKey() { + return false; } } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/temptable/SQLServerLocalTemporaryTableStrategy.java b/hibernate-core/src/main/java/org/hibernate/dialect/temptable/SQLServerLocalTemporaryTableStrategy.java new file mode 100644 index 000000000000..c101b76590a5 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/dialect/temptable/SQLServerLocalTemporaryTableStrategy.java @@ -0,0 +1,25 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.dialect.temptable; + +import org.hibernate.type.SqlTypes; + +/** + * SQL Server specific local temporary table strategy. + */ +public class SQLServerLocalTemporaryTableStrategy extends TransactSQLLocalTemporaryTableStrategy { + + public static final SQLServerLocalTemporaryTableStrategy INSTANCE = new SQLServerLocalTemporaryTableStrategy(); + + @Override + public String getCreateTemporaryTableColumnAnnotation(int sqlTypeCode) { + return switch ( sqlTypeCode ) { + case SqlTypes.CHAR, SqlTypes.VARCHAR, SqlTypes.CLOB, SqlTypes.NCHAR, SqlTypes.NVARCHAR, SqlTypes.NCLOB -> + "collate database_default"; + default -> ""; + }; + } + +} diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/temptable/TemporaryTable.java b/hibernate-core/src/main/java/org/hibernate/dialect/temptable/TemporaryTable.java index 8c0c553fb717..dc1911c198f3 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/temptable/TemporaryTable.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/temptable/TemporaryTable.java @@ -5,36 +5,33 @@ package org.hibernate.dialect.temptable; import java.util.ArrayList; -import java.util.Iterator; import java.util.List; import java.util.UUID; -import java.util.function.BiConsumer; import java.util.function.Consumer; import java.util.function.Function; +import org.hibernate.boot.Metadata; import org.hibernate.boot.model.naming.Identifier; -import org.hibernate.boot.model.relational.Database; import org.hibernate.boot.model.relational.Exportable; -import org.hibernate.boot.model.relational.Namespace; import org.hibernate.boot.model.relational.QualifiedNameParser; import org.hibernate.boot.model.relational.QualifiedTableName; -import org.hibernate.boot.model.relational.SqlStringGenerationContext; +import org.hibernate.boot.spi.MetadataImplementor; import org.hibernate.dialect.Dialect; import org.hibernate.engine.jdbc.Size; -import org.hibernate.engine.jdbc.env.spi.NameQualifierSupport; import org.hibernate.generator.Generator; -import org.hibernate.id.OptimizableGenerator; -import org.hibernate.id.enhanced.Optimizer; +import org.hibernate.generator.OnExecutionGenerator; import org.hibernate.internal.CoreLogging; import org.hibernate.internal.CoreMessageLogger; -import org.hibernate.internal.util.collections.ArrayHelper; import org.hibernate.mapping.Collection; import org.hibernate.mapping.Column; +import org.hibernate.mapping.Component; import org.hibernate.mapping.Contributable; +import org.hibernate.mapping.KeyValue; import org.hibernate.mapping.PersistentClass; import org.hibernate.mapping.Property; import org.hibernate.mapping.Selectable; -import org.hibernate.mapping.Table; +import org.hibernate.mapping.SingleTableSubclass; +import org.hibernate.mapping.Value; import org.hibernate.metamodel.mapping.Association; import org.hibernate.metamodel.mapping.AttributeMapping; import org.hibernate.metamodel.mapping.AttributeMappingsList; @@ -44,22 +41,15 @@ import org.hibernate.metamodel.mapping.EntityIdentifierMapping; import org.hibernate.metamodel.mapping.EntityMappingType; import org.hibernate.metamodel.mapping.EntityValuedModelPart; -import org.hibernate.metamodel.mapping.ForeignKeyDescriptor; -import org.hibernate.metamodel.mapping.JdbcMapping; import org.hibernate.metamodel.mapping.ModelPart; import org.hibernate.metamodel.mapping.PluralAttributeMapping; -import org.hibernate.metamodel.mapping.internal.EmbeddedAttributeMapping; -import org.hibernate.metamodel.mapping.internal.SingleAttributeIdentifierMapping; import org.hibernate.metamodel.spi.RuntimeModelCreationContext; import org.hibernate.persister.entity.EntityPersister; -import org.hibernate.persister.entity.SingleTableEntityPersister; -import org.hibernate.persister.entity.UnionSubclassEntityPersister; import org.hibernate.query.sqm.mutation.internal.SqmMutationStrategyHelper; import org.hibernate.type.BasicType; import org.hibernate.type.StandardBasicTypes; import org.hibernate.type.spi.TypeConfiguration; -import static org.hibernate.boot.model.internal.BinderHelper.findPropertyByName; /** * @author Steve Ebersole @@ -71,10 +61,11 @@ public class TemporaryTable implements Exportable, Contributable { public static final String ENTITY_TABLE_PREFIX = "HTE_"; public static final String DEFAULT_ALIAS = "temptable_"; public static final String ENTITY_TABLE_IDENTITY_COLUMN = "HTE_IDENTITY"; + public static final String ENTITY_ROW_NUMBER_COLUMN = "rn_"; private static final CoreMessageLogger LOG = CoreLogging.messageLogger( TemporaryTable.class ); - private final EntityMappingType entityDescriptor; + private final String contributor; private final String qualifiedTableName; private final TemporaryTableKind temporaryTableKind; @@ -85,62 +76,44 @@ public class TemporaryTable implements Exportable, Contributable { private final Dialect dialect; private TemporaryTable( - EntityMappingType entityDescriptor, + PersistentClass persistentClass, Function temporaryTableNameAdjuster, TemporaryTableKind temporaryTableKind, Dialect dialect, RuntimeModelCreationContext creationContext, Function> columnInitializer) { - this.entityDescriptor = entityDescriptor; - final EntityPersister entityPersister = entityDescriptor.getEntityPersister(); - final EntityPersister rootEntityPersister = entityDescriptor.getRootEntityDescriptor().getEntityPersister(); - final String persisterQuerySpace = entityPersister.getSynchronizedQuerySpaces()[0]; - final QualifiedNameParser.NameParts nameParts = QualifiedNameParser.INSTANCE.parse( persisterQuerySpace ); - // The table name might be a sub-query, which is inappropriate for a temporary table name - final String tableBaseName; - if ( rootEntityPersister != entityPersister - && rootEntityPersister instanceof SingleTableEntityPersister singleTableEntityPersister ) { + this.contributor = persistentClass.getContributor(); + final Identifier tableNameIdentifier; + if ( persistentClass instanceof SingleTableSubclass ) { // In this case, the descriptor is a subclass of a single table inheritance. // To avoid name collisions, we suffix the table name with the subclass number - tableBaseName = nameParts.getObjectName().getText() + ArrayHelper.indexOf( - singleTableEntityPersister.getSubclassClosure(), - entityPersister.getEntityName() - ); - } - else { - tableBaseName = nameParts.getObjectName().getText(); - } - final QualifiedNameParser.NameParts adjustedNameParts = QualifiedNameParser.INSTANCE.parse( - temporaryTableNameAdjuster.apply( tableBaseName ) - ); - final String temporaryTableName = adjustedNameParts.getObjectName().getText(); - final Identifier tableNameIdentifier; - if ( temporaryTableName.length() > dialect.getMaxIdentifierLength() ) { tableNameIdentifier = new Identifier( - temporaryTableName.substring( 0, dialect.getMaxIdentifierLength() ), - nameParts.getObjectName().isQuoted() + persistentClass.getTable().getNameIdentifier().getText() + persistentClass.getSubclassId(), + persistentClass.getTable().getNameIdentifier().isQuoted() ); } else { - tableNameIdentifier = new Identifier( temporaryTableName, nameParts.getObjectName().isQuoted() ); + tableNameIdentifier = persistentClass.getTable().getNameIdentifier(); } + // Have to parse the adjusted name, since it could be prepended by a schema + final QualifiedNameParser.NameParts nameParts = QualifiedNameParser.INSTANCE + .parse( temporaryTableNameAdjuster.apply( tableNameIdentifier.getText() ) ); + final Identifier catalogIdentifier = nameParts.getCatalogName() != null ? nameParts.getCatalogName() + : persistentClass.getTable().getCatalogIdentifier(); + final Identifier schemaIdentifier = nameParts.getSchemaName() != null ? nameParts.getSchemaName() + : persistentClass.getTable().getSchemaIdentifier(); + final String adjustedName = nameParts.getObjectName().getText(); + final Identifier temporaryTableNameIdentifier = new Identifier( + adjustedName.substring( 0, Math.min( dialect.getMaxIdentifierLength(), adjustedName.length() ) ), + tableNameIdentifier.isQuoted() + ); this.qualifiedTableName = creationContext.getSqlStringGenerationContext().format( - new QualifiedTableName( - adjustedNameParts.getCatalogName() != null - ? adjustedNameParts.getCatalogName() - : nameParts.getCatalogName(), - adjustedNameParts.getSchemaName() != null - ? adjustedNameParts.getSchemaName() - : nameParts.getSchemaName(), - tableNameIdentifier - ) + new QualifiedTableName( catalogIdentifier, schemaIdentifier, temporaryTableNameIdentifier ) ); this.temporaryTableKind = temporaryTableKind; this.dialect = dialect; if ( temporaryTableKind == TemporaryTableKind.PERSISTENT ) { - final TypeConfiguration typeConfiguration = entityPersister - .getFactory() - .getTypeConfiguration(); + final TypeConfiguration typeConfiguration = creationContext.getTypeConfiguration(); final BasicType uuidType = typeConfiguration.getBasicTypeRegistry().resolve( StandardBasicTypes.UUID_CHAR ); @@ -189,7 +162,8 @@ public static TemporaryTable createIdTable( Dialect dialect, RuntimeModelCreationContext runtimeModelCreationContext) { return createIdTable( - entityDescriptor, + runtimeModelCreationContext.getBootModel() + .getEntityBinding( entityDescriptor.getEntityName() ), temporaryTableNameAdjuster, dialect.getSupportedTemporaryTableKind(), dialect, @@ -197,87 +171,68 @@ public static TemporaryTable createIdTable( ); } - public static TemporaryTable createIdTable( + @Deprecated(forRemoval = true, since = "7.1") + public static TemporaryTable createEntityTable( EntityMappingType entityDescriptor, Function temporaryTableNameAdjuster, + Dialect dialect, + RuntimeModelCreationContext runtimeModelCreationContext) { + return createIdTable( + runtimeModelCreationContext.getBootModel() + .getEntityBinding( entityDescriptor.getEntityName() ), + temporaryTableNameAdjuster, + dialect.getSupportedTemporaryTableKind(), + dialect, + runtimeModelCreationContext + ); + } + + public static TemporaryTable createIdTable( + PersistentClass persistentClass, + Function temporaryTableNameAdjuster, TemporaryTableKind temporaryTableKind, Dialect dialect, RuntimeModelCreationContext runtimeModelCreationContext) { return new TemporaryTable( - entityDescriptor, + persistentClass, temporaryTableNameAdjuster, temporaryTableKind, dialect, runtimeModelCreationContext, temporaryTable -> { + final MetadataImplementor metadata = runtimeModelCreationContext.getMetadata(); final List columns = new ArrayList<>(); - final PersistentClass entityBinding = runtimeModelCreationContext.getBootModel() - .getEntityBinding( entityDescriptor.getEntityName() ); - final EntityIdentifierMapping identifierMapping = entityDescriptor.getIdentifierMapping(); - int idIdx = 0; - for ( Column column : entityBinding.getKey().getColumns() ) { - final JdbcMapping jdbcMapping = identifierMapping.getJdbcMapping( idIdx++ ); + for ( Column column : persistentClass.getKey().getColumns() ) { columns.add( new TemporaryTableColumn( temporaryTable, column.getText( dialect ), - jdbcMapping, - column.getSqlType( - runtimeModelCreationContext.getMetadata() - ), - column.getColumnSize( - dialect, - runtimeModelCreationContext.getMetadata() - ), + column.getType(), + column.getSqlType( metadata ), + column.getColumnSize( dialect, metadata ), column.isNullable(), true ) ); } - visitPluralAttributes( entityDescriptor, (pluralAttribute, attributeName) -> { - if ( pluralAttribute.getSeparateCollectionTable() != null ) { - // Ensure that the FK target columns are available - final ForeignKeyDescriptor keyDescriptor = pluralAttribute.getKeyDescriptor(); - if ( keyDescriptor == null ) { - // This is expected to happen when processing a - // PostInitCallbackEntry because the callbacks - // are not ordered. The exception is caught in - // MappingModelCreationProcess.executePostInitCallbacks() - // and the callback is re-queued. - throw new IllegalStateException( "Not yet ready: " + pluralAttribute ); - } - final ModelPart fkTarget = keyDescriptor.getTargetPart(); - if ( !fkTarget.isEntityIdentifierMapping() ) { - final PersistentClass declaringClass = runtimeModelCreationContext.getBootModel() - .getEntityBinding( pluralAttribute.findContainingEntityMapping().getEntityName() ); - final Property property = findPropertyByName( declaringClass, attributeName ); - assert property != null; - final Collection collection = (Collection) property.getValue(); - final Iterator columnIterator = collection.getKey().getSelectables().iterator(); - fkTarget.forEachSelectable( - (columnIndex, selection) -> { - final Selectable selectable = columnIterator.next(); - if ( selectable instanceof Column column ) { - columns.add( - new TemporaryTableColumn( - temporaryTable, - column.getText( dialect ), - selection.getJdbcMapping(), - column.getSqlType( - runtimeModelCreationContext.getMetadata() - ), - column.getColumnSize( - dialect, - runtimeModelCreationContext.getMetadata() - ), - column.isNullable() - ) - ); - } - } - ); + visitPluralAttributes( persistentClass.getPropertyClosure(), collection -> { + if ( collection.getCollectionTable() != null && collection.getReferencedPropertyName() != null ) { + final KeyValue collectionKey = collection.getKey(); + for ( Selectable selectable : collectionKey.getSelectables() ) { + if ( selectable instanceof Column column ) { + columns.add( + new TemporaryTableColumn( + temporaryTable, + column.getText( dialect ), + column.getType(), + column.getSqlType( metadata ), + column.getColumnSize( dialect, metadata ), + column.isNullable() + ) + ); + } } } } ); @@ -286,102 +241,52 @@ public static TemporaryTable createIdTable( ); } - private static void visitPluralAttributes( - EntityMappingType entityDescriptor, - BiConsumer consumer) { - entityDescriptor.visitSubTypeAttributeMappings( - attribute -> { - if ( attribute instanceof PluralAttributeMapping pluralAttributeMapping ) { - consumer.accept( pluralAttributeMapping, attribute.getAttributeName() ); - } - else if ( attribute instanceof EmbeddedAttributeMapping embeddedAttributeMapping ) { - visitPluralAttributes( - embeddedAttributeMapping, - attribute.getAttributeName(), - consumer - ); - } - } - ); - } - - private static void visitPluralAttributes( - EmbeddedAttributeMapping attributeMapping, - String attributeName, - BiConsumer consumer) { - attributeMapping.visitSubParts( - modelPart -> { - if ( modelPart instanceof PluralAttributeMapping pluralAttribute ) { - consumer.accept( pluralAttribute, attributeName + "." + pluralAttribute.getAttributeName() ); - } - else if ( modelPart instanceof EmbeddedAttributeMapping embeddedAttribute ) { - visitPluralAttributes( - embeddedAttribute, - attributeName + "." + embeddedAttribute.getAttributeName(), - consumer - ); - } - }, - null - ); - } - - @Deprecated(forRemoval = true, since = "7.1") - public static TemporaryTable createEntityTable( - EntityMappingType entityDescriptor, - Function temporaryTableNameAdjuster, - Dialect dialect, - RuntimeModelCreationContext runtimeModelCreationContext) { - return createIdTable( - entityDescriptor, - temporaryTableNameAdjuster, - dialect.getSupportedTemporaryTableKind(), - dialect, - runtimeModelCreationContext - ); + private static void visitPluralAttributes(List properties, Consumer consumer) { + for ( Property property : properties ) { + final Value value = property.getValue(); + if ( value instanceof Collection collection ) { + consumer.accept( collection ); + } + else if ( value instanceof Component component ) { + visitPluralAttributes( component.getProperties(), consumer ); + } + } } public static TemporaryTable createEntityTable( - EntityMappingType entityDescriptor, + PersistentClass persistentClass, Function temporaryTableNameAdjuster, TemporaryTableKind temporaryTableKind, Dialect dialect, RuntimeModelCreationContext runtimeModelCreationContext) { return new TemporaryTable( - entityDescriptor, + persistentClass, temporaryTableNameAdjuster, temporaryTableKind, dialect, runtimeModelCreationContext, temporaryTable -> { + final MetadataImplementor metadata = runtimeModelCreationContext.getMetadata(); final List columns = new ArrayList<>(); - final PersistentClass entityBinding = runtimeModelCreationContext.getBootModel() - .getEntityBinding( entityDescriptor.getEntityName() ); - - final Generator identifierGenerator = entityDescriptor.getEntityPersister().getGenerator(); - final boolean identityColumn = identifierGenerator.generatedOnExecution(); - final boolean hasOptimizer; + final List rootKeyColumns = persistentClass.getRootClass().getKey().getColumns(); + final boolean identityColumn = rootKeyColumns.size() == 1 && rootKeyColumns.get( 0 ).isIdentity(); + final boolean isExternallyGenerated; if ( identityColumn ) { - hasOptimizer = false; - final EntityIdentifierMapping identifierMapping = entityDescriptor.getIdentifierMapping(); - int idIdx = 0; - for ( Column column : entityBinding.getKey().getColumns() ) { - final JdbcMapping jdbcMapping = identifierMapping.getJdbcMapping( idIdx++ ); + isExternallyGenerated = false; + for ( Column column : persistentClass.getKey().getColumns() ) { String sqlTypeName = ""; if ( dialect.getIdentityColumnSupport().hasDataTypeInIdentityColumn() ) { - sqlTypeName = column.getSqlType( runtimeModelCreationContext.getMetadata() ) + " "; + sqlTypeName = column.getSqlType( metadata ) + " "; } - sqlTypeName = sqlTypeName + dialect.getIdentityColumnSupport().getIdentityColumnString( column.getSqlTypeCode( runtimeModelCreationContext.getMetadata() ) ); + sqlTypeName = sqlTypeName + dialect.getIdentityColumnSupport() + .getIdentityColumnString( column.getSqlTypeCode( metadata ) ); columns.add( new TemporaryTableColumn( temporaryTable, ENTITY_TABLE_IDENTITY_COLUMN, - jdbcMapping, + column.getType(), sqlTypeName, - column.getColumnSize( - dialect, - runtimeModelCreationContext.getMetadata() - ), + column.getColumnSize( dialect, metadata ), // Always report as nullable as the identity column string usually includes the not null constraint true,//column.isNullable() true @@ -390,23 +295,27 @@ public static TemporaryTable createEntityTable( } } else { - if ( identifierGenerator instanceof OptimizableGenerator optimizableGenerator ) { - final Optimizer optimizer = optimizableGenerator.getOptimizer(); - hasOptimizer = optimizer != null && optimizer.getIncrementSize() > 1; - } - else { - hasOptimizer = false; - } + // This is a bit fishy, because for the generator to exist in this map, + // the EntityPersister already has to be built. Currently, we have + // no other way to understand what generators do until we have a boot + // model representation of the generator information, so this will have + // to do + final Generator identifierGenerator = runtimeModelCreationContext.getGenerators() + .get( persistentClass.getRootClass().getEntityName() ); + assert identifierGenerator != null; + + isExternallyGenerated = !(identifierGenerator instanceof OnExecutionGenerator generator + && generator.generatedOnExecution()); } - final EntityIdentifierMapping identifierMapping = entityDescriptor.getIdentifierMapping(); + final Property identifierProperty = persistentClass.getIdentifierProperty(); final String idName; - if ( identifierMapping instanceof SingleAttributeIdentifierMapping ) { - idName = identifierMapping.getAttributeName(); + if ( identifierProperty != null ) { + idName = identifierProperty.getName(); } else { idName = "id"; } - forEachTemporaryTableColumn( runtimeModelCreationContext, temporaryTable, idName, identifierMapping, temporaryTableColumn -> { + forEachTemporaryTableColumn( metadata, temporaryTable, idName, persistentClass.getIdentifier(), temporaryTableColumn -> { columns.add( new TemporaryTableColumn( temporaryTableColumn.getContainingTable(), temporaryTableColumn.getColumnName(), @@ -414,14 +323,14 @@ public static TemporaryTable createEntityTable( temporaryTableColumn.getSqlTypeDefinition(), temporaryTableColumn.getSize(), // We have to set the identity column after the root table insert - identityColumn || hasOptimizer, - !identityColumn && !hasOptimizer + identityColumn || isExternallyGenerated, + !identityColumn && !isExternallyGenerated ) ); }); - final EntityDiscriminatorMapping discriminatorMapping = entityDescriptor.getDiscriminatorMapping(); - if ( discriminatorMapping != null && discriminatorMapping.hasPhysicalColumn() && !discriminatorMapping.isFormula() ) { - forEachTemporaryTableColumn( runtimeModelCreationContext, temporaryTable, "class", discriminatorMapping, temporaryTableColumn -> { + final Value discriminator = persistentClass.getDiscriminator(); + if ( discriminator != null && !discriminator.getSelectables().get( 0 ).isFormula() ) { + forEachTemporaryTableColumn( metadata, temporaryTable, "class", discriminator, temporaryTableColumn -> { columns.add( new TemporaryTableColumn( temporaryTableColumn.getContainingTable(), temporaryTableColumn.getColumnName(), @@ -429,21 +338,25 @@ public static TemporaryTable createEntityTable( temporaryTableColumn.getSqlTypeDefinition(), temporaryTableColumn.getSize(), // We have to set the identity column after the root table insert - discriminatorMapping.isNullable() + discriminator.isNullable() ) ); } ); } // Collect all columns for all entity subtype attributes - entityDescriptor.visitSubTypeAttributeMappings( - attribute -> { - if ( !( attribute instanceof PluralAttributeMapping ) ) { - forEachTemporaryTableColumn( runtimeModelCreationContext, temporaryTable, attribute.getAttributeName(), attribute, columns::add ); - } - } - ); - if ( hasOptimizer ) { - final TypeConfiguration typeConfiguration = runtimeModelCreationContext.getTypeConfiguration(); + for ( Property property : persistentClass.getPropertyClosure() ) { + if ( !property.isSynthetic() ) { + forEachTemporaryTableColumn( + metadata, + temporaryTable, + property.getName(), + property.getValue(), + columns::add + ); + } + } + if ( isExternallyGenerated ) { + final TypeConfiguration typeConfiguration = metadata.getTypeConfiguration(); // We add a special row number column that we can use to identify and join rows final BasicType integerBasicType = typeConfiguration.getBasicTypeForJavaType( Integer.class ); final String rowNumberType; @@ -472,10 +385,10 @@ else if ( dialect.getIdentityColumnSupport().supportsIdentityColumns() ) { ), integerBasicType ) + " " + dialect.getIdentityColumnSupport() - .getIdentityColumnString( integerBasicType.getJdbcType().getDdlTypeCode() ); + .getIdentityColumnString( integerBasicType.getJdbcType().getDdlTypeCode() ); } else { - LOG.multiTableInsertNotAvailable( entityBinding.getEntityName() ); + LOG.multiTableInsertNotAvailable( persistentClass.getEntityName() ); rowNumberType = typeConfiguration.getDdlTypeRegistry().getTypeName( integerBasicType.getJdbcType().getDdlTypeCode(), dialect.getSizeStrategy().resolveSize( @@ -491,7 +404,7 @@ else if ( dialect.getIdentityColumnSupport().supportsIdentityColumns() ) { columns.add( new TemporaryTableColumn( temporaryTable, - "rn_", + ENTITY_ROW_NUMBER_COLUMN, integerBasicType, rowNumberType, Size.nil(), @@ -505,28 +418,16 @@ else if ( dialect.getIdentityColumnSupport().supportsIdentityColumns() ) { ); } - private static void forEachTemporaryTableColumn(RuntimeModelCreationContext runtimeModelCreationContext, TemporaryTable temporaryTable, String prefix, ModelPart modelPart, Consumer consumer) { - final Dialect dialect = runtimeModelCreationContext.getDialect(); - final EntityPersister declaringPersister = modelPart.findContainingEntityMapping().getEntityPersister(); - SqmMutationStrategyHelper.forEachSelectableMapping( prefix, modelPart, (columnName, selectableMapping) -> { - final String tableExpression = selectableMapping.getContainingTableExpression(); - final String tableName = - tableExpression.charAt( 0 ) == '(' && declaringPersister instanceof UnionSubclassEntityPersister - ? declaringPersister.getRootTableName() : tableExpression; - final Table table = findTable( runtimeModelCreationContext, tableName ); - final Column column = table.getColumn( Identifier.toIdentifier( selectableMapping.getSelectionExpression() ) ); + private static void forEachTemporaryTableColumn(Metadata metadata, TemporaryTable temporaryTable, String prefix, Value value, Consumer consumer) { + final Dialect dialect = metadata.getDatabase().getDialect(); + SqmMutationStrategyHelper.forEachSelectableMapping( prefix, value, (columnName, selectable) -> { consumer.accept( new TemporaryTableColumn( temporaryTable, columnName, - selectableMapping.getJdbcMapping(), - column.getSqlType( - runtimeModelCreationContext.getMetadata() - ), - column.getColumnSize( - dialect, - runtimeModelCreationContext.getMetadata() - ), + selectable.getType(), + selectable.getSqlType( metadata ), + selectable.getColumnSize( dialect, metadata ), // Treat regular temporary table columns as nullable for simplicity true ) @@ -534,83 +435,6 @@ private static void forEachTemporaryTableColumn(RuntimeModelCreationContext runt } ); } - private static Table findTable(RuntimeModelCreationContext runtimeModelCreationContext, String tableName) { - final NameQualifierSupport nameQualifierSupport = runtimeModelCreationContext.getJdbcServices() - .getJdbcEnvironment().getNameQualifierSupport(); - final Database database = runtimeModelCreationContext.getMetadata().getDatabase(); - final QualifiedNameParser.NameParts nameParts = QualifiedNameParser.INSTANCE.parse( tableName ); - // Strip off the default catalog and schema names since these are not reflected in the Database#namespaces - final SqlStringGenerationContext sqlContext = runtimeModelCreationContext.getSqlStringGenerationContext(); - final Identifier catalog = - nameParts.getCatalogName() == null || nameParts.getCatalogName().equals( sqlContext.getDefaultCatalog() ) - ? null : nameParts.getCatalogName(); - final Identifier schema = - nameParts.getSchemaName() == null || nameParts.getSchemaName().equals( sqlContext.getDefaultSchema() ) - ? null : nameParts.getSchemaName(); - final Identifier tableNameIdentifier = nameParts.getObjectName(); - Namespace namespace = database.findNamespace( catalog, schema ); - if ( namespace == null ) { - // When parsing a name, we assume a qualifier is a schema, but could maybe be a catalog - if ( schema != null && catalog == null ) { - final Identifier alternativeCatalog = schema.equals( sqlContext.getDefaultCatalog() ) ? null : schema; - namespace = database.findNamespace( alternativeCatalog, null ); - - if ( namespace == null && nameQualifierSupport == NameQualifierSupport.CATALOG ) { - Namespace candidateNamespace = null; - for ( Namespace databaseNamespace : database.getNamespaces() ) { - if ( schema.equals( databaseNamespace.getName().catalog() ) ) { - if ( candidateNamespace != null ) { - // Two namespaces with the same catalog, but different schema names - candidateNamespace = null; - break; - } - candidateNamespace = databaseNamespace; - } - } - if ( candidateNamespace != null ) { - namespace = candidateNamespace; - } - } - } - if ( namespace == null ) { - if ( nameQualifierSupport == NameQualifierSupport.SCHEMA && schema != null ) { - Namespace candidateNamespace = null; - for ( Namespace databaseNamespace : database.getNamespaces() ) { - if ( schema.equals( databaseNamespace.getName().schema() ) ) { - if ( candidateNamespace != null ) { - // Two namespaces with the same schema, but different catalog names - candidateNamespace = null; - break; - } - candidateNamespace = databaseNamespace; - } - } - if ( candidateNamespace != null ) { - namespace = candidateNamespace; - } - } - } - if ( namespace == null ) { - throw new IllegalArgumentException( "Unable to find namespace for " + tableName ); - } - } - final Table table = namespace.locateTable( tableNameIdentifier ); - // The tableNameIdentifier is a physical name, so double check if it is correct - if ( table != null && table.getNameIdentifier().equals( tableNameIdentifier ) ) { - return table; - } - else { - // Fallback to searching for table by comparing physical names - for ( Table namespaceTable : namespace.getTables() ) { - if ( tableNameIdentifier.equals( namespaceTable.getNameIdentifier() ) ) { - return namespaceTable; - } - } - - throw new IllegalArgumentException( "No table with name [" + nameParts.getObjectName() + "] found in namespace [" + namespace.getName() + "]" ); - } - } - public List findTemporaryTableColumns(EntityPersister entityDescriptor, ModelPart modelPart) { final int offset = determineModelPartStartIndex( entityDescriptor, modelPart ); if ( offset == -1 ) { @@ -696,8 +520,10 @@ else if ( modelPart instanceof BasicValuedModelPart basicModelPart ) { return offset + modelPart.getJdbcTypeCount(); } - public EntityMappingType getEntityDescriptor() { - return entityDescriptor; + public boolean isRowNumberGenerated() { + // Only assign a value for the rowNumber column if it isn't using an identity insert + return !dialect.supportsWindowFunctions() + && dialect.getIdentityColumnSupport().supportsIdentityColumns(); } public String getQualifiedTableName() { @@ -726,7 +552,7 @@ public String getTableExpression() { @Override public String getContributor() { - return entityDescriptor.getContributor(); + return contributor; } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/temptable/TemporaryTableHelper.java b/hibernate-core/src/main/java/org/hibernate/dialect/temptable/TemporaryTableHelper.java index 2d8ab178515d..bff3a62d126c 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/temptable/TemporaryTableHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/temptable/TemporaryTableHelper.java @@ -20,6 +20,7 @@ import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.internal.CoreLogging; import org.hibernate.internal.CoreMessageLogger; +import org.hibernate.jdbc.AbstractReturningWork; import org.hibernate.jdbc.AbstractWork; /** @@ -33,7 +34,7 @@ public class TemporaryTableHelper { // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Creation - public static class TemporaryTableCreationWork extends AbstractWork { + public static class TemporaryTableCreationWork extends AbstractReturningWork { private final TemporaryTable temporaryTable; private final TemporaryTableExporter exporter; private final SessionFactoryImplementor sessionFactory; @@ -58,7 +59,7 @@ public TemporaryTableCreationWork( } @Override - public void execute(Connection connection) { + public Boolean execute(Connection connection) { final JdbcServices jdbcServices = sessionFactory.getJdbcServices(); try { @@ -68,6 +69,7 @@ public void execute(Connection connection) { try (Statement statement = connection.createStatement()) { statement.executeUpdate( creationCommand ); jdbcServices.getSqlExceptionHelper().handleAndClearWarnings( statement, WARNING_HANDLER ); + return Boolean.TRUE; } catch (SQLException e) { log.debugf( @@ -81,6 +83,7 @@ public void execute(Connection connection) { catch( Exception e ) { log.debugf( "Error creating temporary table(s) : %s", e.getMessage() ); } + return Boolean.FALSE; } } diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/spi/RuntimeModelCreationContext.java b/hibernate-core/src/main/java/org/hibernate/metamodel/spi/RuntimeModelCreationContext.java index 318eeec63878..d05bc5a22aad 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/spi/RuntimeModelCreationContext.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/spi/RuntimeModelCreationContext.java @@ -37,7 +37,8 @@ public interface RuntimeModelCreationContext { MappingMetamodelImplementor getDomainModel(); default TypeConfiguration getTypeConfiguration() { - return getBootstrapContext().getTypeConfiguration(); + return getBootstrapContext() == null ? getSessionFactory().getTypeConfiguration() + : getBootstrapContext().getTypeConfiguration(); } default JavaTypeRegistry getJavaTypeRegistry() { diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/SqmMutationStrategyHelper.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/SqmMutationStrategyHelper.java index 53f2a9bf0d64..8270e925b2b0 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/SqmMutationStrategyHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/SqmMutationStrategyHelper.java @@ -10,6 +10,19 @@ import org.hibernate.engine.jdbc.spi.JdbcServices; import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.mapping.Any; +import org.hibernate.mapping.BasicValue; +import org.hibernate.mapping.Collection; +import org.hibernate.mapping.Column; +import org.hibernate.mapping.Component; +import org.hibernate.mapping.DependantValue; +import org.hibernate.mapping.OneToMany; +import org.hibernate.mapping.OneToOne; +import org.hibernate.mapping.PersistentClass; +import org.hibernate.mapping.Property; +import org.hibernate.mapping.Selectable; +import org.hibernate.mapping.ToOne; +import org.hibernate.mapping.Value; import org.hibernate.metamodel.mapping.Association; import org.hibernate.metamodel.mapping.AttributeMapping; import org.hibernate.metamodel.mapping.AttributeMappingsList; @@ -25,6 +38,7 @@ import org.hibernate.metamodel.mapping.ModelPart; import org.hibernate.metamodel.mapping.PluralAttributeMapping; import org.hibernate.metamodel.mapping.SelectableMapping; +import org.hibernate.metamodel.mapping.ValuedModelPart; import org.hibernate.metamodel.mapping.internal.EmbeddedAttributeMapping; import org.hibernate.metamodel.mapping.internal.ToOneAttributeMapping; import org.hibernate.persister.entity.EntityPersister; @@ -205,7 +219,6 @@ public static boolean isPartOfId(AttributeMapping attributeMapping) { || isId( embeddableMappingType.getEmbeddedValueMapping() )); } - public static void forEachSelectableMapping(String prefix, ModelPart modelPart, BiConsumer consumer) { if ( modelPart instanceof BasicValuedModelPart basicModelPart ) { if ( basicModelPart.isInsertable() ) { @@ -239,8 +252,12 @@ else if ( modelPart instanceof EntityValuedModelPart entityPart ) { return; } } + final ValuedModelPart targetPart = association.getForeignKeyDescriptor().getTargetPart(); + final String newPrefix = targetPart instanceof AttributeMapping attributeMapping + ? prefix + "_" + attributeMapping.getAttributeName() + : prefix; forEachSelectableMapping( - prefix + "_" + entityPart.getPartName(), + newPrefix, association.getForeignKeyDescriptor().getKeyPart(), consumer ); @@ -261,15 +278,83 @@ else if ( modelPart instanceof DiscriminatedAssociationModelPart discriminatedPa else { final EmbeddableValuedModelPart embeddablePart = ( EmbeddableValuedModelPart ) modelPart; final AttributeMappingsList attributeMappings = embeddablePart.getEmbeddableTypeDescriptor().getAttributeMappings(); + if ( attributeMappings.size() == 0 ) { + throw new IllegalStateException( "AttributeMappingsList not read yet on embeddable: " + embeddablePart ); + } for ( int i = 0; i < attributeMappings.size(); i++ ) { AttributeMapping mapping = attributeMappings.get( i ); if ( !( mapping instanceof PluralAttributeMapping ) ) { - forEachSelectableMapping( prefix + "_" + mapping.getAttributeName(), mapping, consumer ); + final String newPrefix = modelPart.isVirtual() ? mapping.getAttributeName() + : prefix + "_" + mapping.getAttributeName(); + forEachSelectableMapping( newPrefix, mapping, consumer ); } } } } + public static void forEachSelectableMapping(String prefix, Value value, BiConsumer consumer) { + if ( value instanceof BasicValue || value instanceof Any.MetaValue || value instanceof Any.KeyValue ) { + assert value.getSelectables().size() == 1; + final Selectable selectable = value.getSelectables().get( 0 ); + if ( selectable instanceof Column column && value.isColumnInsertable( 0 ) ) { + consumer.accept( prefix, column ); + } + } + else if ( value instanceof DependantValue dependantValue ) { + forEachSelectableMapping( prefix, dependantValue.getWrappedValue(), consumer ); + } + else if ( value instanceof ToOne toOne ) { + if ( toOne instanceof OneToOne oneToOne && oneToOne.getMappedByProperty() != null ) { + // Inverse to-one receives no column + return; + } + final PersistentClass targetEntity = toOne.getBuildingContext().getMetadataCollector() + .getEntityBinding( toOne.getReferencedEntityName() ); + final String targetAttributeName; + final Value targetValue; + if ( toOne.isReferenceToPrimaryKey() ) { + targetValue = targetEntity.getIdentifier(); + targetAttributeName = targetEntity.getIdentifierProperty() != null + ? targetEntity.getIdentifierProperty().getName() : "id"; + } + else { + targetAttributeName = toOne.getReferencedPropertyName(); + targetValue = targetEntity.getReferencedProperty( toOne.getReferencedPropertyName() ).getValue(); + } + forEachSelectableMapping( + prefix + "_" + targetAttributeName, + targetValue, + consumer + ); + } + else if ( value instanceof Any any ) { + forEachSelectableMapping( + prefix + "_discriminator", + any.getDiscriminatorDescriptor() != null ? any.getDiscriminatorDescriptor() : any.getMetaMapping(), + consumer + ); + forEachSelectableMapping( + prefix + "_key", + any.getKeyDescriptor() != null ? any.getKeyDescriptor() : any.getKeyMapping(), + consumer + ); + } + else if ( value instanceof Component component) { + final int propertySpan = component.getPropertySpan(); + for ( int i = 0; i < propertySpan; i++ ) { + final Property property = component.getProperty( i ); + final String newPrefix = component.isEmbedded() ? property.getName() : prefix + "_" + property.getName(); + forEachSelectableMapping( newPrefix, property.getValue(), consumer ); + } + } + else if ( value instanceof OneToMany || value instanceof Collection ) { + // No-op + } + else { + throw new UnsupportedOperationException( "Unsupported value type: " + value.getClass() ); + } + } + private static int findTableIndex(EntityPersister declaringEntityPersister, String tableExpression) { final String[] tableNames = declaringEntityPersister.getTableNames(); for ( int i = 0; i < tableNames.length; i++ ) { diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/cte/CteInsertHandler.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/cte/CteInsertHandler.java index 78b20ec6ef0c..7a0fb736a600 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/cte/CteInsertHandler.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/cte/CteInsertHandler.java @@ -14,11 +14,9 @@ import org.hibernate.id.enhanced.Optimizer; import org.hibernate.internal.util.collections.CollectionHelper; import org.hibernate.internal.util.collections.Stack; -import org.hibernate.metamodel.mapping.AttributeMapping; import org.hibernate.metamodel.mapping.BasicValuedMapping; import org.hibernate.metamodel.mapping.EntityMappingType; import org.hibernate.metamodel.mapping.MappingModelExpressible; -import org.hibernate.metamodel.mapping.ModelPart; import org.hibernate.metamodel.mapping.SqlExpressible; import org.hibernate.persister.entity.EntityPersister; import org.hibernate.query.SemanticException; @@ -202,8 +200,7 @@ public int execute(DomainQueryExecutionContext executionContext) { final BaseSqmToSqlAstConverter.AdditionalInsertValues additionalInsertValues = sqmConverter.visitInsertionTargetPaths( (assignable, columnReferences) -> { final SqmPathInterpretation pathInterpretation = (SqmPathInterpretation) assignable; - final List columns = - cteTable.findCteColumns( entityDescriptor, pathInterpretation.getExpressionType() ); + final List columns = cteTable.findCteColumns( pathInterpretation.getExpressionType() ); targetPathCteColumns.addAll( columns ); targetPathColumns.add( new AbstractMap.SimpleEntry<>( @@ -288,15 +285,17 @@ public int execute(DomainQueryExecutionContext executionContext) { ); } } - querySpec.getSelectClause().addSqlSelection( - new SqlSelectionImpl( - 0, - SqmInsertStrategyHelper.createRowNumberingExpression( - querySpec, - sessionFactory - ) - ) - ); + if ( !assignsId && entityDescriptor.getGenerator().generatedOnExecution() ) { + querySpec.getSelectClause().addSqlSelection( + new SqlSelectionImpl( + 0, + SqmInsertStrategyHelper.createRowNumberingExpression( + querySpec, + sessionFactory + ) + ) + ); + } final ValuesTableGroup valuesTableGroup = new ValuesTableGroup( navigablePath, entityDescriptor.getEntityPersister(), @@ -709,7 +708,7 @@ protected String addDmlCtes( final ConflictClause conflictClause = sqmConverter.visitConflictClause( sqmStatement.getConflictClause() ); final int tableSpan = persister.getTableSpan(); - final List keyCteColumns = findCteColumns( persister.getIdentifierMapping(), queryCte.getCteTable().getCteColumns() ); + final List keyCteColumns = queryCte.getCteTable().findCteColumns( persister.getIdentifierMapping() ); for ( int tableIndex = 0; tableIndex < tableSpan; tableIndex++ ) { final String tableExpression = persister.getTableName( tableIndex ); final TableReference updatingTableReference = updatingTableGroup.getTableReference( @@ -1021,25 +1020,6 @@ else if ( targetColumnsContainAllConstraintColumns( dmlStatement, conflictClause return getCteTableName( rootTableName ); } - private List findCteColumns(ModelPart modelPart, List cteColumns) { - final int numberOfColumns = modelPart.getJdbcTypeCount(); - final List columns = new ArrayList<>( numberOfColumns ); - final String prefix; - if ( modelPart instanceof AttributeMapping attributeMapping ) { - prefix = attributeMapping.getAttributeName(); - } - else { - prefix = "id"; - } - CteTable.forEachCteColumn( prefix, modelPart, e -> { - columns.add( cteColumns.stream() - .filter( cteColumn -> cteColumn.getColumnExpression().equals( e.getColumnExpression() ) ) - .findFirst() - .orElseThrow(() -> new IllegalArgumentException( "Column reference not found: " + e.getColumnExpression() ) ) ); - } ); - return columns; - } - private void handleConflictClause( CteTable dmlResultCte, InsertSelectStatement insertStatement, diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/cte/CteInsertStrategy.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/cte/CteInsertStrategy.java index 4242fc4a1296..f8c461586b17 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/cte/CteInsertStrategy.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/cte/CteInsertStrategy.java @@ -8,6 +8,8 @@ import org.hibernate.dialect.Dialect; import org.hibernate.dialect.temptable.TemporaryTable; import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.mapping.PersistentClass; +import org.hibernate.mapping.SingleTableSubclass; import org.hibernate.metamodel.mapping.EntityMappingType; import org.hibernate.metamodel.spi.RuntimeModelCreationContext; import org.hibernate.persister.entity.EntityPersister; @@ -126,23 +128,26 @@ public CteInsertStrategy( ); } - // The table name might be a sub-query, which is inappropriate for a temporary table name - final String originalTableName = rootDescriptor.getEntityPersister().getSynchronizedQuerySpaces()[0]; - final String name; - if ( Identifier.isQuoted( originalTableName ) ) { - name = dialect.quote( TemporaryTable.ENTITY_TABLE_PREFIX + Identifier.unQuote( originalTableName ) ); - } - else { - name = TemporaryTable.ENTITY_TABLE_PREFIX + originalTableName; - } - final String qualifiedTableName; - if ( name.length() > dialect.getMaxIdentifierLength() ) { - qualifiedTableName = name.substring( 0, dialect.getMaxIdentifierLength() ); + final PersistentClass persistentClass = runtimeModelCreationContext.getMetadata() + .getEntityBinding( rootDescriptor.getEntityName() ); + final Identifier tableNameIdentifier; + if ( persistentClass instanceof SingleTableSubclass ) { + // In this case, the descriptor is a subclass of a single table inheritance. + // To avoid name collisions, we suffix the table name with the subclass number + tableNameIdentifier = new Identifier( + persistentClass.getTable().getNameIdentifier().getText() + persistentClass.getSubclassId(), + persistentClass.getTable().getNameIdentifier().isQuoted() + ); } else { - qualifiedTableName = name; + tableNameIdentifier = persistentClass.getTable().getNameIdentifier(); } - this.entityCteTable = CteTable.createEntityTable( qualifiedTableName, rootDescriptor ); + final String cteName = TemporaryTable.ENTITY_TABLE_PREFIX + tableNameIdentifier.getText(); + final String qualifiedCteName = new Identifier( + cteName.substring( 0, Math.min( dialect.getMaxIdentifierLength(), cteName.length() ) ), + tableNameIdentifier.isQuoted() + ).render( dialect ); + this.entityCteTable = CteTable.createEntityTable( qualifiedCteName, persistentClass ); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/cte/CteMutationStrategy.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/cte/CteMutationStrategy.java index 42e813ad61c9..11727bdb2e68 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/cte/CteMutationStrategy.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/cte/CteMutationStrategy.java @@ -83,7 +83,8 @@ public CteMutationStrategy( ); } - this.idCteTable = CteTable.createIdTable( ID_TABLE_NAME, rootDescriptor ); + this.idCteTable = CteTable.createIdTable( ID_TABLE_NAME, + runtimeModelCreationContext.getMetadata().getEntityBinding( rootDescriptor.getEntityName() ) ); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/ExecuteWithTemporaryTableHelper.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/ExecuteWithTemporaryTableHelper.java index a4d74fc863fc..1908c5b2a7d8 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/ExecuteWithTemporaryTableHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/ExecuteWithTemporaryTableHelper.java @@ -12,8 +12,10 @@ import org.hibernate.dialect.temptable.TemporaryTableHelper; import org.hibernate.dialect.temptable.TemporaryTableHelper.TemporaryTableCreationWork; import org.hibernate.dialect.temptable.TemporaryTableHelper.TemporaryTableDropWork; +import org.hibernate.dialect.temptable.TemporaryTableSessionUidColumn; import org.hibernate.dialect.temptable.TemporaryTableStrategy; import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment; +import org.hibernate.engine.jdbc.spi.JdbcCoordinator; import org.hibernate.engine.jdbc.spi.JdbcServices; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.metamodel.mapping.BasicValuedMapping; @@ -24,6 +26,7 @@ import org.hibernate.query.sqm.mutation.spi.AfterUseAction; import org.hibernate.query.sqm.mutation.spi.BeforeUseAction; import org.hibernate.spi.NavigablePath; +import org.hibernate.sql.SimpleSelect; import org.hibernate.sql.ast.SqlAstJoinType; import org.hibernate.sql.ast.tree.expression.ColumnReference; import org.hibernate.sql.ast.tree.expression.QueryLiteral; @@ -40,6 +43,9 @@ import org.hibernate.sql.exec.spi.JdbcParameterBindings; import org.hibernate.sql.results.internal.SqlSelectionImpl; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; import java.util.UUID; import java.util.function.Function; @@ -199,7 +205,7 @@ public static QuerySpec createIdTableSelectQuerySpec( querySpec.getFromClause().addRoot( idTableGroup ); - applyIdTableSelections( querySpec, idTableReference, idTable, fkModelPart ); + applyIdTableSelections( querySpec, idTableReference, idTable, fkModelPart, entityDescriptor ); applyIdTableRestrictions( querySpec, idTableReference, idTable, sessionUidAccess, executionContext ); return querySpec; @@ -209,9 +215,10 @@ private static void applyIdTableSelections( QuerySpec querySpec, TableReference tableReference, TemporaryTable idTable, - ModelPart fkModelPart) { + ModelPart fkModelPart, + EntityMappingType entityDescriptor) { if ( fkModelPart == null ) { - final int size = idTable.getEntityDescriptor().getIdentifierMapping().getJdbcTypeCount(); + final int size = entityDescriptor.getIdentifierMapping().getJdbcTypeCount(); for ( int i = 0; i < size; i++ ) { final var temporaryTableColumn = idTable.getColumns().get( i ); if ( temporaryTableColumn != idTable.getSessionUidColumn() ) { @@ -287,18 +294,18 @@ public static void performBeforeTemporaryTableUseActions( ); } - public static void performBeforeTemporaryTableUseActions( + public static boolean performBeforeTemporaryTableUseActions( TemporaryTable temporaryTable, TemporaryTableStrategy temporaryTableStrategy, ExecutionContext executionContext) { - performBeforeTemporaryTableUseActions( + return performBeforeTemporaryTableUseActions( temporaryTable, temporaryTableStrategy.getTemporaryTableBeforeUseAction(), executionContext ); } - private static void performBeforeTemporaryTableUseActions( + private static boolean performBeforeTemporaryTableUseActions( TemporaryTable temporaryTable, BeforeUseAction beforeUseAction, ExecutionContext executionContext) { @@ -309,16 +316,81 @@ private static void performBeforeTemporaryTableUseActions( new TemporaryTableCreationWork( temporaryTable, factory ); final var ddlTransactionHandling = dialect.getTemporaryTableDdlTransactionHandling(); if ( ddlTransactionHandling == NONE ) { - executionContext.getSession().doWork( temporaryTableCreationWork ); + return executionContext.getSession().doReturningWork( temporaryTableCreationWork ); } else { final var isolationDelegate = executionContext.getSession().getJdbcCoordinator().getJdbcSessionOwner() .getTransactionCoordinator().createIsolationDelegate(); - isolationDelegate.delegateWork( temporaryTableCreationWork, + return isolationDelegate.delegateWork( temporaryTableCreationWork, ddlTransactionHandling == ISOLATE_AND_TRANSACT ); } } + else { + return false; + } + } + + public static int[] loadInsertedRowNumbers( + TemporaryTable temporaryTable, + Function sessionUidAccess, + int rows, + ExecutionContext executionContext) { + final TemporaryTableSessionUidColumn sessionUidColumn = temporaryTable.getSessionUidColumn(); + + final TemporaryTableColumn rowNumberColumn = temporaryTable.getColumns() + .get( temporaryTable.getColumns().size() - (sessionUidColumn == null ? 1 : 2 ) ); + assert rowNumberColumn != null; + + final SharedSessionContractImplementor session = executionContext.getSession(); + final SimpleSelect simpleSelect = new SimpleSelect( session.getFactory() ) + .setTableName( temporaryTable.getQualifiedTableName() ) + .addColumn( rowNumberColumn.getColumnName() ); + if ( sessionUidColumn != null ) { + simpleSelect.addRestriction( sessionUidColumn.getColumnName() ); + } + final String sqlSelect = simpleSelect.toStatementString(); + + final JdbcCoordinator jdbcCoordinator = session.getJdbcCoordinator(); + PreparedStatement preparedStatement = null; + try { + preparedStatement = jdbcCoordinator.getStatementPreparer().prepareStatement( sqlSelect ); + if ( sessionUidColumn != null ) { + //noinspection unchecked + sessionUidColumn.getJdbcMapping().getJdbcValueBinder().bind( + preparedStatement, + UUID.fromString( sessionUidAccess.apply( session ) ), + 1, + session + ); + } + final ResultSet resultSet = jdbcCoordinator.getResultSetReturn().execute( preparedStatement, sqlSelect ); + final int[] rowNumbers = new int[rows]; + try { + int rowIndex = 0; + while (resultSet.next()) { + rowNumbers[rowIndex++] = resultSet.getInt( 1 ); + } + return rowNumbers; + } + catch ( IndexOutOfBoundsException e ) { + throw new IllegalArgumentException( "Expected " + rows + " to be inserted but found more", e ); + } + } + catch( SQLException ex ) { + throw new IllegalStateException( ex ); + } + finally { + if ( preparedStatement != null ) { + try { + jdbcCoordinator.getLogicalConnection().getResourceRegistry().release( preparedStatement ); + } + catch( Throwable ignore ) { + // ignore + } + jdbcCoordinator.afterStatementExecution(); + } + } } public static void performAfterTemporaryTableUseActions( diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/GlobalTemporaryTableInsertStrategy.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/GlobalTemporaryTableInsertStrategy.java index 253fec77acb4..5be44881bd5d 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/GlobalTemporaryTableInsertStrategy.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/GlobalTemporaryTableInsertStrategy.java @@ -36,7 +36,8 @@ private GlobalTemporaryTableInsertStrategy( RuntimeModelCreationContext runtimeModelCreationContext) { this( TemporaryTable.createEntityTable( - rootEntityDescriptor, + runtimeModelCreationContext.getMetadata() + .getEntityBinding( rootEntityDescriptor.getEntityName() ), basename -> temporaryTableStrategy.adjustTemporaryTableName( TemporaryTable.ENTITY_TABLE_PREFIX + basename ), TemporaryTableKind.GLOBAL, runtimeModelCreationContext.getDialect(), diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/GlobalTemporaryTableMutationStrategy.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/GlobalTemporaryTableMutationStrategy.java index ae865e451520..0f9c136edebc 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/GlobalTemporaryTableMutationStrategy.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/GlobalTemporaryTableMutationStrategy.java @@ -37,7 +37,8 @@ private GlobalTemporaryTableMutationStrategy( RuntimeModelCreationContext runtimeModelCreationContext) { this( TemporaryTable.createIdTable( - rootEntityDescriptor, + runtimeModelCreationContext.getBootModel() + .getEntityBinding( rootEntityDescriptor.getEntityName() ), basename -> temporaryTableStrategy.adjustTemporaryTableName( TemporaryTable.ID_TABLE_PREFIX + basename ), TemporaryTableKind.GLOBAL, runtimeModelCreationContext.getDialect(), diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/GlobalTemporaryTableStrategy.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/GlobalTemporaryTableStrategy.java index fca049808b5d..c3734f794558 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/GlobalTemporaryTableStrategy.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/GlobalTemporaryTableStrategy.java @@ -16,7 +16,6 @@ import org.hibernate.engine.config.spi.StandardConverters; import org.hibernate.engine.jdbc.connections.spi.JdbcConnectionAccess; import org.hibernate.engine.spi.SessionFactoryImplementor; -import org.hibernate.metamodel.mapping.EntityMappingType; import org.hibernate.metamodel.mapping.internal.MappingModelCreationProcess; import org.hibernate.query.sqm.mutation.spi.AfterUseAction; @@ -65,10 +64,6 @@ public TemporaryTableStrategy getTemporaryTableStrategy() { return castNonNull( sessionFactory.getJdbcServices().getDialect().getGlobalTemporaryTableStrategy() ); } - public EntityMappingType getEntityDescriptor() { - return temporaryTable.getEntityDescriptor(); - } - public void prepare(MappingModelCreationProcess mappingModelCreationProcess, JdbcConnectionAccess connectionAccess) { if ( prepared ) { return; diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/InsertExecutionDelegate.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/InsertExecutionDelegate.java index 680d5ae6a3ab..5229921cac90 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/InsertExecutionDelegate.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/InsertExecutionDelegate.java @@ -13,6 +13,7 @@ import java.util.Map; import java.util.UUID; import java.util.function.Function; +import java.util.stream.IntStream; import org.hibernate.dialect.temptable.TemporaryTable; import org.hibernate.dialect.temptable.TemporaryTableColumn; @@ -82,7 +83,6 @@ import org.hibernate.type.descriptor.ValueBinder; import static org.hibernate.generator.EventType.INSERT; -import static org.hibernate.query.sqm.mutation.internal.SqmMutationStrategyHelper.isId; /** * @author Christian Beikov @@ -116,6 +116,7 @@ public InsertExecutionDelegate( TableGroup insertingTableGroup, Map tableReferenceByAlias, List assignments, + boolean assignsId, InsertSelectStatement insertStatement, ConflictClause conflictClause, JdbcParameter sessionUidParameter, @@ -156,7 +157,6 @@ public MappingModelExpressible getResolvedMappingModelType(SqmParameter MappingModelExpressible getResolvedMappingModelType(SqmParameter pathInterpretation - && isId( pathInterpretation.getExpressionType() ); - assignmentsByTable.computeIfAbsent( assignmentTableReference == null ? null : assignmentTableReference.getTableId(), k -> new ArrayList<>() ).add( assignment ); @@ -197,7 +194,7 @@ public int execute(ExecutionContext executionContext) { // NOTE: we could get rid of using a temporary table if the expressions in Values are "stable". // But that is a non-trivial optimization that requires more effort // as we need to split out individual inserts if we have a non-bulk capable optimizer - ExecuteWithTemporaryTableHelper.performBeforeTemporaryTableUseActions( + final boolean createdTable = ExecuteWithTemporaryTableHelper.performBeforeTemporaryTableUseActions( entityTable, temporaryTableStrategy, executionContext @@ -225,6 +222,7 @@ public int execute(ExecutionContext executionContext) { final int insertedRows = insertRootTable( persister.getTableName( 0 ), rows, + createdTable, persister.getKeyColumns( 0 ), executionContext ); @@ -306,6 +304,7 @@ private NamedTableReference resolveUnionTableReference(TableReference tableRefer private int insertRootTable( String tableExpression, int rows, + boolean rowNumberStartsAtOne, String[] keyColumns, ExecutionContext executionContext) { final TableReference updatingTableReference = updatingTableGroup.getTableReference( @@ -509,12 +508,15 @@ private int insertRootTable( } final BeforeExecutionGenerator beforeExecutionGenerator = (BeforeExecutionGenerator) generator; - for ( int i = 0; i < rows; i++ ) { + final IntStream rowNumberStream = !rowNumberStartsAtOne + ? IntStream.of( ExecuteWithTemporaryTableHelper.loadInsertedRowNumbers( entityTable, sessionUidAccess, rows, executionContext ) ) + : IntStream.range( 1, rows + 1 ); + rowNumberStream.forEach( rowNumberValue -> { updateBindings.addBinding( rowNumber, new JdbcParameterBindingImpl( rowNumberColumn.getJdbcMapping(), - i + 1 + rowNumberValue ) ); updateBindings.addBinding( @@ -524,7 +526,7 @@ private int insertRootTable( beforeExecutionGenerator.generate( session, null, null, INSERT ) ) ); - jdbcServices.getJdbcMutationExecutor().execute( + final int updateCount = jdbcServices.getJdbcMutationExecutor().execute( jdbcUpdate, updateBindings, sql -> session @@ -535,7 +537,8 @@ private int insertRootTable( }, executionContext ); - } + assert updateCount == 1; + } ); } identifierMapping.forEachSelectable( 0, (selectionIndex, selectableMapping) -> { @@ -738,25 +741,11 @@ private void insertTable( final InsertSelectStatement insertStatement = new InsertSelectStatement( dmlTargetTableReference ); insertStatement.setSourceSelectStatement( querySpec ); applyAssignments( assignments, insertStatement, temporaryTableReference ); - final EntityPersister entityPersister = entityDescriptor.getEntityPersister(); - final Generator identifierGenerator = entityPersister.getGenerator(); - final boolean needsKeyInsert; - if ( identifierGenerator.generatedOnExecution() ) { - needsKeyInsert = true; - } - else if ( identifierGenerator instanceof OptimizableGenerator ) { - final Optimizer optimizer = ( (OptimizableGenerator) identifierGenerator ).getOptimizer(); - // If the generator uses an optimizer, we have to generate the identifiers for the new rows - needsKeyInsert = optimizer != null && optimizer.getIncrementSize() > 1; - } - else { - needsKeyInsert = true; - } - if ( needsKeyInsert && insertStatement.getTargetColumns() + if ( insertStatement.getTargetColumns() .stream() .noneMatch( c -> keyColumns[0].equals( c.getColumnExpression() ) ) ) { final List primaryKeyTableColumns = - getPrimaryKeyTableColumns( entityPersister, entityTable ); + getPrimaryKeyTableColumns( entityDescriptor.getEntityPersister(), entityTable ); entityDescriptor.getIdentifierMapping().forEachSelectable( 0, (selectionIndex, selectableMapping) -> { insertStatement.addTargetColumnReferences( new ColumnReference( diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/LocalTemporaryTableInsertStrategy.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/LocalTemporaryTableInsertStrategy.java index 9d9279c5ccc5..f9241d38cd04 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/LocalTemporaryTableInsertStrategy.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/LocalTemporaryTableInsertStrategy.java @@ -36,7 +36,8 @@ private LocalTemporaryTableInsertStrategy( RuntimeModelCreationContext runtimeModelCreationContext) { this( TemporaryTable.createEntityTable( - rootEntityDescriptor, + runtimeModelCreationContext.getMetadata() + .getEntityBinding( rootEntityDescriptor.getEntityName() ), basename -> temporaryTableStrategy.adjustTemporaryTableName( TemporaryTable.ENTITY_TABLE_PREFIX + basename ), TemporaryTableKind.LOCAL, runtimeModelCreationContext.getDialect(), diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/LocalTemporaryTableMutationStrategy.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/LocalTemporaryTableMutationStrategy.java index 16b3af4e4c12..d8818f66da52 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/LocalTemporaryTableMutationStrategy.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/LocalTemporaryTableMutationStrategy.java @@ -37,7 +37,8 @@ private LocalTemporaryTableMutationStrategy( RuntimeModelCreationContext runtimeModelCreationContext) { this( TemporaryTable.createIdTable( - rootEntityDescriptor, + runtimeModelCreationContext.getBootModel() + .getEntityBinding( rootEntityDescriptor.getEntityName() ), basename -> temporaryTableStrategy.adjustTemporaryTableName( TemporaryTable.ID_TABLE_PREFIX + basename ), TemporaryTableKind.LOCAL, runtimeModelCreationContext.getDialect(), diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/LocalTemporaryTableStrategy.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/LocalTemporaryTableStrategy.java index 77bd993aa35a..1fc639dfd59c 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/LocalTemporaryTableStrategy.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/LocalTemporaryTableStrategy.java @@ -11,7 +11,6 @@ import org.hibernate.engine.config.spi.StandardConverters; import org.hibernate.engine.jdbc.connections.spi.JdbcConnectionAccess; import org.hibernate.engine.spi.SessionFactoryImplementor; -import org.hibernate.metamodel.mapping.EntityMappingType; import org.hibernate.metamodel.mapping.internal.MappingModelCreationProcess; import java.util.Objects; @@ -63,10 +62,6 @@ public TemporaryTable getTemporaryTable() { return temporaryTable; } - public EntityMappingType getEntityDescriptor() { - return getTemporaryTable().getEntityDescriptor(); - } - public boolean isDropIdTables() { return dropIdTables; } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/PersistentTableInsertStrategy.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/PersistentTableInsertStrategy.java index 6b9bd90ad463..2c980a2887aa 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/PersistentTableInsertStrategy.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/PersistentTableInsertStrategy.java @@ -39,7 +39,8 @@ private PersistentTableInsertStrategy( RuntimeModelCreationContext runtimeModelCreationContext) { this( TemporaryTable.createEntityTable( - rootEntityDescriptor, + runtimeModelCreationContext.getMetadata() + .getEntityBinding( rootEntityDescriptor.getEntityName() ), basename -> temporaryTableStrategy.adjustTemporaryTableName( TemporaryTable.ENTITY_TABLE_PREFIX + basename ), TemporaryTableKind.PERSISTENT, runtimeModelCreationContext.getDialect(), diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/PersistentTableMutationStrategy.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/PersistentTableMutationStrategy.java index 4e5d087cb7a6..7c1d8312fc03 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/PersistentTableMutationStrategy.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/PersistentTableMutationStrategy.java @@ -40,7 +40,8 @@ private PersistentTableMutationStrategy( RuntimeModelCreationContext runtimeModelCreationContext) { this( TemporaryTable.createIdTable( - rootEntityDescriptor, + runtimeModelCreationContext.getBootModel() + .getEntityBinding( rootEntityDescriptor.getEntityName() ), basename -> temporaryTableStrategy.adjustTemporaryTableName( TemporaryTable.ID_TABLE_PREFIX + basename ), TemporaryTableKind.PERSISTENT, runtimeModelCreationContext.getDialect(), diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/PersistentTableStrategy.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/PersistentTableStrategy.java index 1b1f5d9c4404..6675438f58ec 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/PersistentTableStrategy.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/PersistentTableStrategy.java @@ -14,7 +14,6 @@ import org.hibernate.engine.config.spi.StandardConverters; import org.hibernate.engine.jdbc.connections.spi.JdbcConnectionAccess; import org.hibernate.engine.spi.SessionFactoryImplementor; -import org.hibernate.metamodel.mapping.EntityMappingType; import org.hibernate.metamodel.mapping.internal.MappingModelCreationProcess; import org.hibernate.query.sqm.mutation.spi.AfterUseAction; @@ -66,10 +65,6 @@ public TemporaryTableStrategy getTemporaryTableStrategy() { return castNonNull( sessionFactory.getJdbcServices().getDialect().getPersistentTemporaryTableStrategy() ); } - public EntityMappingType getEntityDescriptor() { - return getTemporaryTable().getEntityDescriptor(); - } - public void prepare( MappingModelCreationProcess mappingModelCreationProcess, JdbcConnectionAccess connectionAccess) { diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/TableBasedInsertHandler.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/TableBasedInsertHandler.java index 0909ea371a68..bef567667987 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/TableBasedInsertHandler.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/TableBasedInsertHandler.java @@ -16,9 +16,7 @@ import org.hibernate.dialect.temptable.TemporaryTableStrategy; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; -import org.hibernate.generator.Generator; -import org.hibernate.id.OptimizableGenerator; -import org.hibernate.id.enhanced.Optimizer; +import org.hibernate.generator.OnExecutionGenerator; import org.hibernate.internal.util.collections.CollectionHelper; import org.hibernate.persister.entity.EntityPersister; import org.hibernate.query.spi.DomainQueryExecutionContext; @@ -53,6 +51,8 @@ import org.jboss.logging.Logger; +import static org.hibernate.query.sqm.mutation.internal.SqmMutationStrategyHelper.isId; + /** * @author Christian Beikov */ @@ -192,33 +192,29 @@ protected ExecutionDelegate resolveDelegate(DomainQueryExecutionContext executio new Assignment( columnReference, columnReference ) ); } - else if ( entityDescriptor.getGenerator() instanceof OptimizableGenerator ) { - final Optimizer optimizer = ( (OptimizableGenerator) entityDescriptor.getGenerator() ).getOptimizer(); - if ( optimizer != null && optimizer.getIncrementSize() > 1 ) { - if ( !sessionFactory.getJdbcServices().getDialect().supportsWindowFunctions() ) { - return; - } - final TemporaryTableColumn rowNumberColumn = entityTable.getColumns() - .get( entityTable.getColumns().size() - ( sessionUidColumn == null ? 1 : 2 ) ); - final ColumnReference columnReference = new ColumnReference( - (String) null, - rowNumberColumn.getColumnName(), - false, - null, - rowNumberColumn.getJdbcMapping() - ); - insertStatement.getTargetColumns().add( columnReference ); - targetPathColumns.add( new Assignment( columnReference, columnReference ) ); - querySpec.getSelectClause().addSqlSelection( - new SqlSelectionImpl( - 0, - SqmInsertStrategyHelper.createRowNumberingExpression( - querySpec, - sessionFactory - ) - ) - ); - } + else if ( !(entityDescriptor.getGenerator() instanceof OnExecutionGenerator generator + && generator.generatedOnExecution()) + && !entityTable.isRowNumberGenerated() ) { + final TemporaryTableColumn rowNumberColumn = entityTable.getColumns() + .get( entityTable.getColumns().size() - ( sessionUidColumn == null ? 1 : 2 ) ); + final ColumnReference columnReference = new ColumnReference( + (String) null, + rowNumberColumn.getColumnName(), + false, + null, + rowNumberColumn.getJdbcMapping() + ); + insertStatement.getTargetColumns().add( columnReference ); + targetPathColumns.add( new Assignment( columnReference, columnReference ) ); + querySpec.getSelectClause().addSqlSelection( + new SqlSelectionImpl( + 0, + SqmInsertStrategyHelper.createRowNumberingExpression( + querySpec, + sessionFactory + ) + ) + ); } if ( sessionUidColumn != null ) { final ColumnReference sessionUidColumnReference = new ColumnReference( @@ -241,27 +237,22 @@ else if ( entityDescriptor.getGenerator() instanceof OptimizableGenerator ) { } else { // Add the row number column if there is one - final Generator generator = entityDescriptor.getGenerator(); final BasicType rowNumberType; - if ( generator instanceof OptimizableGenerator ) { - final Optimizer optimizer = ( (OptimizableGenerator) generator ).getOptimizer(); - if ( optimizer != null && optimizer.getIncrementSize() > 1 ) { - final TemporaryTableColumn rowNumberColumn = entityTable.getColumns() - .get( entityTable.getColumns().size() - ( sessionUidColumn == null ? 1 : 2 ) ); - rowNumberType = (BasicType) rowNumberColumn.getJdbcMapping(); - final ColumnReference columnReference = new ColumnReference( - (String) null, - rowNumberColumn.getColumnName(), - false, - null, - rowNumberColumn.getJdbcMapping() - ); - insertStatement.getTargetColumns().add( columnReference ); - targetPathColumns.add( new Assignment( columnReference, columnReference ) ); - } - else { - rowNumberType = null; - } + if ( !(entityDescriptor.getGenerator() instanceof OnExecutionGenerator generator + && generator.generatedOnExecution()) + && !entityTable.isRowNumberGenerated() ) { + final TemporaryTableColumn rowNumberColumn = entityTable.getColumns() + .get( entityTable.getColumns().size() - (sessionUidColumn == null ? 1 : 2) ); + rowNumberType = (BasicType) rowNumberColumn.getJdbcMapping(); + final ColumnReference columnReference = new ColumnReference( + (String) null, + rowNumberColumn.getColumnName(), + false, + null, + rowNumberColumn.getJdbcMapping() + ); + insertStatement.getTargetColumns().add( columnReference ); + targetPathColumns.add( new Assignment( columnReference, columnReference ) ); } else { rowNumberType = null; @@ -301,6 +292,12 @@ else if ( entityDescriptor.getGenerator() instanceof OptimizableGenerator ) { final ConflictClause conflictClause = converterDelegate.visitConflictClause( sqmInsertStatement.getConflictClause() ); converterDelegate.pruneTableGroupJoins(); + boolean assignsId = false; + for ( Assignment assignment : targetPathColumns ) { + assignsId = assignsId || assignment.getAssignable() instanceof SqmPathInterpretation pathInterpretation + && isId( pathInterpretation.getExpressionType() ); + } + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // cross-reference the TableReference by alias. The TableGroup already // cross-references it by name, but the ColumnReference only has the alias @@ -322,6 +319,7 @@ else if ( entityDescriptor.getGenerator() instanceof OptimizableGenerator ) { insertingTableGroup, tableReferenceByAlias, targetPathColumns, + assignsId, insertStatement, conflictClause, sessionUidParameter, @@ -343,6 +341,7 @@ protected ExecutionDelegate buildExecutionDelegate( TableGroup insertingTableGroup, Map tableReferenceByAlias, List assignments, + boolean assignsId, InsertSelectStatement insertStatement, ConflictClause conflictClause, JdbcParameter sessionUidParameter, @@ -357,6 +356,7 @@ protected ExecutionDelegate buildExecutionDelegate( insertingTableGroup, tableReferenceByAlias, assignments, + assignsId, insertStatement, conflictClause, sessionUidParameter, diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/BaseSqmToSqlAstConverter.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/BaseSqmToSqlAstConverter.java index c7aacdeacecc..c92a6172b5c7 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/BaseSqmToSqlAstConverter.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/BaseSqmToSqlAstConverter.java @@ -31,7 +31,39 @@ import org.hibernate.loader.MultipleBagFetchException; import org.hibernate.metamodel.CollectionClassification; import org.hibernate.metamodel.MappingMetamodel; -import org.hibernate.metamodel.mapping.*; +import org.hibernate.metamodel.mapping.AssociationKey; +import org.hibernate.metamodel.mapping.AttributeMapping; +import org.hibernate.metamodel.mapping.BasicEntityIdentifierMapping; +import org.hibernate.metamodel.mapping.BasicValuedMapping; +import org.hibernate.metamodel.mapping.BasicValuedModelPart; +import org.hibernate.metamodel.mapping.Bindable; +import org.hibernate.metamodel.mapping.CollectionPart; +import org.hibernate.metamodel.mapping.DiscriminatorConverter; +import org.hibernate.metamodel.mapping.DiscriminatorMapping; +import org.hibernate.metamodel.mapping.DiscriminatorValueDetails; +import org.hibernate.metamodel.mapping.EmbeddableMappingType; +import org.hibernate.metamodel.mapping.EmbeddableValuedModelPart; +import org.hibernate.metamodel.mapping.EntityAssociationMapping; +import org.hibernate.metamodel.mapping.EntityDiscriminatorMapping; +import org.hibernate.metamodel.mapping.EntityIdentifierMapping; +import org.hibernate.metamodel.mapping.EntityMappingType; +import org.hibernate.metamodel.mapping.EntityValuedModelPart; +import org.hibernate.metamodel.mapping.EntityVersionMapping; +import org.hibernate.metamodel.mapping.ForeignKeyDescriptor; +import org.hibernate.metamodel.mapping.JdbcMapping; +import org.hibernate.metamodel.mapping.JdbcMappingContainer; +import org.hibernate.metamodel.mapping.MappingModelExpressible; +import org.hibernate.metamodel.mapping.MappingType; +import org.hibernate.metamodel.mapping.ModelPart; +import org.hibernate.metamodel.mapping.ModelPartContainer; +import org.hibernate.metamodel.mapping.PluralAttributeMapping; +import org.hibernate.metamodel.mapping.SelectableMapping; +import org.hibernate.metamodel.mapping.SelectableMappings; +import org.hibernate.metamodel.mapping.SoftDeleteMapping; +import org.hibernate.metamodel.mapping.SqlExpressible; +import org.hibernate.metamodel.mapping.SqlTypedMapping; +import org.hibernate.metamodel.mapping.ValueMapping; +import org.hibernate.metamodel.mapping.ValuedModelPart; import org.hibernate.metamodel.mapping.internal.BasicValuedCollectionPart; import org.hibernate.metamodel.mapping.internal.EntityCollectionPart; import org.hibernate.metamodel.mapping.internal.ManyToManyCollectionPart; @@ -41,7 +73,6 @@ import org.hibernate.metamodel.mapping.ordering.OrderByFragment; import org.hibernate.metamodel.model.domain.AnyMappingDomainType; import org.hibernate.metamodel.model.domain.BasicDomainType; -import org.hibernate.query.sqm.DiscriminatorSqmPath; import org.hibernate.metamodel.model.domain.EmbeddableDomainType; import org.hibernate.metamodel.model.domain.EntityDomainType; import org.hibernate.metamodel.model.domain.ManagedDomainType; @@ -57,7 +88,6 @@ import org.hibernate.metamodel.model.domain.internal.EntityTypeImpl; import org.hibernate.persister.entity.EntityNameUse; import org.hibernate.persister.entity.EntityPersister; -import org.hibernate.type.BindableType; import org.hibernate.query.SemanticException; import org.hibernate.query.SortDirection; import org.hibernate.query.common.FetchClauseType; @@ -73,6 +103,7 @@ import org.hibernate.query.sqm.BinaryArithmeticOperator; import org.hibernate.query.sqm.CastType; import org.hibernate.query.sqm.ComparisonOperator; +import org.hibernate.query.sqm.DiscriminatorSqmPath; import org.hibernate.query.sqm.DynamicInstantiationNature; import org.hibernate.query.sqm.InterpretationException; import org.hibernate.query.sqm.SqmExpressible; @@ -137,7 +168,52 @@ import org.hibernate.query.sqm.tree.domain.SqmSimplePath; import org.hibernate.query.sqm.tree.domain.SqmTreatedFrom; import org.hibernate.query.sqm.tree.domain.SqmTreatedPath; -import org.hibernate.query.sqm.tree.expression.*; +import org.hibernate.query.sqm.tree.expression.AsWrapperSqmExpression; +import org.hibernate.query.sqm.tree.expression.Conversion; +import org.hibernate.query.sqm.tree.expression.JpaCriteriaParameter; +import org.hibernate.query.sqm.tree.expression.SqmAliasedNodeRef; +import org.hibernate.query.sqm.tree.expression.SqmAny; +import org.hibernate.query.sqm.tree.expression.SqmAnyDiscriminatorValue; +import org.hibernate.query.sqm.tree.expression.SqmBinaryArithmetic; +import org.hibernate.query.sqm.tree.expression.SqmByUnit; +import org.hibernate.query.sqm.tree.expression.SqmCaseSearched; +import org.hibernate.query.sqm.tree.expression.SqmCaseSimple; +import org.hibernate.query.sqm.tree.expression.SqmCastTarget; +import org.hibernate.query.sqm.tree.expression.SqmCoalesce; +import org.hibernate.query.sqm.tree.expression.SqmCollation; +import org.hibernate.query.sqm.tree.expression.SqmCollectionSize; +import org.hibernate.query.sqm.tree.expression.SqmDistinct; +import org.hibernate.query.sqm.tree.expression.SqmDurationUnit; +import org.hibernate.query.sqm.tree.expression.SqmEnumLiteral; +import org.hibernate.query.sqm.tree.expression.SqmEvery; +import org.hibernate.query.sqm.tree.expression.SqmExpression; +import org.hibernate.query.sqm.tree.expression.SqmExpressionHelper; +import org.hibernate.query.sqm.tree.expression.SqmExtractUnit; +import org.hibernate.query.sqm.tree.expression.SqmFieldLiteral; +import org.hibernate.query.sqm.tree.expression.SqmFormat; +import org.hibernate.query.sqm.tree.expression.SqmFunction; +import org.hibernate.query.sqm.tree.expression.SqmHqlNumericLiteral; +import org.hibernate.query.sqm.tree.expression.SqmJpaCriteriaParameterWrapper; +import org.hibernate.query.sqm.tree.expression.SqmLiteral; +import org.hibernate.query.sqm.tree.expression.SqmLiteralEmbeddableType; +import org.hibernate.query.sqm.tree.expression.SqmLiteralEntityType; +import org.hibernate.query.sqm.tree.expression.SqmLiteralNull; +import org.hibernate.query.sqm.tree.expression.SqmModifiedSubQueryExpression; +import org.hibernate.query.sqm.tree.expression.SqmNamedExpression; +import org.hibernate.query.sqm.tree.expression.SqmNamedParameter; +import org.hibernate.query.sqm.tree.expression.SqmOver; +import org.hibernate.query.sqm.tree.expression.SqmOverflow; +import org.hibernate.query.sqm.tree.expression.SqmParameter; +import org.hibernate.query.sqm.tree.expression.SqmParameterizedEntityType; +import org.hibernate.query.sqm.tree.expression.SqmPositionalParameter; +import org.hibernate.query.sqm.tree.expression.SqmSetReturningFunction; +import org.hibernate.query.sqm.tree.expression.SqmStar; +import org.hibernate.query.sqm.tree.expression.SqmSummarization; +import org.hibernate.query.sqm.tree.expression.SqmToDuration; +import org.hibernate.query.sqm.tree.expression.SqmTrimSpecification; +import org.hibernate.query.sqm.tree.expression.SqmTuple; +import org.hibernate.query.sqm.tree.expression.SqmUnaryOperation; +import org.hibernate.query.sqm.tree.expression.SqmWindow; import org.hibernate.query.sqm.tree.from.SqmAttributeJoin; import org.hibernate.query.sqm.tree.from.SqmCrossJoin; import org.hibernate.query.sqm.tree.from.SqmCteJoin; @@ -219,7 +295,41 @@ import org.hibernate.sql.ast.tree.cte.CteTableGroup; import org.hibernate.sql.ast.tree.cte.SearchClauseSpecification; import org.hibernate.sql.ast.tree.delete.DeleteStatement; -import org.hibernate.sql.ast.tree.expression.*; +import org.hibernate.sql.ast.tree.expression.AliasedExpression; +import org.hibernate.sql.ast.tree.expression.Any; +import org.hibernate.sql.ast.tree.expression.BinaryArithmeticExpression; +import org.hibernate.sql.ast.tree.expression.CaseSearchedExpression; +import org.hibernate.sql.ast.tree.expression.CaseSimpleExpression; +import org.hibernate.sql.ast.tree.expression.CastTarget; +import org.hibernate.sql.ast.tree.expression.Collation; +import org.hibernate.sql.ast.tree.expression.ColumnReference; +import org.hibernate.sql.ast.tree.expression.Distinct; +import org.hibernate.sql.ast.tree.expression.Duration; +import org.hibernate.sql.ast.tree.expression.DurationUnit; +import org.hibernate.sql.ast.tree.expression.EmbeddableTypeLiteral; +import org.hibernate.sql.ast.tree.expression.EntityTypeLiteral; +import org.hibernate.sql.ast.tree.expression.Every; +import org.hibernate.sql.ast.tree.expression.Expression; +import org.hibernate.sql.ast.tree.expression.ExtractUnit; +import org.hibernate.sql.ast.tree.expression.Format; +import org.hibernate.sql.ast.tree.expression.JdbcLiteral; +import org.hibernate.sql.ast.tree.expression.JdbcParameter; +import org.hibernate.sql.ast.tree.expression.Literal; +import org.hibernate.sql.ast.tree.expression.ModifiedSubQueryExpression; +import org.hibernate.sql.ast.tree.expression.Over; +import org.hibernate.sql.ast.tree.expression.Overflow; +import org.hibernate.sql.ast.tree.expression.QueryLiteral; +import org.hibernate.sql.ast.tree.expression.QueryTransformer; +import org.hibernate.sql.ast.tree.expression.SelfRenderingExpression; +import org.hibernate.sql.ast.tree.expression.SelfRenderingSqlFragmentExpression; +import org.hibernate.sql.ast.tree.expression.SqlSelectionExpression; +import org.hibernate.sql.ast.tree.expression.SqlTuple; +import org.hibernate.sql.ast.tree.expression.SqlTupleContainer; +import org.hibernate.sql.ast.tree.expression.Star; +import org.hibernate.sql.ast.tree.expression.Summarization; +import org.hibernate.sql.ast.tree.expression.TrimSpecification; +import org.hibernate.sql.ast.tree.expression.UnaryOperation; +import org.hibernate.sql.ast.tree.expression.UnparsedNumericLiteral; import org.hibernate.sql.ast.tree.from.CorrelatedPluralTableGroup; import org.hibernate.sql.ast.tree.from.CorrelatedTableGroup; import org.hibernate.sql.ast.tree.from.EmbeddableFunctionTableGroup; @@ -281,6 +391,7 @@ import org.hibernate.sql.results.internal.SqlSelectionImpl; import org.hibernate.sql.results.internal.StandardEntityGraphTraversalStateImpl; import org.hibernate.type.BasicType; +import org.hibernate.type.BindableType; import org.hibernate.type.BottomType; import org.hibernate.type.JavaObjectType; import org.hibernate.type.SqlTypes; diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/cte/CteTable.java b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/cte/CteTable.java index 2c6e205cc392..055ecf5ccbba 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/cte/CteTable.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/cte/CteTable.java @@ -4,33 +4,34 @@ */ package org.hibernate.sql.ast.tree.cte; -import java.util.ArrayList; -import java.util.List; -import java.util.function.Consumer; - -import org.hibernate.metamodel.mapping.Association; +import org.hibernate.boot.Metadata; +import org.hibernate.mapping.PersistentClass; +import org.hibernate.mapping.Property; +import org.hibernate.mapping.Value; import org.hibernate.metamodel.mapping.AttributeMapping; -import org.hibernate.metamodel.mapping.AttributeMappingsList; -import org.hibernate.metamodel.mapping.BasicValuedModelPart; -import org.hibernate.metamodel.mapping.EmbeddableValuedModelPart; +import org.hibernate.metamodel.mapping.DiscriminatorMapping; import org.hibernate.metamodel.mapping.EntityDiscriminatorMapping; import org.hibernate.metamodel.mapping.EntityIdentifierMapping; import org.hibernate.metamodel.mapping.EntityMappingType; -import org.hibernate.metamodel.mapping.EntityValuedModelPart; import org.hibernate.metamodel.mapping.ModelPart; import org.hibernate.metamodel.mapping.PluralAttributeMapping; import org.hibernate.metamodel.mapping.internal.SingleAttributeIdentifierMapping; -import org.hibernate.persister.entity.EntityPersister; import org.hibernate.query.sqm.mutation.internal.SqmMutationStrategyHelper; import org.hibernate.query.sqm.tuple.internal.AnonymousTupleTableGroupProducer; import org.hibernate.query.sqm.tuple.internal.CteTupleTableGroupProducer; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; + /** * Describes the table definition for the CTE - its name amd its columns * * @author Steve Ebersole */ public class CteTable { + public static final String ENTITY_ROW_NUMBER_COLUMN = "rn_"; + private final String cteName; private final AnonymousTupleTableGroupProducer tableGroupProducer; private final List cteColumns; @@ -66,6 +67,7 @@ public CteTable withName(String name) { return new CteTable( name, tableGroupProducer, cteColumns ); } + @Deprecated(forRemoval = true, since = "7.1") public static CteTable createIdTable(String cteName, EntityMappingType entityDescriptor) { final int numberOfColumns = entityDescriptor.getIdentifierMapping().getJdbcTypeCount(); final List columns = new ArrayList<>( numberOfColumns ); @@ -78,6 +80,7 @@ public static CteTable createIdTable(String cteName, EntityMappingType entityDes return new CteTable( cteName, columns ); } + @Deprecated(forRemoval = true, since = "7.1") public static CteTable createEntityTable(String cteName, EntityMappingType entityDescriptor) { final int numberOfColumns = entityDescriptor.getIdentifierMapping().getJdbcTypeCount(); final List columns = new ArrayList<>( numberOfColumns ); @@ -107,7 +110,7 @@ public static CteTable createEntityTable(String cteName, EntityMappingType entit // We add a special row number column that we can use to identify and join rows columns.add( new CteColumn( - "rn_", + ENTITY_ROW_NUMBER_COLUMN, entityDescriptor.getEntityPersister() .getFactory() .getTypeConfiguration() @@ -117,92 +120,92 @@ public static CteTable createEntityTable(String cteName, EntityMappingType entit return new CteTable( cteName, columns ); } - public static void forEachCteColumn(String prefix, ModelPart modelPart, Consumer consumer) { - SqmMutationStrategyHelper.forEachSelectableMapping( prefix, modelPart, (s, selectableMapping) -> { - consumer.accept( new CteColumn( s, selectableMapping.getJdbcMapping() ) ); - } ); - } - - public List findCteColumns(EntityPersister entityDescriptor, ModelPart modelPart) { - final int offset = determineModelPartStartIndex( entityDescriptor, modelPart ); - if ( offset == -1 ) { - throw new IllegalStateException( "Couldn't find matching cte columns for: " + modelPart ); + public static CteTable createIdTable(String cteName, PersistentClass persistentClass) { + final Property identifierProperty = persistentClass.getIdentifierProperty(); + final String idName; + if ( identifierProperty != null ) { + idName = identifierProperty.getName(); + } + else { + idName = "id"; } - final int end = offset + modelPart.getJdbcTypeCount(); - // Find a matching cte table column and set that at the current index - return getCteColumns().subList( offset, end ); + final List columns = new ArrayList<>( persistentClass.getIdentifier().getColumnSpan() ); + final Metadata metadata = persistentClass.getIdentifier().getBuildingContext().getMetadataCollector(); + forEachCteColumn( idName, persistentClass.getIdentifier(), columns::add ); + return new CteTable( cteName, columns ); } - private static int determineModelPartStartIndex(EntityPersister entityDescriptor, ModelPart modelPart) { - int offset = 0; - final int idResult = determineIdStartIndex( offset, entityDescriptor, modelPart ); - if ( idResult <= 0 ) { - return -idResult; + public static CteTable createEntityTable(String cteName, PersistentClass persistentClass) { + final List columns = new ArrayList<>( persistentClass.getTable().getColumnSpan() ); + final Property identifierProperty = persistentClass.getIdentifierProperty(); + final String idName; + if ( identifierProperty != null ) { + idName = identifierProperty.getName(); } - offset = idResult; - final EntityDiscriminatorMapping discriminatorMapping = entityDescriptor.getDiscriminatorMapping(); - if ( discriminatorMapping != null && discriminatorMapping.hasPhysicalColumn() && !discriminatorMapping.isFormula() ) { - if ( modelPart == discriminatorMapping ) { - return offset; - } - offset += discriminatorMapping.getJdbcTypeCount(); + else { + idName = "id"; } - final AttributeMappingsList attributeMappings = entityDescriptor.getAttributeMappings(); - for ( int i = 0; i < attributeMappings.size(); i++ ) { - AttributeMapping attribute = attributeMappings.get( i ); - if ( !( attribute instanceof PluralAttributeMapping ) ) { - final int result = determineModelPartStartIndex( offset, attribute, modelPart ); - if ( result <= 0 ) { - return -result; - } - offset = result; + final Metadata metadata = persistentClass.getIdentifier().getBuildingContext().getMetadataCollector(); + forEachCteColumn( idName, persistentClass.getIdentifier(), columns::add ); + + final Value discriminator = persistentClass.getDiscriminator(); + if ( discriminator != null && !discriminator.getSelectables().get( 0 ).isFormula() ) { + forEachCteColumn( "class", persistentClass.getIdentifier(), columns::add ); + } + + // Collect all columns for all entity subtype attributes + for ( Property property : persistentClass.getPropertyClosure() ) { + if ( !property.isSynthetic() ) { + forEachCteColumn( + property.getName(), + property.getValue(), + columns::add + ); } } - return -1; + // We add a special row number column that we can use to identify and join rows + columns.add( + new CteColumn( + ENTITY_ROW_NUMBER_COLUMN, + metadata.getDatabase().getTypeConfiguration().getBasicTypeForJavaType( Integer.class ) + ) + ); + return new CteTable( cteName, columns ); } - private static int determineIdStartIndex(int offset, EntityPersister entityDescriptor, ModelPart modelPart) { - final int originalOffset = offset; - do { - final EntityIdentifierMapping identifierMapping = entityDescriptor.getIdentifierMapping(); - final int result = determineModelPartStartIndex( originalOffset, identifierMapping, modelPart ); - offset = result; - if ( result <= 0 ) { - break; - } - entityDescriptor = (EntityPersister) entityDescriptor.getSuperMappingType(); - } while ( entityDescriptor != null ); + private static void forEachCteColumn(String prefix, Value value, Consumer consumer) { + SqmMutationStrategyHelper.forEachSelectableMapping( prefix, value, (columnName, selectable) -> { + consumer.accept( new CteColumn( columnName, selectable.getType() ) ); + } ); + } - return offset; + private static void forEachCteColumn(String prefix, ModelPart modelPart, Consumer consumer) { + SqmMutationStrategyHelper.forEachSelectableMapping( prefix, modelPart, (s, selectableMapping) -> { + consumer.accept( new CteColumn( s, selectableMapping.getJdbcMapping() ) ); + } ); } - private static int determineModelPartStartIndex(int offset, ModelPart modelPart, ModelPart modelPartToFind) { - if ( modelPart == modelPartToFind ) { - return -offset; + public List findCteColumns(ModelPart modelPart) { + final String prefix; + if ( modelPart instanceof AttributeMapping attributeMapping ) { + prefix = attributeMapping.getAttributeName(); } - if ( modelPart instanceof EntityValuedModelPart entityValuedModelPart ) { - final ModelPart keyPart = - modelPart instanceof Association association - ? association.getForeignKeyDescriptor() - : entityValuedModelPart.getEntityMappingType().getIdentifierMapping(); - return determineModelPartStartIndex( offset, keyPart, modelPartToFind ); + else if ( modelPart instanceof DiscriminatorMapping ) { + prefix = "class"; } - else if ( modelPart instanceof EmbeddableValuedModelPart embeddablePart ) { - final AttributeMappingsList attributeMappings = - embeddablePart.getEmbeddableTypeDescriptor().getAttributeMappings(); - for ( int i = 0; i < attributeMappings.size(); i++ ) { - final AttributeMapping mapping = attributeMappings.get( i ); - final int result = determineModelPartStartIndex( offset, mapping, modelPartToFind ); - if ( result <= 0 ) { - return result; + else { + prefix = ""; + } + final int jdbcTypeCount = modelPart.getJdbcTypeCount(); + final List columns = new ArrayList<>( jdbcTypeCount ); + SqmMutationStrategyHelper.forEachSelectableMapping( prefix, modelPart, (s, selectableMapping) -> { + for ( CteColumn cteColumn : cteColumns ) { + if ( s.equals( cteColumn.getColumnExpression() ) ) { + columns.add( cteColumn ); + break; } - offset = result; } - return offset; - } - else if ( modelPart instanceof BasicValuedModelPart basicModelPart ) { - return offset + (basicModelPart.isInsertable() ? modelPart.getJdbcTypeCount() : 0); - } - return offset + modelPart.getJdbcTypeCount(); + } ); + return columns; } } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/LocalTemporaryTableMutationStrategyNoDropTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/LocalTemporaryTableMutationStrategyNoDropTest.java index 6418e9bd114b..ee70c795c01d 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/LocalTemporaryTableMutationStrategyNoDropTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/LocalTemporaryTableMutationStrategyNoDropTest.java @@ -4,8 +4,11 @@ */ package org.hibernate.orm.test; -import java.util.Map; - +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +import jakarta.persistence.Inheritance; +import jakarta.persistence.InheritanceType; import org.hibernate.boot.model.relational.SqlStringGenerationContext; import org.hibernate.boot.model.relational.internal.SqlStringGenerationContextImpl; import org.hibernate.boot.spi.BootstrapContext; @@ -13,34 +16,28 @@ import org.hibernate.boot.spi.SessionFactoryOptions; import org.hibernate.cache.spi.CacheImplementor; import org.hibernate.dialect.Dialect; -import org.hibernate.dialect.temptable.StandardTemporaryTableExporter; import org.hibernate.dialect.temptable.TemporaryTable; +import org.hibernate.dialect.temptable.TemporaryTableExporter; import org.hibernate.dialect.temptable.TemporaryTableKind; import org.hibernate.engine.jdbc.spi.JdbcServices; import org.hibernate.engine.spi.SessionFactoryImplementor; -import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.generator.Generator; import org.hibernate.mapping.GeneratorSettings; import org.hibernate.metamodel.spi.MappingMetamodelImplementor; import org.hibernate.metamodel.spi.RuntimeModelCreationContext; import org.hibernate.query.sqm.function.SqmFunctionRegistry; -import org.hibernate.query.sqm.mutation.internal.temptable.LocalTemporaryTableMutationStrategy; import org.hibernate.sql.ast.spi.ParameterMarkerStrategy; -import org.hibernate.type.descriptor.jdbc.JdbcType; - +import org.hibernate.testing.orm.junit.DialectFeatureChecks; import org.hibernate.testing.orm.junit.DomainModel; import org.hibernate.testing.orm.junit.JiraKey; +import org.hibernate.testing.orm.junit.RequiresDialectFeature; import org.hibernate.testing.orm.junit.ServiceRegistry; import org.hibernate.testing.orm.junit.SessionFactory; import org.hibernate.testing.orm.junit.SessionFactoryScope; -import org.hibernate.testing.orm.junit.Setting; +import org.hibernate.type.descriptor.jdbc.JdbcType; import org.junit.jupiter.api.Test; -import jakarta.persistence.Entity; -import jakarta.persistence.GeneratedValue; -import jakarta.persistence.Id; -import jakarta.persistence.Inheritance; -import jakarta.persistence.InheritanceType; +import java.util.Map; import static java.util.Collections.emptyMap; import static org.assertj.core.api.AssertionsForClassTypes.assertThat; @@ -52,14 +49,12 @@ ) @SessionFactory @ServiceRegistry( - settings = { - @Setting(name = LocalTemporaryTableMutationStrategy.DROP_ID_TABLES, value = "false") - }, services = @ServiceRegistry.Service( role = ParameterMarkerStrategy.class, impl = LocalTemporaryTableMutationStrategyNoDropTest.ParameterMarkerStrategyImpl.class ) ) +@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsTemporaryTable.class) @JiraKey("HHH-16486") public class LocalTemporaryTableMutationStrategyNoDropTest { @@ -69,10 +64,10 @@ public class LocalTemporaryTableMutationStrategyNoDropTest { public void testGetSqlTruncateCommand(SessionFactoryScope scope) { scope.inTransaction( session -> { - StandardTemporaryTableExporter standardTemporaryTableExporter - = createStandardTemporaryTableExporter( session ); - TemporaryTable idTable = createTemporaryTable( scope, session ); - String sqlTruncateCommand = standardTemporaryTableExporter.getSqlTruncateCommand( + final TemporaryTableExporter temporaryTableExporter = + scope.getSessionFactory().getJdbcServices().getDialect().getTemporaryTableExporter(); + final TemporaryTable idTable = createTemporaryTable( scope ); + final String sqlTruncateCommand = temporaryTableExporter.getSqlTruncateCommand( idTable, null, session @@ -84,25 +79,15 @@ public void testGetSqlTruncateCommand(SessionFactoryScope scope) { } - private static StandardTemporaryTableExporter createStandardTemporaryTableExporter(SessionImplementor session) { - return new StandardTemporaryTableExporter( getDialect() ); - } - - private static Dialect getDialect() { - return new TestDialect(); - } - - private static TemporaryTable createTemporaryTable( - SessionFactoryScope scope, - SessionImplementor session) { - SessionFactoryImplementor sessionFactory = scope.getSessionFactory(); - JdbcServices jdbcServices = sessionFactory.getJdbcServices(); - Dialect dialect = getDialect(); + private static TemporaryTable createTemporaryTable(SessionFactoryScope scope) { + final SessionFactoryImplementor sessionFactory = scope.getSessionFactory(); + final JdbcServices jdbcServices = sessionFactory.getJdbcServices(); return TemporaryTable.createIdTable( - session.getEntityPersister( null, new TestEntity() ), + scope.getMetadataImplementor().getEntityBinding( TestEntity.class.getName() ), basename -> TemporaryTable.ID_TABLE_PREFIX + basename, - dialect, - new ModelCreationContext( sessionFactory, scope, dialect, jdbcServices ) + TemporaryTableKind.PERSISTENT, + jdbcServices.getDialect(), + new ModelCreationContext( sessionFactory, scope, jdbcServices ) ); } @@ -113,20 +98,6 @@ public String createMarker(int position, JdbcType jdbcType) { } } - public static class TestDialect extends Dialect { - - @Override - public boolean supportsTemporaryTables() { - return true; - } - - @Override - public TemporaryTableKind getSupportedTemporaryTableKind() { - return TemporaryTableKind.PERSISTENT; - } - } - - @Entity(name = "ParentEntity") @Inheritance(strategy = InheritanceType.JOINED) public static class ParentEntity { @@ -146,13 +117,11 @@ private static class ModelCreationContext implements RuntimeModelCreationContext private final SessionFactoryImplementor sessionFactory; private final SessionFactoryScope scope; - private final Dialect dialect; private final JdbcServices jdbcServices; - public ModelCreationContext(SessionFactoryImplementor sessionFactory, SessionFactoryScope scope, Dialect dialect, JdbcServices jdbcServices) { + public ModelCreationContext(SessionFactoryImplementor sessionFactory, SessionFactoryScope scope, JdbcServices jdbcServices) { this.sessionFactory = sessionFactory; this.scope = scope; - this.dialect = dialect; this.jdbcServices = jdbcServices; } @@ -188,7 +157,7 @@ public Map getSettings() { @Override public Dialect getDialect() { - return dialect; + return jdbcServices.getDialect(); } @Override diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/bulkid/AbstractMutationStrategyCompositeIdTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/bulkid/AbstractMutationStrategyCompositeIdTest.java index fcae1171781e..95fd07ff4aa2 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/bulkid/AbstractMutationStrategyCompositeIdTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/bulkid/AbstractMutationStrategyCompositeIdTest.java @@ -142,6 +142,21 @@ public void testInsert() { }); } + @Test + public void testInsertSelect() { + doInHibernate( this::sessionFactory, session -> { + final int insertCount = session.createQuery( "insert into Engineer(id, companyName, name, employed, fellow) " + + "select d.id + " + (entityCount() * 2) + ", 'Red Hat', 'John Doe', true, false from Doctor d" ) + .executeUpdate(); + final Engineer engineer = session.find( Engineer.class, + new AbstractMutationStrategyCompositeIdTest_.Person_.Id( entityCount() * 2 + 1, "Red Hat" ) ); + assertEquals( entityCount(), insertCount ); + assertEquals( "John Doe", engineer.getName() ); + assertTrue( engineer.isEmployed() ); + assertFalse( engineer.isFellow() ); + }); + } + //tag::batch-bulk-hql-temp-table-base-class-example[] @Entity(name = "Person") @Inheritance(strategy = InheritanceType.JOINED) diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/bulkid/AbstractMutationStrategyGeneratedIdTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/bulkid/AbstractMutationStrategyGeneratedIdTest.java index f603dac9ea38..2c99efe211f5 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/bulkid/AbstractMutationStrategyGeneratedIdTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/bulkid/AbstractMutationStrategyGeneratedIdTest.java @@ -15,6 +15,7 @@ import org.hibernate.cfg.Configuration; import org.hibernate.query.sqm.mutation.spi.SqmMultiTableInsertStrategy; import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.junit.Before; import org.junit.Test; import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; @@ -54,6 +55,16 @@ protected boolean isCleanupTestDataUsingBulkDelete() { return true; } + @Before + public void setUp() { + doInHibernate( this::sessionFactory, session -> { + Doctor doctor = new Doctor(); + doctor.setName( "Doctor John" ); + doctor.setEmployed( true ); + session.persist( doctor ); + }); + } + @Test public void testInsertStatic() { doInHibernate( this::sessionFactory, session -> { @@ -84,6 +95,37 @@ public void testInsertGenerated() { }); } + @Test + public void testInsertSelectStatic() { + doInHibernate( this::sessionFactory, session -> { + final int insertCount = session.createQuery( "insert into Engineer(id, name, employed, fellow) " + + "select d.id + 1, 'John Doe', true, false from Doctor d" ) + .executeUpdate(); + + final Engineer engineer = session.createQuery( "from Engineer e where e.name = 'John Doe'", Engineer.class ) + .getSingleResult(); + assertEquals( 1, insertCount ); + assertEquals( "John Doe", engineer.getName() ); + assertTrue( engineer.isEmployed() ); + assertFalse( engineer.isFellow() ); + }); + } + + @Test + public void testInsertSelectGenerated() { + doInHibernate( this::sessionFactory, session -> { + final int insertCount = session.createQuery( "insert into Engineer(name, employed, fellow) " + + "select 'John Doe', true, false from Doctor d" ) + .executeUpdate(); + final Engineer engineer = session.createQuery( "from Engineer e where e.name = 'John Doe'", Engineer.class ) + .getSingleResult(); + assertEquals( 1, insertCount ); + assertEquals( "John Doe", engineer.getName() ); + assertTrue( engineer.isEmployed() ); + assertFalse( engineer.isFellow() ); + }); + } + @Entity(name = "Person") @Inheritance(strategy = InheritanceType.JOINED) public static class Person { diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/bulkid/AbstractMutationStrategyGeneratedIdWithOptimizerTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/bulkid/AbstractMutationStrategyGeneratedIdWithOptimizerTest.java index 8798cf8484e2..522dfb57e628 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/bulkid/AbstractMutationStrategyGeneratedIdWithOptimizerTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/bulkid/AbstractMutationStrategyGeneratedIdWithOptimizerTest.java @@ -14,6 +14,7 @@ import org.hibernate.cfg.Configuration; import org.hibernate.query.sqm.mutation.spi.SqmMultiTableInsertStrategy; import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.junit.Before; import org.junit.Test; import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; @@ -53,6 +54,16 @@ protected boolean isCleanupTestDataUsingBulkDelete() { return true; } + @Before + public void setUp() { + doInHibernate( this::sessionFactory, session -> { + Doctor doctor = new Doctor(); + doctor.setName( "Doctor John" ); + doctor.setEmployed( true ); + session.persist( doctor ); + }); + } + @Test public void testInsertStatic() { doInHibernate( this::sessionFactory, session -> { @@ -83,6 +94,37 @@ public void testInsertGenerated() { }); } + @Test + public void testInsertSelectStatic() { + doInHibernate( this::sessionFactory, session -> { + final int insertCount = session.createQuery( "insert into Engineer(id, name, employed, fellow) " + + "select d.id + 1, 'John Doe', true, false from Doctor d" ) + .executeUpdate(); + + final Engineer engineer = session.createQuery( "from Engineer e where e.name = 'John Doe'", Engineer.class ) + .getSingleResult(); + assertEquals( 1, insertCount ); + assertEquals( "John Doe", engineer.getName() ); + assertTrue( engineer.isEmployed() ); + assertFalse( engineer.isFellow() ); + }); + } + + @Test + public void testInsertSelectGenerated() { + doInHibernate( this::sessionFactory, session -> { + final int insertCount = session.createQuery( "insert into Engineer(name, employed, fellow) " + + "select 'John Doe', true, false from Doctor d" ) + .executeUpdate(); + final Engineer engineer = session.createQuery( "from Engineer e where e.name = 'John Doe'", Engineer.class ) + .getSingleResult(); + assertEquals( 1, insertCount ); + assertEquals( "John Doe", engineer.getName() ); + assertTrue( engineer.isEmployed() ); + assertFalse( engineer.isFellow() ); + }); + } + @Entity(name = "Person") @Inheritance(strategy = InheritanceType.JOINED) public static class Person { diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/bulkid/AbstractMutationStrategyGeneratedIdentityTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/bulkid/AbstractMutationStrategyGeneratedIdentityTest.java index 5a7e3e921da7..4884e426da10 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/bulkid/AbstractMutationStrategyGeneratedIdentityTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/bulkid/AbstractMutationStrategyGeneratedIdentityTest.java @@ -12,10 +12,13 @@ import jakarta.persistence.InheritanceType; import org.hibernate.cfg.AvailableSettings; import org.hibernate.cfg.Configuration; +import org.hibernate.dialect.AbstractTransactSQLDialect; +import org.hibernate.dialect.MySQLDialect; import org.hibernate.dialect.OracleDialect; import org.hibernate.query.sqm.mutation.spi.SqmMultiTableInsertStrategy; import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; import org.hibernate.testing.orm.junit.SkipForDialect; +import org.junit.Before; import org.junit.Test; import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; @@ -55,7 +58,19 @@ protected boolean isCleanupTestDataUsingBulkDelete() { return true; } + @Before + public void setUp() { + doInHibernate( this::sessionFactory, session -> { + Doctor doctor = new Doctor(); + doctor.setName( "Doctor John" ); + doctor.setEmployed( true ); + session.persist( doctor ); + }); + } + @Test + @SkipForDialect(dialectClass = MySQLDialect.class, matchSubTypes = true, reason = "MySQL ignores a provided value for an auto_increment column if it's lower than the current sequence value") + @SkipForDialect(dialectClass = AbstractTransactSQLDialect.class, matchSubTypes = true, reason = "T-SQL complains IDENTITY_INSERT is off when a value for an identity column is provided") public void testInsertStatic() { doInHibernate( this::sessionFactory, session -> { session.createQuery( "insert into Engineer(id, name, employed, fellow) values (0, :name, :employed, false)" ) @@ -86,6 +101,40 @@ public void testInsertGenerated() { }); } + @Test + @SkipForDialect(dialectClass = MySQLDialect.class, matchSubTypes = true, reason = "MySQL ignores a provided value for an auto_increment column if it's lower than the current sequence value") + @SkipForDialect(dialectClass = AbstractTransactSQLDialect.class, matchSubTypes = true, reason = "T-SQL complains IDENTITY_INSERT is off when a value for an identity column is provided") + public void testInsertSelectStatic() { + doInHibernate( this::sessionFactory, session -> { + final int insertCount = session.createQuery( "insert into Engineer(id, name, employed, fellow) " + + "select d.id + 1, 'John Doe', true, false from Doctor d" ) + .executeUpdate(); + + final Engineer engineer = session.createQuery( "from Engineer e where e.name = 'John Doe'", Engineer.class ) + .getSingleResult(); + assertEquals( 1, insertCount ); + assertEquals( "John Doe", engineer.getName() ); + assertTrue( engineer.isEmployed() ); + assertFalse( engineer.isFellow() ); + }); + } + + @Test + @SkipForDialect(dialectClass = OracleDialect.class, reason = "Oracle doesn't support insert-select with a returning clause") + public void testInsertSelectGenerated() { + doInHibernate( this::sessionFactory, session -> { + final int insertCount = session.createQuery( "insert into Engineer(name, employed, fellow) " + + "select 'John Doe', true, false from Doctor d" ) + .executeUpdate(); + final Engineer engineer = session.createQuery( "from Engineer e where e.name = 'John Doe'", Engineer.class ) + .getSingleResult(); + assertEquals( 1, insertCount ); + assertEquals( "John Doe", engineer.getName() ); + assertTrue( engineer.isEmployed() ); + assertFalse( engineer.isFellow() ); + }); + } + @Entity(name = "Person") @Inheritance(strategy = InheritanceType.JOINED) public static class Person { diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/bulkid/AbstractMutationStrategyIdTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/bulkid/AbstractMutationStrategyIdTest.java index 712532487316..2cbb7136d98c 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/bulkid/AbstractMutationStrategyIdTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/bulkid/AbstractMutationStrategyIdTest.java @@ -158,6 +158,21 @@ public void testInsert() { }); } + @Test + public void testInsertSelect() { + doInHibernate( this::sessionFactory, session -> { + final int insertCount = session.createQuery( "insert into Engineer(id, name, employed, fellow) " + + "select d.id + " + (entityCount() * 2) + ", 'John Doe', true, false from Doctor d" ) + .executeUpdate(); + final AbstractMutationStrategyIdTest.Engineer engineer = + session.find( AbstractMutationStrategyIdTest.Engineer.class, entityCount() * 2 + 1 ); + assertEquals( entityCount(), insertCount ); + assertEquals( "John Doe", engineer.getName() ); + assertTrue( engineer.isEmployed() ); + assertFalse( engineer.isFellow() ); + }); + } + @Entity(name = "Person") @Inheritance(strategy = InheritanceType.JOINED) public static class Person { diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/hql/BulkManipulationTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/hql/BulkManipulationTest.java index 438700875a3f..b7aab70829db 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/hql/BulkManipulationTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/hql/BulkManipulationTest.java @@ -18,6 +18,7 @@ import org.hibernate.boot.registry.StandardServiceRegistryBuilder; import org.hibernate.cfg.AvailableSettings; import org.hibernate.community.dialect.InformixDialect; +import org.hibernate.dialect.AbstractTransactSQLDialect; import org.hibernate.dialect.CockroachDialect; import org.hibernate.dialect.H2Dialect; import org.hibernate.dialect.MySQLDialect; @@ -524,6 +525,7 @@ public void testInsertWithMismatchedTypes() { @Test @SkipForDialect(value = CockroachDialect.class, comment = "https://github.com/cockroachdb/cockroach/issues/75101") + @SkipForDialect(value = AbstractTransactSQLDialect.class, comment = "T-SQL complains IDENTITY_INSERT is off when a value for an identity column is provided") @RequiresDialectFeature(value = DialectChecks.SupportsTemporaryTableIdentity.class, comment = "The use of the native generator leads to using identity which also needs to be supported on temporary tables") public void testInsertIntoSuperclassPropertiesFails() { TestData data = new TestData();