diff --git a/.github/workflows/broken_links_checker.yml b/.github/workflows/broken_links_checker.yml index 6697080f..6a69306a 100644 --- a/.github/workflows/broken_links_checker.yml +++ b/.github/workflows/broken_links_checker.yml @@ -10,8 +10,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - - name: lychee Link Checker - id: lc - uses: lycheeverse/lychee-action@v1.0.6 - - name: Fail if there were link errors - run: exit ${{ steps.lc.outputs.exit_code }} \ No newline at end of file + - uses: gaurav-nelson/github-action-markdown-link-check@v1 + with: + use-quiet-mode: 'yes' + use-verbose-mode: 'yes' \ No newline at end of file diff --git a/.github/workflows/ci-build.yml b/.github/workflows/ci-build.yml new file mode 100644 index 00000000..a90eca36 --- /dev/null +++ b/.github/workflows/ci-build.yml @@ -0,0 +1,30 @@ +name: CI Build + +on: + - push + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout the repository + uses: actions/checkout@v2 + with: + fetch-depth: 0 + - name: Set up JDK 11 + uses: actions/setup-java@v1 + with: + java-version: 11 + - name: Cache local Maven repository + uses: actions/cache@v2 + with: + path: ~/.m2/repository + key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} + restore-keys: | + ${{ runner.os }}-maven- + - name: Run tests and build with Maven + run: mvn --batch-mode --update-snapshots clean verify sonar:sonar --file pom.xml -DtrimStackTrace=false -Dsonar.organization=exasol -Dsonar.host.url=https://sonarcloud.io -Dsonar.login=$SONAR_TOKEN + env: + GITHUB_OAUTH: ${{ secrets.GITHUB_TOKEN }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} \ No newline at end of file diff --git a/.github/workflows/release_droid_upload_github_release_assets.yml b/.github/workflows/release_droid_upload_github_release_assets.yml index 7b20aa3c..aeb21215 100644 --- a/.github/workflows/release_droid_upload_github_release_assets.yml +++ b/.github/workflows/release_droid_upload_github_release_assets.yml @@ -32,4 +32,9 @@ jobs: uses: shogo82148/actions-upload-release-asset@v1 with: upload_url: ${{ github.event.inputs.upload_url }} - asset_path: target/*.jar \ No newline at end of file + asset_path: target/*.jar + - name: Upload error-code-report + uses: shogo82148/actions-upload-release-asset@v1 + with: + upload_url: ${{ github.event.inputs.upload_url }} + asset_path: target/error_code_report.json \ No newline at end of file diff --git a/.gitignore b/.gitignore index 52b4b002..20861b9b 100644 --- a/.gitignore +++ b/.gitignore @@ -19,3 +19,4 @@ dependency-reduced-pom.xml **/local Scripts .dbeaver* +pom.xml.versionsBackup \ No newline at end of file diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 0789d0fb..00000000 --- a/.travis.yml +++ /dev/null @@ -1,13 +0,0 @@ -language: java - -jdk: - - openjdk11 - -addons: - sonarcloud: - organization: exasol - token: - secure: "Tzw0mn8q0kVwC2x8LJfYCFCn/Zxxp468tbwtoh1xD/KTpGSPhqg8HmfWGKr9r6nRmoghhOVAJufNRWa2ZBOAGQ53TRd44Zub/REyB9GL/8ccG4UiYB3TVAyipQboIfw2dY51oi7s1YMKpgJeraYk56Hd/fF2oMaEDDgtl48Q5yU5FWwYttNQltnl5R1Bwg3wuyghNg+RxcGIW22OQPbvDLeyDHz0UFbqDV8g0bOUqsaTEzHhzBMLz1g1hq5OvWdbwqV3chyHft6Hp5PwIG64dpdhxEhcELcVJyHKe31vaSLOcO8NvSbcJ7By4gtanD4Y3aelxocY7yJEUxtunde9fHUIOi5xcECWpF0Faf6mC17OEuukbE86ryC6tKdPxQkC55zt1troXlQdNqr6l/Ae8PN9sU10Hra28137jXdMX94So63ZFU1ks2SzcjFfdBAHKqE1i7amlE37pRhJp15sm5OVxPJ+3jn8zwYwewq4Zl+KzPUqqqKeCAH/1nAHr9nAN/iJyvz39kilcHjrSi6nCm9UcKh5u0EHFVbfMXHutrtN+vG4F7JICDzvfw1NMiFrNLXwqPESVH4KN8n7CsXevzqpkkzoOBCkYAk1Ww+lGrpZKq2icTgQRaez4SliwZhrcyJjaUghfVKrJZUBS7sLnzY26iuHyD/QCF2kHvMqAzY=" - -script: - - mvn clean org.jacoco:jacoco-maven-plugin:prepare-agent install sonar:sonar \ No newline at end of file diff --git a/README.md b/README.md index be3d2e4d..9c69a856 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,8 @@ # Common module of Exasol Virtual Schemas Adapters -[![Build Status](https://travis-ci.com/exasol/virtual-schema-common-java.svg?branch=main)](https://travis-ci.com/exasol/virtual-schema-common-java) +[![Build Status](https://github.com/exasol/virtual-schema-common-java/actions/workflows/ci-build.yml/badge.svg)](https://github.com/exasol/virtual-schema-common-java/actions/workflows/ci-build.yml) [![Maven Central](https://img.shields.io/maven-central/v/com.exasol/virtual-schema-common-java)](https://search.maven.org/artifact/com.exasol/virtual-schema-common-java) -SonarCloud results: - [![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=com.exasol%3Avirtual-schema-common-java&metric=alert_status)](https://sonarcloud.io/dashboard?id=com.exasol%3Avirtual-schema-common-java) [![Security Rating](https://sonarcloud.io/api/project_badges/measure?project=com.exasol%3Avirtual-schema-common-java&metric=security_rating)](https://sonarcloud.io/dashboard?id=com.exasol%3Avirtual-schema-common-java) @@ -17,9 +15,7 @@ SonarCloud results: [![Duplicated Lines (%)](https://sonarcloud.io/api/project_badges/measure?project=com.exasol%3Avirtual-schema-common-java&metric=duplicated_lines_density)](https://sonarcloud.io/dashboard?id=com.exasol%3Avirtual-schema-common-java) [![Lines of Code](https://sonarcloud.io/api/project_badges/measure?project=com.exasol%3Avirtual-schema-common-java&metric=ncloc)](https://sonarcloud.io/dashboard?id=com.exasol%3Avirtual-schema-common-java) -This is one of the modules of Virtual Schemas Adapters. -The libraries provided by this project are the foundation of the adapter development, i.e. adapters must be implemented on top of them. -You can find the full description of the project here: https://github.com/exasol/virtual-schemas +This is one of the modules of Virtual Schemas Adapters. The libraries provided by this project are the foundation of the adapter development, i.e. adapters must be implemented on top of them. You can find the full description of the project here: https://github.com/exasol/virtual-schemas A Virtual Schema adapter is basically a [UDF](https://docs.exasol.com/database_concepts/udf_scripts.htm). The Exasol core database communicates with this UDF using JSON strings. There are different types of messages, that define the API for a virtual Schema adapter ([protocol reference](doc/development/api/virtual_schema_api.md)). This repository wraps this JSON API with a Java API to facilitate the implementation of Virtual Schema adapters in Java. @@ -28,53 +24,13 @@ Please note that the artifact name changed from "virtualschema-common" to "virtu ## Information for Users * [Changelog](doc/changes/changelog.md) +* [Dependencies](dependencies.md) ## Information for Developers * [Virtual Schema API](doc/development/api/virtual_schema_api.md) * [Capabilities list](doc/development/api/capabilities_list.md) -## Dependencies - -### Run Time Dependencies - -| Dependency | Purpose | License | -|-------------------------------------------------------------------------------------|--------------------------------------------------------|-------------------------------| -| [JSON-P](https://javaee.github.io/jsonp/) | JSON Processing | CDDL-1.0 | -| [Exasol Script API](https://docs.exasol.com/database_concepts/udf_scripts.htm) | Accessing Exasol features | MIT License | - -### Build Time Dependencies - -| Dependency | Purpose | License | -|-------------------------------------------------------------------------------------|--------------------------------------------------------|-------------------------------| -| [Apache Maven](https://maven.apache.org/) | Build tool | Apache License 2.0 | -| [Java Hamcrest](http://hamcrest.org/JavaHamcrest/) | Checking for conditions in code via matchers | BSD License | -| [JSONassert](http://jsonassert.skyscreamer.org/) | Compare JSON documents for semantic equality | Apache License 2.0 | -| [JUnit](https://junit.org/junit5) | Unit testing framework | Eclipse Public License 1.0 | -| [Mockito](http://site.mockito.org/) | Mocking framework | MIT License | -| [JUnit 5 System Extensions](https://github.com/itsallcode/junit5-system-extensions) | Capturing `STDOUT` and `STDERR` | Eclipse Public License 2.0 | -| [Equals Verifier](https://jqno.nl/equalsverifier/) | Testing `equals(...)` and `hashCode()` contracts | Apache License 2.0 | - -### Maven Plug-ins - -| Plug-in | Purpose | License | ----------------------------------------------------------------------------------|--------------------------------------------------------|-------------------------------- -| [Maven Compiler Plugin][maven-compiler-plugin] | Setting required Java version | Apache License 2.0 | -| [Maven GPG Plugin](https://maven.apache.org/plugins/maven-gpg-plugin/) | Signs JARs | Apache License 2.0 | -| [Maven Enforcer Plugin][maven-enforcer-plugin] | Controlling environment constants | Apache License 2.0 | -| [Maven Jacoco Plugin](https://www.eclemma.org/jacoco/trunk/doc/maven.html) | Code coverage metering | Eclipse Public License 2.0 | -| [Maven JavaDoc Plugin](https://maven.apache.org/plugins/maven-javadoc-plugin/) | Creates JavaDoc JARs | Apache License 2.0 | -| [Maven Source Plugin](https://maven.apache.org/plugins/maven-source-plugin/) | Creates source JARs | Apache License 2.0 | -| [Maven Surefire Plugin][maven-surefire-plugin] | Unit testing | Apache License 2.0 | -| [Sonatype OSS Index Maven Plugin][sonatype-oss-index-maven-plugin] | Checking Dependencies Vulnerability | ASL2 | -| [Versions Maven Plugin][versions-maven-plugin] | Checking if dependencies updates are available | Apache License 2.0 | - -[maven-compiler-plugin]: https://maven.apache.org/plugins/maven-compiler-plugin/ -[maven-enforcer-plugin]: http://maven.apache.org/enforcer/maven-enforcer-plugin/ -[maven-surefire-plugin]: https://maven.apache.org/surefire/maven-surefire-plugin/ -[sonatype-oss-index-maven-plugin]: https://sonatype.github.io/ossindex-maven/maven-plugin/ -[versions-maven-plugin]: https://www.mojohaus.org/versions-maven-plugin/ - ## Open Source Project Support Please note that this is an open source project which is officially supported by Exasol. This module is part of a larger project called [Virtual Schemas](https://github.com/exasol/virtual-schemas). diff --git a/dependencies.md b/dependencies.md new file mode 100644 index 00000000..81944fc8 --- /dev/null +++ b/dependencies.md @@ -0,0 +1,84 @@ + +# Dependencies + +## Compile Dependencies + +| Dependency | License | +| ----------------------------------------------- | -------------------------------------------------------- | +| [JSR 374 (JSON Processing) Default Provider][0] | [Dual license consisting of the CDDL v1.1 and GPL v2][1] | +| [Java Interface for EXASOL Scripts][2] | [MIT License][3] | +| [error-reporting-java][4] | [MIT][3] | + +## Test Dependencies + +| Dependency | License | +| -------------------------------- | --------------------------------------------- | +| [Hamcrest][6] | [BSD License 3][7] | +| [JSONassert][8] | [The Apache Software License, Version 2.0][9] | +| [JUnit Jupiter (Aggregator)][10] | [Eclipse Public License v2.0][11] | +| [mockito-junit-jupiter][12] | [The MIT License][13] | +| [JUnit5 System Extensions][14] | [Eclipse Public License v2.0][15] | +| [EqualsVerifier][16] | [Apache License, Version 2.0][9] | + +## Plugin Dependencies + +| Dependency | License | +| ------------------------------------------------------- | --------------------------------------------- | +| [Apache Maven Compiler Plugin][18] | [Apache License, Version 2.0][19] | +| [JaCoCo :: Maven Plugin][20] | [Eclipse Public License 2.0][21] | +| [Maven Surefire Plugin][22] | [Apache License, Version 2.0][19] | +| [Apache Maven Source Plugin][24] | [Apache License, Version 2.0][19] | +| [Apache Maven Javadoc Plugin][26] | [Apache License, Version 2.0][19] | +| [Apache Maven GPG Plugin][28] | [Apache License, Version 2.0][9] | +| [org.sonatype.ossindex.maven:ossindex-maven-plugin][30] | [ASL2][9] | +| [Versions Maven Plugin][32] | [Apache License, Version 2.0][19] | +| [Apache Maven Enforcer Plugin][34] | [Apache License, Version 2.0][19] | +| [Maven Deploy Plugin][36] | [The Apache Software License, Version 2.0][9] | +| [Nexus Staging Maven Plugin][38] | [Eclipse Public License][39] | +| [error-code-crawler-maven-plugin][40] | [MIT][3] | +| [Project keeper maven plugin][42] | [MIT][3] | +| [Reproducible Build Maven Plugin][44] | [Apache 2.0][9] | +| [Maven Clean Plugin][46] | [The Apache Software License, Version 2.0][9] | +| [Maven Resources Plugin][48] | [The Apache Software License, Version 2.0][9] | +| [Maven JAR Plugin][50] | [The Apache Software License, Version 2.0][9] | +| [Maven Install Plugin][52] | [The Apache Software License, Version 2.0][9] | +| [Maven Site Plugin 3][54] | [The Apache Software License, Version 2.0][9] | + +[20]: https://www.eclemma.org/jacoco/index.html +[42]: https://github.com/exasol/project-keeper-maven-plugin +[4]: https://github.com/exasol/error-reporting-java +[15]: http://www.eclipse.org/legal/epl-v20.html +[0]: https://javaee.github.io/jsonp +[9]: http://www.apache.org/licenses/LICENSE-2.0.txt +[22]: https://maven.apache.org/surefire/maven-surefire-plugin/ +[38]: http://www.sonatype.com/public-parent/nexus-maven-plugins/nexus-staging/nexus-staging-maven-plugin/ +[46]: http://maven.apache.org/plugins/maven-clean-plugin/ +[3]: https://opensource.org/licenses/MIT +[12]: https://github.com/mockito/mockito +[32]: http://www.mojohaus.org/versions-maven-plugin/ +[7]: http://opensource.org/licenses/BSD-3-Clause +[18]: https://maven.apache.org/plugins/maven-compiler-plugin/ +[1]: https://oss.oracle.com/licenses/CDDL+GPL-1.1 +[28]: http://maven.apache.org/plugins/maven-gpg-plugin/ +[21]: https://www.eclipse.org/legal/epl-2.0/ +[39]: http://www.eclipse.org/legal/epl-v10.html +[13]: https://github.com/mockito/mockito/blob/main/LICENSE +[44]: http://zlika.github.io/reproducible-build-maven-plugin +[50]: http://maven.apache.org/plugins/maven-jar-plugin/ +[19]: https://www.apache.org/licenses/LICENSE-2.0.txt +[34]: https://maven.apache.org/enforcer/maven-enforcer-plugin/ +[2]: http://www.exasol.com +[11]: https://www.eclipse.org/legal/epl-v20.html +[52]: http://maven.apache.org/plugins/maven-install-plugin/ +[10]: https://junit.org/junit5/ +[30]: https://sonatype.github.io/ossindex-maven/maven-plugin/ +[14]: https://github.com/itsallcode/junit5-system-extensions +[8]: https://github.com/skyscreamer/JSONassert +[16]: http://www.jqno.nl/equalsverifier +[24]: https://maven.apache.org/plugins/maven-source-plugin/ +[6]: http://hamcrest.org/JavaHamcrest/ +[36]: http://maven.apache.org/plugins/maven-deploy-plugin/ +[54]: http://maven.apache.org/plugins/maven-site-plugin/ +[48]: http://maven.apache.org/plugins/maven-resources-plugin/ +[26]: https://maven.apache.org/plugins/maven-javadoc-plugin/ +[40]: https://github.com/exasol/error-code-crawler-maven-plugin diff --git a/doc/changes/changelog.md b/doc/changes/changelog.md index 110a2728..8ae54bb1 100644 --- a/doc/changes/changelog.md +++ b/doc/changes/changelog.md @@ -1,5 +1,6 @@ # Changes +* [15.2.0](changes_15.2.0.md) * [15.1.0](changes_15.1.0.md) * [15.0.1](changes_15.0.1.md) * [15.0.0](changes_15.0.0.md) diff --git a/doc/changes/changes_15.2.0.md b/doc/changes/changes_15.2.0.md new file mode 100644 index 00000000..c9a5824b --- /dev/null +++ b/doc/changes/changes_15.2.0.md @@ -0,0 +1,23 @@ +# Common module of Exasol Virtual Schemas Adapters 15.2.0, released 2021-08-06 + +Code name: SqlNode serialization + +## Summary + +In this release we added a renderer for the `SqlNode` structure. + +## Features + +* #230: Added serializer for SqlNode + +## Dependency Updates + +### Test Dependency Updates + +* Updated `nl.jqno.equalsverifier:equalsverifier:3.6` to `3.7` +* Updated `org.mockito:mockito-junit-jupiter:3.10.0` to `3.11.2` + +### Plugin Dependency Updates + +* Updated `com.exasol:error-code-crawler-maven-plugin:0.1.0` to `0.5.1` +* Updated `com.exasol:project-keeper-maven-plugin:0.6.1` to `0.10.0` diff --git a/errorCodeConfig.yml b/errorCodeConfig.yml deleted file mode 100644 index 1da121b9..00000000 --- a/errorCodeConfig.yml +++ /dev/null @@ -1,3 +0,0 @@ -error-tags: - VS-COM-JAVA: - - com.exasol \ No newline at end of file diff --git a/error_code_config.yml b/error_code_config.yml new file mode 100644 index 00000000..727ef720 --- /dev/null +++ b/error_code_config.yml @@ -0,0 +1,4 @@ +error-tags: + VS-COM-JAVA: + packages: + - com.exasol \ No newline at end of file diff --git a/pom.xml b/pom.xml index cb8149a5..3a4c37d1 100644 --- a/pom.xml +++ b/pom.xml @@ -1,8 +1,10 @@ - + + 4.0.0 com.exasol virtual-schema-common-java - 15.1.0 + 15.2.0 Common module of Exasol Virtual Schemas Adapters This is one of the modules of Virtual Schemas Adapters. The libraries provided by this project are the foundation of the adapter development, i.e. adapters must be implemented on top of them. @@ -92,7 +94,7 @@ org.mockito mockito-junit-jupiter - 3.10.0 + 3.11.2 test @@ -104,7 +106,7 @@ nl.jqno.equalsverifier equalsverifier - 3.6 + 3.7 test @@ -130,36 +132,60 @@ 0.8.6 + prepare-agent prepare-agent + + prepare-agent-integration + + prepare-agent-integration + + + + merge-results + verify + + merge + + + + + ${project.build.directory}/ + + jacoco*.exec + + + + ${project.build.directory}/aggregate.exec + + report - test + verify report + + ${project.build.directory}/aggregate.exec + - - prepare-agent - - prepare-agent - - + org.apache.maven.plugins maven-surefire-plugin 3.0.0-M4 - - - -Djava.util.logging.config.file=src/test/resources/logging.properties ${argLine} - - **IT.java - - + + + -Djava.util.logging.config.file=src/test/resources/logging.properties ${argLine} + + **IT.java + + + org.apache.maven.plugins maven-source-plugin @@ -239,9 +265,10 @@ - - file:///${project.basedir}/versionsMavenPluginRules.xml - + + file:///${project.basedir}/versionsMavenPluginRules.xml + + org.apache.maven.plugins maven-enforcer-plugin @@ -291,7 +318,7 @@ com.exasol error-code-crawler-maven-plugin - 0.1.0 + 0.5.1 @@ -303,7 +330,7 @@ com.exasol project-keeper-maven-plugin - 0.6.1 + 0.10.0 @@ -315,21 +342,27 @@ maven_central + + + LICENSE-exasol-script-api.txt|https://opensource.org/licenses/MIT + + - - io.github.zlika - reproducible-build-maven-plugin - 0.13 - - - strip-jar - package - - strip-jar - - - - + + io.github.zlika + reproducible-build-maven-plugin + 0.13 + + + strip-jar + package + + strip-jar + + + + + \ No newline at end of file diff --git a/release_config.yml b/release_config.yml new file mode 100644 index 00000000..30d247f8 --- /dev/null +++ b/release_config.yml @@ -0,0 +1,4 @@ +release-platforms: + - GitHub + - Maven + - Jira \ No newline at end of file diff --git a/src/main/java/com/exasol/adapter/request/RequestJsonKeys.java b/src/main/java/com/exasol/adapter/request/RequestJsonKeys.java new file mode 100644 index 00000000..04558b27 --- /dev/null +++ b/src/main/java/com/exasol/adapter/request/RequestJsonKeys.java @@ -0,0 +1,63 @@ +package com.exasol.adapter.request; + +/** + * This class contains constants for keywords of the JSON representation of the SqlNode structure. + */ +public class RequestJsonKeys { + public static final String ORDER_BY = "orderBy"; + public static final String GROUP_BY = "groupBy"; + public static final String FROM = "from"; + public static final String LIMIT = "limit"; + public static final String HAVING = "having"; + public static final String FILTER = "filter"; + public static final String SELECT_LIST = "selectList"; + public static final String EXPRESSION = "expression"; + public static final String EXPRESSIONS = "expressions"; + public static final String LEFT = "left"; + public static final String RIGHT = "right"; + public static final String VALUE = "value"; + public static final String ARGUMENTS = "arguments"; + public static final String DISTINCT = "distinct"; + public static final String DATA_TYPE = "dataType"; + public static final String SEPARATOR = "separator"; + public static final String OVERFLOW_BEHAVIOUR = "overflowBehavior"; + public static final String TYPE = "type"; + public static final String TRUNCATION_TYPE = "truncationType"; + public static final String TRUNCATION_FILLER = "truncationFiller"; + public static final String NAME = "name"; + public static final String NUM_ARGS = "numArgs"; + public static final String ALIAS = "alias"; + public static final String CONDITION = "condition"; + public static final String TABLE_NAME = "tableName"; + public static final String TABLE_ALIAS = "tableAlias"; + public static final String RETURNING_DATA_TYPE = "returningDataType"; + public static final String EMPTY_BEHAVIOR = "emptyBehavior"; + public static final String ERROR_BEHAVIOR = "errorBehavior"; + public static final String SRID = "srid"; + public static final String COLUMN_NR = "columnNr"; + public static final String RESULTS = "results"; + public static final String BASIS = "basis"; + public static final String PRECISION = "precision"; + public static final String SCALE = "scale"; + public static final String CHARACTER_SET = "characterSet"; + public static final String SIZE = "size"; + public static final String WITH_LOCAL_TIME_ZONE = "withLocalTimeZone"; + public static final String FROM_TO = "fromTo"; + public static final String FRACTION = "fraction"; + public static final String NUM_ELEMENTS = "numElements"; + public static final String OFFSET = "offset"; + public static final String ORDER_BY_ELEMENT = "order_by_element"; + public static final String IS_ASCENDING = "isAscending"; + public static final String NULLS_LAST = "nullsLast"; + public static final String TYPE_CONSTRAINT = "typeConstraint"; + public static final String KEY_UNIQUENESS_CONSTRAINT = "keyUniquenessConstraint"; + public static final String PATTERN = "pattern"; + public static final String ESCAPE_CHAR = "escapeChar"; + public static final String JOIN_TYPE = "join_type"; + public static final String TO_EXTRACT = "toExtract"; + public static final String BYTE_SIZE = "bytesize"; + + private RequestJsonKeys() { + // no instantiation required + } +} diff --git a/src/main/java/com/exasol/adapter/request/parser/PushdownSqlParser.java b/src/main/java/com/exasol/adapter/request/parser/PushdownSqlParser.java index 88549dab..48fab409 100644 --- a/src/main/java/com/exasol/adapter/request/parser/PushdownSqlParser.java +++ b/src/main/java/com/exasol/adapter/request/parser/PushdownSqlParser.java @@ -1,5 +1,6 @@ package com.exasol.adapter.request.parser; +import static com.exasol.adapter.request.RequestJsonKeys.*; import static com.exasol.adapter.sql.SqlPredicateIsJson.KeyUniquenessConstraint; import static com.exasol.adapter.sql.SqlPredicateIsJson.TypeConstraints; @@ -17,15 +18,6 @@ import com.exasol.errorreporting.ExaError; public final class PushdownSqlParser extends AbstractRequestParser { - private static final String ORDER_BY_KEY = "orderBy"; - private static final String EXPRESSION = "expression"; - private static final String RIGHT = "right"; - private static final String VALUE = "value"; - private static final String ARGUMENTS_KEY = "arguments"; - private static final String DISTINCT_KEY = "distinct"; - private static final String DATA_TYPE = "dataType"; - private static final String SEPARATOR_KEY = "separator"; - private final List involvedTablesMetadata; private PushdownSqlParser(final List involvedTablesMetadata) { @@ -33,7 +25,7 @@ private PushdownSqlParser(final List involvedTablesMetadata) { } public SqlNode parseExpression(final JsonObject expression) { - final String typeName = expression.getString("type", ""); + final String typeName = expression.getString(TYPE, ""); final SqlNodeType type = fromTypeName(typeName); switch (type) { case SELECT: @@ -131,8 +123,8 @@ private SqlStatementSelect parseSelect(final JsonObject select) { having = parseExpression(select.getJsonObject("having")); } SqlOrderBy orderBy = null; - if (select.containsKey(ORDER_BY_KEY)) { - orderBy = parseOrderBy(select.getJsonArray(ORDER_BY_KEY)); + if (select.containsKey(ORDER_BY)) { + orderBy = parseOrderBy(select.getJsonArray(ORDER_BY)); } SqlLimit limit = null; if (select.containsKey("limit")) { @@ -152,10 +144,10 @@ private SqlSelectList createSelectList(final JsonObject select, final SqlNode fr } private SqlNode parseTable(final JsonObject exp) { - final String tableName = exp.getString("name"); + final String tableName = exp.getString(NAME); final TableMetadata tableMetadata = findInvolvedTableMetadata(tableName); - if (exp.containsKey("alias")) { - final String tableAlias = exp.getString("alias"); + if (exp.containsKey(ALIAS)) { + final String tableAlias = exp.getString(ALIAS); return new SqlTable(tableName, tableAlias, tableMetadata); } else { return new SqlTable(tableName, tableMetadata); @@ -163,20 +155,20 @@ private SqlNode parseTable(final JsonObject exp) { } private SqlNode parseJoin(final JsonObject exp) { - final SqlNode left = parseExpression(exp.getJsonObject("left")); + final SqlNode left = parseExpression(exp.getJsonObject(LEFT)); final SqlNode right = parseExpression(exp.getJsonObject(RIGHT)); - final SqlNode condition = parseExpression(exp.getJsonObject("condition")); - final JoinType joinType = fromJoinTypeName(exp.getString("join_type")); + final SqlNode condition = parseExpression(exp.getJsonObject(CONDITION)); + final JoinType joinType = fromJoinTypeName(exp.getString(JOIN_TYPE)); return new SqlJoin(left, right, condition, joinType); } private SqlNode parseColumn(final JsonObject exp) { - final int columnId = exp.getInt("columnNr"); - final String columnName = exp.getString("name"); - final String tableName = exp.getString("tableName"); + final int columnId = exp.getInt(COLUMN_NR); + final String columnName = exp.getString(NAME); + final String tableName = exp.getString(TABLE_NAME); final ColumnMetadata columnMetadata = findColumnMetadata(tableName, columnName); - if (exp.containsKey("tableAlias")) { - final String tableAlias = exp.getString("tableAlias"); + if (exp.containsKey(TABLE_ALIAS)) { + final String tableAlias = exp.getString(TABLE_ALIAS); return new SqlColumn(columnId, columnMetadata, tableName, tableAlias); } else { return new SqlColumn(columnId, columnMetadata, tableName); @@ -218,6 +210,7 @@ private SqlSelectList parseSelectList(final JsonArray selectList) { } } + @SuppressWarnings("java:S1192") // tableName is duplicated but that's ok since it's a parameter private List collectAllInvolvedColumns(final SqlNode from) { final List involvedTables = collectInvolvedTables(from); final Map tableMetadataMap = getInvolvedTablesMetadataMap(); @@ -231,8 +224,8 @@ private List collectAllInvolvedColumns(final SqlNode from) { } } else { throw new IllegalStateException(ExaError.messageBuilder("E-VS-COM-JAVA-9").message( - "Unable to find metadata for table \"{{tableName}}\" during collecting involved columns.") - .unquotedParameter("tableName", tableName).toString()); + "Unable to find metadata for table \"{{tableName|uq}}\" during collecting involved columns.") + .parameter("tableName", tableName).toString()); } } return selectListElements; @@ -288,15 +281,15 @@ private SqlOrderBy parseOrderBy(final JsonArray orderByList) { for (int i = 0; i < orderByList.size(); ++i) { final JsonObject orderElem = orderByList.getJsonObject(i); orderByExpressions.add(parseExpression(orderElem.getJsonObject(EXPRESSION))); - isAsc.add(orderElem.getBoolean("isAscending", true)); - nullsLast.add(orderElem.getBoolean("nullsLast", true)); + isAsc.add(orderElem.getBoolean(IS_ASCENDING, true)); + nullsLast.add(orderElem.getBoolean(NULLS_LAST, true)); } return new SqlOrderBy(orderByExpressions, isAsc, nullsLast); } private SqlLimit parseLimit(final JsonObject limit) { - final int numElements = limit.getInt("numElements"); - final int offset = limit.getInt("offset", 0); + final int numElements = limit.getInt(NUM_ELEMENTS); + final int offset = limit.getInt(OFFSET, 0); return new SqlLimit(numElements, offset); } @@ -317,34 +310,34 @@ private SqlNode parsePredicateIsNull(final JsonObject exp) { private SqlNode parsePredicateLike(final JsonObject exp) { final SqlNode likeLeft = parseExpression(exp.getJsonObject(EXPRESSION)); - final SqlNode likePattern = parseExpression(exp.getJsonObject("pattern")); - if (exp.containsKey("escapeChar")) { - final SqlNode escapeChar = parseExpression(exp.getJsonObject("escapeChar")); + final SqlNode likePattern = parseExpression(exp.getJsonObject(PATTERN)); + if (exp.containsKey(ESCAPE_CHAR)) { + final SqlNode escapeChar = parseExpression(exp.getJsonObject(ESCAPE_CHAR)); return new SqlPredicateLike(likeLeft, likePattern, escapeChar); } return new SqlPredicateLike(likeLeft, likePattern); } private SqlNode parsePredicateLessEqual(final JsonObject exp) { - final SqlNode lessEqLeft = parseExpression(exp.getJsonObject("left")); + final SqlNode lessEqLeft = parseExpression(exp.getJsonObject(LEFT)); final SqlNode lessEqRight = parseExpression(exp.getJsonObject(RIGHT)); return new SqlPredicateLessEqual(lessEqLeft, lessEqRight); } private SqlNode parsePredicateLess(final JsonObject exp) { - final SqlNode lessLeft = parseExpression(exp.getJsonObject("left")); + final SqlNode lessLeft = parseExpression(exp.getJsonObject(LEFT)); final SqlNode lessRight = parseExpression(exp.getJsonObject(RIGHT)); return new SqlPredicateLess(lessLeft, lessRight); } private SqlNode parsePredicateNotEqual(final JsonObject exp) { - final SqlNode notEqualLeft = parseExpression(exp.getJsonObject("left")); + final SqlNode notEqualLeft = parseExpression(exp.getJsonObject(LEFT)); final SqlNode notEqualRight = parseExpression(exp.getJsonObject(RIGHT)); return new SqlPredicateNotEqual(notEqualLeft, notEqualRight); } private SqlNode parsePredicateEqual(final JsonObject exp) { - final SqlNode equalLeft = parseExpression(exp.getJsonObject("left")); + final SqlNode equalLeft = parseExpression(exp.getJsonObject(LEFT)); final SqlNode equalRight = parseExpression(exp.getJsonObject(RIGHT)); return new SqlPredicateEqual(equalLeft, equalRight); } @@ -355,7 +348,7 @@ private SqlNode parsePredicateNot(final JsonObject exp) { } private SqlNode parsePredicateOr(final JsonObject exp) { - final List orPredicates = getListOfSqlNodes(exp, "expressions"); + final List orPredicates = getListOfSqlNodes(exp, EXPRESSIONS); return new SqlPredicateOr(orPredicates); } @@ -370,7 +363,7 @@ private List getListOfSqlNodes(final JsonObject jsonExpression, final S } private SqlNode parsePredicateAnd(final JsonObject exp) { - final List andedPredicates = getListOfSqlNodes(exp, "expressions"); + final List andedPredicates = getListOfSqlNodes(exp, EXPRESSIONS); return new SqlPredicateAnd(andedPredicates); } @@ -381,10 +374,10 @@ private SqlNode parseLiteralInterval(final JsonObject expression) { } private DataType getDataType(final JsonObject dataType) { - final String typeName = dataType.getString("type").toUpperCase(); + final String typeName = dataType.getString(TYPE).toUpperCase(); switch (typeName) { case "DECIMAL": - return DataType.createDecimal(dataType.getInt("precision"), dataType.getInt("scale")); + return DataType.createDecimal(dataType.getInt(PRECISION), dataType.getInt(SCALE)); case "DOUBLE": return DataType.createDouble(); case "VARCHAR": @@ -411,30 +404,30 @@ private DataType getDataType(final JsonObject dataType) { } private DataType getHashtype(final JsonObject dataType) { - final int byteSize = dataType.getInt("bytesize"); + final int byteSize = dataType.getInt(BYTE_SIZE); return DataType.createHashtype(byteSize); } private DataType getVarchar(final JsonObject dataType) { - final String charSet = dataType.getString("characterSet", "UTF8"); - return DataType.createVarChar(dataType.getInt("size"), charSetFromString(charSet)); + final String charSet = dataType.getString(CHARACTER_SET, "UTF8"); + return DataType.createVarChar(dataType.getInt(SIZE), charSetFromString(charSet)); } private DataType getChar(final JsonObject dataType) { - final String charSet = dataType.getString("characterSet", "UTF8"); - return DataType.createChar(dataType.getInt("size"), charSetFromString(charSet)); + final String charSet = dataType.getString(CHARACTER_SET, "UTF8"); + return DataType.createChar(dataType.getInt(SIZE), charSetFromString(charSet)); } private DataType getTimestamp(final JsonObject dataType) { - final boolean withLocalTimezone = dataType.getBoolean("withLocalTimeZone", false); + final boolean withLocalTimezone = dataType.getBoolean(WITH_LOCAL_TIME_ZONE, false); return DataType.createTimestamp(withLocalTimezone); } private DataType getInterval(final JsonObject dataType) { - final int precision = dataType.getInt("precision", 2); - final IntervalType intervalType = intervalTypeFromString(dataType.getString("fromTo")); + final int precision = dataType.getInt(PRECISION, 2); + final IntervalType intervalType = intervalTypeFromString(dataType.getString(FROM_TO)); if (intervalType == IntervalType.DAY_TO_SECOND) { - final int fraction = dataType.getInt("fraction", 3); + final int fraction = dataType.getInt(FRACTION, 3); return DataType.createIntervalDaySecond(precision, fraction); } else { return DataType.createIntervalYearMonth(precision); @@ -442,7 +435,7 @@ private DataType getInterval(final JsonObject dataType) { } private DataType getGeometry(final JsonObject dataType) { - final int srid = dataType.getInt("srid"); + final int srid = dataType.getInt(SRID); return DataType.createGeometry(srid); } @@ -492,13 +485,13 @@ private SqlNode parseLiteralTimestamputc(final JsonObject exp) { private SqlNode parsePredicateLikeRegexp(final JsonObject exp) { final SqlNode likeRegexpLeft = parseExpression(exp.getJsonObject(EXPRESSION)); - final SqlNode likeRegexpPattern = parseExpression(exp.getJsonObject("pattern")); + final SqlNode likeRegexpPattern = parseExpression(exp.getJsonObject(PATTERN)); return new SqlPredicateLikeRegexp(likeRegexpLeft, likeRegexpPattern); } private SqlNode parsePredicateBetween(final JsonObject exp) { final SqlNode betweenExp = parseExpression(exp.getJsonObject(EXPRESSION)); - final SqlNode betweenLeft = parseExpression(exp.getJsonObject("left")); + final SqlNode betweenLeft = parseExpression(exp.getJsonObject(LEFT)); final SqlNode betweenRight = parseExpression(exp.getJsonObject(RIGHT)); return new SqlPredicateBetween(betweenExp, betweenLeft, betweenRight); } @@ -506,45 +499,45 @@ private SqlNode parsePredicateBetween(final JsonObject exp) { private SqlNode parsePredicateIsJson(final JsonObject jsonExpression) { final SqlNode expression = parseExpression(jsonExpression.getJsonObject(EXPRESSION)); final TypeConstraints typeConstraint = TypeConstraints - .valueOf(jsonExpression.getString("typeConstraint").toUpperCase()); + .valueOf(jsonExpression.getString(TYPE_CONSTRAINT).toUpperCase()); final KeyUniquenessConstraint keyUniquenessConstraint = KeyUniquenessConstraint - .of(jsonExpression.getString("keyUniquenessConstraint")); + .of(jsonExpression.getString(KEY_UNIQUENESS_CONSTRAINT)); return new SqlPredicateIsJson(expression, typeConstraint, keyUniquenessConstraint); } private SqlNode parsePredicateIsNotJson(final JsonObject jsonExpression) { final SqlNode expression = parseExpression(jsonExpression.getJsonObject(EXPRESSION)); final TypeConstraints typeConstraint = TypeConstraints - .valueOf(jsonExpression.getString("typeConstraint").toUpperCase()); + .valueOf(jsonExpression.getString(TYPE_CONSTRAINT).toUpperCase()); final KeyUniquenessConstraint keyUniquenessConstraint = KeyUniquenessConstraint - .of(jsonExpression.getString("keyUniquenessConstraint")); + .of(jsonExpression.getString(KEY_UNIQUENESS_CONSTRAINT)); return new SqlPredicateIsNotJson(expression, typeConstraint, keyUniquenessConstraint); } private SqlNode parsePredicateInConstlist(final JsonObject exp) { final SqlNode inExp = parseExpression(exp.getJsonObject(EXPRESSION)); - final List inArguments = getListOfSqlNodes(exp, ARGUMENTS_KEY); + final List inArguments = getListOfSqlNodes(exp, ARGUMENTS); return new SqlPredicateInConstList(inExp, inArguments); } private SqlNode parseFunctionScalar(final JsonObject exp) { - final String functionName = exp.getString("name"); - final List arguments = getListOfSqlNodes(exp, ARGUMENTS_KEY); + final String functionName = exp.getString(NAME); + final List arguments = getListOfSqlNodes(exp, ARGUMENTS); return new SqlFunctionScalar(fromScalarFunctionName(functionName), arguments); } private SqlNode parseFunctionScalarExtract(final JsonObject expression) { final SqlNode argument = getSingleArgument(expression); - final String toExtract = expression.getString("toExtract"); + final String toExtract = expression.getString(TO_EXTRACT); return new SqlFunctionScalarExtract(SqlFunctionScalarExtract.ExtractParameter.valueOf(toExtract), argument); } private SqlNode parseFunctionScalarCase(final JsonObject exp) { - final List caseArguments = getListOfSqlNodes(exp, ARGUMENTS_KEY); - final List caseResults = getListOfSqlNodes(exp, "results"); + final List caseArguments = getListOfSqlNodes(exp, ARGUMENTS); + final List caseResults = getListOfSqlNodes(exp, RESULTS); SqlNode caseBasis = null; - if (exp.containsKey("basis")) { - caseBasis = parseExpression(exp.getJsonObject("basis")); + if (exp.containsKey(BASIS)) { + caseBasis = parseExpression(exp.getJsonObject(BASIS)); } return new SqlFunctionScalarCase(caseArguments, caseResults, caseBasis); } @@ -556,13 +549,13 @@ private SqlNode parseFunctionScalarCast(final JsonObject expression) { } private SqlNode parseFunctionScalarJsonValue(final JsonObject jsonObject) { - final String functionName = jsonObject.getString("name"); - final List arguments = getListOfSqlNodes(jsonObject, ARGUMENTS_KEY); - final DataType returningDataType = getDataType(jsonObject.getJsonObject("returningDataType")); + final String functionName = jsonObject.getString(NAME); + final List arguments = getListOfSqlNodes(jsonObject, ARGUMENTS); + final DataType returningDataType = getDataType(jsonObject.getJsonObject(RETURNING_DATA_TYPE)); final SqlFunctionScalarJsonValue.Behavior emptyBehavior = getScalarJsonValueBehavior(jsonObject, - "emptyBehavior"); + EMPTY_BEHAVIOR); final SqlFunctionScalarJsonValue.Behavior errorBehavior = getScalarJsonValueBehavior(jsonObject, - "errorBehavior"); + ERROR_BEHAVIOR); return new SqlFunctionScalarJsonValue(fromScalarFunctionName(functionName), arguments, returningDataType, emptyBehavior, errorBehavior); } @@ -570,7 +563,7 @@ private SqlNode parseFunctionScalarJsonValue(final JsonObject jsonObject) { private SqlFunctionScalarJsonValue.Behavior getScalarJsonValueBehavior(final JsonObject jsonObject, final String key) { final JsonObject behaviorJson = jsonObject.getJsonObject(key); - final String behaviorTypeString = behaviorJson.getString("type"); + final String behaviorTypeString = behaviorJson.getString(TYPE); final SqlFunctionScalarJsonValue.BehaviorType behaviorType = SqlFunctionScalarJsonValue.BehaviorType .valueOf(behaviorTypeString); final Optional expression = getScalarJsonValueExpression(behaviorJson, behaviorType); @@ -587,22 +580,22 @@ private Optional getScalarJsonValueExpression(final JsonObject jsonObje } private SqlNode parseFunctionAggregate(final JsonObject exp) { - final String setFunctionName = exp.getString("name"); - final List setArguments = getListOfSqlNodes(exp, ARGUMENTS_KEY); - final boolean distinct = exp.containsKey(DISTINCT_KEY) && exp.getBoolean(DISTINCT_KEY); + final String setFunctionName = exp.getString(NAME); + final List setArguments = getListOfSqlNodes(exp, ARGUMENTS); + final boolean distinct = exp.containsKey(DISTINCT) && exp.getBoolean(DISTINCT); return new SqlFunctionAggregate(fromAggregationFunctionName(setFunctionName), setArguments, distinct); } private SqlNode parseFunctionAggregateGroupConcat(final JsonObject expression) { final SqlNode argument = getSingleArgument(expression); final SqlFunctionAggregateGroupConcat.Builder builder = SqlFunctionAggregateGroupConcat.builder(argument); - if (expression.containsKey(DISTINCT_KEY)) { - builder.distinct(expression.getBoolean(DISTINCT_KEY)); + if (expression.containsKey(DISTINCT)) { + builder.distinct(expression.getBoolean(DISTINCT)); } - if (expression.containsKey(ORDER_BY_KEY)) { - builder.orderBy(parseOrderBy(expression.getJsonArray(ORDER_BY_KEY))); + if (expression.containsKey(ORDER_BY)) { + builder.orderBy(parseOrderBy(expression.getJsonArray(ORDER_BY))); } - if (expression.containsKey(SEPARATOR_KEY)) { + if (expression.containsKey(SEPARATOR)) { final SqlLiteralString separator = getSeparator(expression); builder.separator(separator); } @@ -610,16 +603,16 @@ private SqlNode parseFunctionAggregateGroupConcat(final JsonObject expression) { } private SqlNode getSingleArgument(final JsonObject expression) { - final List arguments = expression.getJsonArray(ARGUMENTS_KEY).getValuesAs(JsonObject.class); + final List arguments = expression.getJsonArray(ARGUMENTS).getValuesAs(JsonObject.class); return parseExpression(arguments.get(0)); } private SqlLiteralString getSeparator(final JsonObject expression) { - final JsonValue jsonSeparator = expression.get(SEPARATOR_KEY); + final JsonValue jsonSeparator = expression.get(SEPARATOR); if (jsonSeparator.getValueType() == JsonValue.ValueType.STRING) { - return new SqlLiteralString(expression.getString(SEPARATOR_KEY)); + return new SqlLiteralString(expression.getString(SEPARATOR)); } else { - return (SqlLiteralString) parseExpression(expression.getJsonObject(SEPARATOR_KEY)); + return (SqlLiteralString) parseExpression(expression.getJsonObject(SEPARATOR)); } } @@ -627,30 +620,29 @@ private SqlNode parseFunctionAggregateListagg(final JsonObject expression) { final SqlNode argument = getSingleArgument(expression); final Behavior overflowBehavior = parseOverflowBehavior(expression); final Builder builder = SqlFunctionAggregateListagg.builder(argument, overflowBehavior); - if (expression.containsKey(DISTINCT_KEY)) { - builder.distinct(expression.getBoolean(DISTINCT_KEY)); + if (expression.containsKey(DISTINCT)) { + builder.distinct(expression.getBoolean(DISTINCT)); } - if (expression.containsKey(ORDER_BY_KEY)) { - builder.orderBy(parseOrderBy(expression.getJsonArray(ORDER_BY_KEY))); + if (expression.containsKey(ORDER_BY)) { + builder.orderBy(parseOrderBy(expression.getJsonArray(ORDER_BY))); } - if (expression.containsKey(SEPARATOR_KEY)) { - final SqlLiteralString separator = (SqlLiteralString) parseExpression( - expression.getJsonObject(SEPARATOR_KEY)); + if (expression.containsKey(SEPARATOR)) { + final SqlLiteralString separator = (SqlLiteralString) parseExpression(expression.getJsonObject(SEPARATOR)); builder.separator(separator); } return builder.build(); } private Behavior parseOverflowBehavior(final JsonObject expression) { - final JsonObject overflowBehaviorJson = expression.getJsonObject("overflowBehavior"); - final BehaviorType behaviorType = BehaviorType.valueOf(overflowBehaviorJson.getString("type").toUpperCase()); + final JsonObject overflowBehaviorJson = expression.getJsonObject(OVERFLOW_BEHAVIOUR); + final BehaviorType behaviorType = BehaviorType.valueOf(overflowBehaviorJson.getString(TYPE).toUpperCase()); final Behavior overflowBehavior = new Behavior(behaviorType); if (behaviorType == BehaviorType.TRUNCATE) { overflowBehavior.setTruncationType( - TruncationType.parseTruncationType(overflowBehaviorJson.getString("truncationType"))); - if (overflowBehaviorJson.containsKey("truncationFiller")) { + TruncationType.parseTruncationType(overflowBehaviorJson.getString(TRUNCATION_TYPE))); + if (overflowBehaviorJson.containsKey(TRUNCATION_FILLER)) { final SqlLiteralString truncationFiller = (SqlLiteralString) parseExpression( - overflowBehaviorJson.getJsonObject("truncationFiller")); + overflowBehaviorJson.getJsonObject(TRUNCATION_FILLER)); overflowBehavior.setTruncationFiller(truncationFiller); } } @@ -693,9 +685,9 @@ private TableMetadata findInvolvedTableMetadata(final String tableName) { } } throw new IllegalStateException(ExaError.messageBuilder("E-VS-COM-JAVA-14").message( - "Could not find table metadata for involved table \"{{tableName}}\". All involved tables: {{involvedTables}}") - .unquotedParameter("tableName", tableName) - .parameter("involvedTables", this.involvedTablesMetadata.toString()).toString()); + "Could not find table metadata for involved table \"{{tableName|uq}}\". All involved tables: {{involvedTables}}") + .parameter("tableName", tableName).parameter("involvedTables", this.involvedTablesMetadata.toString()) + .toString()); } private ColumnMetadata findColumnMetadata(final String tableName, final String columnName) { @@ -706,9 +698,9 @@ private ColumnMetadata findColumnMetadata(final String tableName, final String c } } throw new IllegalStateException(ExaError.messageBuilder("E-VS-COM-JAVA-15").message( - "Could not find column metadata for involved table \"{{tableName}}\" and column \"{{columnName}}\". " + "Could not find column metadata for involved table \"{{tableName|uq}}\" and column \"{{columnName|uq}}\". " + "All involved tables: {{involvedTables}}.") - .unquotedParameter("tableName", tableName).unquotedParameter("columnName", columnName) + .parameter("tableName", tableName).parameter("columnName", columnName) .parameter("involvedTables", this.involvedTablesMetadata.toString()).toString()); } diff --git a/src/main/java/com/exasol/adapter/request/renderer/PushdownSqlRenderer.java b/src/main/java/com/exasol/adapter/request/renderer/PushdownSqlRenderer.java new file mode 100644 index 00000000..db2890e2 --- /dev/null +++ b/src/main/java/com/exasol/adapter/request/renderer/PushdownSqlRenderer.java @@ -0,0 +1,481 @@ +package com.exasol.adapter.request.renderer; + +import static com.exasol.adapter.request.RequestJsonKeys.*; + +import java.util.List; +import java.util.Optional; + +import javax.json.*; +import javax.json.spi.JsonProvider; + +import com.exasol.adapter.AdapterException; +import com.exasol.adapter.metadata.DataType; +import com.exasol.adapter.sql.*; +import com.exasol.errorreporting.ExaError; + +/** + * This class serializes {@link SqlNode}s to JSON. + *

+ * It's the inversion of {@link com.exasol.adapter.request.parser.PushdownSqlParser}. + *

+ */ +public class PushdownSqlRenderer { + + private static final JsonProvider JSON = JsonProvider.provider(); + + /** + * Serialize a {@link SqlNode} to JSON. + * + * @param node node to serialize + * @return JSON representation + */ + public JsonValue render(final SqlNode node) { + try { + return node.accept(new ConvertVisitor()); + } catch (final AdapterException exception) { + throw new IllegalStateException(ExaError.messageBuilder("F-VS-COM-JAVA-34") + .message("n unexpected error occurred during request serialization.").ticketMitigation().toString(), + exception); + } + } + + private static class ConvertVisitor implements SqlNodeVisitor { + + @Override + public JsonObject visit(final SqlStatementSelect select) throws AdapterException { + final JsonObjectBuilder builder = createObjectBuilderFor(select); + builder.add(FROM, select.getFromClause().accept(this)); + addIfPresent(select.getSelectList(), SELECT_LIST, builder); + addIfPresent(select.getGroupBy(), GROUP_BY, builder); + addIfPresent(select.getWhereClause(), FILTER, builder); + addIfPresent(select.getHaving(), HAVING, builder); + addIfPresent(select.getOrderBy(), ORDER_BY, builder); + addIfPresent(select.getLimit(), LIMIT, builder); + return builder.build(); + } + + private JsonObjectBuilder createObjectBuilderFor(final SqlNode type) { + return JSON.createObjectBuilder().add(TYPE, type.getType().name().toLowerCase()); + } + + private void addIfPresent(final SqlNode node, final String key, final JsonObjectBuilder builder) + throws AdapterException { + if (node != null) { + builder.add(key, node.accept(this)); + } + } + + @Override + public JsonArray visit(final SqlSelectList selectList) throws AdapterException { + return convertListOfNodes(selectList.getExpressions()); + } + + private JsonArray convertListOfNodes(final List expressions) throws AdapterException { + final JsonArrayBuilder resultBuilder = JSON.createArrayBuilder(); + for (final SqlNode node : expressions) { + resultBuilder.add(node.accept(this)); + } + return resultBuilder.build(); + } + + @Override + public JsonArray visit(final SqlGroupBy groupBy) throws AdapterException { + return convertListOfNodes(groupBy.getExpressions()); + } + + @Override + public JsonObject visit(final SqlColumn sqlColumn) throws AdapterException { + final JsonObjectBuilder builder = createObjectBuilderFor(sqlColumn); + builder.add(COLUMN_NR, sqlColumn.getId()); + builder.add(NAME, sqlColumn.getName()); + builder.add(TABLE_NAME, sqlColumn.getTableName()); + if (sqlColumn.hasTableAlias()) { + builder.add(TABLE_ALIAS, sqlColumn.getTableAlias()); + } + return builder.build(); + } + + @Override + public JsonObject visit(final SqlFunctionAggregate sqlFunctionAggregate) throws AdapterException { + final JsonObjectBuilder builder = createObjectBuilderFor(sqlFunctionAggregate); + builder.add(NAME, sqlFunctionAggregate.getFunctionName()); + builder.add(ARGUMENTS, convertListOfNodes(sqlFunctionAggregate.getArguments())); + if (sqlFunctionAggregate.hasDistinct()) { + builder.add(DISTINCT, true); + } + return builder.build(); + } + + @Override + public JsonObject visit(final SqlFunctionAggregateGroupConcat function) throws AdapterException { + final JsonObjectBuilder builder = createObjectBuilderFor(function); + builder.add(NAME, function.getFunctionName()); + builder.add(ARGUMENTS, convertListOfNodes(List.of(function.getArgument()))); + if (function.hasDistinct()) { + builder.add(DISTINCT, true); + } + addIfPresent(function.getOrderBy(), ORDER_BY, builder); + builder.add(SEPARATOR, function.getSeparator().accept(this)); + return builder.build(); + } + + @Override + public JsonObject visit(final SqlFunctionScalar function) throws AdapterException { + final JsonObjectBuilder builder = createObjectBuilderFor(function); + builder.add(NAME, function.getFunctionName()); + builder.add(NUM_ARGS, function.getArguments().size()); + builder.add(ARGUMENTS, convertListOfNodes(function.getArguments())); + return builder.build(); + } + + @Override + public JsonObject visit(final SqlFunctionScalarCase function) throws AdapterException { + final JsonObjectBuilder builder = createObjectBuilderFor(function); + builder.add(NAME, "CASE"); + builder.add(ARGUMENTS, convertListOfNodes(function.getArguments())); + builder.add(RESULTS, convertListOfNodes(function.getResults())); + addIfPresent(function.getBasis(), BASIS, builder); + return builder.build(); + } + + @Override + public JsonObject visit(final SqlFunctionScalarCast function) throws AdapterException { + final JsonObjectBuilder builder = createObjectBuilderFor(function); + builder.add(DATA_TYPE, render(function.getDataType())); + builder.add(ARGUMENTS, convertListOfNodes(List.of(function.getArgument()))); + builder.add(NAME, "CAST"); + return builder.build(); + } + + private JsonObject render(final DataType dataType) { + final JsonObjectBuilder builder = JSON.createObjectBuilder(); + final DataType.ExaDataType type = dataType.getExaDataType(); + builder.add(TYPE, type.name().toUpperCase()); + switch (type) { + case DECIMAL: + builder.add(PRECISION, dataType.getPrecision()); + builder.add(SCALE, dataType.getScale()); + break; + case VARCHAR: + case CHAR: + builder.add(CHARACTER_SET, dataType.getCharset().name().toLowerCase()); + builder.add(SIZE, dataType.getSize()); + break; + case TIMESTAMP: + builder.add(WITH_LOCAL_TIME_ZONE, dataType.isWithLocalTimezone()); + break; + case INTERVAL: + builder.add(PRECISION, dataType.getPrecision()); + builder.add(FROM_TO, render(dataType.getIntervalType())); + if (dataType.getIntervalType().equals(DataType.IntervalType.DAY_TO_SECOND)) { + builder.add(FRACTION, dataType.getIntervalFraction()); + } + break; + case GEOMETRY: + builder.add(SRID, dataType.getGeometrySrid()); + break; + default: + break; + } + return builder.build(); + } + + private String render(final DataType.IntervalType intervalType) { + switch (intervalType) { + case DAY_TO_SECOND: + return "DAY TO SECONDS"; + case YEAR_TO_MONTH: + return "YEAR TO MONTH"; + default: + throw new IllegalStateException(ExaError.messageBuilder("F-VS-COM-JAVA-35") + .message("Unimplemented interval type {{type}}.", intervalType).ticketMitigation().toString()); + } + } + + @Override + public JsonObject visit(final SqlFunctionScalarExtract function) throws AdapterException { + final JsonObjectBuilder builder = createObjectBuilderFor(function); + builder.add(NAME, "EXTRACT"); + builder.add(TO_EXTRACT, function.getToExtract()); + builder.add(ARGUMENTS, convertListOfNodes(List.of(function.getArgument()))); + return builder.build(); + } + + @Override + public JsonObject visit(final SqlFunctionScalarJsonValue function) throws AdapterException { + final JsonObjectBuilder builder = createObjectBuilderFor(function); + builder.add(NAME, function.getScalarFunction().name()); + builder.add(ARGUMENTS, convertListOfNodes(function.getArguments())); + builder.add(RETURNING_DATA_TYPE, render(function.getReturningDataType())); + builder.add(EMPTY_BEHAVIOR, render(function.getEmptyBehavior())); + builder.add(ERROR_BEHAVIOR, render(function.getErrorBehavior())); + return builder.build(); + } + + private JsonObject render(final SqlFunctionScalarJsonValue.Behavior behaviour) throws AdapterException { + final JsonObjectBuilder builder = JSON.createObjectBuilder(); + builder.add(TYPE, behaviour.getBehaviorType()); + final Optional expression = behaviour.getExpression(); + if (expression.isPresent()) { + builder.add(EXPRESSION, expression.get().accept(this)); + } + return builder.build(); + } + + @Override + public JsonObject visit(final SqlLimit sqlLimit) throws AdapterException { + final JsonObjectBuilder builder = createObjectBuilderFor(sqlLimit); + builder.add(NUM_ELEMENTS, sqlLimit.getLimit()); + builder.add(OFFSET, sqlLimit.getOffset()); + return builder.build(); + } + + @Override + public JsonObject visit(final SqlLiteralBool sqlLiteralBool) throws AdapterException { + final JsonObjectBuilder builder = createObjectBuilderFor(sqlLiteralBool); + builder.add(VALUE, sqlLiteralBool.getValue()); + return builder.build(); + } + + @Override + public JsonObject visit(final SqlLiteralDate sqlLiteralDate) throws AdapterException { + final JsonObjectBuilder builder = createObjectBuilderFor(sqlLiteralDate); + builder.add(VALUE, sqlLiteralDate.getValue()); + return builder.build(); + } + + @Override + public JsonObject visit(final SqlLiteralDouble sqlLiteralDouble) throws AdapterException { + final JsonObjectBuilder builder = createObjectBuilderFor(sqlLiteralDouble); + builder.add(VALUE, String.valueOf(sqlLiteralDouble.getValue())); + return builder.build(); + } + + @Override + public JsonObject visit(final SqlLiteralExactnumeric sqlLiteralExactnumeric) throws AdapterException { + final JsonObjectBuilder builder = createObjectBuilderFor(sqlLiteralExactnumeric); + builder.add(VALUE, sqlLiteralExactnumeric.getValue().toString()); + return builder.build(); + } + + @Override + public JsonObject visit(final SqlLiteralNull sqlLiteralNull) throws AdapterException { + return createObjectBuilderFor(sqlLiteralNull).build(); + } + + @Override + public JsonObject visit(final SqlLiteralString sqlLiteralString) throws AdapterException { + final JsonObjectBuilder builder = createObjectBuilderFor(sqlLiteralString); + builder.add(VALUE, sqlLiteralString.getValue()); + return builder.build(); + } + + @Override + public JsonObject visit(final SqlLiteralTimestamp sqlLiteralTimestamp) throws AdapterException { + final JsonObjectBuilder builder = createObjectBuilderFor(sqlLiteralTimestamp); + builder.add(VALUE, sqlLiteralTimestamp.getValue()); + return builder.build(); + } + + @Override + public JsonObject visit(final SqlLiteralTimestampUtc sqlLiteralTimestampUtc) throws AdapterException { + final JsonObjectBuilder builder = createObjectBuilderFor(sqlLiteralTimestampUtc); + builder.add(VALUE, sqlLiteralTimestampUtc.getValue()); + return builder.build(); + } + + @Override + public JsonObject visit(final SqlLiteralInterval sqlLiteralInterval) throws AdapterException { + final JsonObjectBuilder builder = createObjectBuilderFor(sqlLiteralInterval); + builder.add(VALUE, sqlLiteralInterval.getValue()); + builder.add(DATA_TYPE, render(sqlLiteralInterval.getDataType())); + return builder.build(); + } + + @Override + public JsonArray visit(final SqlOrderBy sqlOrderBy) throws AdapterException { + final JsonArrayBuilder builder = JSON.createArrayBuilder(); + final List isAscendings = sqlOrderBy.isAscending(); + final List expressions = sqlOrderBy.getExpressions(); + final List nullsLasts = sqlOrderBy.nullsLast(); + final int expressionsSize = expressions.size(); + if (expressionsSize != isAscendings.size() || expressionsSize != nullsLasts.size()) { + throw new IllegalStateException(ExaError.messageBuilder("F-VS-COM-JAVA-33").message( + "Can not render SqlOrderBy as JSON because it has an invalid format. The size of the three lists must be equal.") + .ticketMitigation().toString()); + } + for (int index = 0; index < expressionsSize; index++) { + final JsonObjectBuilder objectBuilder = JSON.createObjectBuilder(); + objectBuilder.add(TYPE, ORDER_BY_ELEMENT); + objectBuilder.add(EXPRESSION, expressions.get(index).accept(this)); + objectBuilder.add(IS_ASCENDING, isAscendings.get(index)); + objectBuilder.add(NULLS_LAST, nullsLasts.get(index)); + builder.add(objectBuilder); + } + return builder.build(); + } + + @Override + public JsonObject visit(final SqlPredicateAnd sqlPredicateAnd) throws AdapterException { + final JsonObjectBuilder builder = createObjectBuilderFor(sqlPredicateAnd); + builder.add(EXPRESSIONS, convertListOfNodes(sqlPredicateAnd.getAndedPredicates())); + return builder.build(); + } + + @Override + public JsonObject visit(final SqlPredicateBetween sqlPredicateBetween) throws AdapterException { + final JsonObjectBuilder builder = createObjectBuilderFor(sqlPredicateBetween); + builder.add(LEFT, sqlPredicateBetween.getBetweenLeft().accept(this)); + builder.add(RIGHT, sqlPredicateBetween.getBetweenRight().accept(this)); + builder.add(EXPRESSION, sqlPredicateBetween.getExpression().accept(this)); + return builder.build(); + } + + @Override + public JsonObject visit(final SqlPredicateEqual sqlPredicateEqual) throws AdapterException { + final JsonObjectBuilder builder = getJsonObjectBuilderForComparison(sqlPredicateEqual); + return builder.build(); + } + + private JsonObjectBuilder getJsonObjectBuilderForComparison(final AbstractSqlBinaryEquality sqlPredicateEqual) + throws AdapterException { + final JsonObjectBuilder builder = createObjectBuilderFor(sqlPredicateEqual); + builder.add(LEFT, sqlPredicateEqual.getLeft().accept(this)); + builder.add(RIGHT, sqlPredicateEqual.getRight().accept(this)); + return builder; + } + + @Override + public JsonObject visit(final SqlPredicateInConstList sqlPredicateInConstList) throws AdapterException { + final JsonObjectBuilder builder = createObjectBuilderFor(sqlPredicateInConstList); + builder.add(EXPRESSION, sqlPredicateInConstList.getExpression().accept(this)); + builder.add(ARGUMENTS, convertListOfNodes(sqlPredicateInConstList.getInArguments())); + return builder.build(); + } + + @Override + public JsonObject visit(final SqlPredicateIsJson sqlPredicateIsJson) throws AdapterException { + final JsonObjectBuilder builder = createObjectBuilderFor(sqlPredicateIsJson); + builder.add(EXPRESSION, sqlPredicateIsJson.getExpression().accept(this)); + builder.add(TYPE_CONSTRAINT, sqlPredicateIsJson.getTypeConstraint()); + builder.add(KEY_UNIQUENESS_CONSTRAINT, sqlPredicateIsJson.getKeyUniquenessConstraint()); + return builder.build(); + } + + @Override + public JsonObject visit(final SqlPredicateIsNotJson sqlPredicateIsNotJson) throws AdapterException { + final JsonObjectBuilder builder = createObjectBuilderFor(sqlPredicateIsNotJson); + builder.add(EXPRESSION, sqlPredicateIsNotJson.getExpression().accept(this)); + builder.add(TYPE_CONSTRAINT, sqlPredicateIsNotJson.getTypeConstraint()); + builder.add(KEY_UNIQUENESS_CONSTRAINT, sqlPredicateIsNotJson.getKeyUniquenessConstraint()); + return builder.build(); + } + + @Override + public JsonObject visit(final SqlPredicateLess sqlPredicateLess) throws AdapterException { + final JsonObjectBuilder builder = getJsonObjectBuilderForComparison(sqlPredicateLess); + return builder.build(); + } + + @Override + public JsonObject visit(final SqlPredicateLessEqual sqlPredicateLessEqual) throws AdapterException { + final JsonObjectBuilder builder = getJsonObjectBuilderForComparison(sqlPredicateLessEqual); + return builder.build(); + } + + @Override + public JsonObject visit(final SqlPredicateLike sqlPredicateLike) throws AdapterException { + final JsonObjectBuilder builder = createObjectBuilderFor(sqlPredicateLike); + builder.add(EXPRESSION, sqlPredicateLike.getLeft().accept(this)); + addIfPresent(sqlPredicateLike.getPattern(), PATTERN, builder); + addIfPresent(sqlPredicateLike.getEscapeChar(), ESCAPE_CHAR, builder); + return builder.build(); + } + + @Override + public JsonObject visit(final SqlPredicateLikeRegexp sqlPredicateLikeRegexp) throws AdapterException { + final JsonObjectBuilder builder = createObjectBuilderFor(sqlPredicateLikeRegexp); + builder.add(EXPRESSION, sqlPredicateLikeRegexp.getLeft().accept(this)); + builder.add(PATTERN, sqlPredicateLikeRegexp.getPattern().accept(this)); + return builder.build(); + } + + @Override + public JsonObject visit(final SqlPredicateNot sqlPredicateNot) throws AdapterException { + final JsonObjectBuilder builder = createObjectBuilderFor(sqlPredicateNot); + builder.add(EXPRESSION, sqlPredicateNot.getExpression().accept(this)); + return builder.build(); + } + + @Override + public JsonObject visit(final SqlPredicateNotEqual sqlPredicateNotEqual) throws AdapterException { + final JsonObjectBuilder builder = getJsonObjectBuilderForComparison(sqlPredicateNotEqual); + return builder.build(); + } + + @Override + public JsonObject visit(final SqlPredicateOr sqlPredicateOr) throws AdapterException { + final JsonObjectBuilder builder = createObjectBuilderFor(sqlPredicateOr); + builder.add(EXPRESSIONS, convertListOfNodes(sqlPredicateOr.getOrPredicates())); + return builder.build(); + } + + @Override + public JsonObject visit(final SqlPredicateIsNotNull sqlPredicateIsNotNull) throws AdapterException { + final JsonObjectBuilder builder = createObjectBuilderFor(sqlPredicateIsNotNull); + builder.add(EXPRESSION, sqlPredicateIsNotNull.getExpression().accept(this)); + return builder.build(); + } + + @Override + public JsonObject visit(final SqlPredicateIsNull sqlPredicateIsNull) throws AdapterException { + final JsonObjectBuilder builder = createObjectBuilderFor(sqlPredicateIsNull); + builder.add(EXPRESSION, sqlPredicateIsNull.getExpression().accept(this)); + return builder.build(); + } + + @Override + public JsonObject visit(final SqlTable sqlTable) throws AdapterException { + final JsonObjectBuilder builder = createObjectBuilderFor(sqlTable); + builder.add(NAME, sqlTable.getName()); + if (sqlTable.hasAlias()) { + builder.add(ALIAS, sqlTable.getAlias()); + } + return builder.build(); + } + + @Override + public JsonObject visit(final SqlJoin sqlJoin) throws AdapterException { + final JsonObjectBuilder builder = createObjectBuilderFor(sqlJoin); + builder.add(LEFT, sqlJoin.getLeft().accept(this)); + builder.add(RIGHT, sqlJoin.getRight().accept(this)); + builder.add(CONDITION, sqlJoin.getCondition().accept(this)); + builder.add(JOIN_TYPE, sqlJoin.getJoinType().name().toLowerCase()); + return builder.build(); + } + + @Override + public JsonObject visit(final SqlFunctionAggregateListagg function) throws AdapterException { + final JsonObjectBuilder builder = createObjectBuilderFor(function); + builder.add(NAME, "LISTAGG"); + builder.add(ARGUMENTS, convertListOfNodes(List.of(function.getArgument()))); + builder.add(OVERFLOW_BEHAVIOUR, render(function.getOverflowBehavior())); + builder.add(DISTINCT, function.hasDistinct()); + addIfPresent(function.getOrderBy(), ORDER_BY, builder); + addIfPresent(function.getSeparator(), SEPARATOR, builder); + return builder.build(); + } + + private JsonObject render(final SqlFunctionAggregateListagg.Behavior overflowBehavior) throws AdapterException { + final JsonObjectBuilder builder = JSON.createObjectBuilder(); + builder.add(TYPE, overflowBehavior.getBehaviorType().name()); + if (overflowBehavior.getTruncationType() != null) { + builder.add(TRUNCATION_TYPE, overflowBehavior.getTruncationType()); + } + if (overflowBehavior.hasTruncationFiller()) { + builder.add(TRUNCATION_FILLER, overflowBehavior.getTruncationFiller().accept(this)); + } + return builder.build(); + } + } +} diff --git a/src/test/java/com/exasol/adapter/request/renderer/PushdownSqlRendererTest.java b/src/test/java/com/exasol/adapter/request/renderer/PushdownSqlRendererTest.java new file mode 100644 index 00000000..79afd19d --- /dev/null +++ b/src/test/java/com/exasol/adapter/request/renderer/PushdownSqlRendererTest.java @@ -0,0 +1,762 @@ +package com.exasol.adapter.request.renderer; + +import static com.exasol.adapter.metadata.DataType.createDecimal; +import static com.exasol.adapter.metadata.DataType.createVarChar; +import static com.exasol.adapter.metadata.DataType.ExaCharset.UTF8; + +import java.io.IOException; +import java.io.StringReader; +import java.nio.file.*; +import java.util.ArrayList; +import java.util.List; + +import javax.json.*; + +import org.json.JSONException; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import org.skyscreamer.jsonassert.JSONAssert; + +import com.exasol.adapter.metadata.ColumnMetadata; +import com.exasol.adapter.metadata.TableMetadata; +import com.exasol.adapter.request.parser.PushdownSqlParser; +import com.exasol.adapter.sql.SqlNode; + +class PushdownSqlRendererTest { + + private PushdownSqlParser defaultParser; + + @BeforeEach + void beforeEach() { + final List involvedTables = new ArrayList<>(); + final List defaultColumnMetadata = createDefaultColumnMetadata(); + final TableMetadata tableMetadata = new TableMetadata("CLICKS", "", defaultColumnMetadata, ""); + involvedTables.add(tableMetadata); + this.defaultParser = PushdownSqlParser.createWithTablesMetadata(involvedTables); + } + + private List createDefaultColumnMetadata() { + final List columnMetadataList = new ArrayList<>(); + final ColumnMetadata columnMetadata = ColumnMetadata.builder().name("USER_ID").adapterNotes("") + .type(createDecimal(18, 0)).nullable(true).identity(false).defaultValue("").comment("").build(); + columnMetadataList.add(columnMetadata); + return columnMetadataList; + } + + private JsonObject createJsonObjectFromString(final String json) { + try (final JsonReader jsonReader = Json.createReader(new StringReader(json))) { + return jsonReader.readObject(); + } + } + + @Test + void testParseSelectWithGroupBy() throws JSONException { + final String sqlAsJson = "{" // + + " \"type\" : \"select\", " // + + " \"from\" : " // + + " { " // + + " \"type\" : \"table\", " // + + " \"name\" : \"CLICKS\", " // + + " \"alias\" : \"A\" " // + + " }, " // + + " \"groupBy\" : [ " // + + " { " // + + " \"type\" : \"column\", " // + + " \"name\" : \"USER_ID\", " // + + " \"columnNr\" : 1, " // + + " \"tableName\" : \"CLICKS\" " // + + " } " // + + " ] " // + + "}"; + assertParseAndRenderGeneratesSameJson(sqlAsJson); + } + + private void assertParseAndRenderGeneratesSameJson(final String sqlAsJson) throws JSONException { + assertParseAndRenderGeneratesSameJson(sqlAsJson, this.defaultParser); + } + + private void assertParseAndRenderGeneratesSameJson(final String sqlAsJson, final PushdownSqlParser parser) + throws JSONException { + final JsonObject jsonObject = createJsonObjectFromString(sqlAsJson); + final SqlNode parsed = parser.parseExpression(jsonObject); + final String rendered = new PushdownSqlRenderer().render(parsed).toString(); + JSONAssert.assertEquals(sqlAsJson, rendered, false); + } + + @Test + void testParseSelectWithLimit() throws JSONException { + final String sqlAsJson = "{" // + + " \"type\" : \"select\", " // + + " \"from\" : " // + + " { " // + + " \"type\" : \"table\", " // + + " \"name\" : \"CLICKS\" " // + + " }, " // + + " \"limit\" : " // + + " { " // + + " \"numElements\" : 10 " // + + " } " // + + "}"; + assertParseAndRenderGeneratesSameJson(sqlAsJson); + } + + @Test + void testParseColumn() throws JSONException { + final String sqlAsJson = "{" // + + " \"type\" : \"column\"," // + + " \"name\" : \"USER_ID\"," // + + " \"tableAlias\" : \"A\"," // + + " \"columnNr\" : 1, " // + + " \"tableName\" : \"CLICKS\" " // + + "}"; + assertParseAndRenderGeneratesSameJson(sqlAsJson); + } + + @Test + void testParseLiteralBool() throws JSONException { + final String sqlAsJson = "{" // + + " \"type\" : \"literal_bool\", " // + + " \"value\" : true " // + + "}"; + assertParseAndRenderGeneratesSameJson(sqlAsJson); + } + + @Test + void testParseLiteralNull() throws JSONException { + final String sqlAsJson = "{" // + + " \"type\" : \"literal_null\"" // + + "}"; + assertParseAndRenderGeneratesSameJson(sqlAsJson); + } + + @Test + void testParseLiteralDate() throws JSONException { + final String sqlAsJson = "{" // + + " \"type\" : \"literal_date\", " // + + " \"value\" : \"2015-12-01\" " // + + "}"; + assertParseAndRenderGeneratesSameJson(sqlAsJson); + } + + @Test + void testParseLiteralIntervalDayToSeconds() throws JSONException { + final String sqlAsJson = "{" // + + " \"type\" : \"literal_interval\", " // + + " \"dataType\" : { " // + + " \"type\" : \"INTERVAL\", " // + + " \"fromTo\" : \"DAY TO SECONDS\"" // + + " }, " // + + " \"value\" : \"2015-12-01\" " // + + "}"; + assertParseAndRenderGeneratesSameJson(sqlAsJson); + } + + @Test + void testParseLiteralTimestamp() throws JSONException { + final String sqlAsJson = "{" // + + " \"type\" : \"literal_timestamp\", " // + + " \"value\" : \"2015-12-01 12:01:01.1234\" " // + + "}"; + assertParseAndRenderGeneratesSameJson(sqlAsJson); + } + + @Test + void testParseLiteralTimestamputc() throws JSONException { + final String sqlAsJson = "{" // + + " \"type\" : \"literal_timestamputc\", " // + + " \"value\" : \"2015-12-01 12:01:01.1234\" " // + + "}"; + assertParseAndRenderGeneratesSameJson(sqlAsJson); + } + + @Test + void testParseLiteralDouble() throws JSONException { + final String sqlAsJson = "{" // + + " \"type\" : \"literal_double\", " // + + " \"value\" : \"1.234\" " // + + "}"; + assertParseAndRenderGeneratesSameJson(sqlAsJson); + } + + @Test + void testParseLiteralExactNumeric() throws JSONException { + final String sqlAsJson = "{" // + + " \"type\" : \"literal_exactnumeric\", " // + + " \"value\" : \"100000\" " // + + "}"; + assertParseAndRenderGeneratesSameJson(sqlAsJson); + } + + @Test + void testParseLiteralString() throws JSONException { + final String sqlAsJson = "{" // + + " \"type\" : \"literal_string\", " // + + " \"value\" : \"my string\" " // + + "}"; + assertParseAndRenderGeneratesSameJson(sqlAsJson); + } + + @Test + void testParsePredicateAnd() throws JSONException { + final String sqlAsJson = "{" // + + " \"type\" : \"predicate_and\", " // + + " \"expressions\" : [ " // + + " { " // + + " \"type\" : \"literal_double\", " // + + " \"value\" : \"1.0\" " // + + " }, " // + + " { " // + + " \"type\" : \"literal_double\", " // + + " \"value\" : \"2.0\" " // + + " } " // + + " ] " // + + "}"; + assertParseAndRenderGeneratesSameJson(sqlAsJson); + } + + @Test + void testParsePredicateAndEmptyExpressions() throws JSONException { + final String sqlAsJson = "{" // + + " \"type\" : \"predicate_and\", " // + + " \"expressions\" : [ " // + + " ] " // + + "}"; + assertParseAndRenderGeneratesSameJson(sqlAsJson); + } + + @Test + void testParsePredicateOr() throws JSONException { + final String sqlAsJson = "{" // + + " \"type\" : \"predicate_or\", " // + + " \"expressions\" : [ " // + + " { " // + + " \"type\" : \"literal_double\", " // + + " \"value\" : \"1.0\" " // + + " }, " // + + " { " // + + " \"type\" : \"literal_double\", " // + + " \"value\" : \"2.0\" " // + + " } " // + + " ] " // + + "}"; + assertParseAndRenderGeneratesSameJson(sqlAsJson); + } + + @Test + void testParsePredicateOrEmptyExpressions() throws JSONException { + final String sqlAsJson = "{" // + + " \"type\" : \"predicate_or\", " // + + " \"expressions\" : [ " // + + " ] " // + + "}"; + assertParseAndRenderGeneratesSameJson(sqlAsJson); + } + + @Test + void testParsePredicateNot() throws JSONException { + final String sqlAsJson = "{" // + + " \"type\" : \"predicate_not\", " // + + " \"expression\" : { " // + + " \"type\" : \"literal_null\"" // + + " } " // + + "}"; + assertParseAndRenderGeneratesSameJson(sqlAsJson); + } + + @Test + void testParsePredicateEqual() throws JSONException { + final String sqlAsJson = "{" // + + " \"type\" : \"predicate_equal\", " // + + " \"left\" : { " // + + " \"type\" : \"literal_double\", " // + + " \"value\" : \"1.234\" " // + + " }, " // + + " \"right\" : { " // + + " \"type\" : \"literal_double\", " // + + " \"value\" : \"1.234\" " // + + " } " // + + "}"; + assertParseAndRenderGeneratesSameJson(sqlAsJson); + } + + @Test + void testParsePredicateNotEqual() throws JSONException { + final String sqlAsJson = "{" // + + " \"type\" : \"predicate_notequal\", " // + + " \"left\" : { " // + + " \"type\" : \"literal_double\", " // + + " \"value\" : \"1.2\" " // + + " }, " // + + " \"right\" : { " // + + " \"type\" : \"literal_double\", " // + + " \"value\" : \"1.234\" " // + + " } " // + + "}"; + assertParseAndRenderGeneratesSameJson(sqlAsJson); + } + + @Test + void testParsePredicateLess() throws JSONException { + final String sqlAsJson = "{" // + + " \"type\" : \"predicate_less\", " // + + " \"left\" : { " // + + " \"type\" : \"literal_double\", " // + + " \"value\" : \"1.2\" " // + + " }, " // + + " \"right\" : { " // + + " \"type\" : \"literal_double\", " // + + " \"value\" : \"1.234\" " // + + " } " // + + "}"; + assertParseAndRenderGeneratesSameJson(sqlAsJson); + } + + @Test + void testParsePredicateLessEqual() throws JSONException { + final String sqlAsJson = "{" // + + " \"type\" : \"predicate_lessequal\", " // + + " \"left\" : { " // + + " \"type\" : \"literal_double\", " // + + " \"value\" : \"1.2\" " // + + " }, " // + + " \"right\" : { " // + + " \"type\" : \"literal_double\", " // + + " \"value\" : \"1.234\" " // + + " } " // + + "}"; + assertParseAndRenderGeneratesSameJson(sqlAsJson); + } + + @Test + void testParsePredicateLike() throws JSONException { + final String sqlAsJson = "{" // + + " \"type\" : \"predicate_like\", " // + + " \"expression\" : { " // + + " \"type\" : \"literal_string\", " // + + " \"value\" : \"abcd\" " // + + " }, " // + + " \"pattern\" : { " // + + " \"type\" : \"literal_string\", " // + + " \"value\" : \"a_d\" " // + + " } " // + + "}"; + assertParseAndRenderGeneratesSameJson(sqlAsJson); + } + + @Test + void testParsePredicateLikeRegexp() throws JSONException { + final String sqlAsJson = "{" // + + " \"type\" : \"predicate_like_regexp\", " // + + " \"expression\" : { " // + + " \"type\" : \"literal_string\", " // + + " \"value\" : \"abcd\" " // + + " }, " // + + " \"pattern\" : { " // + + " \"type\" : \"literal_string\", " // + + " \"value\" : \"a_d\" " // + + " } " // + + "}"; + assertParseAndRenderGeneratesSameJson(sqlAsJson); + } + + @Test + void testParsePredicateBetween() throws JSONException { + final String sqlAsJson = "{" // + + " \"type\" : \"predicate_between\", " // + + " \"expression\" : { " // + + " \"type\" : \"literal_double\", " // + + " \"value\" : \"1.2\" " // + + " }, " // + + " \"left\" : { " // + + " \"type\" : \"literal_double\", " // + + " \"value\" : \"1.1\" " // + + " }, " // + + " \"right\" : { " // + + " \"type\" : \"literal_double\", " // + + " \"value\" : \"1.234\" " // + + " } " // + + "}"; + assertParseAndRenderGeneratesSameJson(sqlAsJson); + } + + @Test + void testParsePredicateIsNull() throws JSONException { + final String sqlAsJson = "{" // + + " \"type\" : \"predicate_is_null\", " // + + " \"expression\" : { " // + + " \"type\" : \"literal_double\", " // + + " \"value\" : \"0.0\" " // + + " } " // + + "}"; + assertParseAndRenderGeneratesSameJson(sqlAsJson); + } + + @Test + void testParsePredicateIsNotNull() throws JSONException { + final String sqlAsJson = "{" // + + " \"type\" : \"predicate_is_not_null\", " // + + " \"expression\" : { " // + + " \"type\" : \"literal_double\", " // + + " \"value\" : \"1.0\" " // + + " } " // + + "}"; + assertParseAndRenderGeneratesSameJson(sqlAsJson); + } + + @Test + void testParseFunctionScalar() throws JSONException { + final String sqlAsJson = "{" // + + " \"type\" : \"function_scalar\", " // + + " \"numArgs\" : 1, " // + + " \"name\" : \"ABS\", " // + + " \"arguments\" : [ " // + + " { " // + + " \"type\" : \"literal_double\", " // + + " \"value\" : \"1.0\" " // + + " } " // + + " ] " // + + "}"; + assertParseAndRenderGeneratesSameJson(sqlAsJson); + } + + @Test + void testParseFunctionScalarExtract() throws JSONException { + final String sqlAsJson = "{" // + + " \"type\" : \"function_scalar_extract\", " // + + " \"name\" : \"EXTRACT\", " // + + " \"toExtract\" : \"MINUTE\", " // + + " \"arguments\" : [ " // + + " { " // + + " \"type\" : \"literal_timestamp\", " // + + " \"value\" : \"2015-12-01 12:01:01.1234\" " // + + " } " // + + " ] " // + + "}"; + assertParseAndRenderGeneratesSameJson(sqlAsJson); + } + + @Test + void testParseFunctionScalarCast() throws JSONException { + final String sqlAsJson = "{" // + + " \"type\" : \"function_scalar_cast\", " // + + " \"name\" : \"CAST\", " // + + " \"dataType\" : { " // + + " \"type\" : \"VARCHAR\", " // + + " \"size\" : 10000 " // + + " }, " // + + " \"arguments\" : [ " // + + " { " // + + " \"type\" : \"literal_double\", " // + + " \"value\" : \"1.234\" " // + + " } " // + + " ] " // + + "}"; + assertParseAndRenderGeneratesSameJson(sqlAsJson); + } + + @Test + void testParsePredicateInConstlist() throws JSONException { + final String sqlAsJson = "{" // + + " \"type\" : \"predicate_in_constlist\", " // + + " \"expression\" : { " // + + " \"type\" : \"literal_double\", " // + + " \"value\" : \"2.0\" " // + + " }, " // + + " \"arguments\" : [ " // + + " { " // + + " \"type\" : \"literal_double\", " // + + " \"value\" : \"1.0\" " // + + " }, " // + + " { " // + + " \"type\" : \"literal_double\", " // + + " \"value\" : \"2.0\" " // + + " } " // + + " ] " // + + "}"; + assertParseAndRenderGeneratesSameJson(sqlAsJson); + } + + @Test + void testParsePredicateIsJson() throws JSONException { + final String sqlAsJson = "{" // + + " \"type\" : \"predicate_is_json\", " // + + " \"expression\" : { " // + + " \"type\" : \"literal_string\", " // + + " \"value\" : \"'123'\"" // + + " }, " // + + " \"typeConstraint\" : \"VALUE\", " // + + " \"keyUniquenessConstraint\" : \"WITHOUT UNIQUE KEYS\"" + "}"; + assertParseAndRenderGeneratesSameJson(sqlAsJson); + } + + @Test + void testParsePredicateIsNotJson() throws JSONException { + final String sqlAsJson = "{" // + + " \"type\" : \"predicate_is_not_json\", " // + + " \"expression\" : { " // + + " \"type\" : \"literal_string\", " // + + " \"value\" : \"'123'\"" // + + " }, " // + + " \"typeConstraint\" : \"VALUE\", " // + + " \"keyUniquenessConstraint\" : \"WITHOUT UNIQUE KEYS\"" + "}"; + assertParseAndRenderGeneratesSameJson(sqlAsJson); + } + + @Test + void testParseFunctionScalarCase() throws JSONException { + final String sqlAsJson = "{" // + + " \"type\" : \"function_scalar_case\", " // + + " \"name\" : \"CASE\", " // + + " \"basis\" : { " // + + " \"type\" : \"column\", " // + + " \"columnNr\" : 1, " // + + " \"name\" : \"USER_ID\", " // + + " \"tableName\" : \"CLICKS\" " // + + " }, " // + + " \"arguments\" : [ " // + + " { " // + + " \"type\" : \"literal_exactnumeric\", " // + + " \"value\" : \"1\" " // + + " }, " // + + " { " // + + " \"type\" : \"literal_exactnumeric\", " // + + " \"value\" : \"2\" " // + + " } " // + + " ], " // + + " \"results\" : [ " // + + " { " // + + " \"type\" : \"literal_string\", " // + + " \"value\" : \"VERY GOOD\" " // + + " }, " // + + " { " // + + " \"type\" : \"literal_string\", " // + + " \"value\" : \"GOOD\" " // + + " } " // + + " ] " // + + "}"; + assertParseAndRenderGeneratesSameJson(sqlAsJson); + } + + @Test + void testParseFunctionScalarJsonValue() throws JSONException { + final String sqlAsJson = "{" // + + " \"type\" : \"function_scalar_json_value\", " // + + " \"name\" : \"JSON_VALUE\", " // + + " \"arguments\" : [ " // + + " { " // + + " \"type\" : \"literal_string\", " // + + " \"value\" : \"'first argument'\"" // + + " }, " // + + " { " // + + " \"type\" : \"literal_string\", " // + + " \"value\" : \"'second argument'\"" /// + + " } " // + + " ], " // + + " \"returningDataType\" : { " // + + " \"type\" : \"VARCHAR\", " // + + " \"size\" : 10000 " // + + " }, " // + + " \"emptyBehavior\" : " // + + " { " // + + " \"type\" : \"NULL\" " // + + " }, " // + + " \"errorBehavior\" : " // + + " { " // + + " \"type\" : \"DEFAULT\", " // + + " \"expression\" : " // + + " { " // + + " \"type\" : \"literal_string\", " // + + " \"value\" : \"*** error ***\" " // + + " } " // + + " } " // + + "}"; + assertParseAndRenderGeneratesSameJson(sqlAsJson); + } + + @Test + void testParseFunctionAggregate() throws JSONException { + final String sqlAsJson = "{" // + + " \"type\" : \"function_aggregate\", " // + + " \"name\" : \"SUM\", " // + + " \"arguments\" : [ " // + + " { " // + + " \"type\" : \"literal_double\", " // + + " \"value\" : \"1.0\" " // + + " }, " // + + " { " // + + " \"type\" : \"literal_double\", " // + + " \"value\" : \"2.0\" " // + + " } " // + + " ] " // + + "}"; + assertParseAndRenderGeneratesSameJson(sqlAsJson); + } + + @Test + void testParseFunctionAggregateGroupConcatNewApi() throws JSONException { + final String sqlAsJson = "{" // + + " \"type\" : \"function_aggregate_group_concat\", " // + + " \"name\" : \"GROUP_CONCAT\", " // + + " \"distinct\" : true, " // + + " \"arguments\" : [ " // + + " { " // + + " \"type\" : \"literal_double\", " // + + " \"value\" : \"2.0\" " // + + " } " // + + " ], " // + + " \"orderBy\" : [ " // + + " { " // + + " \"type\" : \"order_by_element\", " // + + " \"expression\" : " // + + " { " // + + " \"type\" : \"column\", " // + + " \"columnNr\" : 1, " // + + " \"name\" : \"USER_ID\", " // + + " \"tableName\" : \"CLICKS\" " // + + " }, " // + + " \"isAscending\" : true, " // + + " \"nullsLast\" : true " // + + " } " // + + " ], " // + + " \"separator\":" // + + " {" // + + " \"type\": \"literal_string\"," // + + " \"value\": \", \"" // + + " }" // + + "}"; + assertParseAndRenderGeneratesSameJson(sqlAsJson); + } + + @Test + void testParseFunctionAggregateGroupListagg() throws JSONException { + final String sqlAsJson = "{" // + + " \"type\": \"function_aggregate_listagg\"," // + + " \"name\": \"LISTAGG\"," // + + " \"distinct\": true," // + + " \"arguments\": [" // + + " {" // + + " \"type\": \"column\"," // + + " \"columnNr\": 1," // + + " \"name\": \"USER_ID\"," // + + " \"tableName\": \"CLICKS\"" // + + " }" // + + " ]," // + + " \"separator\":" // + + " {" // + + " \"type\": \"literal_string\"," // + + " \"value\": \", \"" // + + " }," // + + " \"overflowBehavior\":" // + + " {" // + + " \"type\": \"TRUNCATE\"," // + + " \"truncationType\": \"WITH COUNT\"," // + + " \"truncationFiller\": " // + + " {" // + + " \"type\": \"literal_string\"," // + + " \"value\": \"filler\"" // + + " }" // + + " }," // + + " \"orderBy\": [" // + + " {" // + + " \"type\": \"order_by_element\"," // + + " \"expression\":" // + + " {" // + + " \"type\": \"column\"," // + + " \"columnNr\": 1," // + + " \"name\": \"USER_ID\"," // + + " \"tableName\": \"CLICKS\"" // + + " }," // + + " \"isAscending\": true," // + + " \"nullsLast\": true" // + + " }" // + + " ]" // + + "}"; + assertParseAndRenderGeneratesSameJson(sqlAsJson); + } + + @ParameterizedTest + @ValueSource(strings = { "pushdown_request_simple_inner_join_with_select_list.json", + "pushdown_request_simple_inner_join_without_select_list.json", + "pushdown_request_nested_join_with_repeated_table_without_select_list.json" }) + void testJoin(final String testFile) throws IOException, JSONException { + final String sqlAsJson = new String(Files.readAllBytes(Path.of("src/test/resources/json/", testFile))); + final PushdownSqlParser pushdownSqlParser = getCustomPushdownSqlParserWithTwoTables(); + assertParseAndRenderGeneratesSameJson(sqlAsJson, pushdownSqlParser); + } + + private PushdownSqlParser getCustomPushdownSqlParserWithTwoTables() { + final List tables = new ArrayList<>(); + final List columns = List.of( // + ColumnMetadata.builder().name("ID").adapterNotes("").type(createDecimal(18, 0)).build(), // + ColumnMetadata.builder().name("NAME").adapterNotes("").type(createVarChar(200, UTF8)).build()); + tables.add(new TableMetadata("CUSTOMERS", "", columns, "")); + final List columns2 = List.of( + ColumnMetadata.builder().name("CUSTOMER_ID").adapterNotes("").type(createDecimal(18, 0)).build(), // + ColumnMetadata.builder().name("ITEM_ID").adapterNotes("").type(createVarChar(200, UTF8)).build()); + tables.add(new TableMetadata("ORDERS", "", columns2, "")); + return PushdownSqlParser.createWithTablesMetadata(tables); + } + + @Test + void testParseSelectWithoutSelectList() throws JSONException { + final String sqlAsJson = "{" // + + " \"type\" : \"select\", " // + + " \"from\" : " // + + " { " // + + " \"type\" : \"table\", " // + + " \"name\" : \"CUSTOMERS\" " // + + " }" // + + "}"; + final PushdownSqlParser pushdownSqlParser = getCustomPushdownSqlParserWithTwoTables(); + assertParseAndRenderGeneratesSameJson(sqlAsJson, pushdownSqlParser); + } + + @Test + void testParseSelectWithEmptySelectList() throws JSONException { + final String sqlAsJson = "{" // + + " \"type\" : \"select\", " // + + " \"from\" : " // + + " { " // + + " \"type\" : \"table\", " // + + " \"name\" : \"CLICKS\" " // + + " }," // + + " \"selectList\" : []" // + + "}"; + assertParseAndRenderGeneratesSameJson(sqlAsJson); + } + + @Test + void testNestedJoinRequestWithoutSelectList() throws IOException, JSONException { + final String sqlAsJson = new String(Files.readAllBytes( + Paths.get("src/test/resources/json/pushdown_request_nested_join_without_select_list.json"))); + final PushdownSqlParser pushdownSqlParser = getCustomPushdownSqlParserWithThreeTables(); + assertParseAndRenderGeneratesSameJson(sqlAsJson, pushdownSqlParser); + } + + private PushdownSqlParser getCustomPushdownSqlParserWithThreeTables() { + final List tables = new ArrayList<>(); + final List columns = List.of( // + ColumnMetadata.builder().name("ID").adapterNotes("").type(createVarChar(200, UTF8)).build(), // + ColumnMetadata.builder().name("NAME").adapterNotes("").type(createVarChar(200, UTF8)).build()); + tables.add(new TableMetadata("CUSTOMERS", "", columns, "")); + final List columns2 = List.of( + ColumnMetadata.builder().name("CUSTOMER_ID").adapterNotes("").type(createVarChar(200, UTF8)).build(), // + ColumnMetadata.builder().name("ITEM_ID").adapterNotes("").type(createVarChar(200, UTF8)).build()); + tables.add(new TableMetadata("ORDERS", "", columns2, "")); + final List columns3 = List.of( + ColumnMetadata.builder().name("ITEM_ID").adapterNotes("").type(createVarChar(200, UTF8)).build(), // + ColumnMetadata.builder().name("ITEM_NAME").adapterNotes("").type(createVarChar(200, UTF8)).build()); + tables.add(new TableMetadata("ITEMS", "", columns3, "")); + return PushdownSqlParser.createWithTablesMetadata(tables); + } + + @Test + void testNestedJoinRequestWithoutSelectListReversed() throws IOException, JSONException { + final String sqlAsJson = new String(Files.readAllBytes( + Paths.get("src/test/resources/json/pushdown_request_nested_join_without_select_list_reversed.json"))); + final PushdownSqlParser pushdownSqlParser = getCustomPushdownSqlParserWithThreeTables(); + assertParseAndRenderGeneratesSameJson(sqlAsJson, pushdownSqlParser); + } +} \ No newline at end of file