From 39404da43ae5f8c4727114067cc22a258b2e81ab Mon Sep 17 00:00:00 2001 From: thehkkim Date: Wed, 25 Mar 2026 15:35:34 +0900 Subject: [PATCH] Add Tibero connector Add a new Tibero connector that allows querying and creating tables in an external Tibero database. The connector is based on the JDBC connector framework and supports standard SQL operations. The Tibero JDBC driver (tibero7-jdbc.jar) is not bundled with the plugin because it is not available in public Maven repositories. Users must obtain the driver from TmaxSoft and place it in the plugin directory manually. Co-Authored-By: thehkkim --- docs/src/main/sphinx/connector.md | 1 + docs/src/main/sphinx/connector/tibero.md | 215 +++++++++ plugin/trino-tibero/pom.xml | 122 +++++ .../io/trino/plugin/tibero/TiberoClient.java | 431 ++++++++++++++++++ .../plugin/tibero/TiberoClientModule.java | 97 ++++ .../io/trino/plugin/tibero/TiberoPlugin.java | 25 + .../trino/plugin/tibero/TestTiberoClient.java | 122 +++++ .../trino/plugin/tibero/TestTiberoPlugin.java | 38 ++ .../plugin/tibero/TestTiberoTypeMapping.java | 285 ++++++++++++ pom.xml | 1 + 10 files changed, 1337 insertions(+) create mode 100644 docs/src/main/sphinx/connector/tibero.md create mode 100644 plugin/trino-tibero/pom.xml create mode 100644 plugin/trino-tibero/src/main/java/io/trino/plugin/tibero/TiberoClient.java create mode 100644 plugin/trino-tibero/src/main/java/io/trino/plugin/tibero/TiberoClientModule.java create mode 100644 plugin/trino-tibero/src/main/java/io/trino/plugin/tibero/TiberoPlugin.java create mode 100644 plugin/trino-tibero/src/test/java/io/trino/plugin/tibero/TestTiberoClient.java create mode 100644 plugin/trino-tibero/src/test/java/io/trino/plugin/tibero/TestTiberoPlugin.java create mode 100644 plugin/trino-tibero/src/test/java/io/trino/plugin/tibero/TestTiberoTypeMapping.java diff --git a/docs/src/main/sphinx/connector.md b/docs/src/main/sphinx/connector.md index 6805fdca440a..3c2d75481b28 100644 --- a/docs/src/main/sphinx/connector.md +++ b/docs/src/main/sphinx/connector.md @@ -43,6 +43,7 @@ Snowflake SQL Server System Thrift +Tibero TPC-DS TPC-H ``` diff --git a/docs/src/main/sphinx/connector/tibero.md b/docs/src/main/sphinx/connector/tibero.md new file mode 100644 index 000000000000..f63fcbc7662f --- /dev/null +++ b/docs/src/main/sphinx/connector/tibero.md @@ -0,0 +1,215 @@ +# Tibero connector + +The Tibero connector allows querying and creating tables in an external +[Tibero](https://www.tmaxtibero.com/product/productView.do?prod_cd=tibero&detail_gubun=prod_main) database. +Tibero is an enterprise-grade relational database management system developed +by TmaxSoft. + +## Requirements + +To connect to Tibero, you need: + +- Tibero 6 or higher. +- Tibero JDBC driver (`tibero7-jdbc.jar`) manually copied into the Trino plugin + directory. The driver is not bundled with Trino because it is not available + in public Maven repositories. + +## JDBC driver installation + +The Tibero JDBC driver must be obtained from TmaxSoft and placed in the Trino +plugin directory manually. + +### Obtaining the driver + +- **If you have Tibero database installed**: The JDBC driver is included in the + Tibero installation at `$TB_HOME/client/lib/jar/`. See the + [Tibero JDBC documentation](https://docs.tibero.com/tibero/en/topics/development/jdbc-developers-guide/introduction-to-tibero-jdbc#default-path) + for more details. + +- **If you don't have Tibero installed**: Download the Tibero distribution from + [TechNet](https://technet.tibero.com/en/front/download/findDownloadList.do). + After extracting the archive, the JDBC driver is located at + `$TB_HOME/client/lib/jar/`. + +Contact TmaxSoft for licensing information and to ensure the driver can be used +in your environment. + +### Installing the driver + +Copy the JDBC driver JAR file into the Tibero connector plugin directory: + +```bash +cp /path/to/tibero7-jdbc.jar /plugin/tibero/ +``` + +### Docker deployment + +When using the official `trinodb/trino` Docker image, the Tibero connector +plugin is included but the JDBC driver must be added separately. Create a +custom Docker image as follows: + +```dockerfile +FROM trinodb/trino:{doc_version} + +# Copy the Tibero JDBC driver obtained from TmaxSoft +COPY tibero7-jdbc.jar /usr/lib/trino/plugin/tibero/ + +# Add the Tibero catalog configuration +COPY tibero.properties /etc/trino/catalog/tibero.properties +``` + +Build and run the image: + +```bash +docker build -t trino-tibero . +docker run -p 8080:8080 trino-tibero +``` + +> **Note:** +Ensure you have the appropriate license from TmaxSoft to use the Tibero JDBC +driver in your environment. The driver's usage is subject to TmaxSoft's +licensing terms. + +## Configuration + +To configure the Tibero connector as the `example` catalog, create a file +named `example.properties` in `etc/catalog`. Include the following +connection properties in the file: + +```text +connector.name=tibero +connection-url=jdbc:tibero:thin:@example.net:8629:tibero +connection-user=tibero +connection-password=secret +``` + +The `connection-url` defines the connection information and parameters to pass +to the JDBC driver. The Tibero connector uses the Tibero JDBC Thin driver. +The format is: + +``` +jdbc:tibero:thin:@:: +``` + +The `connection-user` and `connection-password` are typically required and +determine the user credentials for the connection, often a service user. You can +use {doc}`secrets ` to avoid actual values in the catalog +properties files. + + +## Querying Tibero + +The Tibero connector provides a schema for every Tibero database. + +Run `SHOW SCHEMAS` to see the available Tibero databases: + +``` +SHOW SCHEMAS FROM example; +``` + +If you used a different name for your catalog properties file, use that catalog +name instead of `example`. + +> **Note:** +The Tibero user must have access to the table in order to access it from Trino. +The user configuration, in the connection properties file, determines your +privileges in these schemas. + + +### Examples + +If you have a Tibero database named `web`, run `SHOW TABLES` to see the +tables it contains: + +``` +SHOW TABLES FROM example.web; +``` + +To see a list of the columns in the `clicks` table in the `web` +database, run either of the following: + +``` +DESCRIBE example.web.clicks; +SHOW COLUMNS FROM example.web.clicks; +``` + +To access the clicks table in the web database, run the following: + +``` +SELECT * FROM example.web.clicks; +``` + + +## Type mapping + +Because Trino and Tibero each support types that the other does not, this +connector {ref}`modifies some types ` when reading or +writing data. Data types may not map the same way in both directions between +Trino and the data source. Refer to the following sections for type mapping in +each direction. + +### Tibero to Trino type mapping + +Trino supports reading the following Tibero database types: + +| Tibero type | Trino type | Notes | +|-------------|------------|-------| +| `SMALLINT` | `SMALLINT` | | +| `INTEGER` | `INTEGER` | | +| `BIGINT` | `BIGINT` | | +| `REAL` | `REAL` | | +| `DOUBLE` | `DOUBLE` | | +| `NUMBER(p, s)`, `DECIMAL(p, s)` | `DECIMAL(p, s)` | Default precision is 38 if not specified | +| `CHAR(n)` | `CHAR(n)` | | +| `VARCHAR(n)`, `NVARCHAR(n)` | `VARCHAR(n)` | | +| `CLOB`, `NCLOB` | `VARCHAR` | Unbounded VARCHAR | +| `RAW(n)`, `BLOB` | `VARBINARY` | | +| `DATE` | `TIMESTAMP(0)` | Tibero DATE includes time components | +| `TIMESTAMP(p)` | `TIMESTAMP(p)` | Precision up to 9 digits | + +No other types are supported. + +### Trino to Tibero type mapping + +Trino supports creating tables with the following types in Tibero: + +| Trino type | Tibero type | Notes | +|------------|-------------|-------| +| `SMALLINT` | `SMALLINT` | | +| `INTEGER` | `INTEGER` | | +| `BIGINT` | `BIGINT` | | +| `REAL` | `REAL` | | +| `DOUBLE` | `DOUBLE PRECISION` | | +| `CHAR(n)` | `CHAR(n)` | | +| `VARCHAR(n)`, `VARCHAR` | `VARCHAR(n)`, `VARCHAR` | Unbounded VARCHAR is supported | + +No other types are supported. + + +### Mapping datetime types + +Writing a timestamp with fractional second precision (`p`) greater than 9 +rounds the fractional seconds to nine digits. + +Tibero `DATE` type stores year, month, day, hour, minute, and seconds, so it +is mapped to Trino `TIMESTAMP(0)`. + +```{include} jdbc-type-mapping.fragment +``` + + +## SQL support + +The connector provides read access and write access to data and metadata in +Tibero. In addition to the [globally available](sql-globally-available) and +[read operation](sql-read-operations) statements, the connector supports the +following features: + +- [](/sql/insert) +- [](/sql/delete) +- [](/sql/truncate) +- [](/sql/create-table) +- [](/sql/create-table-as) +- [](/sql/drop-table) +- [](/sql/alter-table) +- [](/sql/comment) diff --git a/plugin/trino-tibero/pom.xml b/plugin/trino-tibero/pom.xml new file mode 100644 index 000000000000..7f515085d4f2 --- /dev/null +++ b/plugin/trino-tibero/pom.xml @@ -0,0 +1,122 @@ + + + 4.0.0 + + + io.trino + trino-root + 481-SNAPSHOT + ../../pom.xml + + + trino-tibero + trino-plugin + ${project.artifactId} + Trino - Tibero connector + + + + com.google.guava + guava + + + + com.google.inject + guice + classes + + + + io.airlift + configuration + + + + io.trino + trino-base-jdbc + + + + io.trino + trino-plugin-toolkit + + + + com.fasterxml.jackson.core + jackson-annotations + provided + + + + io.airlift + slice + provided + + + + io.opentelemetry + opentelemetry-api + provided + + + + io.opentelemetry + opentelemetry-api-incubator + provided + + + + io.opentelemetry + opentelemetry-context + provided + + + + io.trino + trino-spi + provided + + + + io.airlift + junit-extensions + test + + + + io.airlift + testing + test + + + + io.trino + trino-main + test + + + + io.trino + trino-testing + test + + + + org.assertj + assertj-core + test + + + + org.junit.jupiter + junit-jupiter-api + test + + + + org.junit.jupiter + junit-jupiter-engine + test + + + diff --git a/plugin/trino-tibero/src/main/java/io/trino/plugin/tibero/TiberoClient.java b/plugin/trino-tibero/src/main/java/io/trino/plugin/tibero/TiberoClient.java new file mode 100644 index 000000000000..7616797e0c31 --- /dev/null +++ b/plugin/trino-tibero/src/main/java/io/trino/plugin/tibero/TiberoClient.java @@ -0,0 +1,431 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.trino.plugin.tibero; + +import com.google.inject.Inject; +import io.trino.plugin.base.mapping.IdentifierMapping; +import io.trino.plugin.jdbc.BaseJdbcClient; +import io.trino.plugin.jdbc.BaseJdbcConfig; +import io.trino.plugin.jdbc.ColumnMapping; +import io.trino.plugin.jdbc.ConnectionFactory; +import io.trino.plugin.jdbc.JdbcTypeHandle; +import io.trino.plugin.jdbc.LongReadFunction; +import io.trino.plugin.jdbc.LongWriteFunction; +import io.trino.plugin.jdbc.ObjectReadFunction; +import io.trino.plugin.jdbc.ObjectWriteFunction; +import io.trino.plugin.jdbc.QueryBuilder; +import io.trino.plugin.jdbc.WriteMapping; +import io.trino.plugin.jdbc.logging.RemoteQueryModifier; +import io.trino.spi.TrinoException; +import io.trino.spi.connector.ConnectorSession; +import io.trino.spi.type.CharType; +import io.trino.spi.type.DecimalType; +import io.trino.spi.type.LongTimestamp; +import io.trino.spi.type.TimestampType; +import io.trino.spi.type.Type; +import io.trino.spi.type.VarcharType; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.sql.Timestamp; +import java.sql.Types; +import java.time.LocalDateTime; +import java.time.ZoneOffset; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeFormatterBuilder; +import java.time.temporal.ChronoField; +import java.util.Optional; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Verify.verify; +import static io.airlift.slice.Slices.utf8Slice; +import static io.airlift.slice.Slices.wrappedBuffer; +import static io.trino.plugin.jdbc.JdbcErrorCode.JDBC_ERROR; +import static io.trino.plugin.jdbc.PredicatePushdownController.DISABLE_PUSHDOWN; +import static io.trino.plugin.jdbc.PredicatePushdownController.FULL_PUSHDOWN; +import static io.trino.plugin.jdbc.StandardColumnMappings.bigintColumnMapping; +import static io.trino.plugin.jdbc.StandardColumnMappings.bigintWriteFunction; +import static io.trino.plugin.jdbc.StandardColumnMappings.charReadFunction; +import static io.trino.plugin.jdbc.StandardColumnMappings.charWriteFunction; +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.fromLongTrinoTimestamp; +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.realColumnMapping; +import static io.trino.plugin.jdbc.StandardColumnMappings.realWriteFunction; +import static io.trino.plugin.jdbc.StandardColumnMappings.smallintColumnMapping; +import static io.trino.plugin.jdbc.StandardColumnMappings.smallintWriteFunction; +import static io.trino.plugin.jdbc.StandardColumnMappings.toLongTrinoTimestamp; +import static io.trino.plugin.jdbc.StandardColumnMappings.toTrinoTimestamp; +import static io.trino.plugin.jdbc.StandardColumnMappings.varbinaryWriteFunction; +import static io.trino.plugin.jdbc.StandardColumnMappings.varcharReadFunction; +import static io.trino.plugin.jdbc.StandardColumnMappings.varcharWriteFunction; +import static io.trino.plugin.jdbc.TypeHandlingJdbcSessionProperties.getUnsupportedTypeHandling; +import static io.trino.plugin.jdbc.UnsupportedTypeHandling.CONVERT_TO_VARCHAR; +import static io.trino.spi.StandardErrorCode.NOT_SUPPORTED; +import static io.trino.spi.type.BigintType.BIGINT; +import static io.trino.spi.type.CharType.createCharType; +import static io.trino.spi.type.DoubleType.DOUBLE; +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.TimestampType.MAX_SHORT_PRECISION; +import static io.trino.spi.type.TimestampType.TIMESTAMP_SECONDS; +import static io.trino.spi.type.TimestampType.createTimestampType; +import static io.trino.spi.type.Timestamps.MICROSECONDS_PER_SECOND; +import static io.trino.spi.type.VarbinaryType.VARBINARY; +import static io.trino.spi.type.VarcharType.createUnboundedVarcharType; +import static io.trino.spi.type.VarcharType.createVarcharType; +import static java.lang.Math.floorDiv; +import static java.lang.Math.floorMod; +import static java.lang.String.format; + +public class TiberoClient + extends BaseJdbcClient +{ + private static final int MAX_TIBERO_TIMESTAMP_PRECISION = 9; + + private static final DateTimeFormatter TIMESTAMP_SECONDS_FORMATTER = DateTimeFormatter.ofPattern("uuuu-MM-dd HH:mm:ss"); + + private static final DateTimeFormatter TIMESTAMP_NANO_OPTIONAL_FORMATTER = new DateTimeFormatterBuilder() + .appendPattern("uuuu-MM-dd HH:mm:ss") + .optionalStart() + .appendFraction(ChronoField.NANO_OF_SECOND, 0, 9, true) + .optionalEnd() + .toFormatter(); + + @Inject + public TiberoClient( + BaseJdbcConfig config, + ConnectionFactory connectionFactory, + QueryBuilder queryBuilder, + IdentifierMapping identifierMapping, + RemoteQueryModifier remoteQueryModifier) + { + super("\"", connectionFactory, queryBuilder, config.getJdbcTypesMappedToVarchar(), identifierMapping, remoteQueryModifier, true); + } + + @Override + public Optional toColumnMapping(ConnectorSession session, Connection connection, JdbcTypeHandle typeHandle) + { + Optional mapping = getForcedMappingToVarchar(typeHandle); + if (mapping.isPresent()) { + return mapping; + } + + String jdbcTypeName = typeHandle.jdbcTypeName() + .orElseThrow(() -> new TrinoException(JDBC_ERROR, "Type name is missing: " + typeHandle)); + + if (jdbcTypeName.equalsIgnoreCase("date")) { + return Optional.of(ColumnMapping.longMapping( + TIMESTAMP_SECONDS, + tiberoTimestampReadFunction(TIMESTAMP_SECONDS), + trinoTimestampToTiberoDateWriteFunction(), + FULL_PUSHDOWN)); + } + switch (typeHandle.jdbcType()) { + case Types.SMALLINT: + return Optional.of(smallintColumnMapping()); + + case Types.INTEGER: + return Optional.of(integerColumnMapping()); + + case Types.BIGINT: + return Optional.of(bigintColumnMapping()); + + case Types.REAL: + return Optional.of(realColumnMapping()); + + case Types.DOUBLE: + return Optional.of(doubleColumnMapping()); + + case Types.CHAR: + return Optional.of(charColumnMapping(typeHandle.requiredColumnSize())); + + case Types.NUMERIC: + case Types.DECIMAL: + int precision = typeHandle.columnSize().orElse(38); + int scale = typeHandle.decimalDigits().orElse(0); + DecimalType decimalType = DecimalType.createDecimalType(precision, scale); + return Optional.of(decimalColumnMapping(decimalType)); + + case Types.TIMESTAMP: + int timestampPrecision = typeHandle.requiredDecimalDigits(); + return Optional.of(tiberoTimestampColumnMapping(createTimestampType(timestampPrecision))); + + case Types.CLOB: + case Types.NCLOB: + return Optional.of(ColumnMapping.sliceMapping( + createUnboundedVarcharType(), + (resultSet, columnIndex) -> utf8Slice(resultSet.getString(columnIndex)), + varcharWriteFunction(), + DISABLE_PUSHDOWN)); + + case Types.VARBINARY: // Tibero's RAW(n) + case Types.BLOB: + return Optional.of(ColumnMapping.sliceMapping( + VARBINARY, + (resultSet, columnIndex) -> wrappedBuffer(resultSet.getBytes(columnIndex)), + varbinaryWriteFunction(), + DISABLE_PUSHDOWN)); + + case Types.VARCHAR: + case Types.NVARCHAR: + return Optional.of(ColumnMapping.sliceMapping( + createVarcharType(typeHandle.requiredColumnSize()), + (varcharResultSet, varcharColumnIndex) -> utf8Slice(varcharResultSet.getString(varcharColumnIndex)), + varcharWriteFunction(), + FULL_PUSHDOWN)); + } + + if (getUnsupportedTypeHandling(session) == CONVERT_TO_VARCHAR) { + return mapToUnboundedVarchar(typeHandle); + } + + return Optional.empty(); + } + + private static ColumnMapping tiberoTimestampColumnMapping(TimestampType timestampType) + { + if (timestampType.isShort()) { + return ColumnMapping.longMapping( + timestampType, + tiberoTimestampReadFunction(timestampType), + tiberoTimestampWriteFunction(timestampType), + FULL_PUSHDOWN); + } + return ColumnMapping.objectMapping( + timestampType, + tiberoLongTimestampReadFunction(timestampType), + tiberoLongTimestampWriteFunction(timestampType), + FULL_PUSHDOWN); + } + + private static ObjectReadFunction tiberoLongTimestampReadFunction(TimestampType timestampType) + { + verifyLongTimestampPrecision(timestampType); + return ObjectReadFunction.of( + LongTimestamp.class, + (resultSet, columnIndex) -> { + LocalDateTime timestamp = resultSet.getObject(columnIndex, LocalDateTime.class); + // Adjust years when the value is B.C. dates because Oracle returns +1 year unless converting to string in their server side + if (timestamp.getYear() <= 0) { + timestamp = timestamp.minusYears(1); + } + return toLongTrinoTimestamp(timestampType, timestamp); + }); + } + + private static ObjectWriteFunction tiberoLongTimestampWriteFunction(TimestampType timestampType) + { + int precision = timestampType.getPrecision(); + verifyLongTimestampPrecision(timestampType); + + return new ObjectWriteFunction() { + @Override + public Class getJavaType() + { + return LongTimestamp.class; + } + + @Override + public void set(PreparedStatement statement, int index, Object value) + throws SQLException + { + LocalDateTime timestamp = fromLongTrinoTimestamp((LongTimestamp) value, precision); + statement.setString(index, TIMESTAMP_NANO_OPTIONAL_FORMATTER.format(timestamp)); + } + + @Override + public String getBindExpression() + { + return getTiberoBindExpression(precision); + } + + @Override + public void setNull(PreparedStatement statement, int index) + throws SQLException + { + statement.setNull(index, Types.VARCHAR); + } + }; + } + + private static void verifyLongTimestampPrecision(TimestampType timestampType) + { + int precision = timestampType.getPrecision(); + checkArgument(precision > MAX_SHORT_PRECISION && precision <= MAX_TIBERO_TIMESTAMP_PRECISION, + "Precision is out of range: %s", precision); + } + + private static LongWriteFunction trinoTimestampToTiberoDateWriteFunction() + { + return new LongWriteFunction() + { + @Override + public String getBindExpression() + { + // Oracle's DATE stores year, month, day, hour, minute, seconds, but not second fraction + return "TO_DATE(?, 'SYYYY-MM-DD HH24:MI:SS')"; + } + + @Override + public void set(PreparedStatement statement, int index, long value) + throws SQLException + { + long epochSecond = floorDiv(value, MICROSECONDS_PER_SECOND); + int microsOfSecond = floorMod(value, MICROSECONDS_PER_SECOND); + verify(microsOfSecond == 0, "Micros of second must be zero: '%s'", value); + LocalDateTime localDateTime = LocalDateTime.ofEpochSecond(epochSecond, 0, ZoneOffset.UTC); + statement.setString(index, TIMESTAMP_SECONDS_FORMATTER.format(localDateTime)); + } + + @Override + public void setNull(PreparedStatement statement, int index) + throws SQLException + { + statement.setNull(index, Types.VARCHAR); + } + }; + } + + private static LongReadFunction tiberoTimestampReadFunction(TimestampType timestampType) + { + return (resultSet, columnIndex) -> { + Timestamp ts = resultSet.getTimestamp(columnIndex); + + // LocalDateTime timestamp = resultSet.getObject(columnIndex, LocalDateTime.class); + LocalDateTime timestamp = ts.toLocalDateTime(); + + // Adjust years when the value is B.C. dates because Oracle returns +1 year unless converting to string in their server side + if (timestamp.getYear() <= 0) { + timestamp = timestamp.minusYears(1); + } + return toTrinoTimestamp(timestampType, timestamp); + }; + } + + private static LongWriteFunction tiberoTimestampWriteFunction(TimestampType timestampType) + { + return new LongWriteFunction() + { + @Override + public String getBindExpression() + { + return getTiberoBindExpression(timestampType.getPrecision()); + } + + @Override + public void set(PreparedStatement statement, int index, long epochMicros) + throws SQLException + { + LocalDateTime timestamp = fromTrinoTimestamp(epochMicros); + statement.setString(index, TIMESTAMP_NANO_OPTIONAL_FORMATTER.format(timestamp)); + } + + @Override + public void setNull(PreparedStatement statement, int index) + throws SQLException + { + statement.setNull(index, Types.VARCHAR); + } + }; + } + + private static String getTiberoBindExpression(int precision) + { + if (precision == 0) { + return "TO_TIMESTAMP(?, 'SYYYY-MM-DD HH24:MI:SS')"; + } + if (precision <= 2) { + return "TO_TIMESTAMP(?, 'SYYYY-MM-DD HH24:MI:SS.FF')"; + } + + return format("TO_TIMESTAMP(?, 'SYYYY-MM-DD HH24:MI:SS.FF%d')", precision); + } + + @Override + public WriteMapping toWriteMapping(ConnectorSession session, Type type) + { + if (type == SMALLINT) { + return WriteMapping.longMapping("smallint", smallintWriteFunction()); + } + if (type == INTEGER) { + return WriteMapping.longMapping("integer", integerWriteFunction()); + } + if (type == BIGINT) { + return WriteMapping.longMapping("bigint", bigintWriteFunction()); + } + + if (type == REAL) { + return WriteMapping.longMapping("real", realWriteFunction()); + } + if (type == DOUBLE) { + return WriteMapping.doubleMapping("double precision", doubleWriteFunction()); + } + + if (type instanceof CharType charType) { + return WriteMapping.sliceMapping("char(" + charType.getLength() + ")", charWriteFunction()); + } + + if (type instanceof VarcharType varcharType) { + String dataType; + if (varcharType.isUnbounded()) { + dataType = "varchar"; + } + else { + dataType = "varchar(" + varcharType.getBoundedLength() + ")"; + } + return WriteMapping.sliceMapping(dataType, varcharWriteFunction()); + } + + if (type instanceof TimestampType timestampType) { + if (timestampType.isShort()) { + return WriteMapping.longMapping("timestamp(" + timestampType.getPrecision() + ")", tiberoTimestampWriteFunction(timestampType)); + } + return WriteMapping.objectMapping("timestamp(" + timestampType.getPrecision() + ")", tiberoLongTimestampWriteFunction(timestampType)); + } + + throw new TrinoException(NOT_SUPPORTED, "Unsupported column type: " + type.getDisplayName()); + } + + private static ColumnMapping charColumnMapping(int charLength) + { + if (charLength > CharType.MAX_LENGTH) { + return varcharColumnMapping(charLength); + } + CharType charType = createCharType(charLength); + return ColumnMapping.sliceMapping( + charType, + charReadFunction(charType), + charWriteFunction(), + DISABLE_PUSHDOWN); + } + + private static ColumnMapping varcharColumnMapping(int varcharLength) + { + VarcharType varcharType = varcharLength <= VarcharType.MAX_LENGTH + ? createVarcharType(varcharLength) + : createUnboundedVarcharType(); + return ColumnMapping.sliceMapping( + varcharType, + varcharReadFunction(varcharType), + varcharWriteFunction(), + DISABLE_PUSHDOWN); + } +} diff --git a/plugin/trino-tibero/src/main/java/io/trino/plugin/tibero/TiberoClientModule.java b/plugin/trino-tibero/src/main/java/io/trino/plugin/tibero/TiberoClientModule.java new file mode 100644 index 000000000000..45b742ddf3b6 --- /dev/null +++ b/plugin/trino-tibero/src/main/java/io/trino/plugin/tibero/TiberoClientModule.java @@ -0,0 +1,97 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.trino.plugin.tibero; + +import com.google.inject.Binder; +import com.google.inject.Provides; +import com.google.inject.Scopes; +import com.google.inject.Singleton; +import io.airlift.configuration.AbstractConfigurationAwareModule; +import io.opentelemetry.api.OpenTelemetry; +import io.trino.plugin.jdbc.BaseJdbcConfig; +import io.trino.plugin.jdbc.ConnectionFactory; +import io.trino.plugin.jdbc.DriverConnectionFactory; +import io.trino.plugin.jdbc.ForBaseJdbc; +import io.trino.plugin.jdbc.JdbcClient; +import io.trino.plugin.jdbc.credential.CredentialProvider; +import io.trino.spi.connector.ConnectorSession; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.SQLException; +import java.util.Properties; + +public class TiberoClientModule + extends AbstractConfigurationAwareModule +{ + @Override + public void setup(Binder binder) + { + binder.bind(JdbcClient.class).annotatedWith(ForBaseJdbc.class).to(TiberoClient.class).in(Scopes.SINGLETON); + } + + @Provides + @Singleton + @ForBaseJdbc + public static ConnectionFactory getConnectionFactory(BaseJdbcConfig config, CredentialProvider credentialProvider, OpenTelemetry openTelemetry) + { + // The Tibero JDBC driver (tibero7-jdbc.jar) is not available in public Maven repositories + // and cannot be bundled with the connector. Users must obtain the driver from TmaxSoft + // and place it in the plugin directory manually (see connector documentation). + // + // Driver loading is deferred to the first actual connection attempt to allow the connector + // to initialize successfully even when the driver is not present at startup time. + // This enables plugin loading and configuration validation without requiring the driver JAR. + String connectionUrl = config.getConnectionUrl(); + return new ConnectionFactory() + { + private volatile ConnectionFactory delegate; + + @Override + public Connection openConnection(ConnectorSession session) + throws SQLException + { + return getDelegate().openConnection(session); + } + + @Override + public void close() + throws SQLException + { + if (delegate != null) { + delegate.close(); + } + } + + private ConnectionFactory getDelegate() + throws SQLException + { + if (delegate == null) { + synchronized (this) { + if (delegate == null) { + delegate = DriverConnectionFactory.builder( + DriverManager.getDriver(connectionUrl), + connectionUrl, + credentialProvider) + .setConnectionProperties(new Properties()) + .setOpenTelemetry(openTelemetry) + .build(); + } + } + } + return delegate; + } + }; + } +} diff --git a/plugin/trino-tibero/src/main/java/io/trino/plugin/tibero/TiberoPlugin.java b/plugin/trino-tibero/src/main/java/io/trino/plugin/tibero/TiberoPlugin.java new file mode 100644 index 000000000000..eeb2bb6c16ed --- /dev/null +++ b/plugin/trino-tibero/src/main/java/io/trino/plugin/tibero/TiberoPlugin.java @@ -0,0 +1,25 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.trino.plugin.tibero; + +import io.trino.plugin.jdbc.JdbcPlugin; + +public class TiberoPlugin + extends JdbcPlugin +{ + public TiberoPlugin() + { + super("tibero", TiberoClientModule::new); + } +} diff --git a/plugin/trino-tibero/src/test/java/io/trino/plugin/tibero/TestTiberoClient.java b/plugin/trino-tibero/src/test/java/io/trino/plugin/tibero/TestTiberoClient.java new file mode 100644 index 000000000000..90e4271a0c2f --- /dev/null +++ b/plugin/trino-tibero/src/test/java/io/trino/plugin/tibero/TestTiberoClient.java @@ -0,0 +1,122 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.trino.plugin.tibero; + +import io.trino.plugin.base.mapping.DefaultIdentifierMapping; +import io.trino.plugin.jdbc.BaseJdbcConfig; +import io.trino.plugin.jdbc.DefaultQueryBuilder; +import io.trino.plugin.jdbc.JdbcClient; +import io.trino.plugin.jdbc.WriteFunction; +import io.trino.plugin.jdbc.WriteMapping; +import io.trino.plugin.jdbc.logging.RemoteQueryModifier; +import io.trino.spi.connector.ConnectorSession; +import io.trino.spi.type.Type; +import io.trino.testing.TestingConnectorSession; +import org.junit.jupiter.api.Test; + +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.sql.Types; + +import static com.google.common.reflect.Reflection.newProxy; +import static io.trino.spi.type.BigintType.BIGINT; +import static io.trino.spi.type.CharType.createCharType; +import static io.trino.spi.type.DoubleType.DOUBLE; +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.TimestampType.TIMESTAMP_MICROS; +import static io.trino.spi.type.TimestampType.TIMESTAMP_MILLIS; +import static io.trino.spi.type.TimestampType.TIMESTAMP_NANOS; +import static io.trino.spi.type.TimestampType.TIMESTAMP_SECONDS; +import static io.trino.spi.type.VarcharType.createUnboundedVarcharType; +import static io.trino.spi.type.VarcharType.createVarcharType; +import static org.assertj.core.api.Assertions.assertThat; + +public class TestTiberoClient +{ + private static final JdbcClient CLIENT = new TiberoClient( + new BaseJdbcConfig(), + session -> { + throw new UnsupportedOperationException(); + }, + new DefaultQueryBuilder(RemoteQueryModifier.NONE), + new DefaultIdentifierMapping(), + RemoteQueryModifier.NONE); + + private static final ConnectorSession SESSION = TestingConnectorSession.SESSION; + + @Test + public void testTypedNullWriteMapping() + throws SQLException + { + testTypedNullWriteMapping(SMALLINT, "smallint", Types.SMALLINT); + testTypedNullWriteMapping(INTEGER, "integer", Types.INTEGER); + testTypedNullWriteMapping(BIGINT, "bigint", Types.BIGINT); + testTypedNullWriteMapping(REAL, "real", Types.REAL); + testTypedNullWriteMapping(DOUBLE, "double precision", Types.DOUBLE); + testTypedNullWriteMapping(createCharType(25), "char(25)", Types.CHAR); + testTypedNullWriteMapping(createUnboundedVarcharType(), "varchar", Types.VARCHAR); + testTypedNullWriteMapping(createVarcharType(123), "varchar(123)", Types.VARCHAR); + } + + @Test + public void testTimestampWriteMapping() + throws SQLException + { + testTimestampWriteMapping(TIMESTAMP_SECONDS, "TO_TIMESTAMP(?, 'SYYYY-MM-DD HH24:MI:SS')", Types.VARCHAR); + testTimestampWriteMapping(TIMESTAMP_MILLIS, "TO_TIMESTAMP(?, 'SYYYY-MM-DD HH24:MI:SS.FF3')", Types.VARCHAR); + testTimestampWriteMapping(TIMESTAMP_MICROS, "TO_TIMESTAMP(?, 'SYYYY-MM-DD HH24:MI:SS.FF6')", Types.VARCHAR); + testTimestampWriteMapping(TIMESTAMP_NANOS, "TO_TIMESTAMP(?, 'SYYYY-MM-DD HH24:MI:SS.FF9')", Types.VARCHAR); + } + + private void testTypedNullWriteMapping(Type type, String dataType, int nullJdbcType) + throws SQLException + { + WriteMapping writeMapping = CLIENT.toWriteMapping(SESSION, type); + assertThat(writeMapping.getWriteFunction()).isNotNull(); + assertThat(writeMapping.getDataType()).isEqualTo(dataType); + + WriteFunction writeFunction = writeMapping.getWriteFunction(); + PreparedStatement statement = newProxy(PreparedStatement.class, (proxy, method, args) -> { + if (method.getName().equals("setNull")) { + assertThat(args[1]).isEqualTo(nullJdbcType); + return null; + } + throw new UnsupportedOperationException("Unexpected method call: " + method.getName()); + }); + + writeFunction.setNull(statement, 1); + } + + private void testTimestampWriteMapping(Type type, String bindExpression, int nullJdbcType) + throws SQLException + { + WriteMapping writeMapping = CLIENT.toWriteMapping(SESSION, type); + assertThat(writeMapping.getWriteFunction()).isNotNull(); + WriteFunction writeFunction = writeMapping.getWriteFunction(); + + assertThat(writeFunction.getBindExpression()).isEqualTo(bindExpression); + + PreparedStatement statement = newProxy(PreparedStatement.class, (proxy, method, args) -> { + if (method.getName().equals("setNull")) { + assertThat(args[1]).isEqualTo(nullJdbcType); + return null; + } + throw new UnsupportedOperationException("Unexpected method call: " + method.getName()); + }); + + writeFunction.setNull(statement, 1); + } +} diff --git a/plugin/trino-tibero/src/test/java/io/trino/plugin/tibero/TestTiberoPlugin.java b/plugin/trino-tibero/src/test/java/io/trino/plugin/tibero/TestTiberoPlugin.java new file mode 100644 index 000000000000..bce243708cb4 --- /dev/null +++ b/plugin/trino-tibero/src/test/java/io/trino/plugin/tibero/TestTiberoPlugin.java @@ -0,0 +1,38 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.trino.plugin.tibero; + +import com.google.common.collect.ImmutableMap; +import io.trino.spi.Plugin; +import io.trino.spi.connector.ConnectorFactory; +import io.trino.testing.TestingConnectorContext; +import org.junit.jupiter.api.Test; + +import static com.google.common.collect.Iterables.getOnlyElement; + +public class TestTiberoPlugin +{ + @Test + public void testCreateConnector() + { + Plugin plugin = new TiberoPlugin(); + ConnectorFactory factory = getOnlyElement(plugin.getConnectorFactories()); + factory.create( + "test", + ImmutableMap.of( + "connection-url", "jdbc:tibero:thin:@//localhost:8629/tibero", + "bootstrap.quiet", "true"), + new TestingConnectorContext()).shutdown(); + } +} diff --git a/plugin/trino-tibero/src/test/java/io/trino/plugin/tibero/TestTiberoTypeMapping.java b/plugin/trino-tibero/src/test/java/io/trino/plugin/tibero/TestTiberoTypeMapping.java new file mode 100644 index 000000000000..7c299e82826b --- /dev/null +++ b/plugin/trino-tibero/src/test/java/io/trino/plugin/tibero/TestTiberoTypeMapping.java @@ -0,0 +1,285 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.trino.plugin.tibero; + +import io.trino.plugin.base.mapping.DefaultIdentifierMapping; +import io.trino.plugin.jdbc.BaseJdbcConfig; +import io.trino.plugin.jdbc.ColumnMapping; +import io.trino.plugin.jdbc.DefaultQueryBuilder; +import io.trino.plugin.jdbc.JdbcTypeHandle; +import io.trino.plugin.jdbc.logging.RemoteQueryModifier; +import io.trino.spi.connector.ConnectorSession; +import io.trino.spi.type.BigintType; +import io.trino.spi.type.CharType; +import io.trino.spi.type.DecimalType; +import io.trino.spi.type.DoubleType; +import io.trino.spi.type.IntegerType; +import io.trino.spi.type.RealType; +import io.trino.spi.type.SmallintType; +import io.trino.spi.type.TimestampType; +import io.trino.spi.type.VarbinaryType; +import io.trino.spi.type.VarcharType; +import io.trino.testing.TestingConnectorSession; +import org.junit.jupiter.api.Test; + +import java.sql.Connection; +import java.sql.Types; +import java.util.Optional; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Type mapping tests for Tibero connector. + * This class tests the mapping between Tibero database types and Trino types. + */ +public class TestTiberoTypeMapping +{ + private static final TiberoClient CLIENT = new TiberoClient( + new BaseJdbcConfig(), + session -> { + throw new UnsupportedOperationException(); + }, + new DefaultQueryBuilder(RemoteQueryModifier.NONE), + new DefaultIdentifierMapping(), + RemoteQueryModifier.NONE); + + private static final ConnectorSession SESSION = TestingConnectorSession.SESSION; + private static final Connection CONNECTION = null; // Not needed for these tests + + @Test + public void testSmallintMapping() + { + assertThat(toTrinoType(Types.SMALLINT, "smallint")) + .hasValueSatisfying(mapping -> assertThat(mapping.getType()).isEqualTo(SmallintType.SMALLINT)); + } + + @Test + public void testIntegerMapping() + { + assertThat(toTrinoType(Types.INTEGER, "integer")) + .hasValueSatisfying(mapping -> assertThat(mapping.getType()).isEqualTo(IntegerType.INTEGER)); + } + + @Test + public void testBigintMapping() + { + assertThat(toTrinoType(Types.BIGINT, "bigint")) + .hasValueSatisfying(mapping -> assertThat(mapping.getType()).isEqualTo(BigintType.BIGINT)); + } + + @Test + public void testRealMapping() + { + assertThat(toTrinoType(Types.REAL, "real")) + .hasValueSatisfying(mapping -> assertThat(mapping.getType()).isEqualTo(RealType.REAL)); + } + + @Test + public void testDoubleMapping() + { + assertThat(toTrinoType(Types.DOUBLE, "double precision")) + .hasValueSatisfying(mapping -> assertThat(mapping.getType()).isEqualTo(DoubleType.DOUBLE)); + } + + @Test + public void testCharMapping() + { + // CHAR(10) + assertThat(toTrinoType(Types.CHAR, "char", 10, 0)) + .hasValueSatisfying(mapping -> { + assertThat(mapping.getType()).isEqualTo(CharType.createCharType(10)); + }); + + // CHAR(255) + assertThat(toTrinoType(Types.CHAR, "char", 255, 0)) + .hasValueSatisfying(mapping -> { + assertThat(mapping.getType()).isEqualTo(CharType.createCharType(255)); + }); + } + + @Test + public void testVarcharMapping() + { + // VARCHAR(10) + assertThat(toTrinoType(Types.VARCHAR, "varchar", 10, 0)) + .hasValueSatisfying(mapping -> { + assertThat(mapping.getType()).isEqualTo(VarcharType.createVarcharType(10)); + }); + + // VARCHAR(255) + assertThat(toTrinoType(Types.VARCHAR, "varchar", 255, 0)) + .hasValueSatisfying(mapping -> { + assertThat(mapping.getType()).isEqualTo(VarcharType.createVarcharType(255)); + }); + + // VARCHAR(65535) + assertThat(toTrinoType(Types.VARCHAR, "varchar", 65535, 0)) + .hasValueSatisfying(mapping -> { + assertThat(mapping.getType()).isEqualTo(VarcharType.createVarcharType(65535)); + }); + } + + @Test + public void testNvarcharMapping() + { + // NVARCHAR(10) + assertThat(toTrinoType(Types.NVARCHAR, "nvarchar", 10, 0)) + .hasValueSatisfying(mapping -> { + assertThat(mapping.getType()).isEqualTo(VarcharType.createVarcharType(10)); + }); + } + + @Test + public void testDecimalMapping() + { + // DECIMAL(3, 0) + assertThat(toTrinoType(Types.DECIMAL, "decimal", 3, 0)) + .hasValueSatisfying(mapping -> { + assertThat(mapping.getType()).isEqualTo(DecimalType.createDecimalType(3, 0)); + }); + + // DECIMAL(10, 2) + assertThat(toTrinoType(Types.DECIMAL, "decimal", 10, 2)) + .hasValueSatisfying(mapping -> { + assertThat(mapping.getType()).isEqualTo(DecimalType.createDecimalType(10, 2)); + }); + + // DECIMAL(38, 0) + assertThat(toTrinoType(Types.DECIMAL, "decimal", 38, 0)) + .hasValueSatisfying(mapping -> { + assertThat(mapping.getType()).isEqualTo(DecimalType.createDecimalType(38, 0)); + }); + + // DECIMAL(38, 38) + assertThat(toTrinoType(Types.DECIMAL, "decimal", 38, 38)) + .hasValueSatisfying(mapping -> { + assertThat(mapping.getType()).isEqualTo(DecimalType.createDecimalType(38, 38)); + }); + } + + @Test + public void testNumericMapping() + { + // NUMERIC is same as DECIMAL in Tibero + assertThat(toTrinoType(Types.NUMERIC, "numeric", 10, 2)) + .hasValueSatisfying(mapping -> { + assertThat(mapping.getType()).isEqualTo(DecimalType.createDecimalType(10, 2)); + }); + } + + @Test + public void testDateMapping() + { + // Tibero DATE maps to TIMESTAMP(0) in Trino + assertThat(toTrinoType("date")) + .hasValueSatisfying(mapping -> { + assertThat(mapping.getType()).isEqualTo(TimestampType.TIMESTAMP_SECONDS); + }); + } + + @Test + public void testTimestampMapping() + { + // TIMESTAMP(0) + assertThat(toTrinoType(Types.TIMESTAMP, "timestamp", 0, 0)) + .hasValueSatisfying(mapping -> { + assertThat(mapping.getType()).isEqualTo(TimestampType.createTimestampType(0)); + }); + + // TIMESTAMP(3) + assertThat(toTrinoType(Types.TIMESTAMP, "timestamp", 0, 3)) + .hasValueSatisfying(mapping -> { + assertThat(mapping.getType()).isEqualTo(TimestampType.createTimestampType(3)); + }); + + // TIMESTAMP(6) + assertThat(toTrinoType(Types.TIMESTAMP, "timestamp", 0, 6)) + .hasValueSatisfying(mapping -> { + assertThat(mapping.getType()).isEqualTo(TimestampType.createTimestampType(6)); + }); + + // TIMESTAMP(9) + assertThat(toTrinoType(Types.TIMESTAMP, "timestamp", 0, 9)) + .hasValueSatisfying(mapping -> { + assertThat(mapping.getType()).isEqualTo(TimestampType.createTimestampType(9)); + }); + } + + @Test + public void testVarbinaryMapping() + { + // Tibero RAW(n) maps to VARBINARY + assertThat(toTrinoType(Types.VARBINARY, "raw", 100, 0)) + .hasValueSatisfying(mapping -> { + assertThat(mapping.getType()).isEqualTo(VarbinaryType.VARBINARY); + }); + } + + @Test + public void testBlobMapping() + { + // BLOB maps to VARBINARY + assertThat(toTrinoType(Types.BLOB, "blob")) + .hasValueSatisfying(mapping -> { + assertThat(mapping.getType()).isEqualTo(VarbinaryType.VARBINARY); + }); + } + + @Test + public void testClobMapping() + { + // CLOB maps to unbounded VARCHAR + assertThat(toTrinoType(Types.CLOB, "clob")) + .hasValueSatisfying(mapping -> { + assertThat(mapping.getType()).isEqualTo(VarcharType.createUnboundedVarcharType()); + }); + } + + @Test + public void testNclobMapping() + { + // NCLOB maps to unbounded VARCHAR + assertThat(toTrinoType(Types.NCLOB, "nclob")) + .hasValueSatisfying(mapping -> { + assertThat(mapping.getType()).isEqualTo(VarcharType.createUnboundedVarcharType()); + }); + } + + private Optional toTrinoType(int jdbcType, String jdbcTypeName) + { + return toTrinoType(jdbcType, jdbcTypeName, 0, 0); + } + + private Optional toTrinoType(String jdbcTypeName) + { + return CLIENT.toColumnMapping(SESSION, CONNECTION, new JdbcTypeHandle( + Types.OTHER, + Optional.of(jdbcTypeName), + Optional.empty(), + Optional.empty(), + Optional.empty(), + Optional.empty())); + } + + private Optional toTrinoType(int jdbcType, String jdbcTypeName, int columnSize, int decimalDigits) + { + return CLIENT.toColumnMapping(SESSION, CONNECTION, new JdbcTypeHandle( + jdbcType, + Optional.of(jdbcTypeName), + Optional.of(columnSize), + Optional.of(decimalDigits), + Optional.empty(), + Optional.empty())); + } +} diff --git a/pom.xml b/pom.xml index 451ecbf5f0cf..494b6bd684a8 100644 --- a/pom.xml +++ b/pom.xml @@ -118,6 +118,7 @@ plugin/trino-thrift plugin/trino-thrift-api plugin/trino-thrift-testing-server + plugin/trino-tibero plugin/trino-tpcds plugin/trino-tpch service/trino-proxy