diff --git a/plugin/trino-sqlserver/src/main/java/io/trino/plugin/sqlserver/SqlServerClient.java b/plugin/trino-sqlserver/src/main/java/io/trino/plugin/sqlserver/SqlServerClient.java index fe979ebbbec1..2d6824852bc5 100644 --- a/plugin/trino-sqlserver/src/main/java/io/trino/plugin/sqlserver/SqlServerClient.java +++ b/plugin/trino-sqlserver/src/main/java/io/trino/plugin/sqlserver/SqlServerClient.java @@ -97,6 +97,8 @@ import io.trino.spi.type.TimestampType; import io.trino.spi.type.TimestampWithTimeZoneType; import io.trino.spi.type.Type; +import io.trino.spi.type.TypeManager; +import io.trino.spi.type.TypeSignature; import io.trino.spi.type.VarbinaryType; import io.trino.spi.type.VarcharType; import microsoft.sql.DateTimeOffset; @@ -137,7 +139,9 @@ import static com.google.common.base.Throwables.throwIfInstanceOf; import static com.google.common.collect.MoreCollectors.toOptional; import static com.google.common.util.concurrent.MoreExecutors.directExecutor; +import static io.airlift.slice.Slices.utf8Slice; import static io.airlift.slice.Slices.wrappedBuffer; +import static io.trino.plugin.base.util.JsonTypeUtil.jsonParse; import static io.trino.plugin.jdbc.CaseSensitivity.CASE_INSENSITIVE; import static io.trino.plugin.jdbc.CaseSensitivity.CASE_SENSITIVE; import static io.trino.plugin.jdbc.JdbcErrorCode.JDBC_ERROR; @@ -191,6 +195,7 @@ import static io.trino.spi.type.IntegerType.INTEGER; import static io.trino.spi.type.RealType.REAL; import static io.trino.spi.type.SmallintType.SMALLINT; +import static io.trino.spi.type.StandardTypes.JSON; import static io.trino.spi.type.TimeType.createTimeType; import static io.trino.spi.type.TimeZoneKey.UTC_KEY; import static io.trino.spi.type.TimeZoneKey.getTimeZoneKey; @@ -236,6 +241,7 @@ public class SqlServerClient private final boolean statisticsEnabled; + private final Type jsonType; private final ConnectorExpressionRewriter connectorExpressionRewriter; private final AggregateFunctionRewriter aggregateFunctionRewriter; @@ -301,11 +307,13 @@ public SqlServerClient( JdbcStatisticsConfig statisticsConfig, ConnectionFactory connectionFactory, QueryBuilder queryBuilder, + TypeManager typeManager, IdentifierMapping identifierMapping, RemoteQueryModifier queryModifier) { super("\"", connectionFactory, queryBuilder, config.getJdbcTypesMappedToVarchar(), identifierMapping, queryModifier, true); + this.jsonType = typeManager.getType(new TypeSignature(JSON)); this.statisticsEnabled = statisticsConfig.isEnabled(); this.connectorExpressionRewriter = JdbcConnectorExpressionRewriterBuilder.newBuilder() @@ -601,6 +609,8 @@ public Optional toColumnMapping(ConnectorSession session, Connect return Optional.of(varbinaryColumnMapping()); case "datetimeoffset": return Optional.of(timestampWithTimeZoneColumnMapping(typeHandle.requiredDecimalDigits())); + case "json": + return Optional.of(jsonColumnMapping()); } switch (typeHandle.jdbcType()) { @@ -681,6 +691,15 @@ public Optional toColumnMapping(ConnectorSession session, Connect return Optional.empty(); } + private ColumnMapping jsonColumnMapping() + { + return ColumnMapping.sliceMapping( + jsonType, + (resultSet, columnIndex) -> jsonParse(utf8Slice(resultSet.getString(columnIndex))), + nvarcharWriteFunction(), + DISABLE_PUSHDOWN); + } + @Override public WriteMapping toWriteMapping(ConnectorSession session, Type type) { @@ -737,6 +756,10 @@ public WriteMapping toWriteMapping(ConnectorSession session, Type type) return WriteMapping.sliceMapping(dataType, charWriteFunction()); } + if (type == jsonType) { + return WriteMapping.sliceMapping("json", nvarcharWriteFunction()); + } + if (type instanceof VarbinaryType) { return WriteMapping.sliceMapping("varbinary(max)", varbinaryWriteFunction()); } diff --git a/plugin/trino-sqlserver/src/test/java/io/trino/plugin/sqlserver/BaseSqlServerTypeMapping.java b/plugin/trino-sqlserver/src/test/java/io/trino/plugin/sqlserver/BaseSqlServerTypeMapping.java index 8b28bdaf2cf2..3a946fb18915 100644 --- a/plugin/trino-sqlserver/src/test/java/io/trino/plugin/sqlserver/BaseSqlServerTypeMapping.java +++ b/plugin/trino-sqlserver/src/test/java/io/trino/plugin/sqlserver/BaseSqlServerTypeMapping.java @@ -56,6 +56,7 @@ import static io.trino.spi.type.VarcharType.createVarcharType; import static io.trino.sql.planner.assertions.PlanMatchPattern.tableScan; import static io.trino.testing.TestingNames.randomNameSuffix; +import static io.trino.type.JsonType.JSON; import static java.lang.String.format; import static java.time.ZoneOffset.UTC; import static org.assertj.core.api.Assertions.assertThat; @@ -998,6 +999,30 @@ public void testSqlServerDatetimeOffsetHistoricalDatesRangeQuery() } } + @Test + public void testSqlServerJson() + { + // Conforms to RFC 4627, so not scalar values, but only objects and arrays. + SqlDataTypeTest.create() + .addRoundTrip("json", "JSON '{}'", JSON, "JSON '{}'") + .addRoundTrip("json", "NULL", JSON, "CAST(NULL AS JSON)") + .addRoundTrip("json", "JSON '{\"a\":1,\"b\":2}'", JSON, "JSON '{\"a\":1,\"b\":2}'") + .addRoundTrip("json", "JSON '{\"a\":[1,2,3],\"b\":{\"aa\":11,\"bb\":[{\"a\":1,\"b\":2},{\"a\":0}]}}'", JSON, "JSON '{\"a\":[1,2,3],\"b\":{\"aa\":11,\"bb\":[{\"a\":1,\"b\":2},{\"a\":0}]}}'") + .addRoundTrip("json", "JSON '[]'", JSON, "JSON '[]'") + .execute(getQueryRunner(), sqlServerCreateAndTrinoInsert("test_json")); + + SqlDataTypeTest.create() + .addRoundTrip("json", "JSON '{}'", JSON, "JSON '{}'") + .addRoundTrip("json", "NULL", JSON, "CAST(NULL AS JSON)") + .addRoundTrip("json", "JSON '{\"a\":1,\"b\":2}'", JSON, "JSON '{\"a\":1,\"b\":2}'") + .addRoundTrip("json", "JSON '{\"a\":[1,2,3],\"b\":{\"aa\":11,\"bb\":[{\"a\":1,\"b\":2},{\"a\":0}]}}'", JSON, "JSON '{\"a\":[1,2,3],\"b\":{\"aa\":11,\"bb\":[{\"a\":1,\"b\":2},{\"a\":0}]}}'") + .addRoundTrip("json", "JSON '[]'", JSON, "JSON '[]'") + .execute(getQueryRunner(), trinoCreateAsSelect("test_json")) + .execute(getQueryRunner(), trinoCreateAndInsert("test_json")); + + } + + protected DataSetup trinoCreateAsSelect(String tableNamePrefix) { return trinoCreateAsSelect(getSession(), tableNamePrefix); diff --git a/plugin/trino-sqlserver/src/test/java/io/trino/plugin/sqlserver/TestSqlServerClient.java b/plugin/trino-sqlserver/src/test/java/io/trino/plugin/sqlserver/TestSqlServerClient.java index 50958eed81d9..f09dc32c7368 100644 --- a/plugin/trino-sqlserver/src/test/java/io/trino/plugin/sqlserver/TestSqlServerClient.java +++ b/plugin/trino-sqlserver/src/test/java/io/trino/plugin/sqlserver/TestSqlServerClient.java @@ -38,6 +38,7 @@ import static io.trino.spi.type.BooleanType.BOOLEAN; import static io.trino.spi.type.DoubleType.DOUBLE; import static io.trino.testing.TestingConnectorSession.SESSION; +import static io.trino.type.InternalTypeManager.TESTING_TYPE_MANAGER; import static org.assertj.core.api.Assertions.assertThat; public class TestSqlServerClient @@ -63,6 +64,7 @@ public class TestSqlServerClient throw new UnsupportedOperationException(); }, new DefaultQueryBuilder(RemoteQueryModifier.NONE), + TESTING_TYPE_MANAGER, new DefaultIdentifierMapping(), RemoteQueryModifier.NONE); diff --git a/plugin/trino-sqlserver/src/test/java/io/trino/plugin/sqlserver/TestSqlServerTypeMapping.java b/plugin/trino-sqlserver/src/test/java/io/trino/plugin/sqlserver/TestSqlServerTypeMapping.java index 762ffdb5b94f..70b02d2a05f7 100644 --- a/plugin/trino-sqlserver/src/test/java/io/trino/plugin/sqlserver/TestSqlServerTypeMapping.java +++ b/plugin/trino-sqlserver/src/test/java/io/trino/plugin/sqlserver/TestSqlServerTypeMapping.java @@ -14,6 +14,9 @@ package io.trino.plugin.sqlserver; import io.trino.testing.QueryRunner; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assumptions.abort; public class TestSqlServerTypeMapping extends BaseSqlServerTypeMapping @@ -26,4 +29,11 @@ protected QueryRunner createQueryRunner() return SqlServerQueryRunner.builder(sqlServer) .build(); } + + @Test + @Override + public void testSqlServerJson() + { + abort("json type is not supported in SQL Server 2019-CU28-ubuntu-20.04"); + } }