diff --git a/README.md b/README.md new file mode 100644 index 0000000..483eb95 --- /dev/null +++ b/README.md @@ -0,0 +1,31 @@ +# Presto OraclePlugin + +This is a plugin for Presto(ver. 0.231.1.) that allow you to use Oracle Jdbc Connection + +[![Presto-Connectors Member](https://img.shields.io/badge/presto--connectors-member-green.svg)](http://presto-connectors.ml) + +## Connection Configuration + +Create new properties file inside etc/catalog dir: + + connector.name=oracle + # connection-url must me the URL to access Oracle via JDBC. It can be different depending on your environment. + # Another example of the URL would be jdbc:oracle:thin:@//ip:port/database. For more information, please go to the JDBC driver docs + connection-url=jdbc:oracle:thin://ip:port/database + connection-user=myuser + connection-password= + +Create a dir inside plugin dir called oracle. To make it easier you could copy mysql dir to oracle and remove the mysql-connector and prestodb-mysql jars. Finally put the prestodb-oracle in plugin/oracle folder. Here is the sptes: + + cd $PRESTODB_HOME + cp -r plugin/mysql plugin/oracle + rm plugin/oracle/mysql-connector* + rm plugin/oracle/presto-mysql* + mv /home/Downloads/presto-oracle*.jar plugin/oracle + +## Building Presto Oracle JDBC Plugin + + mvn clean install + +## Building Oracle Driver +Oracle Driver is not available in common repositories, so you will need to download it from Oracle and install manually in your repository. diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..a27f8aa --- /dev/null +++ b/pom.xml @@ -0,0 +1,381 @@ + + + 4.0.0 + + 0.231.1 + + ml.prestoconnectors + presto-oracle + Presto - Oracle Connector + jar + + + + marcelopaesrech + https://github.com/marcelopaesrech + + + jmrozanec + https://github.com/marcelopaesrech + + + + + https://github.com/marcelopaesrech/presto-oracle/issues + GitHub Issues + + + + https://github.com/marcelopaesrech/presto-oracle + scm:git:git@github.com:marcelopaesrech/presto-oracle.git + scm:git:git@github.com:marcelopaesrech/presto-oracle.git + HEAD + + + + UTF-8 + UTF-8 + UTF-8 + 0.231.1 + 1.3 + 0.36 + 0.189 + + 6.10 + 2.9.10 + + + + + ossrh + Release Repository + https://oss.sonatype.org/service/local/staging/deploy/maven2/ + + + ossrh + Snapshots Repository + https://oss.sonatype.org/content/repositories/snapshots/ + + + + + + + + com.facebook.presto + presto-base-jdbc + ${dep.presto.version} + + + + io.airlift + units + ${dep.airlift.version} + + + + io.airlift + configuration + ${dep.airlift.config.version} + + + + javax.validation + validation-api + 1.1.0.Final + + + + log4j + log4j + 1.2.17 + + + + + com.oracle + ojdbc8 + 12.2.0.1.0 + + + + + com.facebook.presto + presto-spi + ${dep.presto.version} + provided + + + + io.airlift + slice + ${dep.slice.version} + provided + + + + javax.inject + javax.inject + 1 + provided + + + + + com.fasterxml.jackson.core + jackson-annotations + 2.6.0-rc2 + provided + + + + + com.fasterxml.jackson.core + jackson-databind + 2.6.0-rc2 + provided + + + + + org.junit.jupiter + junit-jupiter-api + 5.4.2 + test + + + org.junit.jupiter + junit-jupiter-engine + 5.4.2 + test + + + + org.testng + testng + ${dep.testng.version} + test + + + + com.facebook.airlift + testing + 0.187 + test + + + + com.facebook.airlift + json + 0.187 + test + + + + com.fasterxml.jackson.core + jackson-core + ${dep.jackson.version} + test + + + + com.fasterxml.jackson.core + jackson-databind + ${dep.jackson.version} + test + + + + com.facebook.presto + presto-main + ${dep.presto.version} + test + + + + com.facebook.presto + presto-main + ${dep.presto.version} + test + test-jar + + + + com.facebook.presto + presto-tests + ${dep.presto.version} + test + + + + com.facebook.presto + presto-parser + ${dep.presto.version} + test + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.1 + + 1.8 + 1.8 + + + + org.apache.maven.plugins + maven-source-plugin + 2.2.1 + + + attach-sources + + jar + + + + + + org.apache.maven.plugins + maven-clean-plugin + 2.5 + + + org.apache.maven.plugins + maven-install-plugin + 2.5.1 + + + org.apache.maven.plugins + maven-jar-plugin + 2.4 + + + org.apache.maven.plugins + maven-resources-plugin + 2.6 + + + org.apache.maven.plugins + maven-javadoc-plugin + 2.9.1 + + + attach-javadocs + + jar + + + + + + org.codehaus.mojo + sonar-maven-plugin + 1.0 + + + org.apache.maven.plugins + maven-surefire-plugin + 2.17 + + + org.apache.maven.plugins + maven-deploy-plugin + 2.8.1 + + + org.sonatype.plugins + nexus-staging-maven-plugin + 1.6.2 + true + + ossrh + https://oss.sonatype.org/ + true + + + + org.codehaus.mojo + findbugs-maven-plugin + 2.5.3 + + + org.codehaus.mojo + clirr-maven-plugin + 2.6.1 + + + org.codehaus.mojo + versions-maven-plugin + 2.1 + + + + + + + org.apache.maven.wagon + wagon-webdav + 1.0-beta-2 + + + + + + + release-sign-artifacts + + + performRelease + true + + + + + + org.apache.maven.plugins + maven-gpg-plugin + 1.5 + + + sign-artifacts + verify + + sign + + + + + + + + + ci + + + + org.apache.maven.plugins + maven-surefire-plugin + + + + + + + + + diff --git a/src/checkstyle/checks.xml b/src/checkstyle/checks.xml new file mode 100644 index 0000000..b8b20be --- /dev/null +++ b/src/checkstyle/checks.xml @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/java/com/facebook/presto/plugin/oracle/OracleClient.java b/src/main/java/com/facebook/presto/plugin/oracle/OracleClient.java new file mode 100644 index 0000000..7ac9f89 --- /dev/null +++ b/src/main/java/com/facebook/presto/plugin/oracle/OracleClient.java @@ -0,0 +1,133 @@ +/* + * 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 com.facebook.presto.plugin.oracle; + +import com.facebook.presto.plugin.jdbc.*; +import com.facebook.presto.spi.ConnectorSession; +import com.facebook.presto.spi.PrestoException; +import com.facebook.presto.spi.type.Decimals; +import com.google.common.collect.ImmutableSet; +import oracle.jdbc.OracleDriver; +import org.apache.log4j.Logger; + +import javax.inject.Inject; +import java.sql.*; +import java.util.*; + +import static com.facebook.presto.plugin.jdbc.DriverConnectionFactory.basicConnectionProperties; +import static com.facebook.presto.plugin.jdbc.StandardReadMappings.decimalReadMapping; +import static com.facebook.presto.spi.type.DecimalType.createDecimalType; +import static java.lang.Math.max; + +/** + * Implementation of OracleClient. It describes table, schemas and columns behaviours. + * It allows to change the QueryBuilder to a custom one as well. + * + * @author Marcelo Paes Rech + * + */ +public class OracleClient extends BaseJdbcClient { + + private static final Logger log = Logger.getLogger(OracleClient.class); + + public static List ignoreSchemas = Arrays.asList("anonymous", "olapsys", "sys", "system", "xdb", "xs$null"); + + private static ConnectionFactory connectionFactory(BaseJdbcConfig config, OracleConfig oracleConfig) { + Properties connectionProperties = basicConnectionProperties(config); + if (oracleConfig.getConnectionTimeout() != null) { + connectionProperties.setProperty("oracle.net.CONNECT_TIMEOUT", String.valueOf(oracleConfig.getConnectionTimeout().toMillis())); + } + return new DriverConnectionFactory(new OracleDriver(), config.getConnectionUrl(), connectionProperties); + } + + @Inject + public OracleClient(JdbcConnectorId connectorId, BaseJdbcConfig config, + OracleConfig oracleConfig) throws SQLException { + super(connectorId, config, "", connectionFactory(config, oracleConfig)); + } + + @Override + protected Collection listSchemas(Connection connection) { + try { + ResultSet resultSet = connection.getMetaData().getSchemas(); + Throwable t = null; + try { + ImmutableSet.Builder schemaNames = ImmutableSet.builder(); + while (resultSet.next()) { + String schemaName = resultSet.getString(1).toLowerCase(); + if (!ignoreSchemas.contains(schemaName)) { + schemaNames.add(schemaName); + } + } + return schemaNames.build(); + } catch (Throwable e) { + t = e; + throw e; + } finally { + if (resultSet != null) { + if (t != null) { + try { + resultSet.close(); + } catch (Throwable t2) { + t.addSuppressed(t2); + } + } else { + resultSet.close(); + } + } + + } + } catch (SQLException ex) { + throw new PrestoException(JdbcErrorCode.JDBC_ERROR, ex); + } + } + + @Override + protected ResultSet getTables(Connection connection, Optional schemaName, Optional tableName) + throws SQLException { + // Here we put TABLE and SYNONYM when the table schema is another user schema + DatabaseMetaData metadata = connection.getMetaData(); + Optional escape = Optional.ofNullable(metadata.getSearchStringEscape()); + return metadata.getTables( + schemaName.orElse(null), + null, + escapeNamePattern(tableName, escape).orElse(null), + new String[] {"TABLE", "VIEW", "SYNONYM"}); + } + + @Override + public Optional toPrestoType(ConnectorSession session, JdbcTypeHandle type) { + Optional tp = super.toPrestoType(session, type); + if (!tp.isPresent()) { + int columnSize = type.getColumnSize(); + switch (type.getJdbcType()) { + case Types.NUMERIC: + case Types.DECIMAL: + int decimalDigits = type.getDecimalDigits(); + if (decimalDigits == -127) { + decimalDigits = 0; + columnSize = 22; + } + int precision = columnSize + max(-decimalDigits, 0); // Map decimal(p, -s) (negative scale) to decimal(p+s, 0). + if (precision > Decimals.MAX_PRECISION) { + return Optional.empty(); + } + return Optional.of(decimalReadMapping(createDecimalType(precision, max(decimalDigits, 0)))); + } + } else { + return tp; + } + return Optional.empty(); + } +} diff --git a/src/main/java/com/facebook/presto/plugin/oracle/OracleClientModule.java b/src/main/java/com/facebook/presto/plugin/oracle/OracleClientModule.java new file mode 100644 index 0000000..7d50d77 --- /dev/null +++ b/src/main/java/com/facebook/presto/plugin/oracle/OracleClientModule.java @@ -0,0 +1,54 @@ +/* + * 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 com.facebook.presto.plugin.oracle; + +import com.facebook.airlift.configuration.AbstractConfigurationAwareModule; +import com.facebook.presto.plugin.jdbc.BaseJdbcConfig; +import com.facebook.presto.plugin.jdbc.JdbcClient; +import com.google.inject.Binder; +import com.google.inject.Scopes; +import oracle.jdbc.OracleDriver; + +import java.sql.SQLException; + +import static com.facebook.airlift.configuration.ConfigBinder.configBinder; +import static com.google.common.base.Preconditions.checkArgument; + +/** + * Guice implementation to create the correct DI and binds + * + * @author Marcelo Paes Rech + * + */ +public class OracleClientModule extends AbstractConfigurationAwareModule { + @Override + protected void setup(Binder binder) + { + binder.bind(JdbcClient.class).to(OracleClient.class).in(Scopes.SINGLETON); + ensureCatalogIsEmpty(buildConfigObject(BaseJdbcConfig.class).getConnectionUrl()); + configBinder(binder).bindConfig(OracleConfig.class); + } + + private static void ensureCatalogIsEmpty(String connectionUrl) + { + try { + OracleDriver driver = new OracleDriver(); + boolean bAccept = driver.acceptsURL(connectionUrl); + checkArgument(bAccept, "Invalid JDBC URL for Oracle connector"); + } + catch (SQLException e) { + throw new RuntimeException(e); + } + } +} diff --git a/src/main/java/com/facebook/presto/plugin/oracle/OracleConfig.java b/src/main/java/com/facebook/presto/plugin/oracle/OracleConfig.java new file mode 100644 index 0000000..b809d61 --- /dev/null +++ b/src/main/java/com/facebook/presto/plugin/oracle/OracleConfig.java @@ -0,0 +1,43 @@ +/* + * 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 com.facebook.presto.plugin.oracle; + +import com.facebook.airlift.configuration.Config; + +import io.airlift.units.Duration; +import java.util.concurrent.TimeUnit; + +/** + * To get the custom properties to connect to the database. User, password and + * URL is provided by de BaseJdbcClient is not required. If there is another + * custom configuration it should be put in here. + * + * @author Marcelo Paes Rech + * + */ +public class OracleConfig { + private Duration connectionTimeout = new Duration(10, TimeUnit.SECONDS); + + public Duration getConnectionTimeout() + { + return connectionTimeout; + } + + @Config("oracle.connection-timeout") + public OracleConfig setConnectionTimeout(Duration connectionTimeout) + { + this.connectionTimeout = connectionTimeout; + return this; + } +} diff --git a/src/main/java/com/facebook/presto/plugin/oracle/OraclePlugin.java b/src/main/java/com/facebook/presto/plugin/oracle/OraclePlugin.java new file mode 100644 index 0000000..e9142a2 --- /dev/null +++ b/src/main/java/com/facebook/presto/plugin/oracle/OraclePlugin.java @@ -0,0 +1,36 @@ +/** + * + */ +/* + * 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 com.facebook.presto.plugin.oracle; + +import com.facebook.presto.plugin.jdbc.JdbcPlugin; + +/** + * Initial class injected into PrestoDB via SPI. + * + * @author Marcelo Paes Recg + * + */ +public class OraclePlugin extends JdbcPlugin { + + /** + * Oracle Plugin Constructor + */ + public OraclePlugin() { + //name of the connector and the module implementation + super("oracle", new OracleClientModule()); + } +} diff --git a/src/main/resources/META-INF/services/com.facebook.presto.spi.Plugin b/src/main/resources/META-INF/services/com.facebook.presto.spi.Plugin new file mode 100644 index 0000000..bc3b358 --- /dev/null +++ b/src/main/resources/META-INF/services/com.facebook.presto.spi.Plugin @@ -0,0 +1 @@ +com.facebook.presto.plugin.oracle.OraclePlugin \ No newline at end of file diff --git a/src/test/java/com/facebook/presto/plugin/oracle/OracleClientModuleTest.java b/src/test/java/com/facebook/presto/plugin/oracle/OracleClientModuleTest.java new file mode 100644 index 0000000..73cbfc1 --- /dev/null +++ b/src/test/java/com/facebook/presto/plugin/oracle/OracleClientModuleTest.java @@ -0,0 +1,75 @@ +package com.facebook.presto.plugin.oracle; + +import com.facebook.presto.plugin.jdbc.BaseJdbcConfig; +import com.facebook.presto.plugin.jdbc.JdbcColumnHandle; +import com.facebook.presto.plugin.jdbc.JdbcConnectorId; +import com.facebook.presto.plugin.jdbc.JdbcIdentity; +import com.facebook.presto.spi.ConnectorSession; +import com.facebook.presto.spi.SchemaTableName; +import com.google.common.collect.ImmutableSet; +import oracle.jdbc.OracleDriver; +import org.apache.log4j.Logger; +import org.junit.jupiter.api.Test; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.List; +import java.util.Set; +import static com.facebook.presto.testing.TestingSession.testSessionBuilder; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +class OracleClientModuleTest { + private static final Logger log = Logger.getLogger(OracleClientModuleTest.class); + private static final ConnectorSession session = testSessionBuilder().build().toConnectorSession(); + + public static final String CONNECTOR_ID = "test"; + + @Test + public void connectUrlTest() throws SQLException { + String connectionUrl = "jdbc:oracle:thin:@//10.18.20.180:1521/MUDATA"; + OracleDriver driver = new OracleDriver(); + assertTrue(driver.acceptsURL(connectionUrl)); + } + + @Test + public void listSchemasTest() throws SQLException, ClassNotFoundException { + String connectionUrl = "jdbc:oracle:thin:@//10.18.20.180:1521/MUDATA"; + Class.forName("oracle.jdbc.OracleDriver"); + Connection connection = + DriverManager.getConnection(connectionUrl, "md_flight", "MD_FLIGHT_2018"); + try (ResultSet resultSet = connection.getMetaData().getSchemas()) { + ImmutableSet.Builder schemaNames = ImmutableSet.builder(); + while (resultSet.next()) { + String schemaName = resultSet.getString(1).toLowerCase(); + log.info("Listing schemas: " + schemaName); + schemaNames.add(schemaName); + } + } + catch (SQLException e) { + throw new RuntimeException(e); + } + log.info("end."); + } + + @Test + public void getColumnsTest() throws SQLException, ClassNotFoundException { + String connectionUrl = "jdbc:oracle:thin:@//10.18.20.180:1521/MUDATA"; + Class.forName("oracle.jdbc.OracleDriver"); + JdbcConnectorId connectorId = new JdbcConnectorId(CONNECTOR_ID); + BaseJdbcConfig config = new BaseJdbcConfig(); + config.setConnectionUrl(connectionUrl); + config.setConnectionUser("MD_BOOKING"); + config.setConnectionPassword("MD_BOOKING_2019"); + OracleConfig oracleConfig = new OracleConfig(); + OracleClient client = new OracleClient(connectorId, config, oracleConfig); + + List columns = client.getColumns(session, + client.getTableHandle(JdbcIdentity.from(session), + new SchemaTableName("md_booking", "t07_pass_load_factor"))); + columns.forEach(c -> log.info(String.format("%s.%s", c.getColumnName(), c.getColumnType().toString()))); + log.info("end."); + } +} \ No newline at end of file diff --git a/src/test/resources/log4j.properties b/src/test/resources/log4j.properties new file mode 100644 index 0000000..38f3c66 --- /dev/null +++ b/src/test/resources/log4j.properties @@ -0,0 +1,4 @@ +log4j.rootLogger=DEBUG, consoleAppender +log4j.appender.consoleAppender=org.apache.log4j.ConsoleAppender +log4j.appender.consoleAppender.layout=org.apache.log4j.PatternLayout +log4j.appender.consoleAppender.layout.ConversionPattern=[%t] %-5p %c %x - %m%n \ No newline at end of file