, Integer> classToJdbcTypeBuilder = new HashMap<>();
+
+ static {
+ add("Bool", YdbJdbcTypes.BOOL, boolean.class, Boolean.class);
+
+ add("Int8", YdbJdbcTypes.INT8, byte.class, Byte.class);
+ add("Int16", YdbJdbcTypes.INT16, short.class, Short.class);
+ add("Int32", YdbJdbcTypes.INT32, int.class, Integer.class, LocalTime.class, Time.class);
+ add("Int64", YdbJdbcTypes.INT64, long.class, Long.class, BigInteger.class);
+ add("Decimal(22, 9)", YdbJdbcTypes.DECIMAL_22_9, BigDecimal.class);
+
+ add("Float", YdbJdbcTypes.FLOAT, float.class, Float.class);
+ add("Double", YdbJdbcTypes.DOUBLE, double.class, Double.class);
+
+ add("Utf8", YdbJdbcTypes.TEXT, String.class);
+ add("String", YdbJdbcTypes.BYTES, byte[].class);
+
+ add("Uuid", YdbJdbcTypes.UUID, UUID.class);
+
+ add("Date32", YdbJdbcTypes.DATE32, Date.class, LocalDate.class);
+ add("Datetime64", YdbJdbcTypes.DATETIME64, LocalDateTime.class);
+ add("Timestamp64", YdbJdbcTypes.TIMESTAMP64, java.util.Date.class, Timestamp.class, Instant.class);
+ add("Interval64", YdbJdbcTypes.INTERVAL64, Duration.class);
+
+ classToYdbType = Map.copyOf(classToYdbTypeBuilder);
+ classToJdbcType = Map.copyOf(classToJdbcTypeBuilder);
+ }
+
+ private static void add(String ydbType, int jdbcType, Class>... classes) {
+ for (Class> clazz : classes) {
+ classToYdbTypeBuilder.put(clazz, ydbType);
+ classToJdbcTypeBuilder.put(clazz, jdbcType);
+ }
+ }
+}
diff --git a/jimmer-dialect/src/main/java/ydb/jimmer/dialect/constant/YdbConst.java b/jimmer-dialect/src/main/java/ydb/jimmer/dialect/constant/YdbConst.java
new file mode 100644
index 00000000..00774de8
--- /dev/null
+++ b/jimmer-dialect/src/main/java/ydb/jimmer/dialect/constant/YdbConst.java
@@ -0,0 +1,16 @@
+package ydb.jimmer.dialect.constant;
+
+/**
+ * Contains constants for the SQL types used in the YDB driver.
+ * The values are taken from /tech/ydb/jdbc/YdbConst.java in the YDB driver.
+ */
+public final class YdbConst {
+ private YdbConst() {}
+
+ public static final int SQL_KIND_PRIMITIVE = 10000;
+ public static final int SQL_KIND_DECIMAL = 1 << 14; // 16384
+
+ public static final int ONLINE_CONSISTENT_READ_ONLY = 16;
+ public static final int ONLINE_INCONSISTENT_READ_ONLY = ONLINE_CONSISTENT_READ_ONLY + 1;
+ public static final int STALE_READ_ONLY = 32;
+}
diff --git a/jimmer-dialect/src/main/java/ydb/jimmer/dialect/constant/YdbJdbcTypes.java b/jimmer-dialect/src/main/java/ydb/jimmer/dialect/constant/YdbJdbcTypes.java
new file mode 100644
index 00000000..bb593505
--- /dev/null
+++ b/jimmer-dialect/src/main/java/ydb/jimmer/dialect/constant/YdbJdbcTypes.java
@@ -0,0 +1,140 @@
+package ydb.jimmer.dialect.constant;
+
+/**
+ * Contains constants of the SQL types used in the YDB driver.
+ * The values are taken from /tech/ydb/jdbc/common/YdbTypes.java in the YDB driver.
+ */
+public final class YdbJdbcTypes {
+ private YdbJdbcTypes() {}
+
+ /** Boolean value. */
+ public static final int BOOL = YdbConst.SQL_KIND_PRIMITIVE;
+
+ /** A signed integer. Acceptable values: from -2^7 to 2^7–1. Not supported for table columns */
+ public static final int INT8 = YdbConst.SQL_KIND_PRIMITIVE + 1;
+
+ /** An unsigned integer. Acceptable values: from 0 to 2^8–1. */
+ public static final int UINT8 = YdbConst.SQL_KIND_PRIMITIVE + 2;
+
+ /** A signed integer. Acceptable values: from –2^15 to 2^15–1. Not supported for table columns */
+ public static final int INT16 = YdbConst.SQL_KIND_PRIMITIVE + 3;
+
+ /** An unsigned integer. Acceptable values: from 0 to 2^16–1. Not supported for table columns */
+ public static final int UINT16 = YdbConst.SQL_KIND_PRIMITIVE + 4;
+
+ /** A signed integer. Acceptable values: from –2^31 to 2^31–1. */
+ public static final int INT32 = YdbConst.SQL_KIND_PRIMITIVE + 5;
+
+ /** An unsigned integer. Acceptable values: from 0 to 2^32–1. */
+ public static final int UINT32 = YdbConst.SQL_KIND_PRIMITIVE + 6;
+
+ /** A signed integer. Acceptable values: from –2^63 to 2^63–1. */
+ public static final int INT64 = YdbConst.SQL_KIND_PRIMITIVE + 7;
+
+ /** An unsigned integer. Acceptable values: from 0 to 2^64–1. */
+ public static final int UINT64 = YdbConst.SQL_KIND_PRIMITIVE + 8;
+
+ /** A real number with variable precision, 4 bytes in size. Can't be used in the primary key */
+ public static final int FLOAT = YdbConst.SQL_KIND_PRIMITIVE + 9;
+
+ /** A real number with variable precision, 8 bytes in size. Can't be used in the primary key */
+ public static final int DOUBLE = YdbConst.SQL_KIND_PRIMITIVE + 10;
+
+ /** A binary data, synonym for YDB type String */
+ public static final int BYTES = YdbConst.SQL_KIND_PRIMITIVE + 11;
+
+ /** Text encoded in UTF-8, synonym for YDB type Utf8 */
+ public static final int TEXT = YdbConst.SQL_KIND_PRIMITIVE + 12;
+
+ /** YSON in a textual or binary representation. Doesn't support matching, can't be used in the primary key */
+ public static final int YSON = YdbConst.SQL_KIND_PRIMITIVE + 13;
+
+ /** JSON represented as text. Doesn't support matching, can't be used in the primary key */
+ public static final int JSON = YdbConst.SQL_KIND_PRIMITIVE + 14;
+
+ /** Universally unique identifier UUID. Not supported for table columns */
+ public static final int UUID = YdbConst.SQL_KIND_PRIMITIVE + 15;
+
+ /**
+ * A moment in time corresponding to midnight in UTC, precision to the day.
+ *
+ * Possible values:
+ * from 00:00 01.01.1970 to 00:00 01.01.2106
+ */
+ public static final int DATE = YdbConst.SQL_KIND_PRIMITIVE + 16;
+
+ /**
+ * A moment in time in UTC, precision to the second
+ *
+ * Possible values:
+ * from 00:00 01.01.1970 to 00:00 01.01.2106
+ */
+ public static final int DATETIME = YdbConst.SQL_KIND_PRIMITIVE + 17;
+
+ /**
+ * A moment in time in UTC, precision to the microsecond
+ *
+ * Possible values:
+ * from 00:00 01.01.1970 to 00:00 01.01.2106
+ */
+ public static final int TIMESTAMP = YdbConst.SQL_KIND_PRIMITIVE + 18;
+
+ /**
+ * Time interval, precision to the microsecond
+ *
+ * Possible values:
+ * from -136 years to +136 years
+ */
+ public static final int INTERVAL = YdbConst.SQL_KIND_PRIMITIVE + 19;
+
+ /** Date with time zone label, precision to the day */
+ public static final int TZ_DATE = YdbConst.SQL_KIND_PRIMITIVE + 20;
+
+ /** Date/time with time zone label, precision to the second */
+ public static final int TZ_DATETIME = YdbConst.SQL_KIND_PRIMITIVE + 21;
+
+ /** Date/time with time zone label, precision to the microsecond */
+ public static final int TZ_TIMESTAMP = YdbConst.SQL_KIND_PRIMITIVE + 22;
+
+ /** JSON in an indexed binary representation. Doesn't support matching, can't be used in the primary key */
+ public static final int JSON_DOCUMENT = YdbConst.SQL_KIND_PRIMITIVE + 23;
+
+ /**
+ * A moment in time corresponding to midnight in UTC, precision to the day.
+ *
+ * Possible values:
+ * from 00:00 01.01.144169 BC to 00:00 01.01.148107 AD
+ */
+ public static final int DATE32 = YdbConst.SQL_KIND_PRIMITIVE + 25;
+
+ /**
+ * A moment in time in UTC, precision to the second
+ *
+ * Possible values:
+ * from 00:00 01.01.144169 BC to 00:00 01.01.148107 AD
+ */
+ public static final int DATETIME64 = YdbConst.SQL_KIND_PRIMITIVE + 26;
+
+ /**
+ * A moment in time in UTC, precision to the microsecond
+ *
+ * Possible values:
+ * from 00:00 01.01.144169 BC to 00:00 01.01.148107 AD
+ */
+ public static final int TIMESTAMP64 = YdbConst.SQL_KIND_PRIMITIVE + 27;
+
+ /**
+ * Time interval, precision to the microsecond
+ *
+ * Possible values:
+ * from -292277 years to +292277 years
+ */
+ public static final int INTERVAL64 = YdbConst.SQL_KIND_PRIMITIVE + 28;
+
+ /**
+ * Real number with fixed precision
+ *
+ * Default value
+ */
+ public static final int DECIMAL_22_9 = YdbConst.SQL_KIND_DECIMAL + (22 << 6) + 9;
+}
diff --git a/jimmer-dialect/src/main/java/ydb/jimmer/dialect/scalar/DumbYdbScalarProvider.java b/jimmer-dialect/src/main/java/ydb/jimmer/dialect/scalar/DumbYdbScalarProvider.java
new file mode 100644
index 00000000..a814f8b8
--- /dev/null
+++ b/jimmer-dialect/src/main/java/ydb/jimmer/dialect/scalar/DumbYdbScalarProvider.java
@@ -0,0 +1,53 @@
+package ydb.jimmer.dialect.scalar;
+
+import org.babyfish.jimmer.sql.runtime.AbstractScalarProvider;
+import org.babyfish.jimmer.sql.runtime.Reader;
+import org.jetbrains.annotations.NotNull;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+/**
+ * Passes the given Java type (scalar)
+ * as a new Java type (SQL) without any changes.
+ * This class is used when the given Java type is supported by the JDBC driver,
+ * but is not supported by Jimmer.
+ *
+ * @param the class that needs to pass to the driver without any changes
+ */
+public abstract class DumbYdbScalarProvider extends AbstractScalarProvider {
+ private final Class clazz;
+
+ public DumbYdbScalarProvider(Class clazz) {
+ super(clazz, clazz);
+
+ this.clazz = clazz;
+ }
+
+ @Override
+ public T toScalar(@NotNull T sqlValue) {
+ return sqlValue;
+ }
+
+ @Override
+ public T toSql(@NotNull T scalarValue) {
+ return scalarValue;
+ }
+
+ @Override
+ public Reader reader() {
+ return new DumbReader();
+ }
+
+ private class DumbReader implements Reader {
+ @Override
+ public T read(ResultSet rs, Context ctx) throws SQLException {
+ return rs.getObject(ctx.col(), clazz);
+ }
+
+ @Override
+ public void skip(Context ctx) {
+ ctx.col();
+ }
+ }
+}
diff --git a/jimmer-dialect/src/main/java/ydb/jimmer/dialect/scalar/DurationProvider.java b/jimmer-dialect/src/main/java/ydb/jimmer/dialect/scalar/DurationProvider.java
new file mode 100644
index 00000000..b647b3a3
--- /dev/null
+++ b/jimmer-dialect/src/main/java/ydb/jimmer/dialect/scalar/DurationProvider.java
@@ -0,0 +1,9 @@
+package ydb.jimmer.dialect.scalar;
+
+import java.time.Duration;
+
+public class DurationProvider extends DumbYdbScalarProvider {
+ public DurationProvider() {
+ super(Duration.class);
+ }
+}
diff --git a/jimmer-dialect/src/main/java/ydb/jimmer/dialect/transaction/TransactionContext.java b/jimmer-dialect/src/main/java/ydb/jimmer/dialect/transaction/TransactionContext.java
new file mode 100644
index 00000000..2e14c8da
--- /dev/null
+++ b/jimmer-dialect/src/main/java/ydb/jimmer/dialect/transaction/TransactionContext.java
@@ -0,0 +1,31 @@
+package ydb.jimmer.dialect.transaction;
+
+/**
+ * This class is used to pass transaction setting
+ * to the ConnectionManager.
+ */
+public class TransactionContext {
+ private static final ThreadLocal LOCAL_CONTEXT = new ThreadLocal<>();
+
+ public static void setSettings(int isolationLevel, boolean readOnly) {
+ LOCAL_CONTEXT.set(new TransactionSettings(isolationLevel, readOnly));
+ }
+
+ public static TransactionSettings getSettings() {
+ return LOCAL_CONTEXT.get();
+ }
+
+ public static void clear() {
+ LOCAL_CONTEXT.remove();
+ }
+
+ public static class TransactionSettings {
+ final int isolationLevel;
+ final boolean readOnly;
+
+ TransactionSettings(int isolationLevel, boolean readOnly) {
+ this.isolationLevel = isolationLevel;
+ this.readOnly = readOnly;
+ }
+ }
+}
diff --git a/jimmer-dialect/src/main/java/ydb/jimmer/dialect/transaction/YdbTxConnectionManager.java b/jimmer-dialect/src/main/java/ydb/jimmer/dialect/transaction/YdbTxConnectionManager.java
new file mode 100644
index 00000000..fd89ae2b
--- /dev/null
+++ b/jimmer-dialect/src/main/java/ydb/jimmer/dialect/transaction/YdbTxConnectionManager.java
@@ -0,0 +1,243 @@
+package ydb.jimmer.dialect.transaction;
+
+import org.babyfish.jimmer.sql.exception.ExecutionException;
+import org.babyfish.jimmer.sql.transaction.Propagation;
+import org.babyfish.jimmer.sql.transaction.TxConnectionManager;
+import org.jetbrains.annotations.Nullable;
+
+import javax.sql.DataSource;
+import java.sql.Connection;
+import java.sql.SQLException;
+import java.sql.SQLTimeoutException;
+import java.util.function.Function;
+
+/**
+ * Provides propagation, isolation level, read only mode
+ * and retry from abort/timeout for transactions.
+ */
+public class YdbTxConnectionManager implements TxConnectionManager {
+ private final DataSource dataSource;
+ private final ThreadLocal scopeLocal = new ThreadLocal<>();
+
+ private final int BACKOFF_MULTIPLIER = 2;
+
+ public YdbTxConnectionManager(DataSource dataSource) {
+ this.dataSource = dataSource;
+ }
+
+ @Override
+ public final R execute(@Nullable Connection con, Function block) {
+ if (con != null) {
+ return block.apply(con);
+ }
+ return execute(block);
+ }
+
+ @Override
+ public final R execute(Function block) {
+ return executeTransaction(Propagation.SUPPORTS, block);
+ }
+
+ @Override
+ public final R executeTransaction(Function block) {
+ return executeTransaction(Propagation.REQUIRED, block);
+ }
+
+ public final R executeTransaction(int maxAttempts, long retryDelayMs, Function block) {
+ return executeTransaction(maxAttempts, retryDelayMs, Propagation.REQUIRES_NEW, block);
+ }
+
+ @Override
+ public final R executeTransaction(Propagation propagation, Function block) {
+ return executeTransaction(1, 0, propagation, block);
+ }
+
+ public final R executeTransaction(
+ int maxAttempts,
+ long retryDelayMs,
+ Propagation propagation,
+ Function block
+ ) {
+ for (int i = 0; i < maxAttempts; i++) {
+ try {
+ Scope parent = scopeLocal.get();
+ Scope scope = createScope(parent, propagation);
+ scopeLocal.set(scope);
+ try {
+ R result;
+ try {
+ result = execute(scope.con, block);
+ } catch (RuntimeException | Error ex) {
+ scope.terminate(true);
+ throw ex;
+ }
+ scope.terminate(false);
+ return result;
+ } finally {
+ if (parent != null) {
+ scopeLocal.set(parent);
+ } else {
+ scopeLocal.remove();
+ }
+ }
+ } catch (SQLException | RuntimeException ex) {
+ RuntimeException e = new ExecutionException("JDBC error raised: " + ex.getMessage(), ex);
+ if (ex instanceof RuntimeException rex) {
+ e = rex;
+ }
+
+ if (i == maxAttempts - 1 || !isRetryable(e.getCause())) {
+ throw e;
+ }
+ }
+
+ try {
+ Thread.sleep(retryDelayMs);
+ retryDelayMs *= BACKOFF_MULTIPLIER;
+ } catch (InterruptedException ex) {
+ Thread.currentThread().interrupt();
+ throw new RuntimeException(ex);
+ }
+ }
+
+ throw new RuntimeException("Max attempts exceeded");
+ }
+
+ private boolean isRetryable(Throwable ex) {
+ return ex instanceof SQLTimeoutException ||
+ (ex instanceof SQLException && isRetryableSqlState((SQLException) ex));
+ }
+
+ private boolean isRetryableSqlState(SQLException ex) {
+ String sqlState = ex.getSQLState();
+ return sqlState != null && (
+ sqlState.startsWith("40") || // Transaction rollback
+ sqlState.startsWith("08") // Connection exception
+ );
+ }
+
+ protected Connection openConnection() throws SQLException {
+ return dataSource.getConnection();
+ }
+
+ protected void closeConnection(Connection con) throws SQLException {
+ con.close();
+ }
+
+ protected void startTransaction(Connection con) throws SQLException {
+ con.setAutoCommit(false);
+
+ TransactionContext.TransactionSettings settings = TransactionContext.getSettings();
+ if (settings != null) {
+ if (settings.isolationLevel != Connection.TRANSACTION_NONE) {
+ con.setTransactionIsolation(settings.isolationLevel);
+ }
+ con.setReadOnly(settings.readOnly);
+ }
+ }
+
+ protected void commitTransaction(Connection con) throws SQLException {
+ con.commit();
+ }
+
+ protected void rollbackTransaction(Connection con) throws SQLException {
+ con.rollback();
+ }
+
+ protected void abortTransaction(Connection con) throws SQLException {
+ con.setAutoCommit(true);
+ }
+
+ private Scope createScope(Scope parent, Propagation propagation) throws SQLException {
+ return switch (propagation) {
+ case REQUIRES_NEW -> new Scope(parent, false, true);
+ case SUPPORTS -> new Scope(parent, true, parent != null && parent.withTransaction);
+ case NOT_SUPPORTED -> new Scope(parent, true, false);
+ case MANDATORY -> {
+ if (parent == null || !parent.withTransaction) {
+ throw new ExecutionException(
+ "The transaction propagation is \"MANDATORY\" but there is no transaction context"
+ );
+ }
+ yield new Scope(parent, true, true);
+ }
+ case NEVER -> {
+ if (parent != null && parent.withTransaction) {
+ throw new ExecutionException(
+ "The transaction propagation is \"NEVER\" but there is already a transaction context"
+ );
+ }
+ yield new Scope(parent, true, false);
+ }
+ default -> // REQUIRED
+ new Scope(parent, true, true);
+ };
+ }
+
+ private class Scope {
+ private final Connection con;
+
+ private final boolean withTransaction;
+ private final boolean connectionOwner;
+ private final boolean transactionOwner;
+
+ Scope(Scope parent, boolean borrowConnection, boolean withTransaction) throws SQLException {
+ if (parent != null && parent.withTransaction && !withTransaction) {
+ borrowConnection = false;
+ }
+
+ Connection con;
+ if (parent != null && borrowConnection) {
+ con = parent.con;
+ this.connectionOwner = false;
+ } else {
+ con = openConnection();
+ this.connectionOwner = true;
+ }
+
+ this.withTransaction = withTransaction;
+ if (!withTransaction) {
+ transactionOwner = false;
+ } else if (connectionOwner) {
+ transactionOwner = true;
+ } else {
+ transactionOwner = !parent.withTransaction;
+ }
+
+ if (transactionOwner) {
+ try {
+ startTransaction(con);
+ } catch (SQLException | RuntimeException | Error ex) {
+ closeConnection(con);
+ this.con = null;
+ throw ex;
+ }
+ }
+ this.con = con;
+ }
+
+ void terminate(boolean error) throws SQLException {
+ Connection con = this.con;
+ if (con == null) {
+ return;
+ }
+
+ try {
+ if (transactionOwner) {
+ if (error) {
+ rollbackTransaction(con);
+ } else {
+ commitTransaction(con);
+ }
+ if (!connectionOwner) {
+ abortTransaction(con);
+ }
+ }
+ } finally {
+ if (connectionOwner) {
+ closeConnection(con);
+ }
+ }
+ }
+ }
+}
diff --git a/jimmer-dialect/src/main/java/ydb/jimmer/dialect/transaction/YqlClient.java b/jimmer-dialect/src/main/java/ydb/jimmer/dialect/transaction/YqlClient.java
new file mode 100644
index 00000000..1f379a16
--- /dev/null
+++ b/jimmer-dialect/src/main/java/ydb/jimmer/dialect/transaction/YqlClient.java
@@ -0,0 +1,66 @@
+package ydb.jimmer.dialect.transaction;
+
+import org.babyfish.jimmer.sql.JSqlClient;
+import org.babyfish.jimmer.sql.di.AbstractJSqlClientDelegate;
+import org.babyfish.jimmer.sql.runtime.JSqlClientImplementor;
+import ydb.jimmer.dialect.constant.YdbConst;
+
+import java.sql.Connection;
+import java.util.function.Supplier;
+
+/**
+ * Provides methods for setting transaction isolation level
+ * to the {@link JSqlClient}.
+ */
+public class YqlClient extends AbstractJSqlClientDelegate {
+ private final JSqlClientImplementor delegate;
+
+ public YqlClient(JSqlClientImplementor delegate) {
+ this.delegate = delegate;
+ }
+
+ @Override
+ protected JSqlClientImplementor sqlClient() {
+ return delegate;
+ }
+
+ public R transaction(int maxAttempts, long retryDelayMs, Supplier block) {
+ if (getConnectionManager() instanceof YdbTxConnectionManager ydbCM) {
+ return ydbCM.executeTransaction(maxAttempts, retryDelayMs, con -> block.get());
+ }
+
+ throw new IllegalStateException(
+ "The connection manager does not support retries for transactions. " +
+ "Use YdbTxConnectionManager."
+ );
+ }
+
+ public R withIsolation(int isolationLevel, boolean readOnly, Supplier block) {
+ try {
+ TransactionContext.setSettings(isolationLevel, readOnly);
+ return transaction(block);
+ } finally {
+ TransactionContext.clear();
+ }
+ }
+
+ public R serializableReadWrite(Supplier block) {
+ return withIsolation(Connection.TRANSACTION_SERIALIZABLE, false, block);
+ }
+
+ public R snapshotReadOnly(Supplier block) {
+ return withIsolation(Connection.TRANSACTION_SERIALIZABLE, true, block);
+ }
+
+ public R staleReadOnly(Supplier block) {
+ return withIsolation(YdbConst.STALE_READ_ONLY, true, block);
+ }
+
+ public R onlineConsistentReadOnly(Supplier block) {
+ return withIsolation(YdbConst.ONLINE_CONSISTENT_READ_ONLY, true, block);
+ }
+
+ public R onlineInconsistentReadOnly(Supplier block) {
+ return withIsolation(YdbConst.ONLINE_INCONSISTENT_READ_ONLY, true, block);
+ }
+}
diff --git a/jimmer-dialect/src/test/java/ydb/jimmer/dialect/AbstractInsertTest.java b/jimmer-dialect/src/test/java/ydb/jimmer/dialect/AbstractInsertTest.java
new file mode 100644
index 00000000..bbae2c97
--- /dev/null
+++ b/jimmer-dialect/src/test/java/ydb/jimmer/dialect/AbstractInsertTest.java
@@ -0,0 +1,31 @@
+package ydb.jimmer.dialect;
+
+import org.babyfish.jimmer.sql.ast.Executable;
+import org.babyfish.jimmer.sql.ast.mutation.MutationResult;
+import org.junit.jupiter.api.Assertions;
+
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.SQLException;
+import java.util.function.Consumer;
+
+public abstract class AbstractInsertTest extends AbstractTest {
+ protected void executeAndExpect(Executable extends MutationResult> query, Consumer block) {
+ MutationResult result = null;
+ Throwable throwable = null;
+ try (Connection connection = DriverManager.getConnection(getJdbcURL())) {
+ connection.setAutoCommit(false);
+ try {
+ result = query.execute(connection);
+ } catch (Throwable ex) {
+ throwable = ex;
+ } finally {
+ connection.rollback();
+ }
+ } catch (SQLException e) {
+ Assertions.fail("Database threw an exception: " + e.getMessage());
+ }
+
+ block.accept(new QueryTestContext(executor.getLogs(), result, throwable));
+ }
+}
diff --git a/jimmer-dialect/src/test/java/ydb/jimmer/dialect/AbstractSelectTest.java b/jimmer-dialect/src/test/java/ydb/jimmer/dialect/AbstractSelectTest.java
new file mode 100644
index 00000000..849a0c33
--- /dev/null
+++ b/jimmer-dialect/src/test/java/ydb/jimmer/dialect/AbstractSelectTest.java
@@ -0,0 +1,61 @@
+package ydb.jimmer.dialect;
+
+import org.babyfish.jimmer.sql.ast.Executable;
+import org.junit.jupiter.api.Assertions;
+
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.SQLException;
+import java.util.List;
+import java.util.function.Consumer;
+
+public abstract class AbstractSelectTest extends AbstractTest {
+ protected static void executeAndExpect(Executable extends List> query, Consumer block) {
+ List rows = null;
+ try (Connection connection = DriverManager.getConnection(getJdbcURL())) {
+ connection.setAutoCommit(false);
+ try {
+ rows = query.execute(connection);
+ } finally {
+ connection.rollback();
+ }
+ } catch (SQLException e) {
+ Assertions.fail("Database threw an exception: " + e.getMessage());
+ }
+
+ block.accept(new QueryTestContext(executor.getLogs(), rows));
+ }
+
+ protected static void insert(String tableName, String... values) {
+ if (values.length == 0) {
+ return;
+ }
+ StringBuilder sql = new StringBuilder("INSERT INTO " + tableName + " (id, value) VALUES ");
+ for (int i = 0; i < values.length; i++) {
+ if (i > 0) {
+ sql.append(", ");
+ }
+ sql.append("(").append(i).append(", ").append(values[i]).append(")");
+ }
+ executeSql(sql.toString());
+ }
+
+ protected static String buildJsonResponse(String[] expectedValues) {
+ return buildJsonResponse(0, expectedValues);
+ }
+
+ protected static String buildJsonResponse(int startingId, String[] expectedValues) {
+ StringBuilder json = new StringBuilder("[");
+ for (int i = 0; i < expectedValues.length; i++) {
+ if (json.length() != 1) {
+ json.append(",");
+ }
+ json.append("{");
+ json.append("\"id\":").append(startingId + i).append(",\"value\":").append(expectedValues[i]);
+ json.append("}");
+ }
+ json.append("]");
+
+ return json.toString();
+ }
+}
diff --git a/jimmer-dialect/src/test/java/ydb/jimmer/dialect/AbstractTest.java b/jimmer-dialect/src/test/java/ydb/jimmer/dialect/AbstractTest.java
new file mode 100644
index 00000000..4a3737c8
--- /dev/null
+++ b/jimmer-dialect/src/test/java/ydb/jimmer/dialect/AbstractTest.java
@@ -0,0 +1,139 @@
+package ydb.jimmer.dialect;
+
+import org.babyfish.jimmer.sql.JSqlClient;
+import org.babyfish.jimmer.sql.runtime.DefaultExecutor;
+import org.babyfish.jimmer.sql.runtime.JSqlClientImplementor;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.extension.RegisterExtension;
+import org.springframework.core.io.UrlResource;
+import org.springframework.core.io.support.EncodedResource;
+import org.springframework.jdbc.datasource.DriverManagerDataSource;
+import org.springframework.jdbc.datasource.init.ScriptException;
+import org.springframework.jdbc.datasource.init.ScriptUtils;
+import tech.ydb.test.junit5.YdbHelperExtension;
+import ydb.jimmer.dialect.scalar.DurationProvider;
+import ydb.jimmer.dialect.sqlMonitor.YdbExecutorMonitor;
+import ydb.jimmer.dialect.transaction.YqlClient;
+import ydb.jimmer.dialect.transaction.YdbTxConnectionManager;
+
+import javax.sql.DataSource;
+import java.net.URL;
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.SQLException;
+import java.sql.Statement;
+
+public abstract class AbstractTest {
+ @RegisterExtension
+ private static final YdbHelperExtension ydb = new YdbHelperExtension();
+
+ protected static final YdbExecutorMonitor executor = new YdbExecutorMonitor(
+ new YdbExecutor(DefaultExecutor.INSTANCE)
+ );
+ private static final JSqlClient yqlClient;
+ private static final JSqlClient yqlClientForBatch;
+
+ static {
+ yqlClient = JSqlClient.newBuilder()
+ .setDialect(new YdbDialect())
+ .setExecutor(executor)
+ .addScalarProvider(new DurationProvider())
+ .build();
+
+ yqlClientForBatch = JSqlClient.newBuilder()
+ .setDialect(new YdbDialect())
+ .setExecutor(executor)
+ .setExplicitBatchEnabled(true)
+ .setDumbBatchAcceptable(true)
+ .build();
+ }
+
+ protected static JSqlClient getYqlClient() {
+ return yqlClient;
+ }
+
+ protected static JSqlClient getYqlClientForBatch() {
+ return yqlClientForBatch;
+ }
+
+ protected static YqlClient getIsolationClient() {
+ DataSource dataSource = new DriverManagerDataSource(getJdbcURL());
+ return new YqlClient(
+ (JSqlClientImplementor) JSqlClient.newBuilder()
+ .setConnectionManager(new YdbTxConnectionManager(dataSource))
+ .setDialect(new YdbDialect())
+ .setExecutor(executor)
+ .build());
+ }
+
+ protected void initDatabase() {
+ try (Connection connection = DriverManager.getConnection(getJdbcURL())) {
+ URL dropTablesUrl = AbstractTest.class.getClassLoader().getResource("database-drop-tables-ydb.sql");
+ if (dropTablesUrl == null) {
+ throw new IllegalStateException("Cannot load 'database-drop-tables-ydb.sql'");
+ }
+ try {
+ executeYqlScript(connection, dropTablesUrl);
+ } catch (ScriptException e) {
+ //
+ }
+
+ URL url = AbstractTest.class.getClassLoader().getResource("database-ydb.sql");
+ if (url == null) {
+ throw new IllegalStateException("Cannot load 'database-ydb.sql'");
+ }
+ executeYqlScript(connection, url);
+ } catch (SQLException e) {
+ Assertions.fail("Database threw an exception: " + e.getMessage());
+ }
+ }
+
+ private void executeYqlScript(Connection connection, URL url) throws ScriptException {
+ ScriptUtils.executeSqlScript(
+ connection,
+ new EncodedResource(new UrlResource(url)),
+ false,
+ false,
+ "--",
+ ";",
+ "/*",
+ "*/");
+ }
+
+ protected static String getJdbcURL() {
+ StringBuilder jdbc = new StringBuilder("jdbc:ydb:")
+ .append(ydb.useTls() ? "grpcs://" : "grpc://")
+ .append(ydb.endpoint())
+ .append("/")
+ .append(ydb.database());
+
+ if (ydb.authToken() != null) {
+ jdbc.append("?").append("token=").append(ydb.authToken());
+ }
+
+ return jdbc.toString();
+ }
+
+ protected static void createTable(String tableName, String typeName) {
+ executeSql(
+ "CREATE TABLE " + tableName + "(" +
+ "id Int8," +
+ "value " + typeName + "," +
+ "PRIMARY KEY (id)" +
+ ")");
+ }
+
+ protected static void dropTable(String tableName) {
+ executeSql("DROP TABLE " + tableName);
+ }
+
+ protected static void executeSql(String sql) {
+ try (Connection connection = DriverManager.getConnection(getJdbcURL())) {
+ try (Statement stmt = connection.createStatement()) {
+ stmt.execute(sql);
+ }
+ } catch (SQLException e) {
+ Assertions.fail("Database threw an exception: " + e.getMessage());
+ }
+ }
+}
diff --git a/jimmer-dialect/src/test/java/ydb/jimmer/dialect/JoinTest.java b/jimmer-dialect/src/test/java/ydb/jimmer/dialect/JoinTest.java
new file mode 100644
index 00000000..a42b2b4a
--- /dev/null
+++ b/jimmer-dialect/src/test/java/ydb/jimmer/dialect/JoinTest.java
@@ -0,0 +1,49 @@
+package ydb.jimmer.dialect;
+
+import org.babyfish.jimmer.sql.JoinType;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import ydb.jimmer.dialect.model.StudentTable;
+
+public class JoinTest extends AbstractSelectTest {
+ @BeforeEach
+ protected void setup() {
+ initDatabase();
+ }
+
+ private void joinTest(JoinType joinType, String join) {
+ StudentTable table = StudentTable.$;
+ executeAndExpect(
+ getYqlClient()
+ .createQuery(table)
+ .orderBy(table.group(joinType).name().asc())
+ .select(table),
+ cxt -> cxt.sql(
+ "select tb_1_.id, tb_1_.name, tb_1_.group " +
+ "from student tb_1_ " +
+ join + " join group tb_2_ on tb_1_.group = tb_2_.id " +
+ "order by tb_2_.name asc"
+ )
+ );
+ }
+
+ @Test
+ public void leftJoinTest() {
+ joinTest(JoinType.LEFT, "left");
+ }
+
+ @Test
+ public void rightJoinTest() {
+ joinTest(JoinType.RIGHT, "right");
+ }
+
+ @Test
+ public void innerJoinTest() {
+ joinTest(JoinType.INNER, "inner");
+ }
+
+ @Test
+ public void fullJoinTest() {
+ joinTest(JoinType.FULL, "full");
+ }
+}
diff --git a/jimmer-dialect/src/test/java/ydb/jimmer/dialect/QueryTestContext.java b/jimmer-dialect/src/test/java/ydb/jimmer/dialect/QueryTestContext.java
new file mode 100644
index 00000000..128fdc6d
--- /dev/null
+++ b/jimmer-dialect/src/test/java/ydb/jimmer/dialect/QueryTestContext.java
@@ -0,0 +1,112 @@
+package ydb.jimmer.dialect;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
+import org.babyfish.jimmer.jackson.ImmutableModule;
+import org.babyfish.jimmer.sql.ast.mutation.MutationResult;
+import org.junit.jupiter.api.Assertions;
+import ydb.jimmer.dialect.sqlMonitor.QueryLog;
+
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+public class QueryTestContext {
+ private static final ObjectMapper MAPPER = new ObjectMapper()
+ .registerModule(new ImmutableModule())
+ .registerModule(new JavaTimeModule());
+
+ static {
+ MAPPER.setDateFormat(new SimpleDateFormat("yyyy-MM-dd"));
+ }
+
+ private final List logs;
+ private final List> rows;
+ private final MutationResult result;
+ private final Throwable throwable;
+
+ private int index = 0;
+
+ public QueryTestContext(List logs, List> rows) {
+ this.logs = logs;
+ this.rows = rows;
+ result = null;
+ throwable = null;
+ }
+
+ public QueryTestContext(List logs, MutationResult result, Throwable throwable) {
+ this.logs = logs;
+ this.rows = new ArrayList<>();
+ this.result = result;
+ this.throwable = throwable;
+ }
+
+ public void nextStatement() {
+ index++;
+ }
+
+ public void sql(String sql) {
+ Assertions.assertEquals(
+ sql.replace("--->", "").toLowerCase(),
+ logs.get(index).getSql().toLowerCase(),
+ "statements[" + index + "].sql");
+ }
+
+ public void variables(Object... values) {
+ batchVariables(0, values);
+ batches(1);
+ }
+
+ public void batches(int batchCount) {
+ Assertions.assertEquals(batchCount, logs.get(index).getVariablesList().size());
+ }
+
+ public void batchVariables(int batchIndex, Object... values) {
+ Assertions.assertEquals(
+ values.length,
+ logs.get(index).getVariablesList().get(batchIndex).size(),
+ "statements[" + index + "].variables.size is error, actual variables: " +
+ logs.get(index).getVariablesList().get(batchIndex)
+ );
+ for (int i = 0; i < values.length; i++) {
+ Object expect = values[i];
+
+ Object actual = logs.get(index).getVariablesList().get(batchIndex).get(i);
+
+ Assertions.assertEquals(
+ expect,
+ actual,
+ "statements[" + index + "].batch[" + batchIndex + "].variables[" + i + "] is error, " +
+ "expected variables: " +
+ Arrays.toString(values) +
+ ", actual variables: " +
+ actual
+ );
+ }
+ }
+
+ public void rows(String json) {
+ try {
+ Assertions.assertEquals(
+ json.replace("--->", ""),
+ MAPPER.writeValueAsString(rows)
+ );
+ } catch (JsonProcessingException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public void error(String message) {
+ if (throwable == null && message == null) {
+ return;
+ }
+
+ if (throwable == null) {
+ Assertions.fail("The query didn't produce an exception: " + message);
+ }
+
+ Assertions.assertEquals(message, throwable.getMessage());
+ }
+}
diff --git a/jimmer-dialect/src/test/java/ydb/jimmer/dialect/SelectTest.java b/jimmer-dialect/src/test/java/ydb/jimmer/dialect/SelectTest.java
new file mode 100644
index 00000000..cb43b015
--- /dev/null
+++ b/jimmer-dialect/src/test/java/ydb/jimmer/dialect/SelectTest.java
@@ -0,0 +1,23 @@
+package ydb.jimmer.dialect;
+
+import org.junit.jupiter.api.Test;
+import ydb.jimmer.dialect.model.StudentTable;
+
+public class SelectTest extends AbstractSelectTest {
+ @Test
+ public void OneEntityTest() {
+ initDatabase();
+
+ StudentTable table = StudentTable.$;
+
+ executeAndExpect(
+ getYqlClient()
+ .createQuery(table)
+ .select(table),
+ cxt -> cxt.sql(
+ "select tb_1_.id, tb_1_.name, tb_1_.group " +
+ "from student tb_1_"
+ )
+ );
+ }
+}
diff --git a/jimmer-dialect/src/test/java/ydb/jimmer/dialect/batch/BatchTest.java b/jimmer-dialect/src/test/java/ydb/jimmer/dialect/batch/BatchTest.java
new file mode 100644
index 00000000..28ce64b1
--- /dev/null
+++ b/jimmer-dialect/src/test/java/ydb/jimmer/dialect/batch/BatchTest.java
@@ -0,0 +1,81 @@
+package ydb.jimmer.dialect.batch;
+
+import org.babyfish.jimmer.sql.ast.mutation.SaveMode;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import ydb.jimmer.dialect.AbstractInsertTest;
+import ydb.jimmer.dialect.model.Entity;
+import ydb.jimmer.dialect.model.EntityDraft;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class BatchTest extends AbstractInsertTest {
+ private static final String TABLE_NAME = "simple_table";
+ private static final String TYPE_NAME = "Int32";
+
+ private void batchTest(
+ SaveMode saveMode,
+ String sql,
+ int[][] batches,
+ int[][] expectedBatches
+ ) {
+ List entities = new ArrayList<>();
+ for (int[] batch : batches) {
+ entities.add(EntityDraft.$.produce(t -> {
+ t.setId(batch[0]);
+ t.setValue(batch[1]);
+ }));
+ }
+
+ executeAndExpect(
+ getYqlClientForBatch()
+ .getEntities()
+ .saveEntitiesCommand(entities)
+ .setMode(saveMode),
+ cxt -> {
+ cxt.sql(sql);
+
+ for (int i = 0; i < batches.length; i++) {
+ cxt.batchVariables(i, expectedBatches[i][0], expectedBatches[i][1]);
+ }
+ }
+ );
+ }
+
+ @BeforeEach
+ void setup() {
+ createTable(TABLE_NAME, TYPE_NAME);
+ }
+
+ @AfterEach
+ void teardown() {
+ dropTable(TABLE_NAME);
+ }
+
+ @Test
+ public void insertTest() {
+ int[][] batches = new int[][]{{0, 123}, {1, 456}};
+ batchTest(SaveMode.INSERT_ONLY,
+ "insert into " + TABLE_NAME + "(id, value) values(?, ?)",
+ batches, batches);
+ }
+
+ @Test
+ public void updateTest() {
+ int[][] batches = new int[][]{{0, 123}, {1, 456}};
+ int[][] expectedBatches = new int[][]{{123, 0}, {456, 1}};
+ batchTest(SaveMode.UPDATE_ONLY,
+ "update " + TABLE_NAME + " set value = ? where id = ? returning id",
+ batches, expectedBatches);
+ }
+
+ @Test
+ public void upsertTest() {
+ int[][] batches = new int[][]{{0, 123}, {1, 456}};
+ batchTest(SaveMode.UPSERT,
+ "upsert into " + TABLE_NAME + "(id, value) values(?, ?)",
+ batches, batches);
+ }
+}
diff --git a/jimmer-dialect/src/test/java/ydb/jimmer/dialect/benchmark/CrudScanBenchmarkTest.java b/jimmer-dialect/src/test/java/ydb/jimmer/dialect/benchmark/CrudScanBenchmarkTest.java
new file mode 100644
index 00000000..8f5efede
--- /dev/null
+++ b/jimmer-dialect/src/test/java/ydb/jimmer/dialect/benchmark/CrudScanBenchmarkTest.java
@@ -0,0 +1,183 @@
+package ydb.jimmer.dialect.benchmark;
+
+import org.babyfish.jimmer.sql.JSqlClient;
+import org.babyfish.jimmer.sql.ast.mutation.SaveMode;
+import org.babyfish.jimmer.sql.runtime.ConnectionManager;
+import org.junit.jupiter.api.Test;
+import org.openjdk.jmh.annotations.Benchmark;
+import org.openjdk.jmh.annotations.BenchmarkMode;
+import org.openjdk.jmh.annotations.Fork;
+import org.openjdk.jmh.annotations.Level;
+import org.openjdk.jmh.annotations.Measurement;
+import org.openjdk.jmh.annotations.Mode;
+import org.openjdk.jmh.annotations.OutputTimeUnit;
+import org.openjdk.jmh.annotations.Scope;
+import org.openjdk.jmh.annotations.Setup;
+import org.openjdk.jmh.annotations.State;
+import org.openjdk.jmh.annotations.TearDown;
+import org.openjdk.jmh.annotations.Warmup;
+import org.openjdk.jmh.runner.Runner;
+import org.openjdk.jmh.runner.RunnerException;
+import org.openjdk.jmh.runner.format.OutputFormat;
+import org.openjdk.jmh.runner.format.OutputFormatFactory;
+import org.openjdk.jmh.runner.options.Options;
+import org.openjdk.jmh.runner.options.OptionsBuilder;
+import org.openjdk.jmh.runner.options.VerboseMode;
+import org.springframework.jdbc.datasource.DriverManagerDataSource;
+import ydb.jimmer.dialect.AbstractSelectTest;
+import ydb.jimmer.dialect.YdbDialect;
+import ydb.jimmer.dialect.YqlClientBuilder;
+import ydb.jimmer.dialect.model.Entity;
+import ydb.jimmer.dialect.model.EntityDraft;
+import ydb.jimmer.dialect.model.EntityTable;
+import ydb.jimmer.dialect.transaction.YqlClient;
+
+import javax.sql.DataSource;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+@BenchmarkMode(Mode.AverageTime)
+@OutputTimeUnit(TimeUnit.MICROSECONDS)
+@State(Scope.Benchmark)
+@Warmup(iterations = 3, time = 1, timeUnit = TimeUnit.SECONDS)
+@Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
+@Fork(0)
+public class CrudScanBenchmarkTest extends AbstractSelectTest {
+ private static final String TABLE_NAME = "simple_table";
+ private static final String TYPE_NAME = "Int32";
+
+ private static final int size = 1000;
+
+ private static final String[] values;
+ private static final List existingEntities;
+ private static final List existingIds;
+ private static final List newEntities;
+
+ static {
+ values = new String[size];
+ existingEntities = new ArrayList<>();
+ existingIds = new ArrayList<>();
+ for (int i = 0; i < size; i++) {
+ int value = i;
+ values[i] = String.valueOf(value);
+ existingEntities.add(EntityDraft.$.produce(entity -> {
+ entity.setId(value);
+ entity.setValue(value);
+ }));
+ existingIds.add(value);
+ }
+
+ newEntities = new ArrayList<>();
+ for (int i = 0; i < size; i++) {
+ int value = size + i;
+ newEntities.add(EntityDraft.$.produce(entity -> {
+ entity.setId(value);
+ entity.setValue(value);
+ }));
+ }
+ }
+
+ protected static JSqlClient sqlClient;
+ protected static YqlClient yqlClient;
+
+ static {
+ DataSource dataSource = new DriverManagerDataSource(getJdbcURL());
+ sqlClient = JSqlClient.newBuilder()
+ .setDialect(new YdbDialect())
+ .setConnectionManager(ConnectionManager.simpleConnectionManager(dataSource))
+ .build();
+ yqlClient = YqlClientBuilder.getYqlClient(dataSource);
+ }
+
+ @Setup(Level.Invocation)
+ public void setup() {
+ createTable(TABLE_NAME, TYPE_NAME);
+
+ yqlClient.getEntities()
+ .saveEntitiesCommand(existingEntities)
+ .execute();
+ }
+
+ @TearDown(Level.Invocation)
+ public void tearDown() {
+ dropTable(TABLE_NAME);
+ }
+
+ @Benchmark
+ public void benchmarkRead() {
+ yqlClient.snapshotReadOnly(() -> yqlClient.getEntities().findAll(Entity.class));
+ }
+
+ @Benchmark
+ public void benchmarkScan() {
+ EntityTable table = EntityTable.$;
+ yqlClient.createQuery(table)
+ .where(table.value().ge(size/2))
+ .where(table.value().le(size))
+ .select(table);
+ }
+
+ @Benchmark
+ public void benchmarkScanDialectOnly() {
+ EntityTable table = EntityTable.$;
+ sqlClient.createQuery(table)
+ .where(table.value().ge(size/2))
+ .where(table.value().le(size))
+ .select(table);
+ }
+
+ @Benchmark
+ public void benchmarkReadDialectOnly() {
+ sqlClient.getEntities().findAll(Entity.class);
+ }
+
+ @Benchmark
+ public void benchmarkSave() {
+ yqlClient.getEntities()
+ .saveEntitiesCommand(newEntities)
+ .execute();
+ }
+
+ @Benchmark
+ public void benchmarkSaveDialectOnly() {
+ sqlClient.getEntities()
+ .saveEntitiesCommand(newEntities)
+ .execute();
+ }
+
+ @Benchmark
+ public void benchmarkUpdate() {
+ yqlClient.getEntities()
+ .saveEntitiesCommand(existingEntities)
+ .setMode(SaveMode.UPDATE_ONLY)
+ .execute();
+ }
+
+ @Benchmark
+ public void benchmarkDelete() {
+ yqlClient.getEntities().deleteAll(Entity.class, existingIds);
+ }
+
+ @Benchmark
+ public void benchmarkDeleteDialectOnly() {
+ sqlClient.getEntities().deleteAll(Entity.class, existingIds);
+ }
+
+ @Test
+ void runBenchmarks() throws RunnerException {
+ Options opts = new OptionsBuilder()
+ .include(CrudScanBenchmarkTest.class.getSimpleName())
+ .shouldFailOnError(true)
+ .shouldDoGC(true)
+ .build();
+
+ OutputFormat out = OutputFormatFactory.createFormatInstance(
+ System.out,
+ VerboseMode.NORMAL
+ );
+
+
+ new Runner(opts, out).run();
+ }
+}
diff --git a/jimmer-dialect/src/test/java/ydb/jimmer/dialect/benchmark/JoinBenchmarkTest.java b/jimmer-dialect/src/test/java/ydb/jimmer/dialect/benchmark/JoinBenchmarkTest.java
new file mode 100644
index 00000000..e7780a01
--- /dev/null
+++ b/jimmer-dialect/src/test/java/ydb/jimmer/dialect/benchmark/JoinBenchmarkTest.java
@@ -0,0 +1,174 @@
+package ydb.jimmer.dialect.benchmark;
+
+import org.babyfish.jimmer.sql.JSqlClient;
+import org.babyfish.jimmer.sql.JoinType;
+import org.babyfish.jimmer.sql.runtime.ConnectionManager;
+import org.junit.jupiter.api.Test;
+import org.openjdk.jmh.annotations.Benchmark;
+import org.openjdk.jmh.annotations.BenchmarkMode;
+import org.openjdk.jmh.annotations.Fork;
+import org.openjdk.jmh.annotations.Level;
+import org.openjdk.jmh.annotations.Measurement;
+import org.openjdk.jmh.annotations.Mode;
+import org.openjdk.jmh.annotations.OutputTimeUnit;
+import org.openjdk.jmh.annotations.Scope;
+import org.openjdk.jmh.annotations.Setup;
+import org.openjdk.jmh.annotations.State;
+import org.openjdk.jmh.annotations.Warmup;
+import org.openjdk.jmh.runner.Runner;
+import org.openjdk.jmh.runner.RunnerException;
+import org.openjdk.jmh.runner.format.OutputFormat;
+import org.openjdk.jmh.runner.format.OutputFormatFactory;
+import org.openjdk.jmh.runner.options.Options;
+import org.openjdk.jmh.runner.options.OptionsBuilder;
+import org.openjdk.jmh.runner.options.VerboseMode;
+import org.springframework.jdbc.datasource.DriverManagerDataSource;
+import ydb.jimmer.dialect.AbstractSelectTest;
+import ydb.jimmer.dialect.YdbDialect;
+import ydb.jimmer.dialect.YqlClientBuilder;
+import ydb.jimmer.dialect.model.Group;
+import ydb.jimmer.dialect.model.GroupDraft;
+import ydb.jimmer.dialect.model.Student;
+import ydb.jimmer.dialect.model.StudentDraft;
+import ydb.jimmer.dialect.model.StudentTable;
+import ydb.jimmer.dialect.transaction.YqlClient;
+
+import javax.sql.DataSource;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+import java.util.concurrent.TimeUnit;
+
+@BenchmarkMode(Mode.AverageTime)
+@OutputTimeUnit(TimeUnit.MICROSECONDS)
+@State(Scope.Benchmark)
+@Warmup(iterations = 3, time = 1, timeUnit = TimeUnit.SECONDS)
+@Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
+@Fork(0)
+public class JoinBenchmarkTest extends AbstractSelectTest {
+ private static final int size = 1000;
+
+ private static final List students;
+ private static final List groups;
+
+ static {
+ students = new ArrayList<>();
+ groups = new ArrayList<>();
+ for (int i = 0; i < size; i++) {
+ UUID value = UUID.randomUUID();
+ Group group = GroupDraft.$.produce(g -> {
+ g.setId(value);
+ g.setName("name" + value);
+ });
+ groups.add(group);
+ students.add(StudentDraft.$.produce(entity -> {
+ entity.setId(UUID.randomUUID());
+ entity.setName("name1" + value);
+ entity.setGroup(group);
+ }));
+ students.add(StudentDraft.$.produce(entity -> {
+ entity.setId(UUID.randomUUID());
+ entity.setName("name2" + value);
+ entity.setGroup(group);
+ }));
+ }
+ }
+
+ protected static JSqlClient sqlClient;
+ protected static YqlClient yqlClient;
+
+ static {
+ DataSource dataSource = new DriverManagerDataSource(getJdbcURL());
+ sqlClient = JSqlClient.newBuilder()
+ .setDialect(new YdbDialect())
+ .setConnectionManager(ConnectionManager.simpleConnectionManager(dataSource))
+ .build();
+ yqlClient = YqlClientBuilder.getYqlClient(dataSource);
+ }
+
+ @Setup(Level.Invocation)
+ public void setup() {
+ initDatabase();
+
+ yqlClient.getEntities()
+ .saveEntitiesCommand(students)
+ .execute();
+
+ yqlClient.getEntities()
+ .saveEntitiesCommand(groups)
+ .execute();
+ }
+
+ @Benchmark
+ public void benchmarkLeft() {
+ benchmarkJoin(JoinType.LEFT);
+ }
+
+ @Benchmark
+ public void benchmarkLeftDialectOnly() {
+ benchmarkJoinDialectOnly(JoinType.LEFT);
+ }
+
+ @Benchmark
+ public void benchmarkRight() {
+ benchmarkJoin(JoinType.RIGHT);
+ }
+
+ @Benchmark
+ public void benchmarkRightDialectOnly() {
+ benchmarkJoinDialectOnly(JoinType.RIGHT);
+ }
+
+ @Benchmark
+ public void benchmarkInner() {
+ benchmarkJoin(JoinType.INNER);
+ }
+
+ @Benchmark
+ public void benchmarkInnerDialectOnly() {
+ benchmarkJoinDialectOnly(JoinType.INNER);
+ }
+
+ @Benchmark
+ public void benchmarkFull() {
+ benchmarkJoin(JoinType.FULL);
+ }
+
+ @Benchmark
+ public void benchmarkFullDialectOnly() {
+ benchmarkJoinDialectOnly(JoinType.FULL);
+ }
+
+ private void benchmarkJoin(JoinType joinType) {
+ StudentTable table = StudentTable.$;
+ yqlClient
+ .createQuery(table)
+ .orderBy(table.group(joinType).name().asc())
+ .select(table);
+ }
+
+ private void benchmarkJoinDialectOnly(JoinType joinType) {
+ StudentTable table = StudentTable.$;
+ sqlClient
+ .createQuery(table)
+ .orderBy(table.group(joinType).name().asc())
+ .select(table);
+ }
+
+ @Test
+ void runBenchmarks() throws RunnerException {
+ Options opts = new OptionsBuilder()
+ .include(JoinBenchmarkTest.class.getSimpleName())
+ .shouldFailOnError(true)
+ .shouldDoGC(true)
+ .build();
+
+ OutputFormat out = OutputFormatFactory.createFormatInstance(
+ System.out,
+ VerboseMode.NORMAL
+ );
+
+
+ new Runner(opts, out).run();
+ }
+}
diff --git a/jimmer-dialect/src/test/java/ydb/jimmer/dialect/chaosTests/AlwaysFail.java b/jimmer-dialect/src/test/java/ydb/jimmer/dialect/chaosTests/AlwaysFail.java
new file mode 100644
index 00000000..fd54b9b2
--- /dev/null
+++ b/jimmer-dialect/src/test/java/ydb/jimmer/dialect/chaosTests/AlwaysFail.java
@@ -0,0 +1,13 @@
+package ydb.jimmer.dialect.chaosTests;
+
+import org.babyfish.jimmer.sql.exception.ExecutionException;
+
+import java.sql.SQLTimeoutException;
+import java.util.function.Supplier;
+
+public class AlwaysFail implements Supplier {
+ @Override
+ public R get() {
+ throw new ExecutionException("Persistent failure", new SQLTimeoutException());
+ }
+}
diff --git a/jimmer-dialect/src/test/java/ydb/jimmer/dialect/chaosTests/FailFirstN.java b/jimmer-dialect/src/test/java/ydb/jimmer/dialect/chaosTests/FailFirstN.java
new file mode 100644
index 00000000..cfcd274f
--- /dev/null
+++ b/jimmer-dialect/src/test/java/ydb/jimmer/dialect/chaosTests/FailFirstN.java
@@ -0,0 +1,36 @@
+package ydb.jimmer.dialect.chaosTests;
+
+import org.babyfish.jimmer.sql.exception.ExecutionException;
+
+import java.sql.SQLTimeoutException;
+import java.util.function.Supplier;
+
+public class FailFirstN implements Supplier {
+ private final int n;
+ private final R result;
+
+ private int attempt;
+
+ public FailFirstN(int n) {
+ this.n = n;
+ this.result = null;
+ }
+
+ public FailFirstN(int n, R result) {
+ this.n = n;
+ this.result = result;
+ }
+
+ public int getAttempt() {
+ return attempt;
+ }
+
+ @Override
+ public R get() {
+ if (attempt++ < n) {
+ throw new ExecutionException("Expected failure number " + attempt, new SQLTimeoutException());
+ }
+
+ return result;
+ }
+}
diff --git a/jimmer-dialect/src/test/java/ydb/jimmer/dialect/chaosTests/NotRetryableFail.java b/jimmer-dialect/src/test/java/ydb/jimmer/dialect/chaosTests/NotRetryableFail.java
new file mode 100644
index 00000000..c7e99447
--- /dev/null
+++ b/jimmer-dialect/src/test/java/ydb/jimmer/dialect/chaosTests/NotRetryableFail.java
@@ -0,0 +1,20 @@
+package ydb.jimmer.dialect.chaosTests;
+
+import org.babyfish.jimmer.sql.exception.ExecutionException;
+
+import java.sql.SQLException;
+import java.util.function.Supplier;
+
+public class NotRetryableFail implements Supplier {
+ private int attempt;
+
+ public int getAttempt() {
+ return attempt;
+ }
+
+ @Override
+ public R get() {
+ attempt++;
+ throw new ExecutionException("Not retryable failure", new SQLException());
+ }
+}
diff --git a/jimmer-dialect/src/test/java/ydb/jimmer/dialect/chaosTests/RetryTest.java b/jimmer-dialect/src/test/java/ydb/jimmer/dialect/chaosTests/RetryTest.java
new file mode 100644
index 00000000..e866933a
--- /dev/null
+++ b/jimmer-dialect/src/test/java/ydb/jimmer/dialect/chaosTests/RetryTest.java
@@ -0,0 +1,70 @@
+package ydb.jimmer.dialect.chaosTests;
+
+import org.babyfish.jimmer.sql.exception.ExecutionException;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import ydb.jimmer.dialect.AbstractSelectTest;
+
+public class RetryTest extends AbstractSelectTest {
+ @Test
+ public void succeedAfter1Failure() {
+ int maxAttempts = 2;
+
+ FailFirstN> chaosPolicy = new FailFirstN<>(maxAttempts - 1);
+ Assertions.assertDoesNotThrow(() ->
+ getIsolationClient().transaction(
+ maxAttempts, 0,
+ chaosPolicy
+ )
+ );
+
+ Assertions.assertEquals(maxAttempts, chaosPolicy.getAttempt());
+ }
+
+ @Test
+ public void failAllAttempts() {
+ int maxAttempts = 3;
+
+ Assertions.assertThrows(
+ ExecutionException.class, () ->
+ getIsolationClient().transaction(
+ maxAttempts, 0,
+ new AlwaysFail<>()
+ )
+ );
+ }
+
+ @Test
+ public void failWithoutRetries() {
+ int maxAttempts = 3;
+
+ NotRetryableFail> chaosPolicy = new NotRetryableFail<>();
+ Assertions.assertThrows(
+ ExecutionException.class, () ->
+ getIsolationClient().transaction(
+ maxAttempts, 0,
+ chaosPolicy
+ )
+ );
+
+ Assertions.assertEquals(1, chaosPolicy.getAttempt());
+ }
+
+ @Test
+ public void exponentialBackoffTest() {
+ int maxAttempts = 3;
+
+ FailFirstN> chaosPolicy = new FailFirstN<>(maxAttempts - 1);
+
+ long start = System.currentTimeMillis();
+ Assertions.assertDoesNotThrow(() ->
+ getIsolationClient().transaction(
+ maxAttempts, 100,
+ chaosPolicy
+ )
+ );
+ long elapsed = System.currentTimeMillis() - start;
+
+ Assertions.assertTrue(elapsed >= 300, "the backoff time is less than 100ms");
+ }
+}
diff --git a/jimmer-dialect/src/test/java/ydb/jimmer/dialect/dataTypeTest/InsertDataTypeTest.java b/jimmer-dialect/src/test/java/ydb/jimmer/dialect/dataTypeTest/InsertDataTypeTest.java
new file mode 100644
index 00000000..d00cfef4
--- /dev/null
+++ b/jimmer-dialect/src/test/java/ydb/jimmer/dialect/dataTypeTest/InsertDataTypeTest.java
@@ -0,0 +1,368 @@
+package ydb.jimmer.dialect.dataTypeTest;
+
+import org.babyfish.jimmer.sql.ast.mutation.SaveMode;
+import org.junit.jupiter.api.Test;
+import ydb.jimmer.dialect.AbstractInsertTest;
+import ydb.jimmer.dialect.model.type.ydbBool.YdbBooleanClassDraft;
+import ydb.jimmer.dialect.model.type.ydbBool.YdbBooleanDraft;
+import ydb.jimmer.dialect.model.type.ydbDate32.YdbDateDraft;
+import ydb.jimmer.dialect.model.type.ydbDate32.YdbLocalDateDraft;
+import ydb.jimmer.dialect.model.type.ydbDatetime64.YdbLocalDateTimeDraft;
+import ydb.jimmer.dialect.model.type.ydbDecimal.YdbBigDecimalDraft;
+import ydb.jimmer.dialect.model.type.ydbDouble.YdbDoubleClassDraft;
+import ydb.jimmer.dialect.model.type.ydbDouble.YdbDoubleDraft;
+import ydb.jimmer.dialect.model.type.ydbEnum.Value;
+import ydb.jimmer.dialect.model.type.ydbEnum.YdbEnumDraft;
+import ydb.jimmer.dialect.model.type.ydbFloat.YdbFloatClassDraft;
+import ydb.jimmer.dialect.model.type.ydbFloat.YdbFloatDraft;
+import ydb.jimmer.dialect.model.type.ydbInt16.YdbShortClassDraft;
+import ydb.jimmer.dialect.model.type.ydbInt16.YdbShortDraft;
+import ydb.jimmer.dialect.model.type.ydbInt32.YdbIntDraft;
+import ydb.jimmer.dialect.model.type.ydbInt32.YdbIntegerDraft;
+import ydb.jimmer.dialect.model.type.ydbInt32.YdbLocalTimeDraft;
+import ydb.jimmer.dialect.model.type.ydbInt32.YdbTimeDraft;
+import ydb.jimmer.dialect.model.type.ydbInt64.YdbBigIntegerDraft;
+import ydb.jimmer.dialect.model.type.ydbInt64.YdbLongClassDraft;
+import ydb.jimmer.dialect.model.type.ydbInt64.YdbLongDraft;
+import ydb.jimmer.dialect.model.type.ydbInt8.YdbByteClassDraft;
+import ydb.jimmer.dialect.model.type.ydbInt8.YdbByteDraft;
+import ydb.jimmer.dialect.model.type.ydbInterval64.YdbDurationDraft;
+import ydb.jimmer.dialect.model.type.ydbString.YdbByteArrayDraft;
+import ydb.jimmer.dialect.model.type.ydbTimestamp64.YdbInstantDraft;
+import ydb.jimmer.dialect.model.type.ydbTimestamp64.YdbTimestampDraft;
+import ydb.jimmer.dialect.model.type.ydbTimestamp64.YdbUtilDateDraft;
+import ydb.jimmer.dialect.model.type.ydbUtf8.YdbStringDraft;
+import ydb.jimmer.dialect.model.type.ydbUuid.YdbUuidDraft;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.sql.Date;
+import java.sql.Time;
+import java.sql.Timestamp;
+import java.time.Duration;
+import java.time.Instant;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.time.ZoneId;
+import java.util.UUID;
+
+public class InsertDataTypeTest extends AbstractInsertTest {
+ private void typeTest(String tableName,
+ String typeName,
+ Object input,
+ Object[] variables) {
+ createTable(tableName, typeName);
+
+ executeAndExpect(
+ getYqlClient().getEntities().saveCommand(input)
+ .setMode(SaveMode.INSERT_ONLY),
+ cxt -> {
+ cxt.sql("insert into " + tableName + "(id, value) values(?, ?)");
+ cxt.variables(variables);
+ }
+ );
+
+ dropTable(tableName);
+ }
+
+ /**
+ * {@link org.babyfish.jimmer.sql.ast.impl.Variables#handleDateTime(Object, ZoneId) handleDateTime(Object, ZoneId)}
+ * this Jimmer method is responsible for changing java types without any user input
+ */
+ @Test
+ public void handleDateTimeJimmerTest() {
+ Object[] variables = new Object[]{0, Instant.now()};
+
+ typeTest("ydb_instant", "Timestamp64",
+ YdbInstantDraft.$.produce(t -> {
+ t.setId((Integer) variables[0]);
+ t.setValue((Instant) variables[1]);
+ }),
+ variables);
+
+ Object[] variables1 = new Object[]{0, LocalDateTime.parse("1970-01-01T00:00:00")};
+
+ typeTest("ydb_local_date_time", "DateTime64",
+ YdbLocalDateTimeDraft.$.produce(t -> {
+ t.setId((Integer) variables1[0]);
+ t.setValue((LocalDateTime) variables1[1]);
+ }),
+ variables1);
+
+ Object[] variables2 = new Object[]{0, LocalDate.parse("1970-01-01")};
+
+ typeTest("ydb_local_date", "Date32",
+ YdbLocalDateDraft.$.produce(t -> {
+ t.setId((Integer) variables2[0]);
+ t.setValue((LocalDate) variables2[1]);
+ }),
+ variables2);
+
+ Object[] variables3 = new Object[]{0, LocalTime.parse("10:15")};
+
+ typeTest("ydb_local_time", "Int32",
+ YdbLocalTimeDraft.$.produce(t -> {
+ t.setId((Integer) variables3[0]);
+ t.setValue((LocalTime) variables3[1]);
+ }),
+ variables3);
+
+ Object[] variables4 = new Object[]{0, new java.util.Date(0)};
+
+ typeTest("ydb_util_date", "Timestamp64",
+ YdbUtilDateDraft.$.produce(t -> {
+ t.setId((Integer) variables4[0]);
+ t.setValue((java.util.Date) variables4[1]);
+ }),
+ variables4);
+ }
+
+ @Test
+ public void boolTest() {
+ Object[] variables = new Object[]{0, true};
+
+ typeTest("ydb_boolean", "Bool",
+ YdbBooleanDraft.$.produce(t -> {
+ t.setId((Integer) variables[0]);
+ t.setValue((Boolean) variables[1]);
+ }),
+ variables
+ );
+
+ typeTest("ydb_boolean_class", "Bool",
+ YdbBooleanClassDraft.$.produce(t -> {
+ t.setId((Integer) variables[0]);
+ t.setValue((Boolean) variables[1]);
+ }),
+ variables);
+ }
+
+ @Test
+ public void date32Test() {
+ Object[] variables1 = new Object[]{0, new Date(0)};
+
+ typeTest("ydb_date", "Date32",
+ YdbDateDraft.$.produce(t -> {
+ t.setId((Integer) variables1[0]);
+ t.setValue((Date) variables1[1]);
+ }),
+ variables1);
+ }
+
+ @Test
+ public void decimalTest() {
+ Object[] variables = new Object[]{0, new BigDecimal(0)};
+
+ typeTest("ydb_big_decimal", "Decimal(22, 9)",
+ YdbBigDecimalDraft.$.produce(t -> {
+ t.setId((Integer) variables[0]);
+ t.setValue((BigDecimal) variables[1]);
+ }),
+ variables);
+ }
+
+ @Test
+ public void doubleTest() {
+ Object[] variables = new Object[]{0, 0.1};
+
+ typeTest("ydb_double", "Double",
+ YdbDoubleDraft.$.produce(t -> {
+ t.setId((Integer) variables[0]);
+ t.setValue((double) variables[1]);
+ }),
+ variables);
+
+ typeTest("ydb_double_class", "Double",
+ YdbDoubleClassDraft.$.produce(t -> {
+ t.setId((Integer) variables[0]);
+ t.setValue((Double) variables[1]);
+ }),
+ variables);
+ }
+
+ @Test
+ public void enumTest() {
+ Object[] variables = new Object[]{0, Value.ONE};
+
+ typeTest("ydb_enum", "Utf8",
+ YdbEnumDraft.$.produce(t -> {
+ t.setId((Integer) variables[0]);
+ t.setValue((Value) variables[1]);
+ }),
+ new Object[]{0, "ONE"});
+ }
+
+ @Test
+ public void floatTest() {
+ Object[] variables = new Object[]{0, (float) 0.1};
+
+ typeTest("ydb_float", "Float",
+ YdbFloatDraft.$.produce(t -> {
+ t.setId((Integer) variables[0]);
+ t.setValue((float) variables[1]);
+ }),
+ variables);
+
+ typeTest("ydb_float_class", "Float",
+ YdbFloatClassDraft.$.produce(t -> {
+ t.setId((Integer) variables[0]);
+ t.setValue((Float) variables[1]);
+ }),
+ variables);
+ }
+
+ @Test
+ public void int8Test() {
+ Object[] variables = new Object[]{0, (byte) 1};
+
+ typeTest("ydb_byte", "Int8",
+ YdbByteDraft.$.produce(t -> {
+ t.setId((Integer) variables[0]);
+ t.setValue((byte) variables[1]);
+ }),
+ variables);
+
+ typeTest("ydb_byte_class", "Int8",
+ YdbByteClassDraft.$.produce(t -> {
+ t.setId((Integer) variables[0]);
+ t.setValue((Byte) variables[1]);
+ }),
+ variables);
+ }
+
+ @Test
+ public void int16Test() {
+ Object[] variables = new Object[]{0, (short) 1};
+
+ typeTest("ydb_short", "Int16",
+ YdbShortDraft.$.produce(t -> {
+ t.setId((Integer) variables[0]);
+ t.setValue((short) variables[1]);
+ }),
+ variables);
+
+ typeTest("ydb_short_class", "Int16",
+ YdbShortClassDraft.$.produce(t -> {
+ t.setId((Integer) variables[0]);
+ t.setValue((Short) variables[1]);
+ }),
+ variables);
+ }
+
+ @Test
+ public void int32Test() {
+ Object[] variables = new Object[]{0, 1};
+
+ typeTest("ydb_int", "Int32",
+ YdbIntDraft.$.produce(t -> {
+ t.setId((Integer) variables[0]);
+ t.setValue((int) variables[1]);
+ }),
+ variables);
+
+ typeTest("ydb_integer", "Int32",
+ YdbIntegerDraft.$.produce(t -> {
+ t.setId((Integer) variables[0]);
+ t.setValue((Integer) variables[1]);
+ }),
+ variables);
+
+ Object[] variables2 = new Object[]{0, new Time(0)};
+
+ typeTest("ydb_time", "Int32",
+ YdbTimeDraft.$.produce(t -> {
+ t.setId((Integer) variables2[0]);
+ t.setValue((Time) variables2[1]);
+ }),
+ variables2);
+ }
+
+ @Test
+ public void int64Test() {
+ Object[] variables = new Object[]{0, new BigInteger("1234567890")};
+
+ typeTest("ydb_big_integer", "Int64",
+ YdbBigIntegerDraft.$.produce(t -> {
+ t.setId((Integer) variables[0]);
+ t.setValue((BigInteger) variables[1]);
+ }),
+ variables);
+
+ Object[] variables1 = new Object[]{0, (long) 1};
+
+ typeTest("ydb_long", "Int64",
+ YdbLongDraft.$.produce(t -> {
+ t.setId((Integer) variables1[0]);
+ t.setValue((long) variables1[1]);
+ }),
+ variables1);
+
+ typeTest("ydb_long_class", "Int64",
+ YdbLongClassDraft.$.produce(t -> {
+ t.setId((Integer) variables1[0]);
+ t.setValue((Long) variables1[1]);
+ }),
+ variables1);
+ }
+
+ @Test
+ public void interval64Test() {
+ Object[] variables = new Object[]{0, Duration.ofHours(1)};
+
+ typeTest("ydb_duration", "Interval64",
+ YdbDurationDraft.$.produce(t -> {
+ t.setId((Integer) variables[0]);
+ t.setValue((Duration) variables[1]);
+ }),
+ variables);
+ }
+
+ @Test
+ public void stringTest() {
+ Object[] variables = new Object[]{0, new byte[]{1, 2}};
+
+ typeTest("ydb_byte_array", "String",
+ YdbByteArrayDraft.$.produce(t -> {
+ t.setId((Integer) variables[0]);
+ t.setValue((byte[]) variables[1]);
+ }),
+ variables);
+ }
+
+ @Test
+ public void timestamp64Test() {
+ Object[] variables = new Object[]{0, new Timestamp(0)};
+
+ typeTest("ydb_timestamp", "Timestamp64",
+ YdbTimestampDraft.$.produce(t -> {
+ t.setId((Integer) variables[0]);
+ t.setValue((Timestamp) variables[1]);
+ }),
+ variables);
+ }
+
+ @Test
+ public void utf8Test() {
+ Object[] variables = new Object[]{0, "ydb"};
+
+ typeTest("ydb_string", "Utf8",
+ YdbStringDraft.$.produce(t -> {
+ t.setId((Integer) variables[0]);
+ t.setValue((String) variables[1]);
+ }),
+ variables);
+ }
+
+ @Test
+ public void uuidTest() {
+ Object[] variables = new Object[]{0, UUID.fromString("9e197d65-1914-4d57-a65f-77a52a06baa7")};
+
+ typeTest("ydb_uuid", "Uuid",
+ YdbUuidDraft.$.produce(t -> {
+ t.setId((Integer) variables[0]);
+ t.setValue((UUID) variables[1]);
+ }),
+ variables);
+ }
+}
diff --git a/jimmer-dialect/src/test/java/ydb/jimmer/dialect/dataTypeTest/InsertJsonTest.java b/jimmer-dialect/src/test/java/ydb/jimmer/dialect/dataTypeTest/InsertJsonTest.java
new file mode 100644
index 00000000..32e219a4
--- /dev/null
+++ b/jimmer-dialect/src/test/java/ydb/jimmer/dialect/dataTypeTest/InsertJsonTest.java
@@ -0,0 +1,45 @@
+package ydb.jimmer.dialect.dataTypeTest;
+
+import org.babyfish.jimmer.sql.ast.mutation.SaveMode;
+import org.junit.jupiter.api.Test;
+import ydb.jimmer.dialect.AbstractSelectTest;
+import ydb.jimmer.dialect.model.type.ydbJson.Json;
+import ydb.jimmer.dialect.model.type.ydbJson.YdbJsonDraft;
+import ydb.jimmer.dialect.model.type.ydbJson.YdbJsonTable;
+
+public class InsertJsonTest extends AbstractSelectTest {
+ private static final String TABLE_NAME = "ydb_json";
+ private static final String TYPE_NAME = "Json";
+
+ @Test
+ public void insertJsonTest() {
+ Json json = new Json();
+ json.setA(2);
+ json.setB(3);
+
+ Object[] variables = new Object[]{0, json};
+
+ String[] expectedValues = new String[]{"{\"a\":2,\"b\":3}"};
+ String expectedJson = buildJsonResponse(expectedValues);
+
+ YdbJsonTable table = YdbJsonTable.$;
+
+ createTable(TABLE_NAME, TYPE_NAME);
+
+ getIsolationClient().getEntities().saveCommand(YdbJsonDraft.$.produce(t -> {
+ t.setId((Integer) variables[0]);
+ t.setValue((Json) variables[1]);
+ }))
+ .setMode(SaveMode.INSERT_ONLY).execute();
+ executeAndExpect(getYqlClient().createQuery(table).select(table),
+ cxt -> {
+ cxt.sql("insert into " + TABLE_NAME + "(id, value) values(?, ?)");
+ cxt.nextStatement();
+ cxt.sql("select tb_1_.id, tb_1_.value from " + TABLE_NAME + " tb_1_");
+ cxt.rows(expectedJson);
+ }
+ );
+
+ dropTable(TABLE_NAME);
+ }
+}
diff --git a/jimmer-dialect/src/test/java/ydb/jimmer/dialect/dataTypeTest/SelectDataTypeTest.java b/jimmer-dialect/src/test/java/ydb/jimmer/dialect/dataTypeTest/SelectDataTypeTest.java
new file mode 100644
index 00000000..63e62097
--- /dev/null
+++ b/jimmer-dialect/src/test/java/ydb/jimmer/dialect/dataTypeTest/SelectDataTypeTest.java
@@ -0,0 +1,316 @@
+package ydb.jimmer.dialect.dataTypeTest;
+
+import org.babyfish.jimmer.sql.ast.PropExpression;
+import org.babyfish.jimmer.sql.ast.table.spi.AbstractTypedTable;
+import org.junit.jupiter.api.Test;
+import ydb.jimmer.dialect.AbstractSelectTest;
+import ydb.jimmer.dialect.model.type.ydbBool.YdbBooleanClassTable;
+import ydb.jimmer.dialect.model.type.ydbBool.YdbBooleanTable;
+import ydb.jimmer.dialect.model.type.ydbDate32.YdbDateTable;
+import ydb.jimmer.dialect.model.type.ydbDate32.YdbLocalDateTable;
+import ydb.jimmer.dialect.model.type.ydbDatetime64.YdbLocalDateTimeTable;
+import ydb.jimmer.dialect.model.type.ydbDecimal.YdbBigDecimalTable;
+import ydb.jimmer.dialect.model.type.ydbDouble.YdbDoubleClassTable;
+import ydb.jimmer.dialect.model.type.ydbDouble.YdbDoubleTable;
+import ydb.jimmer.dialect.model.type.ydbEnum.YdbEnumTable;
+import ydb.jimmer.dialect.model.type.ydbFloat.YdbFloatClassTable;
+import ydb.jimmer.dialect.model.type.ydbFloat.YdbFloatTable;
+import ydb.jimmer.dialect.model.type.ydbInt16.YdbShortClassTable;
+import ydb.jimmer.dialect.model.type.ydbInt16.YdbShortTable;
+import ydb.jimmer.dialect.model.type.ydbInt32.YdbIntTable;
+import ydb.jimmer.dialect.model.type.ydbInt32.YdbIntegerTable;
+import ydb.jimmer.dialect.model.type.ydbInt32.YdbLocalTimeTable;
+import ydb.jimmer.dialect.model.type.ydbInt32.YdbTimeTable;
+import ydb.jimmer.dialect.model.type.ydbInt64.YdbBigIntegerTable;
+import ydb.jimmer.dialect.model.type.ydbInt64.YdbLongClassTable;
+import ydb.jimmer.dialect.model.type.ydbInt64.YdbLongTable;
+import ydb.jimmer.dialect.model.type.ydbInt8.YdbByteClassTable;
+import ydb.jimmer.dialect.model.type.ydbInt8.YdbByteTable;
+import ydb.jimmer.dialect.model.type.ydbInterval64.YdbDurationTable;
+import ydb.jimmer.dialect.model.type.ydbJson.YdbJsonTable;
+import ydb.jimmer.dialect.model.type.ydbString.YdbByteArrayTable;
+import ydb.jimmer.dialect.model.type.ydbTimestamp64.YdbInstantTable;
+import ydb.jimmer.dialect.model.type.ydbTimestamp64.YdbTimestampTable;
+import ydb.jimmer.dialect.model.type.ydbTimestamp64.YdbUtilDateTable;
+import ydb.jimmer.dialect.model.type.ydbUtf8.YdbStringTable;
+import ydb.jimmer.dialect.model.type.ydbUuid.YdbUuidTable;
+
+import java.time.ZoneId;
+
+public class SelectDataTypeTest extends AbstractSelectTest {
+ private void typeTest(String tableName,
+ String typeName,
+ AbstractTypedTable> table,
+ PropExpression> prop,
+ String[] values) {
+ typeTest(tableName, typeName, table, prop, values, values);
+ }
+
+ private void typeTest(String tableName,
+ String typeName,
+ AbstractTypedTable> table,
+ PropExpression> prop,
+ String[] valuesToInsert,
+ String[] expectedValues) {
+ createTable(tableName, typeName);
+
+ insert(tableName, valuesToInsert);
+
+ String json = buildJsonResponse(expectedValues);
+
+ var query = getYqlClient().createQuery(table);
+ StringBuilder sql = new StringBuilder("select tb_1_.id, tb_1_.value from " + tableName + " tb_1_");
+ if (expectedValues.length > 1) {
+ query = query.orderBy(prop);
+ sql.append(" order by tb_1_.value asc");
+ }
+
+ executeAndExpect(
+ query.select(table),
+ cxt -> {
+ cxt.sql(sql.toString());
+ cxt.rows(json);
+ }
+ );
+
+ dropTable(tableName);
+ }
+
+ /**
+ * {@link org.babyfish.jimmer.sql.ast.impl.Variables#handleDateTime(Object, ZoneId) handleDateTime(Object, ZoneId)}
+ * this Jimmer method is responsible for changing java types without any user input
+ */
+ @Test
+ public void handleDateTimeJimmerTest() {
+ String[] values = new String[]{"Timestamp64(\"2017-11-27T13:24:00.123456Z\")"};
+ String[] expectedValues = new String[]{"\"2017-11-27T13:24:00.123456Z\""};
+
+ typeTest("ydb_instant", "Timestamp64",
+ YdbInstantTable.$, YdbInstantTable.$.value(),
+ values, expectedValues);
+
+ values = new String[]{"DateTime64(\"2017-11-27T13:24:00Z\")"};
+ expectedValues = new String[]{"\"2017-11-27T13:24:00\""};
+
+ typeTest("ydb_local_date_time", "DateTime64",
+ YdbLocalDateTimeTable.$, YdbLocalDateTimeTable.$.value(),
+ values, expectedValues);
+
+ values = new String[]{"Date32(\"144169-01-01\")"};
+ expectedValues = new String[]{"\"+144169-01-01\""};
+
+ typeTest("ydb_local_date", "Date32",
+ YdbLocalDateTable.$, YdbLocalDateTable.$.value(),
+ values, expectedValues);
+
+ values = new String[]{"-1", "0", "10"};
+ expectedValues = new String[]{"\"02:59:59.999\"", "\"03:00:00\"", "\"03:00:00.01\""};
+
+ typeTest("ydb_local_time", "Int32",
+ YdbLocalTimeTable.$, YdbLocalTimeTable.$.value(),
+ values, expectedValues);
+
+ values = new String[]{"Timestamp64(\"2017-11-27T13:24:00.123456Z\")"};
+ expectedValues = new String[]{"\"2017-11-27\""};
+
+ typeTest("ydb_util_date", "Timestamp64",
+ YdbUtilDateTable.$, YdbUtilDateTable.$.value(),
+ values, expectedValues);
+ }
+
+ @Test
+ public void boolTest() {
+ String[] values = new String[]{"false", "true"};
+
+ typeTest("ydb_boolean", "Bool",
+ YdbBooleanTable.$, YdbBooleanTable.$.value(),
+ values);
+
+ typeTest("ydb_boolean_class", "Bool",
+ YdbBooleanClassTable.$, YdbBooleanClassTable.$.value(),
+ values);
+ }
+
+ @Test
+ public void date32Test() {
+ String[] values = new String[]{"Date32(\"144169-01-01\")"};
+ String[] expectedValues = new String[]{"\"4169-01-01\""};
+
+ typeTest("ydb_date", "Date32",
+ YdbDateTable.$, YdbDateTable.$.value(),
+ values, expectedValues);
+ }
+
+ @Test
+ public void decimalTest() {
+ String[] values = new String[]{"Decimal(\"1.23\", 22, 9)"};
+ String[] expectedValues = new String[]{"1.230000000"};
+
+ typeTest("ydb_big_decimal", "Decimal(22, 9)",
+ YdbBigDecimalTable.$, YdbBigDecimalTable.$.value(),
+ values, expectedValues);
+ }
+
+ @Test
+ public void doubleTest() {
+ String[] values = new String[]{"Double(\"1.23\")"};
+ String[] expectedValues = new String[]{"1.23"};
+
+ typeTest("ydb_double", "Double",
+ YdbDoubleTable.$, YdbDoubleTable.$.value(),
+ values, expectedValues);
+
+ typeTest("ydb_double_class", "Double",
+ YdbDoubleClassTable.$, YdbDoubleClassTable.$.value(),
+ values, expectedValues);
+ }
+
+ @Test
+ public void enumTest() {
+ String[] values = new String[]{"\"ONE\"", "\"TWO\""};
+
+ typeTest("ydb_enum", "Utf8",
+ YdbEnumTable.$, YdbEnumTable.$.value(),
+ values);
+ }
+
+ @Test
+ public void floatTest() {
+ String[] values = new String[]{"Float(\"1.23\")"};
+ String[] expectedValues = new String[]{"1.23"};
+
+ typeTest("ydb_float", "Float",
+ YdbFloatTable.$, YdbFloatTable.$.value(),
+ values, expectedValues);
+
+ typeTest("ydb_float_class", "Float",
+ YdbFloatClassTable.$, YdbFloatClassTable.$.value(),
+ values, expectedValues);
+ }
+
+ @Test
+ public void int8Test() {
+ String[] values = new String[]{"-1", "0", "10"};
+
+ typeTest("ydb_byte", "Int8",
+ YdbByteTable.$, YdbByteTable.$.value(),
+ values);
+
+ typeTest("ydb_byte_class", "Int8",
+ YdbByteClassTable.$, YdbByteClassTable.$.value(),
+ values);
+ }
+
+ @Test
+ public void int16Test() {
+ String[] values = new String[]{"-1", "0", "10"};
+
+ typeTest("ydb_short", "Int16",
+ YdbShortTable.$, YdbShortTable.$.value(),
+ values);
+
+ typeTest("ydb_short_class", "Int16",
+ YdbShortClassTable.$, YdbShortClassTable.$.value(),
+ values);
+ }
+
+ @Test
+ public void int32Test() {
+ String[] values = new String[]{"-1", "0", "10"};
+
+ typeTest("ydb_int", "Int32",
+ YdbIntTable.$, YdbIntTable.$.value(),
+ values);
+
+ typeTest("ydb_integer", "Int32",
+ YdbIntegerTable.$, YdbIntegerTable.$.value(),
+ values);
+
+ values = new String[]{"0", "10"};
+ String[] expectedValues = new String[]{"\"00:00:00\"", "\"00:00:10\""};
+
+ typeTest("ydb_time", "Int32",
+ YdbTimeTable.$, YdbTimeTable.$.value(),
+ values, expectedValues);
+ }
+
+ @Test
+ public void int64Test() {
+ String[] values = new String[]{"-1", "0", "10"};
+
+ typeTest("ydb_big_integer", "Int64",
+ YdbBigIntegerTable.$, YdbBigIntegerTable.$.value(),
+ values);
+
+ typeTest("ydb_long", "Int64",
+ YdbLongTable.$, YdbLongTable.$.value(),
+ values);
+
+ typeTest("ydb_long_class", "Int64",
+ YdbLongClassTable.$, YdbLongClassTable.$.value(),
+ values);
+ }
+
+ @Test
+ public void interval64Test() {
+ String[] values = new String[]{"Interval(\"P0DT0H0M0.567890S\")"};
+ String[] expectedValues = new String[]{"0.567890000"};
+
+ typeTest("ydb_duration", "Interval64",
+ YdbDurationTable.$, YdbDurationTable.$.value(),
+ values, expectedValues);
+ }
+
+ @Test
+ public void jsonTest() {
+ String[] values = new String[]{"Json(@@{\"a\":1,\"b\":null}@@)"};
+ String[] expectedValues = new String[]{"{\"a\":1,\"b\":null}"};
+
+ typeTest("ydb_json", "Json",
+ YdbJsonTable.$, YdbJsonTable.$.value(),
+ values, expectedValues);
+ }
+
+ @Test
+ public void stringTest() {
+ String[] values = new String[]{"\"0\"", "\"string\""};
+ String[] expectedValues = new String[]{"\"MA==\"", "\"c3RyaW5n\""};
+
+ typeTest("ydb_byte_array", "String",
+ YdbByteArrayTable.$, YdbByteArrayTable.$.value(),
+ values, expectedValues);
+ }
+
+ @Test
+ public void timestamp64Test() {
+ String[] values = new String[]{"Timestamp64(\"2017-11-27T13:24:00.123456Z\")"};
+ String[] expectedValues = new String[]{"\"2017-11-27\""};
+
+ typeTest("ydb_timestamp", "Timestamp64",
+ YdbTimestampTable.$, YdbTimestampTable.$.value(),
+ values, expectedValues);
+ }
+
+ @Test
+ public void utf8Test() {
+ String[] values = new String[]{"\"0\"", "\"string\""};
+
+ typeTest("ydb_string", "Utf8",
+ YdbStringTable.$, YdbStringTable.$.value(),
+ values);
+ }
+
+ @Test
+ public void uuidTest() {
+ String[] values = new String[]{
+ "Uuid(\"9e197d65-1914-4d57-a65f-77a52a06baa7\")",
+ "Uuid(\"8e0f2cf4-4656-4d73-970e-a18be9ead78b\")"};
+ String[] expectedValues = new String[]{
+ "\"9e197d65-1914-4d57-a65f-77a52a06baa7\"",
+ "\"8e0f2cf4-4656-4d73-970e-a18be9ead78b\""};
+
+ typeTest("ydb_uuid", "Uuid",
+ YdbUuidTable.$, YdbUuidTable.$.value(),
+ values, expectedValues);
+ }
+}
diff --git a/jimmer-dialect/src/test/java/ydb/jimmer/dialect/model/Entity.java b/jimmer-dialect/src/test/java/ydb/jimmer/dialect/model/Entity.java
new file mode 100644
index 00000000..39a70aca
--- /dev/null
+++ b/jimmer-dialect/src/test/java/ydb/jimmer/dialect/model/Entity.java
@@ -0,0 +1,16 @@
+package ydb.jimmer.dialect.model;
+
+import org.babyfish.jimmer.sql.Column;
+import org.babyfish.jimmer.sql.Id;
+import org.babyfish.jimmer.sql.Table;
+
+@org.babyfish.jimmer.sql.Entity
+@Table(name = "simple_table")
+public interface Entity {
+ @Id
+ @Column(name = "id")
+ int getId();
+
+ @Column(name = "value")
+ int value();
+}
diff --git a/jimmer-dialect/src/test/java/ydb/jimmer/dialect/model/Group.java b/jimmer-dialect/src/test/java/ydb/jimmer/dialect/model/Group.java
new file mode 100644
index 00000000..cb69a3b0
--- /dev/null
+++ b/jimmer-dialect/src/test/java/ydb/jimmer/dialect/model/Group.java
@@ -0,0 +1,22 @@
+package ydb.jimmer.dialect.model;
+
+import org.babyfish.jimmer.sql.Column;
+import org.babyfish.jimmer.sql.Entity;
+import org.babyfish.jimmer.sql.GeneratedValue;
+import org.babyfish.jimmer.sql.Id;
+import org.babyfish.jimmer.sql.Table;
+import org.babyfish.jimmer.sql.meta.UUIDIdGenerator;
+
+import java.util.UUID;
+
+@Entity
+@Table(name = "group")
+public interface Group {
+ @Id
+ @GeneratedValue(generatorType = UUIDIdGenerator.class)
+ @Column(name = "id")
+ UUID id();
+
+ @Column(name = "name")
+ String name();
+}
diff --git a/jimmer-dialect/src/test/java/ydb/jimmer/dialect/model/Student.java b/jimmer-dialect/src/test/java/ydb/jimmer/dialect/model/Student.java
new file mode 100644
index 00000000..3ba0119b
--- /dev/null
+++ b/jimmer-dialect/src/test/java/ydb/jimmer/dialect/model/Student.java
@@ -0,0 +1,31 @@
+package ydb.jimmer.dialect.model;
+
+import org.babyfish.jimmer.sql.Column;
+import org.babyfish.jimmer.sql.Entity;
+import org.babyfish.jimmer.sql.ForeignKeyType;
+import org.babyfish.jimmer.sql.GeneratedValue;
+import org.babyfish.jimmer.sql.Id;
+import org.babyfish.jimmer.sql.JoinColumn;
+import org.babyfish.jimmer.sql.ManyToOne;
+import org.babyfish.jimmer.sql.Table;
+import org.babyfish.jimmer.sql.meta.UUIDIdGenerator;
+
+import javax.annotation.Nullable;
+import java.util.UUID;
+
+@Entity
+@Table(name = "student")
+public interface Student {
+ @Id
+ @GeneratedValue(generatorType = UUIDIdGenerator.class)
+ @Column(name = "id")
+ UUID id();
+
+ @Column(name = "name")
+ String name();
+
+ @ManyToOne
+ @Nullable
+ @JoinColumn(name = "group", foreignKeyType = ForeignKeyType.FAKE)
+ Group group();
+}
diff --git a/jimmer-dialect/src/test/java/ydb/jimmer/dialect/model/streaming/YdbStreaming.java b/jimmer-dialect/src/test/java/ydb/jimmer/dialect/model/streaming/YdbStreaming.java
new file mode 100644
index 00000000..b725e1df
--- /dev/null
+++ b/jimmer-dialect/src/test/java/ydb/jimmer/dialect/model/streaming/YdbStreaming.java
@@ -0,0 +1,17 @@
+package ydb.jimmer.dialect.model.streaming;
+
+import org.babyfish.jimmer.sql.Column;
+import org.babyfish.jimmer.sql.Entity;
+import org.babyfish.jimmer.sql.Id;
+import org.babyfish.jimmer.sql.Table;
+
+@Entity
+@Table(name = "ydb_streaming")
+public interface YdbStreaming {
+ @Id
+ @Column(name = "id")
+ int getId();
+
+ @Column(name = "value")
+ int value();
+}
diff --git a/jimmer-dialect/src/test/java/ydb/jimmer/dialect/model/transaction/YdbTransaction.java b/jimmer-dialect/src/test/java/ydb/jimmer/dialect/model/transaction/YdbTransaction.java
new file mode 100644
index 00000000..e2803f26
--- /dev/null
+++ b/jimmer-dialect/src/test/java/ydb/jimmer/dialect/model/transaction/YdbTransaction.java
@@ -0,0 +1,17 @@
+package ydb.jimmer.dialect.model.transaction;
+
+import org.babyfish.jimmer.sql.Column;
+import org.babyfish.jimmer.sql.Entity;
+import org.babyfish.jimmer.sql.Id;
+import org.babyfish.jimmer.sql.Table;
+
+@Entity
+@Table(name = "ydb_transaction")
+public interface YdbTransaction {
+ @Id
+ @Column(name = "id")
+ int getId();
+
+ @Column(name = "value")
+ int value();
+}
diff --git a/jimmer-dialect/src/test/java/ydb/jimmer/dialect/model/type/ydbBool/YdbBoolean.java b/jimmer-dialect/src/test/java/ydb/jimmer/dialect/model/type/ydbBool/YdbBoolean.java
new file mode 100644
index 00000000..3a17a26b
--- /dev/null
+++ b/jimmer-dialect/src/test/java/ydb/jimmer/dialect/model/type/ydbBool/YdbBoolean.java
@@ -0,0 +1,17 @@
+package ydb.jimmer.dialect.model.type.ydbBool;
+
+import org.babyfish.jimmer.sql.Column;
+import org.babyfish.jimmer.sql.Entity;
+import org.babyfish.jimmer.sql.Id;
+import org.babyfish.jimmer.sql.Table;
+
+@Entity
+@Table(name = "ydb_boolean")
+public interface YdbBoolean {
+ @Id
+ @Column(name = "id")
+ int getId();
+
+ @Column(name = "value")
+ boolean value();
+}
diff --git a/jimmer-dialect/src/test/java/ydb/jimmer/dialect/model/type/ydbBool/YdbBooleanClass.java b/jimmer-dialect/src/test/java/ydb/jimmer/dialect/model/type/ydbBool/YdbBooleanClass.java
new file mode 100644
index 00000000..02d7da81
--- /dev/null
+++ b/jimmer-dialect/src/test/java/ydb/jimmer/dialect/model/type/ydbBool/YdbBooleanClass.java
@@ -0,0 +1,17 @@
+package ydb.jimmer.dialect.model.type.ydbBool;
+
+import org.babyfish.jimmer.sql.Column;
+import org.babyfish.jimmer.sql.Entity;
+import org.babyfish.jimmer.sql.Id;
+import org.babyfish.jimmer.sql.Table;
+
+@Entity
+@Table(name = "ydb_boolean_class")
+public interface YdbBooleanClass {
+ @Id
+ @Column(name = "id")
+ int getId();
+
+ @Column(name = "value")
+ Boolean value();
+}
diff --git a/jimmer-dialect/src/test/java/ydb/jimmer/dialect/model/type/ydbDate32/YdbDate.java b/jimmer-dialect/src/test/java/ydb/jimmer/dialect/model/type/ydbDate32/YdbDate.java
new file mode 100644
index 00000000..3d7411bc
--- /dev/null
+++ b/jimmer-dialect/src/test/java/ydb/jimmer/dialect/model/type/ydbDate32/YdbDate.java
@@ -0,0 +1,20 @@
+package ydb.jimmer.dialect.model.type.ydbDate32;
+
+import org.babyfish.jimmer.sql.Column;
+import org.babyfish.jimmer.sql.Entity;
+import org.babyfish.jimmer.sql.Id;
+import org.babyfish.jimmer.sql.Table;
+
+import java.sql.Date;
+import java.time.LocalDate;
+
+@Entity
+@Table(name = "ydb_date")
+public interface YdbDate {
+ @Id
+ @Column(name = "id")
+ int getId();
+
+ @Column(name = "value")
+ Date value();
+}
diff --git a/jimmer-dialect/src/test/java/ydb/jimmer/dialect/model/type/ydbDate32/YdbLocalDate.java b/jimmer-dialect/src/test/java/ydb/jimmer/dialect/model/type/ydbDate32/YdbLocalDate.java
new file mode 100644
index 00000000..e46c2b37
--- /dev/null
+++ b/jimmer-dialect/src/test/java/ydb/jimmer/dialect/model/type/ydbDate32/YdbLocalDate.java
@@ -0,0 +1,19 @@
+package ydb.jimmer.dialect.model.type.ydbDate32;
+
+import org.babyfish.jimmer.sql.Column;
+import org.babyfish.jimmer.sql.Entity;
+import org.babyfish.jimmer.sql.Id;
+import org.babyfish.jimmer.sql.Table;
+
+import java.time.LocalDate;
+
+@Entity
+@Table(name = "ydb_local_date")
+public interface YdbLocalDate {
+ @Id
+ @Column(name = "id")
+ int getId();
+
+ @Column(name = "value")
+ LocalDate value();
+}
diff --git a/jimmer-dialect/src/test/java/ydb/jimmer/dialect/model/type/ydbDatetime64/YdbLocalDateTime.java b/jimmer-dialect/src/test/java/ydb/jimmer/dialect/model/type/ydbDatetime64/YdbLocalDateTime.java
new file mode 100644
index 00000000..6ebbebb3
--- /dev/null
+++ b/jimmer-dialect/src/test/java/ydb/jimmer/dialect/model/type/ydbDatetime64/YdbLocalDateTime.java
@@ -0,0 +1,19 @@
+package ydb.jimmer.dialect.model.type.ydbDatetime64;
+
+import org.babyfish.jimmer.sql.Column;
+import org.babyfish.jimmer.sql.Entity;
+import org.babyfish.jimmer.sql.Id;
+import org.babyfish.jimmer.sql.Table;
+
+import java.time.LocalDateTime;
+
+@Entity
+@Table(name = "ydb_local_date_time")
+public interface YdbLocalDateTime {
+ @Id
+ @Column(name = "id")
+ int getId();
+
+ @Column(name = "value")
+ LocalDateTime value();
+}
diff --git a/jimmer-dialect/src/test/java/ydb/jimmer/dialect/model/type/ydbDecimal/YdbBigDecimal.java b/jimmer-dialect/src/test/java/ydb/jimmer/dialect/model/type/ydbDecimal/YdbBigDecimal.java
new file mode 100644
index 00000000..b664a6e0
--- /dev/null
+++ b/jimmer-dialect/src/test/java/ydb/jimmer/dialect/model/type/ydbDecimal/YdbBigDecimal.java
@@ -0,0 +1,19 @@
+package ydb.jimmer.dialect.model.type.ydbDecimal;
+
+import org.babyfish.jimmer.sql.Column;
+import org.babyfish.jimmer.sql.Entity;
+import org.babyfish.jimmer.sql.Id;
+import org.babyfish.jimmer.sql.Table;
+
+import java.math.BigDecimal;
+
+@Entity
+@Table(name = "ydb_big_decimal")
+public interface YdbBigDecimal {
+ @Id
+ @Column(name = "id")
+ int getId();
+
+ @Column(name = "value")
+ BigDecimal value();
+}
diff --git a/jimmer-dialect/src/test/java/ydb/jimmer/dialect/model/type/ydbDouble/YdbDouble.java b/jimmer-dialect/src/test/java/ydb/jimmer/dialect/model/type/ydbDouble/YdbDouble.java
new file mode 100644
index 00000000..d0308496
--- /dev/null
+++ b/jimmer-dialect/src/test/java/ydb/jimmer/dialect/model/type/ydbDouble/YdbDouble.java
@@ -0,0 +1,17 @@
+package ydb.jimmer.dialect.model.type.ydbDouble;
+
+import org.babyfish.jimmer.sql.Column;
+import org.babyfish.jimmer.sql.Entity;
+import org.babyfish.jimmer.sql.Id;
+import org.babyfish.jimmer.sql.Table;
+
+@Entity
+@Table(name = "ydb_double")
+public interface YdbDouble {
+ @Id
+ @Column(name = "id")
+ int getId();
+
+ @Column(name = "value")
+ double value();
+}
diff --git a/jimmer-dialect/src/test/java/ydb/jimmer/dialect/model/type/ydbDouble/YdbDoubleClass.java b/jimmer-dialect/src/test/java/ydb/jimmer/dialect/model/type/ydbDouble/YdbDoubleClass.java
new file mode 100644
index 00000000..f4ec49d7
--- /dev/null
+++ b/jimmer-dialect/src/test/java/ydb/jimmer/dialect/model/type/ydbDouble/YdbDoubleClass.java
@@ -0,0 +1,17 @@
+package ydb.jimmer.dialect.model.type.ydbDouble;
+
+import org.babyfish.jimmer.sql.Column;
+import org.babyfish.jimmer.sql.Entity;
+import org.babyfish.jimmer.sql.Id;
+import org.babyfish.jimmer.sql.Table;
+
+@Entity
+@Table(name = "ydb_double_class")
+public interface YdbDoubleClass {
+ @Id
+ @Column(name = "id")
+ int getId();
+
+ @Column(name = "value")
+ Double value();
+}
diff --git a/jimmer-dialect/src/test/java/ydb/jimmer/dialect/model/type/ydbEnum/Value.java b/jimmer-dialect/src/test/java/ydb/jimmer/dialect/model/type/ydbEnum/Value.java
new file mode 100644
index 00000000..24a524c9
--- /dev/null
+++ b/jimmer-dialect/src/test/java/ydb/jimmer/dialect/model/type/ydbEnum/Value.java
@@ -0,0 +1,8 @@
+package ydb.jimmer.dialect.model.type.ydbEnum;
+
+import org.babyfish.jimmer.sql.EnumType;
+
+@EnumType(EnumType.Strategy.NAME)
+public enum Value {
+ ONE, TWO
+}
diff --git a/jimmer-dialect/src/test/java/ydb/jimmer/dialect/model/type/ydbEnum/YdbEnum.java b/jimmer-dialect/src/test/java/ydb/jimmer/dialect/model/type/ydbEnum/YdbEnum.java
new file mode 100644
index 00000000..0fe8f5f7
--- /dev/null
+++ b/jimmer-dialect/src/test/java/ydb/jimmer/dialect/model/type/ydbEnum/YdbEnum.java
@@ -0,0 +1,17 @@
+package ydb.jimmer.dialect.model.type.ydbEnum;
+
+import org.babyfish.jimmer.sql.Column;
+import org.babyfish.jimmer.sql.Entity;
+import org.babyfish.jimmer.sql.Id;
+import org.babyfish.jimmer.sql.Table;
+
+@Entity
+@Table(name = "ydb_enum")
+public interface YdbEnum {
+ @Id
+ @Column(name = "id")
+ int getId();
+
+ @Column(name = "value")
+ Value value();
+}
diff --git a/jimmer-dialect/src/test/java/ydb/jimmer/dialect/model/type/ydbFloat/YdbFloat.java b/jimmer-dialect/src/test/java/ydb/jimmer/dialect/model/type/ydbFloat/YdbFloat.java
new file mode 100644
index 00000000..c3d453c8
--- /dev/null
+++ b/jimmer-dialect/src/test/java/ydb/jimmer/dialect/model/type/ydbFloat/YdbFloat.java
@@ -0,0 +1,17 @@
+package ydb.jimmer.dialect.model.type.ydbFloat;
+
+import org.babyfish.jimmer.sql.Column;
+import org.babyfish.jimmer.sql.Entity;
+import org.babyfish.jimmer.sql.Id;
+import org.babyfish.jimmer.sql.Table;
+
+@Entity
+@Table(name = "ydb_float")
+public interface YdbFloat {
+ @Id
+ @Column(name = "id")
+ int getId();
+
+ @Column(name = "value")
+ float value();
+}
diff --git a/jimmer-dialect/src/test/java/ydb/jimmer/dialect/model/type/ydbFloat/YdbFloatClass.java b/jimmer-dialect/src/test/java/ydb/jimmer/dialect/model/type/ydbFloat/YdbFloatClass.java
new file mode 100644
index 00000000..5cd4fe4e
--- /dev/null
+++ b/jimmer-dialect/src/test/java/ydb/jimmer/dialect/model/type/ydbFloat/YdbFloatClass.java
@@ -0,0 +1,17 @@
+package ydb.jimmer.dialect.model.type.ydbFloat;
+
+import org.babyfish.jimmer.sql.Column;
+import org.babyfish.jimmer.sql.Entity;
+import org.babyfish.jimmer.sql.Id;
+import org.babyfish.jimmer.sql.Table;
+
+@Entity
+@Table(name = "ydb_float_class")
+public interface YdbFloatClass {
+ @Id
+ @Column(name = "id")
+ int getId();
+
+ @Column(name = "value")
+ Float value();
+}
diff --git a/jimmer-dialect/src/test/java/ydb/jimmer/dialect/model/type/ydbInt16/YdbShort.java b/jimmer-dialect/src/test/java/ydb/jimmer/dialect/model/type/ydbInt16/YdbShort.java
new file mode 100644
index 00000000..c8ce74a9
--- /dev/null
+++ b/jimmer-dialect/src/test/java/ydb/jimmer/dialect/model/type/ydbInt16/YdbShort.java
@@ -0,0 +1,17 @@
+package ydb.jimmer.dialect.model.type.ydbInt16;
+
+import org.babyfish.jimmer.sql.Column;
+import org.babyfish.jimmer.sql.Entity;
+import org.babyfish.jimmer.sql.Id;
+import org.babyfish.jimmer.sql.Table;
+
+@Entity
+@Table(name = "ydb_short")
+public interface YdbShort {
+ @Id
+ @Column(name = "id")
+ int getId();
+
+ @Column(name = "value")
+ short value();
+}
diff --git a/jimmer-dialect/src/test/java/ydb/jimmer/dialect/model/type/ydbInt16/YdbShortClass.java b/jimmer-dialect/src/test/java/ydb/jimmer/dialect/model/type/ydbInt16/YdbShortClass.java
new file mode 100644
index 00000000..28ec44e0
--- /dev/null
+++ b/jimmer-dialect/src/test/java/ydb/jimmer/dialect/model/type/ydbInt16/YdbShortClass.java
@@ -0,0 +1,17 @@
+package ydb.jimmer.dialect.model.type.ydbInt16;
+
+import org.babyfish.jimmer.sql.Column;
+import org.babyfish.jimmer.sql.Entity;
+import org.babyfish.jimmer.sql.Id;
+import org.babyfish.jimmer.sql.Table;
+
+@Entity
+@Table(name = "ydb_short_class")
+public interface YdbShortClass {
+ @Id
+ @Column(name = "id")
+ int getId();
+
+ @Column(name = "value")
+ Short value();
+}
diff --git a/jimmer-dialect/src/test/java/ydb/jimmer/dialect/model/type/ydbInt32/YdbInt.java b/jimmer-dialect/src/test/java/ydb/jimmer/dialect/model/type/ydbInt32/YdbInt.java
new file mode 100644
index 00000000..dc22ac17
--- /dev/null
+++ b/jimmer-dialect/src/test/java/ydb/jimmer/dialect/model/type/ydbInt32/YdbInt.java
@@ -0,0 +1,17 @@
+package ydb.jimmer.dialect.model.type.ydbInt32;
+
+import org.babyfish.jimmer.sql.Column;
+import org.babyfish.jimmer.sql.Entity;
+import org.babyfish.jimmer.sql.Id;
+import org.babyfish.jimmer.sql.Table;
+
+@Entity
+@Table(name = "ydb_int")
+public interface YdbInt {
+ @Id
+ @Column(name = "id")
+ int getId();
+
+ @Column(name = "value")
+ int value();
+}
diff --git a/jimmer-dialect/src/test/java/ydb/jimmer/dialect/model/type/ydbInt32/YdbInteger.java b/jimmer-dialect/src/test/java/ydb/jimmer/dialect/model/type/ydbInt32/YdbInteger.java
new file mode 100644
index 00000000..a158daf2
--- /dev/null
+++ b/jimmer-dialect/src/test/java/ydb/jimmer/dialect/model/type/ydbInt32/YdbInteger.java
@@ -0,0 +1,17 @@
+package ydb.jimmer.dialect.model.type.ydbInt32;
+
+import org.babyfish.jimmer.sql.Column;
+import org.babyfish.jimmer.sql.Entity;
+import org.babyfish.jimmer.sql.Id;
+import org.babyfish.jimmer.sql.Table;
+
+@Entity
+@Table(name = "ydb_integer")
+public interface YdbInteger {
+ @Id
+ @Column(name = "id")
+ int getId();
+
+ @Column(name = "value")
+ Integer value();
+}
diff --git a/jimmer-dialect/src/test/java/ydb/jimmer/dialect/model/type/ydbInt32/YdbLocalTime.java b/jimmer-dialect/src/test/java/ydb/jimmer/dialect/model/type/ydbInt32/YdbLocalTime.java
new file mode 100644
index 00000000..2f1528df
--- /dev/null
+++ b/jimmer-dialect/src/test/java/ydb/jimmer/dialect/model/type/ydbInt32/YdbLocalTime.java
@@ -0,0 +1,19 @@
+package ydb.jimmer.dialect.model.type.ydbInt32;
+
+import org.babyfish.jimmer.sql.Column;
+import org.babyfish.jimmer.sql.Entity;
+import org.babyfish.jimmer.sql.Id;
+import org.babyfish.jimmer.sql.Table;
+
+import java.time.LocalTime;
+
+@Entity
+@Table(name = "ydb_local_time")
+public interface YdbLocalTime {
+ @Id
+ @Column(name = "id")
+ int getId();
+
+ @Column(name = "value")
+ LocalTime value();
+}
diff --git a/jimmer-dialect/src/test/java/ydb/jimmer/dialect/model/type/ydbInt32/YdbTime.java b/jimmer-dialect/src/test/java/ydb/jimmer/dialect/model/type/ydbInt32/YdbTime.java
new file mode 100644
index 00000000..45db18cb
--- /dev/null
+++ b/jimmer-dialect/src/test/java/ydb/jimmer/dialect/model/type/ydbInt32/YdbTime.java
@@ -0,0 +1,20 @@
+package ydb.jimmer.dialect.model.type.ydbInt32;
+
+import org.babyfish.jimmer.sql.Column;
+import org.babyfish.jimmer.sql.Entity;
+import org.babyfish.jimmer.sql.Id;
+import org.babyfish.jimmer.sql.Table;
+
+import java.sql.Time;
+import java.time.LocalTime;
+
+@Entity
+@Table(name = "ydb_time")
+public interface YdbTime {
+ @Id
+ @Column(name = "id")
+ int getId();
+
+ @Column(name = "value")
+ Time value();
+}
diff --git a/jimmer-dialect/src/test/java/ydb/jimmer/dialect/model/type/ydbInt64/YdbBigInteger.java b/jimmer-dialect/src/test/java/ydb/jimmer/dialect/model/type/ydbInt64/YdbBigInteger.java
new file mode 100644
index 00000000..78a5b138
--- /dev/null
+++ b/jimmer-dialect/src/test/java/ydb/jimmer/dialect/model/type/ydbInt64/YdbBigInteger.java
@@ -0,0 +1,19 @@
+package ydb.jimmer.dialect.model.type.ydbInt64;
+
+import org.babyfish.jimmer.sql.Column;
+import org.babyfish.jimmer.sql.Entity;
+import org.babyfish.jimmer.sql.Id;
+import org.babyfish.jimmer.sql.Table;
+
+import java.math.BigInteger;
+
+@Entity
+@Table(name = "ydb_big_integer")
+public interface YdbBigInteger {
+ @Id
+ @Column(name = "id")
+ int getId();
+
+ @Column(name = "value")
+ BigInteger value();
+}
diff --git a/jimmer-dialect/src/test/java/ydb/jimmer/dialect/model/type/ydbInt64/YdbLong.java b/jimmer-dialect/src/test/java/ydb/jimmer/dialect/model/type/ydbInt64/YdbLong.java
new file mode 100644
index 00000000..0cb82320
--- /dev/null
+++ b/jimmer-dialect/src/test/java/ydb/jimmer/dialect/model/type/ydbInt64/YdbLong.java
@@ -0,0 +1,17 @@
+package ydb.jimmer.dialect.model.type.ydbInt64;
+
+import org.babyfish.jimmer.sql.Column;
+import org.babyfish.jimmer.sql.Entity;
+import org.babyfish.jimmer.sql.Id;
+import org.babyfish.jimmer.sql.Table;
+
+@Entity
+@Table(name = "ydb_long")
+public interface YdbLong {
+ @Id
+ @Column(name = "id")
+ int getId();
+
+ @Column(name = "value")
+ long value();
+}
diff --git a/jimmer-dialect/src/test/java/ydb/jimmer/dialect/model/type/ydbInt64/YdbLongClass.java b/jimmer-dialect/src/test/java/ydb/jimmer/dialect/model/type/ydbInt64/YdbLongClass.java
new file mode 100644
index 00000000..acf706ab
--- /dev/null
+++ b/jimmer-dialect/src/test/java/ydb/jimmer/dialect/model/type/ydbInt64/YdbLongClass.java
@@ -0,0 +1,17 @@
+package ydb.jimmer.dialect.model.type.ydbInt64;
+
+import org.babyfish.jimmer.sql.Column;
+import org.babyfish.jimmer.sql.Entity;
+import org.babyfish.jimmer.sql.Id;
+import org.babyfish.jimmer.sql.Table;
+
+@Entity
+@Table(name = "ydb_long_class")
+public interface YdbLongClass {
+ @Id
+ @Column(name = "id")
+ int getId();
+
+ @Column(name = "value")
+ Long value();
+}
diff --git a/jimmer-dialect/src/test/java/ydb/jimmer/dialect/model/type/ydbInt8/YdbByte.java b/jimmer-dialect/src/test/java/ydb/jimmer/dialect/model/type/ydbInt8/YdbByte.java
new file mode 100644
index 00000000..83fb8eb3
--- /dev/null
+++ b/jimmer-dialect/src/test/java/ydb/jimmer/dialect/model/type/ydbInt8/YdbByte.java
@@ -0,0 +1,17 @@
+package ydb.jimmer.dialect.model.type.ydbInt8;
+
+import org.babyfish.jimmer.sql.Column;
+import org.babyfish.jimmer.sql.Entity;
+import org.babyfish.jimmer.sql.Id;
+import org.babyfish.jimmer.sql.Table;
+
+@Entity
+@Table(name = "ydb_byte")
+public interface YdbByte {
+ @Id
+ @Column(name = "id")
+ int getId();
+
+ @Column(name = "value")
+ byte value();
+}
diff --git a/jimmer-dialect/src/test/java/ydb/jimmer/dialect/model/type/ydbInt8/YdbByteClass.java b/jimmer-dialect/src/test/java/ydb/jimmer/dialect/model/type/ydbInt8/YdbByteClass.java
new file mode 100644
index 00000000..c05df232
--- /dev/null
+++ b/jimmer-dialect/src/test/java/ydb/jimmer/dialect/model/type/ydbInt8/YdbByteClass.java
@@ -0,0 +1,17 @@
+package ydb.jimmer.dialect.model.type.ydbInt8;
+
+import org.babyfish.jimmer.sql.Column;
+import org.babyfish.jimmer.sql.Entity;
+import org.babyfish.jimmer.sql.Id;
+import org.babyfish.jimmer.sql.Table;
+
+@Entity
+@Table(name = "ydb_byte_class")
+public interface YdbByteClass {
+ @Id
+ @Column(name = "id")
+ int getId();
+
+ @Column(name = "value")
+ Byte value();
+}
diff --git a/jimmer-dialect/src/test/java/ydb/jimmer/dialect/model/type/ydbInterval64/YdbDuration.java b/jimmer-dialect/src/test/java/ydb/jimmer/dialect/model/type/ydbInterval64/YdbDuration.java
new file mode 100644
index 00000000..ed4551bc
--- /dev/null
+++ b/jimmer-dialect/src/test/java/ydb/jimmer/dialect/model/type/ydbInterval64/YdbDuration.java
@@ -0,0 +1,19 @@
+package ydb.jimmer.dialect.model.type.ydbInterval64;
+
+import org.babyfish.jimmer.sql.Column;
+import org.babyfish.jimmer.sql.Entity;
+import org.babyfish.jimmer.sql.Id;
+import org.babyfish.jimmer.sql.Table;
+
+import java.time.Duration;
+
+@Entity
+@Table(name = "ydb_duration")
+public interface YdbDuration {
+ @Id
+ @Column(name = "id")
+ int getId();
+
+ @Column(name = "value")
+ Duration value();
+}
diff --git a/jimmer-dialect/src/test/java/ydb/jimmer/dialect/model/type/ydbJson/Json.java b/jimmer-dialect/src/test/java/ydb/jimmer/dialect/model/type/ydbJson/Json.java
new file mode 100644
index 00000000..29c4febb
--- /dev/null
+++ b/jimmer-dialect/src/test/java/ydb/jimmer/dialect/model/type/ydbJson/Json.java
@@ -0,0 +1,25 @@
+package ydb.jimmer.dialect.model.type.ydbJson;
+
+import org.babyfish.jimmer.sql.Serialized;
+
+@Serialized
+public class Json {
+ private Integer a;
+ private Integer b;
+
+ public Integer getA() {
+ return a;
+ }
+
+ public Integer getB() {
+ return b;
+ }
+
+ public void setA(Integer a) {
+ this.a = a;
+ }
+
+ public void setB(Integer b) {
+ this.b = b;
+ }
+}
diff --git a/jimmer-dialect/src/test/java/ydb/jimmer/dialect/model/type/ydbJson/YdbJson.java b/jimmer-dialect/src/test/java/ydb/jimmer/dialect/model/type/ydbJson/YdbJson.java
new file mode 100644
index 00000000..ed48b625
--- /dev/null
+++ b/jimmer-dialect/src/test/java/ydb/jimmer/dialect/model/type/ydbJson/YdbJson.java
@@ -0,0 +1,17 @@
+package ydb.jimmer.dialect.model.type.ydbJson;
+
+import org.babyfish.jimmer.sql.Column;
+import org.babyfish.jimmer.sql.Entity;
+import org.babyfish.jimmer.sql.Id;
+import org.babyfish.jimmer.sql.Table;
+
+@Entity
+@Table(name = "ydb_json")
+public interface YdbJson {
+ @Id
+ @Column(name = "id")
+ int getId();
+
+ @Column(name = "value")
+ Json value();
+}
diff --git a/jimmer-dialect/src/test/java/ydb/jimmer/dialect/model/type/ydbString/YdbByteArray.java b/jimmer-dialect/src/test/java/ydb/jimmer/dialect/model/type/ydbString/YdbByteArray.java
new file mode 100644
index 00000000..354bff96
--- /dev/null
+++ b/jimmer-dialect/src/test/java/ydb/jimmer/dialect/model/type/ydbString/YdbByteArray.java
@@ -0,0 +1,17 @@
+package ydb.jimmer.dialect.model.type.ydbString;
+
+import org.babyfish.jimmer.sql.Column;
+import org.babyfish.jimmer.sql.Entity;
+import org.babyfish.jimmer.sql.Id;
+import org.babyfish.jimmer.sql.Table;
+
+@Entity
+@Table(name = "ydb_byte_array")
+public interface YdbByteArray {
+ @Id
+ @Column(name = "id")
+ int getId();
+
+ @Column(name = "value")
+ byte[] value();
+}
diff --git a/jimmer-dialect/src/test/java/ydb/jimmer/dialect/model/type/ydbTimestamp64/YdbInstant.java b/jimmer-dialect/src/test/java/ydb/jimmer/dialect/model/type/ydbTimestamp64/YdbInstant.java
new file mode 100644
index 00000000..aef30ac3
--- /dev/null
+++ b/jimmer-dialect/src/test/java/ydb/jimmer/dialect/model/type/ydbTimestamp64/YdbInstant.java
@@ -0,0 +1,19 @@
+package ydb.jimmer.dialect.model.type.ydbTimestamp64;
+
+import org.babyfish.jimmer.sql.Column;
+import org.babyfish.jimmer.sql.Entity;
+import org.babyfish.jimmer.sql.Id;
+import org.babyfish.jimmer.sql.Table;
+
+import java.time.Instant;
+
+@Entity
+@Table(name = "ydb_instant")
+public interface YdbInstant {
+ @Id
+ @Column(name = "id")
+ int getId();
+
+ @Column(name = "value")
+ Instant value();
+}
diff --git a/jimmer-dialect/src/test/java/ydb/jimmer/dialect/model/type/ydbTimestamp64/YdbTimestamp.java b/jimmer-dialect/src/test/java/ydb/jimmer/dialect/model/type/ydbTimestamp64/YdbTimestamp.java
new file mode 100644
index 00000000..8bbb9bfa
--- /dev/null
+++ b/jimmer-dialect/src/test/java/ydb/jimmer/dialect/model/type/ydbTimestamp64/YdbTimestamp.java
@@ -0,0 +1,20 @@
+package ydb.jimmer.dialect.model.type.ydbTimestamp64;
+
+import org.babyfish.jimmer.sql.Column;
+import org.babyfish.jimmer.sql.Entity;
+import org.babyfish.jimmer.sql.Id;
+import org.babyfish.jimmer.sql.Table;
+
+import java.sql.Timestamp;
+import java.time.Instant;
+
+@Entity
+@Table(name = "ydb_timestamp")
+public interface YdbTimestamp {
+ @Id
+ @Column(name = "id")
+ int getId();
+
+ @Column(name = "value")
+ Timestamp value();
+}
diff --git a/jimmer-dialect/src/test/java/ydb/jimmer/dialect/model/type/ydbTimestamp64/YdbUtilDate.java b/jimmer-dialect/src/test/java/ydb/jimmer/dialect/model/type/ydbTimestamp64/YdbUtilDate.java
new file mode 100644
index 00000000..a4225848
--- /dev/null
+++ b/jimmer-dialect/src/test/java/ydb/jimmer/dialect/model/type/ydbTimestamp64/YdbUtilDate.java
@@ -0,0 +1,20 @@
+package ydb.jimmer.dialect.model.type.ydbTimestamp64;
+
+import org.babyfish.jimmer.sql.Column;
+import org.babyfish.jimmer.sql.Entity;
+import org.babyfish.jimmer.sql.Id;
+import org.babyfish.jimmer.sql.Table;
+
+import java.sql.Timestamp;
+import java.util.Date;
+
+@Entity
+@Table(name = "ydb_util_date")
+public interface YdbUtilDate {
+ @Id
+ @Column(name = "id")
+ int getId();
+
+ @Column(name = "value")
+ Date value();
+}
diff --git a/jimmer-dialect/src/test/java/ydb/jimmer/dialect/model/type/ydbUtf8/YdbString.java b/jimmer-dialect/src/test/java/ydb/jimmer/dialect/model/type/ydbUtf8/YdbString.java
new file mode 100644
index 00000000..f30c172c
--- /dev/null
+++ b/jimmer-dialect/src/test/java/ydb/jimmer/dialect/model/type/ydbUtf8/YdbString.java
@@ -0,0 +1,17 @@
+package ydb.jimmer.dialect.model.type.ydbUtf8;
+
+import org.babyfish.jimmer.sql.Column;
+import org.babyfish.jimmer.sql.Entity;
+import org.babyfish.jimmer.sql.Id;
+import org.babyfish.jimmer.sql.Table;
+
+@Entity
+@Table(name = "ydb_string")
+public interface YdbString {
+ @Id
+ @Column(name = "id")
+ int getId();
+
+ @Column(name = "value")
+ String value();
+}
diff --git a/jimmer-dialect/src/test/java/ydb/jimmer/dialect/model/type/ydbUuid/YdbUuid.java b/jimmer-dialect/src/test/java/ydb/jimmer/dialect/model/type/ydbUuid/YdbUuid.java
new file mode 100644
index 00000000..2eab2801
--- /dev/null
+++ b/jimmer-dialect/src/test/java/ydb/jimmer/dialect/model/type/ydbUuid/YdbUuid.java
@@ -0,0 +1,19 @@
+package ydb.jimmer.dialect.model.type.ydbUuid;
+
+import org.babyfish.jimmer.sql.Column;
+import org.babyfish.jimmer.sql.Entity;
+import org.babyfish.jimmer.sql.Id;
+import org.babyfish.jimmer.sql.Table;
+
+import java.util.UUID;
+
+@Entity
+@Table(name = "ydb_uuid")
+public interface YdbUuid {
+ @Id
+ @Column(name = "id")
+ int getId();
+
+ @Column(name = "value")
+ UUID value();
+}
diff --git a/jimmer-dialect/src/test/java/ydb/jimmer/dialect/model/versioning/BookStore.java b/jimmer-dialect/src/test/java/ydb/jimmer/dialect/model/versioning/BookStore.java
new file mode 100644
index 00000000..081ceda8
--- /dev/null
+++ b/jimmer-dialect/src/test/java/ydb/jimmer/dialect/model/versioning/BookStore.java
@@ -0,0 +1,26 @@
+package ydb.jimmer.dialect.model.versioning;
+
+import org.babyfish.jimmer.sql.Column;
+import org.babyfish.jimmer.sql.Entity;
+import org.babyfish.jimmer.sql.Id;
+import org.babyfish.jimmer.sql.Key;
+import org.babyfish.jimmer.sql.KeyUniqueConstraint;
+import org.babyfish.jimmer.sql.Table;
+import org.babyfish.jimmer.sql.Version;
+
+@Entity
+//@KeyUniqueConstraint
+@Table(name = "version_table")
+public interface BookStore {
+ @Id
+ @Column(name = "id")
+ int id();
+
+// @Key
+// @Column(name = "name")
+// String name();
+
+ @Version
+ @Column(name = "value")
+ int value();
+}
diff --git a/jimmer-dialect/src/test/java/ydb/jimmer/dialect/pagination/KeysetTest.java b/jimmer-dialect/src/test/java/ydb/jimmer/dialect/pagination/KeysetTest.java
new file mode 100644
index 00000000..3ed274c1
--- /dev/null
+++ b/jimmer-dialect/src/test/java/ydb/jimmer/dialect/pagination/KeysetTest.java
@@ -0,0 +1,71 @@
+package ydb.jimmer.dialect.pagination;
+
+import org.junit.jupiter.api.Test;
+import ydb.jimmer.dialect.AbstractSelectTest;
+import ydb.jimmer.dialect.QueryTestContext;
+import ydb.jimmer.dialect.YdbKeysetPaginator;
+import ydb.jimmer.dialect.model.Entity;
+import ydb.jimmer.dialect.model.EntityTable;
+
+import java.util.Arrays;
+import java.util.List;
+
+public class KeysetTest extends AbstractSelectTest {
+ private static final String TABLE_NAME = "simple_table";
+ private static final String TYPE_NAME = "Int32";
+
+ private static final int N = 100;
+ private static final int LIMIT = 20;
+ private static final String[] VALUES = new String[100];
+
+ static {
+ for (int i = 0; i < N; i++) {
+ VALUES[i] = String.valueOf(i);
+ }
+ }
+
+ @Test
+ public void simpleTest() {
+ createTable(TABLE_NAME, TYPE_NAME);
+ insert(TABLE_NAME, VALUES);
+
+ YdbKeysetPaginator paginator = new YdbKeysetPaginator(getIsolationClient());
+
+ EntityTable table = EntityTable.$;
+
+ YdbKeysetPaginator.Page page = null;
+ for (int i = 0; i < N; i += LIMIT) {
+ List