From 56ab4906d5c2cc7421ad68f6c8caa7eea4a72ad8 Mon Sep 17 00:00:00 2001 From: Kirill Kurdyukov Date: Thu, 10 Sep 2026 15:57:22 +0300 Subject: [PATCH 1/2] feat: map Trino date to YDB Date32 --- ydb-trino-adapter/README.md | 3 + ydb-trino-adapter/ROADMAP.md | 3 +- .../main/java/tech/ydb/trino/YdbClient.java | 66 ++++++++++++++++- .../java/tech/ydb/trino/YdbTypeUtils.java | 2 +- .../tech/ydb/trino/TestYdbConnectorTest.java | 73 +++++++++++++------ 5 files changed, 119 insertions(+), 28 deletions(-) diff --git a/ydb-trino-adapter/README.md b/ydb-trino-adapter/README.md index 40b27603..f2341d01 100644 --- a/ydb-trino-adapter/README.md +++ b/ydb-trino-adapter/README.md @@ -49,3 +49,6 @@ SELECT * FROM local.default.orders; YDB `Text` отображается в Trino как `varchar`, а `Bytes` — как `varbinary` без декодирования UTF-8. При создании таблиц адаптер использует типы `Text` и `Bytes`. + +Trino `date` создаёт YDB `Date32`; существующие колонки YDB `Date` также читаются +как `date` и не мигрируются. Значения ограничены диапазоном `Date32`. diff --git a/ydb-trino-adapter/ROADMAP.md b/ydb-trino-adapter/ROADMAP.md index 89fe9311..c371725e 100644 --- a/ydb-trino-adapter/ROADMAP.md +++ b/ydb-trino-adapter/ROADMAP.md @@ -136,7 +136,8 @@ unsupported behavior, record: 2. an authoritative documentation link or tracked upstream issue; 3. a focused negative test that proves the connector fails clearly. -The existing negative-date overrides need this treatment. +Trino `date` now uses YDB `Date32`, including BCE values within its native range. +Predicates with values outside that range remain residual Trino filters. YDB `Date` starts at the Unix epoch; see [primitive types](https://ydb.tech/docs/en/yql/reference/types/primitive). CHAR is rejected with the focused inherited contract 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 ba7105e2..82d825b9 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,9 +49,13 @@ import io.trino.spi.connector.SchemaTableName; import io.trino.spi.connector.SortOrder; import io.trino.spi.expression.ConnectorExpression; +import io.trino.spi.expression.Constant; +import io.trino.spi.predicate.Domain; import io.trino.spi.type.DecimalType; import io.trino.spi.type.Type; import io.trino.spi.type.VarcharType; +import tech.ydb.table.values.PrimitiveValue; +import tech.ydb.table.values.PrimitiveType; import jakarta.annotation.Nullable; @@ -58,8 +63,10 @@ import java.sql.DatabaseMetaData; import java.sql.PreparedStatement; import java.sql.ResultSet; +import java.sql.SQLDataException; import java.sql.SQLException; import java.sql.Types; +import java.time.LocalDate; import java.util.Collection; import java.util.List; import java.util.Locale; @@ -127,6 +134,8 @@ 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; + private static final long DATE32_MIN_EPOCH_DAY = -53375809; + private static final long DATE32_MAX_EPOCH_DAY = 53375807; private final ConnectorExpressionRewriter connectorExpressionRewriter; private final AggregateFunctionRewriter aggregateFunctionRewriter; @@ -210,9 +219,19 @@ public Optional convertPredicate( ConnectorExpression expression, Map assignments ) { + if (containsOutOfRangeDateConstant(expression)) { + return Optional.empty(); + } return connectorExpressionRewriter.rewrite(session, expression, assignments); } + private static boolean containsOutOfRangeDateConstant(ConnectorExpression expression) { + if (expression instanceof Constant constant && expression.getType() == DATE && constant.getValue() != null) { + return !isDate32EpochDay((long) constant.getValue()); + } + return expression.getChildren().stream().anyMatch(YdbClient::containsOutOfRangeDateConstant); + } + @Override public Optional convertProjection( ConnectorSession session, @@ -338,7 +357,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(jdbcTypeName.equals("date32") ? date32ColumnMapping() : dateColumnMapping()); case Types.TIMESTAMP -> Optional.of(timestampColumnMapping()); default -> Optional.empty(); }; @@ -377,6 +396,49 @@ private static ColumnMapping dateColumnMapping() { dateWriteFunctionUsingLocalDate()); } + private static ColumnMapping date32ColumnMapping() { + return ColumnMapping.longMapping( + DATE, + dateReadFunctionUsingLocalDate(), + date32WriteFunction(), + (session, domain) -> date32DomainIsSupported(domain) + ? FULL_PUSHDOWN.apply(session, domain) + : DISABLE_PUSHDOWN.apply(session, domain)); + } + + private static LongWriteFunction date32WriteFunction() { + return new LongWriteFunction() { + @Override + public void set(PreparedStatement statement, int index, long daysSinceEpoch) throws SQLException { + if (!isDate32EpochDay(daysSinceEpoch)) { + throw new SQLDataException("Date is outside the supported YDB Date32 range: " + LocalDate.ofEpochDay(daysSinceEpoch)); + } + statement.setObject(index, PrimitiveValue.newDate32(LocalDate.ofEpochDay(daysSinceEpoch))); + } + + @Override + public void setNull(PreparedStatement statement, int index) throws SQLException { + statement.setObject(index, PrimitiveType.Date32.makeOptional().emptyValue()); + } + }; + } + + private static boolean date32DomainIsSupported(Domain domain) { + if (domain.getValues().isDiscreteSet()) { + return domain.getValues().getDiscreteSet().stream() + .mapToLong(value -> (long) value) + .allMatch(YdbClient::isDate32EpochDay); + } + return domain.getValues().isAll() || domain.getValues().isNone() || domain.getValues().getRanges().getOrderedRanges().stream() + .allMatch(range -> + (range.isLowUnbounded() || isDate32EpochDay((long) range.getLowBoundedValue())) && + (range.isHighUnbounded() || isDate32EpochDay((long) range.getHighBoundedValue()))); + } + + private static boolean isDate32EpochDay(long daysSinceEpoch) { + return daysSinceEpoch >= DATE32_MIN_EPOCH_DAY && daysSinceEpoch <= DATE32_MAX_EPOCH_DAY; + } + private static ColumnMapping timestampColumnMapping() { return ColumnMapping.longMapping( TIMESTAMP_MICROS, @@ -420,7 +482,7 @@ public WriteMapping toWriteMapping(ConnectorSession session, Type type) { return WriteMapping.sliceMapping("Bytes", varbinaryWriteFunction()); } if (type == DATE) { - return WriteMapping.longMapping("Date", dateWriteFunctionUsingLocalDate()); + return WriteMapping.longMapping("Date32", date32WriteFunction()); } if (type == TIMESTAMP_MICROS) { return WriteMapping.longMapping("Timestamp", timestampWriteFunction(TIMESTAMP_MICROS)); diff --git a/ydb-trino-adapter/src/main/java/tech/ydb/trino/YdbTypeUtils.java b/ydb-trino-adapter/src/main/java/tech/ydb/trino/YdbTypeUtils.java index 95f737b7..a6aa4009 100644 --- a/ydb-trino-adapter/src/main/java/tech/ydb/trino/YdbTypeUtils.java +++ b/ydb-trino-adapter/src/main/java/tech/ydb/trino/YdbTypeUtils.java @@ -30,7 +30,7 @@ public static Optional toTypeHandle(Type type) { case io.trino.spi.type.DoubleType _ -> Optional.of(new JdbcTypeHandle(Types.DOUBLE, Optional.of("Double"), Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty())); case io.trino.spi.type.DateType _ -> - Optional.of(new JdbcTypeHandle(Types.DATE, Optional.of("Date"), Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty())); + Optional.of(new JdbcTypeHandle(Types.DATE, Optional.of("Date32"), Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty())); case io.trino.spi.type.TimestampType _ -> Optional.of(new JdbcTypeHandle(Types.TIMESTAMP, Optional.of("Timestamp"), Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty())); case DecimalType decimalType -> 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..0e7a73a8 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 @@ -13,6 +13,11 @@ 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.Types; +import java.util.List; import java.util.Optional; import java.util.OptionalInt; @@ -83,24 +88,6 @@ protected boolean hasBehavior(TestingConnectorBehavior connectorBehavior) { }; } - @Test - @Override - public void testInsertNegativeDate() { - // YDB не поддерживает, negative daysSinceEpoch - } - - @Test - @Override - public void testDateYearOfEraPredicate() { - // YDB не поддерживает, negative daysSinceEpoch - } - - @Test - @Override - public void testCreateTableAsSelectNegativeDate() { - // YDB не поддерживает, negative daysSinceEpoch - } - @Test @Override public void testCharVarcharComparison() { @@ -123,6 +110,30 @@ protected TestTable createTableWithDefaultColumns() { "PRIMARY KEY (col_required))"); } + @Test + public void testMergeLegacyDateColumns() { + try (TestTable table = new TestTable( + new JdbcSqlExecutor(YdbQueryRunner.buildJdbcUrl(ydb)), + "legacy_date_merge_", + "(date_key Date NOT NULL, value Date, PRIMARY KEY (date_key))")) { + assertUpdate("MERGE INTO " + table.getName() + " t USING (VALUES (DATE '2020-02-12', DATE '2020-02-13')) s(date_key, value)" + + " ON t.date_key = s.date_key WHEN NOT MATCHED THEN INSERT (date_key, value) VALUES (s.date_key, s.value)", 1); + assertQuery("SELECT * FROM " + table.getName(), "VALUES (DATE '2020-02-12', DATE '2020-02-13')"); + + assertUpdate("MERGE INTO " + table.getName() + " t USING (VALUES (DATE '2020-02-12', DATE '2020-02-14')) s(date_key, value)" + + " ON t.date_key = s.date_key WHEN MATCHED THEN UPDATE SET value = s.value", 1); + assertQuery("SELECT * FROM " + table.getName(), "VALUES (DATE '2020-02-12', DATE '2020-02-14')"); + + assertUpdate("MERGE INTO " + table.getName() + " t USING (VALUES (DATE '2020-02-12', CAST(NULL AS date))) s(date_key, value)" + + " ON t.date_key = s.date_key WHEN MATCHED THEN UPDATE SET value = s.value", 1); + assertQuery("SELECT * FROM " + table.getName(), "VALUES (DATE '2020-02-12', NULL)"); + + assertUpdate("MERGE INTO " + table.getName() + " t USING (VALUES DATE '2020-02-12') s(date_key)" + + " ON t.date_key = s.date_key WHEN MATCHED THEN DELETE", 1); + assertQueryReturnsEmptyResult("SELECT * FROM " + table.getName()); + } + } + @Override protected String errorMessageForInsertIntoNotNullColumn(String columnName) { return "(?s).*(" @@ -142,12 +153,6 @@ 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")) { - return Optional.of(new DataMappingTestSetup( - dataMappingTestSetup.getTrinoTypeName(), - "DATE '2006-06-06'", - "DATE '2026-06-06'" - )); } else if (dataMappingTestSetup.getTrinoTypeName().startsWith("time")) { // Нет time в YQL return Optional.empty(); @@ -155,6 +160,26 @@ protected Optional filterDataMappingSmokeTestData(BaseConn return Optional.of(dataMappingTestSetup); } + @Test + public void testDateDdlUsesDate32() throws Exception { + try (TestTable table = newTrinoTable("date32_", "(value date)")) { + try (Connection connection = DriverManager.getConnection(YdbQueryRunner.buildJdbcUrl(ydb)); + ResultSet columns = connection.getMetaData().getColumns(null, null, table.getName(), "value")) { + assertThat(columns.next()).isTrue(); + assertThat(columns.getInt("DATA_TYPE")).isEqualTo(Types.DATE); + assertThat(columns.getString("TYPE_NAME")).isEqualTo("Date32"); + } + } + } + + @Test + public void testDate32OutOfRangePredicatesAreResidual() { + try (TestTable table = newTrinoTable("date32_out_of_range_", "(value date)", List.of("DATE '2020-02-12'"))) { + assertQueryReturnsEmptyResult("SELECT * FROM " + table.getName() + " WHERE value = DATE '-144169-01-01'"); + assertQueryReturnsEmptyResult("SELECT * FROM " + table.getName() + " WHERE value = DATE '148108-01-01'"); + } + } + @Test public void testVarbinaryCreateTableAndInsert() { try (TestTable table = newTrinoTable("varbinary_insert_", "(id bigint, value varbinary)")) { From 66926f712fab165dea7356e88a5811e0280699a3 Mon Sep 17 00:00:00 2001 From: Kirill Kurdyukov Date: Thu, 10 Sep 2026 16:23:32 +0300 Subject: [PATCH 2/2] fix: use signed YDB temporal types by default --- ydb-trino-adapter/README.md | 5 +- ydb-trino-adapter/ROADMAP.md | 4 +- .../main/java/tech/ydb/trino/YdbClient.java | 80 +++---------------- .../java/tech/ydb/trino/YdbClientModule.java | 1 + .../java/tech/ydb/trino/YdbTypeUtils.java | 2 +- .../tech/ydb/trino/TestYdbConnectorTest.java | 54 +------------ 6 files changed, 24 insertions(+), 122 deletions(-) diff --git a/ydb-trino-adapter/README.md b/ydb-trino-adapter/README.md index f2341d01..a59d3c24 100644 --- a/ydb-trino-adapter/README.md +++ b/ydb-trino-adapter/README.md @@ -50,5 +50,6 @@ SELECT * FROM local.default.orders; YDB `Text` отображается в Trino как `varchar`, а `Bytes` — как `varbinary` без декодирования UTF-8. При создании таблиц адаптер использует типы `Text` и `Bytes`. -Trino `date` создаёт YDB `Date32`; существующие колонки YDB `Date` также читаются -как `date` и не мигрируются. Значения ограничены диапазоном `Date32`. +Trino `date` и `timestamp` создают YDB `Date32` и `Timestamp64`. Адаптер по +умолчанию включает параметр JDBC `forceSignedDatetimes`; значение в +`connection-url` имеет приоритет. Существующие таблицы не мигрируются. diff --git a/ydb-trino-adapter/ROADMAP.md b/ydb-trino-adapter/ROADMAP.md index c371725e..933d89ec 100644 --- a/ydb-trino-adapter/ROADMAP.md +++ b/ydb-trino-adapter/ROADMAP.md @@ -136,8 +136,8 @@ unsupported behavior, record: 2. an authoritative documentation link or tracked upstream issue; 3. a focused negative test that proves the connector fails clearly. -Trino `date` now uses YDB `Date32`, including BCE values within its native range. -Predicates with values outside that range remain residual Trino filters. +Trino `date` and `timestamp` use YDB `Date32` and `Timestamp64`; the connector +enables signed datetime support by default. Existing tables are not migrated. YDB `Date` starts at the Unix epoch; see [primitive types](https://ydb.tech/docs/en/yql/reference/types/primitive). CHAR is rejected with the focused inherited contract 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 82d825b9..2db9fc79 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 @@ -49,13 +49,10 @@ import io.trino.spi.connector.SchemaTableName; import io.trino.spi.connector.SortOrder; import io.trino.spi.expression.ConnectorExpression; -import io.trino.spi.expression.Constant; -import io.trino.spi.predicate.Domain; import io.trino.spi.type.DecimalType; import io.trino.spi.type.Type; +import io.trino.spi.type.TimestampType; import io.trino.spi.type.VarcharType; -import tech.ydb.table.values.PrimitiveValue; -import tech.ydb.table.values.PrimitiveType; import jakarta.annotation.Nullable; @@ -63,10 +60,8 @@ import java.sql.DatabaseMetaData; import java.sql.PreparedStatement; import java.sql.ResultSet; -import java.sql.SQLDataException; import java.sql.SQLException; import java.sql.Types; -import java.time.LocalDate; import java.util.Collection; import java.util.List; import java.util.Locale; @@ -95,6 +90,7 @@ import static io.trino.plugin.jdbc.StandardColumnMappings.decimalColumnMapping; import static io.trino.plugin.jdbc.StandardColumnMappings.doubleColumnMapping; import static io.trino.plugin.jdbc.StandardColumnMappings.doubleWriteFunction; +import static io.trino.plugin.jdbc.StandardColumnMappings.fromTrinoTimestamp; import static io.trino.plugin.jdbc.StandardColumnMappings.integerColumnMapping; import static io.trino.plugin.jdbc.StandardColumnMappings.integerWriteFunction; import static io.trino.plugin.jdbc.StandardColumnMappings.longDecimalWriteFunction; @@ -105,7 +101,6 @@ import static io.trino.plugin.jdbc.StandardColumnMappings.smallintWriteFunction; import static io.trino.plugin.jdbc.StandardColumnMappings.timestampColumnMapping; import static io.trino.plugin.jdbc.StandardColumnMappings.timestampReadFunction; -import static io.trino.plugin.jdbc.StandardColumnMappings.timestampWriteFunction; import static io.trino.plugin.jdbc.StandardColumnMappings.tinyintWriteFunction; import static io.trino.plugin.jdbc.StandardColumnMappings.varbinaryColumnMapping; import static io.trino.plugin.jdbc.StandardColumnMappings.varbinaryWriteFunction; @@ -128,14 +123,13 @@ import static io.trino.spi.type.VarcharType.createVarcharType; import static java.lang.Math.max; import static java.lang.String.format; +import static java.time.ZoneOffset.UTC; import static java.util.stream.Collectors.joining; 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; - private static final long DATE32_MIN_EPOCH_DAY = -53375809; - private static final long DATE32_MAX_EPOCH_DAY = 53375807; private final ConnectorExpressionRewriter connectorExpressionRewriter; private final AggregateFunctionRewriter aggregateFunctionRewriter; @@ -219,19 +213,9 @@ public Optional convertPredicate( ConnectorExpression expression, Map assignments ) { - if (containsOutOfRangeDateConstant(expression)) { - return Optional.empty(); - } return connectorExpressionRewriter.rewrite(session, expression, assignments); } - private static boolean containsOutOfRangeDateConstant(ConnectorExpression expression) { - if (expression instanceof Constant constant && expression.getType() == DATE && constant.getValue() != null) { - return !isDate32EpochDay((long) constant.getValue()); - } - return expression.getChildren().stream().anyMatch(YdbClient::containsOutOfRangeDateConstant); - } - @Override public Optional convertProjection( ConnectorSession session, @@ -357,7 +341,7 @@ public Optional toColumnMapping( : typeHandle.columnSize().orElse(VarcharType.MAX_LENGTH); yield Optional.of(varcharColumnMapping(length)); } - case Types.DATE -> Optional.of(jdbcTypeName.equals("date32") ? date32ColumnMapping() : dateColumnMapping()); + case Types.DATE -> Optional.of(dateColumnMapping()); case Types.TIMESTAMP -> Optional.of(timestampColumnMapping()); default -> Optional.empty(); }; @@ -396,54 +380,16 @@ private static ColumnMapping dateColumnMapping() { dateWriteFunctionUsingLocalDate()); } - private static ColumnMapping date32ColumnMapping() { - return ColumnMapping.longMapping( - DATE, - dateReadFunctionUsingLocalDate(), - date32WriteFunction(), - (session, domain) -> date32DomainIsSupported(domain) - ? FULL_PUSHDOWN.apply(session, domain) - : DISABLE_PUSHDOWN.apply(session, domain)); - } - - private static LongWriteFunction date32WriteFunction() { - return new LongWriteFunction() { - @Override - public void set(PreparedStatement statement, int index, long daysSinceEpoch) throws SQLException { - if (!isDate32EpochDay(daysSinceEpoch)) { - throw new SQLDataException("Date is outside the supported YDB Date32 range: " + LocalDate.ofEpochDay(daysSinceEpoch)); - } - statement.setObject(index, PrimitiveValue.newDate32(LocalDate.ofEpochDay(daysSinceEpoch))); - } - - @Override - public void setNull(PreparedStatement statement, int index) throws SQLException { - statement.setObject(index, PrimitiveType.Date32.makeOptional().emptyValue()); - } - }; - } - - private static boolean date32DomainIsSupported(Domain domain) { - if (domain.getValues().isDiscreteSet()) { - return domain.getValues().getDiscreteSet().stream() - .mapToLong(value -> (long) value) - .allMatch(YdbClient::isDate32EpochDay); - } - return domain.getValues().isAll() || domain.getValues().isNone() || domain.getValues().getRanges().getOrderedRanges().stream() - .allMatch(range -> - (range.isLowUnbounded() || isDate32EpochDay((long) range.getLowBoundedValue())) && - (range.isHighUnbounded() || isDate32EpochDay((long) range.getHighBoundedValue()))); - } - - private static boolean isDate32EpochDay(long daysSinceEpoch) { - return daysSinceEpoch >= DATE32_MIN_EPOCH_DAY && daysSinceEpoch <= DATE32_MAX_EPOCH_DAY; - } - private static ColumnMapping timestampColumnMapping() { return ColumnMapping.longMapping( TIMESTAMP_MICROS, timestampReadFunction(TIMESTAMP_MICROS), - timestampWriteFunction(TIMESTAMP_MICROS)); + timestamp64WriteFunction()); + } + + private static LongWriteFunction timestamp64WriteFunction() { + return LongWriteFunction.of(Types.TIMESTAMP, (statement, index, value) -> + statement.setObject(index, fromTrinoTimestamp(value).toInstant(UTC))); } @Override @@ -482,10 +428,10 @@ public WriteMapping toWriteMapping(ConnectorSession session, Type type) { return WriteMapping.sliceMapping("Bytes", varbinaryWriteFunction()); } if (type == DATE) { - return WriteMapping.longMapping("Date32", date32WriteFunction()); + return WriteMapping.longMapping("Date32", dateWriteFunctionUsingLocalDate()); } - if (type == TIMESTAMP_MICROS) { - return WriteMapping.longMapping("Timestamp", timestampWriteFunction(TIMESTAMP_MICROS)); + if (type instanceof TimestampType timestampType && timestampType.isShort()) { + return WriteMapping.longMapping("Timestamp64", timestamp64WriteFunction()); } throw new TrinoException(NOT_SUPPORTED, "Unsupported column type: " + type); diff --git a/ydb-trino-adapter/src/main/java/tech/ydb/trino/YdbClientModule.java b/ydb-trino-adapter/src/main/java/tech/ydb/trino/YdbClientModule.java index 2591c0a6..f0312701 100644 --- a/ydb-trino-adapter/src/main/java/tech/ydb/trino/YdbClientModule.java +++ b/ydb-trino-adapter/src/main/java/tech/ydb/trino/YdbClientModule.java @@ -55,6 +55,7 @@ public static ConnectionFactory createConnectionFactory( Properties connectionProperties = new Properties(); // Avoid the YDB JDBC 2.3.18 shared-context close/register race under concurrent connections. connectionProperties.setProperty("cacheConnectionsInDriver", "false"); + connectionProperties.setProperty("forceSignedDatetimes", "true"); return DriverConnectionFactory.builder( new YdbDriver(), config.getConnectionUrl(), diff --git a/ydb-trino-adapter/src/main/java/tech/ydb/trino/YdbTypeUtils.java b/ydb-trino-adapter/src/main/java/tech/ydb/trino/YdbTypeUtils.java index a6aa4009..c13902ac 100644 --- a/ydb-trino-adapter/src/main/java/tech/ydb/trino/YdbTypeUtils.java +++ b/ydb-trino-adapter/src/main/java/tech/ydb/trino/YdbTypeUtils.java @@ -32,7 +32,7 @@ public static Optional toTypeHandle(Type type) { case io.trino.spi.type.DateType _ -> Optional.of(new JdbcTypeHandle(Types.DATE, Optional.of("Date32"), Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty())); case io.trino.spi.type.TimestampType _ -> - Optional.of(new JdbcTypeHandle(Types.TIMESTAMP, Optional.of("Timestamp"), Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty())); + Optional.of(new JdbcTypeHandle(Types.TIMESTAMP, Optional.of("Timestamp64"), Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty())); case DecimalType decimalType -> Optional.of(new JdbcTypeHandle(Types.DECIMAL, Optional.of("Decimal"), Optional.of(decimalType.getPrecision()), Optional.of(decimalType.getScale()), Optional.empty(), Optional.empty())); case io.trino.spi.type.VarcharType _ -> 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 0e7a73a8..d90b0070 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 @@ -13,11 +13,6 @@ 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.Types; -import java.util.List; import java.util.Optional; import java.util.OptionalInt; @@ -110,30 +105,6 @@ protected TestTable createTableWithDefaultColumns() { "PRIMARY KEY (col_required))"); } - @Test - public void testMergeLegacyDateColumns() { - try (TestTable table = new TestTable( - new JdbcSqlExecutor(YdbQueryRunner.buildJdbcUrl(ydb)), - "legacy_date_merge_", - "(date_key Date NOT NULL, value Date, PRIMARY KEY (date_key))")) { - assertUpdate("MERGE INTO " + table.getName() + " t USING (VALUES (DATE '2020-02-12', DATE '2020-02-13')) s(date_key, value)" + - " ON t.date_key = s.date_key WHEN NOT MATCHED THEN INSERT (date_key, value) VALUES (s.date_key, s.value)", 1); - assertQuery("SELECT * FROM " + table.getName(), "VALUES (DATE '2020-02-12', DATE '2020-02-13')"); - - assertUpdate("MERGE INTO " + table.getName() + " t USING (VALUES (DATE '2020-02-12', DATE '2020-02-14')) s(date_key, value)" + - " ON t.date_key = s.date_key WHEN MATCHED THEN UPDATE SET value = s.value", 1); - assertQuery("SELECT * FROM " + table.getName(), "VALUES (DATE '2020-02-12', DATE '2020-02-14')"); - - assertUpdate("MERGE INTO " + table.getName() + " t USING (VALUES (DATE '2020-02-12', CAST(NULL AS date))) s(date_key, value)" + - " ON t.date_key = s.date_key WHEN MATCHED THEN UPDATE SET value = s.value", 1); - assertQuery("SELECT * FROM " + table.getName(), "VALUES (DATE '2020-02-12', NULL)"); - - assertUpdate("MERGE INTO " + table.getName() + " t USING (VALUES DATE '2020-02-12') s(date_key)" + - " ON t.date_key = s.date_key WHEN MATCHED THEN DELETE", 1); - assertQueryReturnsEmptyResult("SELECT * FROM " + table.getName()); - } - } - @Override protected String errorMessageForInsertIntoNotNullColumn(String columnName) { return "(?s).*(" @@ -153,33 +124,16 @@ 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().startsWith("time")) { + } else if (dataMappingTestSetup.getTrinoTypeName().equals("time") + || dataMappingTestSetup.getTrinoTypeName().equals("time(6)")) { // Нет time в YQL return Optional.empty(); + } else if (dataMappingTestSetup.getTrinoTypeName().contains("with time zone")) { + return Optional.empty(); } return Optional.of(dataMappingTestSetup); } - @Test - public void testDateDdlUsesDate32() throws Exception { - try (TestTable table = newTrinoTable("date32_", "(value date)")) { - try (Connection connection = DriverManager.getConnection(YdbQueryRunner.buildJdbcUrl(ydb)); - ResultSet columns = connection.getMetaData().getColumns(null, null, table.getName(), "value")) { - assertThat(columns.next()).isTrue(); - assertThat(columns.getInt("DATA_TYPE")).isEqualTo(Types.DATE); - assertThat(columns.getString("TYPE_NAME")).isEqualTo("Date32"); - } - } - } - - @Test - public void testDate32OutOfRangePredicatesAreResidual() { - try (TestTable table = newTrinoTable("date32_out_of_range_", "(value date)", List.of("DATE '2020-02-12'"))) { - assertQueryReturnsEmptyResult("SELECT * FROM " + table.getName() + " WHERE value = DATE '-144169-01-01'"); - assertQueryReturnsEmptyResult("SELECT * FROM " + table.getName() + " WHERE value = DATE '148108-01-01'"); - } - } - @Test public void testVarbinaryCreateTableAndInsert() { try (TestTable table = newTrinoTable("varbinary_insert_", "(id bigint, value varbinary)")) {