From b39cae2e332c58cd493965c4b58d6cf64414ef5d Mon Sep 17 00:00:00 2001 From: anastasiiasergienko Date: Wed, 4 Nov 2020 15:46:30 +0100 Subject: [PATCH 01/12] * #401: Updated to the `virtual-schema-common-jdbc:6.1.0` --- doc/changes/changes_4.0.4.md | 29 +-- doc/dialects/athena.md | 10 +- doc/dialects/aurora.md | 2 +- doc/dialects/bigquery.md | 2 +- doc/dialects/db2.md | 4 +- doc/dialects/hive.md | 4 +- doc/dialects/impala.md | 2 +- doc/dialects/mysql.md | 2 +- doc/dialects/oracle.md | 2 +- doc/dialects/postgresql.md | 2 +- doc/dialects/redshift.md | 2 +- doc/dialects/saphana.md | 2 +- doc/dialects/sql_server.md | 2 +- doc/dialects/sybase.md | 2 +- doc/dialects/teradata.md | 2 +- pom.xml | 25 ++- .../dialects/athena/AthenaIdentifier.java | 84 ++++++++ .../dialects/athena/AthenaSqlDialect.java | 22 +- .../dialects/bigquery/BigQueryIdentifier.java | 55 +++++ .../bigquery/BigQueryQueryRewriter.java | 4 +- .../dialects/bigquery/BigQuerySqlDialect.java | 15 +- .../adapter/dialects/db2/DB2SqlDialect.java | 4 +- .../dialects/db2/DB2SqlGenerationVisitor.java | 13 +- .../dialects/generic/GenericSqlDialect.java | 6 +- .../adapter/dialects/hive/HiveSqlDialect.java | 3 +- .../dialects/impala/ImpalaSqlDialect.java | 13 +- .../impala/ImpalaSqlGenerationVisitor.java | 22 +- .../dialects/mysql/MySqlSqlDialect.java | 4 +- .../dialects/oracle/OracleSqlDialect.java | 4 +- .../oracle/OracleSqlGenerationVisitor.java | 37 ++-- .../postgresql/PostgreSQLSqlDialect.java | 3 +- .../PostgresSQLSqlGenerationVisitor.java | 13 +- .../dialects/redshift/RedshiftSqlDialect.java | 4 +- .../RedshiftSqlGenerationVisitor.java | 15 +- .../dialects/saphana/SapHanaSqlDialect.java | 6 +- .../sqlserver/SqlServerIdentifier.java | 62 ++++++ .../sqlserver/SqlServerSqlDialect.java | 4 +- .../dialects/sybase/SybaseIdentifier.java | 76 +++++++ .../dialects/sybase/SybaseSqlDialect.java | 5 +- .../dialects/teradata/TeradataSqlDialect.java | 4 +- .../dialects/IntegrationTestConstants.java | 4 +- .../dialects/athena/AthenaIdentifierTest.java | 29 +++ .../athena/AthenaSqlDialectFactoryTest.java | 4 +- .../dialects/athena/AthenaSqlDialectTest.java | 15 +- .../bigquery/BigQueryIdentifierTest.java | 12 ++ .../bigquery/BigQueryQueryRewriterTest.java | 2 +- .../BigQuerySqlDialectFactoryTest.java | 4 +- .../bigquery/BigQuerySqlDialectTest.java | 27 ++- .../db2/DB2SqlDialectFactoryTest.java | 4 +- .../dialects/db2/DB2SqlDialectTest.java | 37 ++-- .../db2/DB2SqlGenerationVisitorTest.java | 76 ++++--- .../generic/GenericSqlDialectFactoryTest.java | 6 +- .../dialects/hive/HiveMetadataReaderTest.java | 2 +- .../hive/HiveSqlDialectFactoryTest.java | 2 +- .../dialects/hive/HiveSqlDialectTest.java | 13 +- .../hive/HiveSqlGenerationVisitorTest.java | 39 +++- .../impala/ImpalaSqlDialectFactoryTest.java | 4 +- .../dialects/impala/ImpalaSqlDialectTest.java | 29 ++- .../ImpalaSqlGenerationVisitorTest.java | 35 ++++ .../dialects/mysql/MySqlSqlDialectTest.java | 18 +- .../dialects/oracle/OracleSqlDialectTest.java | 86 ++------ .../OracleSqlGenerationVisitorTest.java | 195 +++++++++++++----- .../postgresql/PostgreSQLSqlDialectTest.java | 25 ++- .../PostgresSQLSqlGenerationVisitorTest.java | 64 ++++-- .../redshift/RedshiftSqlDialectTest.java | 35 ++-- .../RedshiftSqlGenerationVisitorTest.java | 45 ++++ .../saphana/SapHanaSqlDialectTest.java | 23 ++- .../sqlserver/SqlServerSqlDialectTest.java | 20 ++ .../SqlServerSqlGenerationVisitorTest.java | 48 ++++- .../dialects/sybase/SybaseIdentifierTest.java | 27 +++ .../dialects/sybase/SybaseSqlDialectTest.java | 27 ++- .../SybaseSqlGenerationVisitorTest.java | 44 +++- .../teradata/TeradataSqlDialectTest.java | 23 ++- .../TeradataSqlGenerationVisitorTest.java | 34 ++- src/test/java/utils/SqlNodesCreator.java | 94 --------- 75 files changed, 1217 insertions(+), 507 deletions(-) create mode 100644 src/main/java/com/exasol/adapter/dialects/athena/AthenaIdentifier.java create mode 100644 src/main/java/com/exasol/adapter/dialects/bigquery/BigQueryIdentifier.java create mode 100644 src/main/java/com/exasol/adapter/dialects/sqlserver/SqlServerIdentifier.java create mode 100644 src/main/java/com/exasol/adapter/dialects/sybase/SybaseIdentifier.java create mode 100644 src/test/java/com/exasol/adapter/dialects/athena/AthenaIdentifierTest.java create mode 100644 src/test/java/com/exasol/adapter/dialects/bigquery/BigQueryIdentifierTest.java create mode 100644 src/test/java/com/exasol/adapter/dialects/impala/ImpalaSqlGenerationVisitorTest.java create mode 100644 src/test/java/com/exasol/adapter/dialects/redshift/RedshiftSqlGenerationVisitorTest.java create mode 100644 src/test/java/com/exasol/adapter/dialects/sybase/SybaseIdentifierTest.java delete mode 100644 src/test/java/utils/SqlNodesCreator.java diff --git a/doc/changes/changes_4.0.4.md b/doc/changes/changes_4.0.4.md index df8af192f..57864780d 100644 --- a/doc/changes/changes_4.0.4.md +++ b/doc/changes/changes_4.0.4.md @@ -1,4 +1,4 @@ -# Exasol Virtual Schemas 4.0.4, released 2020-10-?? +# Exasol Virtual Schemas 4.0.4, released 2020-11-?? Code name: @@ -16,19 +16,22 @@ Code name: * #263: Removed SybaseMetadataReader class as it was not used by the dialect. * #381: Migrated from version.sh to artifact-reference-checker-maven-plugin. * #389: Improved connection error handling. -* #396: Updated to the `virtual-schema-common-java:6.0.0` +* #396: Updated to the `virtual-schema-common-jdbc:6.0.0` +* #401: Updated to the `virtual-schema-common-jdbc:6.1.0` ## Dependency updates * Added com.exasol:artifact-reference-checker-maven-plugin:0.3.1 -* Updated com.exasol:virtual-schema-common-java:jar:5.0.4 to version 6.0.0 -* Updated org.apache.hbase:hbase-server:jar:2.3.0 to version 2.3.1 -* Updated org.junit.jupiter:junit-jupiter:jar:5.6.2 to version 5.7.0 -* Updated org.mockito:mockito-junit-jupiter:jar:3.4.6 to version 3.5.13 -* Updated com.exasol:exasol-jdbc:jar:6.2.5 to version 7.0.0 -* Updated com.exasol:exasol-testcontainers:jar:2.1.0 to version 3.1.0 -* Updated org.postgresql:postgresql:jar:42.2.14 to version 42.2.16 -* Updated org.apache.hbase:hbase-server:jar:2.3.1 to version 2.3.2 -* Updated com.microsoft.sqlserver:mssql-jdbc:jar:8.4.0.jre11 to version 8.4.1.jre11 -* Updated com.exasol:test-db-builder-java:jar:1.0.1 to version 1.1.0 -* Updated com.exasol:hamcrest-resultset-matcher:jar:1.1.1 to version 1.2.1 \ No newline at end of file +* Updated com.exasol:virtual-schema-common-jdbc:5.0.4 to 6.1.0 +* Updated org.apache.hbase:hbase-server:2.3.0 to 2.3.3 +* Updated org.junit.jupiter:junit-jupiter:5.6.2 to 5.7.0 +* Updated org.mockito:mockito-junit-jupiter:3.4.6 to 3.6.0 +* Updated com.exasol:exasol-jdbc:6.2.5 to 7.0.3 +* Updated com.exasol:exasol-testcontainers:2.1.0 to 3.2.0 +* Updated org.postgresql:postgresql:42.2.14 to 42.2.18 +* Updated org.apache.hbase:hbase-server:2.3.1 to 2.3.2 +* Updated com.microsoft.sqlserver:mssql-jdbc:8.4.0.jre11 to 8.4.1.jre11 +* Updated com.exasol:test-db-builder-java:1.0.1 to 1.1.0 +* Updated com.exasol:hamcrest-resultset-matcher:1.1.1 to 1.2.1 +* Updated nl.jqno.equalsverifier:equalsverifier:3.4.3 to 3.5 +* Updated mysql:mysql-connector-java:8.0.21 to 8.0.22 \ No newline at end of file diff --git a/doc/dialects/athena.md b/doc/dialects/athena.md index 54eccd728..2fce620f2 100644 --- a/doc/dialects/athena.md +++ b/doc/dialects/athena.md @@ -21,12 +21,16 @@ You need to specify the following settings when adding the JDBC driver via EXAOp | Parameter | Value | |-----------|-----------------------------------------------------| | Name | `ATHENA` | -| Main | `com.amazon.athena.jdbc.Driver` | +| Main | `com.simba.athena.jdbc.Driver` | | Prefix | `jdbc:awsathena:` | | Files | `AthenaJDBC42_.jar` | Please refer to the [documentation on configuring JDBC connections to Athena](https://docs.aws.amazon.com/athena/latest/ug/connect-with-jdbc.html) for details. +IMPORTANT: The latest Athena driver requires to **Disable Security Manager**. +It is necessary because JDBC driver requires Java permissions which we do not grant by default. +Please keep in mind that it's not safe to disable the security manager. + ## Uploading the JDBC Driver to EXAOperation 1. [Create a bucket in BucketFS](https://docs.exasol.com/administration/on-premise/bucketfs/create_new_bucket_in_bucketfs_service.htm) @@ -49,7 +53,7 @@ The SQL statement below creates the adapter script, defines the Java class that ```sql CREATE OR REPLACE JAVA ADAPTER SCRIPT ADAPTER.JDBC_ADAPTER AS %scriptclass com.exasol.adapter.RequestDispatcher; - %jar /buckets///virtual-schema-dist-6.0.0-bundle-4.0.4.jar; + %jar /buckets///virtual-schema-dist-6.1.0-bundle-4.0.4.jar; %jar /buckets///AthenaJDBC42-.jar; / ; @@ -77,4 +81,4 @@ CREATE VIRTUAL SCHEMA SQL_DIALECT = 'ATHENA' CONNECTION_NAME = 'ATHENA_CONNECTION' SCHEMA_NAME = ''; -``` +``` \ No newline at end of file diff --git a/doc/dialects/aurora.md b/doc/dialects/aurora.md index 23bbd13f4..c3ade6c6c 100644 --- a/doc/dialects/aurora.md +++ b/doc/dialects/aurora.md @@ -62,7 +62,7 @@ The SQL statement below creates the adapter script, defines the Java class that ```sql CREATE OR REPLACE JAVA ADAPTER SCRIPT ADAPTER.JDBC_ADAPTER AS %scriptclass com.exasol.adapter.RequestDispatcher; - %jar /buckets///virtual-schema-dist-6.0.0-bundle-4.0.4.jar; + %jar /buckets///virtual-schema-dist-6.1.0-bundle-4.0.4.jar; %jar /buckets///postgresql-.jar; / ``` diff --git a/doc/dialects/bigquery.md b/doc/dialects/bigquery.md index 2f9e144fe..1d5742481 100644 --- a/doc/dialects/bigquery.md +++ b/doc/dialects/bigquery.md @@ -33,7 +33,7 @@ List all the JAR files from Magnitude Simba JDBC driver. ```sql CREATE JAVA ADAPTER SCRIPT SCHEMA_FOR_VS_SCRIPT.ADAPTER_SCRIPT_BIGQUERY AS %scriptclass com.exasol.adapter.RequestDispatcher; - %jar /buckets///virtual-schema-dist-6.0.0-bundle-4.0.4.jar; + %jar /buckets///virtual-schema-dist-6.1.0-bundle-4.0.4.jar; %jar /buckets///GoogleBigQueryJDBC42.jar; ... ... diff --git a/doc/dialects/db2.md b/doc/dialects/db2.md index 079666460..707b9e012 100644 --- a/doc/dialects/db2.md +++ b/doc/dialects/db2.md @@ -56,7 +56,7 @@ The SQL statement below creates the adapter script, defines the Java class that ```sql CREATE OR REPLACE JAVA ADAPTER SCRIPT ADAPTER.JDBC_ADAPTER AS %scriptclass com.exasol.adapter.RequestDispatcher; - %jar /buckets///virtual-schema-dist-6.0.0-bundle-4.0.4.jar; + %jar /buckets///virtual-schema-dist-6.1.0-bundle-4.0.4.jar; %jar /buckets///db2jcc4.jar; %jar /buckets///db2jcc_license_cu.jar; / @@ -68,7 +68,7 @@ CREATE OR REPLACE JAVA ADAPTER SCRIPT ADAPTER.JDBC_ADAPTER AS ```sql CREATE OR REPLACE JAVA ADAPTER SCRIPT ADAPTER.JDBC_ADAPTER AS %scriptclass com.exasol.adapter.RequestDispatcher; - %jar /buckets///virtual-schema-dist-6.0.0-bundle-4.0.4.jar; + %jar /buckets///virtual-schema-dist-6.1.0-bundle-4.0.4.jar; %jar /buckets///db2jcc4.jar; %jar /buckets///db2jcc_license_cu.jar; %jar /buckets///db2jcc_license_cisuz.jar; diff --git a/doc/dialects/hive.md b/doc/dialects/hive.md index a75ccdfd1..cf2f2f5c3 100644 --- a/doc/dialects/hive.md +++ b/doc/dialects/hive.md @@ -47,7 +47,7 @@ The SQL statement below creates the adapter script, defines the Java class that ```sql CREATE OR REPLACE JAVA ADAPTER SCRIPT ADAPTER.JDBC_ADAPTER AS %scriptclass com.exasol.adapter.RequestDispatcher; - %jar /buckets///jars/virtual-schema-dist-6.0.0-bundle-4.0.4.jar; + %jar /buckets///jars/virtual-schema-dist-6.1.0-bundle-4.0.4.jar; %jar /buckets///jars/HiveJDBC41.jar; / ``` @@ -302,7 +302,7 @@ In Virtual Schema adapter: CREATE OR REPLACE JAVA ADAPTER SCRIPT ADAPTER.JDBC_ADAPTER AS %jvmoption -Dsun.security.krb5.disableReferrals=true; %scriptclass com.exasol.adapter.RequestDispatcher; - %jar /buckets///jars/virtual-schema-dist-6.0.0-bundle-4.0.4.jar; + %jar /buckets///jars/virtual-schema-dist-6.1.0-bundle-4.0.4.jar; %jar /buckets///jars/HiveJDBC41.jar; / ``` diff --git a/doc/dialects/impala.md b/doc/dialects/impala.md index eed782c5c..871b746fe 100644 --- a/doc/dialects/impala.md +++ b/doc/dialects/impala.md @@ -47,7 +47,7 @@ The SQL statement below creates the adapter script, defines the Java class that ```sql CREATE OR REPLACE JAVA ADAPTER SCRIPT ADAPTER.JDBC_ADAPTER AS %scriptclass com.exasol.adapter.RequestDispatcher; - %jar /buckets///virtual-schema-dist-6.0.0-bundle-4.0.4.jar; + %jar /buckets///virtual-schema-dist-6.1.0-bundle-4.0.4.jar; %jar /buckets///ImpalaJDBC41.jar; / ; diff --git a/doc/dialects/mysql.md b/doc/dialects/mysql.md index 22511c5db..8038a81eb 100644 --- a/doc/dialects/mysql.md +++ b/doc/dialects/mysql.md @@ -51,7 +51,7 @@ The SQL statement below creates the adapter script, defines the Java class that ```sql CREATE OR REPLACE JAVA ADAPTER SCRIPT SCHEMA_FOR_VS_SCRIPT.ADAPTER_SCRIPT_MYSQL AS %scriptclass com.exasol.adapter.RequestDispatcher; - %jar /buckets///virtual-schema-dist-6.0.0-bundle-4.0.4.jar; + %jar /buckets///virtual-schema-dist-6.1.0-bundle-4.0.4.jar; %jar /buckets///mysql-connector-java-.jar; / ; diff --git a/doc/dialects/oracle.md b/doc/dialects/oracle.md index f15a00839..3d91aa592 100644 --- a/doc/dialects/oracle.md +++ b/doc/dialects/oracle.md @@ -48,7 +48,7 @@ The SQL statement below creates the adapter script, defines the Java class that ```sql CREATE JAVA ADAPTER SCRIPT ADAPTER.JDBC_ADAPTER AS %scriptclass com.exasol.adapter.RequestDispatcher; - %jar /buckets///virtual-schema-dist-6.0.0-bundle-4.0.4.jar; + %jar /buckets///virtual-schema-dist-6.1.0-bundle-4.0.4.jar; %jar /buckets///ojdbc.jar; / ; diff --git a/doc/dialects/postgresql.md b/doc/dialects/postgresql.md index 5be5b8417..9ad298ced 100644 --- a/doc/dialects/postgresql.md +++ b/doc/dialects/postgresql.md @@ -25,7 +25,7 @@ The SQL statement below creates the adapter script, defines the Java class that ```sql CREATE OR REPLACE JAVA ADAPTER SCRIPT ADAPTER.JDBC_ADAPTER AS %scriptclass com.exasol.adapter.RequestDispatcher; - %jar /buckets///virtual-schema-dist-6.0.0-bundle-4.0.4.jar; + %jar /buckets///virtual-schema-dist-6.1.0-bundle-4.0.4.jar; %jar /buckets///postgresql-.jar; / ``` diff --git a/doc/dialects/redshift.md b/doc/dialects/redshift.md index 876efdf38..161946173 100644 --- a/doc/dialects/redshift.md +++ b/doc/dialects/redshift.md @@ -51,7 +51,7 @@ The SQL statement below creates the adapter script, defines the Java class that ```sql CREATE OR REPLACE JAVA ADAPTER SCRIPT ADAPTER.JDBC_ADAPTER AS %scriptclass com.exasol.adapter.RequestDispatcher; - %jar /buckets///virtual-schema-dist-6.0.0-bundle-4.0.4.jar; + %jar /buckets///virtual-schema-dist-6.1.0-bundle-4.0.4.jar; %jar /buckets///RedshiftJDBC42-.jar; / ; diff --git a/doc/dialects/saphana.md b/doc/dialects/saphana.md index 0f5f385ba..a27ac2854 100644 --- a/doc/dialects/saphana.md +++ b/doc/dialects/saphana.md @@ -47,7 +47,7 @@ The SQL statement below creates the adapter script, defines the Java class that ```sql CREATE JAVA ADAPTER SCRIPT ADAPTER.JDBC_ADAPTER AS %scriptclass com.exasol.adapter.RequestDispatcher; - %jar /buckets///virtual-schema-dist-6.0.0-bundle-4.0.4.jar; + %jar /buckets///virtual-schema-dist-6.1.0-bundle-4.0.4.jar; %jar /buckets///ngdbc-.jar; / ; diff --git a/doc/dialects/sql_server.md b/doc/dialects/sql_server.md index a3f57b33e..0700e084f 100644 --- a/doc/dialects/sql_server.md +++ b/doc/dialects/sql_server.md @@ -46,7 +46,7 @@ The SQL statement below creates the adapter script, defines the Java class that ```sql CREATE OR REPLACE JAVA ADAPTER SCRIPT SCHEMA_FOR_VS_SCRIPT.ADAPTER_SCRIPT_SQLSERVER AS %scriptclass com.exasol.adapter.RequestDispatcher; - %jar /buckets///virtual-schema-dist-6.0.0-bundle-4.0.4.jar; + %jar /buckets///virtual-schema-dist-6.1.0-bundle-4.0.4.jar; %jar /buckets///mssql-jdbc-.jre8.jar; / ``` diff --git a/doc/dialects/sybase.md b/doc/dialects/sybase.md index 91d6bb822..3d17a9a82 100644 --- a/doc/dialects/sybase.md +++ b/doc/dialects/sybase.md @@ -29,7 +29,7 @@ The SQL statement below creates the adapter script, defines the Java class that ```sql CREATE OR REPLACE JAVA ADAPTER SCRIPT ADAPTER.JDBC_ADAPTER AS %scriptclass com.exasol.adapter.RequestDispatcher; - %jar /buckets///virtual-schema-dist-6.0.0-bundle-4.0.4.jar; + %jar /buckets///virtual-schema-dist-6.1.0-bundle-4.0.4.jar; %jar /buckets///jtds-.jar; / ``` diff --git a/doc/dialects/teradata.md b/doc/dialects/teradata.md index 8bb5931f2..fffc96436 100644 --- a/doc/dialects/teradata.md +++ b/doc/dialects/teradata.md @@ -47,7 +47,7 @@ The SQL statement below creates the adapter script, defines the Java class that ```sql CREATE OR REPLACE JAVA ADAPTER SCRIPT ADAPTER.JDBC_ADAPTER AS %scriptclass com.exasol.adapter.RequestDispatcher; - %jar /buckets///virtual-schema-dist-6.0.0-bundle-4.0.4.jar; + %jar /buckets///virtual-schema-dist-6.1.0-bundle-4.0.4.jar; %jar /buckets///terajdbc4.jar; %jar /buckets///tdgssconfig.jar; / diff --git a/pom.xml b/pom.xml index d421017d6..72d0c04ee 100644 --- a/pom.xml +++ b/pom.xml @@ -11,7 +11,7 @@ UTF-8 11 3.0.0-M4 - 6.0.0 + 6.1.0 1.14.3 target/site/jacoco/jacoco.xml,target/site/jacoco-it/jacoco.xml @@ -48,7 +48,18 @@ virtual-schema-common-jdbc ${vscjdbc.version} + + com.exasol + db-fundamentals-java + 0.1.1 + + + nl.jqno.equalsverifier + equalsverifier + 3.5 + test + com.exasol virtual-schema-common-jdbc @@ -71,20 +82,20 @@ org.mockito mockito-junit-jupiter - 3.5.13 + 3.6.0 test com.exasol exasol-jdbc - 7.0.0 + 7.0.3 test com.exasol exasol-testcontainers - 3.1.0 + 3.2.0 test @@ -102,7 +113,7 @@ org.postgresql postgresql - 42.2.16 + 42.2.18 org.testcontainers @@ -177,7 +188,7 @@ org.apache.hbase hbase-server - 2.3.2 + 2.3.3 test @@ -235,7 +246,7 @@ mysql mysql-connector-java - 8.0.21 + 8.0.22 test diff --git a/src/main/java/com/exasol/adapter/dialects/athena/AthenaIdentifier.java b/src/main/java/com/exasol/adapter/dialects/athena/AthenaIdentifier.java new file mode 100644 index 000000000..8ac34b856 --- /dev/null +++ b/src/main/java/com/exasol/adapter/dialects/athena/AthenaIdentifier.java @@ -0,0 +1,84 @@ +package com.exasol.adapter.dialects.athena; + +import com.exasol.db.Identifier; + +import java.util.Objects; + +/** + * Represents an identifier in the Athena database. + */ +public class AthenaIdentifier implements Identifier { + private final String id; + + private AthenaIdentifier(final String id) { + this.id = id; + } + + /** + * Get the quoted identifier as a {@link String}. + * + * @return quoted identifier + */ + @Override + public String quote() { + if (this.id.startsWith("_")) { + return quoteWithBackticks(this.id); + } else { + return quoteWithDoubleQuotes(this.id); + } + } + + private String quoteWithBackticks(final String identifier) { + return "`" + identifier + "`"; + } + + private String quoteWithDoubleQuotes(final String identifier) { + return "\"" + identifier + "\""; + } + + /** + * Create a new {@link AthenaIdentifier}. + * + * @param id the identifier as {@link String} + * @return new {@link AthenaIdentifier} instance + */ + public static AthenaIdentifier of(final String id) { + if (validate(id)) { + return new AthenaIdentifier(id); + } else { + throw new AssertionError("E-ID-2: Unable to create identifier \"" + id // + + "\" because it contains illegal characters." // + + " For information about valid identifiers, please refer to" // + + " https://docs.aws.amazon.com/athena/latest/ug/tables-databases-columns-names.html"); + } + } + + private static boolean validate(final String id) { + if ((id == null) || id.isEmpty()) { + return false; + } + for (int i = 0; i < id.length(); ++i) { + if (!validateCharacter(id.codePointAt(i))) { + return false; + } + } + return true; + } + + private static boolean validateCharacter(final int codePoint) { + return codePoint == '_' || Character.isLetter(codePoint) || Character.isDigit(codePoint); + } + + @Override + public boolean equals(final Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + final AthenaIdentifier that = (AthenaIdentifier) o; + return Objects.equals(this.id, that.id); + } + + @Override + public int hashCode() { + return Objects.hash(this.id); + } +} diff --git a/src/main/java/com/exasol/adapter/dialects/athena/AthenaSqlDialect.java b/src/main/java/com/exasol/adapter/dialects/athena/AthenaSqlDialect.java index 371451729..b2d2fb18f 100644 --- a/src/main/java/com/exasol/adapter/dialects/athena/AthenaSqlDialect.java +++ b/src/main/java/com/exasol/adapter/dialects/athena/AthenaSqlDialect.java @@ -108,25 +108,7 @@ public boolean requiresSchemaQualifiedTableNames(final SqlGenerationContext cont @Override public String applyQuote(final String identifier) { - if (identifier.startsWith("_")) { - return quoteWithBackticks(identifier); - } else { - return quoteWithDoubleQuotes(identifier); - } - } - - private String quoteWithBackticks(final String identifier) { - final StringBuilder builder = new StringBuilder("`"); - builder.append(identifier); - builder.append("`"); - return builder.toString(); - } - - private String quoteWithDoubleQuotes(final String identifier) { - final StringBuilder builder = new StringBuilder("\""); - builder.append(identifier); - builder.append("\""); - return builder.toString(); + return AthenaIdentifier.of(identifier).quote(); } @Override @@ -148,4 +130,4 @@ protected RemoteMetadataReader createRemoteMetadataReader() { protected QueryRewriter createQueryRewriter() { return new BaseQueryRewriter(this, createRemoteMetadataReader(), this.connectionFactory); } -} +} \ No newline at end of file diff --git a/src/main/java/com/exasol/adapter/dialects/bigquery/BigQueryIdentifier.java b/src/main/java/com/exasol/adapter/dialects/bigquery/BigQueryIdentifier.java new file mode 100644 index 000000000..f35cacf68 --- /dev/null +++ b/src/main/java/com/exasol/adapter/dialects/bigquery/BigQueryIdentifier.java @@ -0,0 +1,55 @@ +package com.exasol.adapter.dialects.bigquery; + +import java.util.Objects; + +import com.exasol.db.Identifier; + +/** + * Represents an identifier in the BigQuery database. + */ +public class BigQueryIdentifier implements Identifier { + private final String id; + + private BigQueryIdentifier(final String id) { + this.id = id; + } + + /** + * Get the quoted identifier as a {@link String}. + * + * @return quoted identifier + */ + @Override + public String quote() { + return "`" + this.id.replace("`", "\\`") + "`"; + } + + /** + * Create a new {@link BigQueryIdentifier}. + *

+ * BigQuery allows any characters when the identifier is quoted. We don't have validations here. + * https://cloud.google.com/bigquery/docs/reference/standard-sql/lexical + *

+ * + * @param id the identifier as {@link String} + * @return new {@link BigQueryIdentifier} instance + */ + public static BigQueryIdentifier of(final String id) { + return new BigQueryIdentifier(id); + } + + @Override + public boolean equals(final Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + final BigQueryIdentifier that = (BigQueryIdentifier) o; + return Objects.equals(this.id, that.id); + } + + @Override + public int hashCode() { + return Objects.hash(this.id); + } +} \ No newline at end of file diff --git a/src/main/java/com/exasol/adapter/dialects/bigquery/BigQueryQueryRewriter.java b/src/main/java/com/exasol/adapter/dialects/bigquery/BigQueryQueryRewriter.java index 47eebcfed..f1b90feed 100644 --- a/src/main/java/com/exasol/adapter/dialects/bigquery/BigQueryQueryRewriter.java +++ b/src/main/java/com/exasol/adapter/dialects/bigquery/BigQueryQueryRewriter.java @@ -229,9 +229,7 @@ private void appendString(final StringBuilder builder, final ResultSet resultSet if (value == null) { builder.append("CAST (NULL AS VARCHAR(4))"); } else { - builder.append("'"); - builder.append(value); - builder.append("'"); + builder.append(this.dialect.getStringLiteral(value)); } } } \ No newline at end of file diff --git a/src/main/java/com/exasol/adapter/dialects/bigquery/BigQuerySqlDialect.java b/src/main/java/com/exasol/adapter/dialects/bigquery/BigQuerySqlDialect.java index 475bdf623..dce93b6cc 100644 --- a/src/main/java/com/exasol/adapter/dialects/bigquery/BigQuerySqlDialect.java +++ b/src/main/java/com/exasol/adapter/dialects/bigquery/BigQuerySqlDialect.java @@ -7,6 +7,8 @@ import static com.exasol.adapter.capabilities.MainCapability.*; import static com.exasol.adapter.capabilities.PredicateCapability.*; import static com.exasol.adapter.capabilities.ScalarFunctionCapability.*; +import static com.exasol.adapter.capabilities.ScalarFunctionCapability.ST_INTERSECTION; +import static com.exasol.adapter.capabilities.ScalarFunctionCapability.ST_UNION; import static com.exasol.adapter.dialects.bigquery.BigQueryProperties.BIGQUERY_ENABLE_IMPORT_PROPERTY; import java.sql.SQLException; @@ -125,7 +127,7 @@ public StructureElementSupport supportsJdbcSchemas() { @Override public String applyQuote(final String identifier) { - return "`" + identifier + "`"; + return BigQueryIdentifier.of(identifier).quote(); } @Override @@ -148,4 +150,13 @@ public void validateProperties() throws PropertyValidationException { super.validateProperties(); validateBooleanProperty(BIGQUERY_ENABLE_IMPORT_PROPERTY); } -} + + @Override + public String getStringLiteral(final String value) { + if (value == null) { + return "NULL"; + } else { + return "'" + value.replace("'", "\\'") + "'"; + } + } +} \ No newline at end of file diff --git a/src/main/java/com/exasol/adapter/dialects/db2/DB2SqlDialect.java b/src/main/java/com/exasol/adapter/dialects/db2/DB2SqlDialect.java index a874311d6..6f5b9ebbd 100644 --- a/src/main/java/com/exasol/adapter/dialects/db2/DB2SqlDialect.java +++ b/src/main/java/com/exasol/adapter/dialects/db2/DB2SqlDialect.java @@ -99,8 +99,10 @@ public StructureElementSupport supportsJdbcSchemas() { } @Override + @SuppressWarnings("squid:S1185") + // https://www.ibm.com/support/knowledgecenter/SSEPEK_10.0.0/sqlref/src/tpc/db2z_sqlidentifiers.html public String applyQuote(final String identifier) { - return "\"" + identifier.replace("\"", "\"\"") + "\""; + return super.applyQuote(identifier); } @Override diff --git a/src/main/java/com/exasol/adapter/dialects/db2/DB2SqlGenerationVisitor.java b/src/main/java/com/exasol/adapter/dialects/db2/DB2SqlGenerationVisitor.java index f16f70c09..48f0b7434 100644 --- a/src/main/java/com/exasol/adapter/dialects/db2/DB2SqlGenerationVisitor.java +++ b/src/main/java/com/exasol/adapter/dialects/db2/DB2SqlGenerationVisitor.java @@ -319,8 +319,7 @@ public String visit(final SqlFunctionAggregateGroupConcat function) throws Adapt final StringBuilder builder = new StringBuilder(); builder.append("LISTAGG"); builder.append("("); - if ((function.getArguments() != null) && (function.getArguments().size() == 1) - && (function.getArguments().get(0) != null)) { + if (function.getArgument() != null) { return getGroupConcat(function, builder); } else { throw new SqlGenerationVisitorException( @@ -330,16 +329,12 @@ public String visit(final SqlFunctionAggregateGroupConcat function) throws Adapt private String getGroupConcat(final SqlFunctionAggregateGroupConcat function, final StringBuilder builder) throws AdapterException { - final String expression = function.getArguments().get(0).accept(this); + final String expression = function.getArgument().accept(this); builder.append(expression); builder.append(", "); - String separator = ","; - if (function.getSeparator() != null) { - separator = function.getSeparator(); - } - builder.append("'"); + final String separator = function.getSeparator() == null ? "','" : function.getSeparator().accept(this); builder.append(separator); - builder.append("') "); + builder.append(") "); builder.append("WITHIN GROUP(ORDER BY "); if (function.hasOrderBy()) { getOrderBy(function, builder); diff --git a/src/main/java/com/exasol/adapter/dialects/generic/GenericSqlDialect.java b/src/main/java/com/exasol/adapter/dialects/generic/GenericSqlDialect.java index b9fc4d5ee..6583204ae 100644 --- a/src/main/java/com/exasol/adapter/dialects/generic/GenericSqlDialect.java +++ b/src/main/java/com/exasol/adapter/dialects/generic/GenericSqlDialect.java @@ -54,6 +54,10 @@ public StructureElementSupport supportsJdbcSchemas() { @Override public String applyQuote(final String identifier) { final String quoteString = this.remoteMetadataReader.getSchemaAdapterNotes().getIdentifierQuoteString(); + if (identifier.contains(quoteString)) { + throw new IllegalArgumentException("An identifier '" + identifier + "' contains illegal substring: '" + + quoteString + "'. Please remove it to use the generic dialect."); + } return quoteString + identifier + quoteString; } @@ -98,4 +102,4 @@ protected RemoteMetadataReader createRemoteMetadataReader() { protected QueryRewriter createQueryRewriter() { return new BaseQueryRewriter(this, this.remoteMetadataReader, this.connectionFactory); } -} +} \ No newline at end of file diff --git a/src/main/java/com/exasol/adapter/dialects/hive/HiveSqlDialect.java b/src/main/java/com/exasol/adapter/dialects/hive/HiveSqlDialect.java index a7705c810..3b8496f99 100644 --- a/src/main/java/com/exasol/adapter/dialects/hive/HiveSqlDialect.java +++ b/src/main/java/com/exasol/adapter/dialects/hive/HiveSqlDialect.java @@ -85,8 +85,9 @@ public StructureElementSupport supportsJdbcSchemas() { } @Override + // https://cwiki.apache.org/confluence/display/Hive/LanguageManual+DDL public String applyQuote(final String identifier) { - return "`" + identifier + "`"; + return "`" + identifier.replace("`", "``") + "`"; } @Override diff --git a/src/main/java/com/exasol/adapter/dialects/impala/ImpalaSqlDialect.java b/src/main/java/com/exasol/adapter/dialects/impala/ImpalaSqlDialect.java index cc970dba8..77617e165 100644 --- a/src/main/java/com/exasol/adapter/dialects/impala/ImpalaSqlDialect.java +++ b/src/main/java/com/exasol/adapter/dialects/impala/ImpalaSqlDialect.java @@ -93,8 +93,9 @@ public StructureElementSupport supportsJdbcSchemas() { } @Override + // https://docs.cloudera.com/documentation/enterprise/latest/topics/impala_identifiers.html public String applyQuote(final String identifier) { - return "`" + identifier + "`"; + return "`" + identifier.replace("`", "``") + "`"; } @Override @@ -131,4 +132,14 @@ protected RemoteMetadataReader createRemoteMetadataReader() { protected QueryRewriter createQueryRewriter() { return new BaseQueryRewriter(this, createRemoteMetadataReader(), this.connectionFactory); } + + @Override + // https://docs.cloudera.com/documentation/enterprise/5-9-x/topics/impala_literals.html#string_literals + public String getStringLiteral(final String value) { + if (value == null) { + return "NULL"; + } else { + return "'" + value.replace("'", "\\'") + "'"; + } + } } \ No newline at end of file diff --git a/src/main/java/com/exasol/adapter/dialects/impala/ImpalaSqlGenerationVisitor.java b/src/main/java/com/exasol/adapter/dialects/impala/ImpalaSqlGenerationVisitor.java index 5100d8008..24116a05a 100644 --- a/src/main/java/com/exasol/adapter/dialects/impala/ImpalaSqlGenerationVisitor.java +++ b/src/main/java/com/exasol/adapter/dialects/impala/ImpalaSqlGenerationVisitor.java @@ -35,17 +35,14 @@ public String visit(final SqlFunctionAggregateGroupConcat function) throws Adapt final StringBuilder builder = new StringBuilder(); builder.append(function.getFunctionName()); builder.append("("); - // To use it group_concat with numeric values we would need to sync group_concat(cast(x as string)). Since we cannot compute the type, we always cast + // To use it group_concat with numeric values we would need to sync group_concat(cast(x as string)). Since we + // cannot compute the type, we always cast builder.append("CAST("); - assert(function.getArguments() != null); - assert(function.getArguments().size() == 1 && function.getArguments().get(0) != null); - builder.append(function.getArguments().get(0).accept(this)); + builder.append(function.getArgument().accept(this)); builder.append(" AS STRING)"); - if (function.getSeparator() != null) { + if (function.hasSeparator()) { builder.append(", "); - builder.append("'"); - builder.append(function.getSeparator()); - builder.append("'"); + builder.append(function.getSeparator().accept(this)); } builder.append(")"); return builder.toString(); @@ -53,7 +50,8 @@ public String visit(final SqlFunctionAggregateGroupConcat function) throws Adapt @Override public String visit(final SqlFunctionAggregate function) throws AdapterException { - final boolean isDirectlyInSelectList = (function.hasParent() && function.getParent().getType() == SqlNodeType.SELECT_LIST); + final boolean isDirectlyInSelectList = (function.hasParent() + && function.getParent().getType() == SqlNodeType.SELECT_LIST); if (function.getFunction() != AggregateFunction.SUM || !isDirectlyInSelectList) { return super.visit(function); } else { @@ -70,11 +68,11 @@ public String visit(final SqlFunctionAggregate function) throws AdapterException distinctSql = "DISTINCT "; } String functionNameInSourceSystem = function.getFunctionName(); - if (dialect.getAggregateFunctionAliases().containsKey(function.getFunction())) { - functionNameInSourceSystem = dialect.getAggregateFunctionAliases().get(function.getFunction()); + if (this.dialect.getAggregateFunctionAliases().containsKey(function.getFunction())) { + functionNameInSourceSystem = this.dialect.getAggregateFunctionAliases().get(function.getFunction()); } return "CAST(" + functionNameInSourceSystem + "(" + distinctSql + String.join(", ", argumentsSql) + ") AS DOUBLE)"; } } -} +} \ No newline at end of file diff --git a/src/main/java/com/exasol/adapter/dialects/mysql/MySqlSqlDialect.java b/src/main/java/com/exasol/adapter/dialects/mysql/MySqlSqlDialect.java index 2f2f98ceb..bff716885 100644 --- a/src/main/java/com/exasol/adapter/dialects/mysql/MySqlSqlDialect.java +++ b/src/main/java/com/exasol/adapter/dialects/mysql/MySqlSqlDialect.java @@ -6,6 +6,8 @@ import static com.exasol.adapter.capabilities.MainCapability.*; import static com.exasol.adapter.capabilities.PredicateCapability.*; import static com.exasol.adapter.capabilities.ScalarFunctionCapability.*; +import static com.exasol.adapter.capabilities.ScalarFunctionCapability.ST_INTERSECTION; +import static com.exasol.adapter.capabilities.ScalarFunctionCapability.ST_UNION; import java.sql.SQLException; import java.util.Set; @@ -87,7 +89,7 @@ public StructureElementSupport supportsJdbcSchemas() { */ @Override public String applyQuote(final String identifier) { - return "`" + identifier + "`"; + return "`" + identifier.replace("`", "``") + "`"; } @Override diff --git a/src/main/java/com/exasol/adapter/dialects/oracle/OracleSqlDialect.java b/src/main/java/com/exasol/adapter/dialects/oracle/OracleSqlDialect.java index cc4e0b10f..386c427fc 100644 --- a/src/main/java/com/exasol/adapter/dialects/oracle/OracleSqlDialect.java +++ b/src/main/java/com/exasol/adapter/dialects/oracle/OracleSqlDialect.java @@ -114,8 +114,10 @@ public SqlNodeVisitor getSqlGenerationVisitor(final SqlGenerationContext } @Override + @SuppressWarnings("squid:S1185") + // https://docs.oracle.com/cd/B19306_01/server.102/b14200/sql_elements008.htm public String applyQuote(final String identifier) { - return "\"" + identifier.replace("\"", "\"\"") + "\""; + return super.applyQuote(identifier); } @Override diff --git a/src/main/java/com/exasol/adapter/dialects/oracle/OracleSqlGenerationVisitor.java b/src/main/java/com/exasol/adapter/dialects/oracle/OracleSqlGenerationVisitor.java index 78878b0f9..eccce6e6c 100644 --- a/src/main/java/com/exasol/adapter/dialects/oracle/OracleSqlGenerationVisitor.java +++ b/src/main/java/com/exasol/adapter/dialects/oracle/OracleSqlGenerationVisitor.java @@ -7,9 +7,7 @@ import com.exasol.adapter.AdapterException; import com.exasol.adapter.dialects.*; -import com.exasol.adapter.metadata.ColumnMetadata; -import com.exasol.adapter.metadata.DataType; -import com.exasol.adapter.metadata.TableMetadata; +import com.exasol.adapter.metadata.*; import com.exasol.adapter.sql.*; /** @@ -327,30 +325,19 @@ public String visit(final SqlFunctionAggregateGroupConcat function) throws Adapt final StringBuilder builder = new StringBuilder(); builder.append("LISTAGG"); builder.append("("); - if ((function.getArguments() != null) && (function.getArguments().size() == 1) - && (function.getArguments().get(0) != null)) { - final String expression = function.getArguments().get(0).accept(this); - builder.append(expression); - builder.append(", "); - String separator = ","; - if (function.getSeparator() != null) { - separator = function.getSeparator(); - } - builder.append("'"); - builder.append(separator); - builder.append("') "); - builder.append("WITHIN GROUP(ORDER BY "); - if (function.hasOrderBy()) { - builder.append(getOrderByString(function)); - } else { - builder.append(expression); - } - builder.append(")"); - return builder.toString(); + final String expression = function.getArgument().accept(this); + builder.append(expression); + builder.append(", "); + final String separator = function.hasSeparator() ? function.getSeparator().accept(this) : "','"; + builder.append(separator); + builder.append(") WITHIN GROUP(ORDER BY "); + if (function.hasOrderBy()) { + builder.append(getOrderByString(function)); } else { - throw new SqlGenerationVisitorException( - "List of arguments of SqlFunctionAggregateGroupConcat should have one argument."); + builder.append(expression); } + builder.append(")"); + return builder.toString(); } private String getOrderByString(final SqlFunctionAggregateGroupConcat function) throws AdapterException { diff --git a/src/main/java/com/exasol/adapter/dialects/postgresql/PostgreSQLSqlDialect.java b/src/main/java/com/exasol/adapter/dialects/postgresql/PostgreSQLSqlDialect.java index 31b1d3919..dfdfaaa06 100644 --- a/src/main/java/com/exasol/adapter/dialects/postgresql/PostgreSQLSqlDialect.java +++ b/src/main/java/com/exasol/adapter/dialects/postgresql/PostgreSQLSqlDialect.java @@ -118,12 +118,13 @@ public StructureElementSupport supportsJdbcSchemas() { } @Override + // https://www.postgresql.org/docs/9.1/sql-syntax-lexical.html public String applyQuote(final String identifier) { String postgreSQLIdentifier = identifier; if (getIdentifierMapping() != PostgreSQLIdentifierMapping.PRESERVE_ORIGINAL_CASE) { postgreSQLIdentifier = convertIdentifierToLowerCase(postgreSQLIdentifier); } - return "\"" + postgreSQLIdentifier.replace("\"", "\"\"") + "\""; + return super.applyQuote(postgreSQLIdentifier); } private String convertIdentifierToLowerCase(final String identifier) { diff --git a/src/main/java/com/exasol/adapter/dialects/postgresql/PostgresSQLSqlGenerationVisitor.java b/src/main/java/com/exasol/adapter/dialects/postgresql/PostgresSQLSqlGenerationVisitor.java index 236ddf18b..5d9820ad1 100644 --- a/src/main/java/com/exasol/adapter/dialects/postgresql/PostgresSQLSqlGenerationVisitor.java +++ b/src/main/java/com/exasol/adapter/dialects/postgresql/PostgresSQLSqlGenerationVisitor.java @@ -274,18 +274,13 @@ public String visit(final SqlFunctionAggregateGroupConcat function) throws Adapt final StringBuilder builder = new StringBuilder(); builder.append("STRING_AGG"); builder.append("("); - if ((function.getArguments() != null) && (function.getArguments().size() == 1) - && (function.getArguments().get(0) != null)) { - final String expression = function.getArguments().get(0).accept(this); + if (function.getArgument() != null) { + final String expression = function.getArgument().accept(this); builder.append(expression); builder.append(", "); - String separator = ","; - if (function.getSeparator() != null) { - separator = function.getSeparator(); - } - builder.append("'"); + final String separator = function.hasSeparator() ? function.getSeparator().accept(this) : "','"; builder.append(separator); - builder.append("') "); + builder.append(") "); return builder.toString(); } else { throw new SqlGenerationVisitorException( diff --git a/src/main/java/com/exasol/adapter/dialects/redshift/RedshiftSqlDialect.java b/src/main/java/com/exasol/adapter/dialects/redshift/RedshiftSqlDialect.java index b5cb85f0f..5e5b3a3f3 100644 --- a/src/main/java/com/exasol/adapter/dialects/redshift/RedshiftSqlDialect.java +++ b/src/main/java/com/exasol/adapter/dialects/redshift/RedshiftSqlDialect.java @@ -93,8 +93,10 @@ public StructureElementSupport supportsJdbcSchemas() { } @Override + @SuppressWarnings("squid:S1185") + // https://docs.aws.amazon.com/redshift/latest/dg/r_names.html public String applyQuote(final String identifier) { - return "\"" + identifier.replace("\"", "\"\"") + "\""; + return super.applyQuote(identifier); } @Override diff --git a/src/main/java/com/exasol/adapter/dialects/redshift/RedshiftSqlGenerationVisitor.java b/src/main/java/com/exasol/adapter/dialects/redshift/RedshiftSqlGenerationVisitor.java index 5fdfc574f..3a7be9779 100644 --- a/src/main/java/com/exasol/adapter/dialects/redshift/RedshiftSqlGenerationVisitor.java +++ b/src/main/java/com/exasol/adapter/dialects/redshift/RedshiftSqlGenerationVisitor.java @@ -18,25 +18,18 @@ public class RedshiftSqlGenerationVisitor extends SqlGenerationVisitor { public RedshiftSqlGenerationVisitor(final SqlDialect dialect, final SqlGenerationContext context) { super(dialect, context); } - + @Override public String visit(final SqlFunctionAggregateGroupConcat function) throws AdapterException { final StringBuilder builder = new StringBuilder(); builder.append("LISTAGG"); builder.append("("); - assert(function.getArguments() != null); - assert(function.getArguments().size() == 1 && function.getArguments().get(0) != null); - final String expression = function.getArguments().get(0).accept(this); + final String expression = function.getArgument().accept(this); builder.append(expression); builder.append(", "); - String separator = ","; - if (function.getSeparator() != null) { - separator = function.getSeparator(); - } - builder.append("'"); + final String separator = function.hasSeparator() ? function.getSeparator().accept(this) : "','"; builder.append(separator); - builder.append("') "); - builder.append("WITHIN GROUP(ORDER BY "); + builder.append(") WITHIN GROUP(ORDER BY "); if (function.hasOrderBy()) { for (int i = 0; i < function.getOrderBy().getExpressions().size(); i++) { if (i > 0) { diff --git a/src/main/java/com/exasol/adapter/dialects/saphana/SapHanaSqlDialect.java b/src/main/java/com/exasol/adapter/dialects/saphana/SapHanaSqlDialect.java index d0eef7eb0..3cbf80ceb 100644 --- a/src/main/java/com/exasol/adapter/dialects/saphana/SapHanaSqlDialect.java +++ b/src/main/java/com/exasol/adapter/dialects/saphana/SapHanaSqlDialect.java @@ -7,6 +7,8 @@ import static com.exasol.adapter.capabilities.MainCapability.*; import static com.exasol.adapter.capabilities.PredicateCapability.*; import static com.exasol.adapter.capabilities.ScalarFunctionCapability.*; +import static com.exasol.adapter.capabilities.ScalarFunctionCapability.ST_INTERSECTION; +import static com.exasol.adapter.capabilities.ScalarFunctionCapability.ST_UNION; import java.sql.SQLException; import java.util.*; @@ -107,8 +109,10 @@ public boolean requiresSchemaQualifiedTableNames(final SqlGenerationContext cont } @Override + // http://sap.optimieren.de/hana/hana/html/_bsql_introduction.html + @SuppressWarnings("squid:S1185") public String applyQuote(final String identifier) { - return "\"" + identifier + "\""; + return super.applyQuote(identifier); } @Override diff --git a/src/main/java/com/exasol/adapter/dialects/sqlserver/SqlServerIdentifier.java b/src/main/java/com/exasol/adapter/dialects/sqlserver/SqlServerIdentifier.java new file mode 100644 index 000000000..34df3ab81 --- /dev/null +++ b/src/main/java/com/exasol/adapter/dialects/sqlserver/SqlServerIdentifier.java @@ -0,0 +1,62 @@ +package com.exasol.adapter.dialects.sqlserver; + +import java.util.Objects; + +import com.exasol.db.Identifier; + +/** + * Represents an identifier in the Sql Server database. + */ +public class SqlServerIdentifier implements Identifier { + private final String id; + + private SqlServerIdentifier(final String id) { + this.id = id; + } + + /** + * Get the quoted identifier as a {@link String}. + * + * @return quoted identifier + */ + @Override + public String quote() { + return "[" + this.id + "]"; + } + + /** + * Create a new {@link SqlServerIdentifier}. + * + * @param id the identifier as {@link String} + * @return new {@link SqlServerIdentifier} instance + */ + public static SqlServerIdentifier of(final String id) { + if (validate(id)) { + return new SqlServerIdentifier(id); + } else { + throw new AssertionError("E-ID-3: Unable to create identifier \"" + id // + + "\" because it contains illegal characters." // + + " For information about valid identifiers, please refer to" // + + " https://docs.microsoft.com/en-us/sql/relational-databases/databases/database-identifiers?redirectedfrom=MSDN&view=sql-server-ver15"); + } + } + + private static boolean validate(final String id) { + return !id.contains("[") && !id.contains("]"); + } + + @Override + public boolean equals(final Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + final SqlServerIdentifier that = (SqlServerIdentifier) o; + return Objects.equals(this.id, that.id); + } + + @Override + public int hashCode() { + return Objects.hash(this.id); + } +} \ No newline at end of file diff --git a/src/main/java/com/exasol/adapter/dialects/sqlserver/SqlServerSqlDialect.java b/src/main/java/com/exasol/adapter/dialects/sqlserver/SqlServerSqlDialect.java index 1321f2497..fa7936590 100644 --- a/src/main/java/com/exasol/adapter/dialects/sqlserver/SqlServerSqlDialect.java +++ b/src/main/java/com/exasol/adapter/dialects/sqlserver/SqlServerSqlDialect.java @@ -7,6 +7,8 @@ import static com.exasol.adapter.capabilities.MainCapability.*; import static com.exasol.adapter.capabilities.PredicateCapability.*; import static com.exasol.adapter.capabilities.ScalarFunctionCapability.*; +import static com.exasol.adapter.capabilities.ScalarFunctionCapability.ST_INTERSECTION; +import static com.exasol.adapter.capabilities.ScalarFunctionCapability.ST_UNION; import java.sql.SQLException; import java.util.*; @@ -113,7 +115,7 @@ public SqlNodeVisitor getSqlGenerationVisitor(final SqlGenerationContext @Override public String applyQuote(final String identifier) { - return "[" + identifier + "]"; + return SqlServerIdentifier.of(identifier).quote(); } @Override diff --git a/src/main/java/com/exasol/adapter/dialects/sybase/SybaseIdentifier.java b/src/main/java/com/exasol/adapter/dialects/sybase/SybaseIdentifier.java new file mode 100644 index 000000000..f1cbf21d5 --- /dev/null +++ b/src/main/java/com/exasol/adapter/dialects/sybase/SybaseIdentifier.java @@ -0,0 +1,76 @@ +package com.exasol.adapter.dialects.sybase; + +import java.util.Objects; +import java.util.Set; + +import com.exasol.db.Identifier; + +/** + * Represents an identifier in the Sybase database. + */ +public class SybaseIdentifier implements Identifier { + private static final Set ALLOWED_CHARS = Set.of(' ', '_', '@', '#', '$'); + private final String id; + + private SybaseIdentifier(final String id) { + this.id = id; + } + + /** + * Get the quoted identifier as a {@link String}. + * + * @return quoted identifier + */ + @Override + public String quote() { + return "[" + this.id + "]"; + } + + /** + * Create a new {@link SybaseIdentifier}. + * + * @param id the identifier as {@link String} + * @return new {@link SybaseIdentifier} instance + */ + public static SybaseIdentifier of(final String id) { + if (validate(id)) { + return new SybaseIdentifier(id); + } else { + throw new AssertionError("E-ID-3: Unable to create identifier \"" + id // + + "\" because it contains illegal characters." // + + " For information about valid identifiers, please refer to" // + + " http://infocenter.sybase.com/help/index.jsp?topic=/com.sybase.infocenter.dc36271.1600/doc/html/san1393050529478.html"); + } + } + + private static boolean validate(final String id) { + if ((id == null) || id.isEmpty()) { + return false; + } + for (int i = 0; i < id.length(); ++i) { + if (!validateCharacter(id.charAt(i))) { + return false; + } + } + return true; + } + + private static boolean validateCharacter(final char ch) { + return ALLOWED_CHARS.contains(ch) || Character.isLetter(ch) || Character.isDigit(ch); + } + + @Override + public boolean equals(final Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + final SybaseIdentifier that = (SybaseIdentifier) o; + return Objects.equals(this.id, that.id); + } + + @Override + public int hashCode() { + return Objects.hash(this.id); + } +} \ No newline at end of file diff --git a/src/main/java/com/exasol/adapter/dialects/sybase/SybaseSqlDialect.java b/src/main/java/com/exasol/adapter/dialects/sybase/SybaseSqlDialect.java index a6898403d..4f461dca2 100644 --- a/src/main/java/com/exasol/adapter/dialects/sybase/SybaseSqlDialect.java +++ b/src/main/java/com/exasol/adapter/dialects/sybase/SybaseSqlDialect.java @@ -7,6 +7,8 @@ import static com.exasol.adapter.capabilities.MainCapability.*; import static com.exasol.adapter.capabilities.PredicateCapability.*; import static com.exasol.adapter.capabilities.ScalarFunctionCapability.*; +import static com.exasol.adapter.capabilities.ScalarFunctionCapability.ST_INTERSECTION; +import static com.exasol.adapter.capabilities.ScalarFunctionCapability.ST_UNION; import java.sql.SQLException; import java.util.*; @@ -113,8 +115,9 @@ public SqlNodeVisitor getSqlGenerationVisitor(final SqlGenerationContext } @Override + // http://infocenter.sybase.com/help/index.jsp?topic=/com.sybase.infocenter.dc36271.1600/doc/html/san1393050529478.html public String applyQuote(final String identifier) { - return "[" + identifier + "]"; + return SybaseIdentifier.of(identifier).quote(); } @Override diff --git a/src/main/java/com/exasol/adapter/dialects/teradata/TeradataSqlDialect.java b/src/main/java/com/exasol/adapter/dialects/teradata/TeradataSqlDialect.java index e731f8e44..71100c2b8 100644 --- a/src/main/java/com/exasol/adapter/dialects/teradata/TeradataSqlDialect.java +++ b/src/main/java/com/exasol/adapter/dialects/teradata/TeradataSqlDialect.java @@ -95,8 +95,10 @@ public SqlNodeVisitor getSqlGenerationVisitor(final SqlGenerationContext } @Override + @SuppressWarnings("squid:S1185") + // https://docs.teradata.com/reader/3AkrVQlhjJMha4KRVJmm1w/G7jI6yuymIYaMNVKNlCSWQ public String applyQuote(final String identifier) { - return "\"" + identifier.replace("\"", "\"\"") + "\""; + return super.applyQuote(identifier); } @Override diff --git a/src/test/java/com/exasol/adapter/dialects/IntegrationTestConstants.java b/src/test/java/com/exasol/adapter/dialects/IntegrationTestConstants.java index 0768c5db0..46b944150 100644 --- a/src/test/java/com/exasol/adapter/dialects/IntegrationTestConstants.java +++ b/src/test/java/com/exasol/adapter/dialects/IntegrationTestConstants.java @@ -3,7 +3,7 @@ import java.nio.file.Path; public final class IntegrationTestConstants { - public static final String VIRTUAL_SCHEMAS_JAR_NAME_AND_VERSION = "virtual-schema-dist-6.0.0-bundle-4.0.4.jar"; + public static final String VIRTUAL_SCHEMAS_JAR_NAME_AND_VERSION = "virtual-schema-dist-6.1.0-bundle-4.0.4.jar"; public static final String EXASOL_DOCKER_IMAGE_REFERENCE = "exasol/docker-db:6.2.9-d1"; public static final Path PATH_TO_VIRTUAL_SCHEMAS_JAR = Path.of("target", VIRTUAL_SCHEMAS_JAR_NAME_AND_VERSION); public static final String SCHEMA_EXASOL = "SCHEMA_EXASOL"; @@ -16,4 +16,4 @@ public final class IntegrationTestConstants { private IntegrationTestConstants() { // intentionally left empty } -} \ No newline at end of file +} diff --git a/src/test/java/com/exasol/adapter/dialects/athena/AthenaIdentifierTest.java b/src/test/java/com/exasol/adapter/dialects/athena/AthenaIdentifierTest.java new file mode 100644 index 000000000..152c346b3 --- /dev/null +++ b/src/test/java/com/exasol/adapter/dialects/athena/AthenaIdentifierTest.java @@ -0,0 +1,29 @@ +package com.exasol.adapter.dialects.athena; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import nl.jqno.equalsverifier.EqualsVerifier; + +class AthenaIdentifierTest { + @ParameterizedTest + @ValueSource(strings = { "_myunderscoretable", "123columnone", "テスト", "таблица" }) + void testCreateValidIdentifier(final String identifier) { + assertDoesNotThrow(() -> AthenaIdentifier.of(identifier)); + } + + @ParameterizedTest + @ValueSource(strings = { "test.table", "test`table", "\" table123" }) + void testCreateInvalidIdentifier(final String identifier) { + assertThrows(AssertionError.class, () -> AthenaIdentifier.of(identifier)); + } + + @Test + void testEqualsAndHashContract() { + EqualsVerifier.simple().forClass(AthenaIdentifier.class).verify(); + } +} \ No newline at end of file diff --git a/src/test/java/com/exasol/adapter/dialects/athena/AthenaSqlDialectFactoryTest.java b/src/test/java/com/exasol/adapter/dialects/athena/AthenaSqlDialectFactoryTest.java index 58e3db2d1..b38e18964 100644 --- a/src/test/java/com/exasol/adapter/dialects/athena/AthenaSqlDialectFactoryTest.java +++ b/src/test/java/com/exasol/adapter/dialects/athena/AthenaSqlDialectFactoryTest.java @@ -1,15 +1,15 @@ package com.exasol.adapter.dialects.athena; +import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.instanceOf; -import static org.hamcrest.MatcherAssert.assertThat; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import com.exasol.adapter.AdapterProperties; -public class AthenaSqlDialectFactoryTest { +class AthenaSqlDialectFactoryTest { private AthenaSqlDialectFactory factory; @BeforeEach diff --git a/src/test/java/com/exasol/adapter/dialects/athena/AthenaSqlDialectTest.java b/src/test/java/com/exasol/adapter/dialects/athena/AthenaSqlDialectTest.java index 41ce04a35..ec1983122 100644 --- a/src/test/java/com/exasol/adapter/dialects/athena/AthenaSqlDialectTest.java +++ b/src/test/java/com/exasol/adapter/dialects/athena/AthenaSqlDialectTest.java @@ -9,6 +9,7 @@ import static com.exasol.reflect.ReflectionUtils.getMethodReturnViaReflection; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.*; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.Mockito.when; import java.sql.Connection; @@ -19,6 +20,7 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; +import org.junit.jupiter.params.provider.ValueSource; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; @@ -105,13 +107,24 @@ void testGetDefaultNullSorting() { assertThat(this.dialect.getDefaultNullSorting(), equalTo(NullSorting.NULLS_SORTED_AT_END)); } - @CsvSource({ "tableName, \"tableName\"", "table123, \"table123\"", "_table, `_table`", + @CsvSource({ "tableName, \"tableName\"", // + "table123, \"table123\"", // + "_table, `_table`", // "table_name, \"table_name\"" }) @ParameterizedTest void testApplyQuote(final String unquoted, final String quoted) { assertThat(this.dialect.applyQuote(unquoted), equalTo(quoted)); } + @ValueSource(strings = { "ab:'ab'", "a'b:'a''b'", "a''b:'a''''b'", "'ab':'''ab'''" }) + @ParameterizedTest + void testGetLiteralString(final String definition) { + final int colonPosition = definition.indexOf(':'); + final String original = definition.substring(0, colonPosition); + final String literal = definition.substring(colonPosition + 1); + assertThat(this.dialect.getStringLiteral(original), equalTo(literal)); + } + @Test void testMetadataReaderClass(@Mock final Connection connectionMock) throws SQLException { when(this.connectionFactoryMock.getConnection()).thenReturn(connectionMock); diff --git a/src/test/java/com/exasol/adapter/dialects/bigquery/BigQueryIdentifierTest.java b/src/test/java/com/exasol/adapter/dialects/bigquery/BigQueryIdentifierTest.java new file mode 100644 index 000000000..9ea64619b --- /dev/null +++ b/src/test/java/com/exasol/adapter/dialects/bigquery/BigQueryIdentifierTest.java @@ -0,0 +1,12 @@ +package com.exasol.adapter.dialects.bigquery; + +import org.junit.jupiter.api.Test; + +import nl.jqno.equalsverifier.EqualsVerifier; + +class BigQueryIdentifierTest { + @Test + void testEqualsAndHashContract() { + EqualsVerifier.simple().forClass(BigQueryIdentifier.class).verify(); + } +} \ No newline at end of file diff --git a/src/test/java/com/exasol/adapter/dialects/bigquery/BigQueryQueryRewriterTest.java b/src/test/java/com/exasol/adapter/dialects/bigquery/BigQueryQueryRewriterTest.java index 10a7a10c7..e55f2ae30 100644 --- a/src/test/java/com/exasol/adapter/dialects/bigquery/BigQueryQueryRewriterTest.java +++ b/src/test/java/com/exasol/adapter/dialects/bigquery/BigQueryQueryRewriterTest.java @@ -90,7 +90,7 @@ void testRewriteWithBoolean() throws AdapterException, SQLException { } @CsvSource({ "string_col, 12, hello, hello", // - "string_col, 12, i'm, i''m", // + "string_col, 12, i'm, i\\'m", // "time_col, 92, 12:10:09.000, 12:10:09.000", // "numeric_col, 2, 22222.2222, 22222.2222", // "numeric_col, 2, 11.5, 11.5", // diff --git a/src/test/java/com/exasol/adapter/dialects/bigquery/BigQuerySqlDialectFactoryTest.java b/src/test/java/com/exasol/adapter/dialects/bigquery/BigQuerySqlDialectFactoryTest.java index 446824e1c..5dc4625a7 100644 --- a/src/test/java/com/exasol/adapter/dialects/bigquery/BigQuerySqlDialectFactoryTest.java +++ b/src/test/java/com/exasol/adapter/dialects/bigquery/BigQuerySqlDialectFactoryTest.java @@ -1,15 +1,15 @@ package com.exasol.adapter.dialects.bigquery; +import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.instanceOf; -import static org.hamcrest.MatcherAssert.assertThat; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import com.exasol.adapter.AdapterProperties; -public class BigQuerySqlDialectFactoryTest { +class BigQuerySqlDialectFactoryTest { private BigQuerySqlDialectFactory factory; @BeforeEach diff --git a/src/test/java/com/exasol/adapter/dialects/bigquery/BigQuerySqlDialectTest.java b/src/test/java/com/exasol/adapter/dialects/bigquery/BigQuerySqlDialectTest.java index a757d6d05..9f47e0798 100644 --- a/src/test/java/com/exasol/adapter/dialects/bigquery/BigQuerySqlDialectTest.java +++ b/src/test/java/com/exasol/adapter/dialects/bigquery/BigQuerySqlDialectTest.java @@ -6,6 +6,8 @@ import static com.exasol.adapter.capabilities.MainCapability.*; import static com.exasol.adapter.capabilities.PredicateCapability.*; import static com.exasol.adapter.capabilities.ScalarFunctionCapability.*; +import static com.exasol.adapter.capabilities.ScalarFunctionCapability.ST_INTERSECTION; +import static com.exasol.adapter.capabilities.ScalarFunctionCapability.ST_UNION; import static com.exasol.adapter.dialects.bigquery.BigQueryProperties.BIGQUERY_ENABLE_IMPORT_PROPERTY; import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.CoreMatchers.instanceOf; @@ -18,9 +20,14 @@ import java.sql.SQLException; import java.util.Map; +import com.exasol.adapter.dialects.athena.AthenaIdentifier; +import nl.jqno.equalsverifier.EqualsVerifier; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; +import org.junit.jupiter.params.provider.ValueSource; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.junit.jupiter.MockitoExtension; @@ -122,9 +129,23 @@ void testGetSupportedProperties() { DEBUG_ADDRESS_PROPERTY, LOG_LEVEL_PROPERTY, BIGQUERY_ENABLE_IMPORT_PROPERTY)); } - @Test - void testApplyQuote() { - assertThat(this.dialect.applyQuote("tableName"), equalTo("`tableName`")); + @CsvSource({ "5Customers, `5Customers`", // + "_dataField1, `_dataField1`", // + "tableName, `tableName`", // + "\" table123 `, `\" table123 \\``" // + }) + @ParameterizedTest + void testApplyQuote(final String unquoted, final String quoted) { + assertThat(this.dialect.applyQuote(unquoted), equalTo(quoted)); + } + + @ValueSource(strings = { "ab:'ab'", "a'b:'a\\'b'", "a''b:'a\\'\\'b'", "'ab':'\\'ab\\''" }) + @ParameterizedTest + void testGetLiteralString(final String definition) { + final int colonPosition = definition.indexOf(':'); + final String original = definition.substring(0, colonPosition); + final String literal = definition.substring(colonPosition + 1); + assertThat(this.dialect.getStringLiteral(original), equalTo(literal)); } @Test diff --git a/src/test/java/com/exasol/adapter/dialects/db2/DB2SqlDialectFactoryTest.java b/src/test/java/com/exasol/adapter/dialects/db2/DB2SqlDialectFactoryTest.java index e35f3c0e0..d5b28ad6a 100644 --- a/src/test/java/com/exasol/adapter/dialects/db2/DB2SqlDialectFactoryTest.java +++ b/src/test/java/com/exasol/adapter/dialects/db2/DB2SqlDialectFactoryTest.java @@ -1,15 +1,15 @@ package com.exasol.adapter.dialects.db2; +import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.instanceOf; -import static org.hamcrest.MatcherAssert.assertThat; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import com.exasol.adapter.AdapterProperties; -public class DB2SqlDialectFactoryTest { +class DB2SqlDialectFactoryTest { private DB2SqlDialectFactory factory; @BeforeEach diff --git a/src/test/java/com/exasol/adapter/dialects/db2/DB2SqlDialectTest.java b/src/test/java/com/exasol/adapter/dialects/db2/DB2SqlDialectTest.java index 3aa3278ea..d5df20de0 100644 --- a/src/test/java/com/exasol/adapter/dialects/db2/DB2SqlDialectTest.java +++ b/src/test/java/com/exasol/adapter/dialects/db2/DB2SqlDialectTest.java @@ -16,8 +16,6 @@ import static org.junit.jupiter.api.Assertions.assertAll; import static org.junit.jupiter.api.Assertions.assertThrows; -import java.sql.Connection; -import java.sql.SQLException; import java.util.HashMap; import java.util.Map; @@ -26,6 +24,9 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; +import org.junit.jupiter.params.provider.ValueSource; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; @@ -38,12 +39,10 @@ @ExtendWith(MockitoExtension.class) class DB2SqlDialectTest { private SqlDialect dialect; - @Mock - private Connection connectionMock; private Map rawProperties; @BeforeEach - void beforeEach(@Mock final ConnectionFactory connectionFactoryMock) throws SQLException { + void beforeEach(@Mock final ConnectionFactory connectionFactoryMock) { this.rawProperties = new HashMap<>(); this.dialect = new DB2SqlDialect(connectionFactoryMock, AdapterProperties.emptyProperties()); } @@ -85,7 +84,7 @@ void testMetadataReaderClass() { @Test void testValidateCatalogProperty() { - setMandatoryProperties("DB2"); + setMandatoryProperties(); this.rawProperties.put(CATALOG_NAME_PROPERTY, "MY_CATALOG"); final AdapterProperties adapterProperties = new AdapterProperties(this.rawProperties); final SqlDialect sqlDialect = new DB2SqlDialect(null, adapterProperties); @@ -95,14 +94,14 @@ void testValidateCatalogProperty() { "The dialect DB2 does not support CATALOG_NAME property. Please, do not set the \"CATALOG_NAME\" property.")); } - private void setMandatoryProperties(final String sqlDialectProperty) { - this.rawProperties.put(AdapterProperties.SQL_DIALECT_PROPERTY, sqlDialectProperty); + private void setMandatoryProperties() { + this.rawProperties.put(AdapterProperties.SQL_DIALECT_PROPERTY, "DB2"); this.rawProperties.put(AdapterProperties.CONNECTION_NAME_PROPERTY, "MY_CONN"); } @Test void testValidateSchemaProperty() throws PropertyValidationException { - setMandatoryProperties("DB2"); + setMandatoryProperties(); this.rawProperties.put(SCHEMA_NAME_PROPERTY, "MY_SCHEMA"); final AdapterProperties adapterProperties = new AdapterProperties(this.rawProperties); final SqlDialect sqlDialect = new DB2SqlDialect(null, adapterProperties); @@ -119,9 +118,21 @@ void testSupportsJdbcSchemas() { assertThat(this.dialect.supportsJdbcSchemas(), equalTo(SqlDialect.StructureElementSupport.MULTIPLE)); } - @Test - void testApplyQuote() { - assertThat(this.dialect.applyQuote("tableName"), equalTo("\"tableName\"")); + @CsvSource({ "tableName, \"tableName\"", // + "\"tableName, \"\"\"tableName\"" // + }) + @ParameterizedTest + void testApplyQuote(final String unquoted, final String quoted) { + assertThat(this.dialect.applyQuote(unquoted), equalTo(quoted)); + } + + @ValueSource(strings = { "ab:'ab'", "a'b:'a''b'", "a''b:'a''''b'", "'ab':'''ab'''" }) + @ParameterizedTest + void testGetLiteralString(final String definition) { + final int colonPosition = definition.indexOf(':'); + final String original = definition.substring(0, colonPosition); + final String literal = definition.substring(colonPosition + 1); + assertThat(this.dialect.getStringLiteral(original), equalTo(literal)); } @Test @@ -143,4 +154,4 @@ void testGetSqlGenerationVisitor() { void testGetDefaultNullSorting() { assertThat(this.dialect.getDefaultNullSorting(), equalTo(SqlDialect.NullSorting.NULLS_SORTED_AT_END)); } -} +} \ No newline at end of file diff --git a/src/test/java/com/exasol/adapter/dialects/db2/DB2SqlGenerationVisitorTest.java b/src/test/java/com/exasol/adapter/dialects/db2/DB2SqlGenerationVisitorTest.java index b6ef9b1ed..1b387d173 100644 --- a/src/test/java/com/exasol/adapter/dialects/db2/DB2SqlGenerationVisitorTest.java +++ b/src/test/java/com/exasol/adapter/dialects/db2/DB2SqlGenerationVisitorTest.java @@ -2,13 +2,13 @@ import static com.exasol.adapter.dialects.VisitorAssertions.assertSqlNodeConvertedToAsterisk; import static com.exasol.adapter.dialects.VisitorAssertions.assertSqlNodeConvertedToOne; +import static com.exasol.adapter.sql.AggregateFunction.AVG; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.jupiter.api.Assertions.assertThrows; -import static utils.SqlNodesCreator.*; -import java.util.ArrayList; -import java.util.List; +import java.math.BigDecimal; +import java.util.*; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -22,13 +22,12 @@ import com.exasol.adapter.AdapterProperties; import com.exasol.adapter.dialects.*; import com.exasol.adapter.jdbc.ConnectionFactory; -import com.exasol.adapter.metadata.ColumnMetadata; -import com.exasol.adapter.metadata.DataType; +import com.exasol.adapter.metadata.*; import com.exasol.adapter.sql.*; @ExtendWith(MockitoExtension.class) class DB2SqlGenerationVisitorTest { - private SqlNodeVisitor visitor; + private SqlGenerationVisitor visitor; @BeforeEach void beforeEach(@Mock final ConnectionFactory connectionFactoryMock) { @@ -96,23 +95,39 @@ void testVisitSqlSelectListAnyValue() throws AdapterException { @Test void testVisitSqlSelectListSelectStar() throws AdapterException { - final SqlSelectList sqlSelectList = createSqlSelectStarListWithoutColumns(); + final SqlSelectList sqlSelectList = SqlSelectList.createSelectStarSelectList(); + final TableMetadata tableMetadata = new TableMetadata("", "", Collections.emptyList(), ""); + final SqlTable fromClause = new SqlTable("", tableMetadata); + final SqlNode sqlStatementSelect = SqlStatementSelect.builder().selectList(sqlSelectList).fromClause(fromClause) + .build(); + sqlSelectList.setParent(sqlStatementSelect); assertSqlNodeConvertedToAsterisk(sqlSelectList, this.visitor); } @Test void testVisitSqlSelectListSelectStarRequiresCast() throws AdapterException { final SqlSelectList sqlSelectList = createSqlSelectStarListWithOneColumn( - "{\"jdbcDataType\":2009, \"typeName\":\"XML\"}", DataType.createVarChar(10, DataType.ExaCharset.UTF8), - "test_column"); + "{\"jdbcDataType\":2009, \"typeName\":\"XML\"}", DataType.createVarChar(10, DataType.ExaCharset.UTF8)); assertThat(this.visitor.visit(sqlSelectList), equalTo("XMLSERIALIZE(\"test_column\" as VARCHAR(32000) INCLUDING XMLDECLARATION)")); } + private SqlSelectList createSqlSelectStarListWithOneColumn(final String adapterNotes, final DataType dataType) { + final SqlSelectList selectList = SqlSelectList.createSelectStarSelectList(); + final List columns = new ArrayList<>(); + columns.add(ColumnMetadata.builder().name("test_column").adapterNotes(adapterNotes).type(dataType).build()); + final TableMetadata tableMetadata = new TableMetadata("", "", columns, ""); + final SqlTable fromClause = new SqlTable("", tableMetadata); + final SqlNode sqlStatementSelect = SqlStatementSelect.builder().selectList(selectList).fromClause(fromClause) + .build(); + selectList.setParent(sqlStatementSelect); + return selectList; + } + @Test void testVisitSqlSelectListSelectStarThrowsException() { final SqlSelectList sqlSelectList = createSqlSelectStarListWithOneColumn("", - DataType.createVarChar(10, DataType.ExaCharset.UTF8), "test_column"); + DataType.createVarChar(10, DataType.ExaCharset.UTF8)); assertThrows(SqlGenerationVisitorException.class, () -> this.visitor.visit(sqlSelectList)); } @@ -126,8 +141,8 @@ void testVisitSqlFunctionScalarTrimOneArgument() throws AdapterException { @Test void testVisitSqlFunctionScalarTrimOTwoArguments() throws AdapterException { - final SqlFunctionScalar sqlFunctionScalar = createSqlFunctionScalarWithTwoStringArguments(ScalarFunction.TRIM, - "ab cdef", "ab"); + final List arguments = List.of(new SqlLiteralString("ab cdef"), new SqlLiteralString("ab")); + final SqlFunctionScalar sqlFunctionScalar = new SqlFunctionScalar(ScalarFunction.TRIM, arguments); assertThat(this.visitor.visit(sqlFunctionScalar), equalTo("TRIM('ab' FROM 'ab cdef')")); } @@ -140,7 +155,12 @@ void testVisitSqlFunctionScalarTrimOTwoArguments() throws AdapterException { @ParameterizedTest void testVisitSqlFunctionScalarAddDateValues(final ScalarFunction scalarFunction, final int value, final String expected) throws AdapterException { - final SqlFunctionScalar sqlFunctionScalar = createSqlFunctionScalarForDateTest(scalarFunction, value); + final SqlColumn firstArgument = new SqlColumn(1, + ColumnMetadata.builder().name("test_column") + .adapterNotes("{\"jdbcDataType\":93, \"typeName\":\"TIMESTAMP\"}") + .type(DataType.createChar(20, DataType.ExaCharset.UTF8)).build()); + final List arguments = List.of(firstArgument, new SqlLiteralExactnumeric(new BigDecimal(value))); + final SqlFunctionScalar sqlFunctionScalar = new SqlFunctionScalar(scalarFunction, arguments); assertThat(this.visitor.visit(sqlFunctionScalar), equalTo("VARCHAR(\"test_column\" + " + expected + ")")); } @@ -168,22 +188,25 @@ void testVisitSqlFunctionScalar1(final ScalarFunction scalarFunction, final Stri @ParameterizedTest void testVisitSqlFunctionScalar2(final ScalarFunction scalarFunction, final String expected) throws AdapterException { - final SqlFunctionScalar sqlFunctionScalar = createSqlFunctionScalarWithTwoStringArguments(scalarFunction, - "left", "right"); + final List arguments = List.of(new SqlLiteralString("left"), new SqlLiteralString("right")); + final SqlFunctionScalar sqlFunctionScalar = new SqlFunctionScalar(scalarFunction, arguments); assertThat(this.visitor.visit(sqlFunctionScalar), equalTo(expected)); } @Test void testVisitSqlFunctionScalarDiv() throws AdapterException { - final SqlFunctionScalar sqlFunctionScalar = createSqlFunctionScalarWithTwoStringArguments(ScalarFunction.DIV, - "left", "right"); + final List arguments = List.of(new SqlLiteralString("left"), new SqlLiteralString("right")); + final SqlFunctionScalar sqlFunctionScalar = new SqlFunctionScalar(ScalarFunction.DIV, arguments); assertThat(this.visitor.visit(sqlFunctionScalar), equalTo("CAST(FLOOR('left' / FLOOR('right')) AS DECIMAL(36, 0))")); } @Test void testVisitSqlFunctionAggregate() throws AdapterException { - final SqlFunctionAggregate sqlFunctionAggregate = createSqlFunctionAggregate(); + final ColumnMetadata columnMetadata = ColumnMetadata.builder().name("test_column").type(DataType.createBool()) + .build(); + final List arguments = List.of(new SqlColumn(1, columnMetadata)); + final SqlFunctionAggregate sqlFunctionAggregate = new SqlFunctionAggregate(AVG, arguments, true); assertThat(this.visitor.visit(sqlFunctionAggregate), equalTo("AVG(DISTINCT \"test_column\")")); } @@ -198,12 +221,17 @@ void testVisitSqlFunctionAggregateVarSamp() throws AdapterException { @Test void testVisitSqlFunctionAggregateGroupConcat() throws AdapterException { - final List arguments = new ArrayList<>(); - arguments.add(new SqlLiteralString("test")); - final SqlOrderBy orderBy = createSqlOrderByDescNullsFirst("test_column", "test_column2"); - final SqlFunctionAggregateGroupConcat sqlFunctionAggregateGroupConcat = new SqlFunctionAggregateGroupConcat( - AggregateFunction.AVG, arguments, orderBy, false, "'"); + final SqlLiteralString argument = new SqlLiteralString("test"); + final ColumnMetadata columnMetadata = ColumnMetadata.builder().name("\"test_column").type(DataType.createBool()) + .build(); + final ColumnMetadata columnMetadata2 = ColumnMetadata.builder().name("test_column2\"") + .type(DataType.createDouble()).build(); + final List orderByArguments = List.of(new SqlColumn(1, columnMetadata), + new SqlColumn(2, columnMetadata2)); + final SqlOrderBy orderBy = new SqlOrderBy(orderByArguments, List.of(false, true), List.of(false, true)); + final SqlFunctionAggregateGroupConcat sqlFunctionAggregateGroupConcat = SqlFunctionAggregateGroupConcat + .builder(argument).orderBy(orderBy).separator(new SqlLiteralString("'")).build(); assertThat(this.visitor.visit(sqlFunctionAggregateGroupConcat), - equalTo("LISTAGG('test', ''') WITHIN GROUP(ORDER BY \"test_column\" DESC, \"test_column2\")")); + equalTo("LISTAGG('test', '''') WITHIN GROUP(ORDER BY \"\"\"test_column\" DESC, \"test_column2\"\"\")")); } } \ No newline at end of file diff --git a/src/test/java/com/exasol/adapter/dialects/generic/GenericSqlDialectFactoryTest.java b/src/test/java/com/exasol/adapter/dialects/generic/GenericSqlDialectFactoryTest.java index 66c3ef964..577bf0c95 100644 --- a/src/test/java/com/exasol/adapter/dialects/generic/GenericSqlDialectFactoryTest.java +++ b/src/test/java/com/exasol/adapter/dialects/generic/GenericSqlDialectFactoryTest.java @@ -1,8 +1,8 @@ package com.exasol.adapter.dialects.generic; +import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.instanceOf; -import static org.hamcrest.MatcherAssert.assertThat; import static org.mockito.Mockito.when; import java.sql.*; @@ -17,11 +17,11 @@ import com.exasol.adapter.jdbc.ConnectionFactory; @ExtendWith(MockitoExtension.class) -public class GenericSqlDialectFactoryTest { +class GenericSqlDialectFactoryTest { private GenericSqlDialectFactory factory; @BeforeEach - void beforeEach() throws SQLException { + void beforeEach() { this.factory = new GenericSqlDialectFactory(); } diff --git a/src/test/java/com/exasol/adapter/dialects/hive/HiveMetadataReaderTest.java b/src/test/java/com/exasol/adapter/dialects/hive/HiveMetadataReaderTest.java index 5c49d9e85..1a27e9daf 100644 --- a/src/test/java/com/exasol/adapter/dialects/hive/HiveMetadataReaderTest.java +++ b/src/test/java/com/exasol/adapter/dialects/hive/HiveMetadataReaderTest.java @@ -11,7 +11,7 @@ import com.exasol.adapter.dialects.IdentifierCaseHandling; import com.exasol.adapter.dialects.IdentifierConverter; -public class HiveMetadataReaderTest { +class HiveMetadataReaderTest { private HiveMetadataReader hiveMetadataReader; @BeforeEach diff --git a/src/test/java/com/exasol/adapter/dialects/hive/HiveSqlDialectFactoryTest.java b/src/test/java/com/exasol/adapter/dialects/hive/HiveSqlDialectFactoryTest.java index cf3c52440..6c89c768e 100644 --- a/src/test/java/com/exasol/adapter/dialects/hive/HiveSqlDialectFactoryTest.java +++ b/src/test/java/com/exasol/adapter/dialects/hive/HiveSqlDialectFactoryTest.java @@ -9,7 +9,7 @@ import com.exasol.adapter.AdapterProperties; -public class HiveSqlDialectFactoryTest { +class HiveSqlDialectFactoryTest { private HiveSqlDialectFactory factory; @BeforeEach diff --git a/src/test/java/com/exasol/adapter/dialects/hive/HiveSqlDialectTest.java b/src/test/java/com/exasol/adapter/dialects/hive/HiveSqlDialectTest.java index fe367c6e8..6f4d8cbc0 100644 --- a/src/test/java/com/exasol/adapter/dialects/hive/HiveSqlDialectTest.java +++ b/src/test/java/com/exasol/adapter/dialects/hive/HiveSqlDialectTest.java @@ -7,6 +7,7 @@ import static com.exasol.adapter.capabilities.MainCapability.*; import static com.exasol.adapter.capabilities.PredicateCapability.*; import static com.exasol.adapter.capabilities.ScalarFunctionCapability.*; +import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.collection.IsIterableContainingInAnyOrder.containsInAnyOrder; import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.jupiter.api.Assertions.assertAll; @@ -21,6 +22,8 @@ import com.exasol.adapter.capabilities.Capabilities; import com.exasol.adapter.dialects.PropertyValidationException; import com.exasol.adapter.dialects.SqlDialect; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; class HiveSqlDialectTest { private SqlDialect dialect; @@ -77,8 +80,16 @@ void testValidateSchemaProperty() throws PropertyValidationException { sqlDialect.validateProperties(); } + @CsvSource({ "tableName, `tableName`", // + "`tableName, ```tableName`" // + }) + @ParameterizedTest + void testApplyQuote(final String unquoted, final String quoted) { + assertThat(this.dialect.applyQuote(unquoted), equalTo(quoted)); + } + private void setMandatoryProperties(final String sqlDialectProperty) { this.rawProperties.put(AdapterProperties.SQL_DIALECT_PROPERTY, sqlDialectProperty); this.rawProperties.put(AdapterProperties.CONNECTION_NAME_PROPERTY, "MY_CONN"); } -} +} \ No newline at end of file diff --git a/src/test/java/com/exasol/adapter/dialects/hive/HiveSqlGenerationVisitorTest.java b/src/test/java/com/exasol/adapter/dialects/hive/HiveSqlGenerationVisitorTest.java index a4160e4a1..cd86bb00e 100644 --- a/src/test/java/com/exasol/adapter/dialects/hive/HiveSqlGenerationVisitorTest.java +++ b/src/test/java/com/exasol/adapter/dialects/hive/HiveSqlGenerationVisitorTest.java @@ -5,10 +5,10 @@ import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.jupiter.api.Assertions.assertThrows; -import static utils.SqlNodesCreator.*; import java.util.*; +import com.exasol.adapter.metadata.*; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -21,12 +21,8 @@ import com.exasol.adapter.AdapterProperties; import com.exasol.adapter.dialects.*; import com.exasol.adapter.jdbc.ConnectionFactory; -import com.exasol.adapter.metadata.ColumnMetadata; -import com.exasol.adapter.metadata.DataType; import com.exasol.adapter.sql.*; -import utils.SqlNodesCreator; - @ExtendWith(MockitoExtension.class) class HiveSqlGenerationVisitorTest { private SqlNodeVisitor visitor; @@ -42,7 +38,12 @@ void beforeEach(@Mock final ConnectionFactory connectionFactoryMock) { @Test void testVisitSqlSelectListSelectStar() throws AdapterException { - final SqlSelectList sqlSelectList = createSqlSelectStarListWithoutColumns(); + final SqlSelectList sqlSelectList = SqlSelectList.createSelectStarSelectList(); + final TableMetadata tableMetadata = new TableMetadata("", "", Collections.emptyList(), ""); + final SqlTable fromClause = new SqlTable("test_table", tableMetadata); + final SqlNode sqlStatementSelect = SqlStatementSelect.builder().selectList(sqlSelectList).fromClause(fromClause) + .build(); + sqlSelectList.setParent(sqlStatementSelect); assertSqlNodeConvertedToAsterisk(sqlSelectList, this.visitor); } @@ -53,7 +54,9 @@ void testVisitSqlSelectListSelectStarRequiresCastBinary() throws AdapterExceptio columns.add(ColumnMetadata.builder().name("test_column") .adapterNotes("{\"jdbcDataType\":-2, \"typeName\":\"BINARY\"}") .type(DataType.createVarChar(10, DataType.ExaCharset.UTF8)).build()); - final SqlNode select = SqlNodesCreator.createSqlStatementSelect(sqlSelectList, columns, ""); + final TableMetadata tableMetadata = new TableMetadata("", "", columns, ""); + final SqlTable fromClause = new SqlTable("", tableMetadata); + final SqlNode select = SqlStatementSelect.builder().selectList(sqlSelectList).fromClause(fromClause).build(); sqlSelectList.setParent(select); assertThat(this.visitor.visit(sqlSelectList), equalTo("base64(`test_column`)")); } @@ -66,7 +69,8 @@ void testVisitSqlSelectListRequiresAnyColumn() throws AdapterException { @Test void testVisitSqlSelectListSelectRegularList() throws AdapterException { - final SqlSelectList sqlSelectList = SqlNodesCreator.createRegularSqlSelectListWithTwoColumns(); + final SqlSelectList sqlSelectList = SqlSelectList + .createRegularSelectList(Arrays.asList(new SqlLiteralBool(true), new SqlLiteralString("string"))); assertThat(this.visitor.visit(sqlSelectList), equalTo("true, 'string'")); } @@ -90,6 +94,19 @@ void testVisitSqlSelectListSelectStarThrowsException() { assertThrows(SqlGenerationVisitorException.class, () -> this.visitor.visit(sqlSelectList)); } + private SqlSelectList createSqlSelectStarListWithOneColumn(final String adapterNotes, final DataType dataType, + final String columnName) { + final SqlSelectList selectList = SqlSelectList.createSelectStarSelectList(); + final List columns = new ArrayList<>(); + columns.add(ColumnMetadata.builder().name(columnName).adapterNotes(adapterNotes).type(dataType).build()); + final TableMetadata tableMetadata = new TableMetadata("", "", columns, ""); + final SqlTable fromClause = new SqlTable("", tableMetadata); + final SqlNode sqlStatementSelect = SqlStatementSelect.builder().selectList(selectList).fromClause(fromClause) + .build(); + selectList.setParent(sqlStatementSelect); + return selectList; + } + @Test void testVisitSqlPredicateEqual() throws AdapterException { final SqlPredicateEqual sqlPredicateEqual = new SqlPredicateEqual(new SqlLiteralBool(true), @@ -178,8 +195,10 @@ void testVisitSqlFunctionScalarSubstring() throws AdapterException { @Test void testVisitSqlFunctionScalarSubstringWithFrom() throws AdapterException { - final SqlFunctionScalar sqlFunctionScalar = createSqlFunctionScalarWithTwoStringArguments(ScalarFunction.SUBSTR, - "string", "FROM 4 FOR 2"); + final List arguments = new ArrayList<>(); + arguments.add(new SqlLiteralString("string")); + arguments.add(new SqlLiteralString("FROM 4 FOR 2")); + final SqlFunctionScalar sqlFunctionScalar = new SqlFunctionScalar(ScalarFunction.SUBSTR, arguments); assertThat(this.visitor.visit(sqlFunctionScalar), equalTo("SUBSTRING('string','FROM 4 FOR 2')")); } diff --git a/src/test/java/com/exasol/adapter/dialects/impala/ImpalaSqlDialectFactoryTest.java b/src/test/java/com/exasol/adapter/dialects/impala/ImpalaSqlDialectFactoryTest.java index fba78d86b..b4c81c0eb 100644 --- a/src/test/java/com/exasol/adapter/dialects/impala/ImpalaSqlDialectFactoryTest.java +++ b/src/test/java/com/exasol/adapter/dialects/impala/ImpalaSqlDialectFactoryTest.java @@ -1,15 +1,15 @@ package com.exasol.adapter.dialects.impala; +import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.instanceOf; -import static org.hamcrest.MatcherAssert.assertThat; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import com.exasol.adapter.AdapterProperties; -public class ImpalaSqlDialectFactoryTest { +class ImpalaSqlDialectFactoryTest { private ImpalaSqlDialectFactory factory; @BeforeEach diff --git a/src/test/java/com/exasol/adapter/dialects/impala/ImpalaSqlDialectTest.java b/src/test/java/com/exasol/adapter/dialects/impala/ImpalaSqlDialectTest.java index 736379bec..ea8064fce 100644 --- a/src/test/java/com/exasol/adapter/dialects/impala/ImpalaSqlDialectTest.java +++ b/src/test/java/com/exasol/adapter/dialects/impala/ImpalaSqlDialectTest.java @@ -25,6 +25,9 @@ import com.exasol.adapter.dialects.PropertyValidationException; import com.exasol.adapter.dialects.SqlDialect; import com.exasol.adapter.sql.ScalarFunction; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; +import org.junit.jupiter.params.provider.ValueSource; class ImpalaSqlDialectTest { private SqlDialect dialect; @@ -100,7 +103,7 @@ void testGetScalarFunctionAliases() { @Test void testValidateCatalogProperty() throws PropertyValidationException { - setMandatoryProperties("IMPALA"); + setMandatoryProperties(); this.rawProperties.put(CATALOG_NAME_PROPERTY, "MY_CATALOG"); final AdapterProperties adapterProperties = new AdapterProperties(this.rawProperties); final SqlDialect sqlDialect = new ImpalaSqlDialect(null, adapterProperties); @@ -109,16 +112,28 @@ void testValidateCatalogProperty() throws PropertyValidationException { @Test void testValidateSchemaProperty() throws PropertyValidationException { - setMandatoryProperties("IMPALA"); + setMandatoryProperties(); this.rawProperties.put(SCHEMA_NAME_PROPERTY, "MY_SCHEMA"); final AdapterProperties adapterProperties = new AdapterProperties(this.rawProperties); final SqlDialect sqlDialect = new ImpalaSqlDialect(null, adapterProperties); sqlDialect.validateProperties(); } - @Test - void testApplyQuote() { - assertThat(this.dialect.applyQuote("tableName"), equalTo("`tableName`")); + @CsvSource({ "tableName, `tableName`", // + "`tableName, ```tableName`" // + }) + @ParameterizedTest + void testApplyQuote(final String unquoted, final String quoted) { + assertThat(this.dialect.applyQuote(unquoted), equalTo(quoted)); + } + + @ValueSource(strings = { "ab:'ab'", "a'b:'a\\'b'", "a''b:'a\\'\\'b'", "'ab':'\\'ab\\''" }) + @ParameterizedTest + void testGetLiteralString(final String definition) { + final int colonPosition = definition.indexOf(':'); + final String original = definition.substring(0, colonPosition); + final String literal = definition.substring(colonPosition + 1); + assertThat(this.dialect.getStringLiteral(original), Matchers.equalTo(literal)); } @Test @@ -141,8 +156,8 @@ void testGetSqlGenerationVisitor() { assertThat(this.dialect.getSqlGenerationVisitor(null), instanceOf(ImpalaSqlGenerationVisitor.class)); } - private void setMandatoryProperties(final String sqlDialectProperty) { - this.rawProperties.put(AdapterProperties.SQL_DIALECT_PROPERTY, sqlDialectProperty); + private void setMandatoryProperties() { + this.rawProperties.put(AdapterProperties.SQL_DIALECT_PROPERTY, "IMPALA"); this.rawProperties.put(AdapterProperties.CONNECTION_NAME_PROPERTY, "MY_CONN"); } } \ No newline at end of file diff --git a/src/test/java/com/exasol/adapter/dialects/impala/ImpalaSqlGenerationVisitorTest.java b/src/test/java/com/exasol/adapter/dialects/impala/ImpalaSqlGenerationVisitorTest.java new file mode 100644 index 000000000..cf0c57e5b --- /dev/null +++ b/src/test/java/com/exasol/adapter/dialects/impala/ImpalaSqlGenerationVisitorTest.java @@ -0,0 +1,35 @@ +package com.exasol.adapter.dialects.impala; + +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.MatcherAssert.assertThat; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import com.exasol.adapter.AdapterException; +import com.exasol.adapter.AdapterProperties; +import com.exasol.adapter.dialects.SqlDialect; +import com.exasol.adapter.dialects.SqlGenerationContext; +import com.exasol.adapter.sql.SqlFunctionAggregateGroupConcat; +import com.exasol.adapter.sql.SqlLiteralString; + +class ImpalaSqlGenerationVisitorTest { + private ImpalaSqlGenerationVisitor visitor; + + @BeforeEach + void beforeEach() { + final SqlDialect dialect = new ImpalaSqlDialectFactory().createSqlDialect(null, + AdapterProperties.emptyProperties()); + final SqlGenerationContext context = new SqlGenerationContext("test_catalog", "test_schema", false); + this.visitor = new ImpalaSqlGenerationVisitor(dialect, context); + } + + @Test + void visitSqlFunctionAggregateGroupConcat() throws AdapterException { + final SqlLiteralString argument = new SqlLiteralString("value'"); + final SqlFunctionAggregateGroupConcat aggregateGroupConcat = SqlFunctionAggregateGroupConcat.builder(argument) + .separator(new SqlLiteralString("'")).build(); + assertThat(this.visitor.visit(aggregateGroupConcat), + equalTo("GROUP_CONCAT(CAST('value\\'' AS STRING), '\\'')")); + } +} \ No newline at end of file diff --git a/src/test/java/com/exasol/adapter/dialects/mysql/MySqlSqlDialectTest.java b/src/test/java/com/exasol/adapter/dialects/mysql/MySqlSqlDialectTest.java index 966c4c691..1cc40c371 100644 --- a/src/test/java/com/exasol/adapter/dialects/mysql/MySqlSqlDialectTest.java +++ b/src/test/java/com/exasol/adapter/dialects/mysql/MySqlSqlDialectTest.java @@ -6,6 +6,8 @@ import static com.exasol.adapter.capabilities.MainCapability.*; import static com.exasol.adapter.capabilities.PredicateCapability.*; import static com.exasol.adapter.capabilities.ScalarFunctionCapability.*; +import static com.exasol.adapter.capabilities.ScalarFunctionCapability.ST_INTERSECTION; +import static com.exasol.adapter.capabilities.ScalarFunctionCapability.ST_UNION; import static com.exasol.reflect.ReflectionUtils.getMethodReturnViaReflection; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.instanceOf; @@ -17,6 +19,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; import org.junit.jupiter.params.provider.ValueSource; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; @@ -109,7 +112,15 @@ void testGetDefaultNullSorting() { assertThat(this.dialect.getDefaultNullSorting(), equalTo(SqlDialect.NullSorting.NULLS_SORTED_AT_END)); } - @ValueSource(strings = { "ab:\'ab\'", "a'b:'a''b'", "a''b:'a''''b'", "'ab':'''ab'''" }) + @CsvSource({ "tableName, `tableName`", // + "`tableName, ```tableName`" // + }) + @ParameterizedTest + void testApplyQuote(final String unquoted, final String quoted) { + assertThat(this.dialect.applyQuote(unquoted), equalTo(quoted)); + } + + @ValueSource(strings = { "ab:'ab'", "a'b:'a''b'", "a''b:'a''''b'", "'ab':'''ab'''" }) @ParameterizedTest void testGetLiteralString(final String definition) { final int colonPosition = definition.indexOf(':'); @@ -118,11 +129,6 @@ void testGetLiteralString(final String definition) { assertThat(this.dialect.getStringLiteral(original), equalTo(literal)); } - @Test - void testApplyQuote() { - assertThat(this.dialect.applyQuote("tableName"), Matchers.equalTo("`tableName`")); - } - @Test void testMetadataReaderClass() { assertThat(getMethodReturnViaReflection(this.dialect, "createRemoteMetadataReader"), diff --git a/src/test/java/com/exasol/adapter/dialects/oracle/OracleSqlDialectTest.java b/src/test/java/com/exasol/adapter/dialects/oracle/OracleSqlDialectTest.java index ef8b865ba..32301fe43 100644 --- a/src/test/java/com/exasol/adapter/dialects/oracle/OracleSqlDialectTest.java +++ b/src/test/java/com/exasol/adapter/dialects/oracle/OracleSqlDialectTest.java @@ -13,44 +13,37 @@ import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.instanceOf; import static org.hamcrest.collection.IsIterableContainingInAnyOrder.containsInAnyOrder; -import static org.junit.Assert.assertEquals; import static org.junit.jupiter.api.Assertions.assertAll; import static org.junit.jupiter.api.Assertions.assertThrows; import java.util.Map; +import org.hamcrest.CoreMatchers; import org.hamcrest.MatcherAssert; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; +import org.junit.jupiter.params.provider.ValueSource; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; -import com.exasol.adapter.AdapterException; import com.exasol.adapter.AdapterProperties; import com.exasol.adapter.capabilities.Capabilities; -import com.exasol.adapter.dialects.*; +import com.exasol.adapter.dialects.PropertyValidationException; +import com.exasol.adapter.dialects.SqlDialect; import com.exasol.adapter.jdbc.ConnectionFactory; -import com.exasol.adapter.sql.*; -import com.exasol.sql.SqlNormalizer; @ExtendWith(MockitoExtension.class) class OracleSqlDialectTest { - private static final String SCHEMA_NAME = "SCHEMA"; - private SqlNode node; private SqlDialect dialect; - private SqlNodeVisitor generator; @Mock private ConnectionFactory connectionFactoryMock; @BeforeEach void beforeEach() { - this.node = DialectTestData.getTestSqlNode(); this.dialect = new OracleSqlDialect(this.connectionFactoryMock, AdapterProperties.emptyProperties()); - final SqlGenerationContext context = new SqlGenerationContext("", SCHEMA_NAME, false); - this.generator = this.dialect.getSqlGenerationVisitor(context); } @Test @@ -86,58 +79,6 @@ void testGetCapabilities() { TO_TIMESTAMP, BIT_AND, BIT_TO_NUM, CASE, NULLIFZERO, ZEROIFNULL))); } - @Test - void testSqlGeneratorWithLimit() throws AdapterException { - final String expectedSql = "SELECT LIMIT_SUBSELECT.* FROM ( " + // - " SELECT \"USER_ID\", COUNT(\"URL\") " + // - " FROM \"SCHEMA\".\"CLICKS\"" + // - " WHERE 1 < \"USER_ID\"" + // - " GROUP BY \"USER_ID\"" + // - " HAVING 1 < COUNT(\"URL\")" + // - " ORDER BY \"USER_ID\" " + // - ") LIMIT_SUBSELECT WHERE ROWNUM <= 10"; // - final String actualSql = this.node.accept(this.generator); - assertEquals(SqlNormalizer.normalizeSql(expectedSql), SqlNormalizer.normalizeSql(actualSql)); - } - - @Test - void testSqlGeneratorWithLimitOffset() throws AdapterException { - ((SqlStatementSelect) this.node).getLimit().setOffset(5); - final String expectedSql = "SELECT c0, c1 FROM (" + // - " SELECT LIMIT_SUBSELECT.*, ROWNUM ROWNUM_SUB FROM ( " + // - " SELECT \"USER_ID\" AS c0, COUNT(\"URL\") AS c1 " + // - " FROM \"SCHEMA\".\"CLICKS\"" + // - " WHERE 1 < \"USER_ID\"" + // - " GROUP BY \"USER_ID\"" + // - " HAVING 1 < COUNT(\"URL\")" + // - " ORDER BY \"USER_ID\"" + // - " ) LIMIT_SUBSELECT WHERE ROWNUM <= 15 " + // - ") WHERE ROWNUM_SUB > 5"; - final String actualSql = this.node.accept(this.generator); - assertEquals(SqlNormalizer.normalizeSql(expectedSql), SqlNormalizer.normalizeSql(actualSql)); - } - - @Test - void testSqlGeneratorWithSelectStarAndOffset() throws AdapterException { - SqlStatementSelect node = (SqlStatementSelect) DialectTestData.getTestSqlNode(); - node.getLimit().setOffset(5); - node = SqlStatementSelect.builder().selectList(SqlSelectList.createSelectStarSelectList()) - .fromClause(node.getFromClause()).whereClause(node.getWhereClause()).groupBy(node.getGroupBy()) - .having(node.getHaving()).orderBy(node.getOrderBy()).limit(node.getLimit()).build(); - final String expectedSql = "SELECT c0, c1 FROM (" + // - " SELECT LIMIT_SUBSELECT.*, ROWNUM ROWNUM_SUB FROM ( " + // - " SELECT \"USER_ID\" AS c0, \"URL\" AS c1 " + // - " FROM \"SCHEMA\".\"CLICKS\"" + // - " WHERE 1 < \"USER_ID\"" + // - " GROUP BY \"USER_ID\"" + // - " HAVING 1 < COUNT(\"URL\")" + // - " ORDER BY \"USER_ID\"" + // - " ) LIMIT_SUBSELECT WHERE ROWNUM <= 15 " + // - ") WHERE ROWNUM_SUB > 5"; - final String actualSql = node.accept(this.generator); - assertEquals(SqlNormalizer.normalizeSql(expectedSql), SqlNormalizer.normalizeSql(actualSql)); - } - @CsvSource({ "FALSE, FALSE, JDBC", // "TRUE, FALSE, LOCAL", // "FALSE, TRUE, ORA" }) @@ -185,4 +126,21 @@ void testQueryRewriterClass() { assertThat(getMethodReturnViaReflection(this.dialect, "createQueryRewriter"), instanceOf(OracleQueryRewriter.class)); } -} + + @CsvSource({ "tableName, \"tableName\"", // + "\"tableName, \"\"\"tableName\"" // + }) + @ParameterizedTest + void testApplyQuote(final String unquoted, final String quoted) { + assertThat(this.dialect.applyQuote(unquoted), equalTo(quoted)); + } + + @ValueSource(strings = { "ab:'ab'", "a'b:'a''b'", "a''b:'a''''b'", "'ab':'''ab'''" }) + @ParameterizedTest + void testGetLiteralString(final String definition) { + final int colonPosition = definition.indexOf(':'); + final String original = definition.substring(0, colonPosition); + final String literal = definition.substring(colonPosition + 1); + assertThat(this.dialect.getStringLiteral(original), CoreMatchers.equalTo(literal)); + } +} \ No newline at end of file diff --git a/src/test/java/com/exasol/adapter/dialects/oracle/OracleSqlGenerationVisitorTest.java b/src/test/java/com/exasol/adapter/dialects/oracle/OracleSqlGenerationVisitorTest.java index 03b113d76..6dcc82567 100644 --- a/src/test/java/com/exasol/adapter/dialects/oracle/OracleSqlGenerationVisitorTest.java +++ b/src/test/java/com/exasol/adapter/dialects/oracle/OracleSqlGenerationVisitorTest.java @@ -7,40 +7,33 @@ import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.containsInAnyOrder; -import static utils.SqlNodesCreator.*; +import static org.junit.Assert.assertEquals; import java.math.BigDecimal; -import java.sql.Connection; -import java.sql.SQLException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; +import java.util.*; +import java.util.stream.Collectors; +import java.util.stream.Stream; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; -import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import com.exasol.adapter.AdapterException; import com.exasol.adapter.AdapterProperties; -import com.exasol.adapter.dialects.SqlDialect; -import com.exasol.adapter.dialects.SqlGenerationContext; -import com.exasol.adapter.jdbc.ConnectionFactory; -import com.exasol.adapter.metadata.ColumnMetadata; -import com.exasol.adapter.metadata.DataType; +import com.exasol.adapter.dialects.*; +import com.exasol.adapter.metadata.*; import com.exasol.adapter.sql.*; +import com.exasol.sql.SqlNormalizer; @ExtendWith(MockitoExtension.class) class OracleSqlGenerationVisitorTest { private OracleSqlGenerationVisitor visitor; - @Mock - private Connection connectionMock; @BeforeEach - void beforeEach(@Mock final ConnectionFactory connectionFactoryMock) throws SQLException { + void beforeEach() { final SqlDialect dialect = new OracleSqlDialectFactory().createSqlDialect(null, AdapterProperties.emptyProperties()); final SqlGenerationContext context = new SqlGenerationContext("test_catalog", "test_schema", false); @@ -63,8 +56,10 @@ void testGetScalarFunctionsCast() { @Test void testVisitSqlStatementSelect() throws AdapterException { final SqlSelectList selectList = SqlSelectList.createAnyValueSelectList(); - final SqlStatementSelect sqlStatementSelect = createSqlStatementSelect(selectList, Collections.emptyList(), - "test_table_name"); + final TableMetadata tableMetadata = new TableMetadata("", "", Collections.emptyList(), ""); + final SqlTable fromClause = new SqlTable("test_table_name", tableMetadata); + final SqlStatementSelect sqlStatementSelect = SqlStatementSelect.builder().selectList(selectList) + .fromClause(fromClause).build(); assertThat(this.visitor.visit(sqlStatementSelect), equalTo("SELECT 1 FROM \"test_schema\".\"test_table_name\"")); } @@ -72,7 +67,8 @@ void testVisitSqlStatementSelect() throws AdapterException { @Test void testVisitSqlStatementSelectWithLimitAnyValue() throws AdapterException { final SqlSelectList selectList = SqlSelectList.createAnyValueSelectList(); - final SqlTable fromClause = createFromClause(Collections.emptyList(), ""); + final TableMetadata tableMetadata = new TableMetadata("", "", Collections.emptyList(), ""); + final SqlTable fromClause = new SqlTable("", tableMetadata); final SqlLimit limit = new SqlLimit(10, 3); final SqlStatementSelect sqlStatementSelect = SqlStatementSelect.builder().selectList(selectList) .fromClause(fromClause).limit(limit).build(); @@ -83,7 +79,8 @@ void testVisitSqlStatementSelectWithLimitAnyValue() throws AdapterException { @Test void testVisitSqlStatementSelectWithLimitSelectStar() throws AdapterException { final SqlSelectList selectList = SqlSelectList.createSelectStarSelectList(); - final SqlTable fromClause = createFromClause(Collections.emptyList(), "test_table_name"); + final TableMetadata tableMetadata = new TableMetadata("", "", Collections.emptyList(), ""); + final SqlTable fromClause = new SqlTable("test_table_name", tableMetadata); final SqlLimit limit = new SqlLimit(10, 3); final SqlStatementSelect sqlStatementSelect = SqlStatementSelect.builder().selectList(selectList) .fromClause(fromClause).limit(limit).build(); @@ -95,8 +92,10 @@ void testVisitSqlStatementSelectWithLimitSelectStar() throws AdapterException { @Test void testVisitSqlStatementSelectWithLimitRegularSelectList() throws AdapterException { - final SqlSelectList selectList = createRegularSqlSelectListWithTwoColumns(); - final SqlTable fromClause = createFromClause(Collections.emptyList(), "test_table_name"); + final SqlSelectList selectList = SqlSelectList + .createRegularSelectList(Arrays.asList(new SqlLiteralBool(true), new SqlLiteralString("string"))); + final TableMetadata tableMetadata = new TableMetadata("", "", Collections.emptyList(), ""); + final SqlTable fromClause = new SqlTable("test_table_name", tableMetadata); final SqlLimit limit = new SqlLimit(10, 3); final SqlStatementSelect sqlStatementSelect = SqlStatementSelect.builder().selectList(selectList) .fromClause(fromClause).limit(limit).build(); @@ -108,8 +107,10 @@ void testVisitSqlStatementSelectWithLimitRegularSelectList() throws AdapterExcep @Test void testVisitSqlStatementSelectWithLimitRegularSelectListWithoutOffset() throws AdapterException { - final SqlSelectList selectList = createRegularSqlSelectListWithTwoColumns(); - final SqlTable fromClause = createFromClause(Collections.emptyList(), "test_table_name"); + final SqlSelectList selectList = SqlSelectList + .createRegularSelectList(Arrays.asList(new SqlLiteralBool(true), new SqlLiteralString("string"))); + final TableMetadata tableMetadata = new TableMetadata("", "", Collections.emptyList(), ""); + final SqlTable fromClause = new SqlTable("test_table_name", tableMetadata); final SqlLimit limit = new SqlLimit(10); final SqlStatementSelect sqlStatementSelect = SqlStatementSelect.builder().selectList(selectList) .fromClause(fromClause).limit(limit).build(); @@ -127,7 +128,7 @@ void testVisitSqlSelectListRequiresAnyColumn() throws AdapterException { @Test void testVisitSqlSelectListSelectStar() throws AdapterException { final SqlSelectList selectList = createSqlSelectStarListWithOneColumn( - "{\"jdbcDataType\":16, \"typeName\":\"BOOLEAN\"}", DataType.createBool(), "test_column"); + "{\"jdbcDataType\":16, \"typeName\":\"BOOLEAN\"}", DataType.createBool()); assertSqlNodeConvertedToAsterisk(selectList, this.visitor); } @@ -136,15 +137,27 @@ void testVisitSqlSelectListSelectStar() throws AdapterException { void testVisitSqlSelectListSelectStarCastToChar(final String dataType) throws AdapterException { final SqlSelectList selectList = createSqlSelectStarListWithOneColumn( "{\"jdbcDataType\":2, \"typeName\":\"" + dataType + "\"}", - DataType.createVarChar(50, DataType.ExaCharset.UTF8), "test_column"); + DataType.createVarChar(50, DataType.ExaCharset.UTF8)); assertThat(this.visitor.visit(selectList), equalTo("TO_CHAR(\"test_column\")")); } + private SqlSelectList createSqlSelectStarListWithOneColumn(final String adapterNotes, final DataType dataType) { + final SqlSelectList selectList = SqlSelectList.createSelectStarSelectList(); + final List columns = new ArrayList<>(); + columns.add(ColumnMetadata.builder().name("test_column").adapterNotes(adapterNotes).type(dataType).build()); + final TableMetadata tableMetadata = new TableMetadata("", "", columns, ""); + final SqlTable fromClause = new SqlTable("", tableMetadata); + final SqlNode sqlStatementSelect = SqlStatementSelect.builder().selectList(selectList).fromClause(fromClause) + .build(); + selectList.setParent(sqlStatementSelect); + return selectList; + } + @Test void testVisitSqlSelectListSelectStarWithTimestamp() throws AdapterException { final SqlSelectList selectList = createSqlSelectStarListWithOneColumn( "{\"jdbcDataType\":2, \"typeName\":\"TIMESTAMP\"}", - DataType.createVarChar(50, DataType.ExaCharset.UTF8), "test_column"); + DataType.createVarChar(50, DataType.ExaCharset.UTF8)); assertThat(this.visitor.visit(selectList), equalTo( "TO_TIMESTAMP(TO_CHAR(\"test_column\", 'YYYY-MM-DD HH24:MI:SS.FF3'), 'YYYY-MM-DD HH24:MI:SS.FF3')")); } @@ -155,7 +168,8 @@ void testVisitSqlSelectListSelectStarNumberCastToDecimal() throws AdapterExcepti final List columns = new ArrayList<>(); columns.add(ColumnMetadata.builder().name("test_column") .adapterNotes("{\"jdbcDataType\":2, \"typeName\":\"NUMBER\"}").type(DataType.createDouble()).build()); - final SqlTable fromClause = createFromClause(columns, ""); + final TableMetadata tableMetadata = new TableMetadata("", "", columns, ""); + final SqlTable fromClause = new SqlTable("", tableMetadata); final SqlNode select = SqlStatementSelect.builder().selectList(selectList).fromClause(fromClause).build(); selectList.setParent(select); assertThat(this.visitor.visit(selectList), equalTo("CAST(\"test_column\" AS DECIMAL(0,0))")); @@ -163,8 +177,10 @@ void testVisitSqlSelectListSelectStarNumberCastToDecimal() throws AdapterExcepti @Test void testVisitSqlSelectListRegularSelectList() throws AdapterException { - final SqlSelectList selectList = createRegularSqlSelectListWithTwoColumns(); - final SqlTable fromClause = createFromClause(Collections.emptyList(), ""); + final SqlSelectList selectList = SqlSelectList + .createRegularSelectList(Arrays.asList(new SqlLiteralBool(true), new SqlLiteralString("string"))); + final TableMetadata tableMetadata = new TableMetadata("", "", Collections.emptyList(), ""); + final SqlTable fromClause = new SqlTable("", tableMetadata); final SqlNode select = SqlStatementSelect.builder().selectList(selectList).fromClause(fromClause).build(); selectList.setParent(select); assertThat(this.visitor.visit(selectList), equalTo("true, 'string'")); @@ -179,18 +195,16 @@ void testVisitSqlPredicateLikeRegexp() throws AdapterException { @Test void testVisitSqlLiteralExactnumeric() { - final SqlLiteralExactnumeric literalExactnumeric = new SqlLiteralExactnumeric(new BigDecimal(5.9)); - assertThat(this.visitor.visit(literalExactnumeric), - equalTo("5.9000000000000003552713678800500929355621337890625")); + final SqlLiteralExactnumeric literalExactnumeric = new SqlLiteralExactnumeric(new BigDecimal("5.9")); + assertThat(this.visitor.visit(literalExactnumeric), equalTo("5.9")); } @Test void testVisitSqlLiteralExactnumericInSelectList() { final SqlSelectList selectList = SqlSelectList.createSelectStarSelectList(); - final SqlLiteralExactnumeric literalExactnumeric = new SqlLiteralExactnumeric(new BigDecimal(5.9)); + final SqlLiteralExactnumeric literalExactnumeric = new SqlLiteralExactnumeric(new BigDecimal("5.9")); literalExactnumeric.setParent(selectList); - assertThat(this.visitor.visit(literalExactnumeric), - equalTo("TO_CHAR(5.9000000000000003552713678800500929355621337890625)")); + assertThat(this.visitor.visit(literalExactnumeric), equalTo("TO_CHAR(5.9)")); } @Test @@ -209,36 +223,47 @@ void testVisitSqlLiteralDoubleInSelectList() { @Test void testVisitSqlFunctionAggregateGroupConcat() throws AdapterException { - final List arguments = new ArrayList<>(); - arguments.add(new SqlLiteralDouble(10.5)); - final SqlFunctionAggregateGroupConcat aggregateGroupConcat = new SqlFunctionAggregateGroupConcat(AVG, arguments, - null, true, "'"); - assertThat(this.visitor.visit(aggregateGroupConcat), equalTo("LISTAGG(10.5, ''') WITHIN GROUP(ORDER BY 10.5)")); + final SqlFunctionAggregateGroupConcat aggregateGroupConcat = SqlFunctionAggregateGroupConcat + .builder(new SqlLiteralDouble(10.5)).separator(new SqlLiteralString("'")).build(); + assertThat(this.visitor.visit(aggregateGroupConcat), + equalTo("LISTAGG(10.5, '''') WITHIN GROUP(ORDER BY 10.5)")); } @Test void testVisitSqlFunctionAggregateGroupConcatWithOrderBy() throws AdapterException { - final List arguments = new ArrayList<>(); - arguments.add(new SqlLiteralDouble(10.5)); - final SqlOrderBy orderBy = createSqlOrderByDescNullsFirst("test_column", "test_column2"); - final SqlFunctionAggregateGroupConcat aggregateGroupConcat = new SqlFunctionAggregateGroupConcat(AVG, arguments, - orderBy, true, "'"); + final ColumnMetadata columnMetadata = ColumnMetadata.builder().name("test_column").type(DataType.createBool()) + .build(); + final ColumnMetadata columnMetadata2 = ColumnMetadata.builder().name("test_column2") + .type(DataType.createDouble()).build(); + final List orderByArguments = List.of(new SqlColumn(1, columnMetadata), + new SqlColumn(2, columnMetadata2)); + final SqlOrderBy orderBy = new SqlOrderBy(orderByArguments, Stream.of(false, true).collect(Collectors.toList()), + Stream.of(false, true).collect(Collectors.toList())); + final SqlFunctionAggregateGroupConcat aggregateGroupConcat = SqlFunctionAggregateGroupConcat + .builder(new SqlLiteralDouble(10.5)).separator(new SqlLiteralString("'")).orderBy(orderBy) + .distinct(true).build(); assertThat(this.visitor.visit(aggregateGroupConcat), equalTo( - "LISTAGG(10.5, ''') WITHIN GROUP(ORDER BY \"test_column\" DESC NULLS FIRST, \"test_column2\")")); + "LISTAGG(10.5, '''') WITHIN GROUP(ORDER BY \"test_column\" DESC NULLS FIRST, \"test_column2\")")); } @Test void testVisitSqlFunctionAggregate() throws AdapterException { - final SqlFunctionAggregate sqlFunctionAggregate = createSqlFunctionAggregate(); + final ColumnMetadata columnMetadata = ColumnMetadata.builder().name("test_column").type(DataType.createBool()) + .build(); + final List arguments = List.of(new SqlColumn(1, columnMetadata)); + final SqlFunctionAggregate sqlFunctionAggregate = new SqlFunctionAggregate(AVG, arguments, true); assertThat(this.visitor.visit(sqlFunctionAggregate), equalTo("AVG(DISTINCT \"test_column\")")); } @Test void testVisitSqlFunctionAggregateInSelectList() throws AdapterException { - final SqlFunctionAggregate sqlFunctionAggregate = createSqlFunctionAggregate(); + final ColumnMetadata columnMetadata = ColumnMetadata.builder().name("test_column").type(DataType.createBool()) + .build(); + final List arguments = List.of(new SqlColumn(1, columnMetadata)); + final SqlFunctionAggregate sqlFunctionAggregate = new SqlFunctionAggregate(AVG, arguments, false); final SqlNode selectList = SqlSelectList.createSelectStarSelectList(); sqlFunctionAggregate.setParent(selectList); - assertThat(this.visitor.visit(sqlFunctionAggregate), equalTo("CAST(AVG(DISTINCT \"test_column\") AS FLOAT)")); + assertThat(this.visitor.visit(sqlFunctionAggregate), equalTo("CAST(AVG(\"test_column\") AS FLOAT)")); } @Test @@ -261,8 +286,8 @@ void testVisitSqlFunctionScalarTrimOneArgument() throws AdapterException { @Test void testVisitSqlFunctionScalarTrimOTwoArguments() throws AdapterException { - final SqlFunctionScalar sqlFunctionScalar = createSqlFunctionScalarWithTwoStringArguments(TRIM, "ab cdef", - "ab"); + final List arguments = List.of(new SqlLiteralString("ab cdef"), new SqlLiteralString("ab")); + final SqlFunctionScalar sqlFunctionScalar = new SqlFunctionScalar(TRIM, arguments); assertThat(this.visitor.visit(sqlFunctionScalar), equalTo("TRIM('ab' FROM 'ab cdef')")); } @@ -275,7 +300,12 @@ void testVisitSqlFunctionScalarTrimOTwoArguments() throws AdapterException { @ParameterizedTest void testVisitSqlFunctionScalarAddDateValues(final ScalarFunction scalarFunction, final String expected) throws AdapterException { - final SqlFunctionScalar sqlFunctionScalar = createSqlFunctionScalarForDateTest(scalarFunction, 10); + final ColumnMetadata columnMetadata = ColumnMetadata.builder().name("test_column") + .adapterNotes("{\"jdbcDataType\":93, " + "\"typeName\":\"TIMESTAMP\"}") + .type(DataType.createChar(20, DataType.ExaCharset.UTF8)).build(); + final List arguments = List.of(new SqlColumn(1, columnMetadata), + new SqlLiteralExactnumeric(new BigDecimal(10))); + final SqlFunctionScalar sqlFunctionScalar = new SqlFunctionScalar(scalarFunction, arguments); assertThat(this.visitor.visit(sqlFunctionScalar), equalTo("(\"test_column\" + INTERVAL " + expected + ")")); } @@ -307,16 +337,71 @@ void testVisitSqlFunctionScalar1(final ScalarFunction scalarFunction, final Stri @ParameterizedTest void testVisitSqlFunctionScalar2(final ScalarFunction scalarFunction, final String expected) throws AdapterException { - final SqlFunctionScalar sqlFunctionScalar = createSqlFunctionScalarWithTwoStringArguments(scalarFunction, - "left", "right"); + final List arguments = List.of(new SqlLiteralString("left"), new SqlLiteralString("right")); + final SqlFunctionScalar sqlFunctionScalar = new SqlFunctionScalar(scalarFunction, arguments); assertThat(this.visitor.visit(sqlFunctionScalar), equalTo(expected)); } @Test void testVisitSqlFunctionScalarInSelectList() throws AdapterException { final SqlSelectList selectList = SqlSelectList.createSelectStarSelectList(); - final SqlFunctionScalar sqlFunctionScalar = createSqlFunctionScalarWithTwoStringArguments(TANH, "test", ""); + final List arguments = List.of(new SqlLiteralString("test"), new SqlLiteralString("")); + final SqlFunctionScalar sqlFunctionScalar = new SqlFunctionScalar(TANH, arguments); sqlFunctionScalar.setParent(selectList); assertThat(this.visitor.visit(sqlFunctionScalar), equalTo("CAST(TANH('test', '') AS FLOAT)")); } + + @Test + void testSqlGeneratorWithLimit() throws AdapterException { + final String expectedSql = "SELECT LIMIT_SUBSELECT.* FROM ( " + // + " SELECT \"USER_ID\", COUNT(\"URL\") " + // + " FROM \"test_schema\".\"CLICKS\"" + // + " WHERE 1 < \"USER_ID\"" + // + " GROUP BY \"USER_ID\"" + // + " HAVING 1 < COUNT(\"URL\")" + // + " ORDER BY \"USER_ID\" " + // + ") LIMIT_SUBSELECT WHERE ROWNUM <= 10"; // + final SqlStatementSelect testSqlNode = (SqlStatementSelect) DialectTestData.getTestSqlNode(); + final String actualSql = this.visitor.visit(testSqlNode); + assertEquals(SqlNormalizer.normalizeSql(expectedSql), SqlNormalizer.normalizeSql(actualSql)); + } + + @Test + void testSqlGeneratorWithLimitOffset() throws AdapterException { + final String expectedSql = "SELECT c0, c1 FROM (" + // + " SELECT LIMIT_SUBSELECT.*, ROWNUM ROWNUM_SUB FROM ( " + // + " SELECT \"USER_ID\" AS c0, COUNT(\"URL\") AS c1 " + // + " FROM \"test_schema\".\"CLICKS\"" + // + " WHERE 1 < \"USER_ID\"" + // + " GROUP BY \"USER_ID\"" + // + " HAVING 1 < COUNT(\"URL\")" + // + " ORDER BY \"USER_ID\"" + // + " ) LIMIT_SUBSELECT WHERE ROWNUM <= 15 " + // + ") WHERE ROWNUM_SUB > 5"; + final SqlStatementSelect testSqlNode = (SqlStatementSelect) DialectTestData.getTestSqlNode(); + testSqlNode.getLimit().setOffset(5); + final String actualSql = this.visitor.visit(testSqlNode); + assertEquals(SqlNormalizer.normalizeSql(expectedSql), SqlNormalizer.normalizeSql(actualSql)); + } + + @Test + void testSqlGeneratorWithSelectStarAndOffset() throws AdapterException { + SqlStatementSelect node = (SqlStatementSelect) DialectTestData.getTestSqlNode(); + node.getLimit().setOffset(5); + node = SqlStatementSelect.builder().selectList(SqlSelectList.createSelectStarSelectList()) + .fromClause(node.getFromClause()).whereClause(node.getWhereClause()).groupBy(node.getGroupBy()) + .having(node.getHaving()).orderBy(node.getOrderBy()).limit(node.getLimit()).build(); + final String expectedSql = "SELECT c0, c1 FROM (" + // + " SELECT LIMIT_SUBSELECT.*, ROWNUM ROWNUM_SUB FROM ( " + // + " SELECT \"USER_ID\" AS c0, \"URL\" AS c1 " + // + " FROM \"test_schema\".\"CLICKS\"" + // + " WHERE 1 < \"USER_ID\"" + // + " GROUP BY \"USER_ID\"" + // + " HAVING 1 < COUNT(\"URL\")" + // + " ORDER BY \"USER_ID\"" + // + " ) LIMIT_SUBSELECT WHERE ROWNUM <= 15 " + // + ") WHERE ROWNUM_SUB > 5"; + final String actualSql = this.visitor.visit(node); + assertEquals(SqlNormalizer.normalizeSql(expectedSql), SqlNormalizer.normalizeSql(actualSql)); + } } \ No newline at end of file diff --git a/src/test/java/com/exasol/adapter/dialects/postgresql/PostgreSQLSqlDialectTest.java b/src/test/java/com/exasol/adapter/dialects/postgresql/PostgreSQLSqlDialectTest.java index d0c29b591..0fee06a3e 100644 --- a/src/test/java/com/exasol/adapter/dialects/postgresql/PostgreSQLSqlDialectTest.java +++ b/src/test/java/com/exasol/adapter/dialects/postgresql/PostgreSQLSqlDialectTest.java @@ -7,6 +7,7 @@ import static com.exasol.adapter.capabilities.PredicateCapability.*; import static com.exasol.adapter.capabilities.ScalarFunctionCapability.*; import static com.exasol.reflect.ReflectionUtils.getMethodReturnViaReflection; +import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.instanceOf; import static org.hamcrest.collection.IsIterableContainingInAnyOrder.containsInAnyOrder; import static org.junit.Assert.assertEquals; @@ -16,9 +17,13 @@ import java.util.Map; +import org.hamcrest.CoreMatchers; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; +import org.junit.jupiter.params.provider.ValueSource; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; @@ -82,14 +87,22 @@ void testGetCapabilities() { DATE_TRUNC, EXTRACT, LOCALTIMESTAMP, POSIX_TIME, TO_CHAR, CASE, HASH_MD5))); } - @Test - void testApplyQuoteOnUpperCase() { - assertEquals("\"abc\"", this.dialect.applyQuote("ABC")); + @CsvSource({ "ABC, \"abc\"", // + "AbCde, \"abcde\"", // + "\"tableName, \"\"\"tablename\"" // + }) + @ParameterizedTest + void testApplyQuote(final String unquoted, final String quoted) { + assertThat(this.dialect.applyQuote(unquoted), equalTo(quoted)); } - @Test - void testApplyQuoteOnMixedCase() { - assertEquals("\"abcde\"", this.dialect.applyQuote("AbCde")); + @ValueSource(strings = { "ab:'ab'", "a'b:'a''b'", "a''b:'a''''b'", "'ab':'''ab'''" }) + @ParameterizedTest + void testGetLiteralString(final String definition) { + final int colonPosition = definition.indexOf(':'); + final String original = definition.substring(0, colonPosition); + final String literal = definition.substring(colonPosition + 1); + assertThat(this.dialect.getStringLiteral(original), CoreMatchers.equalTo(literal)); } @Test diff --git a/src/test/java/com/exasol/adapter/dialects/postgresql/PostgresSQLSqlGenerationVisitorTest.java b/src/test/java/com/exasol/adapter/dialects/postgresql/PostgresSQLSqlGenerationVisitorTest.java index 0d8e90bd1..993965a84 100644 --- a/src/test/java/com/exasol/adapter/dialects/postgresql/PostgresSQLSqlGenerationVisitorTest.java +++ b/src/test/java/com/exasol/adapter/dialects/postgresql/PostgresSQLSqlGenerationVisitorTest.java @@ -6,10 +6,9 @@ import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.jupiter.api.Assertions.assertThrows; -import static utils.SqlNodesCreator.*; -import java.util.ArrayList; -import java.util.List; +import java.math.BigDecimal; +import java.util.*; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -23,12 +22,12 @@ import com.exasol.adapter.AdapterProperties; import com.exasol.adapter.dialects.*; import com.exasol.adapter.jdbc.ConnectionFactory; -import com.exasol.adapter.metadata.DataType; +import com.exasol.adapter.metadata.*; import com.exasol.adapter.sql.*; @ExtendWith(MockitoExtension.class) class PostgresSQLSqlGenerationVisitorTest { - private SqlNodeVisitor visitor; + private SqlGenerationVisitor visitor; @BeforeEach void beforeEach(@Mock final ConnectionFactory connectionFactoryMock) { @@ -50,6 +49,17 @@ void testVisitSqlFunctionScalarAddDate(final ScalarFunction scalarFunction, fina assertThat(this.visitor.visit(sqlFunctionScalar), equalTo("\"test_column\" + interval '10 " + expected + "'")); } + private SqlFunctionScalar createSqlFunctionScalarForDateTest(final ScalarFunction scalarFunction, + final int numericValue) { + final List arguments = new ArrayList<>(); + arguments.add(new SqlColumn(1, + ColumnMetadata.builder().name("test_column") + .adapterNotes("{\"jdbcDataType\":93, " + "\"typeName\":\"TIMESTAMP\"}") + .type(DataType.createChar(20, DataType.ExaCharset.UTF8)).build())); + arguments.add(new SqlLiteralExactnumeric(new BigDecimal(numericValue))); + return new SqlFunctionScalar(scalarFunction, arguments); + } + @CsvSource({ "SECONDS_BETWEEN, SECOND", // "MINUTES_BETWEEN, MINUTE", // "HOURS_BETWEEN, HOUR", // @@ -91,7 +101,12 @@ void testVisitSqlSelectListAnyValue() throws AdapterException { @Test void testVisitSqlSelectListSelectStar() throws AdapterException { - final SqlSelectList sqlSelectList = createSqlSelectStarListWithoutColumns(); + final SqlSelectList sqlSelectList = SqlSelectList.createSelectStarSelectList(); + final TableMetadata tableMetadata = new TableMetadata("", "", Collections.emptyList(), ""); + final SqlTable fromClause = new SqlTable("test_table", tableMetadata); + final SqlNode sqlStatementSelect = SqlStatementSelect.builder().selectList(sqlSelectList).fromClause(fromClause) + .build(); + sqlSelectList.setParent(sqlStatementSelect); assertSqlNodeConvertedToAsterisk(sqlSelectList, this.visitor); } @@ -123,15 +138,15 @@ void testVisitSqlSelectListSelectStarRequiresCast(final String typeName, final S throws AdapterException { final SqlSelectList sqlSelectList = createSqlSelectStarListWithOneColumn( "{\"jdbcDataType\":2009, \"typeName\":\"" + typeName + "\"}", - DataType.createVarChar(10, DataType.ExaCharset.UTF8), "test_column"); + DataType.createVarChar(10, DataType.ExaCharset.UTF8)); assertThat(this.visitor.visit(sqlSelectList), equalTo("CAST(\"test_column\" as " + expectedCastType + " )")); } @Test void testVisitSqlSelectListSelectStarUnsupportedType() throws AdapterException { final SqlSelectList sqlSelectList = createSqlSelectStarListWithOneColumn( - "{\"jdbcDataType\":2009, \"typeName\":\"bytea\"}", DataType.createVarChar(10, DataType.ExaCharset.UTF8), - "test_column"); + "{\"jdbcDataType\":2009, \"typeName\":\"bytea\"}", + DataType.createVarChar(10, DataType.ExaCharset.UTF8)); assertThat(this.visitor.visit(sqlSelectList), equalTo("cast('bytea NOT SUPPORTED' as varchar) as not_supported")); } @@ -151,17 +166,34 @@ void testVisitSqlStatementSelect() throws AdapterException { @Test void testVisitSqlSelectListSelectStarThrowsException() { final SqlSelectList sqlSelectList = createSqlSelectStarListWithOneColumn("", - DataType.createVarChar(10, DataType.ExaCharset.UTF8), "test_column"); + DataType.createVarChar(10, DataType.ExaCharset.UTF8)); assertThrows(SqlGenerationVisitorException.class, () -> this.visitor.visit(sqlSelectList)); } + private SqlSelectList createSqlSelectStarListWithOneColumn(final String adapterNotes, final DataType dataType) { + final SqlSelectList selectList = SqlSelectList.createSelectStarSelectList(); + final List columns = new ArrayList<>(); + columns.add(ColumnMetadata.builder().name("test_column").adapterNotes(adapterNotes).type(dataType).build()); + final TableMetadata tableMetadata = new TableMetadata("", "", columns, ""); + final SqlTable fromClause = new SqlTable("", tableMetadata); + final SqlNode sqlStatementSelect = SqlStatementSelect.builder().selectList(selectList).fromClause(fromClause) + .build(); + selectList.setParent(sqlStatementSelect); + return selectList; + } + @Test void testVisitSqlFunctionAggregateGroupConcat() throws AdapterException { - final List arguments = new ArrayList<>(); - arguments.add(new SqlLiteralString("test")); - final SqlOrderBy orderBy = createSqlOrderByDescNullsFirst("test_column", "test_column2"); - final SqlFunctionAggregateGroupConcat sqlFunctionAggregateGroupConcat = new SqlFunctionAggregateGroupConcat( - AggregateFunction.AVG, arguments, orderBy, false, "'"); - assertThat(this.visitor.visit(sqlFunctionAggregateGroupConcat), equalTo("STRING_AGG('test', ''') ")); + final SqlLiteralString argument = new SqlLiteralString("test"); + final ColumnMetadata columnMetadata = ColumnMetadata.builder().name("test_column").type(DataType.createBool()) + .build(); + final ColumnMetadata columnMetadata2 = ColumnMetadata.builder().name("test_column2") + .type(DataType.createDouble()).build(); + final List orderByArguments = List.of(new SqlColumn(1, columnMetadata), + new SqlColumn(2, columnMetadata2)); + final SqlOrderBy orderBy = new SqlOrderBy(orderByArguments, List.of(false, true), List.of(false, true)); + final SqlFunctionAggregateGroupConcat sqlFunctionAggregateGroupConcat = SqlFunctionAggregateGroupConcat + .builder(argument).separator(new SqlLiteralString("'")).orderBy(orderBy).build(); + assertThat(this.visitor.visit(sqlFunctionAggregateGroupConcat), equalTo("STRING_AGG('test', '''') ")); } } \ No newline at end of file diff --git a/src/test/java/com/exasol/adapter/dialects/redshift/RedshiftSqlDialectTest.java b/src/test/java/com/exasol/adapter/dialects/redshift/RedshiftSqlDialectTest.java index 4c1ef4783..7a542d94f 100644 --- a/src/test/java/com/exasol/adapter/dialects/redshift/RedshiftSqlDialectTest.java +++ b/src/test/java/com/exasol/adapter/dialects/redshift/RedshiftSqlDialectTest.java @@ -20,6 +20,9 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; +import org.junit.jupiter.params.provider.ValueSource; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; @@ -81,7 +84,7 @@ void testMetadataReaderClass() { @Test void testValidateCatalogProperty() throws PropertyValidationException { - setMandatoryProperties("REDSHIFT"); + setMandatoryProperties(); this.rawProperties.put(CATALOG_NAME_PROPERTY, "MY_CATALOG"); final AdapterProperties adapterProperties = new AdapterProperties(this.rawProperties); final SqlDialect sqlDialect = new RedshiftSqlDialect(null, adapterProperties); @@ -90,15 +93,15 @@ void testValidateCatalogProperty() throws PropertyValidationException { @Test void testValidateSchemaProperty() throws PropertyValidationException { - setMandatoryProperties("REDSHIFT"); + setMandatoryProperties(); this.rawProperties.put(SCHEMA_NAME_PROPERTY, "MY_SCHEMA"); final AdapterProperties adapterProperties = new AdapterProperties(this.rawProperties); final SqlDialect sqlDialect = new RedshiftSqlDialect(null, adapterProperties); sqlDialect.validateProperties(); } - private void setMandatoryProperties(final String sqlDialectProperty) { - this.rawProperties.put(AdapterProperties.SQL_DIALECT_PROPERTY, sqlDialectProperty); + private void setMandatoryProperties() { + this.rawProperties.put(AdapterProperties.SQL_DIALECT_PROPERTY, "REDSHIFT"); this.rawProperties.put(AdapterProperties.CONNECTION_NAME_PROPERTY, "MY_CONN"); } @@ -113,9 +116,22 @@ void testGetAggregateFunctionAliases() { assertThat(this.dialect.getAggregateFunctionAliases(), aMapWithSize(0)); } - @Test - void testApplyQuote() { - assertThat(this.dialect.applyQuote("Foo\"Bar"), equalTo("\"Foo\"\"Bar\"")); + @CsvSource({ "ABC, \"ABC\"", // + "AbCde, \"AbCde\"", // + "\"tableName, \"\"\"tableName\"" // + }) + @ParameterizedTest + void testApplyQuote(final String unquoted, final String quoted) { + assertThat(this.dialect.applyQuote(unquoted), equalTo(quoted)); + } + + @ValueSource(strings = { "ab:'ab'", "a'b:'a''b'", "a''b:'a''''b'", "'ab':'''ab'''" }) + @ParameterizedTest + void testGetLiteralString(final String definition) { + final int colonPosition = definition.indexOf(':'); + final String original = definition.substring(0, colonPosition); + final String literal = definition.substring(colonPosition + 1); + assertThat(this.dialect.getStringLiteral(original), CoreMatchers.equalTo(literal)); } @Test @@ -133,11 +149,6 @@ void testGetDefaultNullSorting() { assertThat(this.dialect.getDefaultNullSorting(), equalTo(NullSorting.NULLS_SORTED_AT_END)); } - @Test - void testGetStringLiteral() { - assertThat(this.dialect.getStringLiteral("Foo'Bar"), equalTo("'Foo''Bar'")); - } - @Test void testGetSqlGenerationVisitor() { assertThat(this.dialect.getSqlGenerationVisitor(null), diff --git a/src/test/java/com/exasol/adapter/dialects/redshift/RedshiftSqlGenerationVisitorTest.java b/src/test/java/com/exasol/adapter/dialects/redshift/RedshiftSqlGenerationVisitorTest.java new file mode 100644 index 000000000..f415c2167 --- /dev/null +++ b/src/test/java/com/exasol/adapter/dialects/redshift/RedshiftSqlGenerationVisitorTest.java @@ -0,0 +1,45 @@ +package com.exasol.adapter.dialects.redshift; + +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.MatcherAssert.assertThat; + +import java.util.List; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import com.exasol.adapter.AdapterException; +import com.exasol.adapter.AdapterProperties; +import com.exasol.adapter.dialects.SqlDialect; +import com.exasol.adapter.dialects.SqlGenerationContext; +import com.exasol.adapter.metadata.ColumnMetadata; +import com.exasol.adapter.metadata.DataType; +import com.exasol.adapter.sql.*; + +class RedshiftSqlGenerationVisitorTest { + private RedshiftSqlGenerationVisitor visitor; + + @BeforeEach + void beforeEach() { + final SqlDialect dialect = new RedshiftSqlDialectFactory().createSqlDialect(null, + AdapterProperties.emptyProperties()); + final SqlGenerationContext context = new SqlGenerationContext("test_catalog", "test_schema", false); + this.visitor = new RedshiftSqlGenerationVisitor(dialect, context); + } + + @Test + void visitSqlFunctionAggregateGroupConcat() throws AdapterException { + final SqlLiteralString argument = new SqlLiteralString("value '"); + final ColumnMetadata columnMetadata = ColumnMetadata.builder().name("\"test_column").type(DataType.createBool()) + .build(); + final ColumnMetadata columnMetadata2 = ColumnMetadata.builder().name("test_column2\"") + .type(DataType.createDouble()).build(); + final List orderByArguments = List.of(new SqlColumn(1, columnMetadata), + new SqlColumn(2, columnMetadata2)); + final SqlOrderBy orderBy = new SqlOrderBy(orderByArguments, List.of(false, true), List.of(false, true)); + final SqlFunctionAggregateGroupConcat aggregateGroupConcat = SqlFunctionAggregateGroupConcat.builder(argument) + .separator(new SqlLiteralString("'")).orderBy(orderBy).distinct(true).build(); + assertThat(this.visitor.visit(aggregateGroupConcat), equalTo( + "LISTAGG('value ''', '''') WITHIN GROUP(ORDER BY \"\"\"test_column\" DESC NULLS FIRST, \"test_column2\"\"\")")); + } +} \ No newline at end of file diff --git a/src/test/java/com/exasol/adapter/dialects/saphana/SapHanaSqlDialectTest.java b/src/test/java/com/exasol/adapter/dialects/saphana/SapHanaSqlDialectTest.java index 3a0342fc3..7d250ad07 100644 --- a/src/test/java/com/exasol/adapter/dialects/saphana/SapHanaSqlDialectTest.java +++ b/src/test/java/com/exasol/adapter/dialects/saphana/SapHanaSqlDialectTest.java @@ -6,6 +6,8 @@ import static com.exasol.adapter.capabilities.MainCapability.*; import static com.exasol.adapter.capabilities.PredicateCapability.*; import static com.exasol.adapter.capabilities.ScalarFunctionCapability.*; +import static com.exasol.adapter.capabilities.ScalarFunctionCapability.ST_INTERSECTION; +import static com.exasol.adapter.capabilities.ScalarFunctionCapability.ST_UNION; import static com.exasol.reflect.ReflectionUtils.getMethodReturnViaReflection; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.MatcherAssert.assertThat; @@ -17,10 +19,14 @@ import java.sql.Connection; import java.sql.SQLException; +import org.hamcrest.CoreMatchers; import org.hamcrest.Matchers; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; +import org.junit.jupiter.params.provider.ValueSource; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; @@ -115,10 +121,21 @@ void testGetDefaultNullSorting() { Matchers.equalTo(SqlDialect.NullSorting.NULLS_SORTED_AT_START)); } - @Test - void testApplyQuote() { - assertThat(this.dialect.applyQuote("tableName"), Matchers.equalTo("\"tableName\"")); + @CsvSource({ "tableName, \"tableName\"", // + "\"tableName, \"\"\"tableName\"" // + }) + @ParameterizedTest + void testApplyQuote(final String unquoted, final String quoted) { + assertThat(this.dialect.applyQuote(unquoted), Matchers.equalTo(quoted)); + } + @ValueSource(strings = { "ab:'ab'", "a'b:'a''b'", "a''b:'a''''b'", "'ab':'''ab'''" }) + @ParameterizedTest + void testGetLiteralString(final String definition) { + final int colonPosition = definition.indexOf(':'); + final String original = definition.substring(0, colonPosition); + final String literal = definition.substring(colonPosition + 1); + assertThat(this.dialect.getStringLiteral(original), CoreMatchers.equalTo(literal)); } @Test diff --git a/src/test/java/com/exasol/adapter/dialects/sqlserver/SqlServerSqlDialectTest.java b/src/test/java/com/exasol/adapter/dialects/sqlserver/SqlServerSqlDialectTest.java index c31b09614..19d870712 100644 --- a/src/test/java/com/exasol/adapter/dialects/sqlserver/SqlServerSqlDialectTest.java +++ b/src/test/java/com/exasol/adapter/dialects/sqlserver/SqlServerSqlDialectTest.java @@ -6,12 +6,15 @@ import static com.exasol.adapter.capabilities.MainCapability.*; import static com.exasol.adapter.capabilities.PredicateCapability.*; import static com.exasol.adapter.capabilities.ScalarFunctionCapability.*; +import static com.exasol.adapter.capabilities.ScalarFunctionCapability.ST_INTERSECTION; +import static com.exasol.adapter.capabilities.ScalarFunctionCapability.ST_UNION; import static com.exasol.reflect.ReflectionUtils.getMethodReturnViaReflection; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.instanceOf; import static org.hamcrest.collection.IsIterableContainingInAnyOrder.containsInAnyOrder; import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.jupiter.api.Assertions.assertAll; +import static org.junit.jupiter.api.Assertions.assertThrows; import java.util.Map; @@ -20,6 +23,9 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; +import org.junit.jupiter.params.provider.ValueSource; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; @@ -139,6 +145,20 @@ void testApplyQuote() { assertThat(this.dialect.applyQuote("tableName"), equalTo("[tableName]")); } + @Test + void testApplyQuoteThrowsException() { + assertThrows(AssertionError.class, () -> this.dialect.applyQuote("[tableName]")); + } + + @ValueSource(strings = { "ab:'ab'", "a'b:'a''b'", "a''b:'a''''b'", "'ab':'''ab'''" }) + @ParameterizedTest + void testGetLiteralString(final String definition) { + final int colonPosition = definition.indexOf(':'); + final String original = definition.substring(0, colonPosition); + final String literal = definition.substring(colonPosition + 1); + assertThat(this.dialect.getStringLiteral(original), CoreMatchers.equalTo(literal)); + } + @Test void testRequiresCatalogQualifiedTableNames() { assertThat(this.dialect.requiresCatalogQualifiedTableNames(null), equalTo(true)); diff --git a/src/test/java/com/exasol/adapter/dialects/sqlserver/SqlServerSqlGenerationVisitorTest.java b/src/test/java/com/exasol/adapter/dialects/sqlserver/SqlServerSqlGenerationVisitorTest.java index 3639d96b7..697fc362b 100644 --- a/src/test/java/com/exasol/adapter/dialects/sqlserver/SqlServerSqlGenerationVisitorTest.java +++ b/src/test/java/com/exasol/adapter/dialects/sqlserver/SqlServerSqlGenerationVisitorTest.java @@ -4,11 +4,11 @@ import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.jupiter.api.Assertions.assertThrows; -import static utils.SqlNodesCreator.*; -import java.util.ArrayList; -import java.util.List; +import java.math.BigDecimal; +import java.util.*; +import com.exasol.adapter.metadata.*; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; @@ -17,7 +17,6 @@ import com.exasol.adapter.AdapterException; import com.exasol.adapter.AdapterProperties; import com.exasol.adapter.dialects.*; -import com.exasol.adapter.metadata.DataType; import com.exasol.adapter.sql.*; class SqlServerSqlGenerationVisitorTest { @@ -39,7 +38,12 @@ void testVisitSqlSelectListAnyValue() throws AdapterException { @Test void testVisitSqlSelectListSelectStar() throws AdapterException { - final SqlSelectList sqlSelectList = createSqlSelectStarListWithoutColumns(); + final SqlSelectList sqlSelectList = SqlSelectList.createSelectStarSelectList(); + final TableMetadata tableMetadata = new TableMetadata("", "", Collections.emptyList(), ""); + final SqlTable fromClause = new SqlTable("test_table", tableMetadata); + final SqlNode sqlStatementSelect = SqlStatementSelect.builder().selectList(sqlSelectList).fromClause(fromClause) + .build(); + sqlSelectList.setParent(sqlStatementSelect); assertSqlNodeConvertedToAsterisk(sqlSelectList, this.visitor); } @@ -47,7 +51,7 @@ void testVisitSqlSelectListSelectStar() throws AdapterException { void testVisitSqlSelectListSelectStarRequiresCast() throws AdapterException { final SqlSelectList sqlSelectList = createSqlSelectStarListWithOneColumn( "{\"jdbcDataType\":-155, \"typeName\":\"datetimeoffset\"}", - DataType.createVarChar(36, DataType.ExaCharset.UTF8), "test_column"); + DataType.createVarChar(36, DataType.ExaCharset.UTF8)); assertThat(this.visitor.visit(sqlSelectList), equalTo("CAST([test_column] as VARCHAR(34))")); } @@ -65,10 +69,22 @@ void testVisitSqlStatementSelect() throws AdapterException { @Test void testVisitSqlSelectListSelectStarThrowsException() { final SqlSelectList sqlSelectList = createSqlSelectStarListWithOneColumn("", - DataType.createVarChar(10, DataType.ExaCharset.UTF8), "test_column"); + DataType.createVarChar(10, DataType.ExaCharset.UTF8)); assertThrows(SqlGenerationVisitorException.class, () -> this.visitor.visit(sqlSelectList)); } + private SqlSelectList createSqlSelectStarListWithOneColumn(final String adapterNotes, final DataType dataType) { + final SqlSelectList selectList = SqlSelectList.createSelectStarSelectList(); + final List columns = new ArrayList<>(); + columns.add(ColumnMetadata.builder().name("test_column").adapterNotes(adapterNotes).type(dataType).build()); + final TableMetadata tableMetadata = new TableMetadata("", "", columns, ""); + final SqlTable fromClause = new SqlTable("", tableMetadata); + final SqlNode sqlStatementSelect = SqlStatementSelect.builder().selectList(selectList).fromClause(fromClause) + .build(); + selectList.setParent(sqlStatementSelect); + return selectList; + } + @CsvSource({ "ADD_DAYS, DAY", // "ADD_HOURS, HOUR", // "ADD_MINUTES, MINUTE", // @@ -78,10 +94,20 @@ void testVisitSqlSelectListSelectStarThrowsException() { @ParameterizedTest void testVisitSqlFunctionScalarAddDate(final ScalarFunction scalarFunction, final String expected) throws AdapterException { - final SqlFunctionScalar sqlFunctionScalar = createSqlFunctionScalarForDateTest(scalarFunction, 10); + final SqlFunctionScalar sqlFunctionScalar = createSqlFunctionScalarForDateTest(scalarFunction); assertThat(this.visitor.visit(sqlFunctionScalar), equalTo("DATEADD(" + expected + ",10,[test_column])")); } + private SqlFunctionScalar createSqlFunctionScalarForDateTest(final ScalarFunction scalarFunction) { + final List arguments = new ArrayList<>(); + arguments.add(new SqlColumn(1, + ColumnMetadata.builder().name("test_column") + .adapterNotes("{\"jdbcDataType\":93, " + "\"typeName\":\"TIMESTAMP\"}") + .type(DataType.createChar(20, DataType.ExaCharset.UTF8)).build())); + arguments.add(new SqlLiteralExactnumeric(new BigDecimal(10))); + return new SqlFunctionScalar(scalarFunction, arguments); + } + @CsvSource({ "SECONDS_BETWEEN, SECOND", // "MINUTES_BETWEEN, MINUTE", // "HOURS_BETWEEN, HOUR", // @@ -91,7 +117,7 @@ void testVisitSqlFunctionScalarAddDate(final ScalarFunction scalarFunction, fina @ParameterizedTest void testVisitSqlFunctionScalarTimeBetween(final ScalarFunction scalarFunction, final String expected) throws AdapterException { - final SqlFunctionScalar sqlFunctionScalar = createSqlFunctionScalarForDateTest(scalarFunction, 10); + final SqlFunctionScalar sqlFunctionScalar = createSqlFunctionScalarForDateTest(scalarFunction); assertThat(this.visitor.visit(sqlFunctionScalar), equalTo("DATEDIFF(" + expected + ",10,[test_column])")); } @@ -169,8 +195,8 @@ void testVisitSqlFunctionScalarWithThreeArguments(final ScalarFunction scalarFun @ParameterizedTest void testVisitSqlFunctionScalarWithTwoArguments(final ScalarFunction scalarFunction, final String expected) throws AdapterException { - final SqlFunctionScalar sqlFunctionScalar = createSqlFunctionScalarWithTwoStringArguments(scalarFunction, - "left", "right"); + final List arguments = List.of(new SqlLiteralString("left"), new SqlLiteralString("right")); + final SqlFunctionScalar sqlFunctionScalar = new SqlFunctionScalar(scalarFunction, arguments); assertThat(this.visitor.visit(sqlFunctionScalar), equalTo(expected)); } } \ No newline at end of file diff --git a/src/test/java/com/exasol/adapter/dialects/sybase/SybaseIdentifierTest.java b/src/test/java/com/exasol/adapter/dialects/sybase/SybaseIdentifierTest.java new file mode 100644 index 000000000..919d07adf --- /dev/null +++ b/src/test/java/com/exasol/adapter/dialects/sybase/SybaseIdentifierTest.java @@ -0,0 +1,27 @@ +package com.exasol.adapter.dialects.sybase; + +import nl.jqno.equalsverifier.EqualsVerifier; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import static org.junit.jupiter.api.Assertions.*; + +class SybaseIdentifierTest { + @ParameterizedTest + @ValueSource(strings = { "my_underscore_table", "123 column one", "table@#$", "テスト", "таблица" }) + void testCreateValidIdentifier(final String identifier) { + assertDoesNotThrow(() -> SybaseIdentifier.of(identifier)); + } + + @ParameterizedTest + @ValueSource(strings = { "test[table]", "test`table", "\" table123" }) + void testCreateInvalidIdentifier(final String identifier) { + assertThrows(AssertionError.class, () -> SybaseIdentifier.of(identifier)); + } + + @Test + void testEqualsAndHashContract() { + EqualsVerifier.simple().forClass(SybaseIdentifier.class).verify(); + } +} \ No newline at end of file diff --git a/src/test/java/com/exasol/adapter/dialects/sybase/SybaseSqlDialectTest.java b/src/test/java/com/exasol/adapter/dialects/sybase/SybaseSqlDialectTest.java index edbd90dec..f33c17b04 100644 --- a/src/test/java/com/exasol/adapter/dialects/sybase/SybaseSqlDialectTest.java +++ b/src/test/java/com/exasol/adapter/dialects/sybase/SybaseSqlDialectTest.java @@ -7,10 +7,13 @@ import static com.exasol.adapter.capabilities.MainCapability.*; import static com.exasol.adapter.capabilities.PredicateCapability.*; import static com.exasol.adapter.capabilities.ScalarFunctionCapability.*; +import static com.exasol.adapter.capabilities.ScalarFunctionCapability.ST_INTERSECTION; +import static com.exasol.adapter.capabilities.ScalarFunctionCapability.ST_UNION; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.collection.IsIterableContainingInAnyOrder.containsInAnyOrder; import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.jupiter.api.Assertions.assertAll; +import static org.junit.jupiter.api.Assertions.assertThrows; import java.util.HashMap; import java.util.Map; @@ -26,6 +29,8 @@ import com.exasol.adapter.dialects.SqlDialect; import com.exasol.adapter.sql.AggregateFunction; import com.exasol.adapter.sql.ScalarFunction; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; class SybaseSqlDialectTest { private SqlDialect dialect; @@ -73,7 +78,7 @@ void testGetCapabilities() { @Test void testValidateCatalogProperty() throws PropertyValidationException { - setMandatoryProperties("SYBASE"); + setMandatoryProperties(); this.rawProperties.put(CATALOG_NAME_PROPERTY, "MY_CATALOG"); final AdapterProperties adapterProperties = new AdapterProperties(this.rawProperties); final SqlDialect sqlDialect = new SybaseSqlDialect(null, adapterProperties); @@ -82,15 +87,15 @@ void testValidateCatalogProperty() throws PropertyValidationException { @Test void testValidateSchemaProperty() throws PropertyValidationException { - setMandatoryProperties("SYBASE"); + setMandatoryProperties(); this.rawProperties.put(SCHEMA_NAME_PROPERTY, "MY_SCHEMA"); final AdapterProperties adapterProperties = new AdapterProperties(this.rawProperties); final SqlDialect sqlDialect = new SybaseSqlDialect(null, adapterProperties); sqlDialect.validateProperties(); } - private void setMandatoryProperties(final String sqlDialectProperty) { - this.rawProperties.put(AdapterProperties.SQL_DIALECT_PROPERTY, sqlDialectProperty); + private void setMandatoryProperties() { + this.rawProperties.put(AdapterProperties.SQL_DIALECT_PROPERTY, "SYBASE"); this.rawProperties.put(AdapterProperties.CONNECTION_NAME_PROPERTY, "MY_CONN"); } @@ -133,6 +138,20 @@ void testApplyQuote() { assertThat(this.dialect.applyQuote("tableName"), equalTo("[tableName]")); } + @Test + void testApplyQuoteThrowsException() { + assertThrows(AssertionError.class, () -> this.dialect.applyQuote("[tableName]")); + } + + @ValueSource(strings = { "ab:'ab'", "a'b:'a''b'", "a''b:'a''''b'", "'ab':'''ab'''" }) + @ParameterizedTest + void testGetLiteralString(final String definition) { + final int colonPosition = definition.indexOf(':'); + final String original = definition.substring(0, colonPosition); + final String literal = definition.substring(colonPosition + 1); + assertThat(this.dialect.getStringLiteral(original), CoreMatchers.equalTo(literal)); + } + @Test void testRequiresCatalogQualifiedTableNames() { assertThat(this.dialect.requiresCatalogQualifiedTableNames(null), equalTo(true)); diff --git a/src/test/java/com/exasol/adapter/dialects/sybase/SybaseSqlGenerationVisitorTest.java b/src/test/java/com/exasol/adapter/dialects/sybase/SybaseSqlGenerationVisitorTest.java index 04ff0475a..d50e01778 100644 --- a/src/test/java/com/exasol/adapter/dialects/sybase/SybaseSqlGenerationVisitorTest.java +++ b/src/test/java/com/exasol/adapter/dialects/sybase/SybaseSqlGenerationVisitorTest.java @@ -4,10 +4,9 @@ import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.jupiter.api.Assertions.assertThrows; -import static utils.SqlNodesCreator.*; -import java.util.ArrayList; -import java.util.List; +import java.math.BigDecimal; +import java.util.*; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -21,7 +20,7 @@ import com.exasol.adapter.AdapterProperties; import com.exasol.adapter.dialects.*; import com.exasol.adapter.jdbc.ConnectionFactory; -import com.exasol.adapter.metadata.DataType; +import com.exasol.adapter.metadata.*; import com.exasol.adapter.sql.*; @ExtendWith(MockitoExtension.class) @@ -43,7 +42,12 @@ void testVisitSqlSelectListAnyValue() throws AdapterException { @Test void testVisitSqlSelectListSelectStar() throws AdapterException { - final SqlSelectList sqlSelectList = createSqlSelectStarListWithoutColumns(); + final SqlSelectList sqlSelectList = SqlSelectList.createSelectStarSelectList(); + final TableMetadata tableMetadata = new TableMetadata("", "", Collections.emptyList(), ""); + final SqlTable fromClause = new SqlTable("test_table", tableMetadata); + final SqlNode sqlStatementSelect = SqlStatementSelect.builder().selectList(sqlSelectList).fromClause(fromClause) + .build(); + sqlSelectList.setParent(sqlStatementSelect); assertSqlNodeConvertedToAsterisk(sqlSelectList, this.visitor); } @@ -85,6 +89,19 @@ void testVisitSqlSelectListSelectStarUnsupportedType(final String typeName) thro assertThat(this.visitor.visit(sqlSelectList), equalTo("'" + typeName + " NOT SUPPORTED'")); } + private SqlSelectList createSqlSelectStarListWithOneColumn(final String adapterNotes, final DataType dataType, + final String columnName) { + final SqlSelectList selectList = SqlSelectList.createSelectStarSelectList(); + final List columns = new ArrayList<>(); + columns.add(ColumnMetadata.builder().name(columnName).adapterNotes(adapterNotes).type(dataType).build()); + final TableMetadata tableMetadata = new TableMetadata("", "", columns, ""); + final SqlTable fromClause = new SqlTable("", tableMetadata); + final SqlNode sqlStatementSelect = SqlStatementSelect.builder().selectList(selectList).fromClause(fromClause) + .build(); + selectList.setParent(sqlStatementSelect); + return selectList; + } + @Test void testVisitSqlSelectListSelectStarThrowsException() { final SqlSelectList sqlSelectList = createSqlSelectStarListWithOneColumn("", @@ -106,6 +123,17 @@ void testVisitSqlFunctionScalarAddDate(final ScalarFunction scalarFunction, fina assertThat(this.visitor.visit(sqlFunctionScalar), equalTo("DATEADD(" + expected + ",10,[test_column])")); } + private SqlFunctionScalar createSqlFunctionScalarForDateTest(final ScalarFunction scalarFunction, + final int numericValue) { + final List arguments = new ArrayList<>(); + arguments.add(new SqlColumn(1, + ColumnMetadata.builder().name("test_column") + .adapterNotes("{\"jdbcDataType\":93, " + "\"typeName\":\"TIMESTAMP\"}") + .type(DataType.createChar(20, DataType.ExaCharset.UTF8)).build())); + arguments.add(new SqlLiteralExactnumeric(new BigDecimal(numericValue))); + return new SqlFunctionScalar(scalarFunction, arguments); + } + @CsvSource({ "SECONDS_BETWEEN, SECOND", // "MINUTES_BETWEEN, MINUTE", // "HOURS_BETWEEN, HOUR", // @@ -193,8 +221,10 @@ void testVisitSqlFunctionScalarWithThreeArguments(final ScalarFunction scalarFun @ParameterizedTest void testVisitSqlFunctionScalarWithTwoArguments(final ScalarFunction scalarFunction, final String expected) throws AdapterException { - final SqlFunctionScalar sqlFunctionScalar = createSqlFunctionScalarWithTwoStringArguments(scalarFunction, - "left", "right"); + final List arguments = new ArrayList<>(); + arguments.add(new SqlLiteralString("left")); + arguments.add(new SqlLiteralString("right")); + final SqlFunctionScalar sqlFunctionScalar = new SqlFunctionScalar(scalarFunction, arguments); assertThat(this.visitor.visit(sqlFunctionScalar), equalTo(expected)); } } \ No newline at end of file diff --git a/src/test/java/com/exasol/adapter/dialects/teradata/TeradataSqlDialectTest.java b/src/test/java/com/exasol/adapter/dialects/teradata/TeradataSqlDialectTest.java index 2e385d4a8..36834580e 100644 --- a/src/test/java/com/exasol/adapter/dialects/teradata/TeradataSqlDialectTest.java +++ b/src/test/java/com/exasol/adapter/dialects/teradata/TeradataSqlDialectTest.java @@ -21,9 +21,13 @@ import java.util.Map; import org.hamcrest.CoreMatchers; +import org.hamcrest.Matchers; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; +import org.junit.jupiter.params.provider.ValueSource; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; @@ -61,7 +65,6 @@ void testGetCapabilities() { containsInAnyOrder(COUNT, COUNT_STAR, COUNT_DISTINCT, SUM, SUM_DISTINCT, MIN, MAX, AVG, AVG_DISTINCT, MEDIAN, FIRST_VALUE, LAST_VALUE, STDDEV_POP, STDDEV_SAMP, VAR_POP, VAR_SAMP)), - // () -> assertThat(capabilities.getScalarFunctionCapabilities(), containsInAnyOrder(CEIL, DIV, FLOOR, SIGN, ADD, SUB, MULT, FLOAT_DIV, NEG, ABS, ACOS, ASIN, ATAN, ATAN2, COS, COSH, COT, DEGREES, EXP, GREATEST, LEAST, LN, LOG, MOD, POWER, @@ -102,9 +105,21 @@ void testValidateSchemaProperty() throws PropertyValidationException { sqlDialect.validateProperties(); } - @Test - void testApplyQuote() { - assertThat(this.dialect.applyQuote("tableName"), equalTo("\"tableName\"")); + @CsvSource({ "tableName, \"tableName\"", // + "\"tableName, \"\"\"tableName\"" // + }) + @ParameterizedTest + void testApplyQuote(final String unquoted, final String quoted) { + assertThat(this.dialect.applyQuote(unquoted), Matchers.equalTo(quoted)); + } + + @ValueSource(strings = { "ab:'ab'", "a'b:'a''b'", "a''b:'a''''b'", "'ab':'''ab'''" }) + @ParameterizedTest + void testGetLiteralString(final String definition) { + final int colonPosition = definition.indexOf(':'); + final String original = definition.substring(0, colonPosition); + final String literal = definition.substring(colonPosition + 1); + assertThat(this.dialect.getStringLiteral(original), CoreMatchers.equalTo(literal)); } @Test diff --git a/src/test/java/com/exasol/adapter/dialects/teradata/TeradataSqlGenerationVisitorTest.java b/src/test/java/com/exasol/adapter/dialects/teradata/TeradataSqlGenerationVisitorTest.java index 7ce003021..ea8d59495 100644 --- a/src/test/java/com/exasol/adapter/dialects/teradata/TeradataSqlGenerationVisitorTest.java +++ b/src/test/java/com/exasol/adapter/dialects/teradata/TeradataSqlGenerationVisitorTest.java @@ -5,9 +5,9 @@ import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.jupiter.api.Assertions.assertThrows; -import static utils.SqlNodesCreator.createSqlSelectStarListWithOneColumn; -import static utils.SqlNodesCreator.createSqlSelectStarListWithoutColumns; +import com.exasol.adapter.metadata.*; +import com.exasol.adapter.sql.*; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -20,9 +20,8 @@ import com.exasol.adapter.AdapterProperties; import com.exasol.adapter.dialects.*; import com.exasol.adapter.jdbc.ConnectionFactory; -import com.exasol.adapter.metadata.DataType; -import com.exasol.adapter.sql.SqlSelectList; -import com.exasol.adapter.sql.SqlStatementSelect; + +import java.util.*; @ExtendWith(MockitoExtension.class) class TeradataSqlGenerationVisitorTest { @@ -43,7 +42,12 @@ void testVisitSqlSelectListAnyValue() throws AdapterException { @Test void testVisitSqlSelectListSelectStar() throws AdapterException { - final SqlSelectList sqlSelectList = createSqlSelectStarListWithoutColumns(); + final SqlSelectList sqlSelectList = SqlSelectList.createSelectStarSelectList(); + final TableMetadata tableMetadata = new TableMetadata("", "", Collections.emptyList(), ""); + final SqlTable fromClause = new SqlTable("test_table", tableMetadata); + final SqlNode sqlStatementSelect = SqlStatementSelect.builder().selectList(sqlSelectList).fromClause(fromClause) + .build(); + sqlSelectList.setParent(sqlStatementSelect); assertSqlNodeConvertedToAsterisk(sqlSelectList, this.visitor); } @@ -72,10 +76,22 @@ void testVisitSqlSelectListSelectStarRequiresCast(final String typeName, final S throws AdapterException { final SqlSelectList sqlSelectList = createSqlSelectStarListWithOneColumn( "{\"jdbcDataType\":2009, \"typeName\":\"" + typeName + "\"}", - DataType.createVarChar(10, DataType.ExaCharset.UTF8), "test_column"); + DataType.createVarChar(10, DataType.ExaCharset.UTF8)); assertThat(this.visitor.visit(sqlSelectList), equalTo(expected)); } + private SqlSelectList createSqlSelectStarListWithOneColumn(final String adapterNotes, final DataType dataType) { + final SqlSelectList selectList = SqlSelectList.createSelectStarSelectList(); + final List columns = new ArrayList<>(); + columns.add(ColumnMetadata.builder().name("test_column").adapterNotes(adapterNotes).type(dataType).build()); + final TableMetadata tableMetadata = new TableMetadata("", "", columns, ""); + final SqlTable fromClause = new SqlTable("", tableMetadata); + final SqlNode sqlStatementSelect = SqlStatementSelect.builder().selectList(selectList).fromClause(fromClause) + .build(); + selectList.setParent(sqlStatementSelect); + return selectList; + } + @CsvSource({ "BYTE", // "VARBYTE", // "SYSUDTLIB", // @@ -85,14 +101,14 @@ void testVisitSqlSelectListSelectStarRequiresCast(final String typeName, final S void testVisitSqlSelectListSelectStarUnsupportedType(final String typeName) throws AdapterException { final SqlSelectList sqlSelectList = createSqlSelectStarListWithOneColumn( "{\"jdbcDataType\":2009, \"typeName\":\"" + typeName + "\"}", - DataType.createVarChar(10, DataType.ExaCharset.UTF8), "test_column"); + DataType.createVarChar(10, DataType.ExaCharset.UTF8)); assertThat(this.visitor.visit(sqlSelectList), equalTo("'" + typeName + " NOT SUPPORTED'")); } @Test void testVisitSqlSelectListSelectStarThrowsException() { final SqlSelectList sqlSelectList = createSqlSelectStarListWithOneColumn("", - DataType.createVarChar(10, DataType.ExaCharset.UTF8), "test_column"); + DataType.createVarChar(10, DataType.ExaCharset.UTF8)); assertThrows(SqlGenerationVisitorException.class, () -> this.visitor.visit(sqlSelectList)); } } \ No newline at end of file diff --git a/src/test/java/utils/SqlNodesCreator.java b/src/test/java/utils/SqlNodesCreator.java deleted file mode 100644 index 421720c74..000000000 --- a/src/test/java/utils/SqlNodesCreator.java +++ /dev/null @@ -1,94 +0,0 @@ -package utils; - -import static com.exasol.adapter.sql.AggregateFunction.AVG; - -import java.math.BigDecimal; -import java.util.*; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -import com.exasol.adapter.metadata.*; -import com.exasol.adapter.sql.*; - -/** - * This class contains static methods for fast creation of SQL nodes which are used in tests for SQL generation - * visitors. Helps to avoid duplication and speed up testing. - */ -public class SqlNodesCreator { - private SqlNodesCreator() { - } - - public static SqlOrderBy createSqlOrderByDescNullsFirst(final String columnName1, final String columnName2) { - final List orderByArguments = new ArrayList<>(); - final ColumnMetadata columnMetadata = ColumnMetadata.builder().name("test_column").type(DataType.createBool()) - .build(); - final ColumnMetadata columnMetadata2 = ColumnMetadata.builder().name("test_column2") - .type(DataType.createDouble()).build(); - orderByArguments.add(new SqlColumn(1, columnMetadata)); - orderByArguments.add(new SqlColumn(2, columnMetadata2)); - return new SqlOrderBy(orderByArguments, Stream.of(false, true).collect(Collectors.toList()), - Stream.of(false, true).collect(Collectors.toList())); - } - - public static SqlTable createFromClause(final List columns, final String tableName) { - final TableMetadata tableMetadata = new TableMetadata("", "", columns, ""); - return new SqlTable(tableName, tableMetadata); - } - - public static SqlSelectList createRegularSqlSelectListWithTwoColumns() { - return SqlSelectList - .createRegularSelectList(Arrays.asList(new SqlLiteralBool(true), new SqlLiteralString("string"))); - } - - public static SqlFunctionAggregate createSqlFunctionAggregate() { - final ColumnMetadata columnMetadata = ColumnMetadata.builder().name("test_column").type(DataType.createBool()) - .build(); - final SqlColumn column = new SqlColumn(1, columnMetadata); - final List arguments = new ArrayList<>(); - arguments.add(column); - return new SqlFunctionAggregate(AVG, arguments, true); - } - - public static SqlStatementSelect createSqlStatementSelect(final SqlSelectList sqlSelectList, - final List columns, final String tableName) { - final SqlTable fromClause = createFromClause(columns, tableName); - return SqlStatementSelect.builder().selectList(sqlSelectList).fromClause(fromClause).build(); - } - - public static SqlFunctionScalar createSqlFunctionScalarWithTwoStringArguments(final ScalarFunction scalarFunction, - final String argument1, final String argument2) { - final List arguments = new ArrayList<>(); - arguments.add(new SqlLiteralString(argument1)); - arguments.add(new SqlLiteralString(argument2)); - return new SqlFunctionScalar(scalarFunction, arguments); - } - - public static SqlFunctionScalar createSqlFunctionScalarForDateTest(final ScalarFunction scalarFunction, - final int numericValue) { - final List arguments = new ArrayList<>(); - arguments.add(new SqlColumn(1, - ColumnMetadata.builder().name("test_column") - .adapterNotes("{\"jdbcDataType\":93, " + "\"typeName\":\"TIMESTAMP\"}") - .type(DataType.createChar(20, DataType.ExaCharset.UTF8)).build())); - arguments.add(new SqlLiteralExactnumeric(new BigDecimal(numericValue))); - return new SqlFunctionScalar(scalarFunction, arguments); - } - - public static SqlSelectList createSqlSelectStarListWithOneColumn(final String adapterNotes, final DataType dataType, - final String columnName) { - final SqlSelectList selectList = SqlSelectList.createSelectStarSelectList(); - final List columns = new ArrayList<>(); - columns.add(ColumnMetadata.builder().name(columnName).adapterNotes(adapterNotes).type(dataType).build()); - final SqlNode sqlStatementSelect = createSqlStatementSelect(selectList, columns, ""); - selectList.setParent(sqlStatementSelect); - return selectList; - } - - public static SqlSelectList createSqlSelectStarListWithoutColumns() { - final SqlSelectList sqlSelectList = SqlSelectList.createSelectStarSelectList(); - final SqlNode sqlStatementSelect = createSqlStatementSelect(sqlSelectList, Collections.emptyList(), - "test_table"); - sqlSelectList.setParent(sqlStatementSelect); - return sqlSelectList; - } -} \ No newline at end of file From 741887d3d75f7b850319ccffe8c3461957e16432 Mon Sep 17 00:00:00 2001 From: anastasiiasergienko Date: Mon, 9 Nov 2020 14:15:59 +0100 Subject: [PATCH 02/12] * #401: Fixed review findings --- doc/changes/changes_4.0.4.md | 7 +- doc/dialects/athena.md | 3 +- pom.xml | 2 +- .../dialects/athena/AthenaIdentifier.java | 12 ++-- .../dialects/athena/AthenaSqlDialect.java | 6 ++ .../dialects/bigquery/BigQueryIdentifier.java | 55 ---------------- .../dialects/bigquery/BigQuerySqlDialect.java | 5 +- .../adapter/dialects/db2/DB2SqlDialect.java | 8 ++- .../dialects/generic/GenericSqlDialect.java | 5 ++ .../adapter/dialects/hive/HiveSqlDialect.java | 5 ++ .../dialects/impala/ImpalaIdentifier.java | 64 +++++++++++++++++++ .../dialects/impala/ImpalaSqlDialect.java | 4 +- .../dialects/mysql/MySqlSqlDialect.java | 10 +-- .../dialects/oracle/OracleIdentifier.java | 64 +++++++++++++++++++ .../dialects/oracle/OracleSqlDialect.java | 8 ++- .../postgresql/PostgreSQLSqlDialect.java | 7 +- .../dialects/redshift/RedshiftSqlDialect.java | 8 ++- .../dialects/saphana/SapHanaSqlDialect.java | 8 ++- .../sqlserver/SqlServerIdentifier.java | 10 +-- .../sqlserver/SqlServerSqlDialect.java | 6 ++ .../dialects/sybase/SybaseIdentifier.java | 8 ++- .../dialects/sybase/SybaseSqlDialect.java | 5 ++ .../dialects/teradata/TeradataSqlDialect.java | 10 ++- .../dialects/athena/AthenaSqlDialectTest.java | 7 +- .../bigquery/BigQueryIdentifierTest.java | 12 ---- .../bigquery/BigQuerySqlDialectTest.java | 9 ++- .../dialects/db2/DB2SqlDialectTest.java | 6 +- .../dialects/dummy/DummySqlDialect.java | 5 ++ .../dialects/impala/ImpalaIdentifierTest.java | 29 +++++++++ .../dialects/impala/ImpalaSqlDialectTest.java | 19 ++++-- .../dialects/mysql/MySqlSqlDialectTest.java | 9 ++- .../dialects/oracle/OracleIdentifierTest.java | 29 +++++++++ .../dialects/oracle/OracleSqlDialectTest.java | 19 +++--- .../PostgreSQLIdentifierConverterTest.java | 4 +- .../postgresql/PostgreSQLSqlDialectTest.java | 6 +- .../redshift/RedshiftSqlDialectTest.java | 6 +- .../saphana/SapHanaSqlDialectTest.java | 6 +- .../sqlserver/SqlServerSqlDialectTest.java | 22 +++---- .../dialects/sybase/SybaseSqlDialectTest.java | 14 ++-- .../teradata/TeradataSqlDialectTest.java | 6 +- 40 files changed, 358 insertions(+), 170 deletions(-) delete mode 100644 src/main/java/com/exasol/adapter/dialects/bigquery/BigQueryIdentifier.java create mode 100644 src/main/java/com/exasol/adapter/dialects/impala/ImpalaIdentifier.java create mode 100644 src/main/java/com/exasol/adapter/dialects/oracle/OracleIdentifier.java delete mode 100644 src/test/java/com/exasol/adapter/dialects/bigquery/BigQueryIdentifierTest.java create mode 100644 src/test/java/com/exasol/adapter/dialects/impala/ImpalaIdentifierTest.java create mode 100644 src/test/java/com/exasol/adapter/dialects/oracle/OracleIdentifierTest.java diff --git a/doc/changes/changes_4.0.4.md b/doc/changes/changes_4.0.4.md index 57864780d..e59e478eb 100644 --- a/doc/changes/changes_4.0.4.md +++ b/doc/changes/changes_4.0.4.md @@ -34,4 +34,9 @@ Code name: * Updated com.exasol:test-db-builder-java:1.0.1 to 1.1.0 * Updated com.exasol:hamcrest-resultset-matcher:1.1.1 to 1.2.1 * Updated nl.jqno.equalsverifier:equalsverifier:3.4.3 to 3.5 -* Updated mysql:mysql-connector-java:8.0.21 to 8.0.22 \ No newline at end of file +* Updated mysql:mysql-connector-java:8.0.21 to 8.0.22 +* Updated org.testcontainers:junit-jupiter:1.14.3 to 1.15.0 +* Updated org.testcontainers:mssqlserver:1.14.3 to 1.15.0 +* Updated org.testcontainers:mysql:1.14.3 to 1.15.0 +* Updated org.testcontainers:oracle-xe:1.14.3 to 1.15.0 +* Updated org.testcontainers:postgresql:1.14.3 to 1.15.0 \ No newline at end of file diff --git a/doc/dialects/athena.md b/doc/dialects/athena.md index 2fce620f2..3179869fc 100644 --- a/doc/dialects/athena.md +++ b/doc/dialects/athena.md @@ -28,8 +28,7 @@ You need to specify the following settings when adding the JDBC driver via EXAOp Please refer to the [documentation on configuring JDBC connections to Athena](https://docs.aws.amazon.com/athena/latest/ug/connect-with-jdbc.html) for details. IMPORTANT: The latest Athena driver requires to **Disable Security Manager**. -It is necessary because JDBC driver requires Java permissions which we do not grant by default. -Please keep in mind that it's not safe to disable the security manager. +It is necessary because JDBC driver requires Java permissions which we do not grant by default. ## Uploading the JDBC Driver to EXAOperation diff --git a/pom.xml b/pom.xml index 72d0c04ee..e93bc6418 100644 --- a/pom.xml +++ b/pom.xml @@ -12,7 +12,7 @@ 11 3.0.0-M4 6.1.0 - 1.14.3 + 1.15.0 target/site/jacoco/jacoco.xml,target/site/jacoco-it/jacoco.xml diff --git a/src/main/java/com/exasol/adapter/dialects/athena/AthenaIdentifier.java b/src/main/java/com/exasol/adapter/dialects/athena/AthenaIdentifier.java index 8ac34b856..f5e452bc6 100644 --- a/src/main/java/com/exasol/adapter/dialects/athena/AthenaIdentifier.java +++ b/src/main/java/com/exasol/adapter/dialects/athena/AthenaIdentifier.java @@ -1,9 +1,9 @@ package com.exasol.adapter.dialects.athena; -import com.exasol.db.Identifier; - import java.util.Objects; +import com.exasol.db.Identifier; + /** * Represents an identifier in the Athena database. */ @@ -71,8 +71,12 @@ private static boolean validateCharacter(final int codePoint) { @Override public boolean equals(final Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; + if (this == o) { + return true; + } + if (!(o instanceof AthenaIdentifier)) { + return false; + } final AthenaIdentifier that = (AthenaIdentifier) o; return Objects.equals(this.id, that.id); } diff --git a/src/main/java/com/exasol/adapter/dialects/athena/AthenaSqlDialect.java b/src/main/java/com/exasol/adapter/dialects/athena/AthenaSqlDialect.java index b2d2fb18f..b0e13e625 100644 --- a/src/main/java/com/exasol/adapter/dialects/athena/AthenaSqlDialect.java +++ b/src/main/java/com/exasol/adapter/dialects/athena/AthenaSqlDialect.java @@ -107,6 +107,7 @@ public boolean requiresSchemaQualifiedTableNames(final SqlGenerationContext cont } @Override + // https://docs.aws.amazon.com/athena/latest/ug/tables-databases-columns-names.html public String applyQuote(final String identifier) { return AthenaIdentifier.of(identifier).quote(); } @@ -116,6 +117,11 @@ public NullSorting getDefaultNullSorting() { return NullSorting.NULLS_SORTED_AT_END; } + @Override + public String getStringLiteral(final String value) { + return super.quoteLiteralStringWithSingleQuote(value); + } + @Override protected RemoteMetadataReader createRemoteMetadataReader() { try { diff --git a/src/main/java/com/exasol/adapter/dialects/bigquery/BigQueryIdentifier.java b/src/main/java/com/exasol/adapter/dialects/bigquery/BigQueryIdentifier.java deleted file mode 100644 index f35cacf68..000000000 --- a/src/main/java/com/exasol/adapter/dialects/bigquery/BigQueryIdentifier.java +++ /dev/null @@ -1,55 +0,0 @@ -package com.exasol.adapter.dialects.bigquery; - -import java.util.Objects; - -import com.exasol.db.Identifier; - -/** - * Represents an identifier in the BigQuery database. - */ -public class BigQueryIdentifier implements Identifier { - private final String id; - - private BigQueryIdentifier(final String id) { - this.id = id; - } - - /** - * Get the quoted identifier as a {@link String}. - * - * @return quoted identifier - */ - @Override - public String quote() { - return "`" + this.id.replace("`", "\\`") + "`"; - } - - /** - * Create a new {@link BigQueryIdentifier}. - *

- * BigQuery allows any characters when the identifier is quoted. We don't have validations here. - * https://cloud.google.com/bigquery/docs/reference/standard-sql/lexical - *

- * - * @param id the identifier as {@link String} - * @return new {@link BigQueryIdentifier} instance - */ - public static BigQueryIdentifier of(final String id) { - return new BigQueryIdentifier(id); - } - - @Override - public boolean equals(final Object o) { - if (this == o) - return true; - if (o == null || getClass() != o.getClass()) - return false; - final BigQueryIdentifier that = (BigQueryIdentifier) o; - return Objects.equals(this.id, that.id); - } - - @Override - public int hashCode() { - return Objects.hash(this.id); - } -} \ No newline at end of file diff --git a/src/main/java/com/exasol/adapter/dialects/bigquery/BigQuerySqlDialect.java b/src/main/java/com/exasol/adapter/dialects/bigquery/BigQuerySqlDialect.java index dce93b6cc..fcb92bae5 100644 --- a/src/main/java/com/exasol/adapter/dialects/bigquery/BigQuerySqlDialect.java +++ b/src/main/java/com/exasol/adapter/dialects/bigquery/BigQuerySqlDialect.java @@ -126,8 +126,9 @@ public StructureElementSupport supportsJdbcSchemas() { } @Override + // https://cloud.google.com/bigquery/docs/reference/standard-sql/lexical public String applyQuote(final String identifier) { - return BigQueryIdentifier.of(identifier).quote(); + return "`" + identifier.replace("`", "\\`") + "`"; } @Override @@ -156,7 +157,7 @@ public String getStringLiteral(final String value) { if (value == null) { return "NULL"; } else { - return "'" + value.replace("'", "\\'") + "'"; + return "'" + value.replace("\\", "\\\\").replace("'", "\\'") + "'"; } } } \ No newline at end of file diff --git a/src/main/java/com/exasol/adapter/dialects/db2/DB2SqlDialect.java b/src/main/java/com/exasol/adapter/dialects/db2/DB2SqlDialect.java index 6f5b9ebbd..d2d3a5a34 100644 --- a/src/main/java/com/exasol/adapter/dialects/db2/DB2SqlDialect.java +++ b/src/main/java/com/exasol/adapter/dialects/db2/DB2SqlDialect.java @@ -99,10 +99,9 @@ public StructureElementSupport supportsJdbcSchemas() { } @Override - @SuppressWarnings("squid:S1185") // https://www.ibm.com/support/knowledgecenter/SSEPEK_10.0.0/sqlref/src/tpc/db2z_sqlidentifiers.html public String applyQuote(final String identifier) { - return super.applyQuote(identifier); + return super.quoteIdentifierWithDoubleQuotes(identifier); } @Override @@ -124,4 +123,9 @@ public SqlNodeVisitor getSqlGenerationVisitor(final SqlGenerationContext public NullSorting getDefaultNullSorting() { return NullSorting.NULLS_SORTED_AT_END; } + + @Override + public String getStringLiteral(final String value) { + return super.quoteLiteralStringWithSingleQuote(value); + } } \ No newline at end of file diff --git a/src/main/java/com/exasol/adapter/dialects/generic/GenericSqlDialect.java b/src/main/java/com/exasol/adapter/dialects/generic/GenericSqlDialect.java index 6583204ae..102cec634 100644 --- a/src/main/java/com/exasol/adapter/dialects/generic/GenericSqlDialect.java +++ b/src/main/java/com/exasol/adapter/dialects/generic/GenericSqlDialect.java @@ -86,6 +86,11 @@ public NullSorting getDefaultNullSorting() { } } + @Override + public String getStringLiteral(final String value) { + return this.quoteLiteralStringWithSingleQuote(value); + } + @Override protected RemoteMetadataReader createRemoteMetadataReader() { try { diff --git a/src/main/java/com/exasol/adapter/dialects/hive/HiveSqlDialect.java b/src/main/java/com/exasol/adapter/dialects/hive/HiveSqlDialect.java index 3b8496f99..ee861e5e9 100644 --- a/src/main/java/com/exasol/adapter/dialects/hive/HiveSqlDialect.java +++ b/src/main/java/com/exasol/adapter/dialects/hive/HiveSqlDialect.java @@ -105,6 +105,11 @@ public NullSorting getDefaultNullSorting() { return NullSorting.NULLS_SORTED_LOW; } + @Override + public String getStringLiteral(final String value) { + return super.quoteLiteralStringWithSingleQuote(value); + } + @Override public SqlNodeVisitor getSqlGenerationVisitor(final SqlGenerationContext context) { return new HiveSqlGenerationVisitor(this, context); diff --git a/src/main/java/com/exasol/adapter/dialects/impala/ImpalaIdentifier.java b/src/main/java/com/exasol/adapter/dialects/impala/ImpalaIdentifier.java new file mode 100644 index 000000000..f7b430430 --- /dev/null +++ b/src/main/java/com/exasol/adapter/dialects/impala/ImpalaIdentifier.java @@ -0,0 +1,64 @@ +package com.exasol.adapter.dialects.impala; + +import java.util.Objects; + +import com.exasol.db.Identifier; + +/** + * Represents an identifier in the Impala database. + */ +public class ImpalaIdentifier implements Identifier { + private final String id; + + private ImpalaIdentifier(final String id) { + this.id = id; + } + + /** + * Get the quoted identifier as a {@link String}. + * + * @return quoted identifier + */ + @Override + public String quote() { + return "`" + this.id + "`"; + } + + /** + * Create a new {@link ImpalaIdentifier}. + * + * @param id the identifier as {@link String} + * @return new {@link ImpalaIdentifier} instance + */ + public static ImpalaIdentifier of(final String id) { + if (validate(id)) { + return new ImpalaIdentifier(id); + } else { + throw new AssertionError("E-ID-6: Unable to create identifier \"" + id // + + "\" because it contains illegal characters." // + + " For information about valid identifiers, please refer to" // + + " https://docs.cloudera.com/documentation/enterprise/latest/topics/impala_identifiers.html"); + } + } + + private static boolean validate(final String id) { + return !id.contains("`"); + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (!(o instanceof ImpalaIdentifier)) { + return false; + } + final ImpalaIdentifier that = (ImpalaIdentifier) o; + return Objects.equals(this.id, that.id); + } + + @Override + public int hashCode() { + return Objects.hash(this.id); + } +} \ No newline at end of file diff --git a/src/main/java/com/exasol/adapter/dialects/impala/ImpalaSqlDialect.java b/src/main/java/com/exasol/adapter/dialects/impala/ImpalaSqlDialect.java index 77617e165..bf3e9f654 100644 --- a/src/main/java/com/exasol/adapter/dialects/impala/ImpalaSqlDialect.java +++ b/src/main/java/com/exasol/adapter/dialects/impala/ImpalaSqlDialect.java @@ -95,7 +95,7 @@ public StructureElementSupport supportsJdbcSchemas() { @Override // https://docs.cloudera.com/documentation/enterprise/latest/topics/impala_identifiers.html public String applyQuote(final String identifier) { - return "`" + identifier.replace("`", "``") + "`"; + return ImpalaIdentifier.of(identifier).quote(); } @Override @@ -139,7 +139,7 @@ public String getStringLiteral(final String value) { if (value == null) { return "NULL"; } else { - return "'" + value.replace("'", "\\'") + "'"; + return "'" + value.replace("\\", "\\\\").replace("'", "\\'") + "'"; } } } \ No newline at end of file diff --git a/src/main/java/com/exasol/adapter/dialects/mysql/MySqlSqlDialect.java b/src/main/java/com/exasol/adapter/dialects/mysql/MySqlSqlDialect.java index bff716885..b81cf00fd 100644 --- a/src/main/java/com/exasol/adapter/dialects/mysql/MySqlSqlDialect.java +++ b/src/main/java/com/exasol/adapter/dialects/mysql/MySqlSqlDialect.java @@ -83,11 +83,8 @@ public StructureElementSupport supportsJdbcSchemas() { return StructureElementSupport.NONE; } - /** - * @see ANSI quotes (MySQL - * reference manual) - */ @Override + // https://dev.mysql.com/doc/refman/8.0/en/identifiers.html public String applyQuote(final String identifier) { return "`" + identifier.replace("`", "``") + "`"; } @@ -107,6 +104,11 @@ public NullSorting getDefaultNullSorting() { return NullSorting.NULLS_SORTED_AT_END; } + @Override + public String getStringLiteral(final String value) { + return super.quoteLiteralStringWithSingleQuote(value); + } + @Override protected RemoteMetadataReader createRemoteMetadataReader() { try { diff --git a/src/main/java/com/exasol/adapter/dialects/oracle/OracleIdentifier.java b/src/main/java/com/exasol/adapter/dialects/oracle/OracleIdentifier.java new file mode 100644 index 000000000..5d4a78a45 --- /dev/null +++ b/src/main/java/com/exasol/adapter/dialects/oracle/OracleIdentifier.java @@ -0,0 +1,64 @@ +package com.exasol.adapter.dialects.oracle; + +import java.util.Objects; + +import com.exasol.db.Identifier; + +/** + * Represents an identifier in the Oracle database. + */ +public class OracleIdentifier implements Identifier { + private final String id; + + private OracleIdentifier(final String id) { + this.id = id; + } + + /** + * Get the quoted identifier as a {@link String}. + * + * @return quoted identifier + */ + @Override + public String quote() { + return "\"" + this.id + "\""; + } + + /** + * Create a new {@link OracleIdentifier}. + * + * @param id the identifier as {@link String} + * @return new {@link OracleIdentifier} instance + */ + public static OracleIdentifier of(final String id) { + if (validate(id)) { + return new OracleIdentifier(id); + } else { + throw new AssertionError("E-ID-3: Unable to create identifier \"" + id // + + "\" because it contains illegal characters." // + + " For information about valid identifiers, please refer to" // + + " https://docs.oracle.com/cd/B19306_01/server.102/b14200/sql_elements008.htm"); + } + } + + private static boolean validate(final String id) { + return !id.contains("\""); + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (!(o instanceof OracleIdentifier)) { + return false; + } + final OracleIdentifier that = (OracleIdentifier) o; + return Objects.equals(this.id, that.id); + } + + @Override + public int hashCode() { + return Objects.hash(this.id); + } +} \ No newline at end of file diff --git a/src/main/java/com/exasol/adapter/dialects/oracle/OracleSqlDialect.java b/src/main/java/com/exasol/adapter/dialects/oracle/OracleSqlDialect.java index 386c427fc..39c11412c 100644 --- a/src/main/java/com/exasol/adapter/dialects/oracle/OracleSqlDialect.java +++ b/src/main/java/com/exasol/adapter/dialects/oracle/OracleSqlDialect.java @@ -114,10 +114,9 @@ public SqlNodeVisitor getSqlGenerationVisitor(final SqlGenerationContext } @Override - @SuppressWarnings("squid:S1185") // https://docs.oracle.com/cd/B19306_01/server.102/b14200/sql_elements008.htm public String applyQuote(final String identifier) { - return super.applyQuote(identifier); + return OracleIdentifier.of(identifier).quote(); } @Override @@ -135,6 +134,11 @@ public NullSorting getDefaultNullSorting() { return NullSorting.NULLS_SORTED_HIGH; } + @Override + public String getStringLiteral(final String value) { + return super.quoteLiteralStringWithSingleQuote(value); + } + /** * Return the type of import the Oracle dialect uses. * diff --git a/src/main/java/com/exasol/adapter/dialects/postgresql/PostgreSQLSqlDialect.java b/src/main/java/com/exasol/adapter/dialects/postgresql/PostgreSQLSqlDialect.java index dfdfaaa06..ab4d9327c 100644 --- a/src/main/java/com/exasol/adapter/dialects/postgresql/PostgreSQLSqlDialect.java +++ b/src/main/java/com/exasol/adapter/dialects/postgresql/PostgreSQLSqlDialect.java @@ -124,7 +124,7 @@ public String applyQuote(final String identifier) { if (getIdentifierMapping() != PostgreSQLIdentifierMapping.PRESERVE_ORIGINAL_CASE) { postgreSQLIdentifier = convertIdentifierToLowerCase(postgreSQLIdentifier); } - return super.applyQuote(postgreSQLIdentifier); + return super.quoteIdentifierWithDoubleQuotes(postgreSQLIdentifier); } private String convertIdentifierToLowerCase(final String identifier) { @@ -146,6 +146,11 @@ public NullSorting getDefaultNullSorting() { return NullSorting.NULLS_SORTED_AT_END; } + @Override + public String getStringLiteral(final String value) { + return super.quoteLiteralStringWithSingleQuote(value); + } + @Override public SqlNodeVisitor getSqlGenerationVisitor(final SqlGenerationContext context) { return new PostgresSQLSqlGenerationVisitor(this, context); diff --git a/src/main/java/com/exasol/adapter/dialects/redshift/RedshiftSqlDialect.java b/src/main/java/com/exasol/adapter/dialects/redshift/RedshiftSqlDialect.java index 5e5b3a3f3..8a0249730 100644 --- a/src/main/java/com/exasol/adapter/dialects/redshift/RedshiftSqlDialect.java +++ b/src/main/java/com/exasol/adapter/dialects/redshift/RedshiftSqlDialect.java @@ -93,10 +93,9 @@ public StructureElementSupport supportsJdbcSchemas() { } @Override - @SuppressWarnings("squid:S1185") // https://docs.aws.amazon.com/redshift/latest/dg/r_names.html public String applyQuote(final String identifier) { - return super.applyQuote(identifier); + return super.quoteIdentifierWithDoubleQuotes(identifier); } @Override @@ -114,6 +113,11 @@ public NullSorting getDefaultNullSorting() { return NullSorting.NULLS_SORTED_AT_END; } + @Override + public String getStringLiteral(final String value) { + return super.quoteLiteralStringWithSingleQuote(value); + } + @Override public SqlNodeVisitor getSqlGenerationVisitor(final SqlGenerationContext context) { return new RedshiftSqlGenerationVisitor(this, context); diff --git a/src/main/java/com/exasol/adapter/dialects/saphana/SapHanaSqlDialect.java b/src/main/java/com/exasol/adapter/dialects/saphana/SapHanaSqlDialect.java index 3cbf80ceb..eb6ab1f2f 100644 --- a/src/main/java/com/exasol/adapter/dialects/saphana/SapHanaSqlDialect.java +++ b/src/main/java/com/exasol/adapter/dialects/saphana/SapHanaSqlDialect.java @@ -110,9 +110,8 @@ public boolean requiresSchemaQualifiedTableNames(final SqlGenerationContext cont @Override // http://sap.optimieren.de/hana/hana/html/_bsql_introduction.html - @SuppressWarnings("squid:S1185") public String applyQuote(final String identifier) { - return super.applyQuote(identifier); + return super.quoteIdentifierWithDoubleQuotes(identifier); } @Override @@ -120,6 +119,11 @@ public NullSorting getDefaultNullSorting() { return NullSorting.NULLS_SORTED_AT_START; } + @Override + public String getStringLiteral(final String value) { + return super.quoteLiteralStringWithSingleQuote(value); + } + @Override protected RemoteMetadataReader createRemoteMetadataReader() { try { diff --git a/src/main/java/com/exasol/adapter/dialects/sqlserver/SqlServerIdentifier.java b/src/main/java/com/exasol/adapter/dialects/sqlserver/SqlServerIdentifier.java index 34df3ab81..7ea02118c 100644 --- a/src/main/java/com/exasol/adapter/dialects/sqlserver/SqlServerIdentifier.java +++ b/src/main/java/com/exasol/adapter/dialects/sqlserver/SqlServerIdentifier.java @@ -34,10 +34,10 @@ public static SqlServerIdentifier of(final String id) { if (validate(id)) { return new SqlServerIdentifier(id); } else { - throw new AssertionError("E-ID-3: Unable to create identifier \"" + id // + throw new AssertionError("E-ID-4: Unable to create identifier \"" + id // + "\" because it contains illegal characters." // + " For information about valid identifiers, please refer to" // - + " https://docs.microsoft.com/en-us/sql/relational-databases/databases/database-identifiers?redirectedfrom=MSDN&view=sql-server-ver15"); + + " https://docs.microsoft.com/de-de/sql/t-sql/statements/set-quoted-identifier-transact-sql?view=sql-server-ver15"); } } @@ -47,10 +47,12 @@ private static boolean validate(final String id) { @Override public boolean equals(final Object o) { - if (this == o) + if (this == o) { return true; - if (o == null || getClass() != o.getClass()) + } + if (!(o instanceof SqlServerIdentifier)) { return false; + } final SqlServerIdentifier that = (SqlServerIdentifier) o; return Objects.equals(this.id, that.id); } diff --git a/src/main/java/com/exasol/adapter/dialects/sqlserver/SqlServerSqlDialect.java b/src/main/java/com/exasol/adapter/dialects/sqlserver/SqlServerSqlDialect.java index fa7936590..c8b22a713 100644 --- a/src/main/java/com/exasol/adapter/dialects/sqlserver/SqlServerSqlDialect.java +++ b/src/main/java/com/exasol/adapter/dialects/sqlserver/SqlServerSqlDialect.java @@ -114,6 +114,7 @@ public SqlNodeVisitor getSqlGenerationVisitor(final SqlGenerationContext } @Override + // https://docs.microsoft.com/de-de/sql/t-sql/statements/set-quoted-identifier-transact-sql?view=sql-server-ver15 public String applyQuote(final String identifier) { return SqlServerIdentifier.of(identifier).quote(); } @@ -133,6 +134,11 @@ public NullSorting getDefaultNullSorting() { return NullSorting.NULLS_SORTED_AT_START; } + @Override + public String getStringLiteral(final String value) { + return super.quoteLiteralStringWithSingleQuote(value); + } + @Override protected RemoteMetadataReader createRemoteMetadataReader() { try { diff --git a/src/main/java/com/exasol/adapter/dialects/sybase/SybaseIdentifier.java b/src/main/java/com/exasol/adapter/dialects/sybase/SybaseIdentifier.java index f1cbf21d5..1e9e3f750 100644 --- a/src/main/java/com/exasol/adapter/dialects/sybase/SybaseIdentifier.java +++ b/src/main/java/com/exasol/adapter/dialects/sybase/SybaseIdentifier.java @@ -36,7 +36,7 @@ public static SybaseIdentifier of(final String id) { if (validate(id)) { return new SybaseIdentifier(id); } else { - throw new AssertionError("E-ID-3: Unable to create identifier \"" + id // + throw new AssertionError("E-ID-5: Unable to create identifier \"" + id // + "\" because it contains illegal characters." // + " For information about valid identifiers, please refer to" // + " http://infocenter.sybase.com/help/index.jsp?topic=/com.sybase.infocenter.dc36271.1600/doc/html/san1393050529478.html"); @@ -61,10 +61,12 @@ private static boolean validateCharacter(final char ch) { @Override public boolean equals(final Object o) { - if (this == o) + if (this == o) { return true; - if (o == null || getClass() != o.getClass()) + } + if (!(o instanceof SybaseIdentifier)) { return false; + } final SybaseIdentifier that = (SybaseIdentifier) o; return Objects.equals(this.id, that.id); } diff --git a/src/main/java/com/exasol/adapter/dialects/sybase/SybaseSqlDialect.java b/src/main/java/com/exasol/adapter/dialects/sybase/SybaseSqlDialect.java index 4f461dca2..d8023310f 100644 --- a/src/main/java/com/exasol/adapter/dialects/sybase/SybaseSqlDialect.java +++ b/src/main/java/com/exasol/adapter/dialects/sybase/SybaseSqlDialect.java @@ -135,6 +135,11 @@ public NullSorting getDefaultNullSorting() { return NullSorting.NULLS_SORTED_LOW; } + @Override + public String getStringLiteral(final String value) { + return super.quoteLiteralStringWithSingleQuote(value); + } + @Override protected RemoteMetadataReader createRemoteMetadataReader() { try { diff --git a/src/main/java/com/exasol/adapter/dialects/teradata/TeradataSqlDialect.java b/src/main/java/com/exasol/adapter/dialects/teradata/TeradataSqlDialect.java index 71100c2b8..f2b08b78d 100644 --- a/src/main/java/com/exasol/adapter/dialects/teradata/TeradataSqlDialect.java +++ b/src/main/java/com/exasol/adapter/dialects/teradata/TeradataSqlDialect.java @@ -95,10 +95,9 @@ public SqlNodeVisitor getSqlGenerationVisitor(final SqlGenerationContext } @Override - @SuppressWarnings("squid:S1185") - // https://docs.teradata.com/reader/3AkrVQlhjJMha4KRVJmm1w/G7jI6yuymIYaMNVKNlCSWQ + // https://docs.teradata.com/reader/37meaKdwvl0jrhzrc6FoEw/F5dFR63LmiAnvhOd3C9f8w public String applyQuote(final String identifier) { - return super.applyQuote(identifier); + return super.quoteIdentifierWithDoubleQuotes(identifier); } @Override @@ -115,4 +114,9 @@ public boolean requiresSchemaQualifiedTableNames(final SqlGenerationContext cont public NullSorting getDefaultNullSorting() { return NullSorting.NULLS_SORTED_HIGH; } + + @Override + public String getStringLiteral(final String value) { + return super.quoteLiteralStringWithSingleQuote(value); + } } diff --git a/src/test/java/com/exasol/adapter/dialects/athena/AthenaSqlDialectTest.java b/src/test/java/com/exasol/adapter/dialects/athena/AthenaSqlDialectTest.java index ec1983122..1fc6bf44c 100644 --- a/src/test/java/com/exasol/adapter/dialects/athena/AthenaSqlDialectTest.java +++ b/src/test/java/com/exasol/adapter/dialects/athena/AthenaSqlDialectTest.java @@ -110,6 +110,7 @@ void testGetDefaultNullSorting() { @CsvSource({ "tableName, \"tableName\"", // "table123, \"table123\"", // "_table, `_table`", // + "123table, \"123table\"", // "table_name, \"table_name\"" }) @ParameterizedTest void testApplyQuote(final String unquoted, final String quoted) { @@ -119,10 +120,8 @@ void testApplyQuote(final String unquoted, final String quoted) { @ValueSource(strings = { "ab:'ab'", "a'b:'a''b'", "a''b:'a''''b'", "'ab':'''ab'''" }) @ParameterizedTest void testGetLiteralString(final String definition) { - final int colonPosition = definition.indexOf(':'); - final String original = definition.substring(0, colonPosition); - final String literal = definition.substring(colonPosition + 1); - assertThat(this.dialect.getStringLiteral(original), equalTo(literal)); + assertThat(this.dialect.getStringLiteral(definition.substring(0, definition.indexOf(':'))), + equalTo(definition.substring(definition.indexOf(':') + 1))); } @Test diff --git a/src/test/java/com/exasol/adapter/dialects/bigquery/BigQueryIdentifierTest.java b/src/test/java/com/exasol/adapter/dialects/bigquery/BigQueryIdentifierTest.java deleted file mode 100644 index 9ea64619b..000000000 --- a/src/test/java/com/exasol/adapter/dialects/bigquery/BigQueryIdentifierTest.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.exasol.adapter.dialects.bigquery; - -import org.junit.jupiter.api.Test; - -import nl.jqno.equalsverifier.EqualsVerifier; - -class BigQueryIdentifierTest { - @Test - void testEqualsAndHashContract() { - EqualsVerifier.simple().forClass(BigQueryIdentifier.class).verify(); - } -} \ No newline at end of file diff --git a/src/test/java/com/exasol/adapter/dialects/bigquery/BigQuerySqlDialectTest.java b/src/test/java/com/exasol/adapter/dialects/bigquery/BigQuerySqlDialectTest.java index 9f47e0798..10dda698b 100644 --- a/src/test/java/com/exasol/adapter/dialects/bigquery/BigQuerySqlDialectTest.java +++ b/src/test/java/com/exasol/adapter/dialects/bigquery/BigQuerySqlDialectTest.java @@ -139,13 +139,12 @@ void testApplyQuote(final String unquoted, final String quoted) { assertThat(this.dialect.applyQuote(unquoted), equalTo(quoted)); } - @ValueSource(strings = { "ab:'ab'", "a'b:'a\\'b'", "a''b:'a\\'\\'b'", "'ab':'\\'ab\\''" }) + @ValueSource(strings = { "ab:'ab'", "a'b:'a\\'b'", "a''b:'a\\'\\'b'", "'ab':'\\'ab\\''", "a\\b:'a\\\\b'", + "a\\\\b:'a\\\\\\\\b'", "a\\'b:'a\\\\\\'b'" }) @ParameterizedTest void testGetLiteralString(final String definition) { - final int colonPosition = definition.indexOf(':'); - final String original = definition.substring(0, colonPosition); - final String literal = definition.substring(colonPosition + 1); - assertThat(this.dialect.getStringLiteral(original), equalTo(literal)); + assertThat(this.dialect.getStringLiteral(definition.substring(0, definition.indexOf(':'))), + equalTo(definition.substring(definition.indexOf(':') + 1))); } @Test diff --git a/src/test/java/com/exasol/adapter/dialects/db2/DB2SqlDialectTest.java b/src/test/java/com/exasol/adapter/dialects/db2/DB2SqlDialectTest.java index d5df20de0..e5ac2f3a4 100644 --- a/src/test/java/com/exasol/adapter/dialects/db2/DB2SqlDialectTest.java +++ b/src/test/java/com/exasol/adapter/dialects/db2/DB2SqlDialectTest.java @@ -129,10 +129,8 @@ void testApplyQuote(final String unquoted, final String quoted) { @ValueSource(strings = { "ab:'ab'", "a'b:'a''b'", "a''b:'a''''b'", "'ab':'''ab'''" }) @ParameterizedTest void testGetLiteralString(final String definition) { - final int colonPosition = definition.indexOf(':'); - final String original = definition.substring(0, colonPosition); - final String literal = definition.substring(colonPosition + 1); - assertThat(this.dialect.getStringLiteral(original), equalTo(literal)); + assertThat(this.dialect.getStringLiteral(definition.substring(0, definition.indexOf(':'))), + equalTo(definition.substring(definition.indexOf(':') + 1))); } @Test diff --git a/src/test/java/com/exasol/adapter/dialects/dummy/DummySqlDialect.java b/src/test/java/com/exasol/adapter/dialects/dummy/DummySqlDialect.java index 1444fcec8..567d3e7a5 100644 --- a/src/test/java/com/exasol/adapter/dialects/dummy/DummySqlDialect.java +++ b/src/test/java/com/exasol/adapter/dialects/dummy/DummySqlDialect.java @@ -62,6 +62,11 @@ public NullSorting getDefaultNullSorting() { return null; } + @Override + public String getStringLiteral(final String value) { + return super.quoteLiteralStringWithSingleQuote(value); + } + @Override protected RemoteMetadataReader createRemoteMetadataReader() { try { diff --git a/src/test/java/com/exasol/adapter/dialects/impala/ImpalaIdentifierTest.java b/src/test/java/com/exasol/adapter/dialects/impala/ImpalaIdentifierTest.java new file mode 100644 index 000000000..a158fe0b0 --- /dev/null +++ b/src/test/java/com/exasol/adapter/dialects/impala/ImpalaIdentifierTest.java @@ -0,0 +1,29 @@ +package com.exasol.adapter.dialects.impala; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import nl.jqno.equalsverifier.EqualsVerifier; + +class ImpalaIdentifierTest { + @ParameterizedTest + @ValueSource(strings = { "_myunderscoretable", "123columnone", "テスト", "таблица" }) + void testCreateValidIdentifier(final String identifier) { + assertDoesNotThrow(() -> ImpalaIdentifier.of(identifier)); + } + + @ParameterizedTest + @ValueSource(strings = { "test`table", "`table`" }) + void testCreateInvalidIdentifier(final String identifier) { + assertThrows(AssertionError.class, () -> ImpalaIdentifier.of(identifier)); + } + + @Test + void testEqualsAndHashContract() { + EqualsVerifier.simple().forClass(ImpalaIdentifier.class).verify(); + } +} \ No newline at end of file diff --git a/src/test/java/com/exasol/adapter/dialects/impala/ImpalaSqlDialectTest.java b/src/test/java/com/exasol/adapter/dialects/impala/ImpalaSqlDialectTest.java index ea8064fce..bca6aa156 100644 --- a/src/test/java/com/exasol/adapter/dialects/impala/ImpalaSqlDialectTest.java +++ b/src/test/java/com/exasol/adapter/dialects/impala/ImpalaSqlDialectTest.java @@ -12,6 +12,7 @@ import static org.hamcrest.collection.IsIterableContainingInAnyOrder.containsInAnyOrder; import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.jupiter.api.Assertions.assertAll; +import static org.junit.jupiter.api.Assertions.assertThrows; import java.util.HashMap; import java.util.Map; @@ -120,20 +121,26 @@ void testValidateSchemaProperty() throws PropertyValidationException { } @CsvSource({ "tableName, `tableName`", // - "`tableName, ```tableName`" // + "table ' Name, `table ' Name`", // + "table \" Name, `table \" Name`" // }) @ParameterizedTest void testApplyQuote(final String unquoted, final String quoted) { assertThat(this.dialect.applyQuote(unquoted), equalTo(quoted)); } - @ValueSource(strings = { "ab:'ab'", "a'b:'a\\'b'", "a''b:'a\\'\\'b'", "'ab':'\\'ab\\''" }) + @CsvSource({ "`tableName`", "table`Name", "table name`" }) + @ParameterizedTest + void testApplyQuoteThrowsException(final String identifier) { + assertThrows(AssertionError.class, () -> this.dialect.applyQuote(identifier)); + } + + @ValueSource(strings = { "ab:'ab'", "a'b:'a\\'b'", "a''b:'a\\'\\'b'", "'ab':'\\'ab\\''", "a\\b:'a\\\\b'", + "a\\\\b:'a\\\\\\\\b'", "a\\'b:'a\\\\\\'b'" }) @ParameterizedTest void testGetLiteralString(final String definition) { - final int colonPosition = definition.indexOf(':'); - final String original = definition.substring(0, colonPosition); - final String literal = definition.substring(colonPosition + 1); - assertThat(this.dialect.getStringLiteral(original), Matchers.equalTo(literal)); + assertThat(this.dialect.getStringLiteral(definition.substring(0, definition.indexOf(':'))), + equalTo(definition.substring(definition.indexOf(':') + 1))); } @Test diff --git a/src/test/java/com/exasol/adapter/dialects/mysql/MySqlSqlDialectTest.java b/src/test/java/com/exasol/adapter/dialects/mysql/MySqlSqlDialectTest.java index 1cc40c371..6d5791335 100644 --- a/src/test/java/com/exasol/adapter/dialects/mysql/MySqlSqlDialectTest.java +++ b/src/test/java/com/exasol/adapter/dialects/mysql/MySqlSqlDialectTest.java @@ -113,7 +113,8 @@ void testGetDefaultNullSorting() { } @CsvSource({ "tableName, `tableName`", // - "`tableName, ```tableName`" // + "`tableName, ```tableName`", // + "\"tableName, `\"tableName`" // }) @ParameterizedTest void testApplyQuote(final String unquoted, final String quoted) { @@ -123,10 +124,8 @@ void testApplyQuote(final String unquoted, final String quoted) { @ValueSource(strings = { "ab:'ab'", "a'b:'a''b'", "a''b:'a''''b'", "'ab':'''ab'''" }) @ParameterizedTest void testGetLiteralString(final String definition) { - final int colonPosition = definition.indexOf(':'); - final String original = definition.substring(0, colonPosition); - final String literal = definition.substring(colonPosition + 1); - assertThat(this.dialect.getStringLiteral(original), equalTo(literal)); + assertThat(this.dialect.getStringLiteral(definition.substring(0, definition.indexOf(':'))), + Matchers.equalTo(definition.substring(definition.indexOf(':') + 1))); } @Test diff --git a/src/test/java/com/exasol/adapter/dialects/oracle/OracleIdentifierTest.java b/src/test/java/com/exasol/adapter/dialects/oracle/OracleIdentifierTest.java new file mode 100644 index 000000000..6318d6f0a --- /dev/null +++ b/src/test/java/com/exasol/adapter/dialects/oracle/OracleIdentifierTest.java @@ -0,0 +1,29 @@ +package com.exasol.adapter.dialects.oracle; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import nl.jqno.equalsverifier.EqualsVerifier; + +class OracleIdentifierTest { + @ParameterizedTest + @ValueSource(strings = { "_myunderscoretable", "123columnone", "テスト", "таблица" }) + void testCreateValidIdentifier(final String identifier) { + assertDoesNotThrow(() -> OracleIdentifier.of(identifier)); + } + + @ParameterizedTest + @ValueSource(strings = { "\"testtable\"", "test\"table" }) + void testCreateInvalidIdentifier(final String identifier) { + assertThrows(AssertionError.class, () -> OracleIdentifier.of(identifier)); + } + + @Test + void testEqualsAndHashContract() { + EqualsVerifier.simple().forClass(OracleIdentifier.class).verify(); + } +} \ No newline at end of file diff --git a/src/test/java/com/exasol/adapter/dialects/oracle/OracleSqlDialectTest.java b/src/test/java/com/exasol/adapter/dialects/oracle/OracleSqlDialectTest.java index 32301fe43..1e93ae40c 100644 --- a/src/test/java/com/exasol/adapter/dialects/oracle/OracleSqlDialectTest.java +++ b/src/test/java/com/exasol/adapter/dialects/oracle/OracleSqlDialectTest.java @@ -18,7 +18,6 @@ import java.util.Map; -import org.hamcrest.CoreMatchers; import org.hamcrest.MatcherAssert; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -128,19 +127,23 @@ void testQueryRewriterClass() { } @CsvSource({ "tableName, \"tableName\"", // - "\"tableName, \"\"\"tableName\"" // + "table 'Name, \"table 'Name\"" // }) @ParameterizedTest - void testApplyQuote(final String unquoted, final String quoted) { - assertThat(this.dialect.applyQuote(unquoted), equalTo(quoted)); + void testApplyQuote(final String identifier, final String expected) { + assertThat(this.dialect.applyQuote(identifier), equalTo(expected)); + } + + @CsvSource({ "\"tableName\"", "table\"Name", "table name\"" }) + @ParameterizedTest + void testApplyQuoteThrowsException(final String identifier) { + assertThrows(AssertionError.class, () -> this.dialect.applyQuote(identifier)); } @ValueSource(strings = { "ab:'ab'", "a'b:'a''b'", "a''b:'a''''b'", "'ab':'''ab'''" }) @ParameterizedTest void testGetLiteralString(final String definition) { - final int colonPosition = definition.indexOf(':'); - final String original = definition.substring(0, colonPosition); - final String literal = definition.substring(colonPosition + 1); - assertThat(this.dialect.getStringLiteral(original), CoreMatchers.equalTo(literal)); + assertThat(this.dialect.getStringLiteral(definition.substring(0, definition.indexOf(':'))), + equalTo(definition.substring(definition.indexOf(':') + 1))); } } \ No newline at end of file diff --git a/src/test/java/com/exasol/adapter/dialects/postgresql/PostgreSQLIdentifierConverterTest.java b/src/test/java/com/exasol/adapter/dialects/postgresql/PostgreSQLIdentifierConverterTest.java index 2c1bd8f9c..e23ed3569 100644 --- a/src/test/java/com/exasol/adapter/dialects/postgresql/PostgreSQLIdentifierConverterTest.java +++ b/src/test/java/com/exasol/adapter/dialects/postgresql/PostgreSQLIdentifierConverterTest.java @@ -1,8 +1,8 @@ package com.exasol.adapter.dialects.postgresql; import static com.exasol.adapter.dialects.postgresql.PostgreSQLSqlDialect.POSTGRESQL_IDENTIFIER_MAPPING_PROPERTY; -import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; import java.util.HashMap; import java.util.Map; @@ -13,7 +13,7 @@ import com.exasol.adapter.AdapterProperties; -public class PostgreSQLIdentifierConverterTest { +class PostgreSQLIdentifierConverterTest { private Map rawProperties; @BeforeEach diff --git a/src/test/java/com/exasol/adapter/dialects/postgresql/PostgreSQLSqlDialectTest.java b/src/test/java/com/exasol/adapter/dialects/postgresql/PostgreSQLSqlDialectTest.java index 0fee06a3e..b51339c5e 100644 --- a/src/test/java/com/exasol/adapter/dialects/postgresql/PostgreSQLSqlDialectTest.java +++ b/src/test/java/com/exasol/adapter/dialects/postgresql/PostgreSQLSqlDialectTest.java @@ -99,10 +99,8 @@ void testApplyQuote(final String unquoted, final String quoted) { @ValueSource(strings = { "ab:'ab'", "a'b:'a''b'", "a''b:'a''''b'", "'ab':'''ab'''" }) @ParameterizedTest void testGetLiteralString(final String definition) { - final int colonPosition = definition.indexOf(':'); - final String original = definition.substring(0, colonPosition); - final String literal = definition.substring(colonPosition + 1); - assertThat(this.dialect.getStringLiteral(original), CoreMatchers.equalTo(literal)); + assertThat(this.dialect.getStringLiteral(definition.substring(0, definition.indexOf(':'))), + equalTo(definition.substring(definition.indexOf(':') + 1))); } @Test diff --git a/src/test/java/com/exasol/adapter/dialects/redshift/RedshiftSqlDialectTest.java b/src/test/java/com/exasol/adapter/dialects/redshift/RedshiftSqlDialectTest.java index 7a542d94f..ce3284b0b 100644 --- a/src/test/java/com/exasol/adapter/dialects/redshift/RedshiftSqlDialectTest.java +++ b/src/test/java/com/exasol/adapter/dialects/redshift/RedshiftSqlDialectTest.java @@ -128,10 +128,8 @@ void testApplyQuote(final String unquoted, final String quoted) { @ValueSource(strings = { "ab:'ab'", "a'b:'a''b'", "a''b:'a''''b'", "'ab':'''ab'''" }) @ParameterizedTest void testGetLiteralString(final String definition) { - final int colonPosition = definition.indexOf(':'); - final String original = definition.substring(0, colonPosition); - final String literal = definition.substring(colonPosition + 1); - assertThat(this.dialect.getStringLiteral(original), CoreMatchers.equalTo(literal)); + assertThat(this.dialect.getStringLiteral(definition.substring(0, definition.indexOf(':'))), + equalTo(definition.substring(definition.indexOf(':') + 1))); } @Test diff --git a/src/test/java/com/exasol/adapter/dialects/saphana/SapHanaSqlDialectTest.java b/src/test/java/com/exasol/adapter/dialects/saphana/SapHanaSqlDialectTest.java index 7d250ad07..dc55d639c 100644 --- a/src/test/java/com/exasol/adapter/dialects/saphana/SapHanaSqlDialectTest.java +++ b/src/test/java/com/exasol/adapter/dialects/saphana/SapHanaSqlDialectTest.java @@ -132,10 +132,8 @@ void testApplyQuote(final String unquoted, final String quoted) { @ValueSource(strings = { "ab:'ab'", "a'b:'a''b'", "a''b:'a''''b'", "'ab':'''ab'''" }) @ParameterizedTest void testGetLiteralString(final String definition) { - final int colonPosition = definition.indexOf(':'); - final String original = definition.substring(0, colonPosition); - final String literal = definition.substring(colonPosition + 1); - assertThat(this.dialect.getStringLiteral(original), CoreMatchers.equalTo(literal)); + assertThat(this.dialect.getStringLiteral(definition.substring(0, definition.indexOf(':'))), + Matchers.equalTo(definition.substring(definition.indexOf(':') + 1))); } @Test diff --git a/src/test/java/com/exasol/adapter/dialects/sqlserver/SqlServerSqlDialectTest.java b/src/test/java/com/exasol/adapter/dialects/sqlserver/SqlServerSqlDialectTest.java index 19d870712..e63effb5d 100644 --- a/src/test/java/com/exasol/adapter/dialects/sqlserver/SqlServerSqlDialectTest.java +++ b/src/test/java/com/exasol/adapter/dialects/sqlserver/SqlServerSqlDialectTest.java @@ -9,10 +9,10 @@ import static com.exasol.adapter.capabilities.ScalarFunctionCapability.ST_INTERSECTION; import static com.exasol.adapter.capabilities.ScalarFunctionCapability.ST_UNION; import static com.exasol.reflect.ReflectionUtils.getMethodReturnViaReflection; +import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.instanceOf; import static org.hamcrest.collection.IsIterableContainingInAnyOrder.containsInAnyOrder; -import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.jupiter.api.Assertions.assertAll; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -140,23 +140,23 @@ void testGetAggregateFunctionAliases() { Matchers.equalTo("VARP"))); } - @Test - void testApplyQuote() { - assertThat(this.dialect.applyQuote("tableName"), equalTo("[tableName]")); + @CsvSource({ "tableName, [tableName]", "table \"name, [table \"name]" }) + @ParameterizedTest + void testApplyQuote(final String identifier, final String expected) { + assertThat(this.dialect.applyQuote(identifier), equalTo(expected)); } - @Test - void testApplyQuoteThrowsException() { - assertThrows(AssertionError.class, () -> this.dialect.applyQuote("[tableName]")); + @CsvSource({ "[tableName]", "[table name", "table name]", "table[name", "table]name" }) + @ParameterizedTest + void testApplyQuoteThrowsException(final String identifier) { + assertThrows(AssertionError.class, () -> this.dialect.applyQuote(identifier)); } @ValueSource(strings = { "ab:'ab'", "a'b:'a''b'", "a''b:'a''''b'", "'ab':'''ab'''" }) @ParameterizedTest void testGetLiteralString(final String definition) { - final int colonPosition = definition.indexOf(':'); - final String original = definition.substring(0, colonPosition); - final String literal = definition.substring(colonPosition + 1); - assertThat(this.dialect.getStringLiteral(original), CoreMatchers.equalTo(literal)); + assertThat(this.dialect.getStringLiteral(definition.substring(0, definition.indexOf(':'))), + equalTo(definition.substring(definition.indexOf(':') + 1))); } @Test diff --git a/src/test/java/com/exasol/adapter/dialects/sybase/SybaseSqlDialectTest.java b/src/test/java/com/exasol/adapter/dialects/sybase/SybaseSqlDialectTest.java index f33c17b04..b5a2b606a 100644 --- a/src/test/java/com/exasol/adapter/dialects/sybase/SybaseSqlDialectTest.java +++ b/src/test/java/com/exasol/adapter/dialects/sybase/SybaseSqlDialectTest.java @@ -30,6 +30,7 @@ import com.exasol.adapter.sql.AggregateFunction; import com.exasol.adapter.sql.ScalarFunction; import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; import org.junit.jupiter.params.provider.ValueSource; class SybaseSqlDialectTest { @@ -138,18 +139,17 @@ void testApplyQuote() { assertThat(this.dialect.applyQuote("tableName"), equalTo("[tableName]")); } - @Test - void testApplyQuoteThrowsException() { - assertThrows(AssertionError.class, () -> this.dialect.applyQuote("[tableName]")); + @CsvSource({ "[tableName]", "[table name", "table name]", "table[name", "table]name", "table \"name" }) + @ParameterizedTest + void testApplyQuoteThrowsException(final String identifier) { + assertThrows(AssertionError.class, () -> this.dialect.applyQuote(identifier)); } @ValueSource(strings = { "ab:'ab'", "a'b:'a''b'", "a''b:'a''''b'", "'ab':'''ab'''" }) @ParameterizedTest void testGetLiteralString(final String definition) { - final int colonPosition = definition.indexOf(':'); - final String original = definition.substring(0, colonPosition); - final String literal = definition.substring(colonPosition + 1); - assertThat(this.dialect.getStringLiteral(original), CoreMatchers.equalTo(literal)); + assertThat(this.dialect.getStringLiteral(definition.substring(0, definition.indexOf(':'))), + equalTo(definition.substring(definition.indexOf(':') + 1))); } @Test diff --git a/src/test/java/com/exasol/adapter/dialects/teradata/TeradataSqlDialectTest.java b/src/test/java/com/exasol/adapter/dialects/teradata/TeradataSqlDialectTest.java index 36834580e..e2d5b3654 100644 --- a/src/test/java/com/exasol/adapter/dialects/teradata/TeradataSqlDialectTest.java +++ b/src/test/java/com/exasol/adapter/dialects/teradata/TeradataSqlDialectTest.java @@ -116,10 +116,8 @@ void testApplyQuote(final String unquoted, final String quoted) { @ValueSource(strings = { "ab:'ab'", "a'b:'a''b'", "a''b:'a''''b'", "'ab':'''ab'''" }) @ParameterizedTest void testGetLiteralString(final String definition) { - final int colonPosition = definition.indexOf(':'); - final String original = definition.substring(0, colonPosition); - final String literal = definition.substring(colonPosition + 1); - assertThat(this.dialect.getStringLiteral(original), CoreMatchers.equalTo(literal)); + assertThat(this.dialect.getStringLiteral(definition.substring(0, definition.indexOf(':'))), + equalTo(definition.substring(definition.indexOf(':') + 1))); } @Test From 5a157a9525b78997c0cb3f5909de2696c397c8d0 Mon Sep 17 00:00:00 2001 From: anastasiiasergienko Date: Mon, 9 Nov 2020 17:42:30 +0100 Subject: [PATCH 03/12] * #401: Fixed review findings --- .../dialects/athena/AthenaSqlDialect.java | 1 + .../dialects/bigquery/BigQuerySqlDialect.java | 1 + .../adapter/dialects/db2/DB2SqlDialect.java | 3 ++- .../adapter/dialects/hive/HiveSqlDialect.java | 7 ++++++- .../adapter/dialects/mysql/MySqlSqlDialect.java | 1 + .../dialects/oracle/OracleSqlDialect.java | 1 + .../postgresql/PostgreSQLSqlDialect.java | 1 + .../dialects/redshift/RedshiftSqlDialect.java | 1 + .../dialects/saphana/SapHanaSqlDialect.java | 1 + .../dialects/sqlserver/SqlServerSqlDialect.java | 1 + .../dialects/sybase/SybaseSqlDialect.java | 1 + .../dialects/teradata/TeradataSqlDialect.java | 1 + .../dialects/hive/HiveSqlDialectTest.java | 17 +++++++++++++---- 13 files changed, 31 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/exasol/adapter/dialects/athena/AthenaSqlDialect.java b/src/main/java/com/exasol/adapter/dialects/athena/AthenaSqlDialect.java index b0e13e625..646bff272 100644 --- a/src/main/java/com/exasol/adapter/dialects/athena/AthenaSqlDialect.java +++ b/src/main/java/com/exasol/adapter/dialects/athena/AthenaSqlDialect.java @@ -118,6 +118,7 @@ public NullSorting getDefaultNullSorting() { } @Override + // https://docs.aws.amazon.com/athena/latest/ug/select.html public String getStringLiteral(final String value) { return super.quoteLiteralStringWithSingleQuote(value); } diff --git a/src/main/java/com/exasol/adapter/dialects/bigquery/BigQuerySqlDialect.java b/src/main/java/com/exasol/adapter/dialects/bigquery/BigQuerySqlDialect.java index fcb92bae5..7ac5a9e08 100644 --- a/src/main/java/com/exasol/adapter/dialects/bigquery/BigQuerySqlDialect.java +++ b/src/main/java/com/exasol/adapter/dialects/bigquery/BigQuerySqlDialect.java @@ -153,6 +153,7 @@ public void validateProperties() throws PropertyValidationException { } @Override + // https://cloud.google.com/bigquery/docs/reference/standard-sql/lexical public String getStringLiteral(final String value) { if (value == null) { return "NULL"; diff --git a/src/main/java/com/exasol/adapter/dialects/db2/DB2SqlDialect.java b/src/main/java/com/exasol/adapter/dialects/db2/DB2SqlDialect.java index d2d3a5a34..677a83f50 100644 --- a/src/main/java/com/exasol/adapter/dialects/db2/DB2SqlDialect.java +++ b/src/main/java/com/exasol/adapter/dialects/db2/DB2SqlDialect.java @@ -99,7 +99,7 @@ public StructureElementSupport supportsJdbcSchemas() { } @Override - // https://www.ibm.com/support/knowledgecenter/SSEPEK_10.0.0/sqlref/src/tpc/db2z_sqlidentifiers.html + // https://www.ibm.com/support/knowledgecenter/SSEPGG_11.5.0/com.ibm.db2.luw.sql.ref.doc/doc/r0000720.html public String applyQuote(final String identifier) { return super.quoteIdentifierWithDoubleQuotes(identifier); } @@ -125,6 +125,7 @@ public NullSorting getDefaultNullSorting() { } @Override + // https://www.ibm.com/support/knowledgecenter/SSEPGG_11.5.0/com.ibm.db2.luw.sql.ref.doc/doc/r0008470.html public String getStringLiteral(final String value) { return super.quoteLiteralStringWithSingleQuote(value); } diff --git a/src/main/java/com/exasol/adapter/dialects/hive/HiveSqlDialect.java b/src/main/java/com/exasol/adapter/dialects/hive/HiveSqlDialect.java index ee861e5e9..4f29706ce 100644 --- a/src/main/java/com/exasol/adapter/dialects/hive/HiveSqlDialect.java +++ b/src/main/java/com/exasol/adapter/dialects/hive/HiveSqlDialect.java @@ -106,8 +106,13 @@ public NullSorting getDefaultNullSorting() { } @Override + // https://docs.cloudera.com/documentation/enterprise/5-9-x/topics/impala_literals.html#string_literals public String getStringLiteral(final String value) { - return super.quoteLiteralStringWithSingleQuote(value); + if (value == null) { + return "NULL"; + } else { + return "'" + value.replace("\\", "\\\\").replace("'", "\\'") + "'"; + } } @Override diff --git a/src/main/java/com/exasol/adapter/dialects/mysql/MySqlSqlDialect.java b/src/main/java/com/exasol/adapter/dialects/mysql/MySqlSqlDialect.java index b81cf00fd..fd6c0822b 100644 --- a/src/main/java/com/exasol/adapter/dialects/mysql/MySqlSqlDialect.java +++ b/src/main/java/com/exasol/adapter/dialects/mysql/MySqlSqlDialect.java @@ -105,6 +105,7 @@ public NullSorting getDefaultNullSorting() { } @Override + // https://dev.mysql.com/doc/refman/8.0/en/string-literals.html public String getStringLiteral(final String value) { return super.quoteLiteralStringWithSingleQuote(value); } diff --git a/src/main/java/com/exasol/adapter/dialects/oracle/OracleSqlDialect.java b/src/main/java/com/exasol/adapter/dialects/oracle/OracleSqlDialect.java index 39c11412c..8e6ee08d9 100644 --- a/src/main/java/com/exasol/adapter/dialects/oracle/OracleSqlDialect.java +++ b/src/main/java/com/exasol/adapter/dialects/oracle/OracleSqlDialect.java @@ -135,6 +135,7 @@ public NullSorting getDefaultNullSorting() { } @Override + // https://docs.oracle.com/cd/B19306_01/server.102/b14200/sql_elements003.htm public String getStringLiteral(final String value) { return super.quoteLiteralStringWithSingleQuote(value); } diff --git a/src/main/java/com/exasol/adapter/dialects/postgresql/PostgreSQLSqlDialect.java b/src/main/java/com/exasol/adapter/dialects/postgresql/PostgreSQLSqlDialect.java index ab4d9327c..11683edd4 100644 --- a/src/main/java/com/exasol/adapter/dialects/postgresql/PostgreSQLSqlDialect.java +++ b/src/main/java/com/exasol/adapter/dialects/postgresql/PostgreSQLSqlDialect.java @@ -147,6 +147,7 @@ public NullSorting getDefaultNullSorting() { } @Override + // https://www.postgresql.org/docs/9.2/sql-syntax-lexical.html public String getStringLiteral(final String value) { return super.quoteLiteralStringWithSingleQuote(value); } diff --git a/src/main/java/com/exasol/adapter/dialects/redshift/RedshiftSqlDialect.java b/src/main/java/com/exasol/adapter/dialects/redshift/RedshiftSqlDialect.java index 8a0249730..de2101ec8 100644 --- a/src/main/java/com/exasol/adapter/dialects/redshift/RedshiftSqlDialect.java +++ b/src/main/java/com/exasol/adapter/dialects/redshift/RedshiftSqlDialect.java @@ -114,6 +114,7 @@ public NullSorting getDefaultNullSorting() { } @Override + // https://docs.aws.amazon.com/redshift/latest/dg/r_Examples_with_character_types.html public String getStringLiteral(final String value) { return super.quoteLiteralStringWithSingleQuote(value); } diff --git a/src/main/java/com/exasol/adapter/dialects/saphana/SapHanaSqlDialect.java b/src/main/java/com/exasol/adapter/dialects/saphana/SapHanaSqlDialect.java index eb6ab1f2f..503e98e93 100644 --- a/src/main/java/com/exasol/adapter/dialects/saphana/SapHanaSqlDialect.java +++ b/src/main/java/com/exasol/adapter/dialects/saphana/SapHanaSqlDialect.java @@ -120,6 +120,7 @@ public NullSorting getDefaultNullSorting() { } @Override + // https://help.sap.com/viewer/4fe29514fd584807ac9f2a04f6754767/2.0.04/en-US/885b059cc20340979c8927c9301ceb63.html public String getStringLiteral(final String value) { return super.quoteLiteralStringWithSingleQuote(value); } diff --git a/src/main/java/com/exasol/adapter/dialects/sqlserver/SqlServerSqlDialect.java b/src/main/java/com/exasol/adapter/dialects/sqlserver/SqlServerSqlDialect.java index c8b22a713..6d5dd7898 100644 --- a/src/main/java/com/exasol/adapter/dialects/sqlserver/SqlServerSqlDialect.java +++ b/src/main/java/com/exasol/adapter/dialects/sqlserver/SqlServerSqlDialect.java @@ -135,6 +135,7 @@ public NullSorting getDefaultNullSorting() { } @Override + // https://docs.microsoft.com/en-us/sql/t-sql/data-types/constants-transact-sql?view=sql-server-ver15 public String getStringLiteral(final String value) { return super.quoteLiteralStringWithSingleQuote(value); } diff --git a/src/main/java/com/exasol/adapter/dialects/sybase/SybaseSqlDialect.java b/src/main/java/com/exasol/adapter/dialects/sybase/SybaseSqlDialect.java index d8023310f..b65787b58 100644 --- a/src/main/java/com/exasol/adapter/dialects/sybase/SybaseSqlDialect.java +++ b/src/main/java/com/exasol/adapter/dialects/sybase/SybaseSqlDialect.java @@ -136,6 +136,7 @@ public NullSorting getDefaultNullSorting() { } @Override + // http://infocenter.sybase.com/help/index.jsp?topic=/com.sybase.infocenter.dc01031.0400/doc/html/asc1252677176370.html public String getStringLiteral(final String value) { return super.quoteLiteralStringWithSingleQuote(value); } diff --git a/src/main/java/com/exasol/adapter/dialects/teradata/TeradataSqlDialect.java b/src/main/java/com/exasol/adapter/dialects/teradata/TeradataSqlDialect.java index f2b08b78d..dc50c4276 100644 --- a/src/main/java/com/exasol/adapter/dialects/teradata/TeradataSqlDialect.java +++ b/src/main/java/com/exasol/adapter/dialects/teradata/TeradataSqlDialect.java @@ -116,6 +116,7 @@ public NullSorting getDefaultNullSorting() { } @Override + // https://docs.teradata.com/reader/S0Fw2AVH8ff3MDA0wDOHlQ/74_UPfEbj2v5Yfny_Go8ig public String getStringLiteral(final String value) { return super.quoteLiteralStringWithSingleQuote(value); } diff --git a/src/test/java/com/exasol/adapter/dialects/hive/HiveSqlDialectTest.java b/src/test/java/com/exasol/adapter/dialects/hive/HiveSqlDialectTest.java index 6f4d8cbc0..54c9d735d 100644 --- a/src/test/java/com/exasol/adapter/dialects/hive/HiveSqlDialectTest.java +++ b/src/test/java/com/exasol/adapter/dialects/hive/HiveSqlDialectTest.java @@ -24,6 +24,7 @@ import com.exasol.adapter.dialects.SqlDialect; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; +import org.junit.jupiter.params.provider.ValueSource; class HiveSqlDialectTest { private SqlDialect dialect; @@ -64,7 +65,7 @@ void testGetCapabilities() { @Test void testValidateCatalogProperty() throws PropertyValidationException { - setMandatoryProperties("HIVE"); + setMandatoryProperties(); this.rawProperties.put(CATALOG_NAME_PROPERTY, "MY_CATALOG"); final AdapterProperties adapterProperties = new AdapterProperties(this.rawProperties); final SqlDialect sqlDialect = new HiveSqlDialect(null, adapterProperties); @@ -73,7 +74,7 @@ void testValidateCatalogProperty() throws PropertyValidationException { @Test void testValidateSchemaProperty() throws PropertyValidationException { - setMandatoryProperties("HIVE"); + setMandatoryProperties(); this.rawProperties.put(SCHEMA_NAME_PROPERTY, "MY_SCHEMA"); final AdapterProperties adapterProperties = new AdapterProperties(this.rawProperties); final SqlDialect sqlDialect = new HiveSqlDialect(null, adapterProperties); @@ -88,8 +89,16 @@ void testApplyQuote(final String unquoted, final String quoted) { assertThat(this.dialect.applyQuote(unquoted), equalTo(quoted)); } - private void setMandatoryProperties(final String sqlDialectProperty) { - this.rawProperties.put(AdapterProperties.SQL_DIALECT_PROPERTY, sqlDialectProperty); + @ValueSource(strings = { "ab:'ab'", "a'b:'a\\'b'", "a''b:'a\\'\\'b'", "'ab':'\\'ab\\''", "a\\b:'a\\\\b'", + "a\\\\b:'a\\\\\\\\b'", "a\\'b:'a\\\\\\'b'" }) + @ParameterizedTest + void testGetLiteralString(final String definition) { + assertThat(this.dialect.getStringLiteral(definition.substring(0, definition.indexOf(':'))), + equalTo(definition.substring(definition.indexOf(':') + 1))); + } + + private void setMandatoryProperties() { + this.rawProperties.put(AdapterProperties.SQL_DIALECT_PROPERTY, "HIVE"); this.rawProperties.put(AdapterProperties.CONNECTION_NAME_PROPERTY, "MY_CONN"); } } \ No newline at end of file From 921609524336bf4137d39a5c07631c98b02f9a25 Mon Sep 17 00:00:00 2001 From: Anastasiia Sergienko <46891819+AnastasiiaSergienko@users.noreply.github.com> Date: Tue, 10 Nov 2020 08:39:09 +0100 Subject: [PATCH 04/12] Apply suggestions from code review Co-authored-by: exanm <48916233+exanm@users.noreply.github.com> --- .../com/exasol/adapter/dialects/impala/ImpalaIdentifier.java | 4 ++-- .../com/exasol/adapter/dialects/mysql/MySqlSqlDialect.java | 4 ++-- .../exasol/adapter/dialects/mysql/MySqlSqlDialectTest.java | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/exasol/adapter/dialects/impala/ImpalaIdentifier.java b/src/main/java/com/exasol/adapter/dialects/impala/ImpalaIdentifier.java index f7b430430..2213d25f7 100644 --- a/src/main/java/com/exasol/adapter/dialects/impala/ImpalaIdentifier.java +++ b/src/main/java/com/exasol/adapter/dialects/impala/ImpalaIdentifier.java @@ -37,7 +37,7 @@ public static ImpalaIdentifier of(final String id) { throw new AssertionError("E-ID-6: Unable to create identifier \"" + id // + "\" because it contains illegal characters." // + " For information about valid identifiers, please refer to" // - + " https://docs.cloudera.com/documentation/enterprise/latest/topics/impala_identifiers.html"); + + " https://docs.cloudera.com/documentation/enterprise/latest/topics/impala_identifiers.html"); } } @@ -61,4 +61,4 @@ public boolean equals(final Object o) { public int hashCode() { return Objects.hash(this.id); } -} \ No newline at end of file +} diff --git a/src/main/java/com/exasol/adapter/dialects/mysql/MySqlSqlDialect.java b/src/main/java/com/exasol/adapter/dialects/mysql/MySqlSqlDialect.java index fd6c0822b..2fe36c00a 100644 --- a/src/main/java/com/exasol/adapter/dialects/mysql/MySqlSqlDialect.java +++ b/src/main/java/com/exasol/adapter/dialects/mysql/MySqlSqlDialect.java @@ -107,7 +107,7 @@ public NullSorting getDefaultNullSorting() { @Override // https://dev.mysql.com/doc/refman/8.0/en/string-literals.html public String getStringLiteral(final String value) { - return super.quoteLiteralStringWithSingleQuote(value); + return super.quoteLiteralStringWithSingleQuote(value.replace("\\", "\\\\")); } @Override @@ -129,4 +129,4 @@ protected QueryRewriter createQueryRewriter() { public SqlNodeVisitor getSqlGenerationVisitor(final SqlGenerationContext context) { return new MySqlSqlGenerationVisitor(this, context); } -} \ No newline at end of file +} diff --git a/src/test/java/com/exasol/adapter/dialects/mysql/MySqlSqlDialectTest.java b/src/test/java/com/exasol/adapter/dialects/mysql/MySqlSqlDialectTest.java index 6d5791335..974d2c648 100644 --- a/src/test/java/com/exasol/adapter/dialects/mysql/MySqlSqlDialectTest.java +++ b/src/test/java/com/exasol/adapter/dialects/mysql/MySqlSqlDialectTest.java @@ -121,7 +121,7 @@ void testApplyQuote(final String unquoted, final String quoted) { assertThat(this.dialect.applyQuote(unquoted), equalTo(quoted)); } - @ValueSource(strings = { "ab:'ab'", "a'b:'a''b'", "a''b:'a''''b'", "'ab':'''ab'''" }) + @ValueSource(strings = { "ab:'ab'", "a'b:'a''b'", "a''b:'a''''b'", "'ab':'''ab'''", "a\\b:'a\\\\b'", "a\\\\b:'a\\\\\\\\b'", "a\\'b:'a\\\\''b'" }) @ParameterizedTest void testGetLiteralString(final String definition) { assertThat(this.dialect.getStringLiteral(definition.substring(0, definition.indexOf(':'))), @@ -146,4 +146,4 @@ void testGetSupportedProperties() { void testGetSqlGenerationVisitor() { assertThat(this.dialect.getSqlGenerationVisitor(null), instanceOf(MySqlSqlGenerationVisitor.class)); } -} \ No newline at end of file +} From f4a34eca8b8cd87529b2f52d85d161ec587f2313 Mon Sep 17 00:00:00 2001 From: Anastasiia Sergienko <46891819+AnastasiiaSergienko@users.noreply.github.com> Date: Wed, 11 Nov 2020 11:28:10 +0100 Subject: [PATCH 05/12] Apply suggestions from code review Co-authored-by: exanm <48916233+exanm@users.noreply.github.com> --- .../exasol/adapter/dialects/db2/DB2SqlDialect.java | 4 ++-- .../exasol/adapter/dialects/hive/HiveSqlDialect.java | 5 +++-- .../dialects/postgresql/PostgreSQLSqlDialect.java | 12 ++++++++---- .../adapter/dialects/saphana/SapHanaSqlDialect.java | 4 ++-- 4 files changed, 15 insertions(+), 10 deletions(-) diff --git a/src/main/java/com/exasol/adapter/dialects/db2/DB2SqlDialect.java b/src/main/java/com/exasol/adapter/dialects/db2/DB2SqlDialect.java index 677a83f50..d83b9a6c4 100644 --- a/src/main/java/com/exasol/adapter/dialects/db2/DB2SqlDialect.java +++ b/src/main/java/com/exasol/adapter/dialects/db2/DB2SqlDialect.java @@ -125,8 +125,8 @@ public NullSorting getDefaultNullSorting() { } @Override - // https://www.ibm.com/support/knowledgecenter/SSEPGG_11.5.0/com.ibm.db2.luw.sql.ref.doc/doc/r0008470.html + // https://www.ibm.com/support/knowledgecenter/SSEPGG_11.5.0/com.ibm.db2.luw.sql.ref.doc/doc/r0000731.html public String getStringLiteral(final String value) { return super.quoteLiteralStringWithSingleQuote(value); } -} \ No newline at end of file +} diff --git a/src/main/java/com/exasol/adapter/dialects/hive/HiveSqlDialect.java b/src/main/java/com/exasol/adapter/dialects/hive/HiveSqlDialect.java index 4f29706ce..a7d77ca66 100644 --- a/src/main/java/com/exasol/adapter/dialects/hive/HiveSqlDialect.java +++ b/src/main/java/com/exasol/adapter/dialects/hive/HiveSqlDialect.java @@ -106,7 +106,8 @@ public NullSorting getDefaultNullSorting() { } @Override - // https://docs.cloudera.com/documentation/enterprise/5-9-x/topics/impala_literals.html#string_literals + // https://cwiki.apache.org/confluence/display/Hive/LanguageManual+Types#LanguageManualTypes-StringsstringStrings + // https://cwiki.apache.org/confluence/display/Hive/CAST...FORMAT+with+SQL%3A2016+datetime+formats public String getStringLiteral(final String value) { if (value == null) { return "NULL"; @@ -150,4 +151,4 @@ public void validateProperties() throws PropertyValidationException { protected QueryRewriter createQueryRewriter() { return new BaseQueryRewriter(this, createRemoteMetadataReader(), this.connectionFactory); } -} \ No newline at end of file +} diff --git a/src/main/java/com/exasol/adapter/dialects/postgresql/PostgreSQLSqlDialect.java b/src/main/java/com/exasol/adapter/dialects/postgresql/PostgreSQLSqlDialect.java index 11683edd4..48eb7199b 100644 --- a/src/main/java/com/exasol/adapter/dialects/postgresql/PostgreSQLSqlDialect.java +++ b/src/main/java/com/exasol/adapter/dialects/postgresql/PostgreSQLSqlDialect.java @@ -118,7 +118,7 @@ public StructureElementSupport supportsJdbcSchemas() { } @Override - // https://www.postgresql.org/docs/9.1/sql-syntax-lexical.html + // https://www.postgresql.org/docs/current/sql-syntax-lexical.html#SQL-SYNTAX-IDENTIFIERS public String applyQuote(final String identifier) { String postgreSQLIdentifier = identifier; if (getIdentifierMapping() != PostgreSQLIdentifierMapping.PRESERVE_ORIGINAL_CASE) { @@ -147,9 +147,13 @@ public NullSorting getDefaultNullSorting() { } @Override - // https://www.postgresql.org/docs/9.2/sql-syntax-lexical.html + // https://www.postgresql.org/docs/current/sql-syntax-lexical.html#SQL-SYNTAX-CONSTANTS public String getStringLiteral(final String value) { - return super.quoteLiteralStringWithSingleQuote(value); + if (value == null) { + return "NULL"; + } else { + return "E'" + value.replace("\\", "\\\\").replace("'", "''") + "'"; + } } @Override @@ -179,4 +183,4 @@ private void checkPostgreSQLIdentifierPropertyConsistency() throws PropertyValid + IGNORE_ERRORS_PROPERTY + "). Pick one of: " + POSTGRESQL_UPPERCASE_TABLES_SWITCH); } } -} \ No newline at end of file +} diff --git a/src/main/java/com/exasol/adapter/dialects/saphana/SapHanaSqlDialect.java b/src/main/java/com/exasol/adapter/dialects/saphana/SapHanaSqlDialect.java index 503e98e93..9b8dca3ef 100644 --- a/src/main/java/com/exasol/adapter/dialects/saphana/SapHanaSqlDialect.java +++ b/src/main/java/com/exasol/adapter/dialects/saphana/SapHanaSqlDialect.java @@ -120,7 +120,7 @@ public NullSorting getDefaultNullSorting() { } @Override - // https://help.sap.com/viewer/4fe29514fd584807ac9f2a04f6754767/2.0.04/en-US/885b059cc20340979c8927c9301ceb63.html + // https://help.sap.com/viewer/4fe29514fd584807ac9f2a04f6754767/LATEST/en-US/209f5020751910148fd8fe88aa4d79d9.html public String getStringLiteral(final String value) { return super.quoteLiteralStringWithSingleQuote(value); } @@ -139,4 +139,4 @@ protected RemoteMetadataReader createRemoteMetadataReader() { protected QueryRewriter createQueryRewriter() { return new BaseQueryRewriter(this, createRemoteMetadataReader(), this.connectionFactory); } -} \ No newline at end of file +} From 66454d9314cd612c50a74efffd49ed402ce879e8 Mon Sep 17 00:00:00 2001 From: anastasiiasergienko Date: Wed, 11 Nov 2020 14:29:35 +0100 Subject: [PATCH 06/12] * #401: Fixed review findings --- .../adapter/dialects/redshift/RedshiftSqlDialect.java | 6 +++++- .../adapter/dialects/sqlserver/SqlServerIdentifier.java | 2 +- .../adapter/dialects/sqlserver/SqlServerSqlDialect.java | 2 +- .../exasol/adapter/dialects/sybase/SybaseSqlDialect.java | 2 +- .../dialects/postgresql/PostgreSQLSqlDialectTest.java | 3 ++- .../postgresql/PostgresSQLSqlGenerationVisitorTest.java | 2 +- .../adapter/dialects/redshift/RedshiftSqlDialectTest.java | 3 ++- .../dialects/redshift/RedshiftSqlGenerationVisitorTest.java | 2 +- 8 files changed, 14 insertions(+), 8 deletions(-) diff --git a/src/main/java/com/exasol/adapter/dialects/redshift/RedshiftSqlDialect.java b/src/main/java/com/exasol/adapter/dialects/redshift/RedshiftSqlDialect.java index de2101ec8..810940d74 100644 --- a/src/main/java/com/exasol/adapter/dialects/redshift/RedshiftSqlDialect.java +++ b/src/main/java/com/exasol/adapter/dialects/redshift/RedshiftSqlDialect.java @@ -116,7 +116,11 @@ public NullSorting getDefaultNullSorting() { @Override // https://docs.aws.amazon.com/redshift/latest/dg/r_Examples_with_character_types.html public String getStringLiteral(final String value) { - return super.quoteLiteralStringWithSingleQuote(value); + if (value == null) { + return "NULL"; + } else { + return "'" + value.replace("\\", "\\\\").replace("'", "\\'") + "'"; + } } @Override diff --git a/src/main/java/com/exasol/adapter/dialects/sqlserver/SqlServerIdentifier.java b/src/main/java/com/exasol/adapter/dialects/sqlserver/SqlServerIdentifier.java index 7ea02118c..d6cd2b01a 100644 --- a/src/main/java/com/exasol/adapter/dialects/sqlserver/SqlServerIdentifier.java +++ b/src/main/java/com/exasol/adapter/dialects/sqlserver/SqlServerIdentifier.java @@ -37,7 +37,7 @@ public static SqlServerIdentifier of(final String id) { throw new AssertionError("E-ID-4: Unable to create identifier \"" + id // + "\" because it contains illegal characters." // + " For information about valid identifiers, please refer to" // - + " https://docs.microsoft.com/de-de/sql/t-sql/statements/set-quoted-identifier-transact-sql?view=sql-server-ver15"); + + " https://docs.microsoft.com/sql/relational-databases/databases/database-identifiers?view=sql-server-ver15"); } } diff --git a/src/main/java/com/exasol/adapter/dialects/sqlserver/SqlServerSqlDialect.java b/src/main/java/com/exasol/adapter/dialects/sqlserver/SqlServerSqlDialect.java index 6d5dd7898..5afd776c0 100644 --- a/src/main/java/com/exasol/adapter/dialects/sqlserver/SqlServerSqlDialect.java +++ b/src/main/java/com/exasol/adapter/dialects/sqlserver/SqlServerSqlDialect.java @@ -114,7 +114,7 @@ public SqlNodeVisitor getSqlGenerationVisitor(final SqlGenerationContext } @Override - // https://docs.microsoft.com/de-de/sql/t-sql/statements/set-quoted-identifier-transact-sql?view=sql-server-ver15 + // https://docs.microsoft.com/de-de/sql/relational-databases/databases/database-identifiers?view=sql-server-ver15 public String applyQuote(final String identifier) { return SqlServerIdentifier.of(identifier).quote(); } diff --git a/src/main/java/com/exasol/adapter/dialects/sybase/SybaseSqlDialect.java b/src/main/java/com/exasol/adapter/dialects/sybase/SybaseSqlDialect.java index b65787b58..61df55bed 100644 --- a/src/main/java/com/exasol/adapter/dialects/sybase/SybaseSqlDialect.java +++ b/src/main/java/com/exasol/adapter/dialects/sybase/SybaseSqlDialect.java @@ -138,7 +138,7 @@ public NullSorting getDefaultNullSorting() { @Override // http://infocenter.sybase.com/help/index.jsp?topic=/com.sybase.infocenter.dc01031.0400/doc/html/asc1252677176370.html public String getStringLiteral(final String value) { - return super.quoteLiteralStringWithSingleQuote(value); + return SybaseIdentifier.of(value).quote(); } @Override diff --git a/src/test/java/com/exasol/adapter/dialects/postgresql/PostgreSQLSqlDialectTest.java b/src/test/java/com/exasol/adapter/dialects/postgresql/PostgreSQLSqlDialectTest.java index b51339c5e..5e5f9c4c6 100644 --- a/src/test/java/com/exasol/adapter/dialects/postgresql/PostgreSQLSqlDialectTest.java +++ b/src/test/java/com/exasol/adapter/dialects/postgresql/PostgreSQLSqlDialectTest.java @@ -96,7 +96,8 @@ void testApplyQuote(final String unquoted, final String quoted) { assertThat(this.dialect.applyQuote(unquoted), equalTo(quoted)); } - @ValueSource(strings = { "ab:'ab'", "a'b:'a''b'", "a''b:'a''''b'", "'ab':'''ab'''" }) + @ValueSource(strings = { "ab:E'ab'", "a'b:E'a''b'", "a''b:E'a''''b'", "'ab':E'''ab'''", "a\\\\b:E'a\\\\\\\\b'", + "a\\'b:E'a\\\\''b'" }) @ParameterizedTest void testGetLiteralString(final String definition) { assertThat(this.dialect.getStringLiteral(definition.substring(0, definition.indexOf(':'))), diff --git a/src/test/java/com/exasol/adapter/dialects/postgresql/PostgresSQLSqlGenerationVisitorTest.java b/src/test/java/com/exasol/adapter/dialects/postgresql/PostgresSQLSqlGenerationVisitorTest.java index 993965a84..560f03857 100644 --- a/src/test/java/com/exasol/adapter/dialects/postgresql/PostgresSQLSqlGenerationVisitorTest.java +++ b/src/test/java/com/exasol/adapter/dialects/postgresql/PostgresSQLSqlGenerationVisitorTest.java @@ -194,6 +194,6 @@ void testVisitSqlFunctionAggregateGroupConcat() throws AdapterException { final SqlOrderBy orderBy = new SqlOrderBy(orderByArguments, List.of(false, true), List.of(false, true)); final SqlFunctionAggregateGroupConcat sqlFunctionAggregateGroupConcat = SqlFunctionAggregateGroupConcat .builder(argument).separator(new SqlLiteralString("'")).orderBy(orderBy).build(); - assertThat(this.visitor.visit(sqlFunctionAggregateGroupConcat), equalTo("STRING_AGG('test', '''') ")); + assertThat(this.visitor.visit(sqlFunctionAggregateGroupConcat), equalTo("STRING_AGG(E'test', E'''') ")); } } \ No newline at end of file diff --git a/src/test/java/com/exasol/adapter/dialects/redshift/RedshiftSqlDialectTest.java b/src/test/java/com/exasol/adapter/dialects/redshift/RedshiftSqlDialectTest.java index ce3284b0b..3deacc07d 100644 --- a/src/test/java/com/exasol/adapter/dialects/redshift/RedshiftSqlDialectTest.java +++ b/src/test/java/com/exasol/adapter/dialects/redshift/RedshiftSqlDialectTest.java @@ -125,7 +125,8 @@ void testApplyQuote(final String unquoted, final String quoted) { assertThat(this.dialect.applyQuote(unquoted), equalTo(quoted)); } - @ValueSource(strings = { "ab:'ab'", "a'b:'a''b'", "a''b:'a''''b'", "'ab':'''ab'''" }) + @ValueSource(strings = { "ab:'ab'", "a'b:'a\\'b'", "a''b:'a\\'\\'b'", "'ab':'\\'ab\\''", "a\\b:'a\\\\b'", + "a\\\\b:'a\\\\\\\\b'", "a\\'b:'a\\\\\\'b'" }) @ParameterizedTest void testGetLiteralString(final String definition) { assertThat(this.dialect.getStringLiteral(definition.substring(0, definition.indexOf(':'))), diff --git a/src/test/java/com/exasol/adapter/dialects/redshift/RedshiftSqlGenerationVisitorTest.java b/src/test/java/com/exasol/adapter/dialects/redshift/RedshiftSqlGenerationVisitorTest.java index f415c2167..d4a369352 100644 --- a/src/test/java/com/exasol/adapter/dialects/redshift/RedshiftSqlGenerationVisitorTest.java +++ b/src/test/java/com/exasol/adapter/dialects/redshift/RedshiftSqlGenerationVisitorTest.java @@ -40,6 +40,6 @@ void visitSqlFunctionAggregateGroupConcat() throws AdapterException { final SqlFunctionAggregateGroupConcat aggregateGroupConcat = SqlFunctionAggregateGroupConcat.builder(argument) .separator(new SqlLiteralString("'")).orderBy(orderBy).distinct(true).build(); assertThat(this.visitor.visit(aggregateGroupConcat), equalTo( - "LISTAGG('value ''', '''') WITHIN GROUP(ORDER BY \"\"\"test_column\" DESC NULLS FIRST, \"test_column2\"\"\")")); + "LISTAGG('value \\''', '\\''') WITHIN GROUP(ORDER BY \"\"\"test_column\" DESC NULLS FIRST, \"test_column2\"\"\")")); } } \ No newline at end of file From 69ef11dd79abd469289aca409a8ac35b75aceb67 Mon Sep 17 00:00:00 2001 From: anastasiiasergienko Date: Wed, 11 Nov 2020 15:46:43 +0100 Subject: [PATCH 07/12] * #401: Fixed review findings --- .../exasol/adapter/dialects/sybase/SybaseSqlDialect.java | 5 ++++- .../dialects/redshift/RedshiftSqlGenerationVisitorTest.java | 2 +- .../adapter/dialects/sybase/SybaseSqlDialectTest.java | 6 ++++++ 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/exasol/adapter/dialects/sybase/SybaseSqlDialect.java b/src/main/java/com/exasol/adapter/dialects/sybase/SybaseSqlDialect.java index 61df55bed..66ac7067e 100644 --- a/src/main/java/com/exasol/adapter/dialects/sybase/SybaseSqlDialect.java +++ b/src/main/java/com/exasol/adapter/dialects/sybase/SybaseSqlDialect.java @@ -138,7 +138,10 @@ public NullSorting getDefaultNullSorting() { @Override // http://infocenter.sybase.com/help/index.jsp?topic=/com.sybase.infocenter.dc01031.0400/doc/html/asc1252677176370.html public String getStringLiteral(final String value) { - return SybaseIdentifier.of(value).quote(); + if (value.contains("\n") || value.contains("\r")) { + throw new IllegalArgumentException("Sybase string literal contains illegal characters."); + } + return super.quoteLiteralStringWithSingleQuote(value); } @Override diff --git a/src/test/java/com/exasol/adapter/dialects/redshift/RedshiftSqlGenerationVisitorTest.java b/src/test/java/com/exasol/adapter/dialects/redshift/RedshiftSqlGenerationVisitorTest.java index d4a369352..ad5f30080 100644 --- a/src/test/java/com/exasol/adapter/dialects/redshift/RedshiftSqlGenerationVisitorTest.java +++ b/src/test/java/com/exasol/adapter/dialects/redshift/RedshiftSqlGenerationVisitorTest.java @@ -40,6 +40,6 @@ void visitSqlFunctionAggregateGroupConcat() throws AdapterException { final SqlFunctionAggregateGroupConcat aggregateGroupConcat = SqlFunctionAggregateGroupConcat.builder(argument) .separator(new SqlLiteralString("'")).orderBy(orderBy).distinct(true).build(); assertThat(this.visitor.visit(aggregateGroupConcat), equalTo( - "LISTAGG('value \\''', '\\''') WITHIN GROUP(ORDER BY \"\"\"test_column\" DESC NULLS FIRST, \"test_column2\"\"\")")); + "LISTAGG('value \\'', '\\'') WITHIN GROUP(ORDER BY \"\"\"test_column\" DESC NULLS FIRST, \"test_column2\"\"\")")); } } \ No newline at end of file diff --git a/src/test/java/com/exasol/adapter/dialects/sybase/SybaseSqlDialectTest.java b/src/test/java/com/exasol/adapter/dialects/sybase/SybaseSqlDialectTest.java index b5a2b606a..89070f031 100644 --- a/src/test/java/com/exasol/adapter/dialects/sybase/SybaseSqlDialectTest.java +++ b/src/test/java/com/exasol/adapter/dialects/sybase/SybaseSqlDialectTest.java @@ -152,6 +152,12 @@ void testGetLiteralString(final String definition) { equalTo(definition.substring(definition.indexOf(':') + 1))); } + @ValueSource(strings = { "a\nb", "a\rb", "\r\n" }) + @ParameterizedTest + void testGetLiteralStringWithIllegalChars(final String value) { + assertThrows(IllegalArgumentException.class, () -> this.dialect.getStringLiteral(value)); + } + @Test void testRequiresCatalogQualifiedTableNames() { assertThat(this.dialect.requiresCatalogQualifiedTableNames(null), equalTo(true)); From e03c674b8c099dc85463b3f3dd3ce3e96aeb4a99 Mon Sep 17 00:00:00 2001 From: anastasiiasergienko Date: Thu, 12 Nov 2020 09:54:19 +0100 Subject: [PATCH 08/12] * #401: Fixed review findings --- .../dialects/mysql/MySqlSqlDialect.java | 6 ++++- .../dialects/redshift/RedshiftSqlDialect.java | 5 ++++- .../sqlserver/SqlServerIdentifier.java | 2 +- .../dialects/sybase/SybaseIdentifier.java | 7 +++--- .../dialects/sybase/SybaseSqlDialect.java | 9 +++++--- .../dialects/athena/AthenaSqlDialectTest.java | 6 +++++ .../bigquery/BigQuerySqlDialectTest.java | 6 +++++ .../dialects/db2/DB2SqlDialectTest.java | 5 +++++ .../generic/GenericSqlDialectTest.java | 2 ++ .../dialects/hive/HiveSqlDialectTest.java | 6 +++++ .../dialects/impala/ImpalaSqlDialectTest.java | 6 +++++ .../dialects/mysql/MySqlSqlDialectTest.java | 8 ++++++- .../dialects/oracle/OracleSqlDialectTest.java | 6 +++++ .../postgresql/PostgreSQLSqlDialectTest.java | 5 +++++ .../redshift/RedshiftSqlDialectTest.java | 17 ++++++++++++-- .../RedshiftSqlGenerationVisitorTest.java | 6 ++--- .../saphana/SapHanaSqlDialectTest.java | 5 +++++ .../sqlserver/SqlServerSqlDialectTest.java | 7 +++++- .../dialects/sybase/SybaseIdentifierTest.java | 10 +++++---- .../dialects/sybase/SybaseSqlDialectTest.java | 9 ++++++-- .../SybaseSqlGenerationVisitorTest.java | 22 +++++++++---------- .../teradata/TeradataSqlDialectTest.java | 5 +++++ 22 files changed, 127 insertions(+), 33 deletions(-) diff --git a/src/main/java/com/exasol/adapter/dialects/mysql/MySqlSqlDialect.java b/src/main/java/com/exasol/adapter/dialects/mysql/MySqlSqlDialect.java index 2fe36c00a..596684b47 100644 --- a/src/main/java/com/exasol/adapter/dialects/mysql/MySqlSqlDialect.java +++ b/src/main/java/com/exasol/adapter/dialects/mysql/MySqlSqlDialect.java @@ -107,7 +107,11 @@ public NullSorting getDefaultNullSorting() { @Override // https://dev.mysql.com/doc/refman/8.0/en/string-literals.html public String getStringLiteral(final String value) { - return super.quoteLiteralStringWithSingleQuote(value.replace("\\", "\\\\")); + if (value == null) { + return "NULL"; + } else { + return "'" + value.replace("\\", "\\\\").replace("'", "''") + "'"; + } } @Override diff --git a/src/main/java/com/exasol/adapter/dialects/redshift/RedshiftSqlDialect.java b/src/main/java/com/exasol/adapter/dialects/redshift/RedshiftSqlDialect.java index 810940d74..eea787404 100644 --- a/src/main/java/com/exasol/adapter/dialects/redshift/RedshiftSqlDialect.java +++ b/src/main/java/com/exasol/adapter/dialects/redshift/RedshiftSqlDialect.java @@ -119,7 +119,10 @@ public String getStringLiteral(final String value) { if (value == null) { return "NULL"; } else { - return "'" + value.replace("\\", "\\\\").replace("'", "\\'") + "'"; + if (value.contains("'") || value.contains("\\")) { + throw new IllegalArgumentException("Redshift string literal contains illegal characters: ' or \\."); + } + return "'" + value + "'"; } } diff --git a/src/main/java/com/exasol/adapter/dialects/sqlserver/SqlServerIdentifier.java b/src/main/java/com/exasol/adapter/dialects/sqlserver/SqlServerIdentifier.java index d6cd2b01a..c2f75dadb 100644 --- a/src/main/java/com/exasol/adapter/dialects/sqlserver/SqlServerIdentifier.java +++ b/src/main/java/com/exasol/adapter/dialects/sqlserver/SqlServerIdentifier.java @@ -42,7 +42,7 @@ public static SqlServerIdentifier of(final String id) { } private static boolean validate(final String id) { - return !id.contains("[") && !id.contains("]"); + return !id.contains("[") && !id.contains("]") && !id.contains("\\"); } @Override diff --git a/src/main/java/com/exasol/adapter/dialects/sybase/SybaseIdentifier.java b/src/main/java/com/exasol/adapter/dialects/sybase/SybaseIdentifier.java index 1e9e3f750..9e22c4c6e 100644 --- a/src/main/java/com/exasol/adapter/dialects/sybase/SybaseIdentifier.java +++ b/src/main/java/com/exasol/adapter/dialects/sybase/SybaseIdentifier.java @@ -9,7 +9,7 @@ * Represents an identifier in the Sybase database. */ public class SybaseIdentifier implements Identifier { - private static final Set ALLOWED_CHARS = Set.of(' ', '_', '@', '#', '$'); + private static final Set ALLOWED_CHARS = Set.of('_', '@', '#', '$', '¥', '£'); private final String id; private SybaseIdentifier(final String id) { @@ -23,7 +23,7 @@ private SybaseIdentifier(final String id) { */ @Override public String quote() { - return "[" + this.id + "]"; + return this.id; } /** @@ -56,7 +56,8 @@ private static boolean validate(final String id) { } private static boolean validateCharacter(final char ch) { - return ALLOWED_CHARS.contains(ch) || Character.isLetter(ch) || Character.isDigit(ch); + return ALLOWED_CHARS.contains(ch) || Character.isDigit(ch) || (ch >= 'a' && ch <= 'z') + || (ch >= 'A' && ch <= 'Z'); } @Override diff --git a/src/main/java/com/exasol/adapter/dialects/sybase/SybaseSqlDialect.java b/src/main/java/com/exasol/adapter/dialects/sybase/SybaseSqlDialect.java index 66ac7067e..be3fc3e23 100644 --- a/src/main/java/com/exasol/adapter/dialects/sybase/SybaseSqlDialect.java +++ b/src/main/java/com/exasol/adapter/dialects/sybase/SybaseSqlDialect.java @@ -138,10 +138,13 @@ public NullSorting getDefaultNullSorting() { @Override // http://infocenter.sybase.com/help/index.jsp?topic=/com.sybase.infocenter.dc01031.0400/doc/html/asc1252677176370.html public String getStringLiteral(final String value) { - if (value.contains("\n") || value.contains("\r")) { - throw new IllegalArgumentException("Sybase string literal contains illegal characters."); + if (value == null) { + return "NULL"; + } else if (value.contains("\n") || value.contains("\r") || value.contains("\\")) { + throw new IllegalArgumentException("Sybase string literal contains illegal characters: \\n or \\r or \\."); + } else { + return "'" + value.replace("'", "''") + "'"; } - return super.quoteLiteralStringWithSingleQuote(value); } @Override diff --git a/src/test/java/com/exasol/adapter/dialects/athena/AthenaSqlDialectTest.java b/src/test/java/com/exasol/adapter/dialects/athena/AthenaSqlDialectTest.java index 1fc6bf44c..f73b43aa1 100644 --- a/src/test/java/com/exasol/adapter/dialects/athena/AthenaSqlDialectTest.java +++ b/src/test/java/com/exasol/adapter/dialects/athena/AthenaSqlDialectTest.java @@ -7,6 +7,7 @@ import static com.exasol.adapter.capabilities.PredicateCapability.*; import static com.exasol.adapter.capabilities.ScalarFunctionCapability.*; import static com.exasol.reflect.ReflectionUtils.getMethodReturnViaReflection; +import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.*; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -117,6 +118,11 @@ void testApplyQuote(final String unquoted, final String quoted) { assertThat(this.dialect.applyQuote(unquoted), equalTo(quoted)); } + @Test + void testGetLiteralStringNull() { + assertThat(this.dialect.getStringLiteral(null), equalTo("NULL")); + } + @ValueSource(strings = { "ab:'ab'", "a'b:'a''b'", "a''b:'a''''b'", "'ab':'''ab'''" }) @ParameterizedTest void testGetLiteralString(final String definition) { diff --git a/src/test/java/com/exasol/adapter/dialects/bigquery/BigQuerySqlDialectTest.java b/src/test/java/com/exasol/adapter/dialects/bigquery/BigQuerySqlDialectTest.java index 10dda698b..f85c88367 100644 --- a/src/test/java/com/exasol/adapter/dialects/bigquery/BigQuerySqlDialectTest.java +++ b/src/test/java/com/exasol/adapter/dialects/bigquery/BigQuerySqlDialectTest.java @@ -22,6 +22,7 @@ import com.exasol.adapter.dialects.athena.AthenaIdentifier; import nl.jqno.equalsverifier.EqualsVerifier; +import org.hamcrest.CoreMatchers; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -147,6 +148,11 @@ void testGetLiteralString(final String definition) { equalTo(definition.substring(definition.indexOf(':') + 1))); } + @Test + void testGetLiteralStringNull() { + assertThat(this.dialect.getStringLiteral(null), CoreMatchers.equalTo("NULL")); + } + @Test void testGetAggregateFunctionAliases() { assertThat(this.dialect.getAggregateFunctionAliases().get(AggregateFunction.APPROXIMATE_COUNT_DISTINCT), diff --git a/src/test/java/com/exasol/adapter/dialects/db2/DB2SqlDialectTest.java b/src/test/java/com/exasol/adapter/dialects/db2/DB2SqlDialectTest.java index e5ac2f3a4..ac3f65caf 100644 --- a/src/test/java/com/exasol/adapter/dialects/db2/DB2SqlDialectTest.java +++ b/src/test/java/com/exasol/adapter/dialects/db2/DB2SqlDialectTest.java @@ -133,6 +133,11 @@ void testGetLiteralString(final String definition) { equalTo(definition.substring(definition.indexOf(':') + 1))); } + @Test + void testGetLiteralStringNull() { + assertThat(this.dialect.getStringLiteral(null), CoreMatchers.equalTo("NULL")); + } + @Test void testRequiresCatalogQualifiedTableNames() { assertThat(this.dialect.requiresCatalogQualifiedTableNames(null), equalTo(false)); diff --git a/src/test/java/com/exasol/adapter/dialects/generic/GenericSqlDialectTest.java b/src/test/java/com/exasol/adapter/dialects/generic/GenericSqlDialectTest.java index 102e5cabf..173ae3a03 100644 --- a/src/test/java/com/exasol/adapter/dialects/generic/GenericSqlDialectTest.java +++ b/src/test/java/com/exasol/adapter/dialects/generic/GenericSqlDialectTest.java @@ -1,6 +1,8 @@ package com.exasol.adapter.dialects.generic; import static com.exasol.adapter.AdapterProperties.*; +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.MatcherAssert.assertThat; import static org.mockito.Mockito.when; import java.sql.*; diff --git a/src/test/java/com/exasol/adapter/dialects/hive/HiveSqlDialectTest.java b/src/test/java/com/exasol/adapter/dialects/hive/HiveSqlDialectTest.java index 54c9d735d..5237bce41 100644 --- a/src/test/java/com/exasol/adapter/dialects/hive/HiveSqlDialectTest.java +++ b/src/test/java/com/exasol/adapter/dialects/hive/HiveSqlDialectTest.java @@ -15,6 +15,7 @@ import java.util.HashMap; import java.util.Map; +import org.hamcrest.CoreMatchers; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -97,6 +98,11 @@ void testGetLiteralString(final String definition) { equalTo(definition.substring(definition.indexOf(':') + 1))); } + @Test + void testGetLiteralStringNull() { + assertThat(this.dialect.getStringLiteral(null), CoreMatchers.equalTo("NULL")); + } + private void setMandatoryProperties() { this.rawProperties.put(AdapterProperties.SQL_DIALECT_PROPERTY, "HIVE"); this.rawProperties.put(AdapterProperties.CONNECTION_NAME_PROPERTY, "MY_CONN"); diff --git a/src/test/java/com/exasol/adapter/dialects/impala/ImpalaSqlDialectTest.java b/src/test/java/com/exasol/adapter/dialects/impala/ImpalaSqlDialectTest.java index bca6aa156..13e897f20 100644 --- a/src/test/java/com/exasol/adapter/dialects/impala/ImpalaSqlDialectTest.java +++ b/src/test/java/com/exasol/adapter/dialects/impala/ImpalaSqlDialectTest.java @@ -17,6 +17,7 @@ import java.util.HashMap; import java.util.Map; +import org.hamcrest.CoreMatchers; import org.hamcrest.Matchers; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -143,6 +144,11 @@ void testGetLiteralString(final String definition) { equalTo(definition.substring(definition.indexOf(':') + 1))); } + @Test + void testGetLiteralStringNull() { + assertThat(this.dialect.getStringLiteral(null), CoreMatchers.equalTo("NULL")); + } + @Test void testRequiresCatalogQualifiedTableNames() { assertThat(this.dialect.requiresCatalogQualifiedTableNames(null), equalTo(false)); diff --git a/src/test/java/com/exasol/adapter/dialects/mysql/MySqlSqlDialectTest.java b/src/test/java/com/exasol/adapter/dialects/mysql/MySqlSqlDialectTest.java index 974d2c648..fea129d4f 100644 --- a/src/test/java/com/exasol/adapter/dialects/mysql/MySqlSqlDialectTest.java +++ b/src/test/java/com/exasol/adapter/dialects/mysql/MySqlSqlDialectTest.java @@ -121,13 +121,19 @@ void testApplyQuote(final String unquoted, final String quoted) { assertThat(this.dialect.applyQuote(unquoted), equalTo(quoted)); } - @ValueSource(strings = { "ab:'ab'", "a'b:'a''b'", "a''b:'a''''b'", "'ab':'''ab'''", "a\\b:'a\\\\b'", "a\\\\b:'a\\\\\\\\b'", "a\\'b:'a\\\\''b'" }) + @ValueSource(strings = { "ab:'ab'", "a'b:'a''b'", "a''b:'a''''b'", "'ab':'''ab'''", "a\\b:'a\\\\b'", + "a\\\\b:'a\\\\\\\\b'", "a\\'b:'a\\\\''b'" }) @ParameterizedTest void testGetLiteralString(final String definition) { assertThat(this.dialect.getStringLiteral(definition.substring(0, definition.indexOf(':'))), Matchers.equalTo(definition.substring(definition.indexOf(':') + 1))); } + @Test + void testGetLiteralStringNull() { + assertThat(this.dialect.getStringLiteral(null), equalTo("NULL")); + } + @Test void testMetadataReaderClass() { assertThat(getMethodReturnViaReflection(this.dialect, "createRemoteMetadataReader"), diff --git a/src/test/java/com/exasol/adapter/dialects/oracle/OracleSqlDialectTest.java b/src/test/java/com/exasol/adapter/dialects/oracle/OracleSqlDialectTest.java index 1e93ae40c..0382154aa 100644 --- a/src/test/java/com/exasol/adapter/dialects/oracle/OracleSqlDialectTest.java +++ b/src/test/java/com/exasol/adapter/dialects/oracle/OracleSqlDialectTest.java @@ -18,6 +18,7 @@ import java.util.Map; +import org.hamcrest.CoreMatchers; import org.hamcrest.MatcherAssert; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -146,4 +147,9 @@ void testGetLiteralString(final String definition) { assertThat(this.dialect.getStringLiteral(definition.substring(0, definition.indexOf(':'))), equalTo(definition.substring(definition.indexOf(':') + 1))); } + + @Test + void testGetLiteralStringNull() { + assertThat(this.dialect.getStringLiteral(null), CoreMatchers.equalTo("NULL")); + } } \ No newline at end of file diff --git a/src/test/java/com/exasol/adapter/dialects/postgresql/PostgreSQLSqlDialectTest.java b/src/test/java/com/exasol/adapter/dialects/postgresql/PostgreSQLSqlDialectTest.java index 5e5f9c4c6..acde5efc1 100644 --- a/src/test/java/com/exasol/adapter/dialects/postgresql/PostgreSQLSqlDialectTest.java +++ b/src/test/java/com/exasol/adapter/dialects/postgresql/PostgreSQLSqlDialectTest.java @@ -104,6 +104,11 @@ void testGetLiteralString(final String definition) { equalTo(definition.substring(definition.indexOf(':') + 1))); } + @Test + void testGetLiteralStringNull() { + assertThat(this.dialect.getStringLiteral(null), CoreMatchers.equalTo("NULL")); + } + @Test void testMetadataReaderClass() { assertThat(getMethodReturnViaReflection(this.dialect, "createRemoteMetadataReader"), diff --git a/src/test/java/com/exasol/adapter/dialects/redshift/RedshiftSqlDialectTest.java b/src/test/java/com/exasol/adapter/dialects/redshift/RedshiftSqlDialectTest.java index 3deacc07d..177c0c84f 100644 --- a/src/test/java/com/exasol/adapter/dialects/redshift/RedshiftSqlDialectTest.java +++ b/src/test/java/com/exasol/adapter/dialects/redshift/RedshiftSqlDialectTest.java @@ -8,10 +8,12 @@ import static com.exasol.adapter.capabilities.PredicateCapability.*; import static com.exasol.adapter.capabilities.ScalarFunctionCapability.*; import static com.exasol.reflect.ReflectionUtils.getMethodReturnViaReflection; +import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.Matchers.*; import static org.hamcrest.collection.IsIterableContainingInAnyOrder.containsInAnyOrder; import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.jupiter.api.Assertions.assertAll; +import static org.junit.jupiter.api.Assertions.assertThrows; import java.util.HashMap; import java.util.Map; @@ -125,14 +127,25 @@ void testApplyQuote(final String unquoted, final String quoted) { assertThat(this.dialect.applyQuote(unquoted), equalTo(quoted)); } - @ValueSource(strings = { "ab:'ab'", "a'b:'a\\'b'", "a''b:'a\\'\\'b'", "'ab':'\\'ab\\''", "a\\b:'a\\\\b'", - "a\\\\b:'a\\\\\\\\b'", "a\\'b:'a\\\\\\'b'" }) + @ValueSource(strings = { "ab:'ab'", "abc123:'abc123'" }) @ParameterizedTest void testGetLiteralString(final String definition) { assertThat(this.dialect.getStringLiteral(definition.substring(0, definition.indexOf(':'))), equalTo(definition.substring(definition.indexOf(':') + 1))); } + @ValueSource(strings = { "a'b:'a\\'b'", "a''b:'a\\'\\'b'", "'ab':'\\'ab\\''", "a\\b:'a\\\\b'", + "a\\\\b:'a\\\\\\\\b'", "a\\'b:'a\\\\\\'b'" }) + @ParameterizedTest + void testGetLiteralStringWithIllegalChars(final String value) { + assertThrows(IllegalArgumentException.class, () -> this.dialect.getStringLiteral(value)); + } + + @Test + void testGetLiteralStringNull() { + assertThat(this.dialect.getStringLiteral(null), equalTo("NULL")); + } + @Test void testRequiresCatalogQualifiedTableNames() { assertThat(this.dialect.requiresCatalogQualifiedTableNames(null), equalTo(false)); diff --git a/src/test/java/com/exasol/adapter/dialects/redshift/RedshiftSqlGenerationVisitorTest.java b/src/test/java/com/exasol/adapter/dialects/redshift/RedshiftSqlGenerationVisitorTest.java index ad5f30080..017874f1f 100644 --- a/src/test/java/com/exasol/adapter/dialects/redshift/RedshiftSqlGenerationVisitorTest.java +++ b/src/test/java/com/exasol/adapter/dialects/redshift/RedshiftSqlGenerationVisitorTest.java @@ -29,7 +29,7 @@ void beforeEach() { @Test void visitSqlFunctionAggregateGroupConcat() throws AdapterException { - final SqlLiteralString argument = new SqlLiteralString("value '"); + final SqlLiteralString argument = new SqlLiteralString("value"); final ColumnMetadata columnMetadata = ColumnMetadata.builder().name("\"test_column").type(DataType.createBool()) .build(); final ColumnMetadata columnMetadata2 = ColumnMetadata.builder().name("test_column2\"") @@ -38,8 +38,8 @@ void visitSqlFunctionAggregateGroupConcat() throws AdapterException { new SqlColumn(2, columnMetadata2)); final SqlOrderBy orderBy = new SqlOrderBy(orderByArguments, List.of(false, true), List.of(false, true)); final SqlFunctionAggregateGroupConcat aggregateGroupConcat = SqlFunctionAggregateGroupConcat.builder(argument) - .separator(new SqlLiteralString("'")).orderBy(orderBy).distinct(true).build(); + .separator(new SqlLiteralString("|")).orderBy(orderBy).distinct(true).build(); assertThat(this.visitor.visit(aggregateGroupConcat), equalTo( - "LISTAGG('value \\'', '\\'') WITHIN GROUP(ORDER BY \"\"\"test_column\" DESC NULLS FIRST, \"test_column2\"\"\")")); + "LISTAGG('value', '|') WITHIN GROUP(ORDER BY \"\"\"test_column\" DESC NULLS FIRST, \"test_column2\"\"\")")); } } \ No newline at end of file diff --git a/src/test/java/com/exasol/adapter/dialects/saphana/SapHanaSqlDialectTest.java b/src/test/java/com/exasol/adapter/dialects/saphana/SapHanaSqlDialectTest.java index dc55d639c..473360eb1 100644 --- a/src/test/java/com/exasol/adapter/dialects/saphana/SapHanaSqlDialectTest.java +++ b/src/test/java/com/exasol/adapter/dialects/saphana/SapHanaSqlDialectTest.java @@ -136,6 +136,11 @@ void testGetLiteralString(final String definition) { Matchers.equalTo(definition.substring(definition.indexOf(':') + 1))); } + @Test + void testGetLiteralStringNull() { + assertThat(this.dialect.getStringLiteral(null), equalTo("NULL")); + } + @Test void testMetadataReaderClass(@Mock final Connection connectionMock) throws SQLException { when(this.connectionFactoryMock.getConnection()).thenReturn(connectionMock); diff --git a/src/test/java/com/exasol/adapter/dialects/sqlserver/SqlServerSqlDialectTest.java b/src/test/java/com/exasol/adapter/dialects/sqlserver/SqlServerSqlDialectTest.java index e63effb5d..ba279926c 100644 --- a/src/test/java/com/exasol/adapter/dialects/sqlserver/SqlServerSqlDialectTest.java +++ b/src/test/java/com/exasol/adapter/dialects/sqlserver/SqlServerSqlDialectTest.java @@ -146,7 +146,7 @@ void testApplyQuote(final String identifier, final String expected) { assertThat(this.dialect.applyQuote(identifier), equalTo(expected)); } - @CsvSource({ "[tableName]", "[table name", "table name]", "table[name", "table]name" }) + @CsvSource({ "[tableName]", "[table name", "table name]", "table[name", "table]name", "table\\name" }) @ParameterizedTest void testApplyQuoteThrowsException(final String identifier) { assertThrows(AssertionError.class, () -> this.dialect.applyQuote(identifier)); @@ -159,6 +159,11 @@ void testGetLiteralString(final String definition) { equalTo(definition.substring(definition.indexOf(':') + 1))); } + @Test + void testGetLiteralStringNull() { + assertThat(this.dialect.getStringLiteral(null), CoreMatchers.equalTo("NULL")); + } + @Test void testRequiresCatalogQualifiedTableNames() { assertThat(this.dialect.requiresCatalogQualifiedTableNames(null), equalTo(true)); diff --git a/src/test/java/com/exasol/adapter/dialects/sybase/SybaseIdentifierTest.java b/src/test/java/com/exasol/adapter/dialects/sybase/SybaseIdentifierTest.java index 919d07adf..b767fc88b 100644 --- a/src/test/java/com/exasol/adapter/dialects/sybase/SybaseIdentifierTest.java +++ b/src/test/java/com/exasol/adapter/dialects/sybase/SybaseIdentifierTest.java @@ -1,21 +1,23 @@ package com.exasol.adapter.dialects.sybase; -import nl.jqno.equalsverifier.EqualsVerifier; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertThrows; + import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; -import static org.junit.jupiter.api.Assertions.*; +import nl.jqno.equalsverifier.EqualsVerifier; class SybaseIdentifierTest { @ParameterizedTest - @ValueSource(strings = { "my_underscore_table", "123 column one", "table@#$", "テスト", "таблица" }) + @ValueSource(strings = { "my_underscore_table", "table@#$¥£", "TABLE_123" }) void testCreateValidIdentifier(final String identifier) { assertDoesNotThrow(() -> SybaseIdentifier.of(identifier)); } @ParameterizedTest - @ValueSource(strings = { "test[table]", "test`table", "\" table123" }) + @ValueSource(strings = { "test[table]", "test`table", "\" table123", "テスト", "таблица", "123 column one" }) void testCreateInvalidIdentifier(final String identifier) { assertThrows(AssertionError.class, () -> SybaseIdentifier.of(identifier)); } diff --git a/src/test/java/com/exasol/adapter/dialects/sybase/SybaseSqlDialectTest.java b/src/test/java/com/exasol/adapter/dialects/sybase/SybaseSqlDialectTest.java index 89070f031..78e3f814e 100644 --- a/src/test/java/com/exasol/adapter/dialects/sybase/SybaseSqlDialectTest.java +++ b/src/test/java/com/exasol/adapter/dialects/sybase/SybaseSqlDialectTest.java @@ -136,7 +136,7 @@ void testGetAggregateFunctionAliases() { @Test void testApplyQuote() { - assertThat(this.dialect.applyQuote("tableName"), equalTo("[tableName]")); + assertThat(this.dialect.applyQuote("tableName"), equalTo("tableName")); } @CsvSource({ "[tableName]", "[table name", "table name]", "table[name", "table]name", "table \"name" }) @@ -152,12 +152,17 @@ void testGetLiteralString(final String definition) { equalTo(definition.substring(definition.indexOf(':') + 1))); } - @ValueSource(strings = { "a\nb", "a\rb", "\r\n" }) + @ValueSource(strings = { "a\nb", "a\rb", "\r\n", "a\\'" }) @ParameterizedTest void testGetLiteralStringWithIllegalChars(final String value) { assertThrows(IllegalArgumentException.class, () -> this.dialect.getStringLiteral(value)); } + @Test + void testGetLiteralStringNull() { + assertThat(this.dialect.getStringLiteral(null), CoreMatchers.equalTo("NULL")); + } + @Test void testRequiresCatalogQualifiedTableNames() { assertThat(this.dialect.requiresCatalogQualifiedTableNames(null), equalTo(true)); diff --git a/src/test/java/com/exasol/adapter/dialects/sybase/SybaseSqlGenerationVisitorTest.java b/src/test/java/com/exasol/adapter/dialects/sybase/SybaseSqlGenerationVisitorTest.java index d50e01778..b0bc66230 100644 --- a/src/test/java/com/exasol/adapter/dialects/sybase/SybaseSqlGenerationVisitorTest.java +++ b/src/test/java/com/exasol/adapter/dialects/sybase/SybaseSqlGenerationVisitorTest.java @@ -55,17 +55,17 @@ void testVisitSqlSelectListSelectStar() throws AdapterException { void testVisitSqlStatementSelect() throws AdapterException { final SqlStatementSelect select = (SqlStatementSelect) DialectTestData.getTestSqlNode(); assertThat(this.visitor.visit(select), // - equalTo("SELECT TOP 10 [USER_ID], COUNT([URL])" // - + " FROM [test_catalog].[test_schema].[CLICKS]" // - + " WHERE 1 < [USER_ID]" // - + " GROUP BY [USER_ID] HAVING 1 < COUNT([URL])" // - + " ORDER BY (CASE WHEN [USER_ID] IS NULL THEN 1 ELSE 0 END), [USER_ID]")); + equalTo("SELECT TOP 10 USER_ID, COUNT(URL)" // + + " FROM test_catalog.test_schema.CLICKS" // + + " WHERE 1 < USER_ID" // + + " GROUP BY USER_ID HAVING 1 < COUNT(URL)" // + + " ORDER BY (CASE WHEN USER_ID IS NULL THEN 1 ELSE 0 END), USER_ID")); } - @CsvSource(value = { "text : CAST([test_column] as NVARCHAR(4000) )", // - "time : CONVERT(VARCHAR(12), [test_column], 137)", // - "bigtime : CONVERT(VARCHAR(16), [test_column], 137)", // - "xml : CAST([test_column] as NVARCHAR(4000) )" // + @CsvSource(value = { "text : CAST(test_column as NVARCHAR(4000) )", // + "time : CONVERT(VARCHAR(12), test_column, 137)", // + "bigtime : CONVERT(VARCHAR(16), test_column, 137)", // + "xml : CAST(test_column as NVARCHAR(4000) )" // }, delimiter = ':') @ParameterizedTest void testVisitSqlSelectListSelectStarRequiresCast(final String typeName, final String expected) @@ -120,7 +120,7 @@ void testVisitSqlSelectListSelectStarThrowsException() { void testVisitSqlFunctionScalarAddDate(final ScalarFunction scalarFunction, final String expected) throws AdapterException { final SqlFunctionScalar sqlFunctionScalar = createSqlFunctionScalarForDateTest(scalarFunction, 10); - assertThat(this.visitor.visit(sqlFunctionScalar), equalTo("DATEADD(" + expected + ",10,[test_column])")); + assertThat(this.visitor.visit(sqlFunctionScalar), equalTo("DATEADD(" + expected + ",10,test_column)")); } private SqlFunctionScalar createSqlFunctionScalarForDateTest(final ScalarFunction scalarFunction, @@ -144,7 +144,7 @@ private SqlFunctionScalar createSqlFunctionScalarForDateTest(final ScalarFunctio void testVisitSqlFunctionScalarTimeBetween(final ScalarFunction scalarFunction, final String expected) throws AdapterException { final SqlFunctionScalar sqlFunctionScalar = createSqlFunctionScalarForDateTest(scalarFunction, 10); - assertThat(this.visitor.visit(sqlFunctionScalar), equalTo("DATEDIFF(" + expected + ",10,[test_column])")); + assertThat(this.visitor.visit(sqlFunctionScalar), equalTo("DATEDIFF(" + expected + ",10,test_column)")); } @CsvSource({ "CURRENT_DATE, CAST( GETDATE() AS DATE)", // diff --git a/src/test/java/com/exasol/adapter/dialects/teradata/TeradataSqlDialectTest.java b/src/test/java/com/exasol/adapter/dialects/teradata/TeradataSqlDialectTest.java index e2d5b3654..026159cc7 100644 --- a/src/test/java/com/exasol/adapter/dialects/teradata/TeradataSqlDialectTest.java +++ b/src/test/java/com/exasol/adapter/dialects/teradata/TeradataSqlDialectTest.java @@ -120,6 +120,11 @@ void testGetLiteralString(final String definition) { equalTo(definition.substring(definition.indexOf(':') + 1))); } + @Test + void testGetLiteralStringNull() { + assertThat(this.dialect.getStringLiteral(null), CoreMatchers.equalTo("NULL")); + } + @Test void testRequiresCatalogQualifiedTableNames() { assertThat(this.dialect.requiresCatalogQualifiedTableNames(null), equalTo(false)); From c3bc08a4163192fda04c8cc68fe232b8621e8652 Mon Sep 17 00:00:00 2001 From: Anastasiia Sergienko <46891819+AnastasiiaSergienko@users.noreply.github.com> Date: Fri, 13 Nov 2020 08:44:02 +0100 Subject: [PATCH 09/12] Apply suggestions from code review Co-authored-by: exanm <48916233+exanm@users.noreply.github.com> --- .../com/exasol/adapter/dialects/mysql/MySqlSqlDialect.java | 1 + .../adapter/dialects/postgresql/PostgreSQLSqlDialect.java | 2 ++ .../adapter/dialects/sqlserver/SqlServerIdentifier.java | 4 ++-- .../adapter/dialects/sqlserver/SqlServerSqlDialect.java | 4 ++-- .../adapter/dialects/bigquery/BigQuerySqlDialectTest.java | 3 +-- 5 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/exasol/adapter/dialects/mysql/MySqlSqlDialect.java b/src/main/java/com/exasol/adapter/dialects/mysql/MySqlSqlDialect.java index 596684b47..9ed1de217 100644 --- a/src/main/java/com/exasol/adapter/dialects/mysql/MySqlSqlDialect.java +++ b/src/main/java/com/exasol/adapter/dialects/mysql/MySqlSqlDialect.java @@ -110,6 +110,7 @@ public String getStringLiteral(final String value) { if (value == null) { return "NULL"; } else { + // We replace \ with \\ because we expect that the mode NO_BACKSLASH_ESCAPES is not used. return "'" + value.replace("\\", "\\\\").replace("'", "''") + "'"; } } diff --git a/src/main/java/com/exasol/adapter/dialects/postgresql/PostgreSQLSqlDialect.java b/src/main/java/com/exasol/adapter/dialects/postgresql/PostgreSQLSqlDialect.java index 48eb7199b..14b9066f9 100644 --- a/src/main/java/com/exasol/adapter/dialects/postgresql/PostgreSQLSqlDialect.java +++ b/src/main/java/com/exasol/adapter/dialects/postgresql/PostgreSQLSqlDialect.java @@ -152,6 +152,8 @@ public String getStringLiteral(final String value) { if (value == null) { return "NULL"; } else { + // We use an escape string constant to be independent of the parameter standard_conforming_strings. + // We use '' instead of \' to be independent of the parameter backslash_quote. return "E'" + value.replace("\\", "\\\\").replace("'", "''") + "'"; } } diff --git a/src/main/java/com/exasol/adapter/dialects/sqlserver/SqlServerIdentifier.java b/src/main/java/com/exasol/adapter/dialects/sqlserver/SqlServerIdentifier.java index c2f75dadb..d3a3cbac6 100644 --- a/src/main/java/com/exasol/adapter/dialects/sqlserver/SqlServerIdentifier.java +++ b/src/main/java/com/exasol/adapter/dialects/sqlserver/SqlServerIdentifier.java @@ -37,7 +37,7 @@ public static SqlServerIdentifier of(final String id) { throw new AssertionError("E-ID-4: Unable to create identifier \"" + id // + "\" because it contains illegal characters." // + " For information about valid identifiers, please refer to" // - + " https://docs.microsoft.com/sql/relational-databases/databases/database-identifiers?view=sql-server-ver15"); + + " https://docs.microsoft.com/sql/relational-databases/databases/database-identifiers?view=sql-server-ver15"); } } @@ -61,4 +61,4 @@ public boolean equals(final Object o) { public int hashCode() { return Objects.hash(this.id); } -} \ No newline at end of file +} diff --git a/src/main/java/com/exasol/adapter/dialects/sqlserver/SqlServerSqlDialect.java b/src/main/java/com/exasol/adapter/dialects/sqlserver/SqlServerSqlDialect.java index 5afd776c0..079bb1e8f 100644 --- a/src/main/java/com/exasol/adapter/dialects/sqlserver/SqlServerSqlDialect.java +++ b/src/main/java/com/exasol/adapter/dialects/sqlserver/SqlServerSqlDialect.java @@ -114,7 +114,7 @@ public SqlNodeVisitor getSqlGenerationVisitor(final SqlGenerationContext } @Override - // https://docs.microsoft.com/de-de/sql/relational-databases/databases/database-identifiers?view=sql-server-ver15 + // https://docs.microsoft.com/sql/relational-databases/databases/database-identifiers?view=sql-server-ver15 public String applyQuote(final String identifier) { return SqlServerIdentifier.of(identifier).quote(); } @@ -155,4 +155,4 @@ protected RemoteMetadataReader createRemoteMetadataReader() { protected QueryRewriter createQueryRewriter() { return new BaseQueryRewriter(this, createRemoteMetadataReader(), this.connectionFactory); } -} \ No newline at end of file +} diff --git a/src/test/java/com/exasol/adapter/dialects/bigquery/BigQuerySqlDialectTest.java b/src/test/java/com/exasol/adapter/dialects/bigquery/BigQuerySqlDialectTest.java index f85c88367..129bc875c 100644 --- a/src/test/java/com/exasol/adapter/dialects/bigquery/BigQuerySqlDialectTest.java +++ b/src/test/java/com/exasol/adapter/dialects/bigquery/BigQuerySqlDialectTest.java @@ -20,7 +20,6 @@ import java.sql.SQLException; import java.util.Map; -import com.exasol.adapter.dialects.athena.AthenaIdentifier; import nl.jqno.equalsverifier.EqualsVerifier; import org.hamcrest.CoreMatchers; import org.junit.jupiter.api.BeforeEach; @@ -190,4 +189,4 @@ void testValidateProperties() { containsString("The value 'WRONG VALUE' for the property BIGQUERY_ENABLE_IMPORT is invalid. " + "It has to be either 'true' or 'false' (case insensitive)")); } -} \ No newline at end of file +} From a876e324b9279ff55e858bba14f2109078b3286f Mon Sep 17 00:00:00 2001 From: anastasiiasergienko Date: Fri, 13 Nov 2020 10:10:52 +0100 Subject: [PATCH 10/12] * #401: Fixed review findings --- doc/changes/changes_4.0.4.md | 14 +++++++----- doc/dialects/athena.md | 4 ++-- doc/dialects/aurora.md | 2 +- doc/dialects/bigquery.md | 2 +- doc/dialects/db2.md | 4 ++-- doc/dialects/hive.md | 4 ++-- doc/dialects/impala.md | 2 +- doc/dialects/mysql.md | 2 +- doc/dialects/oracle.md | 2 +- doc/dialects/postgresql.md | 2 +- doc/dialects/redshift.md | 2 +- doc/dialects/saphana.md | 2 +- doc/dialects/sql_server.md | 2 +- doc/dialects/sybase.md | 2 +- doc/dialects/teradata.md | 2 +- pom.xml | 2 +- .../dialects/sybase/SybaseIdentifier.java | 2 +- .../dialects/IntegrationTestConstants.java | 2 +- .../dialects/sybase/SybaseSqlDialectTest.java | 2 +- .../SybaseSqlGenerationVisitorTest.java | 22 +++++++++---------- 20 files changed, 41 insertions(+), 37 deletions(-) diff --git a/doc/changes/changes_4.0.4.md b/doc/changes/changes_4.0.4.md index e59e478eb..8f2479d23 100644 --- a/doc/changes/changes_4.0.4.md +++ b/doc/changes/changes_4.0.4.md @@ -1,6 +1,10 @@ -# Exasol Virtual Schemas 4.0.4, released 2020-11-?? +# Exasol Virtual Schemas 4.0.4, released 2020-11-13 -Code name: +Code name: Important bugfixes + +## Summary + +In this release we fixed a few bugs, including a security issue. Please update your adapters as soon as possible. ## Documentation @@ -8,7 +12,7 @@ Code name: * #377: Improved Scalar Functions API documentation. * #384: Turned embedded JSON into key-value encoding in Adapter Notes API examples. * #386: Remove the documentation that was moved to the portal, added links instead. -* #394: Described 'No suitable driver found', added a note that Hive 1.1.0 has problems with its driver. +* #394: Described 'No suitable driver found', added a note that Hive 1.1.0 has problems with its driver. * #391: Removed the API documentation from this repository and added a link to it. ## Refactoring @@ -17,12 +21,12 @@ Code name: * #381: Migrated from version.sh to artifact-reference-checker-maven-plugin. * #389: Improved connection error handling. * #396: Updated to the `virtual-schema-common-jdbc:6.0.0` -* #401: Updated to the `virtual-schema-common-jdbc:6.1.0` +* #401: Updated to the `virtual-schema-common-jdbc:7.0.0` ## Dependency updates * Added com.exasol:artifact-reference-checker-maven-plugin:0.3.1 -* Updated com.exasol:virtual-schema-common-jdbc:5.0.4 to 6.1.0 +* Updated com.exasol:virtual-schema-common-jdbc:5.0.4 to 7.0.0 * Updated org.apache.hbase:hbase-server:2.3.0 to 2.3.3 * Updated org.junit.jupiter:junit-jupiter:5.6.2 to 5.7.0 * Updated org.mockito:mockito-junit-jupiter:3.4.6 to 3.6.0 diff --git a/doc/dialects/athena.md b/doc/dialects/athena.md index 3179869fc..228f1c96f 100644 --- a/doc/dialects/athena.md +++ b/doc/dialects/athena.md @@ -52,7 +52,7 @@ The SQL statement below creates the adapter script, defines the Java class that ```sql CREATE OR REPLACE JAVA ADAPTER SCRIPT ADAPTER.JDBC_ADAPTER AS %scriptclass com.exasol.adapter.RequestDispatcher; - %jar /buckets///virtual-schema-dist-6.1.0-bundle-4.0.4.jar; + %jar /buckets///virtual-schema-dist-7.0.0-bundle-4.0.4.jar; %jar /buckets///AthenaJDBC42-.jar; / ; @@ -80,4 +80,4 @@ CREATE VIRTUAL SCHEMA SQL_DIALECT = 'ATHENA' CONNECTION_NAME = 'ATHENA_CONNECTION' SCHEMA_NAME = ''; -``` \ No newline at end of file +``` diff --git a/doc/dialects/aurora.md b/doc/dialects/aurora.md index c3ade6c6c..28ce29268 100644 --- a/doc/dialects/aurora.md +++ b/doc/dialects/aurora.md @@ -62,7 +62,7 @@ The SQL statement below creates the adapter script, defines the Java class that ```sql CREATE OR REPLACE JAVA ADAPTER SCRIPT ADAPTER.JDBC_ADAPTER AS %scriptclass com.exasol.adapter.RequestDispatcher; - %jar /buckets///virtual-schema-dist-6.1.0-bundle-4.0.4.jar; + %jar /buckets///virtual-schema-dist-7.0.0-bundle-4.0.4.jar; %jar /buckets///postgresql-.jar; / ``` diff --git a/doc/dialects/bigquery.md b/doc/dialects/bigquery.md index 1d5742481..cd38ce65b 100644 --- a/doc/dialects/bigquery.md +++ b/doc/dialects/bigquery.md @@ -33,7 +33,7 @@ List all the JAR files from Magnitude Simba JDBC driver. ```sql CREATE JAVA ADAPTER SCRIPT SCHEMA_FOR_VS_SCRIPT.ADAPTER_SCRIPT_BIGQUERY AS %scriptclass com.exasol.adapter.RequestDispatcher; - %jar /buckets///virtual-schema-dist-6.1.0-bundle-4.0.4.jar; + %jar /buckets///virtual-schema-dist-7.0.0-bundle-4.0.4.jar; %jar /buckets///GoogleBigQueryJDBC42.jar; ... ... diff --git a/doc/dialects/db2.md b/doc/dialects/db2.md index 707b9e012..2b2c6f0cd 100644 --- a/doc/dialects/db2.md +++ b/doc/dialects/db2.md @@ -56,7 +56,7 @@ The SQL statement below creates the adapter script, defines the Java class that ```sql CREATE OR REPLACE JAVA ADAPTER SCRIPT ADAPTER.JDBC_ADAPTER AS %scriptclass com.exasol.adapter.RequestDispatcher; - %jar /buckets///virtual-schema-dist-6.1.0-bundle-4.0.4.jar; + %jar /buckets///virtual-schema-dist-7.0.0-bundle-4.0.4.jar; %jar /buckets///db2jcc4.jar; %jar /buckets///db2jcc_license_cu.jar; / @@ -68,7 +68,7 @@ CREATE OR REPLACE JAVA ADAPTER SCRIPT ADAPTER.JDBC_ADAPTER AS ```sql CREATE OR REPLACE JAVA ADAPTER SCRIPT ADAPTER.JDBC_ADAPTER AS %scriptclass com.exasol.adapter.RequestDispatcher; - %jar /buckets///virtual-schema-dist-6.1.0-bundle-4.0.4.jar; + %jar /buckets///virtual-schema-dist-7.0.0-bundle-4.0.4.jar; %jar /buckets///db2jcc4.jar; %jar /buckets///db2jcc_license_cu.jar; %jar /buckets///db2jcc_license_cisuz.jar; diff --git a/doc/dialects/hive.md b/doc/dialects/hive.md index cf2f2f5c3..1a2235f31 100644 --- a/doc/dialects/hive.md +++ b/doc/dialects/hive.md @@ -47,7 +47,7 @@ The SQL statement below creates the adapter script, defines the Java class that ```sql CREATE OR REPLACE JAVA ADAPTER SCRIPT ADAPTER.JDBC_ADAPTER AS %scriptclass com.exasol.adapter.RequestDispatcher; - %jar /buckets///jars/virtual-schema-dist-6.1.0-bundle-4.0.4.jar; + %jar /buckets///jars/virtual-schema-dist-7.0.0-bundle-4.0.4.jar; %jar /buckets///jars/HiveJDBC41.jar; / ``` @@ -302,7 +302,7 @@ In Virtual Schema adapter: CREATE OR REPLACE JAVA ADAPTER SCRIPT ADAPTER.JDBC_ADAPTER AS %jvmoption -Dsun.security.krb5.disableReferrals=true; %scriptclass com.exasol.adapter.RequestDispatcher; - %jar /buckets///jars/virtual-schema-dist-6.1.0-bundle-4.0.4.jar; + %jar /buckets///jars/virtual-schema-dist-7.0.0-bundle-4.0.4.jar; %jar /buckets///jars/HiveJDBC41.jar; / ``` diff --git a/doc/dialects/impala.md b/doc/dialects/impala.md index 871b746fe..5b62eab3f 100644 --- a/doc/dialects/impala.md +++ b/doc/dialects/impala.md @@ -47,7 +47,7 @@ The SQL statement below creates the adapter script, defines the Java class that ```sql CREATE OR REPLACE JAVA ADAPTER SCRIPT ADAPTER.JDBC_ADAPTER AS %scriptclass com.exasol.adapter.RequestDispatcher; - %jar /buckets///virtual-schema-dist-6.1.0-bundle-4.0.4.jar; + %jar /buckets///virtual-schema-dist-7.0.0-bundle-4.0.4.jar; %jar /buckets///ImpalaJDBC41.jar; / ; diff --git a/doc/dialects/mysql.md b/doc/dialects/mysql.md index 8038a81eb..e0cce6f06 100644 --- a/doc/dialects/mysql.md +++ b/doc/dialects/mysql.md @@ -51,7 +51,7 @@ The SQL statement below creates the adapter script, defines the Java class that ```sql CREATE OR REPLACE JAVA ADAPTER SCRIPT SCHEMA_FOR_VS_SCRIPT.ADAPTER_SCRIPT_MYSQL AS %scriptclass com.exasol.adapter.RequestDispatcher; - %jar /buckets///virtual-schema-dist-6.1.0-bundle-4.0.4.jar; + %jar /buckets///virtual-schema-dist-7.0.0-bundle-4.0.4.jar; %jar /buckets///mysql-connector-java-.jar; / ; diff --git a/doc/dialects/oracle.md b/doc/dialects/oracle.md index 3d91aa592..fe4617552 100644 --- a/doc/dialects/oracle.md +++ b/doc/dialects/oracle.md @@ -48,7 +48,7 @@ The SQL statement below creates the adapter script, defines the Java class that ```sql CREATE JAVA ADAPTER SCRIPT ADAPTER.JDBC_ADAPTER AS %scriptclass com.exasol.adapter.RequestDispatcher; - %jar /buckets///virtual-schema-dist-6.1.0-bundle-4.0.4.jar; + %jar /buckets///virtual-schema-dist-7.0.0-bundle-4.0.4.jar; %jar /buckets///ojdbc.jar; / ; diff --git a/doc/dialects/postgresql.md b/doc/dialects/postgresql.md index 9ad298ced..2f92d9281 100644 --- a/doc/dialects/postgresql.md +++ b/doc/dialects/postgresql.md @@ -25,7 +25,7 @@ The SQL statement below creates the adapter script, defines the Java class that ```sql CREATE OR REPLACE JAVA ADAPTER SCRIPT ADAPTER.JDBC_ADAPTER AS %scriptclass com.exasol.adapter.RequestDispatcher; - %jar /buckets///virtual-schema-dist-6.1.0-bundle-4.0.4.jar; + %jar /buckets///virtual-schema-dist-7.0.0-bundle-4.0.4.jar; %jar /buckets///postgresql-.jar; / ``` diff --git a/doc/dialects/redshift.md b/doc/dialects/redshift.md index 161946173..167a84b8a 100644 --- a/doc/dialects/redshift.md +++ b/doc/dialects/redshift.md @@ -51,7 +51,7 @@ The SQL statement below creates the adapter script, defines the Java class that ```sql CREATE OR REPLACE JAVA ADAPTER SCRIPT ADAPTER.JDBC_ADAPTER AS %scriptclass com.exasol.adapter.RequestDispatcher; - %jar /buckets///virtual-schema-dist-6.1.0-bundle-4.0.4.jar; + %jar /buckets///virtual-schema-dist-7.0.0-bundle-4.0.4.jar; %jar /buckets///RedshiftJDBC42-.jar; / ; diff --git a/doc/dialects/saphana.md b/doc/dialects/saphana.md index a27ac2854..927671e2c 100644 --- a/doc/dialects/saphana.md +++ b/doc/dialects/saphana.md @@ -47,7 +47,7 @@ The SQL statement below creates the adapter script, defines the Java class that ```sql CREATE JAVA ADAPTER SCRIPT ADAPTER.JDBC_ADAPTER AS %scriptclass com.exasol.adapter.RequestDispatcher; - %jar /buckets///virtual-schema-dist-6.1.0-bundle-4.0.4.jar; + %jar /buckets///virtual-schema-dist-7.0.0-bundle-4.0.4.jar; %jar /buckets///ngdbc-.jar; / ; diff --git a/doc/dialects/sql_server.md b/doc/dialects/sql_server.md index 0700e084f..0197cabf0 100644 --- a/doc/dialects/sql_server.md +++ b/doc/dialects/sql_server.md @@ -46,7 +46,7 @@ The SQL statement below creates the adapter script, defines the Java class that ```sql CREATE OR REPLACE JAVA ADAPTER SCRIPT SCHEMA_FOR_VS_SCRIPT.ADAPTER_SCRIPT_SQLSERVER AS %scriptclass com.exasol.adapter.RequestDispatcher; - %jar /buckets///virtual-schema-dist-6.1.0-bundle-4.0.4.jar; + %jar /buckets///virtual-schema-dist-7.0.0-bundle-4.0.4.jar; %jar /buckets///mssql-jdbc-.jre8.jar; / ``` diff --git a/doc/dialects/sybase.md b/doc/dialects/sybase.md index 3d17a9a82..4175eaaba 100644 --- a/doc/dialects/sybase.md +++ b/doc/dialects/sybase.md @@ -29,7 +29,7 @@ The SQL statement below creates the adapter script, defines the Java class that ```sql CREATE OR REPLACE JAVA ADAPTER SCRIPT ADAPTER.JDBC_ADAPTER AS %scriptclass com.exasol.adapter.RequestDispatcher; - %jar /buckets///virtual-schema-dist-6.1.0-bundle-4.0.4.jar; + %jar /buckets///virtual-schema-dist-7.0.0-bundle-4.0.4.jar; %jar /buckets///jtds-.jar; / ``` diff --git a/doc/dialects/teradata.md b/doc/dialects/teradata.md index fffc96436..b976961f2 100644 --- a/doc/dialects/teradata.md +++ b/doc/dialects/teradata.md @@ -47,7 +47,7 @@ The SQL statement below creates the adapter script, defines the Java class that ```sql CREATE OR REPLACE JAVA ADAPTER SCRIPT ADAPTER.JDBC_ADAPTER AS %scriptclass com.exasol.adapter.RequestDispatcher; - %jar /buckets///virtual-schema-dist-6.1.0-bundle-4.0.4.jar; + %jar /buckets///virtual-schema-dist-7.0.0-bundle-4.0.4.jar; %jar /buckets///terajdbc4.jar; %jar /buckets///tdgssconfig.jar; / diff --git a/pom.xml b/pom.xml index e93bc6418..a576cba12 100644 --- a/pom.xml +++ b/pom.xml @@ -11,7 +11,7 @@ UTF-8 11 3.0.0-M4 - 6.1.0 + 7.0.0 1.15.0 target/site/jacoco/jacoco.xml,target/site/jacoco-it/jacoco.xml diff --git a/src/main/java/com/exasol/adapter/dialects/sybase/SybaseIdentifier.java b/src/main/java/com/exasol/adapter/dialects/sybase/SybaseIdentifier.java index 9e22c4c6e..68d1f3085 100644 --- a/src/main/java/com/exasol/adapter/dialects/sybase/SybaseIdentifier.java +++ b/src/main/java/com/exasol/adapter/dialects/sybase/SybaseIdentifier.java @@ -23,7 +23,7 @@ private SybaseIdentifier(final String id) { */ @Override public String quote() { - return this.id; + return "[" + this.id + "]"; } /** diff --git a/src/test/java/com/exasol/adapter/dialects/IntegrationTestConstants.java b/src/test/java/com/exasol/adapter/dialects/IntegrationTestConstants.java index 46b944150..c8bd8b09e 100644 --- a/src/test/java/com/exasol/adapter/dialects/IntegrationTestConstants.java +++ b/src/test/java/com/exasol/adapter/dialects/IntegrationTestConstants.java @@ -3,7 +3,7 @@ import java.nio.file.Path; public final class IntegrationTestConstants { - public static final String VIRTUAL_SCHEMAS_JAR_NAME_AND_VERSION = "virtual-schema-dist-6.1.0-bundle-4.0.4.jar"; + public static final String VIRTUAL_SCHEMAS_JAR_NAME_AND_VERSION = "virtual-schema-dist-7.0.0-bundle-4.0.4.jar"; public static final String EXASOL_DOCKER_IMAGE_REFERENCE = "exasol/docker-db:6.2.9-d1"; public static final Path PATH_TO_VIRTUAL_SCHEMAS_JAR = Path.of("target", VIRTUAL_SCHEMAS_JAR_NAME_AND_VERSION); public static final String SCHEMA_EXASOL = "SCHEMA_EXASOL"; diff --git a/src/test/java/com/exasol/adapter/dialects/sybase/SybaseSqlDialectTest.java b/src/test/java/com/exasol/adapter/dialects/sybase/SybaseSqlDialectTest.java index 78e3f814e..736deb043 100644 --- a/src/test/java/com/exasol/adapter/dialects/sybase/SybaseSqlDialectTest.java +++ b/src/test/java/com/exasol/adapter/dialects/sybase/SybaseSqlDialectTest.java @@ -136,7 +136,7 @@ void testGetAggregateFunctionAliases() { @Test void testApplyQuote() { - assertThat(this.dialect.applyQuote("tableName"), equalTo("tableName")); + assertThat(this.dialect.applyQuote("tableName"), equalTo("[tableName]")); } @CsvSource({ "[tableName]", "[table name", "table name]", "table[name", "table]name", "table \"name" }) diff --git a/src/test/java/com/exasol/adapter/dialects/sybase/SybaseSqlGenerationVisitorTest.java b/src/test/java/com/exasol/adapter/dialects/sybase/SybaseSqlGenerationVisitorTest.java index b0bc66230..d50e01778 100644 --- a/src/test/java/com/exasol/adapter/dialects/sybase/SybaseSqlGenerationVisitorTest.java +++ b/src/test/java/com/exasol/adapter/dialects/sybase/SybaseSqlGenerationVisitorTest.java @@ -55,17 +55,17 @@ void testVisitSqlSelectListSelectStar() throws AdapterException { void testVisitSqlStatementSelect() throws AdapterException { final SqlStatementSelect select = (SqlStatementSelect) DialectTestData.getTestSqlNode(); assertThat(this.visitor.visit(select), // - equalTo("SELECT TOP 10 USER_ID, COUNT(URL)" // - + " FROM test_catalog.test_schema.CLICKS" // - + " WHERE 1 < USER_ID" // - + " GROUP BY USER_ID HAVING 1 < COUNT(URL)" // - + " ORDER BY (CASE WHEN USER_ID IS NULL THEN 1 ELSE 0 END), USER_ID")); + equalTo("SELECT TOP 10 [USER_ID], COUNT([URL])" // + + " FROM [test_catalog].[test_schema].[CLICKS]" // + + " WHERE 1 < [USER_ID]" // + + " GROUP BY [USER_ID] HAVING 1 < COUNT([URL])" // + + " ORDER BY (CASE WHEN [USER_ID] IS NULL THEN 1 ELSE 0 END), [USER_ID]")); } - @CsvSource(value = { "text : CAST(test_column as NVARCHAR(4000) )", // - "time : CONVERT(VARCHAR(12), test_column, 137)", // - "bigtime : CONVERT(VARCHAR(16), test_column, 137)", // - "xml : CAST(test_column as NVARCHAR(4000) )" // + @CsvSource(value = { "text : CAST([test_column] as NVARCHAR(4000) )", // + "time : CONVERT(VARCHAR(12), [test_column], 137)", // + "bigtime : CONVERT(VARCHAR(16), [test_column], 137)", // + "xml : CAST([test_column] as NVARCHAR(4000) )" // }, delimiter = ':') @ParameterizedTest void testVisitSqlSelectListSelectStarRequiresCast(final String typeName, final String expected) @@ -120,7 +120,7 @@ void testVisitSqlSelectListSelectStarThrowsException() { void testVisitSqlFunctionScalarAddDate(final ScalarFunction scalarFunction, final String expected) throws AdapterException { final SqlFunctionScalar sqlFunctionScalar = createSqlFunctionScalarForDateTest(scalarFunction, 10); - assertThat(this.visitor.visit(sqlFunctionScalar), equalTo("DATEADD(" + expected + ",10,test_column)")); + assertThat(this.visitor.visit(sqlFunctionScalar), equalTo("DATEADD(" + expected + ",10,[test_column])")); } private SqlFunctionScalar createSqlFunctionScalarForDateTest(final ScalarFunction scalarFunction, @@ -144,7 +144,7 @@ private SqlFunctionScalar createSqlFunctionScalarForDateTest(final ScalarFunctio void testVisitSqlFunctionScalarTimeBetween(final ScalarFunction scalarFunction, final String expected) throws AdapterException { final SqlFunctionScalar sqlFunctionScalar = createSqlFunctionScalarForDateTest(scalarFunction, 10); - assertThat(this.visitor.visit(sqlFunctionScalar), equalTo("DATEDIFF(" + expected + ",10,test_column)")); + assertThat(this.visitor.visit(sqlFunctionScalar), equalTo("DATEDIFF(" + expected + ",10,[test_column])")); } @CsvSource({ "CURRENT_DATE, CAST( GETDATE() AS DATE)", // From bc61f3b02ec660bd492e15a25142db3f929c5585 Mon Sep 17 00:00:00 2001 From: anastasiiasergienko Date: Fri, 13 Nov 2020 10:32:13 +0100 Subject: [PATCH 11/12] * #401: Updated test-containers and exasol-docker versions --- doc/changes/changes_4.0.4.md | 2 +- pom.xml | 2 +- .../com/exasol/adapter/dialects/IntegrationTestConstants.java | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/changes/changes_4.0.4.md b/doc/changes/changes_4.0.4.md index 8f2479d23..fd5e33581 100644 --- a/doc/changes/changes_4.0.4.md +++ b/doc/changes/changes_4.0.4.md @@ -31,7 +31,7 @@ In this release we fixed a few bugs, including a security issue. Please update y * Updated org.junit.jupiter:junit-jupiter:5.6.2 to 5.7.0 * Updated org.mockito:mockito-junit-jupiter:3.4.6 to 3.6.0 * Updated com.exasol:exasol-jdbc:6.2.5 to 7.0.3 -* Updated com.exasol:exasol-testcontainers:2.1.0 to 3.2.0 +* Updated com.exasol:exasol-testcontainers:2.1.0 to 3.3.0 * Updated org.postgresql:postgresql:42.2.14 to 42.2.18 * Updated org.apache.hbase:hbase-server:2.3.1 to 2.3.2 * Updated com.microsoft.sqlserver:mssql-jdbc:8.4.0.jre11 to 8.4.1.jre11 diff --git a/pom.xml b/pom.xml index a576cba12..75333c3d4 100644 --- a/pom.xml +++ b/pom.xml @@ -95,7 +95,7 @@ com.exasol exasol-testcontainers - 3.2.0 + 3.3.0 test diff --git a/src/test/java/com/exasol/adapter/dialects/IntegrationTestConstants.java b/src/test/java/com/exasol/adapter/dialects/IntegrationTestConstants.java index c8bd8b09e..b43157f4c 100644 --- a/src/test/java/com/exasol/adapter/dialects/IntegrationTestConstants.java +++ b/src/test/java/com/exasol/adapter/dialects/IntegrationTestConstants.java @@ -4,7 +4,7 @@ public final class IntegrationTestConstants { public static final String VIRTUAL_SCHEMAS_JAR_NAME_AND_VERSION = "virtual-schema-dist-7.0.0-bundle-4.0.4.jar"; - public static final String EXASOL_DOCKER_IMAGE_REFERENCE = "exasol/docker-db:6.2.9-d1"; + public static final String EXASOL_DOCKER_IMAGE_REFERENCE = "exasol/docker-db:6.2.11-d1"; public static final Path PATH_TO_VIRTUAL_SCHEMAS_JAR = Path.of("target", VIRTUAL_SCHEMAS_JAR_NAME_AND_VERSION); public static final String SCHEMA_EXASOL = "SCHEMA_EXASOL"; public static final String ADAPTER_SCRIPT_EXASOL = "ADAPTER_SCRIPT_EXASOL"; From 8e8616ada7cc9380862941c80ea187fd52f9cd53 Mon Sep 17 00:00:00 2001 From: anastasiiasergienko Date: Tue, 17 Nov 2020 10:58:37 +0100 Subject: [PATCH 12/12] * #401: Fixed CVE-2020-15250 --- doc/changes/changes_4.0.4.md | 28 ++++++++++++++++++++++++---- pom.xml | 17 ++++++++++++++++- 2 files changed, 40 insertions(+), 5 deletions(-) diff --git a/doc/changes/changes_4.0.4.md b/doc/changes/changes_4.0.4.md index fd5e33581..4cbbc852f 100644 --- a/doc/changes/changes_4.0.4.md +++ b/doc/changes/changes_4.0.4.md @@ -1,10 +1,29 @@ -# Exasol Virtual Schemas 4.0.4, released 2020-11-13 +# Exasol Virtual Schemas 4.0.4, released 2020-11-17 -Code name: Important bugfixes +Code name: Security Update ## Summary -In this release we fixed a few bugs, including a security issue. Please update your adapters as soon as possible. +Classification: High +Please update your adapters as soon as possible! +This release fixes several SQL injection vulnerabilities on the remote database of the virtual schema. +The local Exasol database defining the virtual schema is not affected. + +All dialects except for Teradata are affected: +* Amazon AWS Athena +* Amazon AWS Aurora +* Amazon AWS Redshift +* Apache Hive +* Apache Impala +* Generic JDBC-capable RDBMS +* Google BigQuery +* IBM DB2 +* Microsoft SQL Server +* MySQL +* Oracle +* PostgreSQL +* SAP HANA +* Sybase ## Documentation @@ -26,12 +45,13 @@ In this release we fixed a few bugs, including a security issue. Please update y ## Dependency updates * Added com.exasol:artifact-reference-checker-maven-plugin:0.3.1 +* Added junit:junit:4.13.1 to fix CVE-2020-15250 * Updated com.exasol:virtual-schema-common-jdbc:5.0.4 to 7.0.0 * Updated org.apache.hbase:hbase-server:2.3.0 to 2.3.3 * Updated org.junit.jupiter:junit-jupiter:5.6.2 to 5.7.0 * Updated org.mockito:mockito-junit-jupiter:3.4.6 to 3.6.0 * Updated com.exasol:exasol-jdbc:6.2.5 to 7.0.3 -* Updated com.exasol:exasol-testcontainers:2.1.0 to 3.3.0 +* Updated com.exasol:exasol-testcontainers:2.1.0 to 3.3.1 * Updated org.postgresql:postgresql:42.2.14 to 42.2.18 * Updated org.apache.hbase:hbase-server:2.3.1 to 2.3.2 * Updated com.microsoft.sqlserver:mssql-jdbc:8.4.0.jre11 to 8.4.1.jre11 diff --git a/pom.xml b/pom.xml index 75333c3d4..00b5434fd 100644 --- a/pom.xml +++ b/pom.xml @@ -95,7 +95,7 @@ com.exasol exasol-testcontainers - 3.3.0 + 3.3.1 test @@ -185,6 +185,14 @@ 0.13.0 test + + + junit + junit + 4.13.1 + test + org.apache.hbase hbase-server @@ -371,6 +379,13 @@ + + + + 7ea56ad4-8a8b-4e51-8ed9-5aad83d8efb1 + + org.codehaus.mojo