Skip to content

Поддержка YDB в Jimmer ORM#215

Open
Rinoyt wants to merge 102 commits into
ydb-platform:mainfrom
Rinoyt:main
Open

Поддержка YDB в Jimmer ORM#215
Rinoyt wants to merge 102 commits into
ydb-platform:mainfrom
Rinoyt:main

Conversation

@Rinoyt
Copy link
Copy Markdown

@Rinoyt Rinoyt commented May 4, 2026

Добавлены диалект и вспомогательные классы для поддержки YDB в Jimmer ORM.

Rinoyt and others added 30 commits November 20, 2025 15:15
1. Started using YdbHelperExtension
2. fixed errors in the SQL file and added a check for dropping tables from the SQL file
The test now successfully sends a "select" request to the DB. Next, there needs be a checker for the result and sql script generated
Java version 17 is the minimum Java version to support all Jimmer features.
auto-increment has been replaced by Uuid generated by Java
Changed mapping of Java classes to SQL types to be the same as in the YDB JDBC
connection.rollback();
if (i == maxRetries - 1 || !isRetryable(ex)) {
throw new RuntimeException(ex);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Severity: Major
Confidence: High

If block.apply(connection) throws a RuntimeException (or any unchecked exception), the catch (SQLException) block is skipped, so connection.rollback() is never called. The connection is then closed by try-with-resources, but whether the JDBC driver auto-rolls-back on close() is driver-specific and not guaranteed by the JDBC spec.

This can lead to partially committed data if the YDB driver doesn't auto-rollback on close.

Suggested fix: add a generic catch (Exception ex) block (or a finally that rolls back if not committed), for example:

try {
    connection.setAutoCommit(false);
    R result = block.apply(connection);
    connection.commit();
    return result;
} catch (Exception ex) {
    connection.rollback();
    if (ex instanceof SQLException sqlEx && (i < maxRetries - 1) && isRetryable(sqlEx)) {
        // continue retry loop
    } else {
        throw (ex instanceof RuntimeException re) ? re : new RuntimeException(ex);
    }
}

}

@Override
public <R> R executeTransaction(Propagation propagation, Function<Connection, R> block) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Severity: Major
Confidence: Medium

The propagation parameter is accepted but completely ignored. Jimmer's TxConnectionManager.executeTransaction contract expects the implementation to respect propagation semantics (REQUIRED, REQUIRES_NEW, NESTED, etc.).

Currently, every call creates a brand-new independent transaction, which is equivalent to REQUIRES_NEW. If a caller passes REQUIRED expecting to join an existing transaction, this implementation silently creates a separate one, which can break transactional consistency.

Suggested fix: either implement propagation support (e.g., using a ThreadLocal to track the current connection/transaction) or document that this class only supports REQUIRES_NEW and throw UnsupportedOperationException for other modes.

) {
sqlClient().getConnectionManager().execute(con -> {
try {
try (PreparedStatement stmt = con.prepareStatement(INSERT_WITH_UUID)) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Severity: Major
Confidence: Medium

The batchSize parameter is validated in the constructor (batchSize < 1 throws) and passed to the superclass, but the save() method here batches all keys in a single executeBatch() call regardless of batchSize. Since both delete() and deleteAll() are overridden, the superclass's chunking logic (which would split work into batchSize-sized batches) is bypassed.

For a large collection of cache keys, this could cause memory pressure or exceed YDB's batch statement limits.

Suggested fix: chunk the keys collection into batchSize-sized groups and call executeBatch() for each chunk:

int count = 0;
for (Object key : keys) {
    // ... setString calls ...
    stmt.addBatch();
    if (++count % batchSize == 0) {
        stmt.executeBatch();
    }
}
if (count % batchSize != 0) {
    stmt.executeBatch();
}

(where batchSize needs to be stored as an instance field)

@@ -0,0 +1,17 @@
docker run \
--restart=always \
-d --rm \
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Severity: Major
Confidence: High

--restart=always and --rm are mutually exclusive Docker flags. On Docker 18.06+ this produces an error:

Conflicting options: --restart and --rm

The script will fail to start the container.

Suggested fix: remove either --rm (if you want the container to persist and auto-restart) or --restart=always (if you want a disposable container that is removed on stop).

protected static YqlClient getIsolationClient() {
DataSource dataSource = new DriverManagerDataSource(getJdbcURL());
return new YqlClient(
(JSqlClientImplementor) JSqlClient.newBuilder()
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Severity: Minor
Confidence: High

getIsolationClient() builds a JSqlClient without calling .setDialect(new YdbDialect()). This means the client returned to tests (used in AbstractTransactionTest, KeysetTest) uses Jimmer's default dialect rather than the YDB dialect.

Currently the tests only execute simple queries where the default dialect happens to produce compatible SQL, but this will silently produce incorrect SQL for any YDB-specific operations (UPSERT, type mappings, etc.).

Suggested fix:

return new YqlClient(
    (JSqlClientImplementor) JSqlClient.newBuilder()
        .setDialect(new YdbDialect())
        .setConnectionManager(new YdbTxConnectionManager(dataSource))
        .setExecutor(executor)
        .build());

public final class YdbClassMapping {
private YdbClassMapping() {}

public static final Map<Class<?>, String> classToYdbType = new HashMap<>();
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Severity: Minor
Confidence: Medium

classToYdbType and classToJdbcType are declared as public static final but the concrete type is a mutable HashMap. Any code that has access to these fields can call put(), remove(), or clear() on them, corrupting the shared mapping for all callers.

Suggested fix: wrap them with Collections.unmodifiableMap() after the static initializer block populates a temporary mutable map, or use Map.ofEntries(...) for an immutable map.

Comment thread jimmer-dialect/pom.xml
<scope>test</scope>
</dependency>
<!-- Source: https://mvnrepository.com/artifact/org.liquibase/liquibase-core -->
<dependency>
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Severity: Minor
Confidence: Medium

liquibase-core is declared at compile scope but is not imported or used anywhere in the main source code (src/main/java). This adds a significant transitive dependency tree (~3 MB+) to the artifact for no functional benefit.

If Liquibase is intended for future use, consider changing the scope to provided or test. If it's not needed, remove it.

@robot-vibe-db
Copy link
Copy Markdown

robot-vibe-db Bot commented May 7, 2026

AI Review Summary

Verdict: ✅ No critical issues found

Critical issues

No critical issues found.

Other findings

  • Major | High: RetryConnectionManager.executeTransactionRuntimeException from the user block does not trigger explicit rollback; relies on driver-specific close behavior — RetryConnectionManager.java:38
  • Major | Medium: RetryConnectionManager.executeTransactionPropagation parameter is accepted but completely ignored — RetryConnectionManager.java:30
  • Major | Medium: UuidTransactionCacheOperator.save()batchSize constructor parameter is validated but never enforced; all keys are batched in a single executeBatch() call — UuidTransactionCacheOperator.java:107
  • Major | High: install.sh--restart=always and --rm flags are mutually exclusive in Docker and will cause the script to fail — install.sh:3
  • Minor | High: AbstractTest.getIsolationClient()YdbDialect is not set on the builder; test client uses the default Jimmer dialect instead of YDB — AbstractTest.java:59
  • Minor | Medium: YdbClassMappingclassToYdbType and classToJdbcType are mutable HashMap exposed as public static final; any caller can mutate them — YdbClassMapping.java:24
  • Minor | Medium: pom.xmlliquibase-core is at compile scope but not used anywhere in main sources — pom.xml:74

This review was generated automatically. Critical issues require attention; other findings are advisory.
If this comment was useful, please give it a 👍 — it helps us improve the review bot.

Rinoyt and others added 19 commits May 11, 2026 01:58
Removed "--rm" to mirror the docker-compose.yml file
… the con.setSavepoint() problem

YDB JDBC driver doesn't support con.setSavepoint(). That's why isTransactionAbortedByError() in dialect was removed (it now returns false and doen't trigger setSavepoint())
Previously, only select calls were using the variable batchSize. Delete operators are now separated into groups to prevent memory pressure and to not exceed YDB's batch statement limits.
Fixed issues mentioned by the bot review and fixed versioning
Fixed retry logic and added chaos tests
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants