diff --git a/.gitattributes b/.gitattributes index 91e2b11..200ee40 100644 --- a/.gitattributes +++ b/.gitattributes @@ -8,6 +8,7 @@ pk_generated_parent.pom linguist-genera .github/workflows/release_droid_prepare_original_checksum.yml linguist-generated=true .github/workflows/release_droid_print_quick_checksum.yml linguist-generated=true .github/workflows/release_droid_release_on_maven_central.yml linguist-generated=true +.github/workflows/release_droid_upload_github_release_assets.yml linguist-generated=true .settings/org.eclipse.jdt.core.prefs linguist-generated=true .settings/org.eclipse.jdt.ui.prefs linguist-generated=true diff --git a/.github/workflows/broken_links_checker.yml b/.github/workflows/broken_links_checker.yml index 82ec1cd..0fbcad5 100644 --- a/.github/workflows/broken_links_checker.yml +++ b/.github/workflows/broken_links_checker.yml @@ -1,3 +1,5 @@ +# Generated by Project Keeper +# https://github.com/exasol/project-keeper/blob/main/project-keeper/src/main/resources/templates/.github/workflows/broken_links_checker.yml name: Broken Links Checker on: diff --git a/.github/workflows/ci-build-next-java.yml b/.github/workflows/ci-build-next-java.yml index 7cbab08..e3acdb7 100644 --- a/.github/workflows/ci-build-next-java.yml +++ b/.github/workflows/ci-build-next-java.yml @@ -1,5 +1,6 @@ +# Generated by Project Keeper +# https://github.com/exasol/project-keeper/blob/main/project-keeper/src/main/resources/templates/.github/workflows/ci-build-next-java.yml name: CI Build next Java - on: push: branches: @@ -18,7 +19,7 @@ jobs: with: fetch-depth: 0 - name: Set up JDK 17 - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: distribution: "temurin" java-version: 17 @@ -26,8 +27,9 @@ jobs: - name: Run tests and build with Maven run: | mvn --batch-mode --update-snapshots clean package -DtrimStackTrace=false \ + -Djava.version=17 \ -Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn - - name: Publish Test Report + - name: Publish Test Report for Java 17 uses: scacap/action-surefire-report@v1 if: ${{ always() && github.event.pull_request.head.repo.full_name == github.repository && github.actor != 'dependabot[bot]' }} with: diff --git a/.github/workflows/ci-build.yml b/.github/workflows/ci-build.yml index acf3c0e..fd65a06 100644 --- a/.github/workflows/ci-build.yml +++ b/.github/workflows/ci-build.yml @@ -8,7 +8,7 @@ on: pull_request: jobs: - build: + matrix-build: runs-on: ubuntu-20.04 concurrency: group: ${{ github.workflow }}-${{ github.ref }}-${{ matrix.exasol_db_version }} @@ -16,9 +16,9 @@ jobs: strategy: fail-fast: false matrix: - exasol_db_version: ["7.1.24"] + exasol_db_version: ["7.1.25"] env: - DEFAULT_EXASOL_DB_VERSION: "7.1.24" + DEFAULT_EXASOL_DB_VERSION: "7.1.25" steps: - name: Free Disk Space if: ${{ false }} @@ -29,13 +29,13 @@ jobs: uses: actions/checkout@v4 with: fetch-depth: 0 - - name: Set up JDK 11 & 17 - uses: actions/setup-java@v3 + - name: Set up JDKs + uses: actions/setup-java@v4 with: distribution: "temurin" java-version: | - 17 11 + 17 cache: "maven" - name: Cache SonarCloud packages uses: actions/cache@v3 @@ -47,7 +47,7 @@ jobs: run: echo 'testcontainers.reuse.enable=true' > "$HOME/.testcontainers.properties" - name: Run tests and build with Maven run: | - JAVA_HOME=$JAVA_HOME_11_X64 mvn --batch-mode clean verify \ + mvn --batch-mode clean verify \ -Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn \ -DtrimStackTrace=false \ -Dcom.exasol.dockerdb.image=${{ matrix.exasol_db_version }} @@ -55,7 +55,7 @@ jobs: # Set additional environment variable as in scala projects the scalatest plugin does not forward # the system property -Dcom.exasol.dockerdb.image to the test's implementation. EXASOL_DB_VERSION: ${{ matrix.exasol_db_version }} - - name: Publish Test Report + - name: Publish Test Report for Exasol ${{ matrix.exasol_db_version }} uses: scacap/action-surefire-report@v1 if: ${{ always() && github.event.pull_request.head.repo.full_name == github.repository && github.actor != 'dependabot[bot]' }} with: @@ -63,12 +63,16 @@ jobs: - name: Sonar analysis if: ${{ env.SONAR_TOKEN != null && matrix.exasol_db_version == env.DEFAULT_EXASOL_DB_VERSION }} run: | - JAVA_HOME=$JAVA_HOME_17_X64 mvn --batch-mode org.sonarsource.scanner.maven:sonar-maven-plugin:sonar \ + mvn --batch-mode org.sonarsource.scanner.maven:sonar-maven-plugin:sonar \ -Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn \ -DtrimStackTrace=false \ - -Dsonar.organization=exasol \ - -Dsonar.host.url=https://sonarcloud.io \ -Dsonar.token=$SONAR_TOKEN env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + + build: + needs: matrix-build + runs-on: ubuntu-latest + steps: + - run: echo "Build successful" diff --git a/.github/workflows/dependencies_check.yml b/.github/workflows/dependencies_check.yml index 4b6eadf..87b64ba 100644 --- a/.github/workflows/dependencies_check.yml +++ b/.github/workflows/dependencies_check.yml @@ -1,5 +1,6 @@ +# Generated by Project Keeper +# https://github.com/exasol/project-keeper/blob/main/project-keeper/src/main/resources/templates/.github/workflows/dependencies_check.yml name: Report Security Issues for Repository - on: workflow_dispatch: schedule: @@ -13,11 +14,13 @@ jobs: steps: - uses: actions/checkout@v4 - - name: Set up JDK 11 - uses: actions/setup-java@v3 + - name: Set up JDKs + uses: actions/setup-java@v4 with: distribution: "temurin" - java-version: 11 + java-version: | + 11 + 17 cache: "maven" - name: Generate ossindex report diff --git a/.github/workflows/release_droid_prepare_original_checksum.yml b/.github/workflows/release_droid_prepare_original_checksum.yml index bc76555..413274b 100644 --- a/.github/workflows/release_droid_prepare_original_checksum.yml +++ b/.github/workflows/release_droid_prepare_original_checksum.yml @@ -1,5 +1,6 @@ +# Generated by Project Keeper +# https://github.com/exasol/project-keeper/blob/main/project-keeper/src/main/resources/templates/.github/workflows/release_droid_prepare_original_checksum.yml name: Release Droid - Prepare Original Checksum - on: workflow_dispatch: @@ -16,11 +17,13 @@ jobs: uses: actions/checkout@v4 with: fetch-depth: 0 - - name: Set up JDK 11 - uses: actions/setup-java@v3 + - name: Set up JDKs + uses: actions/setup-java@v4 with: distribution: "temurin" - java-version: 11 + java-version: | + 11 + 17 cache: "maven" - name: Enable testcontainer reuse run: echo 'testcontainers.reuse.enable=true' > "$HOME/.testcontainers.properties" diff --git a/.github/workflows/release_droid_print_quick_checksum.yml b/.github/workflows/release_droid_print_quick_checksum.yml index aed4444..86979cd 100644 --- a/.github/workflows/release_droid_print_quick_checksum.yml +++ b/.github/workflows/release_droid_print_quick_checksum.yml @@ -1,5 +1,6 @@ +# Generated by Project Keeper +# https://github.com/exasol/project-keeper/blob/main/project-keeper/src/main/resources/templates/.github/workflows/release_droid_print_quick_checksum.yml name: Release Droid - Print Quick Checksum - on: workflow_dispatch: @@ -11,11 +12,13 @@ jobs: uses: actions/checkout@v4 with: fetch-depth: 0 - - name: Set up JDK 11 - uses: actions/setup-java@v3 + - name: Set up JDKs + uses: actions/setup-java@v4 with: distribution: "temurin" - java-version: 11 + java-version: | + 11 + 17 cache: "maven" - name: Build with Maven skipping tests run: mvn --batch-mode clean verify -DskipTests diff --git a/.github/workflows/release_droid_release_on_maven_central.yml b/.github/workflows/release_droid_release_on_maven_central.yml index dfdbd6a..51d0659 100644 --- a/.github/workflows/release_droid_release_on_maven_central.yml +++ b/.github/workflows/release_droid_release_on_maven_central.yml @@ -1,5 +1,6 @@ +# Generated by Project Keeper +# https://github.com/exasol/project-keeper/blob/main/project-keeper/src/main/resources/templates/.github/workflows/release_droid_release_on_maven_central.yml name: Release Droid - Release On Maven Central - on: workflow_dispatch: @@ -12,10 +13,12 @@ jobs: with: fetch-depth: 0 - name: Set up Maven Central Repository - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: distribution: "temurin" - java-version: 11 + java-version: | + 11 + 17 cache: "maven" server-id: ossrh server-username: MAVEN_USERNAME diff --git a/.github/workflows/release_droid_upload_github_release_assets.yml b/.github/workflows/release_droid_upload_github_release_assets.yml index 7ae8bbb..b19f7cf 100644 --- a/.github/workflows/release_droid_upload_github_release_assets.yml +++ b/.github/workflows/release_droid_upload_github_release_assets.yml @@ -1,5 +1,6 @@ +# Generated by Project Keeper +# https://github.com/exasol/project-keeper/blob/main/project-keeper/src/main/resources/templates/.github/workflows/release_droid_upload_github_release_assets.yml name: Release Droid - Upload GitHub Release Assets - on: workflow_dispatch: inputs: @@ -15,11 +16,13 @@ jobs: uses: actions/checkout@v4 with: fetch-depth: 0 - - name: Set up JDK 11 - uses: actions/setup-java@v3 + - name: Set up JDKs + uses: actions/setup-java@v4 with: distribution: "temurin" - java-version: 11 + java-version: | + 11 + 17 cache: "maven" - name: Build with Maven skipping tests run: mvn --batch-mode clean verify -DskipTests diff --git a/.project-keeper.yml b/.project-keeper.yml index aeb7ad1..927a60e 100644 --- a/.project-keeper.yml +++ b/.project-keeper.yml @@ -9,4 +9,4 @@ sources: build: runnerOs: ubuntu-20.04 exasolDbVersions: - - "7.1.24" + - "7.1.25" diff --git a/.vscode/settings.json b/.vscode/settings.json index caf52c3..58dfe90 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,9 +1,9 @@ { "editor.formatOnSave": true, "editor.codeActionsOnSave": { - "source.organizeImports": true, - "source.generate.finalModifiers": true, - "source.fixAll": true + "source.organizeImports": "explicit", + "source.generate.finalModifiers": "explicit", + "source.fixAll": "explicit" }, "java.codeGeneration.useBlocks": true, "java.saveActions.organizeImports": true, diff --git a/dependencies.md b/dependencies.md index fec6150..4bd24ae 100644 --- a/dependencies.md +++ b/dependencies.md @@ -30,27 +30,28 @@ | Dependency | License | | ------------------------------------------------------- | --------------------------------- | | [SonarQube Scanner for Maven][26] | [GNU LGPL 3][27] | -| [Apache Maven Compiler Plugin][28] | [Apache-2.0][29] | -| [Apache Maven Enforcer Plugin][30] | [Apache-2.0][29] | -| [Maven Flatten Plugin][31] | [Apache Software Licenese][29] | -| [org.sonatype.ossindex.maven:ossindex-maven-plugin][32] | [ASL2][33] | -| [Maven Surefire Plugin][34] | [Apache-2.0][29] | -| [Versions Maven Plugin][35] | [Apache License, Version 2.0][29] | -| [duplicate-finder-maven-plugin Maven Mojo][36] | [Apache License 2.0][37] | -| [Project keeper maven plugin][38] | [The MIT License][39] | -| [Apache Maven Assembly Plugin][40] | [Apache-2.0][29] | -| [Apache Maven JAR Plugin][41] | [Apache License, Version 2.0][29] | -| [Artifact reference checker and unifier][42] | [MIT][11] | -| [Apache Maven Deploy Plugin][43] | [Apache-2.0][29] | -| [Apache Maven GPG Plugin][44] | [Apache-2.0][29] | -| [Apache Maven Source Plugin][45] | [Apache License, Version 2.0][29] | -| [Apache Maven Javadoc Plugin][46] | [Apache License, Version 2.0][29] | -| [Nexus Staging Maven Plugin][47] | [Eclipse Public License][48] | -| [Apache Maven Dependency Plugin][49] | [Apache-2.0][29] | -| [Maven Failsafe Plugin][50] | [Apache-2.0][29] | -| [JaCoCo :: Maven Plugin][51] | [Eclipse Public License 2.0][25] | -| [error-code-crawler-maven-plugin][52] | [MIT License][53] | -| [Reproducible Build Maven Plugin][54] | [Apache 2.0][33] | +| [Apache Maven Toolchains Plugin][28] | [Apache License, Version 2.0][29] | +| [Project Keeper Maven plugin][30] | [The MIT License][31] | +| [Apache Maven Compiler Plugin][32] | [Apache-2.0][29] | +| [Apache Maven Enforcer Plugin][33] | [Apache-2.0][29] | +| [Maven Flatten Plugin][34] | [Apache Software Licenese][29] | +| [org.sonatype.ossindex.maven:ossindex-maven-plugin][35] | [ASL2][36] | +| [Maven Surefire Plugin][37] | [Apache-2.0][29] | +| [Versions Maven Plugin][38] | [Apache License, Version 2.0][29] | +| [duplicate-finder-maven-plugin Maven Mojo][39] | [Apache License 2.0][40] | +| [Apache Maven Assembly Plugin][41] | [Apache-2.0][29] | +| [Apache Maven JAR Plugin][42] | [Apache License, Version 2.0][29] | +| [Artifact reference checker and unifier][43] | [MIT][11] | +| [Apache Maven Deploy Plugin][44] | [Apache-2.0][29] | +| [Apache Maven GPG Plugin][45] | [Apache-2.0][29] | +| [Apache Maven Source Plugin][46] | [Apache License, Version 2.0][29] | +| [Apache Maven Javadoc Plugin][47] | [Apache License, Version 2.0][29] | +| [Nexus Staging Maven Plugin][48] | [Eclipse Public License][49] | +| [Apache Maven Dependency Plugin][50] | [Apache-2.0][29] | +| [Maven Failsafe Plugin][51] | [Apache-2.0][29] | +| [JaCoCo :: Maven Plugin][52] | [Eclipse Public License 2.0][25] | +| [error-code-crawler-maven-plugin][53] | [MIT License][54] | +| [Reproducible Build Maven Plugin][55] | [Apache 2.0][36] | [0]: https://github.com/exasol/virtual-schema-common-jdbc/ [1]: https://github.com/exasol/virtual-schema-common-jdbc/blob/main/LICENSE @@ -80,30 +81,31 @@ [25]: https://www.eclipse.org/legal/epl-2.0/ [26]: http://sonarsource.github.io/sonar-scanner-maven/ [27]: http://www.gnu.org/licenses/lgpl.txt -[28]: https://maven.apache.org/plugins/maven-compiler-plugin/ +[28]: https://maven.apache.org/plugins/maven-toolchains-plugin/ [29]: https://www.apache.org/licenses/LICENSE-2.0.txt -[30]: https://maven.apache.org/enforcer/maven-enforcer-plugin/ -[31]: https://www.mojohaus.org/flatten-maven-plugin/ -[32]: https://sonatype.github.io/ossindex-maven/maven-plugin/ -[33]: http://www.apache.org/licenses/LICENSE-2.0.txt -[34]: https://maven.apache.org/surefire/maven-surefire-plugin/ -[35]: https://www.mojohaus.org/versions/versions-maven-plugin/ -[36]: https://basepom.github.io/duplicate-finder-maven-plugin -[37]: http://www.apache.org/licenses/LICENSE-2.0.html -[38]: https://github.com/exasol/project-keeper/ -[39]: https://github.com/exasol/project-keeper/blob/main/LICENSE -[40]: https://maven.apache.org/plugins/maven-assembly-plugin/ -[41]: https://maven.apache.org/plugins/maven-jar-plugin/ -[42]: https://github.com/exasol/artifact-reference-checker-maven-plugin -[43]: https://maven.apache.org/plugins/maven-deploy-plugin/ -[44]: https://maven.apache.org/plugins/maven-gpg-plugin/ -[45]: https://maven.apache.org/plugins/maven-source-plugin/ -[46]: https://maven.apache.org/plugins/maven-javadoc-plugin/ -[47]: http://www.sonatype.com/public-parent/nexus-maven-plugins/nexus-staging/nexus-staging-maven-plugin/ -[48]: http://www.eclipse.org/legal/epl-v10.html -[49]: https://maven.apache.org/plugins/maven-dependency-plugin/ -[50]: https://maven.apache.org/surefire/maven-failsafe-plugin/ -[51]: https://www.jacoco.org/jacoco/trunk/doc/maven.html -[52]: https://github.com/exasol/error-code-crawler-maven-plugin/ -[53]: https://github.com/exasol/error-code-crawler-maven-plugin/blob/main/LICENSE -[54]: http://zlika.github.io/reproducible-build-maven-plugin +[30]: https://github.com/exasol/project-keeper/ +[31]: https://github.com/exasol/project-keeper/blob/main/LICENSE +[32]: https://maven.apache.org/plugins/maven-compiler-plugin/ +[33]: https://maven.apache.org/enforcer/maven-enforcer-plugin/ +[34]: https://www.mojohaus.org/flatten-maven-plugin/ +[35]: https://sonatype.github.io/ossindex-maven/maven-plugin/ +[36]: http://www.apache.org/licenses/LICENSE-2.0.txt +[37]: https://maven.apache.org/surefire/maven-surefire-plugin/ +[38]: https://www.mojohaus.org/versions/versions-maven-plugin/ +[39]: https://basepom.github.io/duplicate-finder-maven-plugin +[40]: http://www.apache.org/licenses/LICENSE-2.0.html +[41]: https://maven.apache.org/plugins/maven-assembly-plugin/ +[42]: https://maven.apache.org/plugins/maven-jar-plugin/ +[43]: https://github.com/exasol/artifact-reference-checker-maven-plugin +[44]: https://maven.apache.org/plugins/maven-deploy-plugin/ +[45]: https://maven.apache.org/plugins/maven-gpg-plugin/ +[46]: https://maven.apache.org/plugins/maven-source-plugin/ +[47]: https://maven.apache.org/plugins/maven-javadoc-plugin/ +[48]: http://www.sonatype.com/public-parent/nexus-maven-plugins/nexus-staging/nexus-staging-maven-plugin/ +[49]: http://www.eclipse.org/legal/epl-v10.html +[50]: https://maven.apache.org/plugins/maven-dependency-plugin/ +[51]: https://maven.apache.org/surefire/maven-failsafe-plugin/ +[52]: https://www.jacoco.org/jacoco/trunk/doc/maven.html +[53]: https://github.com/exasol/error-code-crawler-maven-plugin/ +[54]: https://github.com/exasol/error-code-crawler-maven-plugin/blob/main/LICENSE +[55]: http://zlika.github.io/reproducible-build-maven-plugin diff --git a/doc/changes/changelog.md b/doc/changes/changelog.md index e833da0..d8b1f12 100644 --- a/doc/changes/changelog.md +++ b/doc/changes/changelog.md @@ -1,5 +1,6 @@ # Changes +* [7.2.0](changes_7.2.0.md) * [7.1.6](changes_7.1.6.md) * [7.1.5](changes_7.1.5.md) * [7.1.4](changes_7.1.4.md) diff --git a/doc/changes/changes_7.2.0.md b/doc/changes/changes_7.2.0.md new file mode 100644 index 0000000..72ef0c2 --- /dev/null +++ b/doc/changes/changes_7.2.0.md @@ -0,0 +1,42 @@ +# Exasol Virtual Schema 7.2.0, released 2024-02-22 + +Code name: Add parameter `GENERATE_JDBC_DATATYPE_MAPPING_FOR_EXA` + +## Summary + +Using `IMPORT FROM EXA` might lead to some unexpected datatype mappings. Unlike for a JDBC connection there's no explicit data mapping being generated when using `IMPORT FROM EXA`. The Exasol specific types `GEOMETRY`, `INTERVAL YEAR TO MONTH`, `INTERVAL DAY TO SECOND` and `HASHTYPE` are mapped to `VARCHAR`. This release adds parameter `GENERATE_JDBC_DATATYPE_MAPPING_FOR_EXA`. When setting this to `true`, the data types are mapped as expected. + +Setting `GENERATE_JDBC_DATATYPE_MAPPING_FOR_EXA` to `true` also fixes a bug when joining a table in a virtual schema with a normal table on a `HASHTYPE` column. This failed before in Exasol 7.1 with error message `Feature not supported: Incomparable Types: VARCHAR(32) UTF8 and HASHTYPE(16 BYTE)!`. + +See the [user guide](../dialects/exasol.md#map-data-types-with-exa-import) for details. + +This release also fixes vulnerabilities CVE-2024-25710 and CVE-2024-26308 in transitive test dependency `org.apache.commons:commons-compress`. + +## Security + +* #120: Fixed CVE-2024-25710 in `org.apache.commons:commons-compress` +* #121: Fixed CVE-2024-26308 in `org.apache.commons:commons-compress` + +## Bugfixes + +* #119: Fixed data types for `IMPORT FROM EXA` + +## Dependency Updates + +### Test Dependency Updates + +* Updated `com.exasol:exasol-testcontainers:6.6.3` to `7.0.1` +* Updated `com.exasol:hamcrest-resultset-matcher:1.6.3` to `1.6.4` +* Updated `com.exasol:test-db-builder-java:3.5.2` to `3.5.3` +* Updated `org.junit.jupiter:junit-jupiter:5.10.1` to `5.10.2` +* Updated `org.mockito:mockito-junit-jupiter:5.7.0` to `5.10.0` +* Updated `org.slf4j:slf4j-jdk14:2.0.9` to `2.0.12` +* Updated `org.testcontainers:junit-jupiter:1.19.2` to `1.19.6` + +### Plugin Dependency Updates + +* Updated `com.exasol:project-keeper-maven-plugin:2.9.16` to `3.0.1` +* Updated `org.apache.maven.plugins:maven-failsafe-plugin:3.2.2` to `3.2.3` +* Updated `org.apache.maven.plugins:maven-surefire-plugin:3.2.2` to `3.2.3` +* Added `org.apache.maven.plugins:maven-toolchains-plugin:3.1.0` +* Updated `org.codehaus.mojo:versions-maven-plugin:2.16.1` to `2.16.2` diff --git a/doc/dialects/exasol.md b/doc/dialects/exasol.md index e61a343..370ed55 100644 --- a/doc/dialects/exasol.md +++ b/doc/dialects/exasol.md @@ -17,7 +17,7 @@ The SQL statement below creates the adapter script, defines the Java class that ```sql CREATE JAVA ADAPTER SCRIPT SCHEMA_FOR_VS_SCRIPT.ADAPTER_SCRIPT_EXASOL AS %scriptclass com.exasol.adapter.RequestDispatcher; - %jar /buckets///virtual-schema-dist-11.0.2-exasol-7.1.6.jar; + %jar /buckets///virtual-schema-dist-11.0.2-exasol-7.2.0.jar; / ``` @@ -84,6 +84,36 @@ USING SCHEMA_FOR_VS_SCRIPT.ADAPTER_SCRIPT_EXASOL WITH EXA_CONNECTION = 'EXA_CONNECTION'; ``` +#### Map Datatypes With EXA Import + +Unlike for a JDBC connection `IMPORT FROM EXA` does not use an explicit datatype mapping. In consequence columns of type `HASHTYPE` are mapped to `VARCHAR` and joining such a column therefore failed in Exasol 7.1 with error message `Feature not supported: Incomparable Types: VARCHAR(32) UTF8 and HASHTYPE(16 BYTE)!`. + +Exasol Virtual Schema in version 7.2.0 and later mitigates this by offering parameter `GENERATE_JDBC_DATATYPE_MAPPING_FOR_EXA` with values `true` and `false` (default): + +```sql +CREATE VIRTUAL SCHEMA VIRTUAL_EXASOL +USING SCHEMA_FOR_VS_SCRIPT.ADAPTER_SCRIPT_EXASOL WITH + CONNECTION_NAME = 'JDBC_CONNECTION' + SCHEMA_NAME = '' + IMPORT_FROM_EXA = 'true' + EXA_CONNECTION = 'EXA_CONNECTION' + GENERATE_JDBC_DATATYPE_MAPPING_FOR_EXA = 'true'; +``` + +This will add explicit datatype mapping to the generated command when using `IMPORT FROM EXA`. + +Example for the generated pushdown query with `GENERATE_JDBC_DATATYPE_MAPPING_FOR_EXA = 'false'` (default): + +```sql +IMPORT FROM EXA AT "EXA_CONNECTION" STATEMENT '...' +``` + +Pushdown query with `GENERATE_JDBC_DATATYPE_MAPPING_FOR_EXA = 'true'`: + +```sql +IMPORT INTO (c1 DECIMAL(36,1), c2 .... ) FROM EXA AT "EXA_CONNECTION" STATEMENT '...' +``` + ### Using `IMPORT FROM JDBC` You can alternatively use a regular JDBC connection for the `IMPORT`. Note that this option is slower because it lacks the parallelization the `IMPORT FROM EXA` variant. diff --git a/doc/error_codes.md b/doc/error_codes.md deleted file mode 100644 index 5c37eb2..0000000 --- a/doc/error_codes.md +++ /dev/null @@ -1,3 +0,0 @@ -# Latest Used Error Codes - -E-VS-EXA-5 \ No newline at end of file diff --git a/pk_generated_parent.pom b/pk_generated_parent.pom index 4891cfa..be8777a 100644 --- a/pk_generated_parent.pom +++ b/pk_generated_parent.pom @@ -3,12 +3,14 @@ 4.0.0 com.exasol exasol-virtual-schema-generated-parent - 7.1.6 + 7.2.0 pom UTF-8 UTF-8 11 + exasol + https://sonarcloud.io true @@ -58,6 +60,25 @@ sonar-maven-plugin 3.10.0.2594 + + org.apache.maven.plugins + maven-toolchains-plugin + 3.1.0 + + + + toolchain + + + + + + + ${java.version} + + + + org.apache.maven.plugins maven-compiler-plugin @@ -88,6 +109,9 @@ 3.6.3 + + 17 + @@ -135,7 +159,7 @@ org.apache.maven.plugins maven-surefire-plugin - 3.2.2 + 3.2.3 @@ -146,7 +170,7 @@ org.codehaus.mojo versions-maven-plugin - 2.16.1 + 2.16.2 display-updates @@ -283,7 +307,7 @@ org.apache.maven.plugins maven-javadoc-plugin - 3.6.2 + 3.6.3 attach-javadocs @@ -345,7 +369,7 @@ org.apache.maven.plugins maven-failsafe-plugin - 3.2.2 + 3.2.3 -Djava.util.logging.config.file=src/test/resources/logging.properties ${argLine} diff --git a/pom.xml b/pom.xml index c8d1b95..d9dea63 100644 --- a/pom.xml +++ b/pom.xml @@ -2,7 +2,7 @@ 4.0.0 exasol-virtual-schema - 7.1.6 + 7.2.0 Exasol Virtual Schema This projects contains the Exasol dialect for Exasol's Virtual Schema https://github.com/exasol/exasol-virtual-schema/ @@ -43,44 +43,44 @@ org.junit.jupiter junit-jupiter - 5.10.1 + 5.10.2 test org.mockito mockito-junit-jupiter - 5.7.0 + 5.10.0 test com.exasol exasol-testcontainers - 6.6.3 + 7.0.1 test org.testcontainers junit-jupiter - 1.19.2 + 1.19.6 test com.exasol hamcrest-resultset-matcher - 1.6.3 + 1.6.4 test org.slf4j slf4j-jdk14 - 2.0.9 + 2.0.12 test com.exasol test-db-builder-java - 3.5.2 + 3.5.3 test @@ -95,7 +95,7 @@ com.exasol project-keeper-maven-plugin - 2.9.16 + 3.0.1 @@ -104,6 +104,19 @@ + + org.apache.maven.plugins + maven-compiler-plugin + + + + + -Xlint:all,-processing,-path + -Werror + + + maven-assembly-plugin @@ -162,7 +175,7 @@ exasol-virtual-schema-generated-parent com.exasol - 7.1.6 + 7.2.0 pk_generated_parent.pom diff --git a/src/main/java/com/exasol/adapter/dialects/exasol/ExasolConnectionDefinitionBuilder.java b/src/main/java/com/exasol/adapter/dialects/exasol/ExasolConnectionDefinitionBuilder.java index 0bdfe47..5b5e12b 100644 --- a/src/main/java/com/exasol/adapter/dialects/exasol/ExasolConnectionDefinitionBuilder.java +++ b/src/main/java/com/exasol/adapter/dialects/exasol/ExasolConnectionDefinitionBuilder.java @@ -44,4 +44,4 @@ private String buildImportFromExaConnectionDefinition(final AdapterProperties pr private String getExasolConnectionName(final AdapterProperties properties) { return properties.get(EXASOL_CONNECTION_PROPERTY); } -} \ No newline at end of file +} diff --git a/src/main/java/com/exasol/adapter/dialects/exasol/ExasolFromExaQueryRewriter.java b/src/main/java/com/exasol/adapter/dialects/exasol/ExasolFromExaQueryRewriter.java index 7752763..fc6bbac 100644 --- a/src/main/java/com/exasol/adapter/dialects/exasol/ExasolFromExaQueryRewriter.java +++ b/src/main/java/com/exasol/adapter/dialects/exasol/ExasolFromExaQueryRewriter.java @@ -5,7 +5,8 @@ import com.exasol.adapter.jdbc.RemoteMetadataReader; /** - * Exasol-specific query rewriter for {@code IMPORT FROM EXA}. + * Exasol-specific query rewriter for {@code IMPORT FROM EXA} that does not add data types to the pushdown query. Data + * types like {@code HASHTYPE} will be reported as {@code VARCHAR}. */ public class ExasolFromExaQueryRewriter extends AbstractQueryRewriter { @@ -23,4 +24,4 @@ public ExasolFromExaQueryRewriter(final SqlDialect dialect, final RemoteMetadata protected String generateImportStatement(final String connectionDefinition, final String pushdownQuery) { return "IMPORT FROM EXA " + connectionDefinition + " STATEMENT '" + pushdownQuery.replace("'", "''") + "'"; } -} \ No newline at end of file +} diff --git a/src/main/java/com/exasol/adapter/dialects/exasol/ExasolFromExaWithDataTypeQueryRewriter.java b/src/main/java/com/exasol/adapter/dialects/exasol/ExasolFromExaWithDataTypeQueryRewriter.java new file mode 100644 index 0000000..3fdc0ed --- /dev/null +++ b/src/main/java/com/exasol/adapter/dialects/exasol/ExasolFromExaWithDataTypeQueryRewriter.java @@ -0,0 +1,59 @@ +package com.exasol.adapter.dialects.exasol; + +import java.sql.SQLException; +import java.util.List; +import java.util.logging.Logger; + +import com.exasol.adapter.dialects.SqlDialect; +import com.exasol.adapter.dialects.rewriting.AbstractQueryRewriter; +import com.exasol.adapter.dialects.rewriting.SqlGenerationHelper; +import com.exasol.adapter.jdbc.*; +import com.exasol.adapter.metadata.DataType; + +/** + * Exasol-specific query rewriter for {@code IMPORT FROM EXA} that adds data types to the pushdown query. Data types + * like {@code HASHTYPE} will be reported correctly. + *

+ * This rewriter is similar to {@link ExasolJdbcQueryRewriter} but uses {@code IMPORT INTO (...) FROM EXA}. + */ +class ExasolFromExaWithDataTypeQueryRewriter extends AbstractQueryRewriter { + + private static final Logger LOGGER = Logger.getLogger(ExasolFromExaWithDataTypeQueryRewriter.class.getName()); + private final ConnectionFactory connectionFactory; + + ExasolFromExaWithDataTypeQueryRewriter(final SqlDialect dialect, final RemoteMetadataReader remoteMetadataReader, + final ConnectionFactory connectionFactory) { + super(dialect, remoteMetadataReader, new ExasolConnectionDefinitionBuilder()); + this.connectionFactory = connectionFactory; + } + + @Override + protected String generateImportStatement(final String connectionDefinition, + final List selectListDataTypes, final String pushdownQuery) throws SQLException { + return generateImportStatement(SqlGenerationHelper.createColumnsDescriptionFromDataTypes(selectListDataTypes), + connectionDefinition, pushdownQuery); + } + + @Override + protected String generateImportStatement(final String connectionDefinition, final String pushdownQuery) + throws SQLException { + return generateImportStatement(createColumnsDescriptionFromQuery(pushdownQuery), connectionDefinition, + pushdownQuery); + } + + private String generateImportStatement(final String columnsDescription, final String connectionDefinition, + final String pushdownQuery) { + return "IMPORT INTO (" + columnsDescription + ") FROM EXA " // + + connectionDefinition + " STATEMENT '" // + + pushdownQuery.replace("'", "''") + "'"; + } + + private String createColumnsDescriptionFromQuery(final String query) throws SQLException { + final ColumnMetadataReader columnMetadataReader = this.remoteMetadataReader.getColumnMetadataReader(); + final ResultSetMetadataReader resultSetMetadataReader = new ResultSetMetadataReader( + this.connectionFactory.getConnection(), columnMetadataReader); + final String columnsDescription = resultSetMetadataReader.describeColumns(query); + LOGGER.finer(() -> "Import columns: " + columnsDescription); + return columnsDescription; + } +} diff --git a/src/main/java/com/exasol/adapter/dialects/exasol/ExasolJdbcQueryRewriter.java b/src/main/java/com/exasol/adapter/dialects/exasol/ExasolJdbcQueryRewriter.java index 67edc73..e049054 100644 --- a/src/main/java/com/exasol/adapter/dialects/exasol/ExasolJdbcQueryRewriter.java +++ b/src/main/java/com/exasol/adapter/dialects/exasol/ExasolJdbcQueryRewriter.java @@ -8,7 +8,7 @@ /** * Exasol-specific query rewriter for regular JDBC connections to the remote Exasol data source. */ -public class ExasolJdbcQueryRewriter extends ImportIntoTemporaryTableQueryRewriter { +class ExasolJdbcQueryRewriter extends ImportIntoTemporaryTableQueryRewriter { /** * Create a new instance of the {@link ExasolJdbcQueryRewriter}. * @@ -16,8 +16,8 @@ public class ExasolJdbcQueryRewriter extends ImportIntoTemporaryTableQueryRewrit * @param remoteMetadataReader remote metadata reader * @param connectionFactory factory for JDBC connection to remote data source */ - public ExasolJdbcQueryRewriter(final SqlDialect dialect, final RemoteMetadataReader remoteMetadataReader, + ExasolJdbcQueryRewriter(final SqlDialect dialect, final RemoteMetadataReader remoteMetadataReader, final ConnectionFactory connectionFactory) { super(dialect, remoteMetadataReader, connectionFactory, new ExasolConnectionDefinitionBuilder()); } -} \ No newline at end of file +} diff --git a/src/main/java/com/exasol/adapter/dialects/exasol/ExasolLocalQueryRewriter.java b/src/main/java/com/exasol/adapter/dialects/exasol/ExasolLocalQueryRewriter.java index 1f3c0bd..08cf169 100644 --- a/src/main/java/com/exasol/adapter/dialects/exasol/ExasolLocalQueryRewriter.java +++ b/src/main/java/com/exasol/adapter/dialects/exasol/ExasolLocalQueryRewriter.java @@ -19,7 +19,7 @@ * embedded in. That saves the overhead of using the ExaLoader and is thus considerably faster. *

*/ -public class ExasolLocalQueryRewriter implements QueryRewriter { +class ExasolLocalQueryRewriter implements QueryRewriter { private static final Logger LOGGER = Logger.getLogger(ExasolLocalQueryRewriter.class.getName()); private final SqlDialect dialect; @@ -29,7 +29,7 @@ public class ExasolLocalQueryRewriter implements QueryRewriter { * * @param dialect dialect */ - public ExasolLocalQueryRewriter(final SqlDialect dialect) { + ExasolLocalQueryRewriter(final SqlDialect dialect) { this.dialect = dialect; } @@ -43,4 +43,4 @@ public String rewrite(final SqlStatement statement, final List selectL LOGGER.finer(() -> "SELECT push-down statement:\n" + selectStatement); return selectStatement; } -} \ No newline at end of file +} diff --git a/src/main/java/com/exasol/adapter/dialects/exasol/ExasolProperties.java b/src/main/java/com/exasol/adapter/dialects/exasol/ExasolProperties.java index b4aaa42..7b98861 100644 --- a/src/main/java/com/exasol/adapter/dialects/exasol/ExasolProperties.java +++ b/src/main/java/com/exasol/adapter/dialects/exasol/ExasolProperties.java @@ -7,8 +7,9 @@ final class ExasolProperties { static final String EXASOL_IMPORT_PROPERTY = "IMPORT_FROM_EXA"; static final String EXASOL_CONNECTION_PROPERTY = "EXA_CONNECTION"; static final String EXASOL_IS_LOCAL_PROPERTY = "IS_LOCAL"; + static final String GENERATE_JDBC_DATATYPE_MAPPING_FOR_EXA = "GENERATE_JDBC_DATATYPE_MAPPING_FOR_EXA"; private ExasolProperties() { // prevent instantiation } -} \ No newline at end of file +} diff --git a/src/main/java/com/exasol/adapter/dialects/exasol/ExasolSqlDialect.java b/src/main/java/com/exasol/adapter/dialects/exasol/ExasolSqlDialect.java index 69b0821..8ebd0a9 100644 --- a/src/main/java/com/exasol/adapter/dialects/exasol/ExasolSqlDialect.java +++ b/src/main/java/com/exasol/adapter/dialects/exasol/ExasolSqlDialect.java @@ -35,10 +35,11 @@ public class ExasolSqlDialect extends AbstractSqlDialect { public ExasolSqlDialect(final ConnectionFactory connectionFactory, final AdapterProperties properties) { super(connectionFactory, properties, Set.of(CATALOG_NAME_PROPERTY, SCHEMA_NAME_PROPERTY, EXASOL_IMPORT_PROPERTY, EXASOL_CONNECTION_PROPERTY, - EXASOL_IS_LOCAL_PROPERTY, IGNORE_ERRORS_PROPERTY), // + EXASOL_IS_LOCAL_PROPERTY, IGNORE_ERRORS_PROPERTY, GENERATE_JDBC_DATATYPE_MAPPING_FOR_EXA), // List.of(SchemaNameProperty.validator(NAME), // BooleanProperty.validator(EXASOL_IMPORT_PROPERTY), // BooleanProperty.validator(EXASOL_IS_LOCAL_PROPERTY), // + BooleanProperty.validator(GENERATE_JDBC_DATATYPE_MAPPING_FOR_EXA), // ImportProperty.validator(EXASOL_IMPORT_PROPERTY, EXASOL_CONNECTION_PROPERTY))); this.omitParenthesesMap.addAll(Set.of(SYSDATE, SYSTIMESTAMP, CURRENT_SCHEMA, CURRENT_SESSION, CURRENT_STATEMENT, CURRENT_USER, CURRENT_CLUSTER)); @@ -92,17 +93,26 @@ protected RemoteMetadataReader createRemoteMetadataReader() { protected QueryRewriter createQueryRewriter() { if (this.properties.isEnabled(EXASOL_IS_LOCAL_PROPERTY)) { return new ExasolLocalQueryRewriter(this); - } else if (isImportFromExa(this.properties)) { - return new ExasolFromExaQueryRewriter(this, createRemoteMetadataReader()); + } else if (isImportFromExa()) { + if (isGenerateJdbcDataTypeMappingForExa()) { + return new ExasolFromExaWithDataTypeQueryRewriter(this, createRemoteMetadataReader(), + this.connectionFactory); + } else { + return new ExasolFromExaQueryRewriter(this, createRemoteMetadataReader()); + } } else { return new ExasolJdbcQueryRewriter(this, createRemoteMetadataReader(), this.connectionFactory); } } - private boolean isImportFromExa(final AdapterProperties properties) { + private boolean isImportFromExa() { return properties.isEnabled(EXASOL_IMPORT_PROPERTY); } + private boolean isGenerateJdbcDataTypeMappingForExa() { + return properties.isEnabled(GENERATE_JDBC_DATATYPE_MAPPING_FOR_EXA); + } + @Override public StructureElementSupport supportsJdbcCatalogs() { return StructureElementSupport.SINGLE; diff --git a/src/test/java/com/exasol/adapter/dialects/exasol/AbstractExasolSqlDialectIT.java b/src/test/java/com/exasol/adapter/dialects/exasol/AbstractExasolSqlDialectIT.java index 09fe05f..2b19a0c 100644 --- a/src/test/java/com/exasol/adapter/dialects/exasol/AbstractExasolSqlDialectIT.java +++ b/src/test/java/com/exasol/adapter/dialects/exasol/AbstractExasolSqlDialectIT.java @@ -18,6 +18,7 @@ import java.text.MessageFormat; import java.util.*; import java.util.concurrent.TimeoutException; +import java.util.logging.Logger; import org.hamcrest.Matcher; import org.junit.jupiter.api.*; @@ -42,6 +43,7 @@ @Tag("integration") @Testcontainers abstract class AbstractExasolSqlDialectIT { + private static final Logger LOG = Logger.getLogger(AbstractExasolSqlDialectIT.class.getName()); private static final String COLUMN1_NAME = "C1"; @Container @@ -85,6 +87,11 @@ static void afterAll() throws SQLException { connection.close(); } + @BeforeEach + void logTestName(final TestInfo testInfo) { + LOG.fine(() -> "Running test " + testInfo.getDisplayName() + "..."); + } + @BeforeEach void beforeEach() { this.sourceSchema = objectFactory.createSchema("SOURCE_SCHEMA"); @@ -887,6 +894,21 @@ void testGroupByWithColumnNumber() throws SQLException { } } + @Test + @DisplayName("Verify that a virtual and a normal table can be joined using a HASHTYPE column") + void joinHashtypeTables() throws java.sql.SQLException { + final Table virtualTable = sourceSchema.createTableBuilder("VIRTUAL").column("VHASH", "HASHTYPE(16 BYTE)") + .build(); + try (final ExasolSchema otherSchema = objectFactory.createSchema("OTHER"); + final Table otherTable = otherSchema.createTableBuilder("REAL").column("RHASH", "HASHTYPE(16 BYTE)") + .build(); + final VirtualSchema virtualSchema = createVirtualSchema(this.sourceSchema)) { + final String sql = "select * from " + virtualSchema.getFullyQualifiedName() + "." + virtualTable.getName() + + " INNER JOIN " + otherTable.getFullyQualifiedName() + " ON VHASH = RHASH"; + assertThat(query(sql), table("HASHTYPE", "HASHTYPE").matches()); + } + } + boolean isVersionOrHigher(final int majorVersion, final int minorVersion, final int fixVersion) { final ExasolDockerImageReference version = EXASOL.getDockerImageReference(); final long comparableImageVersion = calculatedComparableVersion((version.hasMajor() ? version.getMajor() : 0), diff --git a/src/test/java/com/exasol/adapter/dialects/exasol/AbstractRemoteExasolVirtualSchemaConnectionIT.java b/src/test/java/com/exasol/adapter/dialects/exasol/AbstractRemoteExasolVirtualSchemaConnectionIT.java index f0bf0bb..4c48635 100644 --- a/src/test/java/com/exasol/adapter/dialects/exasol/AbstractRemoteExasolVirtualSchemaConnectionIT.java +++ b/src/test/java/com/exasol/adapter/dialects/exasol/AbstractRemoteExasolVirtualSchemaConnectionIT.java @@ -11,7 +11,7 @@ */ abstract class AbstractRemoteExasolVirtualSchemaConnectionIT extends AbstractExasolSqlDialectIT { @Test - void testInvervalYearToMonthMapping() { + void testIntervalYearToMonthMapping() { final Table table = createSingleColumnTable("INTERVAL YEAR (9) TO MONTH")// .insert("-999999999-11") // .insert("-1-1") // diff --git a/src/test/java/com/exasol/adapter/dialects/exasol/ExasolColumnMetadataReaderTest.java b/src/test/java/com/exasol/adapter/dialects/exasol/ExasolColumnMetadataReaderTest.java index e16a9f2..287dfa6 100644 --- a/src/test/java/com/exasol/adapter/dialects/exasol/ExasolColumnMetadataReaderTest.java +++ b/src/test/java/com/exasol/adapter/dialects/exasol/ExasolColumnMetadataReaderTest.java @@ -113,7 +113,7 @@ void testMapJdbcTypeVarcharMaxLength() { } @Test - void testMapJdbcTypeVarcharWithUnknownJdbcTypenName() { + void testMapJdbcTypeVarcharWithUnknownJdbcTypeName() { assertTypeMapped(varchar(5).typeName("unknown"), DataType.createVarChar(5, ExaCharset.UTF8)); } @@ -133,12 +133,12 @@ void testMapJdbcTypeLongCharMappedToVarchar() { } @Test - void testMapJdbcTypeLongCharMappedToVarcharMaxVarcharLenth() { + void testMapJdbcTypeLongCharMappedToVarcharMaxVarcharLength() { assertTypeMapped(charType(2_000_001), DataType.createVarChar(2_000_000, ExaCharset.UTF8)); } @Test - void testMapJdbcTypeCharWithUnknownJdbcTypenName() { + void testMapJdbcTypeCharWithUnknownJdbcTypeName() { assertTypeMapped(charType(5).typeName("unknown"), DataType.createChar(5, ExaCharset.UTF8)); } @@ -239,4 +239,4 @@ JDBCTypeDescription build() { this.typeName); } } -} \ No newline at end of file +} diff --git a/src/test/java/com/exasol/adapter/dialects/exasol/ExasolFromExaQueryRewriterTest.java b/src/test/java/com/exasol/adapter/dialects/exasol/ExasolFromExaQueryRewriterTest.java index 84486d9..462ebc6 100644 --- a/src/test/java/com/exasol/adapter/dialects/exasol/ExasolFromExaQueryRewriterTest.java +++ b/src/test/java/com/exasol/adapter/dialects/exasol/ExasolFromExaQueryRewriterTest.java @@ -1,108 +1,76 @@ package com.exasol.adapter.dialects.exasol; import static com.exasol.adapter.AdapterProperties.CONNECTION_NAME_PROPERTY; -import static com.exasol.adapter.dialects.exasol.ExasolProperties.*; +import static com.exasol.adapter.dialects.exasol.ExasolProperties.EXASOL_CONNECTION_PROPERTY; +import static com.exasol.adapter.dialects.exasol.ExasolProperties.EXASOL_IMPORT_PROPERTY; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.equalTo; -import static org.mockito.Mockito.mock; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.when; -import java.math.BigDecimal; import java.sql.Connection; import java.sql.SQLException; import java.util.*; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; +import com.exasol.ExaMetadata; import com.exasol.adapter.AdapterException; import com.exasol.adapter.AdapterProperties; import com.exasol.adapter.dialects.*; -import com.exasol.adapter.dialects.rewriting.AbstractQueryRewriterTestBase; import com.exasol.adapter.jdbc.ConnectionFactory; -import com.exasol.adapter.metadata.*; -import com.exasol.adapter.sql.*; +import com.exasol.adapter.jdbc.RemoteMetadataReader; +import com.exasol.adapter.metadata.DataType; +import com.exasol.adapter.sql.TestSqlStatementFactory; @ExtendWith(MockitoExtension.class) -class ExasolFromExaQueryRewriterTest extends AbstractQueryRewriterTestBase { +class ExasolFromExaQueryRewriterTest { private static final List EMPTY_SELECT_LIST_DATA_TYPES = Collections.emptyList(); - - @BeforeEach - void beforeEach() { - this.statement = TestSqlStatementFactory.createSelectOneFromDual(); - } - - @Test - void rewriteWithJdbcConnection(@Mock final ConnectionFactory connectionFactoryMock) - throws AdapterException, SQLException { - final Connection connectionMock = mockConnection(); - when(connectionFactoryMock.getConnection()).thenReturn(connectionMock); - final AdapterProperties properties = new AdapterProperties(Map.of("CONNECTION_NAME", CONNECTION_NAME)); - final SqlDialectFactory dialectFactory = new ExasolSqlDialectFactory(); - final SqlDialect dialect = dialectFactory.createSqlDialect(connectionFactoryMock, properties); - final ExasolMetadataReader metadataReader = new ExasolMetadataReader(connectionMock, properties); - final QueryRewriter queryRewriter = new ExasolJdbcQueryRewriter(dialect, metadataReader, connectionFactoryMock); - assertThat(queryRewriter.rewrite(this.statement, EMPTY_SELECT_LIST_DATA_TYPES, EXA_METADATA, properties), - equalTo("IMPORT INTO (c1 DECIMAL(18, 0)) FROM JDBC AT " + CONNECTION_NAME - + " STATEMENT 'SELECT 1 FROM \"DUAL\"'")); - } - - @Test - void rewriteWithJdbcConnectionAndExpectedResultSetDataTypes(@Mock final ConnectionFactory connectionFactoryMock) - throws AdapterException, SQLException { - final Connection connectionMock = mock(Connection.class); - final AdapterProperties properties = new AdapterProperties(Map.of("CONNECTION_NAME", CONNECTION_NAME)); - final SqlDialectFactory dialectFactory = new ExasolSqlDialectFactory(); - final SqlDialect dialect = dialectFactory.createSqlDialect(connectionFactoryMock, properties); - final ExasolMetadataReader metadataReader = new ExasolMetadataReader(connectionMock, properties); - final QueryRewriter queryRewriter = new ExasolJdbcQueryRewriter(dialect, metadataReader, connectionFactoryMock); - final List dataTypes = List.of(DataType.createGeometry(4)); - assertThat(queryRewriter.rewrite(this.statement, dataTypes, EXA_METADATA, properties), - equalTo("IMPORT INTO (c1 GEOMETRY(4)) FROM JDBC AT " + CONNECTION_NAME - + " STATEMENT 'SELECT 1 FROM \"DUAL\"'")); - } + @Mock + private RemoteMetadataReader metadataReaderMock; + @Mock + private ConnectionFactory connectionFactoryMock; + @Mock + private ExaMetadata exaMetadataMock; + @Mock + private SqlDialect dialectMock; + @Mock + private SqlGenerator sqlGeneratorMock; + @Mock + private Connection connectionMock; @Test - void rewriteLocal() throws AdapterException, SQLException { - final AdapterProperties properties = new AdapterProperties(Map.of(EXASOL_IS_LOCAL_PROPERTY, "true")); - final SqlDialect dialect = new ExasolSqlDialect(null, properties); - final QueryRewriter queryRewriter = new ExasolLocalQueryRewriter(dialect); - assertThat(queryRewriter.rewrite(this.statement, EMPTY_SELECT_LIST_DATA_TYPES, EXA_METADATA, properties), - equalTo("SELECT 1 FROM \"DUAL\"")); + void rewritePushdownQuery() throws AdapterException, SQLException { + final AdapterProperties properties = createAdapterProperties(); + final SqlDialect dialect = new ExasolSqlDialect(connectionFactoryMock, properties); + final QueryRewriter queryRewriter = new ExasolFromExaQueryRewriter(dialect, + new ExasolMetadataReader(connectionMock, properties)); + assertThat( + queryRewriter.rewrite(TestSqlStatementFactory.createSelectOneFromDual(), EMPTY_SELECT_LIST_DATA_TYPES, + exaMetadataMock, properties), + equalTo("IMPORT FROM EXA AT \"THE_EXA_CONNECTION\" STATEMENT 'SELECT 1 FROM \"DUAL\"'")); } - @Test - void rewriteToImportFromExaWithConnectionDetailsInProperties() throws AdapterException, SQLException { - final AdapterProperties properties = new AdapterProperties(Map.of(EXASOL_IMPORT_PROPERTY, "true", // + private AdapterProperties createAdapterProperties() { + return new AdapterProperties(Map.of(EXASOL_IMPORT_PROPERTY, "true", // CONNECTION_NAME_PROPERTY, "exasol_connection", // - EXASOL_CONNECTION_PROPERTY, "THE_EXA_CONNECTION")); - final SqlDialect dialect = new ExasolSqlDialect(null, properties); - final QueryRewriter queryRewriter = new ExasolFromExaQueryRewriter(dialect, null); - assertThat(queryRewriter.rewrite(this.statement, EMPTY_SELECT_LIST_DATA_TYPES, EXA_METADATA, properties), - equalTo("IMPORT FROM EXA AT \"THE_EXA_CONNECTION\"" + " STATEMENT 'SELECT 1 FROM \"DUAL\"'")); + EXASOL_CONNECTION_PROPERTY, "THE_EXA_CONNECTION", // + "GENERATE_JDBC_DATATYPE_MAPPING_FOR_EXA", "false")); } - static class MySqlStatementFactory { - private static final String SYSDUMMY = "SYSDUMMY1"; - - static public SqlStatement selectGeometry() { - return selectGeometry(SYSDUMMY); - } - - private static SqlStatement selectGeometry(final String tableName) { - final ColumnMetadata columnMetadata = ColumnMetadata.builder().name("the_column") - .type(DataType.createGeometry(18)).build(); - final TableMetadata tableMetadata = new TableMetadata(tableName, "", Arrays.asList(columnMetadata), ""); - final SqlNode fromClause = new SqlTable(tableName, tableMetadata); - final SqlSelectList selectList = SqlSelectList - .createRegularSelectList(List.of(new SqlFunctionScalar(ScalarFunction.ST_POINTN, // - List.of( // - new SqlLiteralExactnumeric(BigDecimal.valueOf(2)), - new SqlLiteralExactnumeric(BigDecimal.valueOf(3)))))); - return SqlStatementSelect.builder().selectList(selectList).fromClause(fromClause).build(); - } + @Test + void rewritePushdownQueryEscapesSingleQuotes() throws AdapterException, SQLException { + final AdapterProperties properties = createAdapterProperties(); + when(dialectMock.getSqlGenerator(any())).thenReturn(sqlGeneratorMock); + when(sqlGeneratorMock.generateSqlFor(any())).thenReturn("string ' with '' quotes \"..."); + final QueryRewriter queryRewriter = new ExasolFromExaQueryRewriter(dialectMock, + new ExasolMetadataReader(connectionMock, properties)); + assertThat( + queryRewriter.rewrite(TestSqlStatementFactory.createSelectOneFromDual(), EMPTY_SELECT_LIST_DATA_TYPES, + exaMetadataMock, properties), + equalTo("IMPORT FROM EXA AT \"THE_EXA_CONNECTION\" STATEMENT 'string '' with '''' quotes \"...'")); } -} \ No newline at end of file +} diff --git a/src/test/java/com/exasol/adapter/dialects/exasol/ExasolFromExaWithDataTypeQueryRewriterTest.java b/src/test/java/com/exasol/adapter/dialects/exasol/ExasolFromExaWithDataTypeQueryRewriterTest.java new file mode 100644 index 0000000..50a14f0 --- /dev/null +++ b/src/test/java/com/exasol/adapter/dialects/exasol/ExasolFromExaWithDataTypeQueryRewriterTest.java @@ -0,0 +1,102 @@ +package com.exasol.adapter.dialects.exasol; + +import static com.exasol.adapter.AdapterProperties.CONNECTION_NAME_PROPERTY; +import static com.exasol.adapter.dialects.exasol.ExasolProperties.*; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.sql.*; +import java.util.*; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import com.exasol.ExaMetadata; +import com.exasol.adapter.AdapterException; +import com.exasol.adapter.AdapterProperties; +import com.exasol.adapter.dialects.*; +import com.exasol.adapter.jdbc.ConnectionFactory; +import com.exasol.adapter.jdbc.RemoteMetadataReader; +import com.exasol.adapter.metadata.DataType; +import com.exasol.adapter.sql.TestSqlStatementFactory; + +@ExtendWith(MockitoExtension.class) +class ExasolFromExaWithDataTypeQueryRewriterTest { + private static final List EMPTY_SELECT_LIST_DATA_TYPES = Collections.emptyList(); + @Mock + private RemoteMetadataReader metadataReaderMock; + @Mock + private ConnectionFactory connectionFactoryMock; + @Mock + private ExaMetadata exaMetadataMock; + @Mock + private SqlDialect dialectMock; + @Mock + private SqlGenerator sqlGeneratorMock; + + @Test + void rewritePushdownQuery() throws AdapterException, SQLException { + final Connection connectionMock = mockConnection(); + when(connectionFactoryMock.getConnection()).thenReturn(connectionMock); + final AdapterProperties properties = createAdapterProperties(); + final SqlDialect dialect = new ExasolSqlDialect(connectionFactoryMock, properties); + final QueryRewriter queryRewriter = new ExasolFromExaWithDataTypeQueryRewriter(dialect, + new ExasolMetadataReader(connectionMock, properties), connectionFactoryMock); + assertThat( + queryRewriter.rewrite(TestSqlStatementFactory.createSelectOneFromDual(), EMPTY_SELECT_LIST_DATA_TYPES, + exaMetadataMock, properties), + equalTo("IMPORT INTO (c1 DECIMAL(18, 0)) FROM EXA AT \"THE_EXA_CONNECTION\"" + + " STATEMENT 'SELECT 1 FROM \"DUAL\"'")); + } + + private AdapterProperties createAdapterProperties() { + return new AdapterProperties(Map.of(EXASOL_IMPORT_PROPERTY, "true", // + CONNECTION_NAME_PROPERTY, "exasol_connection", // + EXASOL_CONNECTION_PROPERTY, "THE_EXA_CONNECTION", // + GENERATE_JDBC_DATATYPE_MAPPING_FOR_EXA, "true")); + } + + @Test + void rewritePushdownQueryEscapesSingleQuotes() throws AdapterException, SQLException { + final Connection connectionMock = mockConnection(); + when(connectionFactoryMock.getConnection()).thenReturn(connectionMock); + final AdapterProperties properties = createAdapterProperties(); + when(dialectMock.getSqlGenerator(any())).thenReturn(sqlGeneratorMock); + when(sqlGeneratorMock.generateSqlFor(any())).thenReturn("string ' with '' quotes \"..."); + final QueryRewriter queryRewriter = new ExasolFromExaWithDataTypeQueryRewriter(dialectMock, + new ExasolMetadataReader(connectionMock, properties), connectionFactoryMock); + assertThat( + queryRewriter.rewrite(TestSqlStatementFactory.createSelectOneFromDual(), EMPTY_SELECT_LIST_DATA_TYPES, + exaMetadataMock, properties), + equalTo("IMPORT INTO (c1 DECIMAL(18, 0)) FROM EXA AT \"THE_EXA_CONNECTION\"" + + " STATEMENT 'string '' with '''' quotes \"...'")); + } + + @Test + void generateImportStatement() throws SQLException { + final Connection connectionMock = mockConnection(); + when(connectionFactoryMock.getConnection()).thenReturn(connectionMock); + final AdapterProperties properties = createAdapterProperties(); + final SqlDialect dialect = new ExasolSqlDialect(connectionFactoryMock, properties); + final ExasolFromExaWithDataTypeQueryRewriter queryRewriter = new ExasolFromExaWithDataTypeQueryRewriter(dialect, + new ExasolMetadataReader(connectionMock, properties), connectionFactoryMock); + assertThat(queryRewriter.generateImportStatement("connection", "pushdownQuery"), + equalTo("IMPORT INTO (c1 DECIMAL(18, 0)) FROM EXA connection STATEMENT 'pushdownQuery'")); + } + + protected Connection mockConnection() throws SQLException { + final ResultSetMetaData metadataMock = mock(ResultSetMetaData.class); + when(metadataMock.getColumnCount()).thenReturn(1); + when(metadataMock.getColumnType(1)).thenReturn(4); + final PreparedStatement statementMock = mock(PreparedStatement.class); + when(statementMock.getMetaData()).thenReturn(metadataMock); + final Connection connectionMock = mock(Connection.class); + when(connectionMock.prepareStatement(any())).thenReturn(statementMock); + return connectionMock; + } +} diff --git a/src/test/java/com/exasol/adapter/dialects/exasol/ExasolJdbcQueryRewriterTest.java b/src/test/java/com/exasol/adapter/dialects/exasol/ExasolJdbcQueryRewriterTest.java new file mode 100644 index 0000000..c0c00e5 --- /dev/null +++ b/src/test/java/com/exasol/adapter/dialects/exasol/ExasolJdbcQueryRewriterTest.java @@ -0,0 +1,97 @@ +package com.exasol.adapter.dialects.exasol; + +import static java.util.Collections.emptyList; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.sql.*; +import java.util.List; +import java.util.Map; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import com.exasol.ExaMetadata; +import com.exasol.adapter.AdapterException; +import com.exasol.adapter.AdapterProperties; +import com.exasol.adapter.dialects.*; +import com.exasol.adapter.jdbc.ConnectionFactory; +import com.exasol.adapter.jdbc.RemoteMetadataReader; +import com.exasol.adapter.metadata.DataType; +import com.exasol.adapter.sql.TestSqlStatementFactory; + +@ExtendWith(MockitoExtension.class) +class ExasolJdbcQueryRewriterTest { + private static final String CONNECTION_NAME = "JDBC_conn"; + + @Mock + private RemoteMetadataReader metadataReaderMock; + @Mock + private ConnectionFactory connectionFactoryMock; + @Mock + private ExaMetadata exaMetadataMock; + + @Test + void pushdownQuery() throws AdapterException, SQLException { + final AdapterProperties properties = new AdapterProperties(Map.of("CONNECTION_NAME", CONNECTION_NAME)); + assertThat( + testee(properties).rewrite(TestSqlStatementFactory.createSelectOneFromDual(), + List.of(DataType.createBool()), exaMetadataMock, properties), + equalTo("IMPORT INTO (c1 BOOLEAN) FROM JDBC AT " + CONNECTION_NAME + + " STATEMENT 'SELECT 1 FROM \"DUAL\"'")); + } + + @Test + void rewriteWithJdbcConnection() throws AdapterException, SQLException { + final Connection connectionMock = mockConnection(); + when(connectionFactoryMock.getConnection()).thenReturn(connectionMock); + final AdapterProperties properties = new AdapterProperties(Map.of("CONNECTION_NAME", CONNECTION_NAME)); + final SqlDialectFactory dialectFactory = new ExasolSqlDialectFactory(); + final SqlDialect dialect = dialectFactory.createSqlDialect(connectionFactoryMock, properties); + final ExasolMetadataReader metadataReader = new ExasolMetadataReader(connectionMock, properties); + final QueryRewriter queryRewriter = new ExasolJdbcQueryRewriter(dialect, metadataReader, connectionFactoryMock); + assertThat( + queryRewriter.rewrite(TestSqlStatementFactory.createSelectOneFromDual(), emptyList(), exaMetadataMock, + properties), + equalTo("IMPORT INTO (c1 DECIMAL(18, 0)) FROM JDBC AT " + CONNECTION_NAME + + " STATEMENT 'SELECT 1 FROM \"DUAL\"'")); + } + + @Test + void rewriteWithJdbcConnectionAndExpectedResultSetDataTypes() throws AdapterException, SQLException { + final Connection connectionMock = mock(Connection.class); + final AdapterProperties properties = new AdapterProperties(Map.of("CONNECTION_NAME", CONNECTION_NAME)); + final SqlDialectFactory dialectFactory = new ExasolSqlDialectFactory(); + final SqlDialect dialect = dialectFactory.createSqlDialect(connectionFactoryMock, properties); + final ExasolMetadataReader metadataReader = new ExasolMetadataReader(connectionMock, properties); + final QueryRewriter queryRewriter = new ExasolJdbcQueryRewriter(dialect, metadataReader, connectionFactoryMock); + final List dataTypes = List.of(DataType.createGeometry(4)); + assertThat( + queryRewriter.rewrite(TestSqlStatementFactory.createSelectOneFromDual(), dataTypes, exaMetadataMock, + properties), + equalTo("IMPORT INTO (c1 GEOMETRY(4)) FROM JDBC AT " + CONNECTION_NAME + + " STATEMENT 'SELECT 1 FROM \"DUAL\"'")); + } + + protected Connection mockConnection() throws SQLException { + final ResultSetMetaData metadataMock = mock(ResultSetMetaData.class); + when(metadataMock.getColumnCount()).thenReturn(1); + when(metadataMock.getColumnType(1)).thenReturn(4); + final PreparedStatement statementMock = mock(PreparedStatement.class); + when(statementMock.getMetaData()).thenReturn(metadataMock); + final Connection connectionMock = mock(Connection.class); + when(connectionMock.prepareStatement(any())).thenReturn(statementMock); + return connectionMock; + } + + private ExasolJdbcQueryRewriter testee(final AdapterProperties properties) { + final SqlDialectFactory dialectFactory = new ExasolSqlDialectFactory(); + final SqlDialect dialect = dialectFactory.createSqlDialect(connectionFactoryMock, properties); + return new ExasolJdbcQueryRewriter(dialect, metadataReaderMock, connectionFactoryMock); + } +} diff --git a/src/test/java/com/exasol/adapter/dialects/exasol/ExasolLocalQueryRewriterTest.java b/src/test/java/com/exasol/adapter/dialects/exasol/ExasolLocalQueryRewriterTest.java new file mode 100644 index 0000000..65ad1a5 --- /dev/null +++ b/src/test/java/com/exasol/adapter/dialects/exasol/ExasolLocalQueryRewriterTest.java @@ -0,0 +1,38 @@ +package com.exasol.adapter.dialects.exasol; + +import static java.util.Collections.emptyList; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; + +import java.sql.SQLException; +import java.util.List; +import java.util.Map; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import com.exasol.ExaMetadata; +import com.exasol.adapter.AdapterException; +import com.exasol.adapter.AdapterProperties; +import com.exasol.adapter.dialects.QueryRewriter; +import com.exasol.adapter.dialects.SqlDialect; +import com.exasol.adapter.metadata.DataType; +import com.exasol.adapter.sql.TestSqlStatementFactory; + +@ExtendWith(MockitoExtension.class) +class ExasolLocalQueryRewriterTest { + private static final List EMPTY_SELECT_LIST_DATA_TYPES = emptyList(); + @Mock + private ExaMetadata exaMetadataMock; + + @Test + void rewriteLocal() throws AdapterException, SQLException { + final AdapterProperties properties = new AdapterProperties(Map.of("IS_LOCAL", "true")); + final SqlDialect dialect = new ExasolSqlDialect(null, properties); + final QueryRewriter queryRewriter = new ExasolLocalQueryRewriter(dialect); + assertThat(queryRewriter.rewrite(TestSqlStatementFactory.createSelectOneFromDual(), + EMPTY_SELECT_LIST_DATA_TYPES, exaMetadataMock, properties), equalTo("SELECT 1 FROM \"DUAL\"")); + } +} diff --git a/src/test/java/com/exasol/adapter/dialects/exasol/ExasolSqlDialectExaConnectionIT.java b/src/test/java/com/exasol/adapter/dialects/exasol/ExasolSqlDialectExaConnectionIT.java index 62d20ff..25ba89b 100644 --- a/src/test/java/com/exasol/adapter/dialects/exasol/ExasolSqlDialectExaConnectionIT.java +++ b/src/test/java/com/exasol/adapter/dialects/exasol/ExasolSqlDialectExaConnectionIT.java @@ -121,4 +121,16 @@ void testCharMappingUtf8() { void testCastVarcharToChar() { castFrom("VARCHAR(20)").to("CHAR(40)").input("Hello.").accept("VARCHAR").verify(pad("Hello.", 40)); } + + @Override + @Test + void joinHashtypeTables() { + final SQLException exception = assertThrows(SQLException.class, super::joinHashtypeTables); + assertThat(exception.getMessage(), anyOf( + // Error message for Exasol 7.1: + containsString("Feature not supported: Incomparable Types: VARCHAR(32) UTF8 and HASHTYPE(16 BYTE)!"), + // Error message for Exasol 8: + containsString( + "Adapter generated invalid pushdown query for virtual table VIRTUAL: Data type mismatch in column number 1 (1-indexed).Expected HASHTYPE(16 BYTE), but got VARCHAR(32) UTF8."))); + } } diff --git a/src/test/java/com/exasol/adapter/dialects/exasol/ExasolSqlDialectExaConnectionWithDataTypesIT.java b/src/test/java/com/exasol/adapter/dialects/exasol/ExasolSqlDialectExaConnectionWithDataTypesIT.java new file mode 100644 index 0000000..019de28 --- /dev/null +++ b/src/test/java/com/exasol/adapter/dialects/exasol/ExasolSqlDialectExaConnectionWithDataTypesIT.java @@ -0,0 +1,98 @@ +package com.exasol.adapter.dialects.exasol; + +import static com.exasol.matcher.ResultSetStructureMatcher.table; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.*; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.Map; +import java.util.Set; + +import org.junit.jupiter.api.*; +import org.testcontainers.containers.JdbcDatabaseContainer.NoDriverFoundException; + +import com.exasol.adapter.properties.PropertyValidationException; +import com.exasol.dbbuilder.dialects.Table; +import com.exasol.dbbuilder.dialects.exasol.ConnectionDefinition; + +/** + * This class exercises a set of tests defined in the base class on a local Exasol, using {@code IMPORT} via a EXA + * connection. + *

+ * In this case the Adapter uses a different (JDBC) connection to attach to the database than the ExaLoader which runs + * this {@code IMPORT}. + *

+ */ +class ExasolSqlDialectExaConnectionWithDataTypesIT extends AbstractRemoteExasolVirtualSchemaConnectionIT { + private static final String EXA_CONNECTION_NAME = "THE_EXA_CONNECTION"; + private ConnectionDefinition exaConnection; + + @Override + @BeforeEach + void beforeEach() { + super.beforeEach(); + this.exaConnection = objectFactory.createConnectionDefinition(EXA_CONNECTION_NAME, getTargetAddress(), + this.user.getName(), this.user.getPassword()); + } + + private String getTargetAddress() { + return "127.0.0.1" + "/" + EXASOL.getTlsCertificateFingerprint().orElseThrow() + ":" + + EXASOL.getDefaultInternalDatabasePort(); + } + + @Override + @AfterEach + void afterEach() { + dropAll(this.exaConnection); + this.exaConnection = null; + super.afterEach(); + } + + @Override + protected Set expectVarcharFor() { + return Set.of(); + } + + @Override + protected Map getConnectionSpecificVirtualSchemaProperties() { + return Map.of("IMPORT_FROM_EXA", "true", // + "EXA_CONNECTION", this.exaConnection.getName(), // + "GENERATE_JDBC_DATATYPE_MAPPING_FOR_EXA", "true"); + } + + @Test + void testPasswordNotVisibleInImportFromExa() throws NoDriverFoundException, SQLException { + final Table table = this.sourceSchema.createTable("T1", "C1", "VARCHAR(20)").insert("Hello."); + this.virtualSchema = createVirtualSchema(this.sourceSchema); + final String sql = "SELECT * FROM " + this.virtualSchema.getFullyQualifiedName() + ".\"" + table.getName() + + "\""; + assertThat(explainVirtual(sql), // + table().row( // + anything(), // + not(anyOf( // + containsString(this.user.getName()), // + containsString(this.user.getPassword()), // + containsString(EXASOL.getUsername()), // + containsString(EXASOL.getPassword()) // + )), // + anything(), // + anything() // + ).matches()); + } + + @Test + void testAlterVirtualSchemaTriggersPropertyValidation() throws SQLException { + this.virtualSchema = createVirtualSchema(this.sourceSchema); + final String name = this.virtualSchema.getFullyQualifiedName(); + final SQLException exception = assertThrows(SQLException.class, + () -> query("alter virtual schema {0} set EXA_CONNECTION = Null", name)); + final String expected = PropertyValidationException.class.getName() + ": E-VSCJDBC-17"; + assertThat(exception.getMessage(), containsString(expected)); + } + + private ResultSet explainVirtual(final String sql) throws SQLException { + return query("EXPLAIN VIRTUAL " + sql); + } +} diff --git a/src/test/java/com/exasol/adapter/dialects/exasol/ExasolSqlDialectTest.java b/src/test/java/com/exasol/adapter/dialects/exasol/ExasolSqlDialectTest.java index b323851..c9a7f20 100644 --- a/src/test/java/com/exasol/adapter/dialects/exasol/ExasolSqlDialectTest.java +++ b/src/test/java/com/exasol/adapter/dialects/exasol/ExasolSqlDialectTest.java @@ -4,7 +4,6 @@ import static com.exasol.adapter.capabilities.MainCapability.*; import static com.exasol.adapter.dialects.exasol.ExasolProperties.*; import static com.exasol.adapter.dialects.exasol.ExasolSqlDialect.EXASOL_TIMESTAMP_WITH_LOCAL_TIME_ZONE_SWITCH; -import static com.exasol.reflect.ReflectionUtils.getMethodReturnViaReflection; import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.equalTo; @@ -32,8 +31,7 @@ import com.exasol.adapter.adapternotes.ColumnAdapterNotes; import com.exasol.adapter.adapternotes.ColumnAdapterNotesJsonConverter; import com.exasol.adapter.capabilities.*; -import com.exasol.adapter.dialects.SqlDialect; -import com.exasol.adapter.dialects.SqlGenerator; +import com.exasol.adapter.dialects.*; import com.exasol.adapter.dialects.rewriting.SqlGenerationContext; import com.exasol.adapter.jdbc.ConnectionFactory; import com.exasol.adapter.metadata.*; @@ -46,12 +44,18 @@ class ExasolSqlDialectTest { @Mock private ConnectionFactory connectionFactoryMock; private ExasolSqlDialect dialect; - private Map rawProperties; @BeforeEach void beforeEach() { - this.dialect = new ExasolSqlDialect(this.connectionFactoryMock, AdapterProperties.emptyProperties()); - this.rawProperties = new HashMap<>(); + this.dialect = testee(AdapterProperties.emptyProperties()); + } + + private ExasolSqlDialect testee(final Map rawProperties) { + return testee(new AdapterProperties(rawProperties)); + } + + private ExasolSqlDialect testee(final AdapterProperties properties) { + return new ExasolSqlDialect(this.connectionFactoryMock, properties); } @CsvSource({ "A1, \"A1\"", // @@ -140,34 +144,35 @@ private TableMetadata getClicksTableMetadata() { @Test void testCreateRemoteMetadataReader(@Mock final Connection connectionMock) throws SQLException { when(this.connectionFactoryMock.getConnection()).thenReturn(connectionMock); - assertThat(getMethodReturnViaReflection(this.dialect, "createRemoteMetadataReader"), - instanceOf(ExasolMetadataReader.class)); + assertThat(this.dialect.createRemoteMetadataReader(), instanceOf(ExasolMetadataReader.class)); } @Test - void testCreateJdbcQueryRewriter(@Mock final Connection connectionMock) throws SQLException { - when(this.connectionFactoryMock.getConnection()).thenReturn(connectionMock); - assertThat(getMethodReturnViaReflection(this.dialect, "createQueryRewriter"), - instanceOf(ExasolJdbcQueryRewriter.class)); + void createQueryRewriterForLocal() { + assertThat(createQueryRewriter(Map.of("IS_LOCAL", "true")), instanceOf(ExasolLocalQueryRewriter.class)); } @Test - void testCreateLocalQueryRewriter() { - this.rawProperties.put(EXASOL_IS_LOCAL_PROPERTY, "true"); - final AdapterProperties properties = new AdapterProperties(this.rawProperties); - final SqlDialect dialect = new ExasolSqlDialect(this.connectionFactoryMock, properties); - assertThat(getMethodReturnViaReflection(dialect, "createQueryRewriter"), - instanceOf(ExasolLocalQueryRewriter.class)); + void createQueryRewriterForExa() { + assertThat(createQueryRewriter(Map.of("IMPORT_FROM_EXA", "true")), + instanceOf(ExasolFromExaQueryRewriter.class)); } @Test - void testCreateFromExaQueryRewriter(@Mock final Connection connectionMock) throws SQLException { - when(this.connectionFactoryMock.getConnection()).thenReturn(connectionMock); - this.rawProperties.put(EXASOL_IMPORT_PROPERTY, "true"); - final AdapterProperties properties = new AdapterProperties(this.rawProperties); - final SqlDialect dialect = new ExasolSqlDialect(this.connectionFactoryMock, properties); - assertThat(getMethodReturnViaReflection(dialect, "createQueryRewriter"), - instanceOf(ExasolFromExaQueryRewriter.class)); + void createQueryRewriterForExaWithDataType() { + assertThat( + createQueryRewriter( + Map.of("IMPORT_FROM_EXA", "true", "GENERATE_JDBC_DATATYPE_MAPPING_FOR_EXA", "true")), + instanceOf(ExasolFromExaWithDataTypeQueryRewriter.class)); + } + + @Test + void createQueryRewriterForJdbc() { + assertThat(createQueryRewriter(Map.of()), instanceOf(ExasolJdbcQueryRewriter.class)); + } + + private QueryRewriter createQueryRewriter(final Map rawProperties) { + return testee(rawProperties).createQueryRewriter(); } @Test @@ -176,14 +181,14 @@ void checkValidBoolOptionsWithExaConnection() throws PropertyValidationException .with(EXASOL_IMPORT_PROPERTY, "TrUe") // .with(EXASOL_CONNECTION_PROPERTY, "MY_EXA_CONNECTION") // .build(); - final SqlDialect sqlDialect = new ExasolSqlDialect(null, adapterProperties); + final SqlDialect sqlDialect = testee(adapterProperties); sqlDialect.validateProperties(); } @Test void checkValidBoolOptionsWithExaConnectionExplicitlyDisabled() throws PropertyValidationException { final AdapterProperties adapterProperties = mandatory().with(EXASOL_IMPORT_PROPERTY, "FalSe").build(); - final SqlDialect sqlDialect = new ExasolSqlDialect(null, adapterProperties); + final SqlDialect sqlDialect = testee(adapterProperties); sqlDialect.validateProperties(); } @@ -191,7 +196,7 @@ void checkValidBoolOptionsWithExaConnectionExplicitlyDisabled() throws PropertyV void testInconsistentExasolProperties() { final AdapterProperties adapterProperties = mandatory().with(EXASOL_CONNECTION_PROPERTY, "localhost:5555") .build(); - final SqlDialect sqlDialect = new ExasolSqlDialect(null, adapterProperties); + final SqlDialect sqlDialect = testee(adapterProperties); final PropertyValidationException exception = assertThrows(PropertyValidationException.class, sqlDialect::validateProperties); assertThat(exception.getMessage(), @@ -203,7 +208,7 @@ void testInvalidExasolProperties() { final AdapterProperties adapterProperties = mandatory() // .with(EXASOL_IMPORT_PROPERTY, "True") // .build(); - final SqlDialect sqlDialect = new ExasolSqlDialect(null, adapterProperties); + final SqlDialect sqlDialect = testee(adapterProperties); final PropertyValidationException exception = assertThrows(PropertyValidationException.class, sqlDialect::validateProperties); assertThat(exception.getMessage(), @@ -215,7 +220,7 @@ void testValidateCatalogProperty() throws PropertyValidationException { final AdapterProperties adapterProperties = mandatory() // .with(CATALOG_NAME_PROPERTY, "MY_CATALOG") // .build(); - final SqlDialect sqlDialect = new ExasolSqlDialect(null, adapterProperties); + final SqlDialect sqlDialect = testee(adapterProperties); sqlDialect.validateProperties(); } @@ -224,7 +229,7 @@ void testValidateSchemaProperty() throws PropertyValidationException { final AdapterProperties adapterProperties = mandatory() // .with(SCHEMA_NAME_PROPERTY, "MY_SCHEMA") // .build(); - final SqlDialect sqlDialect = new ExasolSqlDialect(null, adapterProperties); + final SqlDialect sqlDialect = testee(adapterProperties); sqlDialect.validateProperties(); } @@ -233,7 +238,7 @@ void checkInvalidIsLocalProperty() { final AdapterProperties adapterProperties = mandatory() // .with(EXASOL_IS_LOCAL_PROPERTY, "asdasd") // .build(); - final SqlDialect sqlDialect = new ExasolSqlDialect(null, adapterProperties); + final SqlDialect sqlDialect = testee(adapterProperties); final PropertyValidationException exception = assertThrows(PropertyValidationException.class, sqlDialect::validateProperties); assertThat(exception.getMessage(), containsString("The value 'asdasd' for property 'IS_LOCAL' is invalid." @@ -243,14 +248,14 @@ void checkInvalidIsLocalProperty() { @Test void checkValidIsLocalProperty1() throws PropertyValidationException { final AdapterProperties adapterProperties = mandatory().with(EXASOL_IS_LOCAL_PROPERTY, "TrUe").build(); - final SqlDialect sqlDialect = new ExasolSqlDialect(null, adapterProperties); + final SqlDialect sqlDialect = testee(adapterProperties); sqlDialect.validateProperties(); } @Test void checkValidIsLocalProperty() throws PropertyValidationException { final AdapterProperties adapterProperties = mandatory().with(EXASOL_IS_LOCAL_PROPERTY, "FalSe").build(); - final SqlDialect sqlDialect = new ExasolSqlDialect(null, adapterProperties); + final SqlDialect sqlDialect = testee(adapterProperties); sqlDialect.validateProperties(); } @@ -259,7 +264,7 @@ void testIsTimestampWithLocalTimeZoneEnabled() { final AdapterProperties adapterProperties = mandatory() // .with(IGNORE_ERRORS_PROPERTY, EXASOL_TIMESTAMP_WITH_LOCAL_TIME_ZONE_SWITCH) // .build(); - final ExasolSqlDialect exasolSqlDialect = new ExasolSqlDialect(null, adapterProperties); + final ExasolSqlDialect exasolSqlDialect = testee(adapterProperties); assertThat(exasolSqlDialect.isTimestampWithLocalTimeZoneEnabled(), equalTo(true)); } @@ -268,7 +273,7 @@ void testMissing() throws PropertyValidationException { final AdapterProperties adapterProperties = mandatory() // .remove(AdapterProperties.SCHEMA_NAME_PROPERTY) // .build(); - final ExasolSqlDialect sqlDialect = new ExasolSqlDialect(null, adapterProperties); + final ExasolSqlDialect sqlDialect = testee(adapterProperties); final Exception exception = assertThrows(PropertyValidationException.class, () -> sqlDialect.validateProperties()); assertThat(exception.getMessage(), @@ -307,4 +312,4 @@ AdapterProperties build() { return new AdapterProperties(this.raw); } } -} \ No newline at end of file +} diff --git a/src/test/java/com/exasol/adapter/dialects/exasol/IntegrationTestConfiguration.java b/src/test/java/com/exasol/adapter/dialects/exasol/IntegrationTestConfiguration.java index bf2052e..2ad3e52 100644 --- a/src/test/java/com/exasol/adapter/dialects/exasol/IntegrationTestConfiguration.java +++ b/src/test/java/com/exasol/adapter/dialects/exasol/IntegrationTestConfiguration.java @@ -3,12 +3,12 @@ import java.nio.file.Path; public final class IntegrationTestConfiguration { - private static final String DEFAULT_DOCKER_DB_REFERENCE = "7.1.24"; + private static final String DEFAULT_DOCKER_DB_REFERENCE = "7.1.25"; /** * Do not use MavenProjectVersionGetter here to enable reference checker to check if reference points to the latest * version. */ - public static final String VIRTUAL_SCHEMAS_JAR_NAME_AND_VERSION = "virtual-schema-dist-11.0.2-exasol-7.1.6.jar"; + public static final String VIRTUAL_SCHEMAS_JAR_NAME_AND_VERSION = "virtual-schema-dist-11.0.2-exasol-7.2.0.jar"; public static final Path PATH_TO_VIRTUAL_SCHEMAS_JAR = Path.of("target", VIRTUAL_SCHEMAS_JAR_NAME_AND_VERSION); private IntegrationTestConfiguration() { diff --git a/src/test/java/com/exasol/reflect/ReflectionException.java b/src/test/java/com/exasol/reflect/ReflectionException.java deleted file mode 100644 index a4ee81b..0000000 --- a/src/test/java/com/exasol/reflect/ReflectionException.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.exasol.reflect; - -/** - * This class is intended for wrapping exceptions thrown during attempts to use reflection to keep the number of - * declared exceptions down in unit tests. - */ -public final class ReflectionException extends RuntimeException { - private static final long serialVersionUID = 63504710445197156L; - - public ReflectionException(final Throwable cause) { - super(cause); - } -} \ No newline at end of file diff --git a/src/test/java/com/exasol/reflect/ReflectionUtils.java b/src/test/java/com/exasol/reflect/ReflectionUtils.java deleted file mode 100644 index dd9b0d9..0000000 --- a/src/test/java/com/exasol/reflect/ReflectionUtils.java +++ /dev/null @@ -1,31 +0,0 @@ -package com.exasol.reflect; - -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; - -/** - * This class contains helper methods to reduce the overhead for accessing class members via reflection. This code is - * targeted at making tests more compact and readable and should not be used in production code. - */ -public final class ReflectionUtils { - private ReflectionUtils() { - // prevent instantiation - } - - /** - * @param object instance on which the method is invoked - * @param methodName name of the method to be invoked - * @return resulting return value of the method invocation - */ - public static Object getMethodReturnViaReflection(final Object object, final String methodName) { - final Method method; - try { - method = object.getClass().getDeclaredMethod(methodName); - method.setAccessible(true); - return method.invoke(object); - } catch (final NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException - | InvocationTargetException exception) { - throw new com.exasol.reflect.ReflectionException(exception); - } - } -} \ No newline at end of file