diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index ab3d071e8de1..b6ffc39a6853 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -136,6 +136,7 @@ jobs:
!presto-mongodb,!presto-kafka,!presto-elasticsearch,
!presto-redis,
!presto-sqlserver,!presto-postgresql,!presto-mysql,
+ !presto-oracle,
!presto-phoenix,!presto-iceberg,
!presto-docs,!presto-server,!presto-server-rpm,
!presto-kudu'
@@ -159,6 +160,7 @@ jobs:
- "presto-sqlserver,presto-postgresql,presto-mysql"
- "presto-phoenix,presto-iceberg"
- "presto-kudu"
+ - "presto-oracle"
steps:
- uses: actions/checkout@v2
- uses: actions/setup-java@v1
diff --git a/pom.xml b/pom.xml
index 2412bb661480..dcdf1b64fa2d 100644
--- a/pom.xml
+++ b/pom.xml
@@ -141,6 +141,7 @@
presto-google-sheets
presto-bigquery
presto-pinot
+ presto-oracle
diff --git a/presto-docs/src/main/sphinx/connector.rst b/presto-docs/src/main/sphinx/connector.rst
index 54426da2300a..430a9078669e 100644
--- a/presto-docs/src/main/sphinx/connector.rst
+++ b/presto-docs/src/main/sphinx/connector.rst
@@ -27,6 +27,7 @@ from different data sources.
connector/memsql
connector/mongodb
connector/mysql
+ connector/oracle
connector/phoenix
connector/pinot
connector/postgresql
diff --git a/presto-docs/src/main/sphinx/connector/oracle.rst b/presto-docs/src/main/sphinx/connector/oracle.rst
new file mode 100644
index 000000000000..fdba3c8f6a04
--- /dev/null
+++ b/presto-docs/src/main/sphinx/connector/oracle.rst
@@ -0,0 +1,71 @@
+================
+Oracle Connector
+================
+
+The Oracle connector allows querying and creating tables in an
+external Oracle database. This can be used to join data between
+different systems like Oracle and Hive, or between two different
+Oracle instances.
+
+Configuration
+-------------
+
+To configure the Oracle connector, create a catalog properties file
+in ``etc/catalog`` named, for example, ``oracle.properties``, to
+mount the Oracle connector as the ``oracle`` catalog.
+Create the file with the following contents, replacing the
+connection properties as appropriate for your setup:
+
+.. code-block:: none
+
+ connector.name=oracle
+ connection-url=jdbc:oracle:thin:@example.net:1521/ORCLCDB
+ connection-user=root
+ connection-password=secret
+
+Multiple Oracle Servers
+^^^^^^^^^^^^^^^^^^^^^^^
+
+You can have as many catalogs as you need, so if you have additional
+Oracle servers, simply add another properties file to ``etc/catalog``
+with a different name (making sure it ends in ``.properties``). For
+example, if you name the property file ``sales.properties``, Presto
+will create a catalog named ``sales`` using the configured connector.
+
+Querying Oracle
+---------------
+
+The Oracle connector provides a schema for every Oracle *database*.
+You can see the available Oracle databases by running ``SHOW SCHEMAS``::
+
+ SHOW SCHEMAS FROM oracle;
+
+If you have a Oracle database named ``web``, you can view the tables
+in this database by running ``SHOW TABLES``::
+
+ SHOW TABLES FROM oracle.web;
+
+You can see a list of the columns in the ``clicks`` table in the ``web`` database
+using either of the following::
+
+ DESCRIBE oracle.web.clicks;
+ SHOW COLUMNS FROM oracle.web.clicks;
+
+Finally, you can access the ``clicks`` table in the ``web`` database::
+
+ SELECT * FROM oracle.web.clicks;
+
+If you used a different name for your catalog properties file, use
+that catalog name instead of ``oracle`` in the above examples.
+
+Oracle Connector Limitations
+----------------------------
+
+The following SQL statements are not yet supported:
+
+* :doc:`/sql/delete`
+* :doc:`/sql/grant`
+* :doc:`/sql/revoke`
+* :doc:`/sql/show-grants`
+* :doc:`/sql/show-roles`
+* :doc:`/sql/show-role-grants`
diff --git a/presto-oracle/pom.xml b/presto-oracle/pom.xml
new file mode 100644
index 000000000000..2ac9147e047c
--- /dev/null
+++ b/presto-oracle/pom.xml
@@ -0,0 +1,154 @@
+
+
+ 4.0.0
+
+
+ io.prestosql
+ presto-root
+ 334-SNAPSHOT
+
+
+ presto-oracle
+ Presto - Oracle Connector
+ presto-plugin
+
+
+ ${project.parent.basedir}
+
+
+
+
+ com.oracle.ojdbc
+ ojdbc8
+ 19.3.0.0
+
+
+
+ io.prestosql
+ presto-base-jdbc
+
+
+
+ io.airlift
+ configuration
+
+
+
+ io.airlift
+ log
+
+
+
+ io.airlift
+ log-manager
+ runtime
+
+
+
+ com.google.guava
+ guava
+
+
+
+ com.google.inject
+ guice
+
+
+
+ javax.inject
+ javax.inject
+
+
+
+ javax.validation
+ validation-api
+
+
+
+ io.airlift
+ units
+
+
+
+
+ io.prestosql
+ presto-spi
+ provided
+
+
+
+ io.airlift
+ slice
+ provided
+
+
+
+ com.fasterxml.jackson.core
+ jackson-annotations
+ provided
+
+
+
+ org.openjdk.jol
+ jol-core
+ provided
+
+
+
+
+ org.testng
+ testng
+ test
+
+
+
+ io.prestosql
+ presto-main
+ test
+
+
+
+ io.prestosql
+ presto-testing
+ test
+
+
+
+ io.prestosql.tpch
+ tpch
+ test
+
+
+
+ io.prestosql
+ presto-tpch
+ test
+
+
+
+ org.testcontainers
+ testcontainers
+ test
+
+
+
+ org.testcontainers
+ oracle-xe
+ test
+
+
+
+ io.airlift
+ testing
+ test
+
+
+
+ org.assertj
+ assertj-core
+ test
+
+
+
diff --git a/presto-oracle/src/main/java/io/prestosql/plugin/oracle/OracleClient.java b/presto-oracle/src/main/java/io/prestosql/plugin/oracle/OracleClient.java
new file mode 100644
index 000000000000..eeef6b3b555a
--- /dev/null
+++ b/presto-oracle/src/main/java/io/prestosql/plugin/oracle/OracleClient.java
@@ -0,0 +1,233 @@
+/*
+ * 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.prestosql.plugin.oracle;
+
+import io.prestosql.plugin.jdbc.BaseJdbcClient;
+import io.prestosql.plugin.jdbc.BaseJdbcConfig;
+import io.prestosql.plugin.jdbc.ColumnMapping;
+import io.prestosql.plugin.jdbc.ConnectionFactory;
+import io.prestosql.plugin.jdbc.JdbcIdentity;
+import io.prestosql.plugin.jdbc.JdbcTypeHandle;
+import io.prestosql.plugin.jdbc.WriteMapping;
+import io.prestosql.spi.PrestoException;
+import io.prestosql.spi.connector.ConnectorSession;
+import io.prestosql.spi.connector.SchemaTableName;
+import io.prestosql.spi.type.BigintType;
+import io.prestosql.spi.type.BooleanType;
+import io.prestosql.spi.type.Decimals;
+import io.prestosql.spi.type.IntegerType;
+import io.prestosql.spi.type.SmallintType;
+import io.prestosql.spi.type.TimestampType;
+import io.prestosql.spi.type.TimestampWithTimeZoneType;
+import io.prestosql.spi.type.TinyintType;
+import io.prestosql.spi.type.Type;
+import io.prestosql.spi.type.VarcharType;
+
+import javax.inject.Inject;
+
+import java.math.RoundingMode;
+import java.sql.Connection;
+import java.sql.DatabaseMetaData;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Types;
+import java.util.Optional;
+
+import static io.prestosql.plugin.jdbc.JdbcErrorCode.JDBC_ERROR;
+import static io.prestosql.plugin.jdbc.StandardColumnMappings.bigintColumnMapping;
+import static io.prestosql.plugin.jdbc.StandardColumnMappings.bigintWriteFunction;
+import static io.prestosql.plugin.jdbc.StandardColumnMappings.booleanWriteFunction;
+import static io.prestosql.plugin.jdbc.StandardColumnMappings.decimalColumnMapping;
+import static io.prestosql.plugin.jdbc.StandardColumnMappings.doubleColumnMapping;
+import static io.prestosql.plugin.jdbc.StandardColumnMappings.integerWriteFunction;
+import static io.prestosql.plugin.jdbc.StandardColumnMappings.realColumnMapping;
+import static io.prestosql.plugin.jdbc.StandardColumnMappings.smallintColumnMapping;
+import static io.prestosql.plugin.jdbc.StandardColumnMappings.smallintWriteFunction;
+import static io.prestosql.plugin.jdbc.StandardColumnMappings.timestampWriteFunction;
+import static io.prestosql.plugin.jdbc.StandardColumnMappings.tinyintWriteFunction;
+import static io.prestosql.plugin.jdbc.StandardColumnMappings.varcharColumnMapping;
+import static io.prestosql.plugin.jdbc.StandardColumnMappings.varcharWriteFunction;
+import static io.prestosql.spi.StandardErrorCode.NOT_SUPPORTED;
+import static io.prestosql.spi.type.DecimalType.createDecimalType;
+import static io.prestosql.spi.type.VarcharType.createUnboundedVarcharType;
+import static io.prestosql.spi.type.VarcharType.createVarcharType;
+import static io.prestosql.spi.type.Varchars.isVarcharType;
+import static java.lang.String.format;
+import static java.util.Locale.ENGLISH;
+import static java.util.Objects.requireNonNull;
+
+public class OracleClient
+ extends BaseJdbcClient
+{
+ private final boolean synonymsEnabled;
+ private final int fetchSize = 1000;
+ private final int varcharMaxSize;
+ private final int timestampDefaultPrecision;
+ private final int numberDefaultScale;
+ private final RoundingMode numberRoundingMode;
+
+ @Inject
+ public OracleClient(
+ BaseJdbcConfig config,
+ OracleConfig oracleConfig,
+ ConnectionFactory connectionFactory)
+ {
+ super(config, "\"", connectionFactory);
+
+ requireNonNull(oracleConfig, "oracle config is null");
+ this.synonymsEnabled = oracleConfig.isSynonymsEnabled();
+ this.varcharMaxSize = oracleConfig.getVarcharMaxSize();
+ this.timestampDefaultPrecision = oracleConfig.getTimestampDefaultPrecision();
+ this.numberDefaultScale = oracleConfig.getNumberDefaultScale();
+ this.numberRoundingMode = oracleConfig.getNumberRoundingMode();
+ }
+
+ private String[] getTableTypes()
+ {
+ if (synonymsEnabled) {
+ return new String[] {"TABLE", "VIEW", "SYNONYM"};
+ }
+ return new String[] {"TABLE", "VIEW"};
+ }
+
+ @Override
+ protected ResultSet getTables(Connection connection, Optional schemaName, Optional tableName)
+ throws SQLException
+ {
+ DatabaseMetaData metadata = connection.getMetaData();
+ String escape = metadata.getSearchStringEscape();
+ return metadata.getTables(
+ connection.getCatalog(),
+ escapeNamePattern(schemaName, escape).orElse(null),
+ escapeNamePattern(tableName, escape).orElse(null),
+ getTableTypes());
+ }
+
+ @Override
+ public PreparedStatement getPreparedStatement(Connection connection, String sql)
+ throws SQLException
+ {
+ PreparedStatement statement = connection.prepareStatement(sql);
+ statement.setFetchSize(fetchSize);
+ return statement;
+ }
+
+ @Override
+ protected String generateTemporaryTableName()
+ {
+ return "presto_tmp_" + System.nanoTime();
+ }
+
+ @Override
+ protected void renameTable(JdbcIdentity identity, String catalogName, String schemaName, String tableName, SchemaTableName newTable)
+ {
+ if (!schemaName.equalsIgnoreCase(newTable.getSchemaName())) {
+ throw new PrestoException(NOT_SUPPORTED, "Table rename across schemas is not supported in Oracle");
+ }
+
+ String newTableName = newTable.getTableName().toUpperCase(ENGLISH);
+ String sql = format(
+ "ALTER TABLE %s RENAME TO %s",
+ quoted(catalogName, schemaName, tableName),
+ quoted(newTableName));
+
+ try (Connection connection = connectionFactory.openConnection(identity)) {
+ execute(connection, sql);
+ }
+ catch (SQLException e) {
+ throw new PrestoException(JDBC_ERROR, e);
+ }
+ }
+
+ @Override
+ public void createSchema(JdbcIdentity identity, String schemaName)
+ {
+ // ORA-02420: missing schema authorization clause
+ throw new PrestoException(NOT_SUPPORTED, "This connector does not support creating schemas");
+ }
+
+ @Override
+ public Optional toPrestoType(ConnectorSession session, Connection connection, JdbcTypeHandle typeHandle)
+ {
+ int columnSize = typeHandle.getColumnSize();
+
+ switch (typeHandle.getJdbcType()) {
+ case Types.CLOB:
+ return Optional.of(varcharColumnMapping(createUnboundedVarcharType()));
+ case Types.SMALLINT:
+ return Optional.of(smallintColumnMapping());
+ case Types.FLOAT:
+ if (columnSize == 63) {
+ return Optional.of(realColumnMapping());
+ }
+ return Optional.of(doubleColumnMapping());
+ case Types.NUMERIC:
+ int precision = columnSize == 0 ? Decimals.MAX_PRECISION : columnSize;
+ int scale = typeHandle.getDecimalDigits();
+
+ if (scale == 0) {
+ return Optional.of(bigintColumnMapping());
+ }
+ if (scale < 0 || scale > precision) {
+ return Optional.of(decimalColumnMapping(createDecimalType(precision, numberDefaultScale), numberRoundingMode));
+ }
+
+ return Optional.of(decimalColumnMapping(createDecimalType(precision, scale), numberRoundingMode));
+ case Types.LONGVARCHAR:
+ if (columnSize > VarcharType.MAX_LENGTH || columnSize == 0) {
+ return Optional.of(varcharColumnMapping(createUnboundedVarcharType()));
+ }
+ return Optional.of(varcharColumnMapping(createVarcharType(columnSize)));
+ case Types.VARCHAR:
+ return Optional.of(varcharColumnMapping(createVarcharType(columnSize)));
+ }
+ return super.toPrestoType(session, connection, typeHandle);
+ }
+
+ @Override
+ public WriteMapping toWriteMapping(ConnectorSession session, Type type)
+ {
+ if (type instanceof BooleanType) {
+ return WriteMapping.booleanMapping("number(1,0)", booleanWriteFunction());
+ }
+ if (type instanceof TinyintType) {
+ return WriteMapping.longMapping("number(3,0)", tinyintWriteFunction());
+ }
+ if (type instanceof SmallintType) {
+ return WriteMapping.longMapping("number(5,0)", smallintWriteFunction());
+ }
+ if (type instanceof IntegerType) {
+ return WriteMapping.longMapping("number(10,0)", integerWriteFunction());
+ }
+ if (type instanceof BigintType) {
+ return WriteMapping.longMapping("number(19,0)", bigintWriteFunction());
+ }
+ if (type instanceof TimestampType) {
+ return WriteMapping.longMapping(format("timestamp(%s)", timestampDefaultPrecision), timestampWriteFunction(session));
+ }
+ if (type instanceof TimestampWithTimeZoneType) {
+ return WriteMapping.longMapping(format("timestamp(%s) with time zone", timestampDefaultPrecision), timestampWriteFunction(session));
+ }
+ if (isVarcharType(type)) {
+ if (((VarcharType) type).isUnbounded()) {
+ return super.toWriteMapping(session, createVarcharType(varcharMaxSize));
+ }
+ if (((VarcharType) type).getBoundedLength() > varcharMaxSize) {
+ return WriteMapping.sliceMapping("clob", varcharWriteFunction());
+ }
+ }
+ return super.toWriteMapping(session, type);
+ }
+}
diff --git a/presto-oracle/src/main/java/io/prestosql/plugin/oracle/OracleClientModule.java b/presto-oracle/src/main/java/io/prestosql/plugin/oracle/OracleClientModule.java
new file mode 100644
index 000000000000..0d9e6fcd90aa
--- /dev/null
+++ b/presto-oracle/src/main/java/io/prestosql/plugin/oracle/OracleClientModule.java
@@ -0,0 +1,60 @@
+/*
+ * 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.prestosql.plugin.oracle;
+
+import com.google.inject.Binder;
+import com.google.inject.Module;
+import com.google.inject.Provides;
+import com.google.inject.Scopes;
+import com.google.inject.Singleton;
+import io.prestosql.plugin.jdbc.BaseJdbcConfig;
+import io.prestosql.plugin.jdbc.ConnectionFactory;
+import io.prestosql.plugin.jdbc.DriverConnectionFactory;
+import io.prestosql.plugin.jdbc.ForBaseJdbc;
+import io.prestosql.plugin.jdbc.JdbcClient;
+import io.prestosql.plugin.jdbc.credential.CredentialProvider;
+import oracle.jdbc.OracleConnection;
+import oracle.jdbc.OracleDriver;
+
+import java.sql.SQLException;
+import java.util.Properties;
+
+import static io.airlift.configuration.ConfigBinder.configBinder;
+
+public class OracleClientModule
+ implements Module
+{
+ @Override
+ public void configure(Binder binder)
+ {
+ binder.bind(JdbcClient.class).annotatedWith(ForBaseJdbc.class).to(OracleClient.class).in(Scopes.SINGLETON);
+ configBinder(binder).bindConfig(OracleConfig.class);
+ }
+
+ @Provides
+ @Singleton
+ @ForBaseJdbc
+ public static ConnectionFactory connectionFactory(BaseJdbcConfig config, CredentialProvider credentialProvider, OracleConfig oracleConfig)
+ throws SQLException
+ {
+ Properties connectionProperties = new Properties();
+ connectionProperties.setProperty(OracleConnection.CONNECTION_PROPERTY_INCLUDE_SYNONYMS, String.valueOf(oracleConfig.isSynonymsEnabled()));
+
+ return new DriverConnectionFactory(
+ new OracleDriver(),
+ config.getConnectionUrl(),
+ connectionProperties,
+ credentialProvider);
+ }
+}
diff --git a/presto-oracle/src/main/java/io/prestosql/plugin/oracle/OracleConfig.java b/presto-oracle/src/main/java/io/prestosql/plugin/oracle/OracleConfig.java
new file mode 100644
index 000000000000..ca1e97c03333
--- /dev/null
+++ b/presto-oracle/src/main/java/io/prestosql/plugin/oracle/OracleConfig.java
@@ -0,0 +1,98 @@
+/*
+ * 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.prestosql.plugin.oracle;
+
+import io.airlift.configuration.Config;
+
+import javax.validation.constraints.Max;
+import javax.validation.constraints.Min;
+import javax.validation.constraints.NotNull;
+
+import java.math.RoundingMode;
+
+public class OracleConfig
+{
+ private boolean synonymsEnabled;
+ private int varcharMaxSize = 4000;
+ private int timestampDefaultPrecision = 6;
+ private int numberDefaultScale = 10;
+ private RoundingMode numberRoundingMode = RoundingMode.HALF_UP;
+
+ @NotNull
+ public boolean isSynonymsEnabled()
+ {
+ return synonymsEnabled;
+ }
+
+ @Config("oracle.synonyms.enabled")
+ public OracleConfig setSynonymsEnabled(boolean enabled)
+ {
+ this.synonymsEnabled = enabled;
+ return this;
+ }
+
+ @Min(0)
+ @Max(38)
+ public int getNumberDefaultScale()
+ {
+ return numberDefaultScale;
+ }
+
+ @Config("oracle.number.default-scale")
+ public OracleConfig setNumberDefaultScale(Integer numberDefaultScale)
+ {
+ this.numberDefaultScale = numberDefaultScale;
+ return this;
+ }
+
+ @NotNull
+ public RoundingMode getNumberRoundingMode()
+ {
+ return numberRoundingMode;
+ }
+
+ @Config("oracle.number.rounding-mode")
+ public OracleConfig setNumberRoundingMode(RoundingMode numberRoundingMode)
+ {
+ this.numberRoundingMode = numberRoundingMode;
+ return this;
+ }
+
+ @Min(4000)
+ public int getVarcharMaxSize()
+ {
+ return varcharMaxSize;
+ }
+
+ @Config("oracle.varchar.max-size")
+ public OracleConfig setVarcharMaxSize(int varcharMaxSize)
+ {
+ this.varcharMaxSize = varcharMaxSize;
+ return this;
+ }
+
+ @Min(0)
+ @Max(9)
+ public int getTimestampDefaultPrecision()
+ {
+ return timestampDefaultPrecision;
+ }
+
+ @Config("oracle.timestamp.precision")
+ public OracleConfig setTimestampDefaultPrecision(int timestampDefaultPrecision)
+ {
+ this.timestampDefaultPrecision = timestampDefaultPrecision;
+ return this;
+ }
+}
diff --git a/presto-oracle/src/main/java/io/prestosql/plugin/oracle/OraclePlugin.java b/presto-oracle/src/main/java/io/prestosql/plugin/oracle/OraclePlugin.java
new file mode 100644
index 000000000000..ef0cb4d843c3
--- /dev/null
+++ b/presto-oracle/src/main/java/io/prestosql/plugin/oracle/OraclePlugin.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.prestosql.plugin.oracle;
+
+import io.prestosql.plugin.jdbc.JdbcPlugin;
+
+public class OraclePlugin
+ extends JdbcPlugin
+{
+ public OraclePlugin()
+ {
+ super("oracle", new OracleClientModule());
+ }
+}
diff --git a/presto-oracle/src/test/java/io/prestosql/plugin/oracle/OracleQueryRunner.java b/presto-oracle/src/test/java/io/prestosql/plugin/oracle/OracleQueryRunner.java
new file mode 100644
index 000000000000..40eb6f8dbacb
--- /dev/null
+++ b/presto-oracle/src/test/java/io/prestosql/plugin/oracle/OracleQueryRunner.java
@@ -0,0 +1,101 @@
+/*
+ * 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.prestosql.plugin.oracle;
+
+import com.google.common.collect.ImmutableList;
+import io.airlift.log.Logger;
+import io.airlift.log.Logging;
+import io.prestosql.Session;
+import io.prestosql.plugin.tpch.TpchPlugin;
+import io.prestosql.testing.DistributedQueryRunner;
+import io.prestosql.tpch.TpchTable;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import static io.airlift.testing.Closeables.closeAllSuppress;
+import static io.prestosql.plugin.oracle.TestingOracleServer.TEST_PASS;
+import static io.prestosql.plugin.oracle.TestingOracleServer.TEST_SCHEMA;
+import static io.prestosql.plugin.oracle.TestingOracleServer.TEST_USER;
+import static io.prestosql.plugin.tpch.TpchMetadata.TINY_SCHEMA_NAME;
+import static io.prestosql.testing.QueryAssertions.copyTpchTables;
+import static io.prestosql.testing.TestingSession.testSessionBuilder;
+
+public final class OracleQueryRunner
+{
+ private OracleQueryRunner() {}
+
+ public static DistributedQueryRunner createOracleQueryRunner(TestingOracleServer server)
+ throws Exception
+ {
+ return createOracleQueryRunner(server, ImmutableList.of());
+ }
+
+ public static DistributedQueryRunner createOracleQueryRunner(TestingOracleServer server, TpchTable>... tables)
+ throws Exception
+ {
+ return createOracleQueryRunner(server, ImmutableList.copyOf(tables));
+ }
+
+ public static DistributedQueryRunner createOracleQueryRunner(TestingOracleServer server, Iterable> tables)
+ throws Exception
+ {
+ DistributedQueryRunner queryRunner = null;
+ try {
+ queryRunner = DistributedQueryRunner.builder(createSession()).build();
+
+ queryRunner.installPlugin(new TpchPlugin());
+ queryRunner.createCatalog("tpch", "tpch");
+
+ Map connectorProperties = new HashMap<>();
+ connectorProperties.putIfAbsent("connection-url", server.getJdbcUrl());
+ connectorProperties.putIfAbsent("connection-user", TEST_USER);
+ connectorProperties.putIfAbsent("connection-password", TEST_PASS);
+ connectorProperties.putIfAbsent("allow-drop-table", "true");
+
+ queryRunner.installPlugin(new OraclePlugin());
+ queryRunner.createCatalog("oracle", "oracle", connectorProperties);
+
+ copyTpchTables(queryRunner, "tpch", TINY_SCHEMA_NAME, createSession(), tables);
+
+ return queryRunner;
+ }
+ catch (Throwable e) {
+ closeAllSuppress(e, queryRunner, server);
+ throw e;
+ }
+ }
+
+ public static Session createSession()
+ {
+ return testSessionBuilder()
+ .setCatalog("oracle")
+ .setSchema(TEST_SCHEMA)
+ .build();
+ }
+
+ public static void main(String[] args)
+ throws Exception
+ {
+ Logging.initialize();
+
+ DistributedQueryRunner queryRunner = createOracleQueryRunner(
+ new TestingOracleServer(),
+ TpchTable.getTables());
+
+ Logger log = Logger.get(OracleQueryRunner.class);
+ log.info("======== SERVER STARTED ========");
+ log.info("\n====\n%s\n====", queryRunner.getCoordinator().getBaseUrl());
+ }
+}
diff --git a/presto-oracle/src/test/java/io/prestosql/plugin/oracle/TestOracleConfig.java b/presto-oracle/src/test/java/io/prestosql/plugin/oracle/TestOracleConfig.java
new file mode 100644
index 000000000000..ace39f09109e
--- /dev/null
+++ b/presto-oracle/src/test/java/io/prestosql/plugin/oracle/TestOracleConfig.java
@@ -0,0 +1,59 @@
+/*
+ * 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.prestosql.plugin.oracle;
+
+import com.google.common.collect.ImmutableMap;
+import org.testng.annotations.Test;
+
+import java.math.RoundingMode;
+import java.util.Map;
+
+import static io.airlift.configuration.testing.ConfigAssertions.assertFullMapping;
+import static io.airlift.configuration.testing.ConfigAssertions.assertRecordedDefaults;
+import static io.airlift.configuration.testing.ConfigAssertions.recordDefaults;
+
+public class TestOracleConfig
+{
+ @Test
+ public void testDefaults()
+ {
+ assertRecordedDefaults(recordDefaults(OracleConfig.class)
+ .setSynonymsEnabled(false)
+ .setVarcharMaxSize(4000)
+ .setTimestampDefaultPrecision(6)
+ .setNumberDefaultScale(10)
+ .setNumberRoundingMode(RoundingMode.HALF_UP));
+ }
+
+ @Test
+ public void testExplicitPropertyMappings()
+ {
+ Map properties = new ImmutableMap.Builder()
+ .put("oracle.synonyms.enabled", "true")
+ .put("oracle.varchar.max-size", "10000")
+ .put("oracle.timestamp.precision", "3")
+ .put("oracle.number.default-scale", "2")
+ .put("oracle.number.rounding-mode", "CEILING")
+ .build();
+
+ OracleConfig expected = new OracleConfig()
+ .setSynonymsEnabled(true)
+ .setVarcharMaxSize(10000)
+ .setTimestampDefaultPrecision(3)
+ .setNumberDefaultScale(2)
+ .setNumberRoundingMode(RoundingMode.CEILING);
+
+ assertFullMapping(properties, expected);
+ }
+}
diff --git a/presto-oracle/src/test/java/io/prestosql/plugin/oracle/TestOracleDistributedQueries.java b/presto-oracle/src/test/java/io/prestosql/plugin/oracle/TestOracleDistributedQueries.java
new file mode 100644
index 000000000000..f0aa6130e683
--- /dev/null
+++ b/presto-oracle/src/test/java/io/prestosql/plugin/oracle/TestOracleDistributedQueries.java
@@ -0,0 +1,404 @@
+/*
+ * 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.prestosql.plugin.oracle;
+
+import io.prestosql.Session;
+import io.prestosql.execution.QueryInfo;
+import io.prestosql.testing.AbstractTestDistributedQueries;
+import io.prestosql.testing.DistributedQueryRunner;
+import io.prestosql.testing.MaterializedResult;
+import io.prestosql.testing.QueryRunner;
+import io.prestosql.testing.ResultWithQueryId;
+import io.prestosql.testing.sql.TestTable;
+import io.prestosql.tpch.TpchTable;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.Test;
+
+import java.util.Optional;
+
+import static io.prestosql.spi.type.VarcharType.VARCHAR;
+import static io.prestosql.testing.MaterializedResult.resultBuilder;
+import static io.prestosql.testing.sql.TestTable.randomTableSuffix;
+import static java.lang.String.format;
+import static java.util.stream.Collectors.joining;
+import static java.util.stream.IntStream.range;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertTrue;
+
+public class TestOracleDistributedQueries
+ extends AbstractTestDistributedQueries
+{
+ private TestingOracleServer oracleServer;
+
+ @Override
+ protected QueryRunner createQueryRunner()
+ throws Exception
+ {
+ this.oracleServer = new TestingOracleServer();
+ return OracleQueryRunner.createOracleQueryRunner(
+ oracleServer,
+ TpchTable.getTables());
+ }
+
+ @AfterClass(alwaysRun = true)
+ public final void destroy()
+ {
+ if (oracleServer != null) {
+ oracleServer.close();
+ }
+ }
+
+ @Override
+ protected boolean supportsViews()
+ {
+ return false;
+ }
+
+ @Override
+ protected boolean supportsArrays()
+ {
+ return false;
+ }
+
+ @Override
+ public void testCommentTable()
+ {
+ // table comment not supported
+ }
+
+ @Override
+ public void testDelete()
+ {
+ // delete is not supported
+ }
+
+ @Override
+ public void testCreateSchema()
+ {
+ // schema creation is not supported
+ }
+
+ @Override
+ protected String dataMappingTableName(String prestoTypeName)
+ {
+ return "presto_tmp_" + System.nanoTime();
+ }
+
+ @Override
+ protected Optional filterDataMappingSmokeTestData(DataMappingTestSetup dataMappingTestSetup)
+ {
+ String typeName = dataMappingTestSetup.getPrestoTypeName();
+ if (typeName.equals("timestamp with time zone")
+ || typeName.equals("time")
+ || typeName.equals("varbinary")
+ || typeName.equals("char(3)")) {
+ return Optional.empty();
+ }
+
+ return Optional.of(dataMappingTestSetup);
+ }
+
+ @Override
+ protected TestTable createTableWithDefaultColumns()
+ {
+ return new TestTable(
+ oracleServer::execute,
+ "tpch.table",
+ "(col_required decimal(20,0) NOT NULL," +
+ "col_nullable decimal(20,0)," +
+ "col_default decimal(20,0) DEFAULT 43," +
+ "col_nonnull_default decimal(20,0) DEFAULT 42 NOT NULL ," +
+ "col_required2 decimal(20,0) NOT NULL)");
+ }
+
+ @Test
+ @Override
+ public void testLargeIn()
+ {
+ int numberOfElements = 1000;
+ String longValues = range(0, numberOfElements)
+ .mapToObj(Integer::toString)
+ .collect(joining(", "));
+ assertQuery("SELECT orderkey FROM orders WHERE orderkey IN (" + longValues + ")");
+ assertQuery("SELECT orderkey FROM orders WHERE orderkey NOT IN (" + longValues + ")");
+
+ assertQuery("SELECT orderkey FROM orders WHERE orderkey IN (mod(1000, orderkey), " + longValues + ")");
+ assertQuery("SELECT orderkey FROM orders WHERE orderkey NOT IN (mod(1000, orderkey), " + longValues + ")");
+
+ String arrayValues = range(0, numberOfElements)
+ .mapToObj(i -> format("ARRAY[%s, %s, %s]", i, i + 1, i + 2))
+ .collect(joining(", "));
+ assertQuery("SELECT ARRAY[0, 0, 0] in (ARRAY[0, 0, 0], " + arrayValues + ")", "values true");
+ assertQuery("SELECT ARRAY[0, 0, 0] in (" + arrayValues + ")", "values false");
+ }
+
+ @Test
+ @Override
+ public void testCreateTableAsSelect()
+ {
+ String tableName = "test_ctas" + randomTableSuffix();
+ assertUpdate("CREATE TABLE IF NOT EXISTS " + tableName + " AS SELECT name, regionkey FROM nation", "SELECT count(*) FROM nation");
+ assertTableColumnNames(tableName, "name", "regionkey");
+ assertUpdate("DROP TABLE " + tableName);
+
+ // Some connectors support CREATE TABLE AS but not the ordinary CREATE TABLE. Let's test CTAS IF NOT EXISTS with a table that is guaranteed to exist.
+ assertUpdate("CREATE TABLE IF NOT EXISTS nation AS SELECT orderkey, discount FROM lineitem", 0);
+ assertTableColumnNames("nation", "nationkey", "name", "regionkey", "comment");
+
+ assertCreateTableAsSelect(
+ "SELECT orderdate, orderkey, totalprice FROM orders",
+ "SELECT count(*) FROM orders");
+
+ assertCreateTableAsSelect(
+ "SELECT orderstatus, sum(totalprice) x FROM orders GROUP BY orderstatus",
+ "SELECT count(DISTINCT orderstatus) FROM orders");
+
+ assertCreateTableAsSelect(
+ "SELECT count(*) x FROM lineitem JOIN orders ON lineitem.orderkey = orders.orderkey",
+ "SELECT 1");
+
+ assertCreateTableAsSelect(
+ "SELECT orderkey FROM orders ORDER BY orderkey LIMIT 10",
+ "SELECT 10");
+
+ // this is comment because presto creates a table of varchar(1) and in oracle this unicode occupy 3 char
+ // we should try to get bytes instead of size ??
+ /*
+ assertCreateTableAsSelect(
+ "SELECT '\u2603' unicode",
+ "SELECT 1");
+ */
+ assertCreateTableAsSelect(
+ "SELECT * FROM orders WITH DATA",
+ "SELECT * FROM orders",
+ "SELECT count(*) FROM orders");
+
+ assertCreateTableAsSelect(
+ "SELECT * FROM orders WITH NO DATA",
+ "SELECT * FROM orders LIMIT 0",
+ "SELECT 0");
+
+ // Tests for CREATE TABLE with UNION ALL: exercises PushTableWriteThroughUnion optimizer
+
+ assertCreateTableAsSelect(
+ "SELECT orderdate, orderkey, totalprice FROM orders WHERE orderkey % 2 = 0 UNION ALL " +
+ "SELECT orderdate, orderkey, totalprice FROM orders WHERE orderkey % 2 = 1",
+ "SELECT orderdate, orderkey, totalprice FROM orders",
+ "SELECT count(*) FROM orders");
+
+ assertCreateTableAsSelect(
+ Session.builder(getSession()).setSystemProperty("redistribute_writes", "true").build(),
+ "SELECT CAST(orderdate AS DATE) orderdate, orderkey, totalprice FROM orders UNION ALL " +
+ "SELECT DATE '2000-01-01', 1234567890, 1.23",
+ "SELECT orderdate, orderkey, totalprice FROM orders UNION ALL " +
+ "SELECT DATE '2000-01-01', 1234567890, 1.23",
+ "SELECT count(*) + 1 FROM orders");
+
+ assertCreateTableAsSelect(
+ Session.builder(getSession()).setSystemProperty("redistribute_writes", "false").build(),
+ "SELECT CAST(orderdate AS DATE) orderdate, orderkey, totalprice FROM orders UNION ALL " +
+ "SELECT DATE '2000-01-01', 1234567890, 1.23",
+ "SELECT orderdate, orderkey, totalprice FROM orders UNION ALL " +
+ "SELECT DATE '2000-01-01', 1234567890, 1.23",
+ "SELECT count(*) + 1 FROM orders");
+
+ assertExplainAnalyze("EXPLAIN ANALYZE CREATE TABLE " + tableName + " AS SELECT orderstatus FROM orders");
+ assertQuery("SELECT * from " + tableName, "SELECT orderstatus FROM orders");
+ assertUpdate("DROP TABLE " + tableName);
+ }
+
+ @Test
+ @Override
+ public void testCreateTable()
+ {
+ assertUpdate("CREATE TABLE test_create (a bigint, b double, c varchar)");
+ assertTrue(getQueryRunner().tableExists(getSession(), "test_create"));
+ assertTableColumnNames("test_create", "a", "b", "c");
+
+ assertUpdate("DROP TABLE test_create");
+ assertFalse(getQueryRunner().tableExists(getSession(), "test_create"));
+
+ assertQueryFails("CREATE TABLE test_create (a bad_type)", ".* Unknown type 'bad_type' for column 'a'");
+ assertFalse(getQueryRunner().tableExists(getSession(), "test_create"));
+
+ // Replace test_create_table_if_not_exists with test_create_table_if_not_exist to fetch max size naming on oracle
+ assertUpdate("CREATE TABLE test_create_table_if_not_exist (a bigint, b varchar, c double)");
+ assertTrue(getQueryRunner().tableExists(getSession(), "test_create_table_if_not_exist"));
+ assertTableColumnNames("test_create_table_if_not_exist", "a", "b", "c");
+
+ assertUpdate("CREATE TABLE IF NOT EXISTS test_create_table_if_not_exist (d bigint, e varchar)");
+ assertTrue(getQueryRunner().tableExists(getSession(), "test_create_table_if_not_exist"));
+ assertTableColumnNames("test_create_table_if_not_exist", "a", "b", "c");
+
+ assertUpdate("DROP TABLE test_create_table_if_not_exist");
+ assertFalse(getQueryRunner().tableExists(getSession(), "test_create_table_if_not_exist"));
+
+ // Test CREATE TABLE LIKE
+ assertUpdate("CREATE TABLE test_create_original (a bigint, b double, c varchar)");
+ assertTrue(getQueryRunner().tableExists(getSession(), "test_create_original"));
+ assertTableColumnNames("test_create_original", "a", "b", "c");
+
+ assertUpdate("CREATE TABLE test_create_like (LIKE test_create_original, d boolean, e varchar)");
+ assertTrue(getQueryRunner().tableExists(getSession(), "test_create_like"));
+ assertTableColumnNames("test_create_like", "a", "b", "c", "d", "e");
+
+ assertUpdate("DROP TABLE test_create_original");
+ assertFalse(getQueryRunner().tableExists(getSession(), "test_create_original"));
+
+ assertUpdate("DROP TABLE test_create_like");
+ assertFalse(getQueryRunner().tableExists(getSession(), "test_create_like"));
+ }
+
+ @Test
+ @Override
+ public void testSymbolAliasing()
+ {
+ // Replace tablename to less than 30chars, max size naming on oracle
+ String tableName = "symbol_aliasing" + System.currentTimeMillis();
+ assertUpdate("CREATE TABLE " + tableName + " AS SELECT 1 foo_1, 2 foo_2_4", 1);
+ assertQuery("SELECT foo_1, foo_2_4 FROM " + tableName, "SELECT 1, 2");
+ assertUpdate("DROP TABLE " + tableName);
+ }
+
+ @Test
+ @Override
+ public void testRenameColumn()
+ {
+ // Replace tablename to less than 30chars, max size naming on oracle
+ String tableName = "test_renamecol_" + System.currentTimeMillis();
+ assertUpdate("CREATE TABLE " + tableName + " AS SELECT 'some value' x", 1);
+
+ assertUpdate("ALTER TABLE " + tableName + " RENAME COLUMN x TO y");
+ assertQuery("SELECT y FROM " + tableName, "VALUES 'some value'");
+
+ assertUpdate("ALTER TABLE " + tableName + " RENAME COLUMN y TO Z"); // 'Z' is upper-case, not delimited
+ assertQuery(
+ "SELECT z FROM " + tableName, // 'z' is lower-case, not delimited
+ "VALUES 'some value'");
+
+ // There should be exactly one column
+ assertQuery("SELECT * FROM " + tableName, "VALUES 'some value'");
+
+ assertUpdate("DROP TABLE " + tableName);
+ }
+
+ @Test
+ @Override
+ public void testQueryLoggingCount()
+ {
+ // table name has more than 30chars,max size naming on oracle.
+ // but as this methods call on private methods we disable it
+ }
+
+ @Test
+ @Override
+ public void testWrittenStats()
+ {
+ // Replace tablename to fetch max size naming on oracle
+ String tableName = "written_stats_" + System.currentTimeMillis();
+ String sql = "CREATE TABLE " + tableName + " AS SELECT * FROM nation";
+ DistributedQueryRunner distributedQueryRunner = (DistributedQueryRunner) getQueryRunner();
+ ResultWithQueryId resultResultWithQueryId = distributedQueryRunner.executeWithQueryId(getSession(), sql);
+ QueryInfo queryInfo = distributedQueryRunner.getCoordinator().getQueryManager().getFullQueryInfo(resultResultWithQueryId.getQueryId());
+
+ assertEquals(queryInfo.getQueryStats().getOutputPositions(), 1L);
+ assertEquals(queryInfo.getQueryStats().getWrittenPositions(), 25L);
+ assertTrue(queryInfo.getQueryStats().getLogicalWrittenDataSize().toBytes() > 0L);
+
+ sql = "INSERT INTO " + tableName + " SELECT * FROM nation LIMIT 10";
+ resultResultWithQueryId = distributedQueryRunner.executeWithQueryId(getSession(), sql);
+ queryInfo = distributedQueryRunner.getCoordinator().getQueryManager().getFullQueryInfo(resultResultWithQueryId.getQueryId());
+
+ assertEquals(queryInfo.getQueryStats().getOutputPositions(), 1L);
+ assertEquals(queryInfo.getQueryStats().getWrittenPositions(), 10L);
+ assertTrue(queryInfo.getQueryStats().getLogicalWrittenDataSize().toBytes() > 0L);
+
+ assertUpdate("DROP TABLE " + tableName);
+ }
+
+ @Test
+ @Override
+ public void testShowColumns()
+ {
+ MaterializedResult actual = computeActual("SHOW COLUMNS FROM orders");
+
+ MaterializedResult expectedParametrizedVarchar = resultBuilder(getSession(), VARCHAR, VARCHAR, VARCHAR, VARCHAR)
+ .row("orderkey", "bigint", "", "")
+ .row("custkey", "bigint", "", "")
+ .row("orderstatus", "varchar(1)", "", "")
+ .row("totalprice", "double", "", "")
+ .row("orderdate", "timestamp", "", "")
+ .row("orderpriority", "varchar(15)", "", "")
+ .row("clerk", "varchar(15)", "", "")
+ .row("shippriority", "bigint", "", "")
+ .row("comment", "varchar(79)", "", "")
+ .build();
+
+ // Until we migrate all connectors to parametrized varchar we check two options
+ assertTrue(actual.equals(expectedParametrizedVarchar),
+ format("%s does not matches %s", actual, expectedParametrizedVarchar));
+ }
+
+ @Test
+ @Override
+ public void testInsertUnicode()
+ {
+ // unicode not working correctly as one unicode char takes more than one byte
+ }
+
+ @Test
+ @Override
+ public void testInsertWithCoercion()
+ {
+ assertUpdate("CREATE TABLE test_insert_with_coercion (" +
+ "tinyint_column TINYINT, " +
+ "integer_column INTEGER, " +
+ "decimal_column DECIMAL(5, 3), " +
+ "real_column REAL, " +
+ "char_column CHAR(3), " +
+ "bounded_varchar_column VARCHAR(3), " +
+ "unbounded_varchar_column VARCHAR, " +
+ "date_column DATE)");
+
+ assertUpdate("INSERT INTO test_insert_with_coercion (tinyint_column, integer_column, decimal_column, real_column) VALUES (1e0, 2e0, 3e0, 4e0)", 1);
+ assertUpdate("INSERT INTO test_insert_with_coercion (char_column, bounded_varchar_column, unbounded_varchar_column) VALUES (CAST('aa ' AS varchar), CAST('aa ' AS varchar), CAST('aa ' AS varchar))", 1);
+ assertUpdate("INSERT INTO test_insert_with_coercion (char_column, bounded_varchar_column, unbounded_varchar_column) VALUES (NULL, NULL, NULL)", 1);
+ assertUpdate("INSERT INTO test_insert_with_coercion (char_column, bounded_varchar_column, unbounded_varchar_column) VALUES (CAST(NULL AS varchar), CAST(NULL AS varchar), CAST(NULL AS varchar))", 1);
+ assertUpdate("INSERT INTO test_insert_with_coercion (date_column) VALUES (TIMESTAMP '2019-11-18 22:13:40')", 1);
+
+ // at oracle date field has time part to
+ assertQuery(
+ "SELECT * FROM test_insert_with_coercion",
+ "VALUES " +
+ "(1, 2, 3, 4, NULL, NULL, NULL, NULL), " +
+ "(NULL, NULL, NULL, NULL, 'aa ', 'aa ', 'aa ', NULL), " +
+ "(NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), " +
+ "(NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), " +
+ "(NULL, NULL, NULL, NULL, NULL, NULL, NULL, TIMESTAMP '2019-11-18 22:13:40')");
+
+ // this wont fail
+ //assertQueryFails("INSERT INTO test_insert_with_coercion (integer_column) VALUES (3e9)", "Out of range for integer: 3.0E9");
+ assertQueryFails("INSERT INTO test_insert_with_coercion (char_column) VALUES ('abcd')", "Cannot truncate non-space characters on INSERT");
+ assertQueryFails("INSERT INTO test_insert_with_coercion (bounded_varchar_column) VALUES ('abcd')", "Cannot truncate non-space characters on INSERT");
+
+ assertUpdate("DROP TABLE test_insert_with_coercion");
+ }
+
+ @Override
+ public void testColumnName(String columnName)
+ {
+ // table names generated has more than 30chars, max size naming on oracle.
+ }
+}
diff --git a/presto-oracle/src/test/java/io/prestosql/plugin/oracle/TestOracleIntegrationSmokeTest.java b/presto-oracle/src/test/java/io/prestosql/plugin/oracle/TestOracleIntegrationSmokeTest.java
new file mode 100644
index 000000000000..e5aebffa4304
--- /dev/null
+++ b/presto-oracle/src/test/java/io/prestosql/plugin/oracle/TestOracleIntegrationSmokeTest.java
@@ -0,0 +1,83 @@
+/*
+ * 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.prestosql.plugin.oracle;
+
+import io.prestosql.testing.AbstractTestIntegrationSmokeTest;
+import io.prestosql.testing.MaterializedResult;
+import io.prestosql.testing.QueryRunner;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.Test;
+
+import static io.prestosql.spi.type.VarcharType.VARCHAR;
+import static io.prestosql.testing.assertions.Assert.assertEquals;
+import static io.prestosql.tpch.TpchTable.ORDERS;
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class TestOracleIntegrationSmokeTest
+ extends AbstractTestIntegrationSmokeTest
+{
+ private TestingOracleServer oracleServer;
+
+ @Override
+ protected QueryRunner createQueryRunner()
+ throws Exception
+ {
+ oracleServer = new TestingOracleServer();
+ return OracleQueryRunner.createOracleQueryRunner(oracleServer, ORDERS);
+ }
+
+ @AfterClass(alwaysRun = true)
+ public final void destroy()
+ {
+ oracleServer.close();
+ }
+
+ @Test
+ @Override
+ public void testDescribeTable()
+ {
+ MaterializedResult expectedColumns = MaterializedResult.resultBuilder(getQueryRunner().getDefaultSession(), VARCHAR, VARCHAR, VARCHAR, VARCHAR)
+ .row("orderkey", "bigint", "", "")
+ .row("custkey", "bigint", "", "")
+ .row("orderstatus", "varchar(1)", "", "")
+ .row("totalprice", "double", "", "")
+ .row("orderdate", "timestamp", "", "")
+ .row("orderpriority", "varchar(15)", "", "")
+ .row("clerk", "varchar(15)", "", "")
+ .row("shippriority", "bigint", "", "")
+ .row("comment", "varchar(79)", "", "")
+ .build();
+ MaterializedResult actualColumns = computeActual("DESCRIBE orders");
+ assertEquals(actualColumns, expectedColumns);
+ }
+
+ @Test
+ @Override
+ public void testShowCreateTable()
+ {
+ assertThat((String) computeActual("SHOW CREATE TABLE orders").getOnlyValue())
+ // If the connector reports additional column properties, the expected value needs to be adjusted in the test subclass
+ .matches("CREATE TABLE \\w+\\.\\w+\\.orders \\Q(\n" +
+ " orderkey bigint,\n" +
+ " custkey bigint,\n" +
+ " orderstatus varchar(1),\n" +
+ " totalprice double,\n" +
+ " orderdate timestamp,\n" +
+ " orderpriority varchar(15),\n" +
+ " clerk varchar(15),\n" +
+ " shippriority bigint,\n" +
+ " comment varchar(79)\n" +
+ ")");
+ }
+}
diff --git a/presto-oracle/src/test/java/io/prestosql/plugin/oracle/TestOraclePlugin.java b/presto-oracle/src/test/java/io/prestosql/plugin/oracle/TestOraclePlugin.java
new file mode 100644
index 000000000000..3cc47166bbf9
--- /dev/null
+++ b/presto-oracle/src/test/java/io/prestosql/plugin/oracle/TestOraclePlugin.java
@@ -0,0 +1,33 @@
+/*
+ * 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.prestosql.plugin.oracle;
+
+import com.google.common.collect.ImmutableMap;
+import io.prestosql.spi.Plugin;
+import io.prestosql.spi.connector.ConnectorFactory;
+import io.prestosql.testing.TestingConnectorContext;
+import org.testng.annotations.Test;
+
+import static com.google.common.collect.Iterables.getOnlyElement;
+
+public class TestOraclePlugin
+{
+ @Test
+ public void testCreateConnector()
+ {
+ Plugin plugin = new OraclePlugin();
+ ConnectorFactory factory = getOnlyElement(plugin.getConnectorFactories());
+ factory.create("test", ImmutableMap.of("connection-url", "jdbc:oracle:thin//test"), new TestingConnectorContext());
+ }
+}
diff --git a/presto-oracle/src/test/java/io/prestosql/plugin/oracle/TestOracleTypes.java b/presto-oracle/src/test/java/io/prestosql/plugin/oracle/TestOracleTypes.java
new file mode 100644
index 000000000000..b264e09462b2
--- /dev/null
+++ b/presto-oracle/src/test/java/io/prestosql/plugin/oracle/TestOracleTypes.java
@@ -0,0 +1,164 @@
+/*
+ * 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.prestosql.plugin.oracle;
+
+import io.prestosql.spi.type.BigintType;
+import io.prestosql.spi.type.DecimalType;
+import io.prestosql.spi.type.TimestampType;
+import io.prestosql.spi.type.Type;
+import io.prestosql.testing.AbstractTestQueryFramework;
+import io.prestosql.testing.QueryRunner;
+import io.prestosql.testing.datatype.CreateAsSelectDataSetup;
+import io.prestosql.testing.datatype.DataSetup;
+import io.prestosql.testing.datatype.DataType;
+import io.prestosql.testing.datatype.DataTypeTest;
+import io.prestosql.testing.sql.PrestoSqlExecutor;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.Test;
+
+import java.math.BigDecimal;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.function.Function;
+
+import static io.prestosql.plugin.oracle.OracleQueryRunner.createOracleQueryRunner;
+import static io.prestosql.spi.type.DecimalType.createDecimalType;
+import static io.prestosql.spi.type.VarcharType.createUnboundedVarcharType;
+import static io.prestosql.spi.type.VarcharType.createVarcharType;
+import static io.prestosql.testing.datatype.DataType.stringDataType;
+import static io.prestosql.testing.datatype.DataType.timestampDataType;
+import static io.prestosql.testing.datatype.DataType.varcharDataType;
+import static java.lang.String.format;
+import static java.math.RoundingMode.HALF_UP;
+
+public class TestOracleTypes
+ extends AbstractTestQueryFramework
+{
+ private TestingOracleServer oracleServer;
+
+ @Override
+ protected QueryRunner createQueryRunner()
+ throws Exception
+ {
+ this.oracleServer = new TestingOracleServer();
+ return createOracleQueryRunner(oracleServer);
+ }
+
+ @AfterClass(alwaysRun = true)
+ public final void destroy()
+ {
+ if (oracleServer != null) {
+ oracleServer.close();
+ }
+ }
+
+ private DataSetup prestoCreateAsSelect(String tableNamePrefix)
+ {
+ return new CreateAsSelectDataSetup(new PrestoSqlExecutor(getQueryRunner()), tableNamePrefix);
+ }
+
+ @Test
+ public void testBooleanType()
+ {
+ DataTypeTest.create()
+ .addRoundTrip(booleanOracleType(), true)
+ .addRoundTrip(booleanOracleType(), false)
+ .execute(getQueryRunner(), prestoCreateAsSelect("boolean_types"));
+ }
+
+ @Test
+ public void testSpecialNumberFormats()
+ {
+ oracleServer.execute("CREATE TABLE test (num1 number, num2 number(*,-2))");
+ oracleServer.execute("INSERT INTO test VALUES (12345678901234567890.12345678901234567890123456789012345678, 1234567890.123)");
+ assertQuery("SELECT * FROM test", "VALUES (12345678901234567890.1234567890, 1234567900.0000000000)");
+ }
+
+ @Test
+ public void testDateTimeType()
+ {
+ DataTypeTest.create()
+ .addRoundTrip(dateOracleType(), LocalDate.of(2020, 1, 1))
+ .addRoundTrip(timestampDataType(), LocalDateTime.of(2020, 1, 1, 13, 10, 1))
+ .execute(getQueryRunner(), prestoCreateAsSelect("datetime_types"));
+ }
+
+ @Test
+ public void testVarcharType()
+ {
+ DataTypeTest.create()
+ .addRoundTrip(varcharDataType(10), "test")
+ .addRoundTrip(stringDataType("varchar", createVarcharType(4000)), "test")
+ .addRoundTrip(stringDataType("varchar(5000)", createUnboundedVarcharType()), "test")
+ .addRoundTrip(varcharDataType(3), String.valueOf('\u2603'))
+ .execute(getQueryRunner(), prestoCreateAsSelect("varchar_types"));
+ }
+
+ @Test
+ public void testNumericTypes()
+ {
+ DataTypeTest.create()
+ .addRoundTrip(numberOracleType("tinyint", BigintType.BIGINT), 123L)
+ .addRoundTrip(numberOracleType("tinyint", BigintType.BIGINT), null)
+ .addRoundTrip(numberOracleType("smallint", BigintType.BIGINT), 123L)
+ .addRoundTrip(numberOracleType("integer", BigintType.BIGINT), 123L)
+ .addRoundTrip(numberOracleType("bigint", BigintType.BIGINT), 123L)
+ .addRoundTrip(numberOracleType("decimal", BigintType.BIGINT), 123L)
+ .addRoundTrip(numberOracleType("decimal(20)", BigintType.BIGINT), 123L)
+ .addRoundTrip(numberOracleType("decimal(20,0)", BigintType.BIGINT), 123L)
+ .addRoundTrip(numberOracleType(createDecimalType(5, 1)), BigDecimal.valueOf(123))
+ .addRoundTrip(numberOracleType(createDecimalType(5, 2)), BigDecimal.valueOf(123))
+ .addRoundTrip(numberOracleType(createDecimalType(5, 2)), BigDecimal.valueOf(123.046))
+ .execute(getQueryRunner(), prestoCreateAsSelect("numeric_types"));
+ }
+
+ private static DataType booleanOracleType()
+ {
+ return DataType.dataType(
+ "boolean",
+ BigintType.BIGINT,
+ val -> val ? "1" : "0",
+ val -> val ? 1L : 0L);
+ }
+
+ private static DataType numberOracleType(DecimalType type)
+ {
+ String databaseType = format("decimal(%s, %s)", type.getPrecision(), type.getScale());
+ return numberOracleType(databaseType, type);
+ }
+
+ private static DataType numberOracleType(String inputType, Type resultType)
+ {
+ Function queryResult = (Function) value ->
+ (value instanceof BigDecimal && resultType instanceof DecimalType)
+ ? ((BigDecimal) value).setScale(((DecimalType) resultType).getScale(), HALF_UP)
+ : value;
+
+ return DataType.dataType(
+ inputType,
+ resultType,
+ value -> format("CAST('%s' AS %s)", value, resultType),
+ queryResult);
+ }
+
+ public static DataType dateOracleType()
+ {
+ return DataType.dataType(
+ "date",
+ TimestampType.TIMESTAMP,
+ DateTimeFormatter.ofPattern("'DATE '''yyyy-MM-dd''")::format,
+ LocalDate::atStartOfDay);
+ }
+}
diff --git a/presto-oracle/src/test/java/io/prestosql/plugin/oracle/TestingOracleServer.java b/presto-oracle/src/test/java/io/prestosql/plugin/oracle/TestingOracleServer.java
new file mode 100644
index 000000000000..94536a3c3ca1
--- /dev/null
+++ b/presto-oracle/src/test/java/io/prestosql/plugin/oracle/TestingOracleServer.java
@@ -0,0 +1,83 @@
+/*
+ * 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.prestosql.plugin.oracle;
+
+import org.testcontainers.containers.BindMode;
+import org.testcontainers.containers.OracleContainer;
+
+import java.io.Closeable;
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.SQLException;
+import java.sql.Statement;
+
+import static java.lang.String.format;
+
+public class TestingOracleServer
+ extends OracleContainer
+ implements Closeable
+{
+ public static final String TEST_SCHEMA = "tpch";
+ public static final String TEST_USER = "tpch";
+ public static final String TEST_PASS = "oracle";
+
+ public TestingOracleServer()
+ {
+ super("wnameless/oracle-xe-11g-r2");
+
+ // this is added to allow more processes on database, otherwise the tests end up giving a ORA-12519
+ // to fix this we have to change the number of processes of SPFILE
+ // but this command needs a database restart and if we restart the docker the configuration is lost.
+ // configuration added:
+ // ALTER SYSTEM SET processes=500 SCOPE=SPFILE
+ // ALTER SYSTEM SET disk_asynch_io = FALSE SCOPE = SPFILE
+ this.addFileSystemBind("src/test/resources/spfileXE.ora",
+ "/u01/app/oracle/product/11.2.0/xe/dbs/spfileXE.ora",
+ BindMode.READ_ONLY);
+
+ start();
+ try (Connection connection = DriverManager.getConnection(getJdbcUrl(), super.getUsername(), super.getPassword());
+ Statement statement = connection.createStatement()) {
+ statement.execute(format("CREATE TABLESPACE %s DATAFILE 'test_db.dat' SIZE 100M ONLINE", TEST_SCHEMA));
+ statement.execute(format("CREATE USER %s IDENTIFIED BY %s DEFAULT TABLESPACE %s", TEST_USER, TEST_PASS, TEST_SCHEMA));
+ statement.execute(format("GRANT UNLIMITED TABLESPACE TO %s", TEST_USER));
+ statement.execute(format("GRANT ALL PRIVILEGES TO %s", TEST_USER));
+ }
+ catch (SQLException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public void execute(String sql)
+ {
+ execute(sql, TEST_USER, TEST_PASS);
+ }
+
+ public void execute(String sql, String user, String password)
+ {
+ try (Connection connection = DriverManager.getConnection(getJdbcUrl(), user, password);
+ Statement statement = connection.createStatement()) {
+ statement.execute(sql);
+ }
+ catch (SQLException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public void close()
+ {
+ stop();
+ }
+}
diff --git a/presto-oracle/src/test/resources/spfileXE.ora b/presto-oracle/src/test/resources/spfileXE.ora
new file mode 100644
index 000000000000..92a8ea486856
Binary files /dev/null and b/presto-oracle/src/test/resources/spfileXE.ora differ
diff --git a/presto-server/src/main/provisio/presto.xml b/presto-server/src/main/provisio/presto.xml
index 84f71f3c52fa..e87c168e2a6f 100644
--- a/presto-server/src/main/provisio/presto.xml
+++ b/presto-server/src/main/provisio/presto.xml
@@ -134,6 +134,12 @@
+
+
+
+
+
+
diff --git a/presto-testing/src/main/java/io/prestosql/testing/AbstractTestDistributedQueries.java b/presto-testing/src/main/java/io/prestosql/testing/AbstractTestDistributedQueries.java
index f89752934ef3..339d1e748687 100644
--- a/presto-testing/src/main/java/io/prestosql/testing/AbstractTestDistributedQueries.java
+++ b/presto-testing/src/main/java/io/prestosql/testing/AbstractTestDistributedQueries.java
@@ -1241,6 +1241,11 @@ public Object[][] testColumnNameDataProvider()
};
}
+ protected String dataMappingTableName(String prestoTypeName)
+ {
+ return "test_data_mapping_smoke_" + prestoTypeName.replaceAll("[^a-zA-Z0-9]", "_") + "_" + randomTableSuffix();
+ }
+
@Test(dataProvider = "testDataMappingSmokeTestDataProvider")
public void testDataMappingSmokeTest(DataMappingTestSetup dataMappingTestSetup)
{
@@ -1248,7 +1253,7 @@ public void testDataMappingSmokeTest(DataMappingTestSetup dataMappingTestSetup)
String sampleValueLiteral = dataMappingTestSetup.getSampleValueLiteral();
String highValueLiteral = dataMappingTestSetup.getHighValueLiteral();
- String tableName = "test_data_mapping_smoke_" + prestoTypeName.replaceAll("[^a-zA-Z0-9]", "_") + "_" + randomTableSuffix();
+ String tableName = dataMappingTableName(prestoTypeName);
Runnable setup = () -> {
// TODO test with both CTAS *and* CREATE TABLE + INSERT, since they use different connector API methods.