Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import org.hibernate.boot.model.FunctionContributions;
import org.hibernate.boot.model.TypeContributions;
import org.hibernate.dialect.Dialect;
import org.hibernate.internal.util.config.ConfigurationHelper;
import org.hibernate.dialect.identity.IdentityColumnSupport;
import org.hibernate.dialect.pagination.LimitHandler;
import org.hibernate.dialect.pagination.LimitOffsetLimitHandler;
Expand Down Expand Up @@ -94,8 +95,20 @@ public class YdbDialect extends Dialect {
);
private static final ConcurrentHashMap<Integer, DecimalJdbcType> DECIMAL_JDBC_TYPE_CACHE = new ConcurrentHashMap<>();

/**
* When true, FOR UPDATE / FOR SHARE lock hints are ignored and an empty string is returned.
* Configured via {@value tech.ydb.hibernate.dialect.YdbSettings#IGNORE_LOCK_HINTS}.
* Default is {@code false} to support backward compatibility
*/
private final boolean ignoreLockHints;

public YdbDialect(DialectResolutionInfo dialectResolutionInfo) {
super(dialectResolutionInfo);
ignoreLockHints = ConfigurationHelper.getBoolean(
YdbSettings.IGNORE_LOCK_HINTS,
dialectResolutionInfo.getConfigurationValues(),
false
);
}

@Override
Expand Down Expand Up @@ -392,6 +405,9 @@ public String getCurrentTimestampSelectString() {

@Override
public String getForUpdateString() {
if (ignoreLockHints) {
return "";
}
throw new UnsupportedOperationException("YDB does not support FOR UPDATE statement");
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package tech.ydb.hibernate.dialect;

/**
* Dialect-specific configuration settings for YDB.
*
* @author Ainur Mukhtarov
*/
public interface YdbSettings {

/**
* Specifies whether the dialect should ignore FOR UPDATE / FOR SHARE lock hints
* and omit them from generated SQL.
* YDB does not support {@code SELECT ... FOR UPDATE} or {@code FOR SHARE}.
* When this setting is {@code true}, the dialect returns an empty string for
* lock clauses, allowing applications that use
* {@code PESSIMISTIC_WRITE} to run without errors.
* Set to {@code false} to throw {@link UnsupportedOperationException} when
* lock hints are requested, making the lack of support explicit.
*
* By default, it is disabled to support backward compatibility
*
* @see jakarta.persistence.LockModeType#PESSIMISTIC_WRITE
* @see jakarta.persistence.LockModeType#PESSIMISTIC_READ
*/
String IGNORE_LOCK_HINTS = "hibernate.dialect.ydb.ignore_lock_hints";
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package tech.ydb.hibernate.lock;

import jakarta.persistence.LockModeType;
import org.hibernate.cfg.AvailableSettings;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import tech.ydb.test.junit5.YdbHelperExtension;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static tech.ydb.hibernate.TestUtils.*;
import static tech.ydb.hibernate.dialect.YdbSettings.IGNORE_LOCK_HINTS;

/**
* Tests for {@value tech.ydb.hibernate.dialect.YdbSettings#IGNORE_LOCK_HINTS} setting.
*
* @author Ainur Mukhtarov
*/
public class LockTests {

private static final long ENTITY_ID = 1L;

@RegisterExtension
private static final YdbHelperExtension ydb = new YdbHelperExtension();

@Test
void whenIgnoreLockHintsTrue_pessimisticLockSucceeds() {
runWithConfiguration(true, () -> {
assertEntityFoundWithLock(LockModeType.PESSIMISTIC_WRITE);
assertEntityFoundWithLock(LockModeType.PESSIMISTIC_READ);
assertEntityFoundWithLock(LockModeType.PESSIMISTIC_FORCE_INCREMENT);
});
}

@Test
void whenIgnoreLockHintsFalse_pessimisticLockThrows() {
runWithConfiguration(false, () -> {
assertThrowsWithLock(LockModeType.PESSIMISTIC_WRITE);
assertThrowsWithLock(LockModeType.PESSIMISTIC_READ);
assertThrowsWithLock(LockModeType.PESSIMISTIC_FORCE_INCREMENT);
});
}

private void persistEntity() {
var entity = new TestEntity();
entity.setId(ENTITY_ID);
entity.setStringValue("test");

inTransaction(session -> session.persist(entity));
}

private static void assertEntityFoundWithLock(LockModeType lockMode) {
inTransaction(session -> {
var actual = session.find(TestEntity.class, ENTITY_ID, lockMode);

assertEquals(ENTITY_ID, actual.getId());
assertEquals("test", actual.getStringValue());
});
}

private static void assertThrowsWithLock(LockModeType pessimisticForceIncrement) {
assertThrows(
UnsupportedOperationException.class,
() -> inTransaction(s -> s.find(TestEntity.class, ENTITY_ID, pessimisticForceIncrement)),
"YDB does not support FOR UPDATE statement"
);
}

private void runWithConfiguration(boolean settingValue, Runnable test) {
SESSION_FACTORY = basedConfiguration()
.setProperty(AvailableSettings.URL, jdbcUrl(ydb))
.addAnnotatedClass(TestEntity.class)
.setProperty(IGNORE_LOCK_HINTS, settingValue)
.buildSessionFactory();
try {
persistEntity();

test.run();
} finally {
inTransaction(session -> {
var entity = session.find(TestEntity.class, ENTITY_ID);
if (entity != null) {
session.remove(entity);
}
});
if (SESSION_FACTORY != null) {
SESSION_FACTORY.close();
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package tech.ydb.hibernate.lock;

import jakarta.persistence.*;
import lombok.Data;

@Entity
@Data
@Table(name = "lock_test_entity")
public class TestEntity {
@Id
private Long id;

@Column(name = "string_value", nullable = false)
private String stringValue;

@Version
@Column(name = "version")
private int version;
}