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 9bc6298e6c47..60e821c0a973 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 @@ -205,7 +205,7 @@ public HANALegacyDialect(HANAServerConfiguration configuration, boolean defaultT this.maxLobPrefetchSize = configuration.getMaxLobPrefetchSize(); this.useUnicodeStringTypes = useUnicodeStringTypesDefault(); - this.lockingSupport = buildLockingSupport(); + this.lockingSupport = HANALockingSupport.forDialectVersion( configuration.getFullVersion() ); } private LockingSupport buildLockingSupport() { @@ -998,34 +998,18 @@ public String getReadLockString(Timeout timeout) { } @Override - public String getReadLockString(String aliases, Timeout timeout) { - return getWriteLockString( aliases, timeout ); + public String getForUpdateString(Timeout timeout) { + return withTimeout( getForUpdateString(), timeout.milliseconds() ); } @Override - public String getWriteLockString(Timeout timeout) { - if ( Timeouts.isRealTimeout( timeout ) ) { - return getForUpdateString() + " wait " + getTimeoutInSeconds( timeout.milliseconds() ); - } - else if ( timeout.milliseconds() == Timeouts.NO_WAIT_MILLI ) { - return getForUpdateNowaitString(); - } - else { - return getForUpdateString(); - } + public String getReadLockString(String aliases, Timeout timeout) { + return getWriteLockString( aliases, timeout ); } @Override public String getWriteLockString(String aliases, Timeout timeout) { - if ( Timeouts.isRealTimeout( timeout ) ) { - return getForUpdateString( aliases ) + " wait " + getTimeoutInSeconds( timeout.milliseconds() ); - } - else if ( timeout.milliseconds() == Timeouts.NO_WAIT_MILLI ) { - return getForUpdateNowaitString( aliases ); - } - else { - return getForUpdateString( aliases ); - } + return withTimeout( getForUpdateString( aliases ), timeout.milliseconds() ); } @Override @@ -1039,29 +1023,17 @@ public String getReadLockString(String aliases, int timeout) { } @Override - public String getWriteLockString(int timeout) { - if ( Timeouts.isRealTimeout( timeout ) ) { - return getForUpdateString() + " wait " + Timeouts.getTimeoutInSeconds( timeout ); - } - else if ( timeout == Timeouts.NO_WAIT_MILLI ) { - return getForUpdateNowaitString(); - } - else { - return getForUpdateString(); - } + public String getWriteLockString(String aliases, int timeout) { + return withTimeout( getForUpdateString( aliases ), timeout ); } - @Override - public String getWriteLockString(String aliases, int timeout) { - if ( timeout > 0 ) { - return getForUpdateString( aliases ) + " wait " + getTimeoutInSeconds( timeout ); - } - else if ( timeout == 0 ) { - return getForUpdateNowaitString( aliases ); - } - else { - return getForUpdateString( aliases ); - } + private String withTimeout(String lockString, int timeout) { + return switch (timeout) { + case Timeouts.NO_WAIT_MILLI -> supportsNoWait() ? lockString + " nowait" : lockString; + case Timeouts.SKIP_LOCKED_MILLI -> supportsSkipLocked() ? lockString + SQL_IGNORE_LOCKED : lockString; + case Timeouts.WAIT_FOREVER_MILLI -> lockString; + default -> supportsWait() ? lockString + " wait " + getTimeoutInSeconds( timeout ) : lockString; + }; } @Override @@ -2006,11 +1978,6 @@ public String getForUpdateSkipLockedString(String aliases) { getForUpdateString(aliases) + SQL_IGNORE_LOCKED : getForUpdateString(aliases); } - @Override - public String getForUpdateString(LockMode lockMode) { - return super.getForUpdateString(lockMode); - } - @Override public String getDual() { return "sys.dummy"; 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 613ea3390a85..3a821bceaacf 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/HANADialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/HANADialect.java @@ -1003,34 +1003,18 @@ public String getReadLockString(Timeout timeout) { } @Override - public String getReadLockString(String aliases, Timeout timeout) { - return getWriteLockString( aliases, timeout ); + public String getForUpdateString(Timeout timeout) { + return withTimeout( getForUpdateString(), timeout.milliseconds() ); } @Override - public String getWriteLockString(Timeout timeout) { - if ( Timeouts.isRealTimeout( timeout ) ) { - return getForUpdateString() + " wait " + Timeouts.getTimeoutInSeconds( timeout.milliseconds() ); - } - else if ( timeout.milliseconds() == Timeouts.NO_WAIT_MILLI ) { - return getForUpdateNowaitString(); - } - else { - return getForUpdateString(); - } + public String getReadLockString(String aliases, Timeout timeout) { + return getWriteLockString( aliases, timeout ); } @Override public String getWriteLockString(String aliases, Timeout timeout) { - if ( Timeouts.isRealTimeout( timeout ) ) { - return getForUpdateString( aliases ) + " wait " + getTimeoutInSeconds( timeout.milliseconds() ); - } - else if ( timeout.milliseconds() == Timeouts.NO_WAIT_MILLI ) { - return getForUpdateNowaitString( aliases ); - } - else { - return getForUpdateString( aliases ); - } + return withTimeout( getForUpdateString( aliases ), timeout.milliseconds() ); } @Override @@ -1044,29 +1028,17 @@ public String getReadLockString(String aliases, int timeout) { } @Override - public String getWriteLockString(int timeout) { - if ( timeout > 0 ) { - return getForUpdateString() + " wait " + getTimeoutInSeconds( timeout ); - } - else if ( timeout == Timeouts.NO_WAIT_MILLI ) { - return getForUpdateNowaitString(); - } - else { - return getForUpdateString(); - } + public String getWriteLockString(String aliases, int timeout) { + return withTimeout( getForUpdateString( aliases ), timeout ); } - @Override - public String getWriteLockString(String aliases, int timeout) { - if ( timeout > 0 ) { - return getForUpdateString( aliases ) + " wait " + getTimeoutInSeconds( timeout ); - } - else if ( timeout == 0 ) { - return getForUpdateNowaitString( aliases ); - } - else { - return getForUpdateString( aliases ); - } + private String withTimeout(String lockString, int timeout) { + return switch (timeout) { + case Timeouts.NO_WAIT_MILLI -> supportsNoWait() ? lockString + " nowait" : lockString; + case Timeouts.SKIP_LOCKED_MILLI -> supportsSkipLocked() ? lockString + SQL_IGNORE_LOCKED : lockString; + case Timeouts.WAIT_FOREVER_MILLI -> lockString; + default -> supportsWait() ? lockString + " wait " + getTimeoutInSeconds( timeout ) : lockString; + }; } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/lock/internal/HANALockingSupport.java b/hibernate-core/src/main/java/org/hibernate/dialect/lock/internal/HANALockingSupport.java index ac60c6b418f6..61fe43d857de 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/lock/internal/HANALockingSupport.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/lock/internal/HANALockingSupport.java @@ -4,9 +4,11 @@ */ package org.hibernate.dialect.lock.internal; +import org.hibernate.dialect.DatabaseVersion; import org.hibernate.dialect.RowLockStrategy; import org.hibernate.dialect.lock.PessimisticLockStyle; import org.hibernate.dialect.lock.spi.ConnectionLockTimeoutStrategy; +import org.hibernate.dialect.lock.spi.LockingSupport; import org.hibernate.dialect.lock.spi.OuterJoinLockingType; /** @@ -15,14 +17,24 @@ * @author Steve Ebersole */ public class HANALockingSupport extends LockingSupportParameterized { - public static final HANALockingSupport HANA_LOCKING_SUPPORT = new HANALockingSupport( true ); + public static final HANALockingSupport HANA_LOCKING_SUPPORT = new HANALockingSupport( true, true ); + + public static LockingSupport forDialectVersion(DatabaseVersion version) { + final boolean supportsWait = version.isSameOrAfter( 2, 0, 10 ); + final boolean supportsSkipLocked = version.isSameOrAfter(2, 0, 30); + return new HANALockingSupport( supportsWait, supportsSkipLocked ); + } public HANALockingSupport(boolean supportsSkipLocked) { + this( false, supportsSkipLocked ); + } + + private HANALockingSupport(boolean supportsWait, boolean supportsSkipLocked) { super( PessimisticLockStyle.CLAUSE, RowLockStrategy.COLUMN, - false, - false, + supportsWait, + supportsWait, supportsSkipLocked, OuterJoinLockingType.IDENTIFIED ); diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/internal/StandardLockingClauseStrategy.java b/hibernate-core/src/main/java/org/hibernate/sql/ast/internal/StandardLockingClauseStrategy.java index d3d8799641c8..bd09dc8f34ba 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/ast/internal/StandardLockingClauseStrategy.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/ast/internal/StandardLockingClauseStrategy.java @@ -10,21 +10,26 @@ import org.hibernate.dialect.RowLockStrategy; import org.hibernate.internal.util.collections.CollectionHelper; import org.hibernate.metamodel.mapping.EntityAssociationMapping; +import org.hibernate.metamodel.mapping.EntityMappingType; import org.hibernate.metamodel.mapping.ForeignKeyDescriptor; import org.hibernate.metamodel.mapping.ModelPart; +import org.hibernate.metamodel.mapping.ModelPartContainer; import org.hibernate.metamodel.mapping.PluralAttributeMapping; +import org.hibernate.metamodel.mapping.TableDetails; import org.hibernate.metamodel.mapping.ValuedModelPart; import org.hibernate.metamodel.mapping.internal.BasicValuedCollectionPart; import org.hibernate.persister.entity.EntityPersister; +import org.hibernate.persister.entity.mutation.EntityTableMapping; import org.hibernate.sql.ast.SqlAstJoinType; import org.hibernate.sql.ast.spi.LockingClauseStrategy; import org.hibernate.sql.ast.spi.SqlAppender; +import org.hibernate.sql.ast.tree.from.NamedTableReference; import org.hibernate.sql.ast.tree.from.TableGroup; import org.hibernate.sql.ast.tree.from.TableGroupJoin; import org.hibernate.sql.ast.tree.from.TableReferenceJoin; +import org.hibernate.sql.model.TableMapping; import java.util.ArrayList; -import java.util.Collections; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; @@ -212,21 +217,61 @@ private void addTableAliases(TableGroup tableGroup, List lockItems) { } private void addColumnRefs(TableGroup tableGroup, List lockItems) { - Collections.addAll( lockItems, determineKeyColumnRefs( tableGroup ) ); - } - - private String[] determineKeyColumnRefs(TableGroup tableGroup) { final String[] keyColumns = determineKeyColumnNames( tableGroup.getModelPart() ); - final String[] result = new String[keyColumns.length]; final String tableAlias = tableGroup.getPrimaryTableReference().getIdentificationVariable(); for ( int i = 0; i < keyColumns.length; i++ ) { // NOTE: in some tests with Oracle, the qualifiers are being applied twice; // still need to track that down. possibly, unexpected calls to // `Dialect#applyLocksToSql`? assert !keyColumns[i].contains( "." ); - result[i] = tableAlias + "." + keyColumns[i]; + lockItems.add( tableAlias + "." + keyColumns[i] ); + } + + final List tableReferenceJoins = tableGroup.getTableReferenceJoins(); + if ( CollectionHelper.isNotEmpty( tableReferenceJoins ) ) { + final EntityPersister entityPersister = determineEntityPersister( tableGroup.getModelPart() ); + for ( int i = 0; i < tableReferenceJoins.size(); i++ ) { + final TableReferenceJoin tableReferenceJoin = tableReferenceJoins.get( i ); + final NamedTableReference joinedTableReference = tableReferenceJoin.getJoinedTableReference(); + final String tableJoinAlias = joinedTableReference.getIdentificationVariable(); + final TableMapping tableMapping = determineTableMapping( entityPersister, tableReferenceJoin ); + for ( TableDetails.KeyColumn keyColumn : tableMapping.getKeyDetails().getKeyColumns() ) { + lockItems.add( tableJoinAlias + "." + keyColumn.getColumnName() ); + } + } + } + } + + private TableMapping determineTableMapping(EntityPersister entityPersister, TableReferenceJoin tableReferenceJoin) { + final NamedTableReference joinedTableReference = tableReferenceJoin.getJoinedTableReference(); + for ( EntityTableMapping tableMapping : entityPersister.getTableMappings() ) { + if ( joinedTableReference.containsAffectedTableName( tableMapping.getTableName() ) ) { + return tableMapping; + } + } + for ( EntityMappingType subMappingType : entityPersister.getSubMappingTypes() ) { + for ( EntityTableMapping tableMapping : subMappingType.getEntityPersister().getTableMappings() ) { + if ( joinedTableReference.containsAffectedTableName( tableMapping.getTableName() ) ) { + return tableMapping; + } + } + } + throw new IllegalArgumentException( "Couldn't find subclass index for joined table reference " + joinedTableReference ); + } + + private EntityPersister determineEntityPersister(ModelPartContainer modelPart) { + if ( modelPart instanceof EntityPersister entityPersister ) { + return entityPersister; + } + else if ( modelPart instanceof PluralAttributeMapping pluralAttributeMapping ) { + return pluralAttributeMapping.getCollectionDescriptor().getElementPersister(); + } + else if ( modelPart instanceof EntityAssociationMapping entityAssociationMapping ) { + return entityAssociationMapping.getAssociatedEntityMappingType().getEntityPersister(); + } + else { + throw new IllegalArgumentException( "Expected table group with table joins to have an entity typed model part but got: " + modelPart ); } - return result; } private String[] determineKeyColumnNames(ModelPart modelPart) { diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/dialect/unit/locktimeout/HANALockTimeoutTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/dialect/unit/locktimeout/HANALockTimeoutTest.java index 85b21d5f0123..22e24823d8c2 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/dialect/unit/locktimeout/HANALockTimeoutTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/dialect/unit/locktimeout/HANALockTimeoutTest.java @@ -55,11 +55,11 @@ public void testLockTimeoutNoAliasNoWait() { @Test public void testLockTimeoutNoAliasSkipLocked() { assertEquals( - " for update", + " for update ignore locked", dialect.getForUpdateString( new LockOptions( LockMode.PESSIMISTIC_READ ).setTimeout( SKIP_LOCKED ) ) ); assertEquals( - " for update", + " for update ignore locked", dialect.getForUpdateString( new LockOptions( LockMode.PESSIMISTIC_WRITE ).setTimeout( SKIP_LOCKED ) ) ); } @@ -100,7 +100,7 @@ public void testLockTimeoutAliasNoWait() { public void testLockTimeoutAliasSkipLocked() { String alias = "a"; assertEquals( - " for update of a", + " for update of a ignore locked", dialect.getForUpdateString( alias, new LockOptions( LockMode.PESSIMISTIC_READ ) @@ -108,7 +108,7 @@ public void testLockTimeoutAliasSkipLocked() { ) ); assertEquals( - " for update of a", + " for update of a ignore locked", dialect.getForUpdateString( alias, new LockOptions( LockMode.PESSIMISTIC_WRITE )