From 28e0645e5b8a8484aab3ee419301494d3160d8ca Mon Sep 17 00:00:00 2001 From: KirillKurdyukov Date: Fri, 11 Sep 2026 15:39:43 +0300 Subject: [PATCH 01/10] Support YDB Date and Date32 in Trino --- .github/workflows/ci-trino-adapter.yaml | 10 +- ydb-trino-adapter/README.md | 9 ++ .../main/java/tech/ydb/trino/YdbClient.java | 69 ++++++++++- .../tech/ydb/trino/TestYdbConnectorTest.java | 108 +++++++++++++++--- 4 files changed, 177 insertions(+), 19 deletions(-) diff --git a/.github/workflows/ci-trino-adapter.yaml b/.github/workflows/ci-trino-adapter.yaml index 1b251633..2e45ba0d 100644 --- a/.github/workflows/ci-trino-adapter.yaml +++ b/.github/workflows/ci-trino-adapter.yaml @@ -40,4 +40,12 @@ jobs: - name: Build and test Trino Adapter working-directory: ./ydb-trino-adapter - run: mvn $MAVEN_ARGS clean test + run: mvn $MAVEN_ARGS -Duser.timezone=UTC clean test + + - name: Build and test Trino Adapter with signed datetimes + working-directory: ./ydb-trino-adapter + run: mvn $MAVEN_ARGS -Duser.timezone=UTC -Dydb.test.force-signed-datetimes=true clean test + + - name: Test native dates in Europe/Moscow + working-directory: ./ydb-trino-adapter + run: mvn $MAVEN_ARGS -Duser.timezone=Europe/Moscow -Dtest='TestYdbConnectorTest#testNativeDateCompatibility' test diff --git a/ydb-trino-adapter/README.md b/ydb-trino-adapter/README.md index 40b27603..a5aa6fc9 100644 --- a/ydb-trino-adapter/README.md +++ b/ydb-trino-adapter/README.md @@ -49,3 +49,12 @@ SELECT * FROM local.default.orders; YDB `Text` отображается в Trino как `varchar`, а `Bytes` — как `varbinary` без декодирования UTF-8. При создании таблиц адаптер использует типы `Text` и `Bytes`. + +## Даты + +По умолчанию Trino `date` создаётся как YDB `Date`. Параметр JDBC URL +`forceSignedDatetimes=true` переключает новые столбцы на `Date32`; существующие +столбцы обоих типов поддерживаются независимо от этого параметра. Для MERGE с +такими столбцами требуется стандартная подготовка запросов YDB JDBC — режим +`disablePrepareDataQuery=true` не поддерживается. Здесь проверена совместимость +только `Date` и `Date32`. diff --git a/ydb-trino-adapter/src/main/java/tech/ydb/trino/YdbClient.java b/ydb-trino-adapter/src/main/java/tech/ydb/trino/YdbClient.java index 466a277c..07aa5636 100644 --- a/ydb-trino-adapter/src/main/java/tech/ydb/trino/YdbClient.java +++ b/ydb-trino-adapter/src/main/java/tech/ydb/trino/YdbClient.java @@ -23,6 +23,7 @@ import io.trino.plugin.jdbc.JdbcSortItem; import io.trino.plugin.jdbc.JdbcTableHandle; import io.trino.plugin.jdbc.JdbcTypeHandle; +import io.trino.plugin.jdbc.LongWriteFunction; import io.trino.plugin.jdbc.PreparedQuery; import io.trino.plugin.jdbc.QueryBuilder; import io.trino.plugin.jdbc.RemoteTableName; @@ -48,6 +49,7 @@ import io.trino.spi.connector.SchemaTableName; import io.trino.spi.connector.SortOrder; import io.trino.spi.expression.ConnectorExpression; +import io.trino.spi.predicate.Domain; import io.trino.spi.type.DecimalType; import io.trino.spi.type.Type; import io.trino.spi.type.VarcharType; @@ -60,6 +62,7 @@ import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Types; +import java.time.LocalDate; import java.util.Collection; import java.util.List; import java.util.Locale; @@ -68,6 +71,7 @@ import java.util.Optional; import java.util.OptionalInt; import java.util.OptionalLong; +import java.util.Properties; import java.util.Set; import java.util.TreeMap; import java.util.function.BiFunction; @@ -76,6 +80,11 @@ import java.util.stream.Collectors; import java.util.stream.Stream; +import tech.ydb.jdbc.settings.YdbConfig; +import tech.ydb.jdbc.settings.YdbOperationProperties; +import tech.ydb.table.values.PrimitiveType; +import tech.ydb.table.values.PrimitiveValue; + import static io.trino.plugin.jdbc.DefaultJdbcMetadata.MERGE_ROW_ID; import static io.trino.plugin.jdbc.JdbcErrorCode.JDBC_ERROR; import static io.trino.plugin.jdbc.PredicatePushdownController.DISABLE_PUSHDOWN; @@ -127,10 +136,16 @@ public class YdbClient extends BaseJdbcClient { static final String DEFAULT_SCHEMA = "default"; private static final int YDB_DEFAULT_DECIMAL_PRECISION = 22; private static final int YDB_DEFAULT_DECIMAL_SCALE = 9; + // https://github.com/ydb-platform/ydb/blob/0b875c4c2a25249d17ef41db8ea1806c6882a95e/yql/essentials/public/udf/udf_data_type.h#L162-L176 + private static final long YDB_DATE_MIN = 0; + private static final long YDB_DATE_MAX = 49_673; + private static final long YDB_DATE32_MIN = -53_375_809; + private static final long YDB_DATE32_MAX = 53_375_808; private final ConnectorExpressionRewriter connectorExpressionRewriter; private final AggregateFunctionRewriter aggregateFunctionRewriter; private final ProjectFunctionRewriter projectFunctionRewriter; + private final boolean forceSignedDatetimes; @Inject public YdbClient( @@ -150,6 +165,14 @@ public YdbClient( true ); + try { + forceSignedDatetimes = new YdbOperationProperties(YdbConfig.from(config.getConnectionUrl(), new Properties())) + .getForceNewDatetypes(); + } + catch (SQLException e) { + throw new TrinoException(JDBC_ERROR, "Invalid YDB JDBC configuration", e); + } + this.connectorExpressionRewriter = JdbcConnectorExpressionRewriterBuilder.newBuilder() .addStandardRules(this::quoted) .add(new RewriteIn()) @@ -338,7 +361,7 @@ public Optional toColumnMapping( : typeHandle.columnSize().orElse(VarcharType.MAX_LENGTH); yield Optional.of(varcharColumnMapping(length)); } - case Types.DATE -> Optional.of(dateColumnMapping()); + case Types.DATE -> Optional.of(dateColumnMapping(jdbcTypeName.equals("date32"))); case Types.TIMESTAMP -> Optional.of(timestampColumnMapping()); default -> Optional.empty(); }; @@ -370,11 +393,49 @@ private static ColumnMapping varcharColumnMapping(int varcharLength) { FULL_PUSHDOWN); } - private static ColumnMapping dateColumnMapping() { + private static ColumnMapping dateColumnMapping(boolean signed) { return ColumnMapping.longMapping( DATE, dateReadFunctionUsingLocalDate(), - dateWriteFunctionUsingLocalDate()); + dateWriteFunction(signed), + (session, domain) -> isDateDomainSupported(domain, signed) + ? FULL_PUSHDOWN.apply(session, domain) + : DISABLE_PUSHDOWN.apply(session, domain)); + } + + private static boolean isDateDomainSupported(Domain domain, boolean signed) { + if (domain.getType() != DATE) { + return true; + } + return domain.getValues().getRanges().getOrderedRanges().stream().allMatch(range -> + (range.isLowUnbounded() || isDateSupported((long) range.getLowBoundedValue(), signed)) && + (range.isHighUnbounded() || isDateSupported((long) range.getHighBoundedValue(), signed))); + } + + private static boolean isDateSupported(long value, boolean signed) { + return signed + ? value >= YDB_DATE32_MIN && value < YDB_DATE32_MAX + : value >= YDB_DATE_MIN && value < YDB_DATE_MAX; + } + + private static LongWriteFunction dateWriteFunction(boolean signed) { + PrimitiveType type = signed ? PrimitiveType.Date32 : PrimitiveType.Date; + return new LongWriteFunction() { + @Override + public void set(PreparedStatement statement, int index, long value) throws SQLException { + if (!isDateSupported(value, signed)) { + throw new SQLException("Date epoch day %s is outside YDB %s range" + .formatted(value, signed ? "Date32" : "Date")); + } + LocalDate date = LocalDate.ofEpochDay(value); + statement.setObject(index, signed ? PrimitiveValue.newDate32(date) : PrimitiveValue.newDate(date)); + } + + @Override + public void setNull(PreparedStatement statement, int index) throws SQLException { + statement.setObject(index, type.makeOptional().emptyValue()); + } + }; } private static ColumnMapping timestampColumnMapping() { @@ -420,7 +481,7 @@ public WriteMapping toWriteMapping(ConnectorSession session, Type type) { return WriteMapping.sliceMapping("Bytes", varbinaryWriteFunction()); } if (type == DATE) { - return WriteMapping.longMapping("Date", dateWriteFunctionUsingLocalDate()); + return WriteMapping.longMapping(forceSignedDatetimes ? "Date32" : "Date", dateWriteFunctionUsingLocalDate()); } if (type == TIMESTAMP_MICROS) { return WriteMapping.longMapping("Timestamp", timestampWriteFunction(TIMESTAMP_MICROS)); diff --git a/ydb-trino-adapter/src/test/java/tech/ydb/trino/TestYdbConnectorTest.java b/ydb-trino-adapter/src/test/java/tech/ydb/trino/TestYdbConnectorTest.java index 11f8d34f..68bcd9f4 100644 --- a/ydb-trino-adapter/src/test/java/tech/ydb/trino/TestYdbConnectorTest.java +++ b/ydb-trino-adapter/src/test/java/tech/ydb/trino/TestYdbConnectorTest.java @@ -1,5 +1,6 @@ package tech.ydb.trino; +import com.google.common.collect.ImmutableMap; import io.trino.spi.type.Type; import io.trino.spi.type.VarcharType; import io.trino.testing.BaseConnectorTest; @@ -13,22 +14,36 @@ import org.junit.jupiter.api.extension.RegisterExtension; import tech.ydb.test.junit5.YdbHelperExtension; +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.ResultSet; +import java.sql.Statement; +import java.time.LocalDate; import java.util.Optional; import java.util.OptionalInt; +import static io.trino.testing.TestingNames.randomNameSuffix; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; public class TestYdbConnectorTest extends BaseConnectorTest { + private static final String ALTERNATE_DATE_CATALOG = "alternate_dates"; + private static final boolean FORCE_SIGNED_DATETIMES = Boolean.getBoolean("ydb.test.force-signed-datetimes"); @RegisterExtension static final YdbHelperExtension ydb = new YdbHelperExtension(); @Override protected QueryRunner createQueryRunner() throws Exception { - return YdbQueryRunner.builder(ydb) + String jdbcUrl = YdbQueryRunner.buildJdbcUrl(ydb); + QueryRunner queryRunner = YdbQueryRunner.builder(ydb) + .addConnectorProperty("connection-url", jdbcUrl + "&forceSignedDatetimes=" + FORCE_SIGNED_DATETIMES) .setInitialTables(REQUIRED_TPCH_TABLES) .build(); + queryRunner.createCatalog(ALTERNATE_DATE_CATALOG, "ydb", ImmutableMap.of( + "connection-url", jdbcUrl + "&forceSignedDatetimes=" + !FORCE_SIGNED_DATETIMES, + "insert.non-transactional-insert.enabled", "true")); + return queryRunner; } @Test @@ -79,26 +94,19 @@ protected boolean hasBehavior(TestingConnectorBehavior connectorBehavior) { SUPPORTS_DROP_DEFAULT_COLUMN_VALUE, SUPPORTS_ADD_COLUMN_NOT_NULL_CONSTRAINT -> false; case SUPPORTS_TOPN_PUSHDOWN_WITH_VARCHAR -> true; + case SUPPORTS_NEGATIVE_DATE -> FORCE_SIGNED_DATETIMES; default -> super.hasBehavior(connectorBehavior); }; } - @Test - @Override - public void testInsertNegativeDate() { - // YDB не поддерживает, negative daysSinceEpoch - } - - @Test @Override - public void testDateYearOfEraPredicate() { - // YDB не поддерживает, negative daysSinceEpoch + protected String errorMessageForInsertNegativeDate(String date) { + return ".*outside YDB Date range.*"; } - @Test @Override - public void testCreateTableAsSelectNegativeDate() { - // YDB не поддерживает, negative daysSinceEpoch + protected String errorMessageForCreateTableAsSelectNegativeDate(String date) { + return ".*negative daysSinceEpoch.*"; } @Test @@ -142,7 +150,7 @@ protected boolean isColumnNameRejected(Exception exception, String columnName, b protected Optional filterDataMappingSmokeTestData(BaseConnectorTest.DataMappingTestSetup dataMappingTestSetup) { if (dataMappingTestSetup.getTrinoTypeName().equals("char(3)")) { return Optional.of(dataMappingTestSetup.asUnsupported()); - } else if (dataMappingTestSetup.getTrinoTypeName().equals("date")) { + } else if (dataMappingTestSetup.getTrinoTypeName().equals("date") && !FORCE_SIGNED_DATETIMES) { return Optional.of(new DataMappingTestSetup( dataMappingTestSetup.getTrinoTypeName(), "DATE '2006-06-06'", @@ -163,6 +171,78 @@ public void testVarbinaryCreateTableAndInsert() { } } + @Test + public void testNativeDateCompatibility() throws Exception { + verifyNativeDateCompatibility("local"); + verifyNativeDateCompatibility(ALTERNATE_DATE_CATALOG); + } + + private void verifyNativeDateCompatibility(String catalog) throws Exception { + String ddlTable = "trino_date_" + randomNameSuffix(); + boolean signed = catalog.equals("local") ? FORCE_SIGNED_DATETIMES : !FORCE_SIGNED_DATETIMES; + try (TestTable table = new TestTable( + new JdbcSqlExecutor(YdbQueryRunner.buildJdbcUrl(ydb)), + "native_dates_", + "(legacy_key Date NOT NULL, signed_key Date32 NOT NULL, legacy_value Date, signed_value Date32, " + + "PRIMARY KEY (legacy_key, signed_key))")) { + String name = catalog + ".default." + table.getName(); + assertUpdate("INSERT INTO " + name + " VALUES " + + "(DATE '2020-01-01', DATE '-0001-01-01', DATE '2000-01-01', NULL), " + + "(DATE '2020-01-02', DATE '-0001-01-02', NULL, DATE '-0002-01-01'), " + + "(DATE '2020-01-01', DATE '-0001-01-04', DATE '2004-01-01', NULL)", 3); + assertQuery("SELECT legacy_value, signed_value FROM " + name + + " WHERE legacy_key = DATE '2020-01-01' AND signed_key = DATE '-0001-01-01'", + "VALUES (DATE '2000-01-01', CAST(NULL AS DATE))"); + for (long day : new long[]{-1, 0, 49_672, 49_673}) { + assertQueryReturnsEmptyResult("SELECT * FROM " + name + + " WHERE legacy_key = date_add('day', " + day + ", DATE '1970-01-01')"); + } + for (long day : new long[]{-53_375_810, -53_375_809, 53_375_807, 53_375_808}) { + assertQueryReturnsEmptyResult("SELECT * FROM " + name + + " WHERE signed_key = date_add('day', " + day + ", DATE '1970-01-01')"); + } + assertQueryFails("INSERT INTO " + name + " VALUES (DATE '2020-02-01', " + + "date_add('day', 106751992, DATE '1970-01-01'), NULL, NULL)", ".*outside YDB Date32 range.*"); + assertUpdate("UPDATE " + name + " SET legacy_value = DATE '2001-01-01', signed_value = DATE '-0003-01-01'" + + " WHERE legacy_key = DATE '2020-01-01' AND signed_key = DATE '-0001-01-01'", 1); + assertUpdate(""" + MERGE INTO %s t + USING (VALUES + (DATE '2020-01-01', DATE '-0001-01-01', CAST(NULL AS DATE), DATE '-0004-01-01', 'update'), + (DATE '2020-01-02', DATE '-0001-01-02', CAST(NULL AS DATE), CAST(NULL AS DATE), 'delete'), + (DATE '2020-01-03', DATE '-0001-01-03', DATE '2003-01-01', CAST(NULL AS DATE), 'insert') + ) s (legacy_key, signed_key, legacy_value, signed_value, operation) + ON (t.legacy_key = s.legacy_key AND t.signed_key = s.signed_key) + WHEN MATCHED AND s.operation = 'delete' THEN DELETE + WHEN MATCHED THEN UPDATE SET legacy_value = s.legacy_value, signed_value = s.signed_value + WHEN NOT MATCHED THEN INSERT VALUES (s.legacy_key, s.signed_key, s.legacy_value, s.signed_value) + """.formatted(name), 3); + assertQuery("SELECT legacy_key, signed_key, legacy_value, signed_value FROM " + name + " ORDER BY legacy_key", + "VALUES " + + "(DATE '2020-01-01', DATE '-0001-01-01', NULL, DATE '-0004-01-01'), " + + "(DATE '2020-01-01', DATE '-0001-01-04', DATE '2004-01-01', NULL), " + + "(DATE '2020-01-03', DATE '-0001-01-03', DATE '2003-01-01', NULL)"); + try (Connection connection = DriverManager.getConnection(YdbQueryRunner.buildJdbcUrl(ydb)); + Statement statement = connection.createStatement(); + ResultSet rows = statement.executeQuery("SELECT * FROM `" + table.getName() + "` ORDER BY legacy_key, signed_key")) { + assertThat(rows.next()).isTrue(); + assertThat(rows.getObject("legacy_value", LocalDate.class)).isNull(); + assertThat(rows.getObject("signed_value", LocalDate.class)).isEqualTo(LocalDate.of(-4, 1, 1)); + } + } + try { + assertUpdate("CREATE TABLE " + catalog + ".default." + ddlTable + " (value DATE)"); + try (Connection connection = DriverManager.getConnection(YdbQueryRunner.buildJdbcUrl(ydb)); + ResultSet columns = connection.getMetaData().getColumns(null, null, ddlTable, "value")) { + assertThat(columns.next()).isTrue(); + assertThat(columns.getString("TYPE_NAME")).isEqualTo(signed ? "Date32" : "Date"); + } + } + finally { + assertUpdate("DROP TABLE IF EXISTS " + catalog + ".default." + ddlTable); + } + } + @Override protected Optional filterCaseSensitiveDataMappingTestData(DataMappingTestSetup dataMappingTestSetup) { if (dataMappingTestSetup.getTrinoTypeName().equals("char(1)")) { From 909d96ceb5e60efb940c05643f07da504a059c68 Mon Sep 17 00:00:00 2001 From: KirillKurdyukov Date: Fri, 11 Sep 2026 15:48:58 +0300 Subject: [PATCH 02/10] Run Trino date modes on separate CI runners --- .github/workflows/ci-trino-adapter.yaml | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/.github/workflows/ci-trino-adapter.yaml b/.github/workflows/ci-trino-adapter.yaml index 2e45ba0d..7a8aa939 100644 --- a/.github/workflows/ci-trino-adapter.yaml +++ b/.github/workflows/ci-trino-adapter.yaml @@ -21,8 +21,13 @@ jobs: runs-on: ubuntu-latest strategy: + fail-fast: false matrix: java: [ '25' ] + test_args: + - '-Duser.timezone=UTC' + - '-Duser.timezone=UTC -Dydb.test.force-signed-datetimes=true' + - "-Duser.timezone=Europe/Moscow -Dtest='TestYdbConnectorTest#testNativeDateCompatibility'" steps: - uses: actions/checkout@v5 @@ -40,12 +45,4 @@ jobs: - name: Build and test Trino Adapter working-directory: ./ydb-trino-adapter - run: mvn $MAVEN_ARGS -Duser.timezone=UTC clean test - - - name: Build and test Trino Adapter with signed datetimes - working-directory: ./ydb-trino-adapter - run: mvn $MAVEN_ARGS -Duser.timezone=UTC -Dydb.test.force-signed-datetimes=true clean test - - - name: Test native dates in Europe/Moscow - working-directory: ./ydb-trino-adapter - run: mvn $MAVEN_ARGS -Duser.timezone=Europe/Moscow -Dtest='TestYdbConnectorTest#testNativeDateCompatibility' test + run: mvn $MAVEN_ARGS ${{matrix.test_args}} clean test From 4c77760a54be0961317c742512c73ba965b40124 Mon Sep 17 00:00:00 2001 From: KirillKurdyukov Date: Fri, 11 Sep 2026 16:11:12 +0300 Subject: [PATCH 03/10] Use Date32 bindings for Trino dates --- ydb-trino-adapter/README.md | 2 +- .../main/java/tech/ydb/trino/YdbClient.java | 55 ++----------------- .../tech/ydb/trino/TestYdbConnectorTest.java | 16 ++---- 3 files changed, 10 insertions(+), 63 deletions(-) diff --git a/ydb-trino-adapter/README.md b/ydb-trino-adapter/README.md index a5aa6fc9..d91c8af8 100644 --- a/ydb-trino-adapter/README.md +++ b/ydb-trino-adapter/README.md @@ -57,4 +57,4 @@ YDB `Text` отображается в Trino как `varchar`, а `Bytes` — к столбцы обоих типов поддерживаются независимо от этого параметра. Для MERGE с такими столбцами требуется стандартная подготовка запросов YDB JDBC — режим `disablePrepareDataQuery=true` не поддерживается. Здесь проверена совместимость -только `Date` и `Date32`. +только `Date` и `Date32`; предикаты ограничены диапазоном YDB `Date32`. diff --git a/ydb-trino-adapter/src/main/java/tech/ydb/trino/YdbClient.java b/ydb-trino-adapter/src/main/java/tech/ydb/trino/YdbClient.java index 07aa5636..f7a68f7e 100644 --- a/ydb-trino-adapter/src/main/java/tech/ydb/trino/YdbClient.java +++ b/ydb-trino-adapter/src/main/java/tech/ydb/trino/YdbClient.java @@ -23,7 +23,6 @@ import io.trino.plugin.jdbc.JdbcSortItem; import io.trino.plugin.jdbc.JdbcTableHandle; import io.trino.plugin.jdbc.JdbcTypeHandle; -import io.trino.plugin.jdbc.LongWriteFunction; import io.trino.plugin.jdbc.PreparedQuery; import io.trino.plugin.jdbc.QueryBuilder; import io.trino.plugin.jdbc.RemoteTableName; @@ -49,7 +48,6 @@ import io.trino.spi.connector.SchemaTableName; import io.trino.spi.connector.SortOrder; import io.trino.spi.expression.ConnectorExpression; -import io.trino.spi.predicate.Domain; import io.trino.spi.type.DecimalType; import io.trino.spi.type.Type; import io.trino.spi.type.VarcharType; @@ -82,7 +80,6 @@ import tech.ydb.jdbc.settings.YdbConfig; import tech.ydb.jdbc.settings.YdbOperationProperties; -import tech.ydb.table.values.PrimitiveType; import tech.ydb.table.values.PrimitiveValue; import static io.trino.plugin.jdbc.DefaultJdbcMetadata.MERGE_ROW_ID; @@ -92,8 +89,8 @@ import static io.trino.plugin.jdbc.StandardColumnMappings.bigintColumnMapping; import static io.trino.plugin.jdbc.StandardColumnMappings.bigintWriteFunction; import static io.trino.plugin.jdbc.StandardColumnMappings.booleanColumnMapping; -import static io.trino.plugin.jdbc.StandardColumnMappings.dateWriteFunctionUsingLocalDate; import static io.trino.plugin.jdbc.StandardColumnMappings.dateReadFunctionUsingLocalDate; +import static io.trino.plugin.jdbc.StandardColumnMappings.dateWriteFunctionUsingLocalDate; import static io.trino.plugin.jdbc.StandardColumnMappings.decimalColumnMapping; import static io.trino.plugin.jdbc.StandardColumnMappings.doubleColumnMapping; import static io.trino.plugin.jdbc.StandardColumnMappings.doubleWriteFunction; @@ -136,12 +133,6 @@ public class YdbClient extends BaseJdbcClient { static final String DEFAULT_SCHEMA = "default"; private static final int YDB_DEFAULT_DECIMAL_PRECISION = 22; private static final int YDB_DEFAULT_DECIMAL_SCALE = 9; - // https://github.com/ydb-platform/ydb/blob/0b875c4c2a25249d17ef41db8ea1806c6882a95e/yql/essentials/public/udf/udf_data_type.h#L162-L176 - private static final long YDB_DATE_MIN = 0; - private static final long YDB_DATE_MAX = 49_673; - private static final long YDB_DATE32_MIN = -53_375_809; - private static final long YDB_DATE32_MAX = 53_375_808; - private final ConnectorExpressionRewriter connectorExpressionRewriter; private final AggregateFunctionRewriter aggregateFunctionRewriter; private final ProjectFunctionRewriter projectFunctionRewriter; @@ -361,7 +352,7 @@ public Optional toColumnMapping( : typeHandle.columnSize().orElse(VarcharType.MAX_LENGTH); yield Optional.of(varcharColumnMapping(length)); } - case Types.DATE -> Optional.of(dateColumnMapping(jdbcTypeName.equals("date32"))); + case Types.DATE -> Optional.of(dateColumnMapping()); case Types.TIMESTAMP -> Optional.of(timestampColumnMapping()); default -> Optional.empty(); }; @@ -393,49 +384,11 @@ private static ColumnMapping varcharColumnMapping(int varcharLength) { FULL_PUSHDOWN); } - private static ColumnMapping dateColumnMapping(boolean signed) { + private static ColumnMapping dateColumnMapping() { return ColumnMapping.longMapping( DATE, dateReadFunctionUsingLocalDate(), - dateWriteFunction(signed), - (session, domain) -> isDateDomainSupported(domain, signed) - ? FULL_PUSHDOWN.apply(session, domain) - : DISABLE_PUSHDOWN.apply(session, domain)); - } - - private static boolean isDateDomainSupported(Domain domain, boolean signed) { - if (domain.getType() != DATE) { - return true; - } - return domain.getValues().getRanges().getOrderedRanges().stream().allMatch(range -> - (range.isLowUnbounded() || isDateSupported((long) range.getLowBoundedValue(), signed)) && - (range.isHighUnbounded() || isDateSupported((long) range.getHighBoundedValue(), signed))); - } - - private static boolean isDateSupported(long value, boolean signed) { - return signed - ? value >= YDB_DATE32_MIN && value < YDB_DATE32_MAX - : value >= YDB_DATE_MIN && value < YDB_DATE_MAX; - } - - private static LongWriteFunction dateWriteFunction(boolean signed) { - PrimitiveType type = signed ? PrimitiveType.Date32 : PrimitiveType.Date; - return new LongWriteFunction() { - @Override - public void set(PreparedStatement statement, int index, long value) throws SQLException { - if (!isDateSupported(value, signed)) { - throw new SQLException("Date epoch day %s is outside YDB %s range" - .formatted(value, signed ? "Date32" : "Date")); - } - LocalDate date = LocalDate.ofEpochDay(value); - statement.setObject(index, signed ? PrimitiveValue.newDate32(date) : PrimitiveValue.newDate(date)); - } - - @Override - public void setNull(PreparedStatement statement, int index) throws SQLException { - statement.setObject(index, type.makeOptional().emptyValue()); - } - }; + (statement, index, value) -> statement.setObject(index, PrimitiveValue.newDate32(LocalDate.ofEpochDay(value)))); } private static ColumnMapping timestampColumnMapping() { diff --git a/ydb-trino-adapter/src/test/java/tech/ydb/trino/TestYdbConnectorTest.java b/ydb-trino-adapter/src/test/java/tech/ydb/trino/TestYdbConnectorTest.java index 68bcd9f4..407963bc 100644 --- a/ydb-trino-adapter/src/test/java/tech/ydb/trino/TestYdbConnectorTest.java +++ b/ydb-trino-adapter/src/test/java/tech/ydb/trino/TestYdbConnectorTest.java @@ -101,7 +101,7 @@ protected boolean hasBehavior(TestingConnectorBehavior connectorBehavior) { @Override protected String errorMessageForInsertNegativeDate(String date) { - return ".*outside YDB Date range.*"; + return ".*negative daysSinceEpoch.*"; } @Override @@ -193,16 +193,10 @@ private void verifyNativeDateCompatibility(String catalog) throws Exception { assertQuery("SELECT legacy_value, signed_value FROM " + name + " WHERE legacy_key = DATE '2020-01-01' AND signed_key = DATE '-0001-01-01'", "VALUES (DATE '2000-01-01', CAST(NULL AS DATE))"); - for (long day : new long[]{-1, 0, 49_672, 49_673}) { - assertQueryReturnsEmptyResult("SELECT * FROM " + name + - " WHERE legacy_key = date_add('day', " + day + ", DATE '1970-01-01')"); - } - for (long day : new long[]{-53_375_810, -53_375_809, 53_375_807, 53_375_808}) { - assertQueryReturnsEmptyResult("SELECT * FROM " + name + - " WHERE signed_key = date_add('day', " + day + ", DATE '1970-01-01')"); - } - assertQueryFails("INSERT INTO " + name + " VALUES (DATE '2020-02-01', " + - "date_add('day', 106751992, DATE '1970-01-01'), NULL, NULL)", ".*outside YDB Date32 range.*"); + assertQueryReturnsEmptyResult("SELECT * FROM " + name + " WHERE legacy_key = DATE '-0001-01-01'"); + assertQuery("SELECT count(*) FROM " + name + " WHERE legacy_key > DATE '-0001-01-01'", "VALUES BIGINT '3'"); + assertQueryReturnsEmptyResult("SELECT * FROM " + name + " WHERE legacy_key = DATE '2106-01-01'"); + assertQuery("SELECT count(*) FROM " + name + " WHERE legacy_key < DATE '2106-01-01'", "VALUES BIGINT '3'"); assertUpdate("UPDATE " + name + " SET legacy_value = DATE '2001-01-01', signed_value = DATE '-0003-01-01'" + " WHERE legacy_key = DATE '2020-01-01' AND signed_key = DATE '-0001-01-01'", 1); assertUpdate(""" From c9e871d969da68083ffcc1e5a01707bd73f30c79 Mon Sep 17 00:00:00 2001 From: KirillKurdyukov Date: Fri, 11 Sep 2026 16:13:43 +0300 Subject: [PATCH 04/10] Separate Date widening predicate coverage --- .../tech/ydb/trino/TestYdbConnectorTest.java | 24 +++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/ydb-trino-adapter/src/test/java/tech/ydb/trino/TestYdbConnectorTest.java b/ydb-trino-adapter/src/test/java/tech/ydb/trino/TestYdbConnectorTest.java index 407963bc..20e73b73 100644 --- a/ydb-trino-adapter/src/test/java/tech/ydb/trino/TestYdbConnectorTest.java +++ b/ydb-trino-adapter/src/test/java/tech/ydb/trino/TestYdbConnectorTest.java @@ -12,10 +12,12 @@ import org.assertj.core.api.Assertions; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; +import tech.ydb.table.values.PrimitiveValue; import tech.ydb.test.junit5.YdbHelperExtension; import java.sql.Connection; import java.sql.DriverManager; +import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.Statement; import java.time.LocalDate; @@ -186,6 +188,24 @@ private void verifyNativeDateCompatibility(String catalog) throws Exception { "(legacy_key Date NOT NULL, signed_key Date32 NOT NULL, legacy_value Date, signed_value Date32, " + "PRIMARY KEY (legacy_key, signed_key))")) { String name = catalog + ".default." + table.getName(); + try (Connection connection = DriverManager.getConnection(YdbQueryRunner.buildJdbcUrl(ydb)); + PreparedStatement statement = connection.prepareStatement( + "INSERT INTO `" + table.getName() + "` (legacy_key, signed_key) VALUES (?, ?)")) { + statement.setObject(1, PrimitiveValue.newDate(LocalDate.of(2020, 1, 1))); + statement.setObject(2, PrimitiveValue.newDate32(LocalDate.of(-1, 1, 1))); + statement.executeUpdate(); + } + assertQueryReturnsEmptyResult("SELECT * FROM " + name + " WHERE legacy_key = DATE '-0001-01-01'"); + assertQuery("SELECT count(*) FROM " + name + " WHERE legacy_key > DATE '-0001-01-01'", "VALUES BIGINT '1'"); + assertQueryReturnsEmptyResult("SELECT * FROM " + name + " WHERE legacy_key = DATE '2106-01-01'"); + assertQuery("SELECT count(*) FROM " + name + " WHERE legacy_key < DATE '2106-01-01'", "VALUES BIGINT '1'"); + try (Connection connection = DriverManager.getConnection(YdbQueryRunner.buildJdbcUrl(ydb)); + PreparedStatement statement = connection.prepareStatement( + "DELETE FROM `" + table.getName() + "` WHERE legacy_key = ? AND signed_key = ?")) { + statement.setObject(1, PrimitiveValue.newDate(LocalDate.of(2020, 1, 1))); + statement.setObject(2, PrimitiveValue.newDate32(LocalDate.of(-1, 1, 1))); + statement.executeUpdate(); + } assertUpdate("INSERT INTO " + name + " VALUES " + "(DATE '2020-01-01', DATE '-0001-01-01', DATE '2000-01-01', NULL), " + "(DATE '2020-01-02', DATE '-0001-01-02', NULL, DATE '-0002-01-01'), " + @@ -193,10 +213,6 @@ private void verifyNativeDateCompatibility(String catalog) throws Exception { assertQuery("SELECT legacy_value, signed_value FROM " + name + " WHERE legacy_key = DATE '2020-01-01' AND signed_key = DATE '-0001-01-01'", "VALUES (DATE '2000-01-01', CAST(NULL AS DATE))"); - assertQueryReturnsEmptyResult("SELECT * FROM " + name + " WHERE legacy_key = DATE '-0001-01-01'"); - assertQuery("SELECT count(*) FROM " + name + " WHERE legacy_key > DATE '-0001-01-01'", "VALUES BIGINT '3'"); - assertQueryReturnsEmptyResult("SELECT * FROM " + name + " WHERE legacy_key = DATE '2106-01-01'"); - assertQuery("SELECT count(*) FROM " + name + " WHERE legacy_key < DATE '2106-01-01'", "VALUES BIGINT '3'"); assertUpdate("UPDATE " + name + " SET legacy_value = DATE '2001-01-01', signed_value = DATE '-0003-01-01'" + " WHERE legacy_key = DATE '2020-01-01' AND signed_key = DATE '-0001-01-01'", 1); assertUpdate(""" From ec86f79b6fb6d4dba14ea85b71bed8a7185bb052 Mon Sep 17 00:00:00 2001 From: KirillKurdyukov Date: Fri, 11 Sep 2026 16:18:56 +0300 Subject: [PATCH 05/10] Fix date predicate expected query --- .../src/test/java/tech/ydb/trino/TestYdbConnectorTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ydb-trino-adapter/src/test/java/tech/ydb/trino/TestYdbConnectorTest.java b/ydb-trino-adapter/src/test/java/tech/ydb/trino/TestYdbConnectorTest.java index 20e73b73..aec41c92 100644 --- a/ydb-trino-adapter/src/test/java/tech/ydb/trino/TestYdbConnectorTest.java +++ b/ydb-trino-adapter/src/test/java/tech/ydb/trino/TestYdbConnectorTest.java @@ -196,9 +196,9 @@ private void verifyNativeDateCompatibility(String catalog) throws Exception { statement.executeUpdate(); } assertQueryReturnsEmptyResult("SELECT * FROM " + name + " WHERE legacy_key = DATE '-0001-01-01'"); - assertQuery("SELECT count(*) FROM " + name + " WHERE legacy_key > DATE '-0001-01-01'", "VALUES BIGINT '1'"); + assertQuery("SELECT count(*) FROM " + name + " WHERE legacy_key > DATE '-0001-01-01'", "VALUES CAST(1 AS BIGINT)"); assertQueryReturnsEmptyResult("SELECT * FROM " + name + " WHERE legacy_key = DATE '2106-01-01'"); - assertQuery("SELECT count(*) FROM " + name + " WHERE legacy_key < DATE '2106-01-01'", "VALUES BIGINT '1'"); + assertQuery("SELECT count(*) FROM " + name + " WHERE legacy_key < DATE '2106-01-01'", "VALUES CAST(1 AS BIGINT)"); try (Connection connection = DriverManager.getConnection(YdbQueryRunner.buildJdbcUrl(ydb)); PreparedStatement statement = connection.prepareStatement( "DELETE FROM `" + table.getName() + "` WHERE legacy_key = ? AND signed_key = ?")) { From a167d62559332ccbc65b3c1e257a6faf38d81264 Mon Sep 17 00:00:00 2001 From: KirillKurdyukov Date: Fri, 11 Sep 2026 16:21:22 +0300 Subject: [PATCH 06/10] Match legacy Date rejection --- .../src/test/java/tech/ydb/trino/TestYdbConnectorTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ydb-trino-adapter/src/test/java/tech/ydb/trino/TestYdbConnectorTest.java b/ydb-trino-adapter/src/test/java/tech/ydb/trino/TestYdbConnectorTest.java index aec41c92..d453b878 100644 --- a/ydb-trino-adapter/src/test/java/tech/ydb/trino/TestYdbConnectorTest.java +++ b/ydb-trino-adapter/src/test/java/tech/ydb/trino/TestYdbConnectorTest.java @@ -103,7 +103,7 @@ protected boolean hasBehavior(TestingConnectorBehavior connectorBehavior) { @Override protected String errorMessageForInsertNegativeDate(String date) { - return ".*negative daysSinceEpoch.*"; + return ".*Cannot cast parameter \\[dt\\] from \\[Date32\\] to \\[Date\\].*"; } @Override From 341a09d9f7e20416cf8a318bcfe874e74d6ad7db Mon Sep 17 00:00:00 2001 From: KirillKurdyukov Date: Fri, 11 Sep 2026 16:25:39 +0300 Subject: [PATCH 07/10] Revert "Match legacy Date rejection" This reverts commit a167d62559332ccbc65b3c1e257a6faf38d81264. --- .../src/test/java/tech/ydb/trino/TestYdbConnectorTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ydb-trino-adapter/src/test/java/tech/ydb/trino/TestYdbConnectorTest.java b/ydb-trino-adapter/src/test/java/tech/ydb/trino/TestYdbConnectorTest.java index d453b878..aec41c92 100644 --- a/ydb-trino-adapter/src/test/java/tech/ydb/trino/TestYdbConnectorTest.java +++ b/ydb-trino-adapter/src/test/java/tech/ydb/trino/TestYdbConnectorTest.java @@ -103,7 +103,7 @@ protected boolean hasBehavior(TestingConnectorBehavior connectorBehavior) { @Override protected String errorMessageForInsertNegativeDate(String date) { - return ".*Cannot cast parameter \\[dt\\] from \\[Date32\\] to \\[Date\\].*"; + return ".*negative daysSinceEpoch.*"; } @Override From 6905e63449b33bd6c36c252f900bd942ba78d000 Mon Sep 17 00:00:00 2001 From: KirillKurdyukov Date: Fri, 11 Sep 2026 16:31:43 +0300 Subject: [PATCH 08/10] Verify native date predicate fixture --- .../tech/ydb/trino/TestYdbConnectorTest.java | 61 +++++++++++++------ 1 file changed, 43 insertions(+), 18 deletions(-) diff --git a/ydb-trino-adapter/src/test/java/tech/ydb/trino/TestYdbConnectorTest.java b/ydb-trino-adapter/src/test/java/tech/ydb/trino/TestYdbConnectorTest.java index aec41c92..b0fb7320 100644 --- a/ydb-trino-adapter/src/test/java/tech/ydb/trino/TestYdbConnectorTest.java +++ b/ydb-trino-adapter/src/test/java/tech/ydb/trino/TestYdbConnectorTest.java @@ -19,6 +19,7 @@ import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; +import java.sql.SQLException; import java.sql.Statement; import java.time.LocalDate; import java.util.Optional; @@ -27,6 +28,7 @@ import static io.trino.testing.TestingNames.randomNameSuffix; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertAll; public class TestYdbConnectorTest extends BaseConnectorTest { private static final String ALTERNATE_DATE_CATALOG = "alternate_dates"; @@ -173,6 +175,47 @@ public void testVarbinaryCreateTableAndInsert() { } } + @Test + public void testDatePredicateWidening() throws Exception { + assertAll( + () -> verifyDatePredicateWidening("local"), + () -> verifyDatePredicateWidening(ALTERNATE_DATE_CATALOG)); + } + + private void verifyDatePredicateWidening(String catalog) throws Exception { + try (TestTable table = new TestTable( + new JdbcSqlExecutor(YdbQueryRunner.buildJdbcUrl(ydb)), + "date_predicate_", + "(legacy_key Date NOT NULL, signed_key Date32 NOT NULL, PRIMARY KEY (legacy_key, signed_key))")) { + try (Connection connection = DriverManager.getConnection(YdbQueryRunner.buildJdbcUrl(ydb)); + PreparedStatement statement = connection.prepareStatement( + "INSERT INTO `" + table.getName() + "` (legacy_key, signed_key) VALUES (?, ?)")) { + setDateKey(statement); + statement.executeUpdate(); + } + try (Connection connection = DriverManager.getConnection(YdbQueryRunner.buildJdbcUrl(ydb)); + Statement statement = connection.createStatement(); + ResultSet rows = statement.executeQuery("SELECT * FROM `" + table.getName() + "`")) { + assertThat(rows.next()).isTrue(); + assertThat(rows.getObject("legacy_key", LocalDate.class)).isEqualTo(LocalDate.of(2020, 1, 1)); + assertThat(rows.getObject("signed_key", LocalDate.class)).isEqualTo(LocalDate.of(-1, 1, 1)); + assertThat(rows.next()).isFalse(); + } + String name = catalog + ".default." + table.getName(); + assertQuery("SELECT count(*) FROM " + name, "VALUES CAST(1 AS BIGINT)"); + assertAll( + () -> assertQueryReturnsEmptyResult("SELECT * FROM " + name + " WHERE legacy_key = DATE '-0001-01-01'"), + () -> assertQuery("SELECT count(*) FROM " + name + " WHERE legacy_key > DATE '-0001-01-01'", "VALUES CAST(1 AS BIGINT)"), + () -> assertQueryReturnsEmptyResult("SELECT * FROM " + name + " WHERE legacy_key = DATE '2106-01-01'"), + () -> assertQuery("SELECT count(*) FROM " + name + " WHERE legacy_key < DATE '2106-01-01'", "VALUES CAST(1 AS BIGINT)")); + } + } + + private static void setDateKey(PreparedStatement statement) throws SQLException { + statement.setObject(1, PrimitiveValue.newDate(LocalDate.of(2020, 1, 1))); + statement.setObject(2, PrimitiveValue.newDate32(LocalDate.of(-1, 1, 1))); + } + @Test public void testNativeDateCompatibility() throws Exception { verifyNativeDateCompatibility("local"); @@ -188,24 +231,6 @@ private void verifyNativeDateCompatibility(String catalog) throws Exception { "(legacy_key Date NOT NULL, signed_key Date32 NOT NULL, legacy_value Date, signed_value Date32, " + "PRIMARY KEY (legacy_key, signed_key))")) { String name = catalog + ".default." + table.getName(); - try (Connection connection = DriverManager.getConnection(YdbQueryRunner.buildJdbcUrl(ydb)); - PreparedStatement statement = connection.prepareStatement( - "INSERT INTO `" + table.getName() + "` (legacy_key, signed_key) VALUES (?, ?)")) { - statement.setObject(1, PrimitiveValue.newDate(LocalDate.of(2020, 1, 1))); - statement.setObject(2, PrimitiveValue.newDate32(LocalDate.of(-1, 1, 1))); - statement.executeUpdate(); - } - assertQueryReturnsEmptyResult("SELECT * FROM " + name + " WHERE legacy_key = DATE '-0001-01-01'"); - assertQuery("SELECT count(*) FROM " + name + " WHERE legacy_key > DATE '-0001-01-01'", "VALUES CAST(1 AS BIGINT)"); - assertQueryReturnsEmptyResult("SELECT * FROM " + name + " WHERE legacy_key = DATE '2106-01-01'"); - assertQuery("SELECT count(*) FROM " + name + " WHERE legacy_key < DATE '2106-01-01'", "VALUES CAST(1 AS BIGINT)"); - try (Connection connection = DriverManager.getConnection(YdbQueryRunner.buildJdbcUrl(ydb)); - PreparedStatement statement = connection.prepareStatement( - "DELETE FROM `" + table.getName() + "` WHERE legacy_key = ? AND signed_key = ?")) { - statement.setObject(1, PrimitiveValue.newDate(LocalDate.of(2020, 1, 1))); - statement.setObject(2, PrimitiveValue.newDate32(LocalDate.of(-1, 1, 1))); - statement.executeUpdate(); - } assertUpdate("INSERT INTO " + name + " VALUES " + "(DATE '2020-01-01', DATE '-0001-01-01', DATE '2000-01-01', NULL), " + "(DATE '2020-01-02', DATE '-0001-01-02', NULL, DATE '-0002-01-01'), " + From 5e2bcfd25f49b4aa7ed7676447f64a1c0ec36a12 Mon Sep 17 00:00:00 2001 From: KirillKurdyukov Date: Fri, 11 Sep 2026 16:32:20 +0300 Subject: [PATCH 09/10] Run date predicate diagnostics in Moscow --- .github/workflows/ci-trino-adapter.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci-trino-adapter.yaml b/.github/workflows/ci-trino-adapter.yaml index 7a8aa939..ae4bca04 100644 --- a/.github/workflows/ci-trino-adapter.yaml +++ b/.github/workflows/ci-trino-adapter.yaml @@ -27,7 +27,7 @@ jobs: test_args: - '-Duser.timezone=UTC' - '-Duser.timezone=UTC -Dydb.test.force-signed-datetimes=true' - - "-Duser.timezone=Europe/Moscow -Dtest='TestYdbConnectorTest#testNativeDateCompatibility'" + - "-Duser.timezone=Europe/Moscow -Dtest='TestYdbConnectorTest#testNativeDateCompatibility+testDatePredicateWidening'" steps: - uses: actions/checkout@v5 From 2e556566bea51c2c9ed534cef210e5b7eb5b7292 Mon Sep 17 00:00:00 2001 From: KirillKurdyukov Date: Fri, 11 Sep 2026 16:33:05 +0300 Subject: [PATCH 10/10] Keep date diagnostics independent --- .../java/tech/ydb/trino/TestYdbConnectorTest.java | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/ydb-trino-adapter/src/test/java/tech/ydb/trino/TestYdbConnectorTest.java b/ydb-trino-adapter/src/test/java/tech/ydb/trino/TestYdbConnectorTest.java index b0fb7320..4967376e 100644 --- a/ydb-trino-adapter/src/test/java/tech/ydb/trino/TestYdbConnectorTest.java +++ b/ydb-trino-adapter/src/test/java/tech/ydb/trino/TestYdbConnectorTest.java @@ -19,7 +19,6 @@ import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; -import java.sql.SQLException; import java.sql.Statement; import java.time.LocalDate; import java.util.Optional; @@ -190,7 +189,8 @@ private void verifyDatePredicateWidening(String catalog) throws Exception { try (Connection connection = DriverManager.getConnection(YdbQueryRunner.buildJdbcUrl(ydb)); PreparedStatement statement = connection.prepareStatement( "INSERT INTO `" + table.getName() + "` (legacy_key, signed_key) VALUES (?, ?)")) { - setDateKey(statement); + statement.setObject(1, PrimitiveValue.newDate(LocalDate.of(2020, 1, 1))); + statement.setObject(2, PrimitiveValue.newDate32(LocalDate.of(-1, 1, 1))); statement.executeUpdate(); } try (Connection connection = DriverManager.getConnection(YdbQueryRunner.buildJdbcUrl(ydb)); @@ -211,15 +211,11 @@ private void verifyDatePredicateWidening(String catalog) throws Exception { } } - private static void setDateKey(PreparedStatement statement) throws SQLException { - statement.setObject(1, PrimitiveValue.newDate(LocalDate.of(2020, 1, 1))); - statement.setObject(2, PrimitiveValue.newDate32(LocalDate.of(-1, 1, 1))); - } - @Test public void testNativeDateCompatibility() throws Exception { - verifyNativeDateCompatibility("local"); - verifyNativeDateCompatibility(ALTERNATE_DATE_CATALOG); + assertAll( + () -> verifyNativeDateCompatibility("local"), + () -> verifyNativeDateCompatibility(ALTERNATE_DATE_CATALOG)); } private void verifyNativeDateCompatibility(String catalog) throws Exception {