diff --git a/doc/changes/changes_4.0.4.md b/doc/changes/changes_4.0.4.md index df8af192f..4cbbc852f 100644 --- a/doc/changes/changes_4.0.4.md +++ b/doc/changes/changes_4.0.4.md @@ -1,6 +1,29 @@ -# Exasol Virtual Schemas 4.0.4, released 2020-10-?? +# Exasol Virtual Schemas 4.0.4, released 2020-11-17 -Code name: +Code name: Security Update + +## Summary + +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 @@ -8,7 +31,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 @@ -16,19 +39,28 @@ 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:7.0.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 +* 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.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 +* 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 +* 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 54eccd728..228f1c96f 100644 --- a/doc/dialects/athena.md +++ b/doc/dialects/athena.md @@ -21,12 +21,15 @@ 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. + ## 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 +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.0.0-bundle-4.0.4.jar; + %jar /buckets///virtual-schema-dist-7.0.0-bundle-4.0.4.jar; %jar /buckets///AthenaJDBC42-.jar; / ; diff --git a/doc/dialects/aurora.md b/doc/dialects/aurora.md index 23bbd13f4..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.0.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 2f9e144fe..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.0.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 079666460..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.0.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.0.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 a75ccdfd1..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.0.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.0.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 eed782c5c..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.0.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 22511c5db..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.0.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 f15a00839..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.0.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 5be5b8417..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.0.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 876efdf38..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.0.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 0f5f385ba..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.0.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 a3f57b33e..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.0.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 91d6bb822..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.0.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 8bb5931f2..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.0.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 d421017d6..00b5434fd 100644 --- a/pom.xml +++ b/pom.xml @@ -11,8 +11,8 @@ UTF-8 11 3.0.0-M4 - 6.0.0 - 1.14.3 + 7.0.0 + 1.15.0 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.3.1 test @@ -102,7 +113,7 @@ org.postgresql postgresql - 42.2.16 + 42.2.18 org.testcontainers @@ -174,10 +185,18 @@ 0.13.0 test + + + junit + junit + 4.13.1 + test + org.apache.hbase hbase-server - 2.3.2 + 2.3.3 test @@ -235,7 +254,7 @@ mysql mysql-connector-java - 8.0.21 + 8.0.22 test @@ -360,6 +379,13 @@ + + + + 7ea56ad4-8a8b-4e51-8ed9-5aad83d8efb1 + + org.codehaus.mojo 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..f5e452bc6 --- /dev/null +++ b/src/main/java/com/exasol/adapter/dialects/athena/AthenaIdentifier.java @@ -0,0 +1,88 @@ +package com.exasol.adapter.dialects.athena; + +import java.util.Objects; + +import com.exasol.db.Identifier; + +/** + * 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 instanceof AthenaIdentifier)) { + 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..646bff272 100644 --- a/src/main/java/com/exasol/adapter/dialects/athena/AthenaSqlDialect.java +++ b/src/main/java/com/exasol/adapter/dialects/athena/AthenaSqlDialect.java @@ -107,26 +107,9 @@ 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) { - 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 @@ -134,6 +117,12 @@ public NullSorting getDefaultNullSorting() { return NullSorting.NULLS_SORTED_AT_END; } + @Override + // https://docs.aws.amazon.com/athena/latest/ug/select.html + public String getStringLiteral(final String value) { + return super.quoteLiteralStringWithSingleQuote(value); + } + @Override protected RemoteMetadataReader createRemoteMetadataReader() { try { @@ -148,4 +137,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/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..7ac5a9e08 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; @@ -124,8 +126,9 @@ public StructureElementSupport supportsJdbcSchemas() { } @Override + // https://cloud.google.com/bigquery/docs/reference/standard-sql/lexical public String applyQuote(final String identifier) { - return "`" + identifier + "`"; + return "`" + identifier.replace("`", "\\`") + "`"; } @Override @@ -148,4 +151,14 @@ public void validateProperties() throws PropertyValidationException { super.validateProperties(); validateBooleanProperty(BIGQUERY_ENABLE_IMPORT_PROPERTY); } -} + + @Override + // https://cloud.google.com/bigquery/docs/reference/standard-sql/lexical + public String getStringLiteral(final String value) { + if (value == null) { + return "NULL"; + } else { + 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 a874311d6..d83b9a6c4 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,9 @@ public StructureElementSupport supportsJdbcSchemas() { } @Override + // 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 "\"" + identifier.replace("\"", "\"\"") + "\""; + return super.quoteIdentifierWithDoubleQuotes(identifier); } @Override @@ -122,4 +123,10 @@ public SqlNodeVisitor getSqlGenerationVisitor(final SqlGenerationContext public NullSorting getDefaultNullSorting() { return NullSorting.NULLS_SORTED_AT_END; } -} \ No newline at end of file + + @Override + // 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); + } +} 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..102cec634 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; } @@ -82,6 +86,11 @@ public NullSorting getDefaultNullSorting() { } } + @Override + public String getStringLiteral(final String value) { + return this.quoteLiteralStringWithSingleQuote(value); + } + @Override protected RemoteMetadataReader createRemoteMetadataReader() { try { @@ -98,4 +107,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..a7d77ca66 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 @@ -104,6 +105,17 @@ public NullSorting getDefaultNullSorting() { return NullSorting.NULLS_SORTED_LOW; } + @Override + // 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"; + } else { + return "'" + value.replace("\\", "\\\\").replace("'", "\\'") + "'"; + } + } + @Override public SqlNodeVisitor getSqlGenerationVisitor(final SqlGenerationContext context) { return new HiveSqlGenerationVisitor(this, context); @@ -139,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/impala/ImpalaIdentifier.java b/src/main/java/com/exasol/adapter/dialects/impala/ImpalaIdentifier.java new file mode 100644 index 000000000..2213d25f7 --- /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); + } +} 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..bf3e9f654 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 ImpalaIdentifier.of(identifier).quote(); } @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("\\", "\\\\").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..9ed1de217 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; @@ -81,13 +83,10 @@ 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 + "`"; + return "`" + identifier.replace("`", "``") + "`"; } @Override @@ -105,6 +104,17 @@ public NullSorting getDefaultNullSorting() { return NullSorting.NULLS_SORTED_AT_END; } + @Override + // https://dev.mysql.com/doc/refman/8.0/en/string-literals.html + 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("'", "''") + "'"; + } + } + @Override protected RemoteMetadataReader createRemoteMetadataReader() { try { @@ -124,4 +134,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/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 cc4e0b10f..8e6ee08d9 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,9 @@ public SqlNodeVisitor getSqlGenerationVisitor(final SqlGenerationContext } @Override + // https://docs.oracle.com/cd/B19306_01/server.102/b14200/sql_elements008.htm public String applyQuote(final String identifier) { - return "\"" + identifier.replace("\"", "\"\"") + "\""; + return OracleIdentifier.of(identifier).quote(); } @Override @@ -133,6 +134,12 @@ public NullSorting getDefaultNullSorting() { return NullSorting.NULLS_SORTED_HIGH; } + @Override + // https://docs.oracle.com/cd/B19306_01/server.102/b14200/sql_elements003.htm + 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/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..14b9066f9 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/current/sql-syntax-lexical.html#SQL-SYNTAX-IDENTIFIERS public String applyQuote(final String identifier) { String postgreSQLIdentifier = identifier; if (getIdentifierMapping() != PostgreSQLIdentifierMapping.PRESERVE_ORIGINAL_CASE) { postgreSQLIdentifier = convertIdentifierToLowerCase(postgreSQLIdentifier); } - return "\"" + postgreSQLIdentifier.replace("\"", "\"\"") + "\""; + return super.quoteIdentifierWithDoubleQuotes(postgreSQLIdentifier); } private String convertIdentifierToLowerCase(final String identifier) { @@ -145,6 +146,18 @@ public NullSorting getDefaultNullSorting() { return NullSorting.NULLS_SORTED_AT_END; } + @Override + // https://www.postgresql.org/docs/current/sql-syntax-lexical.html#SQL-SYNTAX-CONSTANTS + 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("'", "''") + "'"; + } + } + @Override public SqlNodeVisitor getSqlGenerationVisitor(final SqlGenerationContext context) { return new PostgresSQLSqlGenerationVisitor(this, context); @@ -172,4 +185,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/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..eea787404 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,9 @@ public StructureElementSupport supportsJdbcSchemas() { } @Override + // https://docs.aws.amazon.com/redshift/latest/dg/r_names.html public String applyQuote(final String identifier) { - return "\"" + identifier.replace("\"", "\"\"") + "\""; + return super.quoteIdentifierWithDoubleQuotes(identifier); } @Override @@ -112,6 +113,19 @@ public NullSorting getDefaultNullSorting() { return NullSorting.NULLS_SORTED_AT_END; } + @Override + // https://docs.aws.amazon.com/redshift/latest/dg/r_Examples_with_character_types.html + public String getStringLiteral(final String value) { + if (value == null) { + return "NULL"; + } else { + if (value.contains("'") || value.contains("\\")) { + throw new IllegalArgumentException("Redshift string literal contains illegal characters: ' or \\."); + } + return "'" + value + "'"; + } + } + @Override public SqlNodeVisitor getSqlGenerationVisitor(final SqlGenerationContext context) { return new RedshiftSqlGenerationVisitor(this, context); 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..9b8dca3ef 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,9 @@ public boolean requiresSchemaQualifiedTableNames(final SqlGenerationContext cont } @Override + // http://sap.optimieren.de/hana/hana/html/_bsql_introduction.html public String applyQuote(final String identifier) { - return "\"" + identifier + "\""; + return super.quoteIdentifierWithDoubleQuotes(identifier); } @Override @@ -116,6 +119,12 @@ public NullSorting getDefaultNullSorting() { return NullSorting.NULLS_SORTED_AT_START; } + @Override + // https://help.sap.com/viewer/4fe29514fd584807ac9f2a04f6754767/LATEST/en-US/209f5020751910148fd8fe88aa4d79d9.html + public String getStringLiteral(final String value) { + return super.quoteLiteralStringWithSingleQuote(value); + } + @Override protected RemoteMetadataReader createRemoteMetadataReader() { try { @@ -130,4 +139,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/sqlserver/SqlServerIdentifier.java b/src/main/java/com/exasol/adapter/dialects/sqlserver/SqlServerIdentifier.java new file mode 100644 index 000000000..d3a3cbac6 --- /dev/null +++ b/src/main/java/com/exasol/adapter/dialects/sqlserver/SqlServerIdentifier.java @@ -0,0 +1,64 @@ +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-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"); + } + } + + private static boolean validate(final String id) { + return !id.contains("[") && !id.contains("]") && !id.contains("\\"); + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (!(o instanceof SqlServerIdentifier)) { + return false; + } + final SqlServerIdentifier that = (SqlServerIdentifier) 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/sqlserver/SqlServerSqlDialect.java b/src/main/java/com/exasol/adapter/dialects/sqlserver/SqlServerSqlDialect.java index 1321f2497..079bb1e8f 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.*; @@ -112,8 +114,9 @@ public SqlNodeVisitor getSqlGenerationVisitor(final SqlGenerationContext } @Override + // https://docs.microsoft.com/sql/relational-databases/databases/database-identifiers?view=sql-server-ver15 public String applyQuote(final String identifier) { - return "[" + identifier + "]"; + return SqlServerIdentifier.of(identifier).quote(); } @Override @@ -131,6 +134,12 @@ public NullSorting getDefaultNullSorting() { return NullSorting.NULLS_SORTED_AT_START; } + @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); + } + @Override protected RemoteMetadataReader createRemoteMetadataReader() { try { @@ -146,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/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..68d1f3085 --- /dev/null +++ b/src/main/java/com/exasol/adapter/dialects/sybase/SybaseIdentifier.java @@ -0,0 +1,79 @@ +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-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"); + } + } + + 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.isDigit(ch) || (ch >= 'a' && ch <= 'z') + || (ch >= 'A' && ch <= 'Z'); + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (!(o instanceof SybaseIdentifier)) { + 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..be3fc3e23 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 @@ -132,6 +135,18 @@ public NullSorting getDefaultNullSorting() { return NullSorting.NULLS_SORTED_LOW; } + @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 == 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("'", "''") + "'"; + } + } + @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 e731f8e44..dc50c4276 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,9 @@ public SqlNodeVisitor getSqlGenerationVisitor(final SqlGenerationContext } @Override + // https://docs.teradata.com/reader/37meaKdwvl0jrhzrc6FoEw/F5dFR63LmiAnvhOd3C9f8w public String applyQuote(final String identifier) { - return "\"" + identifier.replace("\"", "\"\"") + "\""; + return super.quoteIdentifierWithDoubleQuotes(identifier); } @Override @@ -113,4 +114,10 @@ public boolean requiresSchemaQualifiedTableNames(final SqlGenerationContext cont public NullSorting getDefaultNullSorting() { return NullSorting.NULLS_SORTED_HIGH; } + + @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/IntegrationTestConstants.java b/src/test/java/com/exasol/adapter/dialects/IntegrationTestConstants.java index 0768c5db0..b43157f4c 100644 --- a/src/test/java/com/exasol/adapter/dialects/IntegrationTestConstants.java +++ b/src/test/java/com/exasol/adapter/dialects/IntegrationTestConstants.java @@ -3,8 +3,8 @@ 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 EXASOL_DOCKER_IMAGE_REFERENCE = "exasol/docker-db:6.2.9-d1"; + 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.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"; @@ -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..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,8 +7,10 @@ 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; import static org.mockito.Mockito.when; import java.sql.Connection; @@ -19,6 +21,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 +108,28 @@ 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`", // + "123table, \"123table\"", // "table_name, \"table_name\"" }) @ParameterizedTest 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) { + assertThat(this.dialect.getStringLiteral(definition.substring(0, definition.indexOf(':'))), + equalTo(definition.substring(definition.indexOf(':') + 1))); + } + @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/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..129bc875c 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 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; +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,27 @@ void testGetSupportedProperties() { DEBUG_ADDRESS_PROPERTY, LOG_LEVEL_PROPERTY, BIGQUERY_ENABLE_IMPORT_PROPERTY)); } + @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\\''", "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))); + } + @Test - void testApplyQuote() { - assertThat(this.dialect.applyQuote("tableName"), equalTo("`tableName`")); + void testGetLiteralStringNull() { + assertThat(this.dialect.getStringLiteral(null), CoreMatchers.equalTo("NULL")); } @Test @@ -164,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 +} 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..ac3f65caf 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,24 @@ void testSupportsJdbcSchemas() { assertThat(this.dialect.supportsJdbcSchemas(), equalTo(SqlDialect.StructureElementSupport.MULTIPLE)); } + @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) { + assertThat(this.dialect.getStringLiteral(definition.substring(0, definition.indexOf(':'))), + equalTo(definition.substring(definition.indexOf(':') + 1))); + } + @Test - void testApplyQuote() { - assertThat(this.dialect.applyQuote("tableName"), equalTo("\"tableName\"")); + void testGetLiteralStringNull() { + assertThat(this.dialect.getStringLiteral(null), CoreMatchers.equalTo("NULL")); } @Test @@ -143,4 +157,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/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/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/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/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..5237bce41 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; @@ -14,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; @@ -21,6 +23,9 @@ 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; +import org.junit.jupiter.params.provider.ValueSource; class HiveSqlDialectTest { private SqlDialect dialect; @@ -61,7 +66,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); @@ -70,15 +75,36 @@ 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); sqlDialect.validateProperties(); } - private void setMandatoryProperties(final String sqlDialectProperty) { - this.rawProperties.put(AdapterProperties.SQL_DIALECT_PROPERTY, sqlDialectProperty); + @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\\''", "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))); + } + + @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"); } -} +} \ 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/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/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..13e897f20 100644 --- a/src/test/java/com/exasol/adapter/dialects/impala/ImpalaSqlDialectTest.java +++ b/src/test/java/com/exasol/adapter/dialects/impala/ImpalaSqlDialectTest.java @@ -12,10 +12,12 @@ 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; +import org.hamcrest.CoreMatchers; import org.hamcrest.Matchers; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -25,6 +27,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 +105,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 +114,39 @@ 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(); } + @CsvSource({ "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)); + } + + @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) { + assertThat(this.dialect.getStringLiteral(definition.substring(0, definition.indexOf(':'))), + equalTo(definition.substring(definition.indexOf(':') + 1))); + } + @Test - void testApplyQuote() { - assertThat(this.dialect.applyQuote("tableName"), equalTo("`tableName`")); + void testGetLiteralStringNull() { + assertThat(this.dialect.getStringLiteral(null), CoreMatchers.equalTo("NULL")); } @Test @@ -141,8 +169,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..fea129d4f 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,18 +112,26 @@ 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`", // + "\"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'''", "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(':'))), + Matchers.equalTo(definition.substring(definition.indexOf(':') + 1))); } @Test - void testApplyQuote() { - assertThat(this.dialect.applyQuote("tableName"), Matchers.equalTo("`tableName`")); + void testGetLiteralStringNull() { + assertThat(this.dialect.getStringLiteral(null), equalTo("NULL")); } @Test @@ -141,4 +152,4 @@ void testGetSupportedProperties() { void testGetSqlGenerationVisitor() { assertThat(this.dialect.getSqlGenerationVisitor(null), instanceOf(MySqlSqlGenerationVisitor.class)); } -} \ No newline at end of file +} 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 ef8b865ba..0382154aa 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,30 @@ void testQueryRewriterClass() { assertThat(getMethodReturnViaReflection(this.dialect, "createQueryRewriter"), instanceOf(OracleQueryRewriter.class)); } -} + + @CsvSource({ "tableName, \"tableName\"", // + "table 'Name, \"table 'Name\"" // + }) + @ParameterizedTest + 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) { + 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/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/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 d0c29b591..acde5efc1 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,26 @@ 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)); + } + + @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(':'))), + equalTo(definition.substring(definition.indexOf(':') + 1))); } @Test - void testApplyQuoteOnMixedCase() { - assertEquals("\"abcde\"", this.dialect.applyQuote("AbCde")); + void testGetLiteralStringNull() { + assertThat(this.dialect.getStringLiteral(null), CoreMatchers.equalTo("NULL")); } @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..560f03857 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(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 4c1ef4783..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; @@ -20,6 +22,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 +86,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 +95,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 +118,32 @@ void testGetAggregateFunctionAliases() { assertThat(this.dialect.getAggregateFunctionAliases(), aMapWithSize(0)); } + @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'", "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 testApplyQuote() { - assertThat(this.dialect.applyQuote("Foo\"Bar"), equalTo("\"Foo\"\"Bar\"")); + void testGetLiteralStringNull() { + assertThat(this.dialect.getStringLiteral(null), equalTo("NULL")); } @Test @@ -133,11 +161,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..017874f1f --- /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..473360eb1 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,24 @@ 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) { + 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 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..ba279926c 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.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; 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; @@ -134,9 +140,28 @@ void testGetAggregateFunctionAliases() { Matchers.equalTo("VARP"))); } + @CsvSource({ "tableName, [tableName]", "table \"name, [table \"name]" }) + @ParameterizedTest + 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", "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) { + assertThat(this.dialect.getStringLiteral(definition.substring(0, definition.indexOf(':'))), + equalTo(definition.substring(definition.indexOf(':') + 1))); + } + @Test - void testApplyQuote() { - assertThat(this.dialect.applyQuote("tableName"), equalTo("[tableName]")); + void testGetLiteralStringNull() { + assertThat(this.dialect.getStringLiteral(null), CoreMatchers.equalTo("NULL")); } @Test 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..b767fc88b --- /dev/null +++ b/src/test/java/com/exasol/adapter/dialects/sybase/SybaseIdentifierTest.java @@ -0,0 +1,29 @@ +package com.exasol.adapter.dialects.sybase; + +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 SybaseIdentifierTest { + @ParameterizedTest + @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", "テスト", "таблица", "123 column one" }) + 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..736deb043 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,9 @@ 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.CsvSource; +import org.junit.jupiter.params.provider.ValueSource; class SybaseSqlDialectTest { private SqlDialect dialect; @@ -73,7 +79,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 +88,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 +139,30 @@ void testApplyQuote() { assertThat(this.dialect.applyQuote("tableName"), equalTo("[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) { + assertThat(this.dialect.getStringLiteral(definition.substring(0, definition.indexOf(':'))), + equalTo(definition.substring(definition.indexOf(':') + 1))); + } + + @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 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..026159cc7 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,24 @@ 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), Matchers.equalTo(quoted)); + } + + @ValueSource(strings = { "ab:'ab'", "a'b:'a''b'", "a''b:'a''''b'", "'ab':'''ab'''" }) + @ParameterizedTest + void testGetLiteralString(final String definition) { + assertThat(this.dialect.getStringLiteral(definition.substring(0, definition.indexOf(':'))), + equalTo(definition.substring(definition.indexOf(':') + 1))); + } + @Test - void testApplyQuote() { - assertThat(this.dialect.applyQuote("tableName"), equalTo("\"tableName\"")); + void testGetLiteralStringNull() { + assertThat(this.dialect.getStringLiteral(null), CoreMatchers.equalTo("NULL")); } @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