diff --git a/.github/workflows/sql-test-and-build-workflow.yml b/.github/workflows/sql-test-and-build-workflow.yml index 87256e6175..fecfe7adc2 100644 --- a/.github/workflows/sql-test-and-build-workflow.yml +++ b/.github/workflows/sql-test-and-build-workflow.yml @@ -11,7 +11,6 @@ on: - '!sql-jdbc/**' - '**gradle*' - '**lombok*' - - '**checkstyle*' - 'integ-test/**' - '**/*.jar' - '**/*.pom' diff --git a/.github/workflows/sql-test-workflow.yml b/.github/workflows/sql-test-workflow.yml index cdc08c7480..9ca27dffaf 100644 --- a/.github/workflows/sql-test-workflow.yml +++ b/.github/workflows/sql-test-workflow.yml @@ -31,24 +31,16 @@ jobs: - name: Run tests id: tests run: | - # checkstyle - ./gradlew :opensearch:checkstyleMain || echo "* Checkstyle failed for opensearch/src" > report.log - ./gradlew :opensearch:checkstyleTest || echo "* Checkstyle failed for opensearch/test" >> report.log - ./gradlew :sql:checkstyleMain || echo "* Checkstyle failed for sql/src" >> report.log - ./gradlew :sql:checkstyleTest || echo "* Checkstyle failed for sql/test" >> report.log - ./gradlew :ppl:checkstyleMain || echo "* Checkstyle failed for ppl/src" >> report.log - ./gradlew :ppl:checkstyleTest || echo "* Checkstyle failed for ppl/test" >> report.log - ./gradlew :core:checkstyleMain || echo "* Checkstyle failed for core/src" >> report.log - ./gradlew :core:checkstyleTest || echo "* Checkstyle failed for core/test" >> report.log - ./gradlew :common:checkstyleMain || echo "* Checkstyle failed for common/src" >> report.log - ./gradlew :common:checkstyleTest || echo "* Checkstyle failed for common/test" >> report.log - ./gradlew :legacy:checkstyleMain || echo "* Checkstyle failed for legacy/src" >> report.log - ./gradlew :legacy:checkstyleTest || echo "* Checkstyle failed for legacy/test" >> report.log - ./gradlew :protocol:checkstyleMain || echo "* Checkstyle failed for protocol/src" >> report.log - ./gradlew :protocol:checkstyleTest || echo "* Checkstyle failed for protocol/test" >> report.log - ./gradlew :opensearch-sql-plugin:checkstyleMain || echo "* Checkstyle failed for plugin/src" >> report.log - ./gradlew :opensearch-sql-plugin:checkstyleTest || echo "* Checkstyle failed for plugin/test" >> report.log - # Add checkstyle for `integ-test` when fixed + # Spotless + ./gradlew :opensearch:spotlessCheck || echo "* Spotless failed for opensearch" > report.log + ./gradlew :sql:spotlessCheck || echo "* Spotless failed for sql" >> report.log + ./gradlew :ppl:spotlessCheck || echo "* Spotless failed for ppl" >> report.log + ./gradlew :core:spotlessCheck || echo "* Spotless failed for core" >> report.log + ./gradlew :common:spotlessCheck || echo "* Spotless failed for common" >> report.log + ./gradlew :legacy:spotlessCheck || echo "* Spotless failed for legacy" >> report.log + ./gradlew :protocol:spotlessCheck || echo "* Spotless failed for protocol" >> report.log + ./gradlew :opensearch-sql-plugin:spotlessCheck || echo "* Spotless failed for plugin" >> report.log + ./gradlew :integ-test:spotlessCheck || echo "* Spotless failed for integ-test" >> report.log # Unit tests ./gradlew :opensearch:test || echo "* Unit tests failed for opensearch" >> report.log ./gradlew :ppl:test || echo "* Unit tests failed for sql" >> report.log diff --git a/DEVELOPER_GUIDE.rst b/DEVELOPER_GUIDE.rst index 8c23d98d91..c0d2f85668 100644 --- a/DEVELOPER_GUIDE.rst +++ b/DEVELOPER_GUIDE.rst @@ -127,7 +127,6 @@ The plugin codebase is in standard layout of Gradle project:: ├── THIRD-PARTY ├── build.gradle ├── config - │ └── checkstyle ├── docs │   ├── attributions.md │   ├── category.json @@ -170,7 +169,6 @@ Here are sub-folders (Gradle modules) for plugin source code: Here are other files and sub-folders that you are likely to touch: - ``build.gradle``: Gradle build script. -- ``config``: only Checkstyle configuration files for now. - ``docs``: documentation for developers and reference manual for users. - ``doc-test``: code that run .rst docs in ``docs`` folder by Python doctest library. @@ -189,7 +187,7 @@ Java files in the OpenSearch codebase are formatted with the Eclipse JDT formatt The formatting check can be run explicitly with:: -./gradlew spotlessJavaCheck +./gradlew spotlessCheck The code can be formatted with:: @@ -197,7 +195,7 @@ The code can be formatted with:: These tasks can also be run for specific modules, e.g.:: -./gradlew server:spotlessJavaCheck +./gradlew server:spotlessCheck For more information on the spotless for the OpenSearch project please see `https://github.com/opensearch-project/OpenSearch/blob/main/DEVELOPER_GUIDE.md#java-language-formatting-guidelines `_. @@ -230,9 +228,7 @@ Most of the time you just need to run ./gradlew build which will make sure you p * - ./gradlew generateGrammarSource - (Re-)Generate ANTLR parser from grammar file. * - ./gradlew compileJava - - Compile all Java source files. - * - ./gradlew checkstyle - - Run all checks according to Checkstyle configuration. + - Compile all Java source files. * - ./gradlew test - Run all unit tests. * - ./gradlew :integ-test:integTest diff --git a/build.gradle b/build.gradle index 2ab7abc42a..ebe9291e23 100644 --- a/build.gradle +++ b/build.gradle @@ -62,7 +62,6 @@ buildscript { plugins { id 'nebula.ospackage' version "8.3.0" id 'java-library' - id 'checkstyle' id "io.freefair.lombok" version "6.4.0" id 'jacoco' id 'com.diffplug.spotless' version '6.19.0' @@ -80,22 +79,10 @@ repositories { maven { url 'https://jitpack.io' } } -// Spotless checks will be added as PRs are applied to resolve each style issue is approved. spotless { java { target fileTree('.') { - include 'datasources/**/*.java', - 'core/**/*.java', - 'protocol/**/*.java', - 'prometheus/**/*.java', - 'sql/**/*.java', - 'common/**/*.java', - 'spark/**/*.java', - 'plugin/**/*.java', - 'ppl/**/*.java', - 'integ-test/**/*java', - 'core/**/*.java', - 'opensearch/**/*.java' + include '**/*.java' exclude '**/build/**', '**/build-*/**' } importOrder() @@ -194,22 +181,6 @@ jacocoTestCoverageVerification { } check.dependsOn jacocoTestCoverageVerification -// TODO: fix code style in main and test source code -allprojects { - apply plugin: 'checkstyle' - checkstyle { - configFile rootProject.file("config/checkstyle/google_checks.xml") - toolVersion "10.3.2" - configProperties = [ - "org.checkstyle.google.suppressionfilter.config": rootProject.file("config/checkstyle/suppressions.xml")] - ignoreFailures = false - } -} -checkstyle { - configFile file("config/checkstyle/checkstyle.xml") -} -checkstyleMain.ignoreFailures = false -checkstyleTest.ignoreFailures = true configurations.all { resolutionStrategy.force 'junit:junit:4.13.2' diff --git a/common/build.gradle b/common/build.gradle index 25cdcd6566..d27e213db1 100644 --- a/common/build.gradle +++ b/common/build.gradle @@ -25,17 +25,13 @@ plugins { id 'java-library' id "io.freefair.lombok" + id 'com.diffplug.spotless' version '6.19.0' } repositories { mavenCentral() } -// Being ignored as a temporary measure before being removed in favour of -// spotless https://github.com/opensearch-project/sql/issues/1101 -checkstyleTest.ignoreFailures = true -checkstyleMain.ignoreFailures = true - dependencies { api "org.antlr:antlr4-runtime:4.7.1" api group: 'com.google.guava', name: 'guava', version: '32.0.1-jre' @@ -68,3 +64,22 @@ configurations.all { resolutionStrategy.force "joda-time:joda-time:2.10.12" resolutionStrategy.force "org.slf4j:slf4j-api:1.7.36" } + +spotless { + java { + target fileTree('.') { + include '**/*.java' + exclude '**/build/**', '**/build-*/**' + } + importOrder() +// Needs https://github.com/opensearch-project/sql/issues/1893 to be addressed first +// licenseHeader("/*\n" + +// " * Copyright OpenSearch Contributors\n" + +// " * SPDX-License-Identifier: Apache-2.0\n" + +// " */\n\n") + removeUnusedImports() + trimTrailingWhitespace() + endWithNewline() + googleJavaFormat('1.17.0').reflowLongStrings().groupArtifact('com.google.googlejavaformat:google-java-format') + } +} diff --git a/config/checkstyle/checkstyle.xml b/config/checkstyle/checkstyle.xml deleted file mode 100644 index 3d0e8f074d..0000000000 --- a/config/checkstyle/checkstyle.xml +++ /dev/null @@ -1,120 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/config/checkstyle/google_checks.xml b/config/checkstyle/google_checks.xml deleted file mode 100644 index 12c90f8495..0000000000 --- a/config/checkstyle/google_checks.xml +++ /dev/null @@ -1,316 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/config/checkstyle/suppressions.xml b/config/checkstyle/suppressions.xml deleted file mode 100644 index ff366c9457..0000000000 --- a/config/checkstyle/suppressions.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/core/build.gradle b/core/build.gradle index 0e563b274e..675e73ba32 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -28,18 +28,14 @@ plugins { id 'jacoco' id 'info.solidsoft.pitest' version '1.9.0' id 'java-test-fixtures' + id 'com.diffplug.spotless' version '6.19.0' + } repositories { mavenCentral() } -// Being ignored as a temporary measure before being removed in favour of -// spotless https://github.com/opensearch-project/sql/issues/1101 -checkstyleTest.ignoreFailures = true -checkstyleMain.ignoreFailures = true -checkstyleTestFixtures.ignoreFailures = true - pitest { targetClasses = ['org.opensearch.sql.*'] pitestVersion = '1.9.0' @@ -67,6 +63,24 @@ dependencies { testImplementation group: 'org.mockito', name: 'mockito-junit-jupiter', version: '3.12.4' } +spotless { + java { + target fileTree('.') { + include '**/*.java' + exclude '**/build/**', '**/build-*/**' + } + importOrder() +// licenseHeader("/*\n" + +// " * Copyright OpenSearch Contributors\n" + +// " * SPDX-License-Identifier: Apache-2.0\n" + +// " */\n\n") + removeUnusedImports() + trimTrailingWhitespace() + endWithNewline() + googleJavaFormat('1.17.0').reflowLongStrings().groupArtifact('com.google.googlejavaformat:google-java-format') + } +} + test { useJUnitPlatform() testLogging { diff --git a/datasources/build.gradle b/datasources/build.gradle index 830fadbc35..ef52db2305 100644 --- a/datasources/build.gradle +++ b/datasources/build.gradle @@ -31,9 +31,6 @@ dependencies { testImplementation 'org.junit.jupiter:junit-jupiter:5.6.2' } -checkstyleTest.ignoreFailures = true -checkstyleMain.ignoreFailures = true - test { useJUnitPlatform() testLogging { diff --git a/doctest/build.gradle b/doctest/build.gradle index c3a177f900..5cab1060e2 100644 --- a/doctest/build.gradle +++ b/doctest/build.gradle @@ -9,6 +9,7 @@ plugins { id 'base' id 'com.wiredforcode.spawn' id "de.undercouch.download" version "5.3.0" + id 'com.diffplug.spotless' version '6.19.0' } apply plugin: 'opensearch.testclusters' @@ -140,3 +141,21 @@ tasks.register("runRestTestCluster", RunTask) { description = 'Runs OpenSearch SQL plugin' useCluster testClusters.docTestCluster; } + +spotless { + java { + target fileTree('.') { + include '**/*.java' + exclude '**/build/**', '**/build-*/**' + } + importOrder() +// licenseHeader("/*\n" + +// " * Copyright OpenSearch Contributors\n" + +// " * SPDX-License-Identifier: Apache-2.0\n" + +// " */\n\n") + removeUnusedImports() + trimTrailingWhitespace() + endWithNewline() + googleJavaFormat('1.17.0').reflowLongStrings().groupArtifact('com.google.googlejavaformat:google-java-format') + } +} diff --git a/integ-test/build.gradle b/integ-test/build.gradle index 7cb0983670..eedf2f4a03 100644 --- a/integ-test/build.gradle +++ b/integ-test/build.gradle @@ -33,6 +33,7 @@ import java.util.stream.Collectors plugins { id "de.undercouch.download" version "5.3.0" + id 'com.diffplug.spotless' version '6.19.0' } apply plugin: 'opensearch.build' @@ -56,11 +57,6 @@ repositories { } } -// Being ignored as a temporary measure before being removed in favour of -// spotless https://github.com/opensearch-project/sql/issues/1101 -checkstyleTest.ignoreFailures = true -checkstyleMain.ignoreFailures = true - ext { projectSubstitutions = [:] licenseFile = rootProject.file('LICENSE.TXT') @@ -194,7 +190,6 @@ dependencies { dependencyLicenses.enabled = false testingConventions.enabled = false -checkstyleTest.ignoreFailures = true forbiddenApisTest.enabled = false thirdPartyAudit.enabled = false @@ -634,6 +629,24 @@ task bwcTestSuite(type: StandaloneRestIntegTestTask) { def opensearch_tmp_dir = rootProject.file('build/private/es_tmp').absoluteFile opensearch_tmp_dir.mkdirs() +spotless { + java { + target fileTree('.') { + include '**/*.java' + exclude '**/build/**', '**/build-*/**' + } + importOrder() +// licenseHeader("/*\n" + +// " * Copyright OpenSearch Contributors\n" + +// " * SPDX-License-Identifier: Apache-2.0\n" + +// " */\n\n") + removeUnusedImports() + trimTrailingWhitespace() + endWithNewline() + googleJavaFormat('1.17.0').reflowLongStrings().groupArtifact('com.google.googlejavaformat:google-java-format') + } +} + task integTestRemote(type: RestIntegTestTask) { testLogging { events "passed", "skipped", "failed" diff --git a/legacy/build.gradle b/legacy/build.gradle index fce04ae9ba..fc985989e5 100644 --- a/legacy/build.gradle +++ b/legacy/build.gradle @@ -26,6 +26,7 @@ plugins { id 'java' id 'io.freefair.lombok' id 'antlr' + id 'com.diffplug.spotless' version '6.19.0' } generateGrammarSource { @@ -53,8 +54,23 @@ compileJava { } } -checkstyleTest.ignoreFailures = true -checkstyleMain.ignoreFailures = true +spotless { + java { + target fileTree('.') { + include '**/*.java' + exclude '**/build/**', '**/build-*/**' + } + importOrder() +// licenseHeader("/*\n" + +// " * Copyright OpenSearch Contributors\n" + +// " * SPDX-License-Identifier: Apache-2.0\n" + +// " */\n\n") + removeUnusedImports() + trimTrailingWhitespace() + endWithNewline() + googleJavaFormat('1.17.0').reflowLongStrings().groupArtifact('com.google.googlejavaformat:google-java-format') + } +} // TODO: Similarly, need to fix compiling errors in test source code compileTestJava.options.warnings = false diff --git a/legacy/src/main/java/org/opensearch/sql/legacy/antlr/SimilarSymbols.java b/legacy/src/main/java/org/opensearch/sql/legacy/antlr/SimilarSymbols.java index 0f87b9eb05..7410e56e49 100644 --- a/legacy/src/main/java/org/opensearch/sql/legacy/antlr/SimilarSymbols.java +++ b/legacy/src/main/java/org/opensearch/sql/legacy/antlr/SimilarSymbols.java @@ -3,7 +3,6 @@ * SPDX-License-Identifier: Apache-2.0 */ - package org.opensearch.sql.legacy.antlr; import java.util.Collection; @@ -13,50 +12,51 @@ import org.apache.lucene.search.spell.LevenshteinDistance; import org.apache.lucene.search.spell.StringDistance; -/** - * String similarity for finding most similar string. - */ +/** String similarity for finding most similar string. */ public class SimilarSymbols { - /** LevenshteinDistance instance is basically a math util which is supposed to be thread safe */ - private static final StringDistance ALGORITHM = new LevenshteinDistance(); - - /** Symbol candidate list from which to pick one as most similar symbol to a target */ - private final Collection candidates; - - public SimilarSymbols(Collection candidates) { - this.candidates = Collections.unmodifiableCollection(candidates); + /** LevenshteinDistance instance is basically a math util which is supposed to be thread safe */ + private static final StringDistance ALGORITHM = new LevenshteinDistance(); + + /** Symbol candidate list from which to pick one as most similar symbol to a target */ + private final Collection candidates; + + public SimilarSymbols(Collection candidates) { + this.candidates = Collections.unmodifiableCollection(candidates); + } + + /** + * Find most similar string in candidates by calculating similarity distance among target and + * candidate strings. + * + * @param target string to match + * @return most similar string to the target + */ + public String mostSimilarTo(String target) { + Optional closest = + candidates.stream() + .map(candidate -> new SymbolDistance(candidate, target)) + .max(Comparator.comparing(SymbolDistance::similarity)); + if (closest.isPresent()) { + return closest.get().candidate; } - - /** - * Find most similar string in candidates by calculating similarity distance - * among target and candidate strings. - * - * @param target string to match - * @return most similar string to the target - */ - public String mostSimilarTo(String target) { - Optional closest = candidates.stream(). - map(candidate -> new SymbolDistance(candidate, target)). - max(Comparator.comparing(SymbolDistance::similarity)); - if (closest.isPresent()) { - return closest.get().candidate; - } - return target; + return target; + } + + /** + * Distance (similarity) between 2 symbols. This class is mainly for Java 8 stream comparator API + */ + private static class SymbolDistance { + private final String candidate; + private final String target; + + private SymbolDistance(String candidate, String target) { + this.candidate = candidate; + this.target = target; } - /** Distance (similarity) between 2 symbols. This class is mainly for Java 8 stream comparator API */ - private static class SymbolDistance { - private final String candidate; - private final String target; - - private SymbolDistance(String candidate, String target) { - this.candidate = candidate; - this.target = target; - } - - public float similarity() { - return ALGORITHM.getDistance(candidate, target); - } + public float similarity() { + return ALGORITHM.getDistance(candidate, target); } + } } diff --git a/legacy/src/main/java/org/opensearch/sql/legacy/antlr/SqlAnalysisConfig.java b/legacy/src/main/java/org/opensearch/sql/legacy/antlr/SqlAnalysisConfig.java index 56c69755a6..703c7d6586 100644 --- a/legacy/src/main/java/org/opensearch/sql/legacy/antlr/SqlAnalysisConfig.java +++ b/legacy/src/main/java/org/opensearch/sql/legacy/antlr/SqlAnalysisConfig.java @@ -3,49 +3,48 @@ * SPDX-License-Identifier: Apache-2.0 */ - package org.opensearch.sql.legacy.antlr; -/** - * Configuration for SQL analysis. - */ +/** Configuration for SQL analysis. */ public class SqlAnalysisConfig { - /** Is entire analyzer enabled to perform the analysis */ - private final boolean isAnalyzerEnabled; - - /** Is suggestion enabled for field name typo */ - private final boolean isFieldSuggestionEnabled; - - /** Skip entire analysis for index mapping larger than this threhold */ - private final int analysisThreshold; - - public SqlAnalysisConfig(boolean isAnalyzerEnabled, - boolean isFieldSuggestionEnabled, - int analysisThreshold) { - this.isAnalyzerEnabled = isAnalyzerEnabled; - this.isFieldSuggestionEnabled = isFieldSuggestionEnabled; - this.analysisThreshold = analysisThreshold; - } - - public boolean isAnalyzerEnabled() { - return isAnalyzerEnabled; - } - - public boolean isFieldSuggestionEnabled() { - return isFieldSuggestionEnabled; - } - - public int getAnalysisThreshold() { - return analysisThreshold; - } - - @Override - public String toString() { - return "SqlAnalysisConfig{" - + "isAnalyzerEnabled=" + isAnalyzerEnabled - + ", isFieldSuggestionEnabled=" + isFieldSuggestionEnabled - + ", analysisThreshold=" + analysisThreshold - + '}'; - } + /** Is entire analyzer enabled to perform the analysis */ + private final boolean isAnalyzerEnabled; + + /** Is suggestion enabled for field name typo */ + private final boolean isFieldSuggestionEnabled; + + /** Skip entire analysis for index mapping larger than this threhold */ + private final int analysisThreshold; + + public SqlAnalysisConfig( + boolean isAnalyzerEnabled, boolean isFieldSuggestionEnabled, int analysisThreshold) { + this.isAnalyzerEnabled = isAnalyzerEnabled; + this.isFieldSuggestionEnabled = isFieldSuggestionEnabled; + this.analysisThreshold = analysisThreshold; + } + + public boolean isAnalyzerEnabled() { + return isAnalyzerEnabled; + } + + public boolean isFieldSuggestionEnabled() { + return isFieldSuggestionEnabled; + } + + public int getAnalysisThreshold() { + return analysisThreshold; + } + + @Override + public String toString() { + return "SqlAnalysisConfig{" + + "isAnalyzerEnabled=" + + isAnalyzerEnabled + + ", isFieldSuggestionEnabled=" + + isFieldSuggestionEnabled + + ", analysisThreshold=" + + analysisThreshold + + '}'; + } } diff --git a/legacy/src/main/java/org/opensearch/sql/legacy/antlr/SqlAnalysisException.java b/legacy/src/main/java/org/opensearch/sql/legacy/antlr/SqlAnalysisException.java index 1856d568a2..b1d1204f21 100644 --- a/legacy/src/main/java/org/opensearch/sql/legacy/antlr/SqlAnalysisException.java +++ b/legacy/src/main/java/org/opensearch/sql/legacy/antlr/SqlAnalysisException.java @@ -3,15 +3,12 @@ * SPDX-License-Identifier: Apache-2.0 */ - package org.opensearch.sql.legacy.antlr; -/** - * SQL query analysis abstract exception. - */ +/** SQL query analysis abstract exception. */ public class SqlAnalysisException extends RuntimeException { - public SqlAnalysisException(String message) { - super(message); - } + public SqlAnalysisException(String message) { + super(message); + } } diff --git a/legacy/src/main/java/org/opensearch/sql/legacy/antlr/semantic/SemanticAnalysisException.java b/legacy/src/main/java/org/opensearch/sql/legacy/antlr/semantic/SemanticAnalysisException.java index 742642fb42..45c2dbc1dc 100644 --- a/legacy/src/main/java/org/opensearch/sql/legacy/antlr/semantic/SemanticAnalysisException.java +++ b/legacy/src/main/java/org/opensearch/sql/legacy/antlr/semantic/SemanticAnalysisException.java @@ -3,18 +3,14 @@ * SPDX-License-Identifier: Apache-2.0 */ - package org.opensearch.sql.legacy.antlr.semantic; import org.opensearch.sql.legacy.antlr.SqlAnalysisException; -/** - * Exception for semantic analysis - */ +/** Exception for semantic analysis */ public class SemanticAnalysisException extends SqlAnalysisException { - public SemanticAnalysisException(String message) { - super(message); - } - + public SemanticAnalysisException(String message) { + super(message); + } } diff --git a/legacy/src/main/java/org/opensearch/sql/legacy/antlr/semantic/scope/SemanticContext.java b/legacy/src/main/java/org/opensearch/sql/legacy/antlr/semantic/scope/SemanticContext.java index 968aff0df2..73fa5d1655 100644 --- a/legacy/src/main/java/org/opensearch/sql/legacy/antlr/semantic/scope/SemanticContext.java +++ b/legacy/src/main/java/org/opensearch/sql/legacy/antlr/semantic/scope/SemanticContext.java @@ -3,46 +3,44 @@ * SPDX-License-Identifier: Apache-2.0 */ - package org.opensearch.sql.legacy.antlr.semantic.scope; import java.util.Objects; /** - * Semantic context responsible for environment chain (stack) management and everything required for analysis. - * This context should be shared by different stages in future, particularly - * from semantic analysis to logical planning to physical planning. + * Semantic context responsible for environment chain (stack) management and everything required for + * analysis. This context should be shared by different stages in future, particularly from semantic + * analysis to logical planning to physical planning. */ public class SemanticContext { - /** Environment stack for symbol scope management */ - private Environment environment = new Environment(null); - - /** - * Push a new environment - */ - public void push() { - environment = new Environment(environment); - } - - /** - * Return current environment - * @return current environment - */ - public Environment peek() { - return environment; - } - - /** - * Pop up current environment from environment chain - * @return current environment (before pop) - */ - public Environment pop() { - Objects.requireNonNull(environment, "Fail to pop context due to no environment present"); - - Environment curEnv = environment; - environment = curEnv.getParent(); - return curEnv; - } - + /** Environment stack for symbol scope management */ + private Environment environment = new Environment(null); + + /** Push a new environment */ + public void push() { + environment = new Environment(environment); + } + + /** + * Return current environment + * + * @return current environment + */ + public Environment peek() { + return environment; + } + + /** + * Pop up current environment from environment chain + * + * @return current environment (before pop) + */ + public Environment pop() { + Objects.requireNonNull(environment, "Fail to pop context due to no environment present"); + + Environment curEnv = environment; + environment = curEnv.getParent(); + return curEnv; + } } diff --git a/legacy/src/main/java/org/opensearch/sql/legacy/antlr/semantic/scope/Symbol.java b/legacy/src/main/java/org/opensearch/sql/legacy/antlr/semantic/scope/Symbol.java index e9b6892e68..837baf1c00 100644 --- a/legacy/src/main/java/org/opensearch/sql/legacy/antlr/semantic/scope/Symbol.java +++ b/legacy/src/main/java/org/opensearch/sql/legacy/antlr/semantic/scope/Symbol.java @@ -3,34 +3,30 @@ * SPDX-License-Identifier: Apache-2.0 */ - package org.opensearch.sql.legacy.antlr.semantic.scope; -/** - * Symbol in the scope - */ +/** Symbol in the scope */ public class Symbol { - private final Namespace namespace; - - private final String name; + private final Namespace namespace; - public Symbol(Namespace namespace, String name) { - this.namespace = namespace; - this.name = name; - } + private final String name; - public Namespace getNamespace() { - return namespace; - } + public Symbol(Namespace namespace, String name) { + this.namespace = namespace; + this.name = name; + } - public String getName() { - return name; - } + public Namespace getNamespace() { + return namespace; + } - @Override - public String toString() { - return namespace + " [" + name + "]"; - } + public String getName() { + return name; + } + @Override + public String toString() { + return namespace + " [" + name + "]"; + } } diff --git a/legacy/src/main/java/org/opensearch/sql/legacy/antlr/semantic/scope/SymbolTable.java b/legacy/src/main/java/org/opensearch/sql/legacy/antlr/semantic/scope/SymbolTable.java index a8f0174c25..ee9f4545a6 100644 --- a/legacy/src/main/java/org/opensearch/sql/legacy/antlr/semantic/scope/SymbolTable.java +++ b/legacy/src/main/java/org/opensearch/sql/legacy/antlr/semantic/scope/SymbolTable.java @@ -3,7 +3,6 @@ * SPDX-License-Identifier: Apache-2.0 */ - package org.opensearch.sql.legacy.antlr.semantic.scope; import static java.util.Collections.emptyMap; @@ -17,79 +16,79 @@ import java.util.stream.Collectors; import org.opensearch.sql.legacy.antlr.semantic.types.Type; -/** - * Symbol table for symbol definition and resolution. - */ +/** Symbol table for symbol definition and resolution. */ public class SymbolTable { - /** - * Two-dimension hash table to manage symbols with type in different namespace - */ - private Map> tableByNamespace = new EnumMap<>(Namespace.class); + /** Two-dimension hash table to manage symbols with type in different namespace */ + private Map> tableByNamespace = + new EnumMap<>(Namespace.class); - /** - * Store symbol with the type. Create new map for namespace for the first time. - * @param symbol symbol to define - * @param type symbol type - */ - public void store(Symbol symbol, Type type) { - tableByNamespace.computeIfAbsent( - symbol.getNamespace(), - ns -> new TreeMap<>() - ).computeIfAbsent( - symbol.getName(), - symbolName -> new TypeSupplier(symbolName, type) - ).add(type); - } + /** + * Store symbol with the type. Create new map for namespace for the first time. + * + * @param symbol symbol to define + * @param type symbol type + */ + public void store(Symbol symbol, Type type) { + tableByNamespace + .computeIfAbsent(symbol.getNamespace(), ns -> new TreeMap<>()) + .computeIfAbsent(symbol.getName(), symbolName -> new TypeSupplier(symbolName, type)) + .add(type); + } - /** - * Look up symbol in the namespace map. - * @param symbol symbol to look up - * @return symbol type which is optional - */ - public Optional lookup(Symbol symbol) { - Map table = tableByNamespace.get(symbol.getNamespace()); - TypeSupplier typeSupplier = null; - if (table != null) { - typeSupplier = table.get(symbol.getName()); - } - return Optional.ofNullable(typeSupplier).map(TypeSupplier::get); + /** + * Look up symbol in the namespace map. + * + * @param symbol symbol to look up + * @return symbol type which is optional + */ + public Optional lookup(Symbol symbol) { + Map table = tableByNamespace.get(symbol.getNamespace()); + TypeSupplier typeSupplier = null; + if (table != null) { + typeSupplier = table.get(symbol.getName()); } + return Optional.ofNullable(typeSupplier).map(TypeSupplier::get); + } - /** - * Look up symbols by a prefix. - * @param prefix a symbol prefix - * @return symbols starting with the prefix - */ - public Map lookupByPrefix(Symbol prefix) { - NavigableMap table = tableByNamespace.get(prefix.getNamespace()); - if (table != null) { - return table.subMap(prefix.getName(), prefix.getName() + Character.MAX_VALUE) - .entrySet().stream() - .filter(entry -> null != entry.getValue().get()) - .collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().get())); - } - return emptyMap(); + /** + * Look up symbols by a prefix. + * + * @param prefix a symbol prefix + * @return symbols starting with the prefix + */ + public Map lookupByPrefix(Symbol prefix) { + NavigableMap table = tableByNamespace.get(prefix.getNamespace()); + if (table != null) { + return table + .subMap(prefix.getName(), prefix.getName() + Character.MAX_VALUE) + .entrySet() + .stream() + .filter(entry -> null != entry.getValue().get()) + .collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().get())); } + return emptyMap(); + } - /** - * Look up all symbols in the namespace. - * @param namespace a namespace - * @return all symbols in the namespace map - */ - public Map lookupAll(Namespace namespace) { - return tableByNamespace.getOrDefault(namespace, emptyNavigableMap()) - .entrySet().stream() - .filter(entry -> null != entry.getValue().get()) - .collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().get())); - } + /** + * Look up all symbols in the namespace. + * + * @param namespace a namespace + * @return all symbols in the namespace map + */ + public Map lookupAll(Namespace namespace) { + return tableByNamespace.getOrDefault(namespace, emptyNavigableMap()).entrySet().stream() + .filter(entry -> null != entry.getValue().get()) + .collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().get())); + } - /** - * Check if namespace map in empty (none definition) - * @param namespace a namespace - * @return true for empty - */ - public boolean isEmpty(Namespace namespace) { - return tableByNamespace.getOrDefault(namespace, emptyNavigableMap()).isEmpty(); - } + /** + * Check if namespace map in empty (none definition) + * + * @param namespace a namespace + * @return true for empty + */ + public boolean isEmpty(Namespace namespace) { + return tableByNamespace.getOrDefault(namespace, emptyNavigableMap()).isEmpty(); + } } diff --git a/legacy/src/main/java/org/opensearch/sql/legacy/antlr/semantic/scope/TypeSupplier.java b/legacy/src/main/java/org/opensearch/sql/legacy/antlr/semantic/scope/TypeSupplier.java index 355ae70249..7c2410cf76 100644 --- a/legacy/src/main/java/org/opensearch/sql/legacy/antlr/semantic/scope/TypeSupplier.java +++ b/legacy/src/main/java/org/opensearch/sql/legacy/antlr/semantic/scope/TypeSupplier.java @@ -3,7 +3,6 @@ * SPDX-License-Identifier: Apache-2.0 */ - package org.opensearch.sql.legacy.antlr.semantic.scope; import java.util.HashSet; @@ -13,39 +12,38 @@ import org.opensearch.sql.legacy.antlr.semantic.types.Type; /** - * The TypeSupplier is construct by the symbolName and symbolType. - * The TypeSupplier implement the {@link Supplier} interface to provide the {@link Type}. - * The TypeSupplier maintain types to track different {@link Type} definition for the same symbolName. + * The TypeSupplier is construct by the symbolName and symbolType. The TypeSupplier implement the + * {@link Supplier} interface to provide the {@link Type}. The TypeSupplier maintain types to + * track different {@link Type} definition for the same symbolName. */ public class TypeSupplier implements Supplier { - private final String symbolName; - private final Type symbolType; - private final Set types; + private final String symbolName; + private final Type symbolType; + private final Set types; - public TypeSupplier(String symbolName, Type symbolType) { - this.symbolName = symbolName; - this.symbolType = symbolType; - this.types = new HashSet<>(); - this.types.add(symbolType); - } + public TypeSupplier(String symbolName, Type symbolType) { + this.symbolName = symbolName; + this.symbolType = symbolType; + this.types = new HashSet<>(); + this.types.add(symbolType); + } - public TypeSupplier add(Type type) { - types.add(type); - return this; - } + public TypeSupplier add(Type type) { + types.add(type); + return this; + } - /** - * Get the {@link Type} - * Throw {@link SemanticAnalysisException} if conflict found. - * Currently, if the two types not equal, they are treated as conflicting. - */ - @Override - public Type get() { - if (types.size() > 1) { - throw new SemanticAnalysisException( - String.format("Field [%s] have conflict type [%s]", symbolName, types)); - } else { - return symbolType; - } + /** + * Get the {@link Type} Throw {@link SemanticAnalysisException} if conflict found. Currently, if + * the two types not equal, they are treated as conflicting. + */ + @Override + public Type get() { + if (types.size() > 1) { + throw new SemanticAnalysisException( + String.format("Field [%s] have conflict type [%s]", symbolName, types)); + } else { + return symbolType; } + } } diff --git a/legacy/src/main/java/org/opensearch/sql/legacy/antlr/semantic/types/Type.java b/legacy/src/main/java/org/opensearch/sql/legacy/antlr/semantic/types/Type.java index 0491c4e568..539e3478d2 100644 --- a/legacy/src/main/java/org/opensearch/sql/legacy/antlr/semantic/types/Type.java +++ b/legacy/src/main/java/org/opensearch/sql/legacy/antlr/semantic/types/Type.java @@ -3,7 +3,6 @@ * SPDX-License-Identifier: Apache-2.0 */ - package org.opensearch.sql.legacy.antlr.semantic.types; import static org.opensearch.sql.legacy.antlr.semantic.types.base.OpenSearchDataType.TYPE_ERROR; @@ -15,66 +14,64 @@ import org.opensearch.sql.legacy.antlr.visitor.Reducible; import org.opensearch.sql.legacy.utils.StringUtils; -/** - * Type interface which represents any type of symbol in the SQL. - */ +/** Type interface which represents any type of symbol in the SQL. */ public interface Type extends Reducible { - /** - * Hide generic type ugliness and error check here in one place. - */ - @SuppressWarnings("unchecked") - @Override - default T reduce(List others) { - List actualArgTypes = (List) others; - Type result = construct(actualArgTypes); - if (result != TYPE_ERROR) { - return (T) result; - } - - // Generate error message by current type name, argument types and usage of current type - // For example, 'Function [LOG] cannot work with [TEXT, INTEGER]. Usage: LOG(NUMBER) -> NUMBER - String actualArgTypesStr; - if (actualArgTypes.isEmpty()) { - actualArgTypesStr = ""; - } else { - actualArgTypesStr = actualArgTypes.stream(). - map(Type::usage). - collect(Collectors.joining(", ")); - } + /** Hide generic type ugliness and error check here in one place. */ + @SuppressWarnings("unchecked") + @Override + default T reduce(List others) { + List actualArgTypes = (List) others; + Type result = construct(actualArgTypes); + if (result != TYPE_ERROR) { + return (T) result; + } - throw new SemanticAnalysisException( - StringUtils.format("%s cannot work with [%s]. Usage: %s", - this, actualArgTypesStr, usage())); + // Generate error message by current type name, argument types and usage of current type + // For example, 'Function [LOG] cannot work with [TEXT, INTEGER]. Usage: LOG(NUMBER) -> NUMBER + String actualArgTypesStr; + if (actualArgTypes.isEmpty()) { + actualArgTypesStr = ""; + } else { + actualArgTypesStr = + actualArgTypes.stream().map(Type::usage).collect(Collectors.joining(", ")); } - /** - * Type descriptive name - * @return name - */ - String getName(); + throw new SemanticAnalysisException( + StringUtils.format( + "%s cannot work with [%s]. Usage: %s", this, actualArgTypesStr, usage())); + } - /** - * Check if current type is compatible with other of same type. - * @param other other type - * @return true if compatible - */ - default boolean isCompatible(Type other) { - return other == UNKNOWN || this == other; - } + /** + * Type descriptive name + * + * @return name + */ + String getName(); + + /** + * Check if current type is compatible with other of same type. + * + * @param other other type + * @return true if compatible + */ + default boolean isCompatible(Type other) { + return other == UNKNOWN || this == other; + } - /** - * Construct a new type by applying current constructor on other types. - * Constructor is a generic conception that could be function, operator, join etc. - * - * @param others other types - * @return a new type as result - */ - Type construct(List others); + /** + * Construct a new type by applying current constructor on other types. Constructor is a generic + * conception that could be function, operator, join etc. + * + * @param others other types + * @return a new type as result + */ + Type construct(List others); - /** - * Return typical usage of current type - * @return usage string - */ - String usage(); + /** + * Return typical usage of current type + * + * @return usage string + */ + String usage(); } diff --git a/legacy/src/main/java/org/opensearch/sql/legacy/antlr/semantic/types/TypeExpression.java b/legacy/src/main/java/org/opensearch/sql/legacy/antlr/semantic/types/TypeExpression.java index eacca7b00d..5a9d4d7410 100644 --- a/legacy/src/main/java/org/opensearch/sql/legacy/antlr/semantic/types/TypeExpression.java +++ b/legacy/src/main/java/org/opensearch/sql/legacy/antlr/semantic/types/TypeExpression.java @@ -3,7 +3,6 @@ * SPDX-License-Identifier: Apache-2.0 */ - package org.opensearch.sql.legacy.antlr.semantic.types; import static org.opensearch.sql.legacy.antlr.semantic.types.base.OpenSearchDataType.TYPE_ERROR; @@ -17,105 +16,107 @@ import org.opensearch.sql.legacy.utils.StringUtils; /** - * Type expression representing specification(s) of constructor such as function, operator etc. - * Type expression has to be an interface with default methods because most subclass needs to be Enum. + * Type expression representing specification(s) of constructor such as function, operator etc. Type + * expression has to be an interface with default methods because most subclass needs to be Enum. */ public interface TypeExpression extends Type { - @Override - default Type construct(List actualArgs) { - TypeExpressionSpec[] specifications = specifications(); - if (specifications.length == 0) { - // Empty spec means type check for this type expression is not implemented yet. - // Return this to be compatible with everything. - return UNKNOWN; - } + @Override + default Type construct(List actualArgs) { + TypeExpressionSpec[] specifications = specifications(); + if (specifications.length == 0) { + // Empty spec means type check for this type expression is not implemented yet. + // Return this to be compatible with everything. + return UNKNOWN; + } - // Create a temp specification for compatibility check. - TypeExpressionSpec actualSpec = new TypeExpressionSpec(); - actualSpec.argTypes = actualArgs.toArray(new Type[0]); - - // Perform compatibility check between actual spec (argument types) and expected. - // If found any compatible spec, it means actual spec is legal and thus apply to get result type. - // Ex. Actual=[INTEGER], Specs=[NUMBER->NUMBER], [STRING->NUMBER]. So first spec matches and return NUMBER. - for (TypeExpressionSpec spec : specifications) { - if (spec.isCompatible(actualSpec)) { - return spec.constructFunc.apply(actualArgs.toArray(new Type[0])); - } - } - return TYPE_ERROR; + // Create a temp specification for compatibility check. + TypeExpressionSpec actualSpec = new TypeExpressionSpec(); + actualSpec.argTypes = actualArgs.toArray(new Type[0]); + + // Perform compatibility check between actual spec (argument types) and expected. + // If found any compatible spec, it means actual spec is legal and thus apply to get result + // type. + // Ex. Actual=[INTEGER], Specs=[NUMBER->NUMBER], [STRING->NUMBER]. So first spec matches and + // return NUMBER. + for (TypeExpressionSpec spec : specifications) { + if (spec.isCompatible(actualSpec)) { + return spec.constructFunc.apply(actualArgs.toArray(new Type[0])); + } + } + return TYPE_ERROR; + } + + @Override + default String usage() { + return Arrays.stream(specifications()) + .map(spec -> getName() + spec) + .collect(Collectors.joining(" or ")); + } + + /** + * Each type expression may be overloaded and include multiple specifications. + * + * @return all valid specifications or empty which means not implemented yet + */ + TypeExpressionSpec[] specifications(); + + /** + * A specification is combination of a construct function and arg types for a type expression + * (represent a constructor) + */ + class TypeExpressionSpec { + Type[] argTypes; + Function constructFunc; + + public TypeExpressionSpec map(Type... args) { + this.argTypes = args; + return this; } - @Override - default String usage() { - return Arrays.stream(specifications()). - map(spec -> getName() + spec). - collect(Collectors.joining(" or ")); + public TypeExpressionSpec to(Function constructFunc) { + // Required for generic type to replace placeholder ex.T with actual position in argument + // list. + // So construct function of generic type can return binding type finally. + this.constructFunc = Generic.specialize(constructFunc, argTypes); + return this; } - /** - * Each type expression may be overloaded and include multiple specifications. - * @return all valid specifications or empty which means not implemented yet - */ - TypeExpressionSpec[] specifications(); - - /** - * A specification is combination of a construct function and arg types - * for a type expression (represent a constructor) - */ - class TypeExpressionSpec { - Type[] argTypes; - Function constructFunc; - - public TypeExpressionSpec map(Type... args) { - this.argTypes = args; - return this; - } + /** Return a base type no matter what's the arg types Mostly this is used for empty arg types */ + public TypeExpressionSpec to(Type returnType) { + this.constructFunc = x -> returnType; + return this; + } - public TypeExpressionSpec to(Function constructFunc) { - // Required for generic type to replace placeholder ex.T with actual position in argument list. - // So construct function of generic type can return binding type finally. - this.constructFunc = Generic.specialize(constructFunc, argTypes); - return this; - } + public boolean isCompatible(TypeExpressionSpec otherSpec) { + Type[] expectArgTypes = this.argTypes; + Type[] actualArgTypes = otherSpec.argTypes; - /** Return a base type no matter what's the arg types - Mostly this is used for empty arg types */ - public TypeExpressionSpec to(Type returnType) { - this.constructFunc = x -> returnType; - return this; - } + // Check if arg numbers exactly match + if (expectArgTypes.length != actualArgTypes.length) { + return false; + } - public boolean isCompatible(TypeExpressionSpec otherSpec) { - Type[] expectArgTypes = this.argTypes; - Type[] actualArgTypes = otherSpec.argTypes; - - // Check if arg numbers exactly match - if (expectArgTypes.length != actualArgTypes.length) { - return false; - } - - // Check if all arg types are compatible - for (int i = 0; i < expectArgTypes.length; i++) { - if (!expectArgTypes[i].isCompatible(actualArgTypes[i])) { - return false; - } - } - return true; + // Check if all arg types are compatible + for (int i = 0; i < expectArgTypes.length; i++) { + if (!expectArgTypes[i].isCompatible(actualArgTypes[i])) { + return false; } + } + return true; + } - @Override - public String toString() { - String argTypesStr = Arrays.stream(argTypes). - map(Type::usage). - collect(Collectors.joining(", ")); + @Override + public String toString() { + String argTypesStr = + Arrays.stream(argTypes).map(Type::usage).collect(Collectors.joining(", ")); - // Only show generic type name in return value for clarity - Type returnType = constructFunc.apply(argTypes); - String returnTypeStr = (returnType instanceof Generic) ? returnType.getName() : returnType.usage(); + // Only show generic type name in return value for clarity + Type returnType = constructFunc.apply(argTypes); + String returnTypeStr = + (returnType instanceof Generic) ? returnType.getName() : returnType.usage(); - return StringUtils.format("(%s) -> %s", argTypesStr, returnTypeStr); - } + return StringUtils.format("(%s) -> %s", argTypesStr, returnTypeStr); } - + } } diff --git a/legacy/src/main/java/org/opensearch/sql/legacy/antlr/semantic/types/operator/SetOperator.java b/legacy/src/main/java/org/opensearch/sql/legacy/antlr/semantic/types/operator/SetOperator.java index 988c9856e3..e8a80cd821 100644 --- a/legacy/src/main/java/org/opensearch/sql/legacy/antlr/semantic/types/operator/SetOperator.java +++ b/legacy/src/main/java/org/opensearch/sql/legacy/antlr/semantic/types/operator/SetOperator.java @@ -3,7 +3,6 @@ * SPDX-License-Identifier: Apache-2.0 */ - package org.opensearch.sql.legacy.antlr.semantic.types.operator; import static org.opensearch.sql.legacy.antlr.semantic.types.base.OpenSearchDataType.TYPE_ERROR; @@ -11,45 +10,43 @@ import java.util.List; import org.opensearch.sql.legacy.antlr.semantic.types.Type; -/** - * Set operator between queries. - */ +/** Set operator between queries. */ public enum SetOperator implements Type { - UNION, - MINUS, - IN; - - @Override - public String getName() { - return name(); + UNION, + MINUS, + IN; + + @Override + public String getName() { + return name(); + } + + @Override + public Type construct(List others) { + if (others.size() < 2) { + throw new IllegalStateException(""); } - @Override - public Type construct(List others) { - if (others.size() < 2) { - throw new IllegalStateException(""); - } - - // Compare each type and return anyone for now if pass - for (int i = 0; i < others.size() - 1; i++) { - Type type1 = others.get(i); - Type type2 = others.get(i + 1); - - // Do it again as in Product because single base type won't be wrapped in Product - if (!type1.isCompatible(type2) && !type2.isCompatible(type1)) { - return TYPE_ERROR; - } - } - return others.get(0); - } - - @Override - public String usage() { - return "Please return field(s) of compatible type from each query."; - } + // Compare each type and return anyone for now if pass + for (int i = 0; i < others.size() - 1; i++) { + Type type1 = others.get(i); + Type type2 = others.get(i + 1); - @Override - public String toString() { - return "Operator [" + getName() + "]"; + // Do it again as in Product because single base type won't be wrapped in Product + if (!type1.isCompatible(type2) && !type2.isCompatible(type1)) { + return TYPE_ERROR; + } } + return others.get(0); + } + + @Override + public String usage() { + return "Please return field(s) of compatible type from each query."; + } + + @Override + public String toString() { + return "Operator [" + getName() + "]"; + } } diff --git a/legacy/src/main/java/org/opensearch/sql/legacy/antlr/semantic/visitor/SemanticAnalyzer.java b/legacy/src/main/java/org/opensearch/sql/legacy/antlr/semantic/visitor/SemanticAnalyzer.java index 32bad91737..0655062be3 100644 --- a/legacy/src/main/java/org/opensearch/sql/legacy/antlr/semantic/visitor/SemanticAnalyzer.java +++ b/legacy/src/main/java/org/opensearch/sql/legacy/antlr/semantic/visitor/SemanticAnalyzer.java @@ -3,7 +3,6 @@ * SPDX-License-Identifier: Apache-2.0 */ - package org.opensearch.sql.legacy.antlr.semantic.visitor; import static org.opensearch.sql.legacy.utils.StringUtils.unquoteFullColumn; @@ -13,125 +12,123 @@ import org.opensearch.sql.legacy.antlr.semantic.types.Type; import org.opensearch.sql.legacy.antlr.visitor.GenericSqlParseTreeVisitor; -/** - * Main visitor implementation to drive the entire semantic analysis. - */ +/** Main visitor implementation to drive the entire semantic analysis. */ public class SemanticAnalyzer implements GenericSqlParseTreeVisitor { - private final OpenSearchMappingLoader mappingLoader; - - private final TypeChecker typeChecker; - - public SemanticAnalyzer(OpenSearchMappingLoader mappingLoader, TypeChecker typeChecker) { - this.mappingLoader = mappingLoader; - this.typeChecker = typeChecker; - } - - @Override - public void visitRoot() { - mappingLoader.visitRoot(); - typeChecker.visitRoot(); - } - - @Override - public void visitQuery() { - mappingLoader.visitQuery(); - typeChecker.visitQuery(); - } - - @Override - public void endVisitQuery() { - mappingLoader.endVisitQuery(); - typeChecker.endVisitQuery(); - } - - @Override - public Type visitSelect(List itemTypes) { - mappingLoader.visitSelect(itemTypes); - return typeChecker.visitSelect(itemTypes); - } - - @Override - public Type visitSelectAllColumn() { - mappingLoader.visitSelectAllColumn(); - return typeChecker.visitSelectAllColumn(); - } - - @Override - public void visitAs(String alias, Type type) { - mappingLoader.visitAs(unquoteSingleField(alias), type); - typeChecker.visitAs(unquoteSingleField(alias), type); - } - - @Override - public Type visitIndexName(String indexName) { - mappingLoader.visitIndexName(unquoteSingleField(indexName)); - return typeChecker.visitIndexName(unquoteSingleField(indexName)); - } - - @Override - public Type visitFieldName(String fieldName) { - mappingLoader.visitFieldName(unquoteFullColumn(fieldName)); - return typeChecker.visitFieldName(unquoteFullColumn(fieldName)); - } - - @Override - public Type visitFunctionName(String funcName) { - mappingLoader.visitFunctionName(funcName); - return typeChecker.visitFunctionName(funcName); - } - - @Override - public Type visitOperator(String opName) { - mappingLoader.visitOperator(opName); - return typeChecker.visitOperator(opName); - } - - @Override - public Type visitString(String text) { - mappingLoader.visitString(text); - return typeChecker.visitString(text); - } - - @Override - public Type visitInteger(String text) { - mappingLoader.visitInteger(text); - return typeChecker.visitInteger(text); - } - - @Override - public Type visitFloat(String text) { - mappingLoader.visitFloat(text); - return typeChecker.visitFloat(text); - } - - @Override - public Type visitBoolean(String text) { - mappingLoader.visitBoolean(text); - return typeChecker.visitBoolean(text); - } - - @Override - public Type visitDate(String text) { - mappingLoader.visitDate(text); - return typeChecker.visitDate(text); - } - - @Override - public Type visitNull() { - mappingLoader.visitNull(); - return typeChecker.visitNull(); - } - - @Override - public Type visitConvertedType(String text) { - mappingLoader.visitConvertedType(text); - return typeChecker.visitConvertedType(text); - } - - @Override - public Type defaultValue() { - mappingLoader.defaultValue(); - return typeChecker.defaultValue(); - } + private final OpenSearchMappingLoader mappingLoader; + + private final TypeChecker typeChecker; + + public SemanticAnalyzer(OpenSearchMappingLoader mappingLoader, TypeChecker typeChecker) { + this.mappingLoader = mappingLoader; + this.typeChecker = typeChecker; + } + + @Override + public void visitRoot() { + mappingLoader.visitRoot(); + typeChecker.visitRoot(); + } + + @Override + public void visitQuery() { + mappingLoader.visitQuery(); + typeChecker.visitQuery(); + } + + @Override + public void endVisitQuery() { + mappingLoader.endVisitQuery(); + typeChecker.endVisitQuery(); + } + + @Override + public Type visitSelect(List itemTypes) { + mappingLoader.visitSelect(itemTypes); + return typeChecker.visitSelect(itemTypes); + } + + @Override + public Type visitSelectAllColumn() { + mappingLoader.visitSelectAllColumn(); + return typeChecker.visitSelectAllColumn(); + } + + @Override + public void visitAs(String alias, Type type) { + mappingLoader.visitAs(unquoteSingleField(alias), type); + typeChecker.visitAs(unquoteSingleField(alias), type); + } + + @Override + public Type visitIndexName(String indexName) { + mappingLoader.visitIndexName(unquoteSingleField(indexName)); + return typeChecker.visitIndexName(unquoteSingleField(indexName)); + } + + @Override + public Type visitFieldName(String fieldName) { + mappingLoader.visitFieldName(unquoteFullColumn(fieldName)); + return typeChecker.visitFieldName(unquoteFullColumn(fieldName)); + } + + @Override + public Type visitFunctionName(String funcName) { + mappingLoader.visitFunctionName(funcName); + return typeChecker.visitFunctionName(funcName); + } + + @Override + public Type visitOperator(String opName) { + mappingLoader.visitOperator(opName); + return typeChecker.visitOperator(opName); + } + + @Override + public Type visitString(String text) { + mappingLoader.visitString(text); + return typeChecker.visitString(text); + } + + @Override + public Type visitInteger(String text) { + mappingLoader.visitInteger(text); + return typeChecker.visitInteger(text); + } + + @Override + public Type visitFloat(String text) { + mappingLoader.visitFloat(text); + return typeChecker.visitFloat(text); + } + + @Override + public Type visitBoolean(String text) { + mappingLoader.visitBoolean(text); + return typeChecker.visitBoolean(text); + } + + @Override + public Type visitDate(String text) { + mappingLoader.visitDate(text); + return typeChecker.visitDate(text); + } + + @Override + public Type visitNull() { + mappingLoader.visitNull(); + return typeChecker.visitNull(); + } + + @Override + public Type visitConvertedType(String text) { + mappingLoader.visitConvertedType(text); + return typeChecker.visitConvertedType(text); + } + + @Override + public Type defaultValue() { + mappingLoader.defaultValue(); + return typeChecker.defaultValue(); + } } diff --git a/legacy/src/main/java/org/opensearch/sql/legacy/antlr/semantic/visitor/TypeChecker.java b/legacy/src/main/java/org/opensearch/sql/legacy/antlr/semantic/visitor/TypeChecker.java index 59c0036575..19119c776c 100644 --- a/legacy/src/main/java/org/opensearch/sql/legacy/antlr/semantic/visitor/TypeChecker.java +++ b/legacy/src/main/java/org/opensearch/sql/legacy/antlr/semantic/visitor/TypeChecker.java @@ -3,7 +3,6 @@ * SPDX-License-Identifier: Apache-2.0 */ - package org.opensearch.sql.legacy.antlr.semantic.visitor; import static org.opensearch.sql.legacy.antlr.semantic.types.base.OpenSearchDataType.UNKNOWN; @@ -31,198 +30,196 @@ import org.opensearch.sql.legacy.antlr.visitor.GenericSqlParseTreeVisitor; import org.opensearch.sql.legacy.utils.StringUtils; -/** - * SQL semantic analyzer that determines if a syntactical correct query is meaningful. - */ +/** SQL semantic analyzer that determines if a syntactical correct query is meaningful. */ public class TypeChecker implements GenericSqlParseTreeVisitor { - private static final Type NULL_TYPE = new Type() { + private static final Type NULL_TYPE = + new Type() { @Override public String getName() { - return "NULL"; + return "NULL"; } @Override public boolean isCompatible(Type other) { - throw new IllegalStateException("Compatibility check on NULL type with " + other); + throw new IllegalStateException("Compatibility check on NULL type with " + other); } @Override public Type construct(List others) { - throw new IllegalStateException("Construct operation on NULL type with " + others); + throw new IllegalStateException("Construct operation on NULL type with " + others); } @Override public String usage() { - throw new IllegalStateException("Usage print operation on NULL type"); - } - }; - - /** Semantic context for symbol scope management */ - private final SemanticContext context; - - /** Should suggestion provided. Disabled by default for security concern. */ - private final boolean isSuggestEnabled; - - public TypeChecker(SemanticContext context) { - this.context = context; - this.isSuggestEnabled = false; - } - - public TypeChecker(SemanticContext context, boolean isSuggestEnabled) { - this.context = context; - this.isSuggestEnabled = isSuggestEnabled; - } - - @Override - public void visitRoot() { - defineFunctionNames(ScalarFunction.values()); - defineFunctionNames(OpenSearchScalarFunction.values()); - defineFunctionNames(AggregateFunction.values()); - defineOperatorNames(ComparisonOperator.values()); - defineOperatorNames(SetOperator.values()); - defineOperatorNames(JoinOperator.values()); - } - - @Override - public void visitQuery() { - context.push(); - } - - @Override - public void endVisitQuery() { - context.pop(); - } - - @Override - public Type visitSelect(List itemTypes) { - if (itemTypes.size() == 1) { - return itemTypes.get(0); - } else if (itemTypes.size() == 0) { - return visitSelectAllColumn(); - } - // Return product for empty (SELECT *) and #items > 1 - return new Product(itemTypes); - } - - @Override - public Type visitSelectAllColumn() { - return resolveAllColumn(); - } - - @Override - public void visitAs(String alias, Type type) { - defineFieldName(alias, type); - } - - @Override - public Type visitIndexName(String indexName) { - return resolve(new Symbol(Namespace.FIELD_NAME, indexName)); - } - - @Override - public Type visitFieldName(String fieldName) { - // Bypass hidden fields which is not present in mapping, ex. _id, _type. - if (fieldName.startsWith("_")) { - return UNKNOWN; - } - // Ignore case for function/operator though field name is case sensitive - return resolve(new Symbol(Namespace.FIELD_NAME, fieldName)); - } - - @Override - public Type visitFunctionName(String funcName) { - return resolve(new Symbol(Namespace.FUNCTION_NAME, StringUtils.toUpper(funcName))); - } - - @Override - public Type visitOperator(String opName) { - return resolve(new Symbol(Namespace.OPERATOR_NAME, StringUtils.toUpper(opName))); - } - - @Override - public Type visitString(String text) { - return OpenSearchDataType.STRING; - } - - @Override - public Type visitInteger(String text) { - return OpenSearchDataType.INTEGER; - } - - @Override - public Type visitFloat(String text) { - return OpenSearchDataType.FLOAT; - } - - @Override - public Type visitBoolean(String text) { - // "IS [NOT] MISSING" can be used on any data type - return "MISSING".equalsIgnoreCase(text) ? UNKNOWN : OpenSearchDataType.BOOLEAN; - } - - @Override - public Type visitDate(String text) { - return OpenSearchDataType.DATE; - } - - @Override - public Type visitNull() { - return UNKNOWN; - } - - @Override - public Type visitConvertedType(String text) { - return OpenSearchDataType.typeOf(text); - } - - @Override - public Type defaultValue() { - return NULL_TYPE; - } - - private void defineFieldName(String fieldName, Type type) { - Symbol symbol = new Symbol(Namespace.FIELD_NAME, fieldName); - if (!environment().resolve(symbol).isPresent()) { - environment().define(symbol, type); + throw new IllegalStateException("Usage print operation on NULL type"); } - } - - private void defineFunctionNames(TypeExpression[] expressions) { - for (TypeExpression expr : expressions) { - environment().define(new Symbol(Namespace.FUNCTION_NAME, expr.getName()), expr); - } - } - - private void defineOperatorNames(Type[] expressions) { - for (Type expr : expressions) { - environment().define(new Symbol(Namespace.OPERATOR_NAME, expr.getName()), expr); - } - } - - private Type resolve(Symbol symbol) { - Optional type = environment().resolve(symbol); - if (type.isPresent()) { - return type.get(); - } - - String errorMsg = StringUtils.format("%s cannot be found or used here.", symbol); - - if (isSuggestEnabled || symbol.getNamespace() != Namespace.FIELD_NAME) { - Set allSymbolsInScope = environment().resolveAll(symbol.getNamespace()).keySet(); - String suggestedWord = new SimilarSymbols(allSymbolsInScope).mostSimilarTo(symbol.getName()); - errorMsg += StringUtils.format(" Did you mean [%s]?", suggestedWord); - } - throw new SemanticAnalysisException(errorMsg); - } - - private Type resolveAllColumn() { - environment().resolveAll(Namespace.FIELD_NAME); - return new Product(ImmutableList.of()); - } - - private Environment environment() { - return context.peek(); - } - + }; + + /** Semantic context for symbol scope management */ + private final SemanticContext context; + + /** Should suggestion provided. Disabled by default for security concern. */ + private final boolean isSuggestEnabled; + + public TypeChecker(SemanticContext context) { + this.context = context; + this.isSuggestEnabled = false; + } + + public TypeChecker(SemanticContext context, boolean isSuggestEnabled) { + this.context = context; + this.isSuggestEnabled = isSuggestEnabled; + } + + @Override + public void visitRoot() { + defineFunctionNames(ScalarFunction.values()); + defineFunctionNames(OpenSearchScalarFunction.values()); + defineFunctionNames(AggregateFunction.values()); + defineOperatorNames(ComparisonOperator.values()); + defineOperatorNames(SetOperator.values()); + defineOperatorNames(JoinOperator.values()); + } + + @Override + public void visitQuery() { + context.push(); + } + + @Override + public void endVisitQuery() { + context.pop(); + } + + @Override + public Type visitSelect(List itemTypes) { + if (itemTypes.size() == 1) { + return itemTypes.get(0); + } else if (itemTypes.size() == 0) { + return visitSelectAllColumn(); + } + // Return product for empty (SELECT *) and #items > 1 + return new Product(itemTypes); + } + + @Override + public Type visitSelectAllColumn() { + return resolveAllColumn(); + } + + @Override + public void visitAs(String alias, Type type) { + defineFieldName(alias, type); + } + + @Override + public Type visitIndexName(String indexName) { + return resolve(new Symbol(Namespace.FIELD_NAME, indexName)); + } + + @Override + public Type visitFieldName(String fieldName) { + // Bypass hidden fields which is not present in mapping, ex. _id, _type. + if (fieldName.startsWith("_")) { + return UNKNOWN; + } + // Ignore case for function/operator though field name is case sensitive + return resolve(new Symbol(Namespace.FIELD_NAME, fieldName)); + } + + @Override + public Type visitFunctionName(String funcName) { + return resolve(new Symbol(Namespace.FUNCTION_NAME, StringUtils.toUpper(funcName))); + } + + @Override + public Type visitOperator(String opName) { + return resolve(new Symbol(Namespace.OPERATOR_NAME, StringUtils.toUpper(opName))); + } + + @Override + public Type visitString(String text) { + return OpenSearchDataType.STRING; + } + + @Override + public Type visitInteger(String text) { + return OpenSearchDataType.INTEGER; + } + + @Override + public Type visitFloat(String text) { + return OpenSearchDataType.FLOAT; + } + + @Override + public Type visitBoolean(String text) { + // "IS [NOT] MISSING" can be used on any data type + return "MISSING".equalsIgnoreCase(text) ? UNKNOWN : OpenSearchDataType.BOOLEAN; + } + + @Override + public Type visitDate(String text) { + return OpenSearchDataType.DATE; + } + + @Override + public Type visitNull() { + return UNKNOWN; + } + + @Override + public Type visitConvertedType(String text) { + return OpenSearchDataType.typeOf(text); + } + + @Override + public Type defaultValue() { + return NULL_TYPE; + } + + private void defineFieldName(String fieldName, Type type) { + Symbol symbol = new Symbol(Namespace.FIELD_NAME, fieldName); + if (!environment().resolve(symbol).isPresent()) { + environment().define(symbol, type); + } + } + + private void defineFunctionNames(TypeExpression[] expressions) { + for (TypeExpression expr : expressions) { + environment().define(new Symbol(Namespace.FUNCTION_NAME, expr.getName()), expr); + } + } + + private void defineOperatorNames(Type[] expressions) { + for (Type expr : expressions) { + environment().define(new Symbol(Namespace.OPERATOR_NAME, expr.getName()), expr); + } + } + + private Type resolve(Symbol symbol) { + Optional type = environment().resolve(symbol); + if (type.isPresent()) { + return type.get(); + } + + String errorMsg = StringUtils.format("%s cannot be found or used here.", symbol); + + if (isSuggestEnabled || symbol.getNamespace() != Namespace.FIELD_NAME) { + Set allSymbolsInScope = environment().resolveAll(symbol.getNamespace()).keySet(); + String suggestedWord = new SimilarSymbols(allSymbolsInScope).mostSimilarTo(symbol.getName()); + errorMsg += StringUtils.format(" Did you mean [%s]?", suggestedWord); + } + throw new SemanticAnalysisException(errorMsg); + } + + private Type resolveAllColumn() { + environment().resolveAll(Namespace.FIELD_NAME); + return new Product(ImmutableList.of()); + } + + private Environment environment() { + return context.peek(); + } } diff --git a/legacy/src/main/java/org/opensearch/sql/legacy/antlr/syntax/SyntaxAnalysisErrorListener.java b/legacy/src/main/java/org/opensearch/sql/legacy/antlr/syntax/SyntaxAnalysisErrorListener.java index 185f2696b7..5f0c7e38d1 100644 --- a/legacy/src/main/java/org/opensearch/sql/legacy/antlr/syntax/SyntaxAnalysisErrorListener.java +++ b/legacy/src/main/java/org/opensearch/sql/legacy/antlr/syntax/SyntaxAnalysisErrorListener.java @@ -3,7 +3,6 @@ * SPDX-License-Identifier: Apache-2.0 */ - package org.opensearch.sql.legacy.antlr.syntax; import org.antlr.v4.runtime.BaseErrorListener; @@ -15,50 +14,53 @@ import org.opensearch.sql.legacy.utils.StringUtils; /** - * Syntax analysis error listener that handles any syntax error by throwing exception with useful information. + * Syntax analysis error listener that handles any syntax error by throwing exception with useful + * information. */ public class SyntaxAnalysisErrorListener extends BaseErrorListener { - @Override - public void syntaxError(Recognizer recognizer, Object offendingSymbol, - int line, int charPositionInLine, String msg, - RecognitionException e) { + @Override + public void syntaxError( + Recognizer recognizer, + Object offendingSymbol, + int line, + int charPositionInLine, + String msg, + RecognitionException e) { - CommonTokenStream tokens = (CommonTokenStream) recognizer.getInputStream(); - Token offendingToken = (Token) offendingSymbol; - String query = tokens.getText(); + CommonTokenStream tokens = (CommonTokenStream) recognizer.getInputStream(); + Token offendingToken = (Token) offendingSymbol; + String query = tokens.getText(); - throw new SyntaxAnalysisException( - StringUtils.format( - "Failed to parse query due to offending symbol [%s] at: '%s' <--- HERE... More details: %s", - getOffendingText(offendingToken), - truncateQueryAtOffendingToken(query, offendingToken), - getDetails(recognizer, msg, e) - ) - ); - } + throw new SyntaxAnalysisException( + StringUtils.format( + "Failed to parse query due to offending symbol [%s] at: '%s' <--- HERE... More details:" + + " %s", + getOffendingText(offendingToken), + truncateQueryAtOffendingToken(query, offendingToken), + getDetails(recognizer, msg, e))); + } - private String getOffendingText(Token offendingToken) { - return offendingToken.getText(); - } + private String getOffendingText(Token offendingToken) { + return offendingToken.getText(); + } - private String truncateQueryAtOffendingToken(String query, Token offendingToken) { - return query.substring(0, offendingToken.getStopIndex() + 1); - } + private String truncateQueryAtOffendingToken(String query, Token offendingToken) { + return query.substring(0, offendingToken.getStopIndex() + 1); + } - /** - * As official JavaDoc says, e=null means parser was able to recover from the error. - * In other words, "msg" argument includes the information we want. - */ - private String getDetails(Recognizer recognizer, String msg, RecognitionException e) { - String details; - if (e == null) { - details = msg; - } else { - IntervalSet followSet = e.getExpectedTokens(); - details = "Expecting tokens in " + followSet.toString(recognizer.getVocabulary()); - } - return details; + /** + * As official JavaDoc says, e=null means parser was able to recover from the error. In other + * words, "msg" argument includes the information we want. + */ + private String getDetails(Recognizer recognizer, String msg, RecognitionException e) { + String details; + if (e == null) { + details = msg; + } else { + IntervalSet followSet = e.getExpectedTokens(); + details = "Expecting tokens in " + followSet.toString(recognizer.getVocabulary()); } - + return details; + } } diff --git a/legacy/src/main/java/org/opensearch/sql/legacy/antlr/syntax/SyntaxAnalysisException.java b/legacy/src/main/java/org/opensearch/sql/legacy/antlr/syntax/SyntaxAnalysisException.java index f79de62229..dce5437a19 100644 --- a/legacy/src/main/java/org/opensearch/sql/legacy/antlr/syntax/SyntaxAnalysisException.java +++ b/legacy/src/main/java/org/opensearch/sql/legacy/antlr/syntax/SyntaxAnalysisException.java @@ -3,17 +3,14 @@ * SPDX-License-Identifier: Apache-2.0 */ - package org.opensearch.sql.legacy.antlr.syntax; import org.opensearch.sql.legacy.antlr.SqlAnalysisException; -/** - * Exception for syntax analysis - */ +/** Exception for syntax analysis */ public class SyntaxAnalysisException extends SqlAnalysisException { - public SyntaxAnalysisException(String message) { - super(message); - } + public SyntaxAnalysisException(String message) { + super(message); + } } diff --git a/legacy/src/main/java/org/opensearch/sql/legacy/antlr/visitor/AntlrSqlParseTreeVisitor.java b/legacy/src/main/java/org/opensearch/sql/legacy/antlr/visitor/AntlrSqlParseTreeVisitor.java index 00db9a6591..5e89b3b8ae 100644 --- a/legacy/src/main/java/org/opensearch/sql/legacy/antlr/visitor/AntlrSqlParseTreeVisitor.java +++ b/legacy/src/main/java/org/opensearch/sql/legacy/antlr/visitor/AntlrSqlParseTreeVisitor.java @@ -120,23 +120,25 @@ public T visitOuterJoin(OuterJoinContext ctx) { return visitJoin(ctx.children, ctx.tableSourceItem()); } - /** - *
-     * Enforce visit order because ANTLR is generic and unaware.
-     *
-     * Visiting order is:
-     *  FROM
-     *  => WHERE
-     *   => SELECT
-     *    => GROUP BY
-     *     => HAVING
-     *      => ORDER BY
-     *       => LIMIT
-     *  
- */ - @Override - public T visitQuerySpecification(QuerySpecificationContext ctx) { - visitor.visitQuery(); + /** + * + * + *
+   * Enforce visit order because ANTLR is generic and unaware.
+   *
+   * Visiting order is:
+   *  FROM
+   *  => WHERE
+   *   => SELECT
+   *    => GROUP BY
+   *     => HAVING
+   *      => ORDER BY
+   *       => LIMIT
+   *  
+ */ + @Override + public T visitQuerySpecification(QuerySpecificationContext ctx) { + visitor.visitQuery(); // Always visit FROM clause first to define symbols FromClauseContext fromClause = ctx.fromClause(); diff --git a/legacy/src/main/java/org/opensearch/sql/legacy/antlr/visitor/UnsupportedSemanticVerifier.java b/legacy/src/main/java/org/opensearch/sql/legacy/antlr/visitor/UnsupportedSemanticVerifier.java index dc37425a62..919af8e6e2 100644 --- a/legacy/src/main/java/org/opensearch/sql/legacy/antlr/visitor/UnsupportedSemanticVerifier.java +++ b/legacy/src/main/java/org/opensearch/sql/legacy/antlr/visitor/UnsupportedSemanticVerifier.java @@ -3,7 +3,6 @@ * SPDX-License-Identifier: Apache-2.0 */ - package org.opensearch.sql.legacy.antlr.visitor; import com.google.common.collect.Sets; @@ -17,71 +16,68 @@ public class UnsupportedSemanticVerifier { - private static final Set mathConstants = Sets.newHashSet( - "e", "pi" - ); - - private static final Set supportedNestedFunctions = Sets.newHashSet( - "nested", "reverse_nested", "score", "match_query", "matchquery" - ); + private static final Set mathConstants = Sets.newHashSet("e", "pi"); - /** - * The following two sets include the functions and operators that have requested or issued by users - * but the plugin does not support yet. - */ - private static final Set unsupportedFunctions = Sets.newHashSet( - "adddate", "addtime", "datetime", "greatest", "least" - ); + private static final Set supportedNestedFunctions = + Sets.newHashSet("nested", "reverse_nested", "score", "match_query", "matchquery"); - private static final Set unsupportedOperators = Sets.newHashSet( - "div" - ); + /** + * The following two sets include the functions and operators that have requested or issued by + * users but the plugin does not support yet. + */ + private static final Set unsupportedFunctions = + Sets.newHashSet("adddate", "addtime", "datetime", "greatest", "least"); + private static final Set unsupportedOperators = Sets.newHashSet("div"); - /** - * The scalar function calls are separated into (a)typical function calls; (b)nested function calls with functions - * as arguments, like abs(log(...)); (c)aggregations with functions as aggregators, like max(abs(....)). - * Currently, we do not support nested functions or nested aggregations, aka (b) and (c). - * However, for the special EsFunctions included in the [supportedNestedFunctions] set, we have supported them in - * nested function calls and aggregations (b&c). Besides, the math constants included in the [mathConstants] set - * are regraded as scalar functions, but they are working well in the painless script. - * - * Thus, the types of functions to throw exceptions: - * (I)case (b) except that the arguments are from the [mathConstants] set; - * (II) case (b) except that the arguments are from the [supportedNestedFunctions] set; - * (III) case (c) except that the aggregators are from thet [supportedNestedFunctions] set. - */ - public static void verify(ScalarFunctionCallContext ctx) { - String funcName = StringUtils.toLower(ctx.scalarFunctionName().getText()); + /** + * The scalar function calls are separated into (a)typical function calls; (b)nested function + * calls with functions as arguments, like abs(log(...)); (c)aggregations with functions as + * aggregators, like max(abs(....)). Currently, we do not support nested functions or nested + * aggregations, aka (b) and (c). However, for the special EsFunctions included in the + * [supportedNestedFunctions] set, we have supported them in nested function calls and + * aggregations (b&c). Besides, the math constants included in the [mathConstants] set are + * regraded as scalar functions, but they are working well in the painless script. + * + *

Thus, the types of functions to throw exceptions: (I)case (b) except that the arguments are + * from the [mathConstants] set; (II) case (b) except that the arguments are from the + * [supportedNestedFunctions] set; (III) case (c) except that the aggregators are from thet + * [supportedNestedFunctions] set. + */ + public static void verify(ScalarFunctionCallContext ctx) { + String funcName = StringUtils.toLower(ctx.scalarFunctionName().getText()); - // type (III) - if (ctx.parent.parent instanceof OpenSearchLegacySqlParser.FunctionAsAggregatorFunctionContext - && !(supportedNestedFunctions.contains(StringUtils.toLower(funcName)))) { - throw new SqlFeatureNotImplementedException(StringUtils.format( - "Aggregation calls with function aggregator like [%s] are not supported yet", - ctx.parent.parent.getText())); + // type (III) + if (ctx.parent.parent instanceof OpenSearchLegacySqlParser.FunctionAsAggregatorFunctionContext + && !(supportedNestedFunctions.contains(StringUtils.toLower(funcName)))) { + throw new SqlFeatureNotImplementedException( + StringUtils.format( + "Aggregation calls with function aggregator like [%s] are not supported yet", + ctx.parent.parent.getText())); - // type (I) and (II) - } else if (ctx.parent.parent instanceof OpenSearchLegacySqlParser.NestedFunctionArgsContext - && !(mathConstants.contains(funcName) || supportedNestedFunctions.contains(funcName))) { - throw new SqlFeatureNotImplementedException(StringUtils.format( - "Nested function calls like [%s] are not supported yet", ctx.parent.parent.parent.getText())); + // type (I) and (II) + } else if (ctx.parent.parent instanceof OpenSearchLegacySqlParser.NestedFunctionArgsContext + && !(mathConstants.contains(funcName) || supportedNestedFunctions.contains(funcName))) { + throw new SqlFeatureNotImplementedException( + StringUtils.format( + "Nested function calls like [%s] are not supported yet", + ctx.parent.parent.parent.getText())); - // unsupported functions - } else if (unsupportedFunctions.contains(funcName)) { - throw new SqlFeatureNotImplementedException(StringUtils.format("Function [%s] is not supported yet", - funcName)); - } + // unsupported functions + } else if (unsupportedFunctions.contains(funcName)) { + throw new SqlFeatureNotImplementedException( + StringUtils.format("Function [%s] is not supported yet", funcName)); } + } - public static void verify(MathOperatorContext ctx) { - if (unsupportedOperators.contains(StringUtils.toLower(ctx.getText()))) { - throw new SqlFeatureNotImplementedException(StringUtils.format("Operator [%s] is not supported yet", - ctx.getText())); - } + public static void verify(MathOperatorContext ctx) { + if (unsupportedOperators.contains(StringUtils.toLower(ctx.getText()))) { + throw new SqlFeatureNotImplementedException( + StringUtils.format("Operator [%s] is not supported yet", ctx.getText())); } + } - public static void verify(RegexpPredicateContext ctx) { - throw new SqlFeatureNotImplementedException("Regexp predicate is not supported yet"); - } + public static void verify(RegexpPredicateContext ctx) { + throw new SqlFeatureNotImplementedException("Regexp predicate is not supported yet"); + } } diff --git a/legacy/src/main/java/org/opensearch/sql/legacy/cursor/Cursor.java b/legacy/src/main/java/org/opensearch/sql/legacy/cursor/Cursor.java index 8cc83a5fe2..300706a80b 100644 --- a/legacy/src/main/java/org/opensearch/sql/legacy/cursor/Cursor.java +++ b/legacy/src/main/java/org/opensearch/sql/legacy/cursor/Cursor.java @@ -9,11 +9,11 @@ public interface Cursor { NullCursor NULL_CURSOR = new NullCursor(); - /** - * All cursor's are of the form :
- * The serialized form before encoding is upto Cursor implementation - */ - String generateCursorId(); + /** + * All cursor's are of the form :
+ * The serialized form before encoding is upto Cursor implementation + */ + String generateCursorId(); CursorType getType(); } diff --git a/legacy/src/main/java/org/opensearch/sql/legacy/domain/Condition.java b/legacy/src/main/java/org/opensearch/sql/legacy/domain/Condition.java index 8804c543f6..f86635910a 100644 --- a/legacy/src/main/java/org/opensearch/sql/legacy/domain/Condition.java +++ b/legacy/src/main/java/org/opensearch/sql/legacy/domain/Condition.java @@ -377,14 +377,14 @@ public void setChildType(String childType) { this.childType = childType; } - /** - * Return true if the opear is {@link OPERATOR#NESTED_COMPLEX}
- * For example, the opear is {@link OPERATOR#NESTED_COMPLEX} when condition is - * nested('projects', projects.started_year > 2000 OR projects.name LIKE '%security%') - */ - public boolean isNestedComplex() { - return OPERATOR.NESTED_COMPLEX == OPERATOR; - } + /** + * Return true if the opear is {@link OPERATOR#NESTED_COMPLEX}
+ * For example, the opear is {@link OPERATOR#NESTED_COMPLEX} when condition is nested('projects', + * projects.started_year > 2000 OR projects.name LIKE '%security%') + */ + public boolean isNestedComplex() { + return OPERATOR.NESTED_COMPLEX == OPERATOR; + } @Override public String toString() { diff --git a/legacy/src/main/java/org/opensearch/sql/legacy/domain/Having.java b/legacy/src/main/java/org/opensearch/sql/legacy/domain/Having.java index 7d0765580b..a53fb0c275 100644 --- a/legacy/src/main/java/org/opensearch/sql/legacy/domain/Having.java +++ b/legacy/src/main/java/org/opensearch/sql/legacy/domain/Having.java @@ -26,11 +26,12 @@ /** * Domain object for HAVING clause in SQL which covers both the parsing and explain logic. - *

- * Responsibilities: + * + *

Responsibilities: + * *

    - *
  1. Parsing: parse conditions out during initialization - *
  2. Explain: translate conditions to OpenSearch query DSL (Bucket Selector Aggregation) + *
  3. Parsing: parse conditions out during initialization + *
  4. Explain: translate conditions to OpenSearch query DSL (Bucket Selector Aggregation) *
*/ public class Having { @@ -126,32 +127,35 @@ private Script explainConditions() throws SqlParseException { return new Script(doExplain(conditions)); } - /** - *
-     * Explain conditions recursively.
-     * Example: HAVING c >= 2 OR NOT (a > 20 AND c <= 10 OR a < 1) OR a < 5
-     * Object: Where(?:
-     * [
-     * Condition(?:c>=2),
-     * Where(or:
-     * [
-     * Where(?:a<=20), Where(or:c>10), Where(and:a>=1)],
-     * ]),
-     * Condition(or:a<5)
-     * ])
-     * 

- * Note: a) Where(connector : condition expression). - * b) Condition is a subclass of Where. - * c) connector=? means it doesn't matter for first condition in the list - *

- * @param wheres conditions - * @return painless script string - * @throws SqlParseException unknown type of expression other than identifier and value - */ - private String doExplain(List wheres) throws SqlParseException { - if (wheres == null || wheres.isEmpty()) { - return ""; - } + /** + * + * + *
+   * Explain conditions recursively.
+   * Example: HAVING c >= 2 OR NOT (a > 20 AND c <= 10 OR a < 1) OR a < 5
+   * Object: Where(?:
+   * [
+   * Condition(?:c>=2),
+   * Where(or:
+   * [
+   * Where(?:a<=20), Where(or:c>10), Where(and:a>=1)],
+   * ]),
+   * Condition(or:a<5)
+   * ])
+   * 

+ * Note: a) Where(connector : condition expression). + * b) Condition is a subclass of Where. + * c) connector=? means it doesn't matter for first condition in the list + *

+ * + * @param wheres conditions + * @return painless script string + * @throws SqlParseException unknown type of expression other than identifier and value + */ + private String doExplain(List wheres) throws SqlParseException { + if (wheres == null || wheres.isEmpty()) { + return ""; + } StringBuilder script = new StringBuilder(); for (Where cond : wheres) { diff --git a/legacy/src/main/java/org/opensearch/sql/legacy/domain/TableOnJoinSelect.java b/legacy/src/main/java/org/opensearch/sql/legacy/domain/TableOnJoinSelect.java index cf27cb51ee..e0dcb2899f 100644 --- a/legacy/src/main/java/org/opensearch/sql/legacy/domain/TableOnJoinSelect.java +++ b/legacy/src/main/java/org/opensearch/sql/legacy/domain/TableOnJoinSelect.java @@ -3,45 +3,40 @@ * SPDX-License-Identifier: Apache-2.0 */ - package org.opensearch.sql.legacy.domain; import java.util.List; -/** - * Created by Eliran on 28/8/2015. - */ +/** Created by Eliran on 28/8/2015. */ public class TableOnJoinSelect extends Select { - private List connectedFields; - private List selectedFields; - private String alias; - - public TableOnJoinSelect() { - } + private List connectedFields; + private List selectedFields; + private String alias; + public TableOnJoinSelect() {} - public List getConnectedFields() { - return connectedFields; - } + public List getConnectedFields() { + return connectedFields; + } - public void setConnectedFields(List connectedFields) { - this.connectedFields = connectedFields; - } + public void setConnectedFields(List connectedFields) { + this.connectedFields = connectedFields; + } - public List getSelectedFields() { - return selectedFields; - } + public List getSelectedFields() { + return selectedFields; + } - public void setSelectedFields(List selectedFields) { - this.selectedFields = selectedFields; - } + public void setSelectedFields(List selectedFields) { + this.selectedFields = selectedFields; + } - public String getAlias() { - return alias; - } + public String getAlias() { + return alias; + } - public void setAlias(String alias) { - this.alias = alias; - } + public void setAlias(String alias) { + this.alias = alias; + } } diff --git a/legacy/src/main/java/org/opensearch/sql/legacy/domain/Where.java b/legacy/src/main/java/org/opensearch/sql/legacy/domain/Where.java index ae05e33e51..d6f767203b 100644 --- a/legacy/src/main/java/org/opensearch/sql/legacy/domain/Where.java +++ b/legacy/src/main/java/org/opensearch/sql/legacy/domain/Where.java @@ -3,70 +3,69 @@ * SPDX-License-Identifier: Apache-2.0 */ - package org.opensearch.sql.legacy.domain; import java.util.LinkedList; public class Where implements Cloneable { - public enum CONN { - AND, OR; + public enum CONN { + AND, + OR; - public CONN negative() { - return this == AND ? OR : AND; - } + public CONN negative() { + return this == AND ? OR : AND; } + } - public static Where newInstance() { - return new Where(CONN.AND); - } + public static Where newInstance() { + return new Where(CONN.AND); + } - private LinkedList wheres = new LinkedList<>(); + private LinkedList wheres = new LinkedList<>(); - protected CONN conn; + protected CONN conn; - public Where(String connStr) { - this.conn = CONN.valueOf(connStr.toUpperCase()); - } + public Where(String connStr) { + this.conn = CONN.valueOf(connStr.toUpperCase()); + } - public Where(CONN conn) { - this.conn = conn; - } + public Where(CONN conn) { + this.conn = conn; + } - public void addWhere(Where where) { - wheres.add(where); - } + public void addWhere(Where where) { + wheres.add(where); + } - public CONN getConn() { - return this.conn; - } - - public void setConn(CONN conn) { - this.conn = conn; - } + public CONN getConn() { + return this.conn; + } - public LinkedList getWheres() { - return wheres; - } + public void setConn(CONN conn) { + this.conn = conn; + } - @Override - public String toString() { - if (wheres.size() > 0) { - String whereStr = wheres.toString(); - return this.conn + " ( " + whereStr.substring(1, whereStr.length() - 1) + " ) "; - } else { - return ""; - } + public LinkedList getWheres() { + return wheres; + } + @Override + public String toString() { + if (wheres.size() > 0) { + String whereStr = wheres.toString(); + return this.conn + " ( " + whereStr.substring(1, whereStr.length() - 1) + " ) "; + } else { + return ""; } + } - @Override - public Object clone() throws CloneNotSupportedException { - Where clonedWhere = new Where(this.getConn()); - for (Where innerWhere : this.getWheres()) { - clonedWhere.addWhere((Where) innerWhere.clone()); - } - return clonedWhere; + @Override + public Object clone() throws CloneNotSupportedException { + Where clonedWhere = new Where(this.getConn()); + for (Where innerWhere : this.getWheres()) { + clonedWhere.addWhere((Where) innerWhere.clone()); } + return clonedWhere; + } } diff --git a/legacy/src/main/java/org/opensearch/sql/legacy/esdomain/LocalClusterState.java b/legacy/src/main/java/org/opensearch/sql/legacy/esdomain/LocalClusterState.java index 84875b9531..1e6595bd32 100644 --- a/legacy/src/main/java/org/opensearch/sql/legacy/esdomain/LocalClusterState.java +++ b/legacy/src/main/java/org/opensearch/sql/legacy/esdomain/LocalClusterState.java @@ -31,11 +31,12 @@ /** * Local cluster state information which may be stale but help avoid blocking operation in NIO * thread. + * *
    - *
  1. Why extending TransportAction doesn't work here? TransportAction enforce implementation to - * be performed remotely but local cluster state read is expected here. - *
  2. Why injection by AbstractModule doesn't work here? Because this state needs to be used - * across the plugin, ex. in rewriter, pretty formatter etc. + *
  3. Why extending TransportAction doesn't work here? TransportAction enforce implementation to + * be performed remotely but local cluster state read is expected here. + *
  4. Why injection by AbstractModule doesn't work here? Because this state needs to be used + * across the plugin, ex. in rewriter, pretty formatter etc. *
*/ public class LocalClusterState { diff --git a/legacy/src/main/java/org/opensearch/sql/legacy/esdomain/mapping/FieldMappings.java b/legacy/src/main/java/org/opensearch/sql/legacy/esdomain/mapping/FieldMappings.java index 05b3f2854e..e92fdbea33 100644 --- a/legacy/src/main/java/org/opensearch/sql/legacy/esdomain/mapping/FieldMappings.java +++ b/legacy/src/main/java/org/opensearch/sql/legacy/esdomain/mapping/FieldMappings.java @@ -14,6 +14,8 @@ import org.opensearch.cluster.metadata.MappingMetadata; /** + * + * *
  * Field mappings in a specific type.
  * 

diff --git a/legacy/src/main/java/org/opensearch/sql/legacy/esdomain/mapping/IndexMappings.java b/legacy/src/main/java/org/opensearch/sql/legacy/esdomain/mapping/IndexMappings.java index 22cb99c44e..61e707e8ef 100644 --- a/legacy/src/main/java/org/opensearch/sql/legacy/esdomain/mapping/IndexMappings.java +++ b/legacy/src/main/java/org/opensearch/sql/legacy/esdomain/mapping/IndexMappings.java @@ -13,6 +13,8 @@ import org.opensearch.cluster.metadata.Metadata; /** + * + * *

  * Index mappings in the cluster.
  * 

diff --git a/legacy/src/main/java/org/opensearch/sql/legacy/exception/SQLFeatureDisabledException.java b/legacy/src/main/java/org/opensearch/sql/legacy/exception/SQLFeatureDisabledException.java index 52cdda3cdd..4578cd6c93 100644 --- a/legacy/src/main/java/org/opensearch/sql/legacy/exception/SQLFeatureDisabledException.java +++ b/legacy/src/main/java/org/opensearch/sql/legacy/exception/SQLFeatureDisabledException.java @@ -3,15 +3,13 @@ * SPDX-License-Identifier: Apache-2.0 */ - package org.opensearch.sql.legacy.exception; public class SQLFeatureDisabledException extends Exception { - private static final long serialVersionUID = 1L; - - public SQLFeatureDisabledException(String message) { - super(message); - } + private static final long serialVersionUID = 1L; + public SQLFeatureDisabledException(String message) { + super(message); + } } diff --git a/legacy/src/main/java/org/opensearch/sql/legacy/exception/SqlFeatureNotImplementedException.java b/legacy/src/main/java/org/opensearch/sql/legacy/exception/SqlFeatureNotImplementedException.java index 9225986132..43ad6d97b5 100644 --- a/legacy/src/main/java/org/opensearch/sql/legacy/exception/SqlFeatureNotImplementedException.java +++ b/legacy/src/main/java/org/opensearch/sql/legacy/exception/SqlFeatureNotImplementedException.java @@ -3,21 +3,20 @@ * SPDX-License-Identifier: Apache-2.0 */ - package org.opensearch.sql.legacy.exception; /** - * Intended for cases when we knowingly omitted some case, letting users know that we didn't implemented feature, but - * it may be implemented in future. + * Intended for cases when we knowingly omitted some case, letting users know that we didn't + * implemented feature, but it may be implemented in future. */ public class SqlFeatureNotImplementedException extends RuntimeException { - private static final long serialVersionUID = 1; + private static final long serialVersionUID = 1; - public SqlFeatureNotImplementedException(String message) { - super(message); - } + public SqlFeatureNotImplementedException(String message) { + super(message); + } - public SqlFeatureNotImplementedException(String message, Throwable cause) { - super(message, cause); - } + public SqlFeatureNotImplementedException(String message, Throwable cause) { + super(message, cause); + } } diff --git a/legacy/src/main/java/org/opensearch/sql/legacy/exception/SqlParseException.java b/legacy/src/main/java/org/opensearch/sql/legacy/exception/SqlParseException.java index c93ad2a2fa..a09ddc97d1 100644 --- a/legacy/src/main/java/org/opensearch/sql/legacy/exception/SqlParseException.java +++ b/legacy/src/main/java/org/opensearch/sql/legacy/exception/SqlParseException.java @@ -3,16 +3,13 @@ * SPDX-License-Identifier: Apache-2.0 */ - package org.opensearch.sql.legacy.exception; public class SqlParseException extends Exception { - public SqlParseException(String message) { - super(message); - } - - - private static final long serialVersionUID = 1L; + public SqlParseException(String message) { + super(message); + } + private static final long serialVersionUID = 1L; } diff --git a/legacy/src/main/java/org/opensearch/sql/legacy/executor/csv/CSVResult.java b/legacy/src/main/java/org/opensearch/sql/legacy/executor/csv/CSVResult.java index 28bc559a01..eb76cd021e 100644 --- a/legacy/src/main/java/org/opensearch/sql/legacy/executor/csv/CSVResult.java +++ b/legacy/src/main/java/org/opensearch/sql/legacy/executor/csv/CSVResult.java @@ -28,17 +28,18 @@ public CSVResult(List headers, List lines) { this.lines = lines; } - /** - * Sanitize both headers and data lines by: - *

    - *
  1. First prepend single quote if first char is sensitive (= - + @) - *
  2. Second double quote entire cell if any comma found - *
- */ - public CSVResult(String separator, List headers, List> lines) { - this.headers = sanitizeHeaders(separator, headers); - this.lines = sanitizeLines(separator, lines); - } + /** + * Sanitize both headers and data lines by: + * + *
    + *
  1. First prepend single quote if first char is sensitive (= - + @) + *
  2. Second double quote entire cell if any comma found + *
+ */ + public CSVResult(String separator, List headers, List> lines) { + this.headers = sanitizeHeaders(separator, headers); + this.lines = sanitizeLines(separator, lines); + } /** * Return CSV header names which are sanitized because OpenSearch allows special character present diff --git a/legacy/src/main/java/org/opensearch/sql/legacy/executor/cursor/CursorResultExecutor.java b/legacy/src/main/java/org/opensearch/sql/legacy/executor/cursor/CursorResultExecutor.java index 620b8e7b86..66c69f3430 100644 --- a/legacy/src/main/java/org/opensearch/sql/legacy/executor/cursor/CursorResultExecutor.java +++ b/legacy/src/main/java/org/opensearch/sql/legacy/executor/cursor/CursorResultExecutor.java @@ -103,19 +103,22 @@ private String handleDefaultCursorRequest(Client client, DefaultCursor cursor) { int rowsLeft = (int) cursor.getRowsLeft(); int fetch = cursor.getFetchSize(); - if (rowsLeft < fetch && rowsLeft < searchHitArray.length) { - /** - * This condition implies we are on the last page, and we might need to truncate the result from SearchHit[] - * Avoid truncating in following two scenarios - *
    - *
  1. number of rows to be sent equals fetchSize - *
  2. size of SearchHit[] is already less that rows that needs to be sent - *
- * Else truncate to desired number of rows - */ - SearchHit[] newSearchHits = Arrays.copyOf(searchHitArray, rowsLeft); - searchHits = new SearchHits(newSearchHits, searchHits.getTotalHits(), searchHits.getMaxScore()); - } + if (rowsLeft < fetch && rowsLeft < searchHitArray.length) { + /** + * This condition implies we are on the last page, and we might need to truncate the result + * from SearchHit[] Avoid truncating in following two scenarios + * + *
    + *
  1. number of rows to be sent equals fetchSize + *
  2. size of SearchHit[] is already less that rows that needs to be sent + *
+ * + * Else truncate to desired number of rows + */ + SearchHit[] newSearchHits = Arrays.copyOf(searchHitArray, rowsLeft); + searchHits = + new SearchHits(newSearchHits, searchHits.getTotalHits(), searchHits.getMaxScore()); + } rowsLeft = rowsLeft - fetch; diff --git a/legacy/src/main/java/org/opensearch/sql/legacy/executor/format/SelectResultSet.java b/legacy/src/main/java/org/opensearch/sql/legacy/executor/format/SelectResultSet.java index 445bdd45a0..c60691cb7c 100644 --- a/legacy/src/main/java/org/opensearch/sql/legacy/executor/format/SelectResultSet.java +++ b/legacy/src/main/java/org/opensearch/sql/legacy/executor/format/SelectResultSet.java @@ -721,29 +721,31 @@ private Map addNumericAggregation( return data; } - /** - *
-     * Simplifies the structure of row's source Map by flattening it, making the full path of an object the key
-     * and the Object it refers to the value. This handles the case of regular object since nested objects will not
-     * be in hit.source but rather in hit.innerHits
-     * 

- * Sample input: - * keys = ['comments.likes'] - * row = comments: { - * likes: 2 - * } - *

- * Return: - * flattenedRow = {comment.likes: 2} - *

- */ - @SuppressWarnings("unchecked") - private Map flatRow(List keys, Map row) { - Map flattenedRow = new HashMap<>(); - for (String key : keys) { - String[] splitKeys = key.split("\\."); - boolean found = true; - Object currentObj = row; + /** + * + * + *
+   * Simplifies the structure of row's source Map by flattening it, making the full path of an object the key
+   * and the Object it refers to the value. This handles the case of regular object since nested objects will not
+   * be in hit.source but rather in hit.innerHits
+   * 

+ * Sample input: + * keys = ['comments.likes'] + * row = comments: { + * likes: 2 + * } + *

+ * Return: + * flattenedRow = {comment.likes: 2} + *

+ */ + @SuppressWarnings("unchecked") + private Map flatRow(List keys, Map row) { + Map flattenedRow = new HashMap<>(); + for (String key : keys) { + String[] splitKeys = key.split("\\."); + boolean found = true; + Object currentObj = row; for (String splitKey : splitKeys) { // This check is made to prevent Cast Exception as an ArrayList of objects can be in the @@ -770,31 +772,33 @@ private Map flatRow(List keys, Map row) return flattenedRow; } - /** - *
-     * If innerHits associated with column name exists, flatten both the inner field name and the inner rows in it.
-     * 

- * Sample input: - * newKeys = {'region', 'employees.age'}, row = {'region': 'US'} - * innerHits = employees: { - * hits: [{ - * source: { - * age: 26, - * firstname: 'Hank' - * } - * },{ - * source: { - * age: 30, - * firstname: 'John' - * } - * }] - * } - *

- */ - private List flatNestedField(Set newKeys, Map row, - Map innerHits) { - List result = new ArrayList<>(); - result.add(new DataRows.Row(row)); + /** + * + * + *
+   * If innerHits associated with column name exists, flatten both the inner field name and the inner rows in it.
+   * 

+ * Sample input: + * newKeys = {'region', 'employees.age'}, row = {'region': 'US'} + * innerHits = employees: { + * hits: [{ + * source: { + * age: 26, + * firstname: 'Hank' + * } + * },{ + * source: { + * age: 30, + * firstname: 'John' + * } + * }] + * } + *

+ */ + private List flatNestedField( + Set newKeys, Map row, Map innerHits) { + List result = new ArrayList<>(); + result.add(new DataRows.Row(row)); if (innerHits == null) { return result; @@ -819,37 +823,40 @@ private void doFlatNestedFieldName(String colName, SearchHit[] colValue, Set - * Do Cartesian Product between current outer row and inner rows by nested loop and remove original outer row. - *

- * Sample input: - * colName = 'employees', rows = [{region: 'US'}] - * colValue= [{ - * source: { - * age: 26, - * firstname: 'Hank' - * } - * },{ - * source: { - * age: 30, - * firstname: 'John' - * } - * }] - *

- * Return: - * [ - * {region:'US', employees.age:26, employees.firstname:'Hank'}, - * {region:'US', employees.age:30, employees.firstname:'John'} - * ] - *

- */ - private List doFlatNestedFieldValue(String colName, SearchHit[] colValue, List rows) { - List result = new ArrayList<>(); - for (DataRows.Row row : rows) { - for (SearchHit hit : colValue) { - Map innerRow = hit.getSourceAsMap(); - Map copy = new HashMap<>(); + /** + * + * + *
+   * Do Cartesian Product between current outer row and inner rows by nested loop and remove original outer row.
+   * 

+ * Sample input: + * colName = 'employees', rows = [{region: 'US'}] + * colValue= [{ + * source: { + * age: 26, + * firstname: 'Hank' + * } + * },{ + * source: { + * age: 30, + * firstname: 'John' + * } + * }] + *

+ * Return: + * [ + * {region:'US', employees.age:26, employees.firstname:'Hank'}, + * {region:'US', employees.age:30, employees.firstname:'John'} + * ] + *

+ */ + private List doFlatNestedFieldValue( + String colName, SearchHit[] colValue, List rows) { + List result = new ArrayList<>(); + for (DataRows.Row row : rows) { + for (SearchHit hit : colValue) { + Map innerRow = hit.getSourceAsMap(); + Map copy = new HashMap<>(); for (String field : row.getContents().keySet()) { copy.put(field, row.getData(field)); diff --git a/legacy/src/main/java/org/opensearch/sql/legacy/executor/format/ShowResultSet.java b/legacy/src/main/java/org/opensearch/sql/legacy/executor/format/ShowResultSet.java index 0a32f6c582..263bf1e7db 100644 --- a/legacy/src/main/java/org/opensearch/sql/legacy/executor/format/ShowResultSet.java +++ b/legacy/src/main/java/org/opensearch/sql/legacy/executor/format/ShowResultSet.java @@ -3,7 +3,6 @@ * SPDX-License-Identifier: Apache-2.0 */ - package org.opensearch.sql.legacy.executor.format; import java.util.ArrayList; @@ -21,62 +20,62 @@ public class ShowResultSet extends ResultSet { - private static final String TABLE_TYPE = "BASE TABLE"; - - private IndexStatement statement; - private Object queryResult; - - public ShowResultSet(Client client, IndexStatement statement, Object queryResult) { - this.client = client; - this.clusterName = getClusterName(); - this.statement = statement; - this.queryResult = queryResult; - - this.schema = new Schema(statement, loadColumns()); - this.dataRows = new DataRows(loadRows()); + private static final String TABLE_TYPE = "BASE TABLE"; + + private IndexStatement statement; + private Object queryResult; + + public ShowResultSet(Client client, IndexStatement statement, Object queryResult) { + this.client = client; + this.clusterName = getClusterName(); + this.statement = statement; + this.queryResult = queryResult; + + this.schema = new Schema(statement, loadColumns()); + this.dataRows = new DataRows(loadRows()); + } + + private List loadColumns() { + List columns = new ArrayList<>(); + // Unused Columns are still included in Schema to match JDBC/ODBC standard + columns.add(new Column("TABLE_CAT", null, Type.KEYWORD)); + columns.add(new Column("TABLE_SCHEM", null, Type.KEYWORD)); // Not used + columns.add(new Column("TABLE_NAME", null, Type.KEYWORD)); + columns.add(new Column("TABLE_TYPE", null, Type.KEYWORD)); + columns.add(new Column("REMARKS", null, Type.KEYWORD)); // Not used + columns.add(new Column("TYPE_CAT", null, Type.KEYWORD)); // Not used + columns.add(new Column("TYPE_SCHEM", null, Type.KEYWORD)); // Not used + columns.add(new Column("TYPE_NAME", null, Type.KEYWORD)); // Not used + columns.add(new Column("SELF_REFERENCING_COL_NAME", null, Type.KEYWORD)); // Not used + columns.add(new Column("REF_GENERATION", null, Type.KEYWORD)); // Not used + + return columns; + } + + private List loadRows() { + List rows = new ArrayList<>(); + for (String index : extractIndices()) { + rows.add(new Row(loadData(index))); } - private List loadColumns() { - List columns = new ArrayList<>(); - // Unused Columns are still included in Schema to match JDBC/ODBC standard - columns.add(new Column("TABLE_CAT", null, Type.KEYWORD)); - columns.add(new Column("TABLE_SCHEM", null, Type.KEYWORD)); // Not used - columns.add(new Column("TABLE_NAME", null, Type.KEYWORD)); - columns.add(new Column("TABLE_TYPE", null, Type.KEYWORD)); - columns.add(new Column("REMARKS", null, Type.KEYWORD)); // Not used - columns.add(new Column("TYPE_CAT", null, Type.KEYWORD)); // Not used - columns.add(new Column("TYPE_SCHEM", null, Type.KEYWORD)); // Not used - columns.add(new Column("TYPE_NAME", null, Type.KEYWORD)); // Not used - columns.add(new Column("SELF_REFERENCING_COL_NAME", null, Type.KEYWORD)); // Not used - columns.add(new Column("REF_GENERATION", null, Type.KEYWORD)); // Not used + return rows; + } - return columns; - } + private List extractIndices() { + String indexPattern = statement.getIndexPattern(); + String[] indices = ((GetIndexResponse) queryResult).getIndices(); - private List loadRows() { - List rows = new ArrayList<>(); - for (String index : extractIndices()) { - rows.add(new Row(loadData(index))); - } + return Arrays.stream(indices) + .filter(index -> matchesPatternIfRegex(index, indexPattern)) + .collect(Collectors.toList()); + } - return rows; - } + private Map loadData(String tableName) { + Map data = new HashMap<>(); + data.put("TABLE_CAT", clusterName); + data.put("TABLE_NAME", tableName); + data.put("TABLE_TYPE", TABLE_TYPE); - private List extractIndices() { - String indexPattern = statement.getIndexPattern(); - String[] indices = ((GetIndexResponse) queryResult).getIndices(); - - return Arrays.stream(indices) - .filter(index -> matchesPatternIfRegex(index, indexPattern)) - .collect(Collectors.toList()); - } - - private Map loadData(String tableName) { - Map data = new HashMap<>(); - data.put("TABLE_CAT", clusterName); - data.put("TABLE_NAME", tableName); - data.put("TABLE_TYPE", TABLE_TYPE); - - return data; - } + return data; + } } diff --git a/legacy/src/main/java/org/opensearch/sql/legacy/executor/multi/UnionExecutor.java b/legacy/src/main/java/org/opensearch/sql/legacy/executor/multi/UnionExecutor.java index 4b4080156d..6b8b64c4e8 100644 --- a/legacy/src/main/java/org/opensearch/sql/legacy/executor/multi/UnionExecutor.java +++ b/legacy/src/main/java/org/opensearch/sql/legacy/executor/multi/UnionExecutor.java @@ -3,7 +3,6 @@ * SPDX-License-Identifier: Apache-2.0 */ - package org.opensearch.sql.legacy.executor.multi; import java.util.ArrayList; @@ -23,87 +22,92 @@ import org.opensearch.sql.legacy.query.multi.MultiQueryRequestBuilder; import org.opensearch.sql.legacy.utils.Util; -/** - * Created by Eliran on 21/8/2016. - */ +/** Created by Eliran on 21/8/2016. */ public class UnionExecutor implements ElasticHitsExecutor { - private MultiQueryRequestBuilder multiQueryBuilder; - private SearchHits results; - private Client client; - private int currentId; + private MultiQueryRequestBuilder multiQueryBuilder; + private SearchHits results; + private Client client; + private int currentId; - public UnionExecutor(Client client, MultiQueryRequestBuilder builder) { - multiQueryBuilder = builder; - this.client = client; - currentId = 0; - } + public UnionExecutor(Client client, MultiQueryRequestBuilder builder) { + multiQueryBuilder = builder; + this.client = client; + currentId = 0; + } - @Override - public void run() { - SearchResponse firstResponse = this.multiQueryBuilder.getFirstSearchRequest().get(); - SearchHit[] hits = firstResponse.getHits().getHits(); - List unionHits = new ArrayList<>(hits.length); - fillInternalSearchHits(unionHits, hits, this.multiQueryBuilder.getFirstTableFieldToAlias()); - SearchResponse secondResponse = this.multiQueryBuilder.getSecondSearchRequest().get(); - fillInternalSearchHits(unionHits, secondResponse.getHits().getHits(), - this.multiQueryBuilder.getSecondTableFieldToAlias()); - int totalSize = unionHits.size(); - SearchHit[] unionHitsArr = unionHits.toArray(new SearchHit[totalSize]); - this.results = new SearchHits(unionHitsArr, new TotalHits(totalSize, Relation.EQUAL_TO), 1.0f); - } + @Override + public void run() { + SearchResponse firstResponse = this.multiQueryBuilder.getFirstSearchRequest().get(); + SearchHit[] hits = firstResponse.getHits().getHits(); + List unionHits = new ArrayList<>(hits.length); + fillInternalSearchHits(unionHits, hits, this.multiQueryBuilder.getFirstTableFieldToAlias()); + SearchResponse secondResponse = this.multiQueryBuilder.getSecondSearchRequest().get(); + fillInternalSearchHits( + unionHits, + secondResponse.getHits().getHits(), + this.multiQueryBuilder.getSecondTableFieldToAlias()); + int totalSize = unionHits.size(); + SearchHit[] unionHitsArr = unionHits.toArray(new SearchHit[totalSize]); + this.results = new SearchHits(unionHitsArr, new TotalHits(totalSize, Relation.EQUAL_TO), 1.0f); + } - private void fillInternalSearchHits(List unionHits, SearchHit[] hits, - Map fieldNameToAlias) { - for (SearchHit hit : hits) { - Map documentFields = new HashMap<>(); - Map metaFields = new HashMap<>(); - hit.getFields().forEach((fieldName, docField) -> - (MapperService.META_FIELDS_BEFORE_7DOT8.contains(fieldName) ? metaFields : documentFields).put(fieldName, docField)); - SearchHit searchHit = new SearchHit(currentId, hit.getId(), documentFields, metaFields); - searchHit.sourceRef(hit.getSourceRef()); - searchHit.getSourceAsMap().clear(); - Map sourceAsMap = hit.getSourceAsMap(); - if (!fieldNameToAlias.isEmpty()) { - updateFieldNamesToAlias(sourceAsMap, fieldNameToAlias); - } - searchHit.getSourceAsMap().putAll(sourceAsMap); - currentId++; - unionHits.add(searchHit); - } + private void fillInternalSearchHits( + List unionHits, SearchHit[] hits, Map fieldNameToAlias) { + for (SearchHit hit : hits) { + Map documentFields = new HashMap<>(); + Map metaFields = new HashMap<>(); + hit.getFields() + .forEach( + (fieldName, docField) -> + (MapperService.META_FIELDS_BEFORE_7DOT8.contains(fieldName) + ? metaFields + : documentFields) + .put(fieldName, docField)); + SearchHit searchHit = new SearchHit(currentId, hit.getId(), documentFields, metaFields); + searchHit.sourceRef(hit.getSourceRef()); + searchHit.getSourceAsMap().clear(); + Map sourceAsMap = hit.getSourceAsMap(); + if (!fieldNameToAlias.isEmpty()) { + updateFieldNamesToAlias(sourceAsMap, fieldNameToAlias); + } + searchHit.getSourceAsMap().putAll(sourceAsMap); + currentId++; + unionHits.add(searchHit); } + } - - private void updateFieldNamesToAlias(Map sourceAsMap, Map fieldNameToAlias) { - for (Map.Entry fieldToAlias : fieldNameToAlias.entrySet()) { - String fieldName = fieldToAlias.getKey(); - Object value = null; - Map deleteFrom = null; - if (fieldName.contains(".")) { - String[] split = fieldName.split("\\."); - String[] path = Arrays.copyOf(split, split.length - 1); - Object placeInMap = Util.searchPathInMap(sourceAsMap, path); - if (placeInMap != null) { - if (!Map.class.isAssignableFrom(placeInMap.getClass())) { - continue; - } - } - deleteFrom = (Map) placeInMap; - value = deleteFrom.get(split[split.length - 1]); - } else if (sourceAsMap.containsKey(fieldName)) { - value = sourceAsMap.get(fieldName); - deleteFrom = sourceAsMap; - } - if (value != null) { - sourceAsMap.put(fieldToAlias.getValue(), value); - deleteFrom.remove(fieldName); - } + private void updateFieldNamesToAlias( + Map sourceAsMap, Map fieldNameToAlias) { + for (Map.Entry fieldToAlias : fieldNameToAlias.entrySet()) { + String fieldName = fieldToAlias.getKey(); + Object value = null; + Map deleteFrom = null; + if (fieldName.contains(".")) { + String[] split = fieldName.split("\\."); + String[] path = Arrays.copyOf(split, split.length - 1); + Object placeInMap = Util.searchPathInMap(sourceAsMap, path); + if (placeInMap != null) { + if (!Map.class.isAssignableFrom(placeInMap.getClass())) { + continue; + } } - Util.clearEmptyPaths(sourceAsMap); + deleteFrom = (Map) placeInMap; + value = deleteFrom.get(split[split.length - 1]); + } else if (sourceAsMap.containsKey(fieldName)) { + value = sourceAsMap.get(fieldName); + deleteFrom = sourceAsMap; + } + if (value != null) { + sourceAsMap.put(fieldToAlias.getValue(), value); + deleteFrom.remove(fieldName); + } } + Util.clearEmptyPaths(sourceAsMap); + } - @Override - public SearchHits getHits() { - return results; - } + @Override + public SearchHits getHits() { + return results; + } } diff --git a/legacy/src/main/java/org/opensearch/sql/legacy/expression/core/builder/UnaryExpressionBuilder.java b/legacy/src/main/java/org/opensearch/sql/legacy/expression/core/builder/UnaryExpressionBuilder.java index f9bdce8ce4..3d40c3a527 100644 --- a/legacy/src/main/java/org/opensearch/sql/legacy/expression/core/builder/UnaryExpressionBuilder.java +++ b/legacy/src/main/java/org/opensearch/sql/legacy/expression/core/builder/UnaryExpressionBuilder.java @@ -3,7 +3,6 @@ * SPDX-License-Identifier: Apache-2.0 */ - package org.opensearch.sql.legacy.expression.core.builder; import java.util.Arrays; @@ -14,32 +13,31 @@ import org.opensearch.sql.legacy.expression.domain.BindingTuple; import org.opensearch.sql.legacy.expression.model.ExprValue; -/** - * The definition of the Expression Builder which has one argument. - */ +/** The definition of the Expression Builder which has one argument. */ @RequiredArgsConstructor public class UnaryExpressionBuilder implements ExpressionBuilder { - private final ScalarOperator op; + private final ScalarOperator op; - /** - * Build the expression with two {@link Expression} as arguments. - * @param expressionList expression list. - * @return expression. - */ - @Override - public Expression build(List expressionList) { - Expression expression = expressionList.get(0); + /** + * Build the expression with two {@link Expression} as arguments. + * + * @param expressionList expression list. + * @return expression. + */ + @Override + public Expression build(List expressionList) { + Expression expression = expressionList.get(0); - return new Expression() { - @Override - public ExprValue valueOf(BindingTuple tuple) { - return op.apply(Arrays.asList(expression.valueOf(tuple))); - } + return new Expression() { + @Override + public ExprValue valueOf(BindingTuple tuple) { + return op.apply(Arrays.asList(expression.valueOf(tuple))); + } - @Override - public String toString() { - return String.format("%s(%s)", op.name(), expression); - } - }; - } + @Override + public String toString() { + return String.format("%s(%s)", op.name(), expression); + } + }; + } } diff --git a/legacy/src/main/java/org/opensearch/sql/legacy/expression/core/operator/UnaryScalarOperator.java b/legacy/src/main/java/org/opensearch/sql/legacy/expression/core/operator/UnaryScalarOperator.java index a6bfc48a1a..deb979f767 100644 --- a/legacy/src/main/java/org/opensearch/sql/legacy/expression/core/operator/UnaryScalarOperator.java +++ b/legacy/src/main/java/org/opensearch/sql/legacy/expression/core/operator/UnaryScalarOperator.java @@ -3,7 +3,6 @@ * SPDX-License-Identifier: Apache-2.0 */ - package org.opensearch.sql.legacy.expression.core.operator; import static org.opensearch.sql.legacy.expression.model.ExprValueUtils.getDoubleValue; @@ -18,36 +17,37 @@ import org.opensearch.sql.legacy.expression.model.ExprValueFactory; /** - * Unary Scalar Operator take one {@link ExprValue} as arguments ans return one {@link ExprValue} as result. + * Unary Scalar Operator take one {@link ExprValue} as arguments ans return one {@link ExprValue} as + * result. */ @RequiredArgsConstructor public class UnaryScalarOperator implements ScalarOperator { - private final ScalarOperation op; - private final Function integerFunc; - private final Function longFunc; - private final Function doubleFunc; - private final Function floatFunc; + private final ScalarOperation op; + private final Function integerFunc; + private final Function longFunc; + private final Function doubleFunc; + private final Function floatFunc; - @Override - public ExprValue apply(List exprValues) { - ExprValue exprValue = exprValues.get(0); - switch (exprValue.kind()) { - case DOUBLE_VALUE: - return ExprValueFactory.from(doubleFunc.apply(getDoubleValue(exprValue))); - case INTEGER_VALUE: - return ExprValueFactory.from(integerFunc.apply(getIntegerValue(exprValue))); - case LONG_VALUE: - return ExprValueFactory.from(longFunc.apply(getLongValue(exprValue))); - case FLOAT_VALUE: - return ExprValueFactory.from(floatFunc.apply(getFloatValue(exprValue))); - default: - throw new RuntimeException(String.format("unexpected operation type: %s(%s)", op.name(), - exprValue.kind())); - } + @Override + public ExprValue apply(List exprValues) { + ExprValue exprValue = exprValues.get(0); + switch (exprValue.kind()) { + case DOUBLE_VALUE: + return ExprValueFactory.from(doubleFunc.apply(getDoubleValue(exprValue))); + case INTEGER_VALUE: + return ExprValueFactory.from(integerFunc.apply(getIntegerValue(exprValue))); + case LONG_VALUE: + return ExprValueFactory.from(longFunc.apply(getLongValue(exprValue))); + case FLOAT_VALUE: + return ExprValueFactory.from(floatFunc.apply(getFloatValue(exprValue))); + default: + throw new RuntimeException( + String.format("unexpected operation type: %s(%s)", op.name(), exprValue.kind())); } + } - @Override - public String name() { - return op.name(); - } + @Override + public String name() { + return op.name(); + } } diff --git a/legacy/src/main/java/org/opensearch/sql/legacy/parser/NestedType.java b/legacy/src/main/java/org/opensearch/sql/legacy/parser/NestedType.java index 4deeba1309..5951975077 100644 --- a/legacy/src/main/java/org/opensearch/sql/legacy/parser/NestedType.java +++ b/legacy/src/main/java/org/opensearch/sql/legacy/parser/NestedType.java @@ -116,26 +116,28 @@ public String getBucketPath() { return bucketPath.getBucketPath(); } - /** - *
-     * Return true if the filed is the nested filed.
-     * For example, the mapping
-     * {
-     * "projects":{
-     * "type": "nested"
-     * "properties": {
-     * "name": {
-     * "type": "text"
-     * }
-     * }
-     * }
-     * }
-     * 

- * If the filed is projects, return true. - * If the filed is projects.name, return false. - *

- */ - public boolean isNestedField() { - return !field.contains(".") && field.equalsIgnoreCase(path); - } + /** + * + * + *
+   * Return true if the filed is the nested filed.
+   * For example, the mapping
+   * {
+   * "projects":{
+   * "type": "nested"
+   * "properties": {
+   * "name": {
+   * "type": "text"
+   * }
+   * }
+   * }
+   * }
+   * 

+ * If the filed is projects, return true. + * If the filed is projects.name, return false. + *

+ */ + public boolean isNestedField() { + return !field.contains(".") && field.equalsIgnoreCase(path); + } } diff --git a/legacy/src/main/java/org/opensearch/sql/legacy/parser/SQLOdbcExpr.java b/legacy/src/main/java/org/opensearch/sql/legacy/parser/SQLOdbcExpr.java index ed03051b66..64d1235f4d 100644 --- a/legacy/src/main/java/org/opensearch/sql/legacy/parser/SQLOdbcExpr.java +++ b/legacy/src/main/java/org/opensearch/sql/legacy/parser/SQLOdbcExpr.java @@ -3,50 +3,44 @@ * SPDX-License-Identifier: Apache-2.0 */ - package org.opensearch.sql.legacy.parser; import com.alibaba.druid.sql.ast.expr.SQLCharExpr; import com.alibaba.druid.sql.visitor.SQLASTVisitor; - -/** - * Created by jheimbouch on 3/17/15. - */ +/** Created by jheimbouch on 3/17/15. */ public class SQLOdbcExpr extends SQLCharExpr { - private static final long serialVersionUID = 1L; - - public SQLOdbcExpr() { - - } + private static final long serialVersionUID = 1L; - public SQLOdbcExpr(String text) { - super(text); - } + public SQLOdbcExpr() {} - @Override - public void output(StringBuffer buf) { - if ((this.text == null) || (this.text.length() == 0)) { - buf.append("NULL"); - } else { - buf.append("{ts '"); - buf.append(this.text.replaceAll("'", "''")); - buf.append("'}"); - } - } - - @Override - public String getText() { - StringBuilder sb = new StringBuilder(); - sb.append("{ts '"); - sb.append(this.text); - sb.append("'}"); - return sb.toString(); - } + public SQLOdbcExpr(String text) { + super(text); + } - protected void accept0(SQLASTVisitor visitor) { - visitor.visit(this); - visitor.endVisit(this); + @Override + public void output(StringBuffer buf) { + if ((this.text == null) || (this.text.length() == 0)) { + buf.append("NULL"); + } else { + buf.append("{ts '"); + buf.append(this.text.replaceAll("'", "''")); + buf.append("'}"); } + } + + @Override + public String getText() { + StringBuilder sb = new StringBuilder(); + sb.append("{ts '"); + sb.append(this.text); + sb.append("'}"); + return sb.toString(); + } + + protected void accept0(SQLASTVisitor visitor) { + visitor.visit(this); + visitor.endVisit(this); + } } diff --git a/legacy/src/main/java/org/opensearch/sql/legacy/parser/SQLParensIdentifierExpr.java b/legacy/src/main/java/org/opensearch/sql/legacy/parser/SQLParensIdentifierExpr.java index b9682ce84a..96c95e4e2f 100644 --- a/legacy/src/main/java/org/opensearch/sql/legacy/parser/SQLParensIdentifierExpr.java +++ b/legacy/src/main/java/org/opensearch/sql/legacy/parser/SQLParensIdentifierExpr.java @@ -3,27 +3,24 @@ * SPDX-License-Identifier: Apache-2.0 */ - package org.opensearch.sql.legacy.parser; import com.alibaba.druid.sql.ast.expr.SQLIdentifierExpr; - /** - * An Identifier that is wrapped in parenthesis. - * This is for tracking in group bys the difference between "group by state, age" and "group by (state), (age)". - * For non group by identifiers, it acts as a normal SQLIdentifierExpr. + * An Identifier that is wrapped in parentheses. This is for tracking in group bys the difference + * between "group by state, age" and "group by (state), (age)". For non group by identifiers, it + * acts as a normal SQLIdentifierExpr. */ public class SQLParensIdentifierExpr extends SQLIdentifierExpr { - public SQLParensIdentifierExpr() { - } + public SQLParensIdentifierExpr() {} - public SQLParensIdentifierExpr(String name) { - super(name); - } + public SQLParensIdentifierExpr(String name) { + super(name); + } - public SQLParensIdentifierExpr(SQLIdentifierExpr expr) { - super(expr.getName()); - } + public SQLParensIdentifierExpr(SQLIdentifierExpr expr) { + super(expr.getName()); + } } diff --git a/legacy/src/main/java/org/opensearch/sql/legacy/parser/SqlParser.java b/legacy/src/main/java/org/opensearch/sql/legacy/parser/SqlParser.java index cf184750f2..947533630b 100644 --- a/legacy/src/main/java/org/opensearch/sql/legacy/parser/SqlParser.java +++ b/legacy/src/main/java/org/opensearch/sql/legacy/parser/SqlParser.java @@ -3,7 +3,6 @@ * SPDX-License-Identifier: Apache-2.0 */ - package org.opensearch.sql.legacy.parser; import static org.opensearch.sql.legacy.utils.Util.NESTED_JOIN_TYPE; @@ -50,546 +49,571 @@ import org.opensearch.sql.legacy.exception.SqlParseException; import org.opensearch.sql.legacy.query.multi.MultiQuerySelect; - /** * OpenSearch sql support * * @author ansj */ public class SqlParser { - private FieldMaker fieldMaker = new FieldMaker(); + private FieldMaker fieldMaker = new FieldMaker(); - public SqlParser() { + public SqlParser() {} + public Select parseSelect(SQLQueryExpr mySqlExpr) throws SqlParseException { + MySqlSelectQueryBlock query = (MySqlSelectQueryBlock) mySqlExpr.getSubQuery().getQuery(); + SubQueryParser subQueryParser = new SubQueryParser(this); + if (subQueryParser.containSubqueryInFrom(query)) { + return subQueryParser.parseSubQueryInFrom(query); + } else { + return parseSelect(query); } + } - public Select parseSelect(SQLQueryExpr mySqlExpr) throws SqlParseException { - MySqlSelectQueryBlock query = (MySqlSelectQueryBlock) mySqlExpr.getSubQuery().getQuery(); - SubQueryParser subQueryParser = new SubQueryParser(this); - if (subQueryParser.containSubqueryInFrom(query)) { - return subQueryParser.parseSubQueryInFrom(query); - } else { - return parseSelect(query); - } - } + public Select parseSelect(MySqlSelectQueryBlock query) throws SqlParseException { - public Select parseSelect(MySqlSelectQueryBlock query) throws SqlParseException { + Select select = new Select(); + WhereParser whereParser = new WhereParser(this, query, fieldMaker); - Select select = new Select(); - WhereParser whereParser = new WhereParser(this, query, fieldMaker); + if (query.getAttribute(NESTED_JOIN_TYPE) != null) { + select.setNestedJoinType((SQLJoinTableSource.JoinType) query.getAttribute(NESTED_JOIN_TYPE)); + } - if (query.getAttribute(NESTED_JOIN_TYPE) != null) { - select.setNestedJoinType((SQLJoinTableSource.JoinType) query.getAttribute(NESTED_JOIN_TYPE)); - } + findSelect(query, select, query.getFrom().getAlias()); - findSelect(query, select, query.getFrom().getAlias()); + select.getFrom().addAll(findFrom(query.getFrom())); - select.getFrom().addAll(findFrom(query.getFrom())); + select.setWhere(whereParser.findWhere()); - select.setWhere(whereParser.findWhere()); + select.fillSubQueries(); - select.fillSubQueries(); + select.getHints().addAll(parseHints(query.getHints())); - select.getHints().addAll(parseHints(query.getHints())); + findLimit(query.getLimit(), select); - findLimit(query.getLimit(), select); + if (query.getOrderBy() != null) { + addOrderByToSelect(select, query, query.getOrderBy().getItems(), null); + } - if (query.getOrderBy() != null) { - addOrderByToSelect(select, query, query.getOrderBy().getItems(), null); - } + if (query.getGroupBy() != null) { + findGroupBy(query, select); + } - if (query.getGroupBy() != null) { - findGroupBy(query, select); - } + return select; + } - return select; - } + public Delete parseDelete(SQLDeleteStatement deleteStatement) throws SqlParseException { + Delete delete = new Delete(); + WhereParser whereParser = new WhereParser(this, deleteStatement); - public Delete parseDelete(SQLDeleteStatement deleteStatement) throws SqlParseException { - Delete delete = new Delete(); - WhereParser whereParser = new WhereParser(this, deleteStatement); + delete.getFrom().addAll(findFrom(deleteStatement.getTableSource())); - delete.getFrom().addAll(findFrom(deleteStatement.getTableSource())); + delete.setWhere(whereParser.findWhere()); - delete.setWhere(whereParser.findWhere()); + return delete; + } - return delete; - } + public MultiQuerySelect parseMultiSelect(SQLUnionQuery query) throws SqlParseException { + Select firstTableSelect = this.parseSelect((MySqlSelectQueryBlock) query.getLeft()); + Select secondTableSelect = this.parseSelect((MySqlSelectQueryBlock) query.getRight()); + return new MultiQuerySelect(query.getOperator(), firstTableSelect, secondTableSelect); + } - public MultiQuerySelect parseMultiSelect(SQLUnionQuery query) throws SqlParseException { - Select firstTableSelect = this.parseSelect((MySqlSelectQueryBlock) query.getLeft()); - Select secondTableSelect = this.parseSelect((MySqlSelectQueryBlock) query.getRight()); - return new MultiQuerySelect(query.getOperator(), firstTableSelect, secondTableSelect); + private void findSelect(MySqlSelectQueryBlock query, Select select, String tableAlias) + throws SqlParseException { + List selectList = query.getSelectList(); + for (SQLSelectItem sqlSelectItem : selectList) { + Field field = + fieldMaker.makeField(sqlSelectItem.getExpr(), sqlSelectItem.getAlias(), tableAlias); + select.addField(field); } - - private void findSelect(MySqlSelectQueryBlock query, Select select, String tableAlias) throws SqlParseException { - List selectList = query.getSelectList(); - for (SQLSelectItem sqlSelectItem : selectList) { - Field field = fieldMaker.makeField(sqlSelectItem.getExpr(), sqlSelectItem.getAlias(), tableAlias); - select.addField(field); - } + } + + private void findGroupBy(MySqlSelectQueryBlock query, Select select) throws SqlParseException { + Map aliasesToExperssions = + query.getSelectList().stream() + .filter(item -> item.getAlias() != null) + .collect(Collectors.toMap(SQLSelectItem::getAlias, SQLSelectItem::getExpr)); + + SQLSelectGroupByClause groupBy = query.getGroupBy(); + SQLTableSource sqlTableSource = query.getFrom(); + + findHaving(query, select); + + List items = groupBy.getItems(); + + List standardGroupBys = new ArrayList<>(); + for (SQLExpr sqlExpr : items) { + // todo: mysql expr patch + if (sqlExpr instanceof MySqlSelectGroupByExpr) { + MySqlSelectGroupByExpr sqlSelectGroupByExpr = (MySqlSelectGroupByExpr) sqlExpr; + sqlExpr = sqlSelectGroupByExpr.getExpr(); + } + + if ((sqlExpr instanceof SQLParensIdentifierExpr + || !(sqlExpr instanceof SQLIdentifierExpr || sqlExpr instanceof SQLMethodInvokeExpr)) + && !standardGroupBys.isEmpty()) { + // flush the standard group bys + select.addGroupBy(convertExprsToFields(standardGroupBys, sqlTableSource)); + standardGroupBys = new ArrayList<>(); + } + + if (sqlExpr instanceof SQLParensIdentifierExpr) { + // single item with parens (should get its own aggregation) + select.addGroupBy(fieldMaker.makeField(sqlExpr, null, sqlTableSource.getAlias())); + } else if (sqlExpr instanceof SQLListExpr) { + // multiple items in their own list + SQLListExpr listExpr = (SQLListExpr) sqlExpr; + select.addGroupBy(convertExprsToFields(listExpr.getItems(), sqlTableSource)); + } else { + // check if field is actually alias + if (aliasesToExperssions.containsKey(sqlExpr.toString())) { + sqlExpr = aliasesToExperssions.get(sqlExpr.toString()); + } + standardGroupBys.add(sqlExpr); + } } - - private void findGroupBy(MySqlSelectQueryBlock query, Select select) throws SqlParseException { - Map aliasesToExperssions = query - .getSelectList() - .stream() - .filter(item -> item.getAlias() != null) - .collect(Collectors.toMap(SQLSelectItem::getAlias, SQLSelectItem::getExpr)); - - SQLSelectGroupByClause groupBy = query.getGroupBy(); - SQLTableSource sqlTableSource = query.getFrom(); - - findHaving(query, select); - - List items = groupBy.getItems(); - - List standardGroupBys = new ArrayList<>(); - for (SQLExpr sqlExpr : items) { - //todo: mysql expr patch - if (sqlExpr instanceof MySqlSelectGroupByExpr) { - MySqlSelectGroupByExpr sqlSelectGroupByExpr = (MySqlSelectGroupByExpr) sqlExpr; - sqlExpr = sqlSelectGroupByExpr.getExpr(); - } - - if ((sqlExpr instanceof SQLParensIdentifierExpr || !(sqlExpr instanceof SQLIdentifierExpr - || sqlExpr instanceof SQLMethodInvokeExpr)) && !standardGroupBys.isEmpty()) { - // flush the standard group bys - select.addGroupBy(convertExprsToFields(standardGroupBys, sqlTableSource)); - standardGroupBys = new ArrayList<>(); - } - - if (sqlExpr instanceof SQLParensIdentifierExpr) { - // single item with parens (should get its own aggregation) - select.addGroupBy(fieldMaker.makeField(sqlExpr, null, sqlTableSource.getAlias())); - } else if (sqlExpr instanceof SQLListExpr) { - // multiple items in their own list - SQLListExpr listExpr = (SQLListExpr) sqlExpr; - select.addGroupBy(convertExprsToFields(listExpr.getItems(), sqlTableSource)); - } else { - // check if field is actually alias - if (aliasesToExperssions.containsKey(sqlExpr.toString())) { - sqlExpr = aliasesToExperssions.get(sqlExpr.toString()); - } - standardGroupBys.add(sqlExpr); - } - } - if (!standardGroupBys.isEmpty()) { - select.addGroupBy(convertExprsToFields(standardGroupBys, sqlTableSource)); - } + if (!standardGroupBys.isEmpty()) { + select.addGroupBy(convertExprsToFields(standardGroupBys, sqlTableSource)); } - - private void findHaving(MySqlSelectQueryBlock query, Select select) throws SqlParseException { - select.setHaving(new Having(query.getGroupBy(), new WhereParser(this, query, fieldMaker))); + } + + private void findHaving(MySqlSelectQueryBlock query, Select select) throws SqlParseException { + select.setHaving(new Having(query.getGroupBy(), new WhereParser(this, query, fieldMaker))); + } + + private List convertExprsToFields( + List exprs, SQLTableSource sqlTableSource) throws SqlParseException { + List fields = new ArrayList<>(exprs.size()); + for (SQLExpr expr : exprs) { + // here we suppose groupby field will not have alias,so set null in second parameter + fields.add(fieldMaker.makeField(expr, null, sqlTableSource.getAlias())); } + return fields; + } - private List convertExprsToFields(List exprs, SQLTableSource sqlTableSource) - throws SqlParseException { - List fields = new ArrayList<>(exprs.size()); - for (SQLExpr expr : exprs) { - //here we suppose groupby field will not have alias,so set null in second parameter - fields.add(fieldMaker.makeField(expr, null, sqlTableSource.getAlias())); - } - return fields; + private String sameAliasWhere(Where where, String... aliases) throws SqlParseException { + if (where == null) { + return null; } - private String sameAliasWhere(Where where, String... aliases) throws SqlParseException { - if (where == null) { - return null; - } - - if (where instanceof Condition) { - Condition condition = (Condition) where; - String fieldName = condition.getName(); - for (String alias : aliases) { - String prefix = alias + "."; - if (fieldName.startsWith(prefix)) { - return alias; - } - } - throw new SqlParseException(String.format("Field [%s] with condition [%s] does not contain an alias", - fieldName, condition.toString())); - } - List sameAliases = new ArrayList<>(); - if (where.getWheres() != null && where.getWheres().size() > 0) { - for (Where innerWhere : where.getWheres()) { - sameAliases.add(sameAliasWhere(innerWhere, aliases)); - } - } - - if (sameAliases.contains(null)) { - return null; - } - String firstAlias = sameAliases.get(0); - //return null if more than one alias - for (String alias : sameAliases) { - if (!alias.equals(firstAlias)) { - return null; - } - } - return firstAlias; - } - - private void addOrderByToSelect(Select select, MySqlSelectQueryBlock queryBlock, List items, - String alias) - throws SqlParseException { - - Map aliasesToExpressions = queryBlock - .getSelectList() - .stream() - .filter(item -> item.getAlias() != null) - .collect(Collectors.toMap(SQLSelectItem::getAlias, SQLSelectItem::getExpr)); - - for (SQLSelectOrderByItem sqlSelectOrderByItem : items) { - if (sqlSelectOrderByItem.getType() == null) { - sqlSelectOrderByItem.setType(SQLOrderingSpecification.ASC); - } - String type = sqlSelectOrderByItem.getType().toString(); - SQLExpr expr = extractExprFromOrderExpr(sqlSelectOrderByItem); - - if (expr instanceof SQLIdentifierExpr) { - if (queryBlock.getGroupBy() == null || queryBlock.getGroupBy().getItems().isEmpty()) { - if (aliasesToExpressions.containsKey(((SQLIdentifierExpr) expr).getName())) { - expr = aliasesToExpressions.get(((SQLIdentifierExpr) expr).getName()); - } - } - } - - Field field = fieldMaker.makeField(expr, null, null); - - SQLExpr sqlExpr = sqlSelectOrderByItem.getExpr(); - if (sqlExpr instanceof SQLBinaryOpExpr && hasNullOrderInBinaryOrderExpr(sqlExpr)) { - // override Field.expression to SQLBinaryOpExpr, - // which was set by FieldMaker.makeField() to SQLIdentifierExpr above - field.setExpression(sqlExpr); - } - - String orderByName; - if (field.isScriptField()) { - MethodField methodField = (MethodField) field; - - // 0 - generated field name - final int SCRIPT_CONTENT_INDEX = 1; - orderByName = methodField.getParams().get(SCRIPT_CONTENT_INDEX).toString(); - - } else { - orderByName = field.toString(); - } - - orderByName = orderByName.replace("`", ""); - if (alias != null) { - orderByName = orderByName.replaceFirst(alias + "\\.", ""); - } - select.addOrderBy(field.getNestedPath(), orderByName, type, field); - } + if (where instanceof Condition) { + Condition condition = (Condition) where; + String fieldName = condition.getName(); + for (String alias : aliases) { + String prefix = alias + "."; + if (fieldName.startsWith(prefix)) { + return alias; + } + } + throw new SqlParseException( + String.format( + "Field [%s] with condition [%s] does not contain an alias", + fieldName, condition.toString())); } - - private SQLExpr extractExprFromOrderExpr(SQLSelectOrderByItem sqlSelectOrderByItem) { - SQLExpr expr = sqlSelectOrderByItem.getExpr(); - - // extract SQLIdentifier from Order IS NULL/NOT NULL expression to generate Field - // else passing SQLBinaryOpExpr to FieldMaker.makeFieldImpl tries to convert to SQLMethodInvokeExpr - // and throws SQLParserException - if (hasNullOrderInBinaryOrderExpr(expr)) { - return ((SQLBinaryOpExpr) expr).getLeft(); - } - return expr; - } - - private boolean hasNullOrderInBinaryOrderExpr(SQLExpr expr) { - /** - * Valid AST that meets ORDER BY IS NULL/NOT NULL condition (true) - * - * SQLSelectOrderByItem - * | - * SQLBinaryOpExpr (Is || IsNot) - * / \ - * SQLIdentifierExpr SQLNullExpr - */ - if (!(expr instanceof SQLBinaryOpExpr)) { - return false; - } - - // check "shape of expression": - SQLBinaryOpExpr binaryExpr = (SQLBinaryOpExpr) expr; - if (!(binaryExpr.getLeft() instanceof SQLIdentifierExpr)|| !(binaryExpr.getRight() instanceof SQLNullExpr)) { - return false; - } - - // check that operator IS or IS NOT - SQLBinaryOperator operator = binaryExpr.getOperator(); - return operator == SQLBinaryOperator.Is || operator == SQLBinaryOperator.IsNot; - + List sameAliases = new ArrayList<>(); + if (where.getWheres() != null && where.getWheres().size() > 0) { + for (Where innerWhere : where.getWheres()) { + sameAliases.add(sameAliasWhere(innerWhere, aliases)); + } } - private void findLimit(MySqlSelectQueryBlock.Limit limit, Select select) { - - if (limit == null) { - return; - } + if (sameAliases.contains(null)) { + return null; + } + String firstAlias = sameAliases.get(0); + // return null if more than one alias + for (String alias : sameAliases) { + if (!alias.equals(firstAlias)) { + return null; + } + } + return firstAlias; + } + + private void addOrderByToSelect( + Select select, + MySqlSelectQueryBlock queryBlock, + List items, + String alias) + throws SqlParseException { + + Map aliasesToExpressions = + queryBlock.getSelectList().stream() + .filter(item -> item.getAlias() != null) + .collect(Collectors.toMap(SQLSelectItem::getAlias, SQLSelectItem::getExpr)); + + for (SQLSelectOrderByItem sqlSelectOrderByItem : items) { + if (sqlSelectOrderByItem.getType() == null) { + sqlSelectOrderByItem.setType(SQLOrderingSpecification.ASC); + } + String type = sqlSelectOrderByItem.getType().toString(); + SQLExpr expr = extractExprFromOrderExpr(sqlSelectOrderByItem); + + if (expr instanceof SQLIdentifierExpr) { + if (queryBlock.getGroupBy() == null || queryBlock.getGroupBy().getItems().isEmpty()) { + if (aliasesToExpressions.containsKey(((SQLIdentifierExpr) expr).getName())) { + expr = aliasesToExpressions.get(((SQLIdentifierExpr) expr).getName()); + } + } + } + + Field field = fieldMaker.makeField(expr, null, null); + + SQLExpr sqlExpr = sqlSelectOrderByItem.getExpr(); + if (sqlExpr instanceof SQLBinaryOpExpr && hasNullOrderInBinaryOrderExpr(sqlExpr)) { + // override Field.expression to SQLBinaryOpExpr, + // which was set by FieldMaker.makeField() to SQLIdentifierExpr above + field.setExpression(sqlExpr); + } + + String orderByName; + if (field.isScriptField()) { + MethodField methodField = (MethodField) field; + + // 0 - generated field name + final int SCRIPT_CONTENT_INDEX = 1; + orderByName = methodField.getParams().get(SCRIPT_CONTENT_INDEX).toString(); + + } else { + orderByName = field.toString(); + } + + orderByName = orderByName.replace("`", ""); + if (alias != null) { + orderByName = orderByName.replaceFirst(alias + "\\.", ""); + } + select.addOrderBy(field.getNestedPath(), orderByName, type, field); + } + } - select.setRowCount(Integer.parseInt(limit.getRowCount().toString())); + private SQLExpr extractExprFromOrderExpr(SQLSelectOrderByItem sqlSelectOrderByItem) { + SQLExpr expr = sqlSelectOrderByItem.getExpr(); - if (limit.getOffset() != null) { - select.setOffset(Integer.parseInt(limit.getOffset().toString())); - } + // extract SQLIdentifier from Order IS NULL/NOT NULL expression to generate Field + // else passing SQLBinaryOpExpr to FieldMaker.makeFieldImpl tries to convert to + // SQLMethodInvokeExpr + // and throws SQLParserException + if (hasNullOrderInBinaryOrderExpr(expr)) { + return ((SQLBinaryOpExpr) expr).getLeft(); } + return expr; + } + private boolean hasNullOrderInBinaryOrderExpr(SQLExpr expr) { /** - * Parse the from clause + * Valid AST that meets ORDER BY IS NULL/NOT NULL condition (true) * - * @param from the from clause. - * @return list of From objects represents all the sources. + *

SQLSelectOrderByItem | SQLBinaryOpExpr (Is || IsNot) / \ SQLIdentifierExpr SQLNullExpr */ - private List findFrom(SQLTableSource from) { - boolean isSqlExprTable = from.getClass().isAssignableFrom(SQLExprTableSource.class); - - if (isSqlExprTable) { - SQLExprTableSource fromExpr = (SQLExprTableSource) from; - String[] split = fromExpr.getExpr().toString().split(","); - - ArrayList fromList = new ArrayList<>(); - for (String source : split) { - fromList.add(new From(source.trim(), fromExpr.getAlias())); - } - return fromList; - } + if (!(expr instanceof SQLBinaryOpExpr)) { + return false; + } - SQLJoinTableSource joinTableSource = ((SQLJoinTableSource) from); - List fromList = new ArrayList<>(); - fromList.addAll(findFrom(joinTableSource.getLeft())); - fromList.addAll(findFrom(joinTableSource.getRight())); - return fromList; + // check "shape of expression": + SQLBinaryOpExpr binaryExpr = (SQLBinaryOpExpr) expr; + if (!(binaryExpr.getLeft() instanceof SQLIdentifierExpr) + || !(binaryExpr.getRight() instanceof SQLNullExpr)) { + return false; } - public JoinSelect parseJoinSelect(SQLQueryExpr sqlExpr) throws SqlParseException { + // check that operator IS or IS NOT + SQLBinaryOperator operator = binaryExpr.getOperator(); + return operator == SQLBinaryOperator.Is || operator == SQLBinaryOperator.IsNot; + } - MySqlSelectQueryBlock query = (MySqlSelectQueryBlock) sqlExpr.getSubQuery().getQuery(); + private void findLimit(MySqlSelectQueryBlock.Limit limit, Select select) { - List joinedFrom = findJoinedFrom(query.getFrom()); - if (joinedFrom.size() != 2) { - throw new RuntimeException("currently supports only 2 tables join"); - } + if (limit == null) { + return; + } - JoinSelect joinSelect = createBasicJoinSelectAccordingToTableSource((SQLJoinTableSource) query.getFrom()); - List hints = parseHints(query.getHints()); - joinSelect.setHints(hints); - String firstTableAlias = joinedFrom.get(0).getAlias(); - String secondTableAlias = joinedFrom.get(1).getAlias(); - Map aliasToWhere = splitAndFindWhere(query.getWhere(), firstTableAlias, secondTableAlias); - Map> aliasToOrderBy = splitAndFindOrder(query.getOrderBy(), firstTableAlias, - secondTableAlias); - List connectedConditions = getConditionsFlatten(joinSelect.getConnectedWhere()); - joinSelect.setConnectedConditions(connectedConditions); - fillTableSelectedJoin(joinSelect.getFirstTable(), query, joinedFrom.get(0), - aliasToWhere.get(firstTableAlias), aliasToOrderBy.get(firstTableAlias), connectedConditions); - fillTableSelectedJoin(joinSelect.getSecondTable(), query, joinedFrom.get(1), - aliasToWhere.get(secondTableAlias), aliasToOrderBy.get(secondTableAlias), connectedConditions); - - updateJoinLimit(query.getLimit(), joinSelect); - - //todo: throw error feature not supported: no group bys on joins ? - return joinSelect; - } - - private Map> splitAndFindOrder(SQLOrderBy orderBy, String firstTableAlias, - String secondTableAlias) - throws SqlParseException { - Map> aliasToOrderBys = new HashMap<>(); - aliasToOrderBys.put(firstTableAlias, new ArrayList<>()); - aliasToOrderBys.put(secondTableAlias, new ArrayList<>()); - if (orderBy == null) { - return aliasToOrderBys; - } - List orderByItems = orderBy.getItems(); - for (SQLSelectOrderByItem orderByItem : orderByItems) { - if (orderByItem.getExpr().toString().startsWith(firstTableAlias + ".")) { - aliasToOrderBys.get(firstTableAlias).add(orderByItem); - } else if (orderByItem.getExpr().toString().startsWith(secondTableAlias + ".")) { - aliasToOrderBys.get(secondTableAlias).add(orderByItem); - } else { - throw new SqlParseException("order by field on join request should have alias before, got " - + orderByItem.getExpr().toString()); - } + select.setRowCount(Integer.parseInt(limit.getRowCount().toString())); - } - return aliasToOrderBys; + if (limit.getOffset() != null) { + select.setOffset(Integer.parseInt(limit.getOffset().toString())); } - - private void updateJoinLimit(MySqlSelectQueryBlock.Limit limit, JoinSelect joinSelect) { - if (limit != null && limit.getRowCount() != null) { - int sizeLimit = Integer.parseInt(limit.getRowCount().toString()); - joinSelect.setTotalLimit(sizeLimit); - } + } + + /** + * Parse the from clause + * + * @param from the from clause. + * @return list of From objects represents all the sources. + */ + private List findFrom(SQLTableSource from) { + boolean isSqlExprTable = from.getClass().isAssignableFrom(SQLExprTableSource.class); + + if (isSqlExprTable) { + SQLExprTableSource fromExpr = (SQLExprTableSource) from; + String[] split = fromExpr.getExpr().toString().split(","); + + ArrayList fromList = new ArrayList<>(); + for (String source : split) { + fromList.add(new From(source.trim(), fromExpr.getAlias())); + } + return fromList; } - private List parseHints(List sqlHints) throws SqlParseException { - List hints = new ArrayList<>(); - for (SQLCommentHint sqlHint : sqlHints) { - Hint hint = HintFactory.getHintFromString(sqlHint.getText()); - if (hint != null) { - hints.add(hint); - } - } - return hints; - } + SQLJoinTableSource joinTableSource = ((SQLJoinTableSource) from); + List fromList = new ArrayList<>(); + fromList.addAll(findFrom(joinTableSource.getLeft())); + fromList.addAll(findFrom(joinTableSource.getRight())); + return fromList; + } - private JoinSelect createBasicJoinSelectAccordingToTableSource(SQLJoinTableSource joinTableSource) - throws SqlParseException { - JoinSelect joinSelect = new JoinSelect(); - if (joinTableSource.getCondition() != null) { - Where where = Where.newInstance(); - WhereParser whereParser = new WhereParser(this, joinTableSource.getCondition()); - whereParser.parseWhere(joinTableSource.getCondition(), where); - joinSelect.setConnectedWhere(where); - } - SQLJoinTableSource.JoinType joinType = joinTableSource.getJoinType(); - joinSelect.setJoinType(joinType); - return joinSelect; - } + public JoinSelect parseJoinSelect(SQLQueryExpr sqlExpr) throws SqlParseException { - private Map splitAndFindWhere(SQLExpr whereExpr, String firstTableAlias, String secondTableAlias) - throws SqlParseException { - WhereParser whereParser = new WhereParser(this, whereExpr); - Where where = whereParser.findWhere(); - return splitWheres(where, firstTableAlias, secondTableAlias); - } + MySqlSelectQueryBlock query = (MySqlSelectQueryBlock) sqlExpr.getSubQuery().getQuery(); - private void fillTableSelectedJoin(TableOnJoinSelect tableOnJoin, MySqlSelectQueryBlock query, From tableFrom, - Where where, List orderBys, List conditions) - throws SqlParseException { - String alias = tableFrom.getAlias(); - fillBasicTableSelectJoin(tableOnJoin, tableFrom, where, orderBys, query); - tableOnJoin.setConnectedFields(getConnectedFields(conditions, alias)); - tableOnJoin.setSelectedFields(new ArrayList<>(tableOnJoin.getFields())); - tableOnJoin.setAlias(alias); - tableOnJoin.fillSubQueries(); + List joinedFrom = findJoinedFrom(query.getFrom()); + if (joinedFrom.size() != 2) { + throw new RuntimeException("currently supports only 2 tables join"); } - private List getConnectedFields(List conditions, String alias) throws SqlParseException { - List fields = new ArrayList<>(); - String prefix = alias + "."; - for (Condition condition : conditions) { - if (condition.getName().startsWith(prefix)) { - fields.add(new Field(condition.getName().replaceFirst(prefix, ""), null)); - } else { - if (!((condition.getValue() instanceof SQLPropertyExpr) - || (condition.getValue() instanceof SQLIdentifierExpr) - || (condition.getValue() instanceof String))) { - throw new SqlParseException("Illegal condition content: " + condition.toString()); - } - String aliasDotValue = condition.getValue().toString(); - int indexOfDot = aliasDotValue.indexOf("."); - String owner = aliasDotValue.substring(0, indexOfDot); - if (owner.equals(alias)) { - fields.add(new Field(aliasDotValue.substring(indexOfDot + 1), null)); - } - } - } - return fields; + JoinSelect joinSelect = + createBasicJoinSelectAccordingToTableSource((SQLJoinTableSource) query.getFrom()); + List hints = parseHints(query.getHints()); + joinSelect.setHints(hints); + String firstTableAlias = joinedFrom.get(0).getAlias(); + String secondTableAlias = joinedFrom.get(1).getAlias(); + Map aliasToWhere = + splitAndFindWhere(query.getWhere(), firstTableAlias, secondTableAlias); + Map> aliasToOrderBy = + splitAndFindOrder(query.getOrderBy(), firstTableAlias, secondTableAlias); + List connectedConditions = getConditionsFlatten(joinSelect.getConnectedWhere()); + joinSelect.setConnectedConditions(connectedConditions); + fillTableSelectedJoin( + joinSelect.getFirstTable(), + query, + joinedFrom.get(0), + aliasToWhere.get(firstTableAlias), + aliasToOrderBy.get(firstTableAlias), + connectedConditions); + fillTableSelectedJoin( + joinSelect.getSecondTable(), + query, + joinedFrom.get(1), + aliasToWhere.get(secondTableAlias), + aliasToOrderBy.get(secondTableAlias), + connectedConditions); + + updateJoinLimit(query.getLimit(), joinSelect); + + // todo: throw error feature not supported: no group bys on joins ? + return joinSelect; + } + + private Map> splitAndFindOrder( + SQLOrderBy orderBy, String firstTableAlias, String secondTableAlias) + throws SqlParseException { + Map> aliasToOrderBys = new HashMap<>(); + aliasToOrderBys.put(firstTableAlias, new ArrayList<>()); + aliasToOrderBys.put(secondTableAlias, new ArrayList<>()); + if (orderBy == null) { + return aliasToOrderBys; } - - private void fillBasicTableSelectJoin(TableOnJoinSelect select, From from, Where where, - List orderBys, MySqlSelectQueryBlock query) - throws SqlParseException { - select.getFrom().add(from); - findSelect(query, select, from.getAlias()); - select.setWhere(where); - addOrderByToSelect(select, query, orderBys, from.getAlias()); + List orderByItems = orderBy.getItems(); + for (SQLSelectOrderByItem orderByItem : orderByItems) { + if (orderByItem.getExpr().toString().startsWith(firstTableAlias + ".")) { + aliasToOrderBys.get(firstTableAlias).add(orderByItem); + } else if (orderByItem.getExpr().toString().startsWith(secondTableAlias + ".")) { + aliasToOrderBys.get(secondTableAlias).add(orderByItem); + } else { + throw new SqlParseException( + "order by field on join request should have alias before, got " + + orderByItem.getExpr().toString()); + } } + return aliasToOrderBys; + } - private List getJoinConditionsFlatten(SQLJoinTableSource from) throws SqlParseException { - List conditions = new ArrayList<>(); - if (from.getCondition() == null) { - return conditions; - } - Where where = Where.newInstance(); - WhereParser whereParser = new WhereParser(this, from.getCondition()); - whereParser.parseWhere(from.getCondition(), where); - addIfConditionRecursive(where, conditions); - return conditions; + private void updateJoinLimit(MySqlSelectQueryBlock.Limit limit, JoinSelect joinSelect) { + if (limit != null && limit.getRowCount() != null) { + int sizeLimit = Integer.parseInt(limit.getRowCount().toString()); + joinSelect.setTotalLimit(sizeLimit); } - - private List getConditionsFlatten(Where where) throws SqlParseException { - List conditions = new ArrayList<>(); - if (where == null) { - return conditions; - } - addIfConditionRecursive(where, conditions); - return conditions; + } + + private List parseHints(List sqlHints) throws SqlParseException { + List hints = new ArrayList<>(); + for (SQLCommentHint sqlHint : sqlHints) { + Hint hint = HintFactory.getHintFromString(sqlHint.getText()); + if (hint != null) { + hints.add(hint); + } + } + return hints; + } + + private JoinSelect createBasicJoinSelectAccordingToTableSource(SQLJoinTableSource joinTableSource) + throws SqlParseException { + JoinSelect joinSelect = new JoinSelect(); + if (joinTableSource.getCondition() != null) { + Where where = Where.newInstance(); + WhereParser whereParser = new WhereParser(this, joinTableSource.getCondition()); + whereParser.parseWhere(joinTableSource.getCondition(), where); + joinSelect.setConnectedWhere(where); + } + SQLJoinTableSource.JoinType joinType = joinTableSource.getJoinType(); + joinSelect.setJoinType(joinType); + return joinSelect; + } + + private Map splitAndFindWhere( + SQLExpr whereExpr, String firstTableAlias, String secondTableAlias) throws SqlParseException { + WhereParser whereParser = new WhereParser(this, whereExpr); + Where where = whereParser.findWhere(); + return splitWheres(where, firstTableAlias, secondTableAlias); + } + + private void fillTableSelectedJoin( + TableOnJoinSelect tableOnJoin, + MySqlSelectQueryBlock query, + From tableFrom, + Where where, + List orderBys, + List conditions) + throws SqlParseException { + String alias = tableFrom.getAlias(); + fillBasicTableSelectJoin(tableOnJoin, tableFrom, where, orderBys, query); + tableOnJoin.setConnectedFields(getConnectedFields(conditions, alias)); + tableOnJoin.setSelectedFields(new ArrayList<>(tableOnJoin.getFields())); + tableOnJoin.setAlias(alias); + tableOnJoin.fillSubQueries(); + } + + private List getConnectedFields(List conditions, String alias) + throws SqlParseException { + List fields = new ArrayList<>(); + String prefix = alias + "."; + for (Condition condition : conditions) { + if (condition.getName().startsWith(prefix)) { + fields.add(new Field(condition.getName().replaceFirst(prefix, ""), null)); + } else { + if (!((condition.getValue() instanceof SQLPropertyExpr) + || (condition.getValue() instanceof SQLIdentifierExpr) + || (condition.getValue() instanceof String))) { + throw new SqlParseException("Illegal condition content: " + condition.toString()); + } + String aliasDotValue = condition.getValue().toString(); + int indexOfDot = aliasDotValue.indexOf("."); + String owner = aliasDotValue.substring(0, indexOfDot); + if (owner.equals(alias)) { + fields.add(new Field(aliasDotValue.substring(indexOfDot + 1), null)); + } + } + } + return fields; + } + + private void fillBasicTableSelectJoin( + TableOnJoinSelect select, + From from, + Where where, + List orderBys, + MySqlSelectQueryBlock query) + throws SqlParseException { + select.getFrom().add(from); + findSelect(query, select, from.getAlias()); + select.setWhere(where); + addOrderByToSelect(select, query, orderBys, from.getAlias()); + } + + private List getJoinConditionsFlatten(SQLJoinTableSource from) + throws SqlParseException { + List conditions = new ArrayList<>(); + if (from.getCondition() == null) { + return conditions; + } + Where where = Where.newInstance(); + WhereParser whereParser = new WhereParser(this, from.getCondition()); + whereParser.parseWhere(from.getCondition(), where); + addIfConditionRecursive(where, conditions); + return conditions; + } + + private List getConditionsFlatten(Where where) throws SqlParseException { + List conditions = new ArrayList<>(); + if (where == null) { + return conditions; + } + addIfConditionRecursive(where, conditions); + return conditions; + } + + private Map splitWheres(Where where, String... aliases) throws SqlParseException { + Map aliasToWhere = new HashMap<>(); + for (String alias : aliases) { + aliasToWhere.put(alias, null); + } + if (where == null) { + return aliasToWhere; } + String allWhereFromSameAlias = sameAliasWhere(where, aliases); + if (allWhereFromSameAlias != null) { + removeAliasPrefix(where, allWhereFromSameAlias); + aliasToWhere.put(allWhereFromSameAlias, where); + return aliasToWhere; + } + for (Where innerWhere : where.getWheres()) { + String sameAlias = sameAliasWhere(innerWhere, aliases); + if (sameAlias == null) { + throw new SqlParseException( + "Currently support only one hierarchy on different tables where"); + } + removeAliasPrefix(innerWhere, sameAlias); + Where aliasCurrentWhere = aliasToWhere.get(sameAlias); + if (aliasCurrentWhere == null) { + aliasToWhere.put(sameAlias, innerWhere); + } else { + Where andWhereContainer = Where.newInstance(); + andWhereContainer.addWhere(aliasCurrentWhere); + andWhereContainer.addWhere(innerWhere); + aliasToWhere.put(sameAlias, andWhereContainer); + } + } - private Map splitWheres(Where where, String... aliases) throws SqlParseException { - Map aliasToWhere = new HashMap<>(); - for (String alias : aliases) { - aliasToWhere.put(alias, null); - } - if (where == null) { - return aliasToWhere; - } + return aliasToWhere; + } - String allWhereFromSameAlias = sameAliasWhere(where, aliases); - if (allWhereFromSameAlias != null) { - removeAliasPrefix(where, allWhereFromSameAlias); - aliasToWhere.put(allWhereFromSameAlias, where); - return aliasToWhere; - } - for (Where innerWhere : where.getWheres()) { - String sameAlias = sameAliasWhere(innerWhere, aliases); - if (sameAlias == null) { - throw new SqlParseException("Currently support only one hierarchy on different tables where"); - } - removeAliasPrefix(innerWhere, sameAlias); - Where aliasCurrentWhere = aliasToWhere.get(sameAlias); - if (aliasCurrentWhere == null) { - aliasToWhere.put(sameAlias, innerWhere); - } else { - Where andWhereContainer = Where.newInstance(); - andWhereContainer.addWhere(aliasCurrentWhere); - andWhereContainer.addWhere(innerWhere); - aliasToWhere.put(sameAlias, andWhereContainer); - } - } + private void removeAliasPrefix(Where where, String alias) { - return aliasToWhere; + if (where instanceof Condition) { + Condition cond = (Condition) where; + String aliasPrefix = alias + "."; + cond.setName(cond.getName().replaceFirst(aliasPrefix, "")); + return; } - - private void removeAliasPrefix(Where where, String alias) { - - if (where instanceof Condition) { - Condition cond = (Condition) where; - String aliasPrefix = alias + "."; - cond.setName(cond.getName().replaceFirst(aliasPrefix, "")); - return; - } - for (Where innerWhere : where.getWheres()) { - removeAliasPrefix(innerWhere, alias); - } + for (Where innerWhere : where.getWheres()) { + removeAliasPrefix(innerWhere, alias); } - - private void addIfConditionRecursive(Where where, List conditions) throws SqlParseException { - if (where instanceof Condition) { - Condition cond = (Condition) where; - if (!((cond.getValue() instanceof SQLIdentifierExpr) || (cond.getValue() instanceof SQLPropertyExpr) - || (cond.getValue() instanceof String))) { - throw new SqlParseException("conditions on join should be one side is secondTable OPEAR firstTable, " - + "condition was:" + cond.toString()); - } - conditions.add(cond); - } - for (Where innerWhere : where.getWheres()) { - addIfConditionRecursive(innerWhere, conditions); - } + } + + private void addIfConditionRecursive(Where where, List conditions) + throws SqlParseException { + if (where instanceof Condition) { + Condition cond = (Condition) where; + if (!((cond.getValue() instanceof SQLIdentifierExpr) + || (cond.getValue() instanceof SQLPropertyExpr) + || (cond.getValue() instanceof String))) { + throw new SqlParseException( + "conditions on join should be one side is secondTable OPEAR firstTable, " + + "condition was:" + + cond.toString()); + } + conditions.add(cond); } - - private List findJoinedFrom(SQLTableSource from) { - SQLJoinTableSource joinTableSource = ((SQLJoinTableSource) from); - List fromList = new ArrayList<>(); - fromList.addAll(findFrom(joinTableSource.getLeft())); - fromList.addAll(findFrom(joinTableSource.getRight())); - return fromList; + for (Where innerWhere : where.getWheres()) { + addIfConditionRecursive(innerWhere, conditions); } - - + } + + private List findJoinedFrom(SQLTableSource from) { + SQLJoinTableSource joinTableSource = ((SQLJoinTableSource) from); + List fromList = new ArrayList<>(); + fromList.addAll(findFrom(joinTableSource.getLeft())); + fromList.addAll(findFrom(joinTableSource.getRight())); + return fromList; + } } diff --git a/legacy/src/main/java/org/opensearch/sql/legacy/parser/SubQueryExpression.java b/legacy/src/main/java/org/opensearch/sql/legacy/parser/SubQueryExpression.java index 168318c490..e9b0797d00 100644 --- a/legacy/src/main/java/org/opensearch/sql/legacy/parser/SubQueryExpression.java +++ b/legacy/src/main/java/org/opensearch/sql/legacy/parser/SubQueryExpression.java @@ -3,42 +3,39 @@ * SPDX-License-Identifier: Apache-2.0 */ - package org.opensearch.sql.legacy.parser; import org.opensearch.sql.legacy.domain.Select; -/** - * Created by Eliran on 3/10/2015. - */ +/** Created by Eliran on 3/10/2015. */ public class SubQueryExpression { - private Object[] values; - private Select select; - private String returnField; - - public SubQueryExpression(Select innerSelect) { - this.select = innerSelect; - this.returnField = select.getFields().get(0).getName(); - values = null; - } - - public Object[] getValues() { - return values; - } - - public void setValues(Object[] values) { - this.values = values; - } - - public Select getSelect() { - return select; - } - - public void setSelect(Select select) { - this.select = select; - } - - public String getReturnField() { - return returnField; - } + private Object[] values; + private Select select; + private String returnField; + + public SubQueryExpression(Select innerSelect) { + this.select = innerSelect; + this.returnField = select.getFields().get(0).getName(); + values = null; + } + + public Object[] getValues() { + return values; + } + + public void setValues(Object[] values) { + this.values = values; + } + + public Select getSelect() { + return select; + } + + public void setSelect(Select select) { + this.select = select; + } + + public String getReturnField() { + return returnField; + } } diff --git a/legacy/src/main/java/org/opensearch/sql/legacy/parser/SubQueryParser.java b/legacy/src/main/java/org/opensearch/sql/legacy/parser/SubQueryParser.java index 71b19db0cf..b6d04ddd54 100644 --- a/legacy/src/main/java/org/opensearch/sql/legacy/parser/SubQueryParser.java +++ b/legacy/src/main/java/org/opensearch/sql/legacy/parser/SubQueryParser.java @@ -3,7 +3,6 @@ * SPDX-License-Identifier: Apache-2.0 */ - package org.opensearch.sql.legacy.parser; import com.alibaba.druid.sql.ast.SQLExpr; @@ -23,88 +22,89 @@ import org.opensearch.sql.legacy.domain.Where; import org.opensearch.sql.legacy.exception.SqlParseException; -/** - * Definition of SubQuery Parser - */ +/** Definition of SubQuery Parser */ public class SubQueryParser { - private final SqlParser sqlParser; + private final SqlParser sqlParser; - public SubQueryParser(SqlParser sqlParser) { - this.sqlParser = sqlParser; - } + public SubQueryParser(SqlParser sqlParser) { + this.sqlParser = sqlParser; + } - public boolean containSubqueryInFrom(MySqlSelectQueryBlock query) { - return query.getFrom() instanceof SQLSubqueryTableSource; - } + public boolean containSubqueryInFrom(MySqlSelectQueryBlock query) { + return query.getFrom() instanceof SQLSubqueryTableSource; + } - public Select parseSubQueryInFrom(MySqlSelectQueryBlock query) throws SqlParseException { - assert query.getFrom() instanceof SQLSubqueryTableSource; + public Select parseSubQueryInFrom(MySqlSelectQueryBlock query) throws SqlParseException { + assert query.getFrom() instanceof SQLSubqueryTableSource; - Select select = sqlParser.parseSelect( - (MySqlSelectQueryBlock) ((SQLSubqueryTableSource) query.getFrom()).getSelect() - .getQuery()); - String subQueryAlias = query.getFrom().getAlias(); - return pushSelect(query.getSelectList(), select, subQueryAlias); - } + Select select = + sqlParser.parseSelect( + (MySqlSelectQueryBlock) + ((SQLSubqueryTableSource) query.getFrom()).getSelect().getQuery()); + String subQueryAlias = query.getFrom().getAlias(); + return pushSelect(query.getSelectList(), select, subQueryAlias); + } - private Select pushSelect(List selectItems, Select subquerySelect, String subQueryAlias) { - Map> fieldAliasRewriter = prepareFieldAliasRewriter( - selectItems, - subQueryAlias); + private Select pushSelect( + List selectItems, Select subquerySelect, String subQueryAlias) { + Map> fieldAliasRewriter = + prepareFieldAliasRewriter(selectItems, subQueryAlias); - //1. rewrite field in select list - Iterator fieldIterator = subquerySelect.getFields().iterator(); - while (fieldIterator.hasNext()) { - Field field = fieldIterator.next(); - /* - * return true if the subquerySelectItem in the final select list. - * for example, subquerySelectItem is "SUM(emp.empno) as TEMP", - * and final select list is TEMP. then return true. - */ - String fieldIdentifier = Strings.isNullOrEmpty(field.getAlias()) ? field.getName() : field.getAlias(); - if (fieldAliasRewriter.containsKey(fieldIdentifier)) { - field.setAlias(fieldAliasRewriter.get(fieldIdentifier).apply(fieldIdentifier)); - } else { - fieldIterator.remove(); - } - } - - //2. rewrite field in order by - for (Order orderBy : subquerySelect.getOrderBys()) { - if (fieldAliasRewriter.containsKey(orderBy.getName())) { - String replaceOrderName = fieldAliasRewriter.get(orderBy.getName()).apply(orderBy.getName()); - orderBy.setName(replaceOrderName); - orderBy.getSortField().setName(replaceOrderName); - } - } + // 1. rewrite field in select list + Iterator fieldIterator = subquerySelect.getFields().iterator(); + while (fieldIterator.hasNext()) { + Field field = fieldIterator.next(); + /* + * return true if the subquerySelectItem in the final select list. + * for example, subquerySelectItem is "SUM(emp.empno) as TEMP", + * and final select list is TEMP. then return true. + */ + String fieldIdentifier = + Strings.isNullOrEmpty(field.getAlias()) ? field.getName() : field.getAlias(); + if (fieldAliasRewriter.containsKey(fieldIdentifier)) { + field.setAlias(fieldAliasRewriter.get(fieldIdentifier).apply(fieldIdentifier)); + } else { + fieldIterator.remove(); + } + } - // 3. rewrite field in having - if (subquerySelect.getHaving() != null) { - for (Where condition : subquerySelect.getHaving().getConditions()) { - Condition cond = (Condition) condition; - if (fieldAliasRewriter.containsKey(cond.getName())) { - String replaceOrderName = fieldAliasRewriter.get(cond.getName()).apply(cond.getName()); - cond.setName(replaceOrderName); - } - } - } - return subquerySelect; + // 2. rewrite field in order by + for (Order orderBy : subquerySelect.getOrderBys()) { + if (fieldAliasRewriter.containsKey(orderBy.getName())) { + String replaceOrderName = + fieldAliasRewriter.get(orderBy.getName()).apply(orderBy.getName()); + orderBy.setName(replaceOrderName); + orderBy.getSortField().setName(replaceOrderName); + } } - private Map> prepareFieldAliasRewriter(List selectItems, - String owner) { - HashMap> selectMap = new HashMap<>(); - for (SQLSelectItem item : selectItems) { - if (Strings.isNullOrEmpty(item.getAlias())) { - selectMap.put(getFieldName(item.getExpr(), owner), Function.identity()); - } else { - selectMap.put(getFieldName(item.getExpr(), owner), s -> item.getAlias()); - } + // 3. rewrite field in having + if (subquerySelect.getHaving() != null) { + for (Where condition : subquerySelect.getHaving().getConditions()) { + Condition cond = (Condition) condition; + if (fieldAliasRewriter.containsKey(cond.getName())) { + String replaceOrderName = fieldAliasRewriter.get(cond.getName()).apply(cond.getName()); + cond.setName(replaceOrderName); } - return selectMap; + } } + return subquerySelect; + } - private String getFieldName(SQLExpr expr, String owner) { - return expr.toString().replace(String.format("%s.", owner), ""); + private Map> prepareFieldAliasRewriter( + List selectItems, String owner) { + HashMap> selectMap = new HashMap<>(); + for (SQLSelectItem item : selectItems) { + if (Strings.isNullOrEmpty(item.getAlias())) { + selectMap.put(getFieldName(item.getExpr(), owner), Function.identity()); + } else { + selectMap.put(getFieldName(item.getExpr(), owner), s -> item.getAlias()); + } } + return selectMap; + } + + private String getFieldName(SQLExpr expr, String owner) { + return expr.toString().replace(String.format("%s.", owner), ""); + } } diff --git a/legacy/src/main/java/org/opensearch/sql/legacy/parser/WhereParser.java b/legacy/src/main/java/org/opensearch/sql/legacy/parser/WhereParser.java index c3ea5270e3..a329d1ed52 100644 --- a/legacy/src/main/java/org/opensearch/sql/legacy/parser/WhereParser.java +++ b/legacy/src/main/java/org/opensearch/sql/legacy/parser/WhereParser.java @@ -3,7 +3,6 @@ * SPDX-License-Identifier: Apache-2.0 */ - package org.opensearch.sql.legacy.parser; import com.alibaba.druid.sql.ast.SQLExpr; @@ -43,637 +42,808 @@ import org.opensearch.sql.legacy.utils.SQLFunctions; import org.opensearch.sql.legacy.utils.Util; -/** - * Created by allwefantasy on 9/2/16. - */ +/** Created by allwefantasy on 9/2/16. */ public class WhereParser { - private FieldMaker fieldMaker; + private FieldMaker fieldMaker; - private MySqlSelectQueryBlock query; - private SQLDeleteStatement delete; - private SQLExpr where; - private SqlParser sqlParser; + private MySqlSelectQueryBlock query; + private SQLDeleteStatement delete; + private SQLExpr where; + private SqlParser sqlParser; - public WhereParser(SqlParser sqlParser, MySqlSelectQueryBlock query, FieldMaker fieldMaker) { - this.sqlParser = sqlParser; - this.where = query.getWhere(); - - this.query = query; - this.fieldMaker = fieldMaker; - } + public WhereParser(SqlParser sqlParser, MySqlSelectQueryBlock query, FieldMaker fieldMaker) { + this.sqlParser = sqlParser; + this.where = query.getWhere(); - public WhereParser(SqlParser sqlParser, SQLDeleteStatement delete) { - this(sqlParser, delete.getWhere()); + this.query = query; + this.fieldMaker = fieldMaker; + } - this.delete = delete; - } + public WhereParser(SqlParser sqlParser, SQLDeleteStatement delete) { + this(sqlParser, delete.getWhere()); - public WhereParser(SqlParser sqlParser, SQLExpr expr) { - this(sqlParser); - this.where = expr; - } + this.delete = delete; + } - public WhereParser(SqlParser sqlParser) { - this.sqlParser = sqlParser; - this.fieldMaker = new FieldMaker(); - } + public WhereParser(SqlParser sqlParser, SQLExpr expr) { + this(sqlParser); + this.where = expr; + } - public Where findWhere() throws SqlParseException { - if (where == null) { - return null; - } + public WhereParser(SqlParser sqlParser) { + this.sqlParser = sqlParser; + this.fieldMaker = new FieldMaker(); + } - Where myWhere = Where.newInstance(); - parseWhere(where, myWhere); - return myWhere; + public Where findWhere() throws SqlParseException { + if (where == null) { + return null; } - public void parseWhere(SQLExpr expr, Where where) throws SqlParseException { - if (expr instanceof SQLBinaryOpExpr) { - SQLBinaryOpExpr bExpr = (SQLBinaryOpExpr) expr; - if (explainSpecialCondWithBothSidesAreLiterals(bExpr, where)) { - return; - } - if (explainSpecialCondWithBothSidesAreProperty(bExpr, where)) { - return; - } - } - - if (expr instanceof SQLBinaryOpExpr && !isCond((SQLBinaryOpExpr) expr)) { - SQLBinaryOpExpr bExpr = (SQLBinaryOpExpr) expr; - routeCond(bExpr, bExpr.getLeft(), where); - routeCond(bExpr, bExpr.getRight(), where); - } else if (expr instanceof SQLNotExpr) { - parseWhere(((SQLNotExpr) expr).getExpr(), where); - negateWhere(where); - } else { - explainCond("AND", expr, where); - } + Where myWhere = Where.newInstance(); + parseWhere(where, myWhere); + return myWhere; + } + + public void parseWhere(SQLExpr expr, Where where) throws SqlParseException { + if (expr instanceof SQLBinaryOpExpr) { + SQLBinaryOpExpr bExpr = (SQLBinaryOpExpr) expr; + if (explainSpecialCondWithBothSidesAreLiterals(bExpr, where)) { + return; + } + if (explainSpecialCondWithBothSidesAreProperty(bExpr, where)) { + return; + } } - private void negateWhere(Where where) throws SqlParseException { - for (Where sub : where.getWheres()) { - if (sub instanceof Condition) { - Condition cond = (Condition) sub; - cond.setOPERATOR(cond.getOPERATOR().negative()); - } else { - negateWhere(sub); - } - sub.setConn(sub.getConn().negative()); - } + if (expr instanceof SQLBinaryOpExpr && !isCond((SQLBinaryOpExpr) expr)) { + SQLBinaryOpExpr bExpr = (SQLBinaryOpExpr) expr; + routeCond(bExpr, bExpr.getLeft(), where); + routeCond(bExpr, bExpr.getRight(), where); + } else if (expr instanceof SQLNotExpr) { + parseWhere(((SQLNotExpr) expr).getExpr(), where); + negateWhere(where); + } else { + explainCond("AND", expr, where); } - - //some where conditions eg. 1=1 or 3>2 or 'a'='b' - private boolean explainSpecialCondWithBothSidesAreLiterals(SQLBinaryOpExpr bExpr, Where where) - throws SqlParseException { - if ((bExpr.getLeft() instanceof SQLNumericLiteralExpr || bExpr.getLeft() instanceof SQLCharExpr) - && (bExpr.getRight() instanceof SQLNumericLiteralExpr || bExpr.getRight() instanceof SQLCharExpr) - ) { - SQLMethodInvokeExpr sqlMethodInvokeExpr = new SQLMethodInvokeExpr("script", null); - String operator = bExpr.getOperator().getName(); - if (operator.equals("=")) { - operator = "=="; - } - sqlMethodInvokeExpr.addParameter( - new SQLCharExpr(Util.expr2Object(bExpr.getLeft(), "'") - + " " + operator + " " + Util.expr2Object(bExpr.getRight(), "'")) - ); - - explainCond("AND", sqlMethodInvokeExpr, where); - return true; - } - return false; + } + + private void negateWhere(Where where) throws SqlParseException { + for (Where sub : where.getWheres()) { + if (sub instanceof Condition) { + Condition cond = (Condition) sub; + cond.setOPERATOR(cond.getOPERATOR().negative()); + } else { + negateWhere(sub); + } + sub.setConn(sub.getConn().negative()); } - - //some where conditions eg. field1=field2 or field1>field2 - private boolean explainSpecialCondWithBothSidesAreProperty(SQLBinaryOpExpr bExpr, Where where) - throws SqlParseException { - //join is not support - if ((bExpr.getLeft() instanceof SQLPropertyExpr || bExpr.getLeft() instanceof SQLIdentifierExpr) - && (bExpr.getRight() instanceof SQLPropertyExpr || bExpr.getRight() instanceof SQLIdentifierExpr) - && Sets.newHashSet("=", "<", ">", ">=", "<=").contains(bExpr.getOperator().getName()) - && !Util.isFromJoinOrUnionTable(bExpr) - ) { - SQLMethodInvokeExpr sqlMethodInvokeExpr = new SQLMethodInvokeExpr("script", null); - String operator = bExpr.getOperator().getName(); - if (operator.equals("=")) { - operator = "=="; - } - - String leftProperty = Util.expr2Object(bExpr.getLeft()).toString(); - String rightProperty = Util.expr2Object(bExpr.getRight()).toString(); - if (leftProperty.split("\\.").length > 1) { - - leftProperty = leftProperty.substring(leftProperty.split("\\.")[0].length() + 1); - } - - if (rightProperty.split("\\.").length > 1) { - rightProperty = rightProperty.substring(rightProperty.split("\\.")[0].length() + 1); - } - - sqlMethodInvokeExpr.addParameter(new SQLCharExpr( - "doc['" + leftProperty + "'].value " + operator + " doc['" + rightProperty + "'].value")); - - explainCond("AND", sqlMethodInvokeExpr, where); - return true; - } - return false; + } + + // some where conditions eg. 1=1 or 3>2 or 'a'='b' + private boolean explainSpecialCondWithBothSidesAreLiterals(SQLBinaryOpExpr bExpr, Where where) + throws SqlParseException { + if ((bExpr.getLeft() instanceof SQLNumericLiteralExpr || bExpr.getLeft() instanceof SQLCharExpr) + && (bExpr.getRight() instanceof SQLNumericLiteralExpr + || bExpr.getRight() instanceof SQLCharExpr)) { + SQLMethodInvokeExpr sqlMethodInvokeExpr = new SQLMethodInvokeExpr("script", null); + String operator = bExpr.getOperator().getName(); + if (operator.equals("=")) { + operator = "=="; + } + sqlMethodInvokeExpr.addParameter( + new SQLCharExpr( + Util.expr2Object(bExpr.getLeft(), "'") + + " " + + operator + + " " + + Util.expr2Object(bExpr.getRight(), "'"))); + + explainCond("AND", sqlMethodInvokeExpr, where); + return true; } - - - private boolean isCond(SQLBinaryOpExpr expr) { - SQLExpr leftSide = expr.getLeft(); - if (leftSide instanceof SQLMethodInvokeExpr) { - return isAllowedMethodOnConditionLeft((SQLMethodInvokeExpr) leftSide, expr.getOperator()); - } - return leftSide instanceof SQLIdentifierExpr - || leftSide instanceof SQLPropertyExpr - || leftSide instanceof SQLVariantRefExpr - || leftSide instanceof SQLCastExpr; + return false; + } + + // some where conditions eg. field1=field2 or field1>field2 + private boolean explainSpecialCondWithBothSidesAreProperty(SQLBinaryOpExpr bExpr, Where where) + throws SqlParseException { + // join is not support + if ((bExpr.getLeft() instanceof SQLPropertyExpr || bExpr.getLeft() instanceof SQLIdentifierExpr) + && (bExpr.getRight() instanceof SQLPropertyExpr + || bExpr.getRight() instanceof SQLIdentifierExpr) + && Sets.newHashSet("=", "<", ">", ">=", "<=").contains(bExpr.getOperator().getName()) + && !Util.isFromJoinOrUnionTable(bExpr)) { + SQLMethodInvokeExpr sqlMethodInvokeExpr = new SQLMethodInvokeExpr("script", null); + String operator = bExpr.getOperator().getName(); + if (operator.equals("=")) { + operator = "=="; + } + + String leftProperty = Util.expr2Object(bExpr.getLeft()).toString(); + String rightProperty = Util.expr2Object(bExpr.getRight()).toString(); + if (leftProperty.split("\\.").length > 1) { + + leftProperty = leftProperty.substring(leftProperty.split("\\.")[0].length() + 1); + } + + if (rightProperty.split("\\.").length > 1) { + rightProperty = rightProperty.substring(rightProperty.split("\\.")[0].length() + 1); + } + + sqlMethodInvokeExpr.addParameter( + new SQLCharExpr( + "doc['" + + leftProperty + + "'].value " + + operator + + " doc['" + + rightProperty + + "'].value")); + + explainCond("AND", sqlMethodInvokeExpr, where); + return true; } + return false; + } - private boolean isAllowedMethodOnConditionLeft(SQLMethodInvokeExpr method, SQLBinaryOperator operator) { - return (method.getMethodName().toLowerCase().equals("nested") - || method.getMethodName().toLowerCase().equals("children") - || SQLFunctions.isFunctionTranslatedToScript(method.getMethodName()) - ) && !operator.isLogical(); + private boolean isCond(SQLBinaryOpExpr expr) { + SQLExpr leftSide = expr.getLeft(); + if (leftSide instanceof SQLMethodInvokeExpr) { + return isAllowedMethodOnConditionLeft((SQLMethodInvokeExpr) leftSide, expr.getOperator()); } - - - private void routeCond(SQLBinaryOpExpr bExpr, SQLExpr sub, Where where) throws SqlParseException { - if (sub instanceof SQLBinaryOpExpr && !isCond((SQLBinaryOpExpr) sub)) { - SQLBinaryOpExpr binarySub = (SQLBinaryOpExpr) sub; - if (binarySub.getOperator().priority != bExpr.getOperator().priority) { - Where subWhere = new Where(bExpr.getOperator().name); - where.addWhere(subWhere); - parseWhere(binarySub, subWhere); - } else { - parseWhere(binarySub, where); - } - } else if (sub instanceof SQLNotExpr) { - Where subWhere = new Where(bExpr.getOperator().name); - where.addWhere(subWhere); - parseWhere(((SQLNotExpr) sub).getExpr(), subWhere); - negateWhere(subWhere); - } else { - explainCond(bExpr.getOperator().name, sub, where); - } + return leftSide instanceof SQLIdentifierExpr + || leftSide instanceof SQLPropertyExpr + || leftSide instanceof SQLVariantRefExpr + || leftSide instanceof SQLCastExpr; + } + + private boolean isAllowedMethodOnConditionLeft( + SQLMethodInvokeExpr method, SQLBinaryOperator operator) { + return (method.getMethodName().toLowerCase().equals("nested") + || method.getMethodName().toLowerCase().equals("children") + || SQLFunctions.isFunctionTranslatedToScript(method.getMethodName())) + && !operator.isLogical(); + } + + private void routeCond(SQLBinaryOpExpr bExpr, SQLExpr sub, Where where) throws SqlParseException { + if (sub instanceof SQLBinaryOpExpr && !isCond((SQLBinaryOpExpr) sub)) { + SQLBinaryOpExpr binarySub = (SQLBinaryOpExpr) sub; + if (binarySub.getOperator().priority != bExpr.getOperator().priority) { + Where subWhere = new Where(bExpr.getOperator().name); + where.addWhere(subWhere); + parseWhere(binarySub, subWhere); + } else { + parseWhere(binarySub, where); + } + } else if (sub instanceof SQLNotExpr) { + Where subWhere = new Where(bExpr.getOperator().name); + where.addWhere(subWhere); + parseWhere(((SQLNotExpr) sub).getExpr(), subWhere); + negateWhere(subWhere); + } else { + explainCond(bExpr.getOperator().name, sub, where); } - - private void explainCond(String opear, SQLExpr expr, Where where) throws SqlParseException { - if (expr instanceof SQLBinaryOpExpr) { - SQLBinaryOpExpr soExpr = (SQLBinaryOpExpr) expr; - - boolean methodAsOpear = false; - - boolean isNested = false; - boolean isChildren = false; - - NestedType nestedType = new NestedType(); - if (nestedType.tryFillFromExpr(soExpr.getLeft())) { - soExpr.setLeft(new SQLIdentifierExpr(nestedType.field)); - isNested = true; - } - - ChildrenType childrenType = new ChildrenType(); - if (childrenType.tryFillFromExpr(soExpr.getLeft())) { - soExpr.setLeft(new SQLIdentifierExpr(childrenType.field)); - isChildren = true; - } - - if (soExpr.getRight() instanceof SQLMethodInvokeExpr) { - SQLMethodInvokeExpr method = (SQLMethodInvokeExpr) soExpr.getRight(); - String methodName = method.getMethodName().toLowerCase(); - - if (Condition.OPERATOR.methodNameToOpear.containsKey(methodName)) { - Object[] methodParametersValue = getMethodValuesWithSubQueries(method); - - final Condition condition; - // fix OPEAR - Condition.OPERATOR oper = Condition.OPERATOR.methodNameToOpear.get(methodName); - if (soExpr.getOperator() == SQLBinaryOperator.LessThanOrGreater - || soExpr.getOperator() == SQLBinaryOperator.NotEqual) { - oper = oper.negative(); - } - if (isNested) { - condition = new Condition(Where.CONN.valueOf(opear), soExpr.getLeft().toString(), - soExpr.getLeft(), oper, methodParametersValue, soExpr.getRight(), nestedType); - } else if (isChildren) { - condition = new Condition(Where.CONN.valueOf(opear), soExpr.getLeft().toString(), - soExpr.getLeft(), oper, methodParametersValue, soExpr.getRight(), childrenType); - } else { - condition = new Condition(Where.CONN.valueOf(opear), soExpr.getLeft().toString(), - soExpr.getLeft(), oper, methodParametersValue, soExpr.getRight(), null); - } - - where.addWhere(condition); - methodAsOpear = true; - } - } - - if (!methodAsOpear) { - final Condition condition; - - if (isNested) { - condition = new Condition(Where.CONN.valueOf(opear), soExpr.getLeft().toString(), soExpr.getLeft(), - soExpr.getOperator().name, parseValue(soExpr.getRight()), soExpr.getRight(), nestedType); - } else if (isChildren) { - condition = new Condition(Where.CONN.valueOf(opear), soExpr.getLeft().toString(), soExpr.getLeft(), - soExpr.getOperator().name, parseValue(soExpr.getRight()), soExpr.getRight(), childrenType); - } else { - SQLMethodInvokeExpr sqlMethodInvokeExpr = parseSQLBinaryOpExprWhoIsConditionInWhere(soExpr); - if (sqlMethodInvokeExpr == null) { - condition = new Condition(Where.CONN.valueOf(opear), soExpr.getLeft().toString(), - soExpr.getLeft(), soExpr.getOperator().name, parseValue(soExpr.getRight()), - soExpr.getRight(), null); - } else { - ScriptFilter scriptFilter = new ScriptFilter(); - if (!scriptFilter.tryParseFromMethodExpr(sqlMethodInvokeExpr)) { - throw new SqlParseException("could not parse script filter"); - } - condition = new Condition(Where.CONN.valueOf(opear), null, soExpr.getLeft(), - "SCRIPT", scriptFilter, soExpr.getRight()); - - } - - } - where.addWhere(condition); + } + + private void explainCond(String opear, SQLExpr expr, Where where) throws SqlParseException { + if (expr instanceof SQLBinaryOpExpr) { + SQLBinaryOpExpr soExpr = (SQLBinaryOpExpr) expr; + + boolean methodAsOpear = false; + + boolean isNested = false; + boolean isChildren = false; + + NestedType nestedType = new NestedType(); + if (nestedType.tryFillFromExpr(soExpr.getLeft())) { + soExpr.setLeft(new SQLIdentifierExpr(nestedType.field)); + isNested = true; + } + + ChildrenType childrenType = new ChildrenType(); + if (childrenType.tryFillFromExpr(soExpr.getLeft())) { + soExpr.setLeft(new SQLIdentifierExpr(childrenType.field)); + isChildren = true; + } + + if (soExpr.getRight() instanceof SQLMethodInvokeExpr) { + SQLMethodInvokeExpr method = (SQLMethodInvokeExpr) soExpr.getRight(); + String methodName = method.getMethodName().toLowerCase(); + + if (Condition.OPERATOR.methodNameToOpear.containsKey(methodName)) { + Object[] methodParametersValue = getMethodValuesWithSubQueries(method); + + final Condition condition; + // fix OPEAR + Condition.OPERATOR oper = Condition.OPERATOR.methodNameToOpear.get(methodName); + if (soExpr.getOperator() == SQLBinaryOperator.LessThanOrGreater + || soExpr.getOperator() == SQLBinaryOperator.NotEqual) { + oper = oper.negative(); + } + if (isNested) { + condition = + new Condition( + Where.CONN.valueOf(opear), + soExpr.getLeft().toString(), + soExpr.getLeft(), + oper, + methodParametersValue, + soExpr.getRight(), + nestedType); + } else if (isChildren) { + condition = + new Condition( + Where.CONN.valueOf(opear), + soExpr.getLeft().toString(), + soExpr.getLeft(), + oper, + methodParametersValue, + soExpr.getRight(), + childrenType); + } else { + condition = + new Condition( + Where.CONN.valueOf(opear), + soExpr.getLeft().toString(), + soExpr.getLeft(), + oper, + methodParametersValue, + soExpr.getRight(), + null); + } + + where.addWhere(condition); + methodAsOpear = true; + } + } + + if (!methodAsOpear) { + final Condition condition; + + if (isNested) { + condition = + new Condition( + Where.CONN.valueOf(opear), + soExpr.getLeft().toString(), + soExpr.getLeft(), + soExpr.getOperator().name, + parseValue(soExpr.getRight()), + soExpr.getRight(), + nestedType); + } else if (isChildren) { + condition = + new Condition( + Where.CONN.valueOf(opear), + soExpr.getLeft().toString(), + soExpr.getLeft(), + soExpr.getOperator().name, + parseValue(soExpr.getRight()), + soExpr.getRight(), + childrenType); + } else { + SQLMethodInvokeExpr sqlMethodInvokeExpr = + parseSQLBinaryOpExprWhoIsConditionInWhere(soExpr); + if (sqlMethodInvokeExpr == null) { + condition = + new Condition( + Where.CONN.valueOf(opear), + soExpr.getLeft().toString(), + soExpr.getLeft(), + soExpr.getOperator().name, + parseValue(soExpr.getRight()), + soExpr.getRight(), + null); + } else { + ScriptFilter scriptFilter = new ScriptFilter(); + if (!scriptFilter.tryParseFromMethodExpr(sqlMethodInvokeExpr)) { + throw new SqlParseException("could not parse script filter"); } - } else if (expr instanceof SQLInListExpr) { - SQLInListExpr siExpr = (SQLInListExpr) expr; - String leftSide = siExpr.getExpr().toString(); - - boolean isNested = false; - boolean isChildren = false; + condition = + new Condition( + Where.CONN.valueOf(opear), + null, + soExpr.getLeft(), + "SCRIPT", + scriptFilter, + soExpr.getRight()); + } + } + where.addWhere(condition); + } + } else if (expr instanceof SQLInListExpr) { + SQLInListExpr siExpr = (SQLInListExpr) expr; + String leftSide = siExpr.getExpr().toString(); - NestedType nestedType = new NestedType(); - if (nestedType.tryFillFromExpr(siExpr.getExpr())) { - leftSide = nestedType.field; + boolean isNested = false; + boolean isChildren = false; - isNested = false; - } + NestedType nestedType = new NestedType(); + if (nestedType.tryFillFromExpr(siExpr.getExpr())) { + leftSide = nestedType.field; - ChildrenType childrenType = new ChildrenType(); - if (childrenType.tryFillFromExpr(siExpr.getExpr())) { - leftSide = childrenType.field; + isNested = false; + } - isChildren = true; - } + ChildrenType childrenType = new ChildrenType(); + if (childrenType.tryFillFromExpr(siExpr.getExpr())) { + leftSide = childrenType.field; - final Condition condition; - - if (isNested) { - condition = new Condition(Where.CONN.valueOf(opear), leftSide, null, siExpr.isNot() ? "NOT IN" : "IN", - parseValue(siExpr.getTargetList()), null, nestedType); - } else if (isChildren) { - condition = new Condition(Where.CONN.valueOf(opear), leftSide, null, siExpr.isNot() ? "NOT IN" : "IN", - parseValue(siExpr.getTargetList()), null, childrenType); - } else { - condition = new Condition(Where.CONN.valueOf(opear), leftSide, null, siExpr.isNot() ? "NOT IN" : "IN", - parseValue(siExpr.getTargetList()), null); - } + isChildren = true; + } - where.addWhere(condition); - } else if (expr instanceof SQLBetweenExpr) { - SQLBetweenExpr between = ((SQLBetweenExpr) expr); - String leftSide = between.getTestExpr().toString(); + final Condition condition; - boolean isNested = false; - boolean isChildren = false; + if (isNested) { + condition = + new Condition( + Where.CONN.valueOf(opear), + leftSide, + null, + siExpr.isNot() ? "NOT IN" : "IN", + parseValue(siExpr.getTargetList()), + null, + nestedType); + } else if (isChildren) { + condition = + new Condition( + Where.CONN.valueOf(opear), + leftSide, + null, + siExpr.isNot() ? "NOT IN" : "IN", + parseValue(siExpr.getTargetList()), + null, + childrenType); + } else { + condition = + new Condition( + Where.CONN.valueOf(opear), + leftSide, + null, + siExpr.isNot() ? "NOT IN" : "IN", + parseValue(siExpr.getTargetList()), + null); + } - NestedType nestedType = new NestedType(); - if (nestedType.tryFillFromExpr(between.getTestExpr())) { - leftSide = nestedType.field; + where.addWhere(condition); + } else if (expr instanceof SQLBetweenExpr) { + SQLBetweenExpr between = ((SQLBetweenExpr) expr); + String leftSide = between.getTestExpr().toString(); - isNested = true; - } + boolean isNested = false; + boolean isChildren = false; - ChildrenType childrenType = new ChildrenType(); - if (childrenType.tryFillFromExpr(between.getTestExpr())) { - leftSide = childrenType.field; + NestedType nestedType = new NestedType(); + if (nestedType.tryFillFromExpr(between.getTestExpr())) { + leftSide = nestedType.field; - isChildren = true; - } + isNested = true; + } - final Condition condition; - - if (isNested) { - condition = new Condition(Where.CONN.valueOf(opear), leftSide, null, - between.isNot() ? "NOT BETWEEN" : "BETWEEN", new Object[]{parseValue(between.beginExpr), - parseValue(between.endExpr)}, null, nestedType); - } else if (isChildren) { - condition = new Condition(Where.CONN.valueOf(opear), leftSide, null, - between.isNot() ? "NOT BETWEEN" : "BETWEEN", new Object[]{parseValue(between.beginExpr), - parseValue(between.endExpr)}, null, childrenType); - } else { - condition = new Condition(Where.CONN.valueOf(opear), leftSide, null, - between.isNot() ? "NOT BETWEEN" : "BETWEEN", new Object[]{parseValue(between.beginExpr), - parseValue(between.endExpr)}, null, null); - } + ChildrenType childrenType = new ChildrenType(); + if (childrenType.tryFillFromExpr(between.getTestExpr())) { + leftSide = childrenType.field; - where.addWhere(condition); - } else if (expr instanceof SQLMethodInvokeExpr) { - - SQLMethodInvokeExpr methodExpr = (SQLMethodInvokeExpr) expr; - List methodParameters = methodExpr.getParameters(); - - String methodName = methodExpr.getMethodName(); - if (SpatialParamsFactory.isAllowedMethod(methodName)) { - String fieldName = methodParameters.get(0).toString(); - - boolean isNested = false; - boolean isChildren = false; - - NestedType nestedType = new NestedType(); - if (nestedType.tryFillFromExpr(methodParameters.get(0))) { - fieldName = nestedType.field; - - isNested = true; - } - - ChildrenType childrenType = new ChildrenType(); - if (childrenType.tryFillFromExpr(methodParameters.get(0))) { - fieldName = childrenType.field; - - isChildren = true; - } - - Object spatialParamsObject = SpatialParamsFactory.generateSpatialParamsObject(methodName, - methodParameters); - - final Condition condition; - - if (isNested) { - condition = new Condition(Where.CONN.valueOf(opear), fieldName, null, methodName, - spatialParamsObject, null, nestedType); - } else if (isChildren) { - condition = new Condition(Where.CONN.valueOf(opear), fieldName, null, methodName, - spatialParamsObject, null, childrenType); - } else { - condition = new Condition(Where.CONN.valueOf(opear), fieldName, null, methodName, - spatialParamsObject, null, null); - } - - where.addWhere(condition); - } else if (methodName.toLowerCase().equals("nested")) { - NestedType nestedType = new NestedType(); - - if (!nestedType.tryFillFromExpr(expr)) { - throw new SqlParseException("could not fill nested from expr:" + expr); - } - - Condition condition = new Condition(Where.CONN.valueOf(opear), nestedType.path, null, - methodName.toUpperCase(), nestedType.where, null); - - where.addWhere(condition); - } else if (methodName.toLowerCase().equals("children")) { - ChildrenType childrenType = new ChildrenType(); - - if (!childrenType.tryFillFromExpr(expr)) { - throw new SqlParseException("could not fill children from expr:" + expr); - } - - Condition condition = new Condition(Where.CONN.valueOf(opear), childrenType.childType, null, - methodName.toUpperCase(), childrenType.where, null); - - where.addWhere(condition); - } else if (methodName.toLowerCase().equals("script")) { - ScriptFilter scriptFilter = new ScriptFilter(); - if (!scriptFilter.tryParseFromMethodExpr(methodExpr)) { - throw new SqlParseException("could not parse script filter"); - } - Condition condition = new Condition(Where.CONN.valueOf(opear), null, null, "SCRIPT", - scriptFilter, null); - where.addWhere(condition); - } else if (Maker.isQueryFunction(methodName)) { - Condition condition = getConditionForMethod(expr, Where.CONN.valueOf(opear)); - - where.addWhere(condition); - } else { - throw new SqlParseException("unsupported method: " + methodName); - } - } else if (expr instanceof SQLInSubQueryExpr) { - SQLInSubQueryExpr sqlIn = (SQLInSubQueryExpr) expr; + isChildren = true; + } - Select innerSelect = sqlParser.parseSelect((MySqlSelectQueryBlock) sqlIn.getSubQuery().getQuery()); + final Condition condition; - if (innerSelect.getFields() == null || innerSelect.getFields().size() != 1) { - throw new SqlParseException("should only have one return field in subQuery"); - } + if (isNested) { + condition = + new Condition( + Where.CONN.valueOf(opear), + leftSide, + null, + between.isNot() ? "NOT BETWEEN" : "BETWEEN", + new Object[] {parseValue(between.beginExpr), parseValue(between.endExpr)}, + null, + nestedType); + } else if (isChildren) { + condition = + new Condition( + Where.CONN.valueOf(opear), + leftSide, + null, + between.isNot() ? "NOT BETWEEN" : "BETWEEN", + new Object[] {parseValue(between.beginExpr), parseValue(between.endExpr)}, + null, + childrenType); + } else { + condition = + new Condition( + Where.CONN.valueOf(opear), + leftSide, + null, + between.isNot() ? "NOT BETWEEN" : "BETWEEN", + new Object[] {parseValue(between.beginExpr), parseValue(between.endExpr)}, + null, + null); + } - SubQueryExpression subQueryExpression = new SubQueryExpression(innerSelect); + where.addWhere(condition); + } else if (expr instanceof SQLMethodInvokeExpr) { - String leftSide = sqlIn.getExpr().toString(); + SQLMethodInvokeExpr methodExpr = (SQLMethodInvokeExpr) expr; + List methodParameters = methodExpr.getParameters(); - boolean isNested = false; - boolean isChildren = false; + String methodName = methodExpr.getMethodName(); + if (SpatialParamsFactory.isAllowedMethod(methodName)) { + String fieldName = methodParameters.get(0).toString(); - NestedType nestedType = new NestedType(); - if (nestedType.tryFillFromExpr(sqlIn.getExpr())) { - leftSide = nestedType.field; + boolean isNested = false; + boolean isChildren = false; - isNested = true; - } + NestedType nestedType = new NestedType(); + if (nestedType.tryFillFromExpr(methodParameters.get(0))) { + fieldName = nestedType.field; - ChildrenType childrenType = new ChildrenType(); - if (childrenType.tryFillFromExpr(sqlIn.getExpr())) { - leftSide = childrenType.field; + isNested = true; + } - isChildren = true; - } + ChildrenType childrenType = new ChildrenType(); + if (childrenType.tryFillFromExpr(methodParameters.get(0))) { + fieldName = childrenType.field; - final Condition condition; - - if (isNested) { - condition = new Condition(Where.CONN.valueOf(opear), leftSide, null, sqlIn.isNot() ? "NOT IN" : "IN", - subQueryExpression, null, nestedType); - } else if (isChildren) { - condition = new Condition(Where.CONN.valueOf(opear), leftSide, null, sqlIn.isNot() ? "NOT IN" : "IN", - subQueryExpression, null, childrenType); - } else { - condition = new Condition(Where.CONN.valueOf(opear), leftSide, null, sqlIn.isNot() ? "NOT IN" : "IN", - subQueryExpression, null, null); - } + isChildren = true; + } - where.addWhere(condition); + Object spatialParamsObject = + SpatialParamsFactory.generateSpatialParamsObject(methodName, methodParameters); + + final Condition condition; + + if (isNested) { + condition = + new Condition( + Where.CONN.valueOf(opear), + fieldName, + null, + methodName, + spatialParamsObject, + null, + nestedType); + } else if (isChildren) { + condition = + new Condition( + Where.CONN.valueOf(opear), + fieldName, + null, + methodName, + spatialParamsObject, + null, + childrenType); } else { - throw new SqlParseException("err find condition " + expr.getClass()); + condition = + new Condition( + Where.CONN.valueOf(opear), + fieldName, + null, + methodName, + spatialParamsObject, + null, + null); } - } - private MethodField parseSQLMethodInvokeExprWithFunctionInWhere(SQLMethodInvokeExpr soExpr) - throws SqlParseException { + where.addWhere(condition); + } else if (methodName.toLowerCase().equals("nested")) { + NestedType nestedType = new NestedType(); - MethodField methodField = fieldMaker.makeMethodField(soExpr.getMethodName(), - soExpr.getParameters(), - null, - null, - query != null ? query.getFrom().getAlias() : null, - false); - return methodField; - } + if (!nestedType.tryFillFromExpr(expr)) { + throw new SqlParseException("could not fill nested from expr:" + expr); + } - private MethodField parseSQLCastExprWithFunctionInWhere(SQLCastExpr soExpr) throws SqlParseException { - ArrayList parameters = new ArrayList<>(); - parameters.add(soExpr.getExpr()); - return fieldMaker.makeMethodField( - "CAST", - parameters, + Condition condition = + new Condition( + Where.CONN.valueOf(opear), + nestedType.path, null, - null, - query != null ? query.getFrom().getAlias() : null, - false - ); - } + methodName.toUpperCase(), + nestedType.where, + null); - private SQLMethodInvokeExpr parseSQLBinaryOpExprWhoIsConditionInWhere(SQLBinaryOpExpr soExpr) - throws SqlParseException { - - if (bothSideAreNotFunction(soExpr) && bothSidesAreNotCast(soExpr)) { - return null; - } + where.addWhere(condition); + } else if (methodName.toLowerCase().equals("children")) { + ChildrenType childrenType = new ChildrenType(); - if (soExpr.getLeft() instanceof SQLMethodInvokeExpr) { - if (!SQLFunctions.isFunctionTranslatedToScript(((SQLMethodInvokeExpr) soExpr.getLeft()).getMethodName())) { - return null; - } + if (!childrenType.tryFillFromExpr(expr)) { + throw new SqlParseException("could not fill children from expr:" + expr); } - if (soExpr.getRight() instanceof SQLMethodInvokeExpr) { - if (!SQLFunctions.isFunctionTranslatedToScript(((SQLMethodInvokeExpr) soExpr.getRight()).getMethodName())) { - return null; - } + Condition condition = + new Condition( + Where.CONN.valueOf(opear), + childrenType.childType, + null, + methodName.toUpperCase(), + childrenType.where, + null); + + where.addWhere(condition); + } else if (methodName.toLowerCase().equals("script")) { + ScriptFilter scriptFilter = new ScriptFilter(); + if (!scriptFilter.tryParseFromMethodExpr(methodExpr)) { + throw new SqlParseException("could not parse script filter"); } + Condition condition = + new Condition(Where.CONN.valueOf(opear), null, null, "SCRIPT", scriptFilter, null); + where.addWhere(condition); + } else if (Maker.isQueryFunction(methodName)) { + Condition condition = getConditionForMethod(expr, Where.CONN.valueOf(opear)); + where.addWhere(condition); + } else { + throw new SqlParseException("unsupported method: " + methodName); + } + } else if (expr instanceof SQLInSubQueryExpr) { + SQLInSubQueryExpr sqlIn = (SQLInSubQueryExpr) expr; - MethodField leftMethod = new MethodField(null, Lists.newArrayList( - new KVValue("", Util.expr2Object(soExpr.getLeft(), "'"))), null, null); - MethodField rightMethod = new MethodField(null, Lists.newArrayList( - new KVValue("", Util.expr2Object(soExpr.getRight(), "'"))), null, null); + Select innerSelect = + sqlParser.parseSelect((MySqlSelectQueryBlock) sqlIn.getSubQuery().getQuery()); - if (soExpr.getLeft() instanceof SQLIdentifierExpr || soExpr.getLeft() instanceof SQLPropertyExpr) { - leftMethod = new MethodField(null, Lists.newArrayList( - new KVValue("", "doc['" + Util.expr2Object(soExpr.getLeft(), "'") + "'].value")), - null, null); - } - - if (soExpr.getRight() instanceof SQLIdentifierExpr || soExpr.getRight() instanceof SQLPropertyExpr) { - rightMethod = new MethodField(null, Lists.newArrayList( - new KVValue("", "doc['" + Util.expr2Object(soExpr.getRight(), "'") + "'].value")), - null, null); - } + if (innerSelect.getFields() == null || innerSelect.getFields().size() != 1) { + throw new SqlParseException("should only have one return field in subQuery"); + } - if (soExpr.getLeft() instanceof SQLMethodInvokeExpr) { - leftMethod = parseSQLMethodInvokeExprWithFunctionInWhere((SQLMethodInvokeExpr) soExpr.getLeft()); - } - if (soExpr.getRight() instanceof SQLMethodInvokeExpr) { - rightMethod = parseSQLMethodInvokeExprWithFunctionInWhere((SQLMethodInvokeExpr) soExpr.getRight()); - } + SubQueryExpression subQueryExpression = new SubQueryExpression(innerSelect); - if (soExpr.getLeft() instanceof SQLCastExpr) { - leftMethod = parseSQLCastExprWithFunctionInWhere((SQLCastExpr) soExpr.getLeft()); - } - if (soExpr.getRight() instanceof SQLCastExpr) { - rightMethod = parseSQLCastExprWithFunctionInWhere((SQLCastExpr) soExpr.getRight()); - } + String leftSide = sqlIn.getExpr().toString(); - String v1 = leftMethod.getParams().get(0).value.toString(); - String v1Dec = leftMethod.getParams().size() == 2 ? leftMethod.getParams().get(1).value.toString() + ";" : ""; + boolean isNested = false; + boolean isChildren = false; + NestedType nestedType = new NestedType(); + if (nestedType.tryFillFromExpr(sqlIn.getExpr())) { + leftSide = nestedType.field; - String v2 = rightMethod.getParams().get(0).value.toString(); - String v2Dec = rightMethod.getParams().size() == 2 ? rightMethod.getParams().get(1).value.toString() + ";" : ""; + isNested = true; + } - String operator = soExpr.getOperator().getName(); + ChildrenType childrenType = new ChildrenType(); + if (childrenType.tryFillFromExpr(sqlIn.getExpr())) { + leftSide = childrenType.field; - if (operator.equals("=")) { - operator = "=="; - } + isChildren = true; + } - String finalStr = v1Dec + v2Dec + v1 + " " + operator + " " + v2; + final Condition condition; - SQLMethodInvokeExpr scriptMethod = new SQLMethodInvokeExpr("script", null); - scriptMethod.addParameter(new SQLCharExpr(finalStr)); - return scriptMethod; + if (isNested) { + condition = + new Condition( + Where.CONN.valueOf(opear), + leftSide, + null, + sqlIn.isNot() ? "NOT IN" : "IN", + subQueryExpression, + null, + nestedType); + } else if (isChildren) { + condition = + new Condition( + Where.CONN.valueOf(opear), + leftSide, + null, + sqlIn.isNot() ? "NOT IN" : "IN", + subQueryExpression, + null, + childrenType); + } else { + condition = + new Condition( + Where.CONN.valueOf(opear), + leftSide, + null, + sqlIn.isNot() ? "NOT IN" : "IN", + subQueryExpression, + null, + null); + } + where.addWhere(condition); + } else { + throw new SqlParseException("err find condition " + expr.getClass()); + } + } + + private MethodField parseSQLMethodInvokeExprWithFunctionInWhere(SQLMethodInvokeExpr soExpr) + throws SqlParseException { + + MethodField methodField = + fieldMaker.makeMethodField( + soExpr.getMethodName(), + soExpr.getParameters(), + null, + null, + query != null ? query.getFrom().getAlias() : null, + false); + return methodField; + } + + private MethodField parseSQLCastExprWithFunctionInWhere(SQLCastExpr soExpr) + throws SqlParseException { + ArrayList parameters = new ArrayList<>(); + parameters.add(soExpr.getExpr()); + return fieldMaker.makeMethodField( + "CAST", parameters, null, null, query != null ? query.getFrom().getAlias() : null, false); + } + + private SQLMethodInvokeExpr parseSQLBinaryOpExprWhoIsConditionInWhere(SQLBinaryOpExpr soExpr) + throws SqlParseException { + + if (bothSideAreNotFunction(soExpr) && bothSidesAreNotCast(soExpr)) { + return null; } - private Boolean bothSideAreNotFunction(SQLBinaryOpExpr soExpr) { - return !(soExpr.getLeft() instanceof SQLMethodInvokeExpr || soExpr.getRight() instanceof SQLMethodInvokeExpr); + if (soExpr.getLeft() instanceof SQLMethodInvokeExpr) { + if (!SQLFunctions.isFunctionTranslatedToScript( + ((SQLMethodInvokeExpr) soExpr.getLeft()).getMethodName())) { + return null; + } } - private Boolean bothSidesAreNotCast(SQLBinaryOpExpr soExpr) { - return !(soExpr.getLeft() instanceof SQLCastExpr || soExpr.getRight() instanceof SQLCastExpr); + if (soExpr.getRight() instanceof SQLMethodInvokeExpr) { + if (!SQLFunctions.isFunctionTranslatedToScript( + ((SQLMethodInvokeExpr) soExpr.getRight()).getMethodName())) { + return null; + } } - private Object[] getMethodValuesWithSubQueries(SQLMethodInvokeExpr method) throws SqlParseException { - List values = new ArrayList<>(); - for (SQLExpr innerExpr : method.getParameters()) { - if (innerExpr instanceof SQLQueryExpr) { - Select select = sqlParser.parseSelect((MySqlSelectQueryBlock) ((SQLQueryExpr) innerExpr).getSubQuery() - .getQuery()); - values.add(new SubQueryExpression(select)); - } else if (innerExpr instanceof SQLTextLiteralExpr) { - values.add(((SQLTextLiteralExpr) innerExpr).getText()); - } else { - values.add(innerExpr); - } + MethodField leftMethod = + new MethodField( + null, + Lists.newArrayList(new KVValue("", Util.expr2Object(soExpr.getLeft(), "'"))), + null, + null); + MethodField rightMethod = + new MethodField( + null, + Lists.newArrayList(new KVValue("", Util.expr2Object(soExpr.getRight(), "'"))), + null, + null); + + if (soExpr.getLeft() instanceof SQLIdentifierExpr + || soExpr.getLeft() instanceof SQLPropertyExpr) { + leftMethod = + new MethodField( + null, + Lists.newArrayList( + new KVValue("", "doc['" + Util.expr2Object(soExpr.getLeft(), "'") + "'].value")), + null, + null); + } - } - return values.toArray(); + if (soExpr.getRight() instanceof SQLIdentifierExpr + || soExpr.getRight() instanceof SQLPropertyExpr) { + rightMethod = + new MethodField( + null, + Lists.newArrayList( + new KVValue("", "doc['" + Util.expr2Object(soExpr.getRight(), "'") + "'].value")), + null, + null); } - private Object[] parseValue(List targetList) throws SqlParseException { - Object[] value = new Object[targetList.size()]; - for (int i = 0; i < targetList.size(); i++) { - value[i] = parseValue(targetList.get(i)); - } - return value; + if (soExpr.getLeft() instanceof SQLMethodInvokeExpr) { + leftMethod = + parseSQLMethodInvokeExprWithFunctionInWhere((SQLMethodInvokeExpr) soExpr.getLeft()); + } + if (soExpr.getRight() instanceof SQLMethodInvokeExpr) { + rightMethod = + parseSQLMethodInvokeExprWithFunctionInWhere((SQLMethodInvokeExpr) soExpr.getRight()); } - private Object parseValue(SQLExpr expr) throws SqlParseException { - if (expr instanceof SQLNumericLiteralExpr) { - Number number = ((SQLNumericLiteralExpr) expr).getNumber(); - if (number instanceof BigDecimal) { - return number.doubleValue(); - } - if (number instanceof BigInteger) { - return number.longValue(); - } - return ((SQLNumericLiteralExpr) expr).getNumber(); - } else if (expr instanceof SQLCharExpr) { - return ((SQLCharExpr) expr).getText(); - } else if (expr instanceof SQLMethodInvokeExpr) { - return expr; - } else if (expr instanceof SQLNullExpr) { - return null; - } else if (expr instanceof SQLIdentifierExpr) { - return expr; - } else if (expr instanceof SQLPropertyExpr) { - return expr; - } else if (expr instanceof SQLBooleanExpr) { - return ((SQLBooleanExpr) expr).getValue(); - } else { - throw new SqlParseException( - String.format("Failed to parse SqlExpression of type %s. expression value: %s", - expr.getClass(), expr) - ); - } + if (soExpr.getLeft() instanceof SQLCastExpr) { + leftMethod = parseSQLCastExprWithFunctionInWhere((SQLCastExpr) soExpr.getLeft()); + } + if (soExpr.getRight() instanceof SQLCastExpr) { + rightMethod = parseSQLCastExprWithFunctionInWhere((SQLCastExpr) soExpr.getRight()); } - public static Condition getConditionForMethod(SQLExpr expr, Where.CONN conn) throws SqlParseException { - SQLExpr param = ((SQLMethodInvokeExpr) expr).getParameters().get(0); - String fieldName = param.toString(); + String v1 = leftMethod.getParams().get(0).value.toString(); + String v1Dec = + leftMethod.getParams().size() == 2 + ? leftMethod.getParams().get(1).value.toString() + ";" + : ""; - NestedType nestedType = new NestedType(); - ChildrenType childrenType = new ChildrenType(); + String v2 = rightMethod.getParams().get(0).value.toString(); + String v2Dec = + rightMethod.getParams().size() == 2 + ? rightMethod.getParams().get(1).value.toString() + ";" + : ""; - if (nestedType.tryFillFromExpr(param)) { - return new Condition(conn, nestedType.field, null, "=", expr, expr, nestedType); - } else if (childrenType.tryFillFromExpr(param)) { - return new Condition(conn, childrenType.field, null, "=", expr, expr, childrenType); - } else { - return new Condition(conn, fieldName, null, "=", expr, expr, null); - } + String operator = soExpr.getOperator().getName(); + + if (operator.equals("=")) { + operator = "=="; + } + + String finalStr = v1Dec + v2Dec + v1 + " " + operator + " " + v2; + + SQLMethodInvokeExpr scriptMethod = new SQLMethodInvokeExpr("script", null); + scriptMethod.addParameter(new SQLCharExpr(finalStr)); + return scriptMethod; + } + + private Boolean bothSideAreNotFunction(SQLBinaryOpExpr soExpr) { + return !(soExpr.getLeft() instanceof SQLMethodInvokeExpr + || soExpr.getRight() instanceof SQLMethodInvokeExpr); + } + + private Boolean bothSidesAreNotCast(SQLBinaryOpExpr soExpr) { + return !(soExpr.getLeft() instanceof SQLCastExpr || soExpr.getRight() instanceof SQLCastExpr); + } + + private Object[] getMethodValuesWithSubQueries(SQLMethodInvokeExpr method) + throws SqlParseException { + List values = new ArrayList<>(); + for (SQLExpr innerExpr : method.getParameters()) { + if (innerExpr instanceof SQLQueryExpr) { + Select select = + sqlParser.parseSelect( + (MySqlSelectQueryBlock) ((SQLQueryExpr) innerExpr).getSubQuery().getQuery()); + values.add(new SubQueryExpression(select)); + } else if (innerExpr instanceof SQLTextLiteralExpr) { + values.add(((SQLTextLiteralExpr) innerExpr).getText()); + } else { + values.add(innerExpr); + } + } + return values.toArray(); + } + + private Object[] parseValue(List targetList) throws SqlParseException { + Object[] value = new Object[targetList.size()]; + for (int i = 0; i < targetList.size(); i++) { + value[i] = parseValue(targetList.get(i)); + } + return value; + } + + private Object parseValue(SQLExpr expr) throws SqlParseException { + if (expr instanceof SQLNumericLiteralExpr) { + Number number = ((SQLNumericLiteralExpr) expr).getNumber(); + if (number instanceof BigDecimal) { + return number.doubleValue(); + } + if (number instanceof BigInteger) { + return number.longValue(); + } + return ((SQLNumericLiteralExpr) expr).getNumber(); + } else if (expr instanceof SQLCharExpr) { + return ((SQLCharExpr) expr).getText(); + } else if (expr instanceof SQLMethodInvokeExpr) { + return expr; + } else if (expr instanceof SQLNullExpr) { + return null; + } else if (expr instanceof SQLIdentifierExpr) { + return expr; + } else if (expr instanceof SQLPropertyExpr) { + return expr; + } else if (expr instanceof SQLBooleanExpr) { + return ((SQLBooleanExpr) expr).getValue(); + } else { + throw new SqlParseException( + String.format( + "Failed to parse SqlExpression of type %s. expression value: %s", + expr.getClass(), expr)); + } + } + + public static Condition getConditionForMethod(SQLExpr expr, Where.CONN conn) + throws SqlParseException { + SQLExpr param = ((SQLMethodInvokeExpr) expr).getParameters().get(0); + String fieldName = param.toString(); + + NestedType nestedType = new NestedType(); + ChildrenType childrenType = new ChildrenType(); + + if (nestedType.tryFillFromExpr(param)) { + return new Condition(conn, nestedType.field, null, "=", expr, expr, nestedType); + } else if (childrenType.tryFillFromExpr(param)) { + return new Condition(conn, childrenType.field, null, "=", expr, expr, childrenType); + } else { + return new Condition(conn, fieldName, null, "=", expr, expr, null); } + } } diff --git a/legacy/src/main/java/org/opensearch/sql/legacy/plugin/RestSQLQueryAction.java b/legacy/src/main/java/org/opensearch/sql/legacy/plugin/RestSQLQueryAction.java index 12176d4fa7..309a7c9c2a 100644 --- a/legacy/src/main/java/org/opensearch/sql/legacy/plugin/RestSQLQueryAction.java +++ b/legacy/src/main/java/org/opensearch/sql/legacy/plugin/RestSQLQueryAction.java @@ -41,10 +41,11 @@ /** * New SQL REST action handler. This will not be registered to OpenSearch unless: + * *
    - *
  1. we want to test new SQL engine; - *
  2. all old functionalities migrated to new query engine and legacy REST handler removed. - *
+ *
  • we want to test new SQL engine; + *
  • all old functionalities migrated to new query engine and legacy REST handler removed. + * */ public class RestSQLQueryAction extends BaseRestHandler { diff --git a/legacy/src/main/java/org/opensearch/sql/legacy/query/ShowQueryAction.java b/legacy/src/main/java/org/opensearch/sql/legacy/query/ShowQueryAction.java index 7a414087e4..d9baa901fa 100644 --- a/legacy/src/main/java/org/opensearch/sql/legacy/query/ShowQueryAction.java +++ b/legacy/src/main/java/org/opensearch/sql/legacy/query/ShowQueryAction.java @@ -3,7 +3,6 @@ * SPDX-License-Identifier: Apache-2.0 */ - package org.opensearch.sql.legacy.query; import static org.opensearch.sql.legacy.utils.Util.prepareIndexRequestBuilder; @@ -15,22 +14,23 @@ public class ShowQueryAction extends QueryAction { - private final IndexStatement statement; + private final IndexStatement statement; - public ShowQueryAction(Client client, IndexStatement statement) { - super(client, null); - this.statement = statement; - } + public ShowQueryAction(Client client, IndexStatement statement) { + super(client, null); + this.statement = statement; + } - @Override - public QueryStatement getQueryStatement() { - return statement; - } + @Override + public QueryStatement getQueryStatement() { + return statement; + } - @Override - public SqlOpenSearchRequestBuilder explain() { - final GetIndexRequestBuilder indexRequestBuilder = prepareIndexRequestBuilder(client, statement); + @Override + public SqlOpenSearchRequestBuilder explain() { + final GetIndexRequestBuilder indexRequestBuilder = + prepareIndexRequestBuilder(client, statement); - return new SqlOpenSearchRequestBuilder(indexRequestBuilder); - } + return new SqlOpenSearchRequestBuilder(indexRequestBuilder); + } } diff --git a/legacy/src/main/java/org/opensearch/sql/legacy/query/SqlElasticDeleteByQueryRequestBuilder.java b/legacy/src/main/java/org/opensearch/sql/legacy/query/SqlElasticDeleteByQueryRequestBuilder.java index 6963996b22..2203cbb39e 100644 --- a/legacy/src/main/java/org/opensearch/sql/legacy/query/SqlElasticDeleteByQueryRequestBuilder.java +++ b/legacy/src/main/java/org/opensearch/sql/legacy/query/SqlElasticDeleteByQueryRequestBuilder.java @@ -3,7 +3,6 @@ * SPDX-License-Identifier: Apache-2.0 */ - package org.opensearch.sql.legacy.query; import org.opensearch.action.ActionRequest; @@ -12,41 +11,39 @@ import org.opensearch.core.action.ActionResponse; import org.opensearch.index.reindex.DeleteByQueryRequestBuilder; -/** - * Created by Eliran on 19/8/2015. - */ +/** Created by Eliran on 19/8/2015. */ public class SqlElasticDeleteByQueryRequestBuilder implements SqlElasticRequestBuilder { - DeleteByQueryRequestBuilder deleteByQueryRequestBuilder; - - public SqlElasticDeleteByQueryRequestBuilder(DeleteByQueryRequestBuilder deleteByQueryRequestBuilder) { - this.deleteByQueryRequestBuilder = deleteByQueryRequestBuilder; - } - - @Override - public ActionRequest request() { - return deleteByQueryRequestBuilder.request(); + DeleteByQueryRequestBuilder deleteByQueryRequestBuilder; + + public SqlElasticDeleteByQueryRequestBuilder( + DeleteByQueryRequestBuilder deleteByQueryRequestBuilder) { + this.deleteByQueryRequestBuilder = deleteByQueryRequestBuilder; + } + + @Override + public ActionRequest request() { + return deleteByQueryRequestBuilder.request(); + } + + @Override + public String explain() { + try { + SearchRequestBuilder source = deleteByQueryRequestBuilder.source(); + return source.toString(); + } catch (Exception e) { + e.printStackTrace(); } + return null; + } - @Override - public String explain() { - try { - SearchRequestBuilder source = deleteByQueryRequestBuilder.source(); - return source.toString(); - } catch (Exception e) { - e.printStackTrace(); - } - return null; - } - - @Override - public ActionResponse get() { + @Override + public ActionResponse get() { - return this.deleteByQueryRequestBuilder.get(); - } - - @Override - public ActionRequestBuilder getBuilder() { - return deleteByQueryRequestBuilder; - } + return this.deleteByQueryRequestBuilder.get(); + } + @Override + public ActionRequestBuilder getBuilder() { + return deleteByQueryRequestBuilder; + } } diff --git a/legacy/src/main/java/org/opensearch/sql/legacy/query/SqlElasticRequestBuilder.java b/legacy/src/main/java/org/opensearch/sql/legacy/query/SqlElasticRequestBuilder.java index e1f3db3fa7..7babbe5abe 100644 --- a/legacy/src/main/java/org/opensearch/sql/legacy/query/SqlElasticRequestBuilder.java +++ b/legacy/src/main/java/org/opensearch/sql/legacy/query/SqlElasticRequestBuilder.java @@ -3,22 +3,19 @@ * SPDX-License-Identifier: Apache-2.0 */ - package org.opensearch.sql.legacy.query; import org.opensearch.action.ActionRequest; import org.opensearch.action.ActionRequestBuilder; import org.opensearch.core.action.ActionResponse; -/** - * Created by Eliran on 19/8/2015. - */ +/** Created by Eliran on 19/8/2015. */ public interface SqlElasticRequestBuilder { - ActionRequest request(); + ActionRequest request(); - String explain(); + String explain(); - ActionResponse get(); + ActionResponse get(); - ActionRequestBuilder getBuilder(); + ActionRequestBuilder getBuilder(); } diff --git a/legacy/src/main/java/org/opensearch/sql/legacy/query/SqlOpenSearchRequestBuilder.java b/legacy/src/main/java/org/opensearch/sql/legacy/query/SqlOpenSearchRequestBuilder.java index 6bba1048c4..2beb16837b 100644 --- a/legacy/src/main/java/org/opensearch/sql/legacy/query/SqlOpenSearchRequestBuilder.java +++ b/legacy/src/main/java/org/opensearch/sql/legacy/query/SqlOpenSearchRequestBuilder.java @@ -3,45 +3,42 @@ * SPDX-License-Identifier: Apache-2.0 */ - package org.opensearch.sql.legacy.query; import org.opensearch.action.ActionRequest; import org.opensearch.action.ActionRequestBuilder; import org.opensearch.core.action.ActionResponse; -/** - * Created by Eliran on 19/8/2015. - */ +/** Created by Eliran on 19/8/2015. */ public class SqlOpenSearchRequestBuilder implements SqlElasticRequestBuilder { - ActionRequestBuilder requestBuilder; - - public SqlOpenSearchRequestBuilder(ActionRequestBuilder requestBuilder) { - this.requestBuilder = requestBuilder; - } - - @Override - public ActionRequest request() { - return requestBuilder.request(); - } - - @Override - public String explain() { - return requestBuilder.toString(); - } - - @Override - public ActionResponse get() { - return requestBuilder.get(); - } - - @Override - public ActionRequestBuilder getBuilder() { - return requestBuilder; - } - - @Override - public String toString() { - return this.requestBuilder.toString(); - } + ActionRequestBuilder requestBuilder; + + public SqlOpenSearchRequestBuilder(ActionRequestBuilder requestBuilder) { + this.requestBuilder = requestBuilder; + } + + @Override + public ActionRequest request() { + return requestBuilder.request(); + } + + @Override + public String explain() { + return requestBuilder.toString(); + } + + @Override + public ActionResponse get() { + return requestBuilder.get(); + } + + @Override + public ActionRequestBuilder getBuilder() { + return requestBuilder; + } + + @Override + public String toString() { + return this.requestBuilder.toString(); + } } diff --git a/legacy/src/main/java/org/opensearch/sql/legacy/query/join/TableInJoinRequestBuilder.java b/legacy/src/main/java/org/opensearch/sql/legacy/query/join/TableInJoinRequestBuilder.java index b1a07486b7..0b37497541 100644 --- a/legacy/src/main/java/org/opensearch/sql/legacy/query/join/TableInJoinRequestBuilder.java +++ b/legacy/src/main/java/org/opensearch/sql/legacy/query/join/TableInJoinRequestBuilder.java @@ -3,7 +3,6 @@ * SPDX-License-Identifier: Apache-2.0 */ - package org.opensearch.sql.legacy.query.join; import java.util.List; @@ -11,56 +10,53 @@ import org.opensearch.sql.legacy.domain.Field; import org.opensearch.sql.legacy.domain.Select; -/** - * Created by Eliran on 28/8/2015. - */ +/** Created by Eliran on 28/8/2015. */ public class TableInJoinRequestBuilder { - private SearchRequestBuilder requestBuilder; - private String alias; - private List returnedFields; - private Select originalSelect; - private Integer hintLimit; + private SearchRequestBuilder requestBuilder; + private String alias; + private List returnedFields; + private Select originalSelect; + private Integer hintLimit; - public TableInJoinRequestBuilder() { - } + public TableInJoinRequestBuilder() {} - public SearchRequestBuilder getRequestBuilder() { - return requestBuilder; - } + public SearchRequestBuilder getRequestBuilder() { + return requestBuilder; + } - public void setRequestBuilder(SearchRequestBuilder requestBuilder) { - this.requestBuilder = requestBuilder; - } + public void setRequestBuilder(SearchRequestBuilder requestBuilder) { + this.requestBuilder = requestBuilder; + } - public String getAlias() { - return alias; - } + public String getAlias() { + return alias; + } - public void setAlias(String alias) { - this.alias = alias; - } + public void setAlias(String alias) { + this.alias = alias; + } - public List getReturnedFields() { - return returnedFields; - } + public List getReturnedFields() { + return returnedFields; + } - public void setReturnedFields(List returnedFields) { - this.returnedFields = returnedFields; - } + public void setReturnedFields(List returnedFields) { + this.returnedFields = returnedFields; + } - public Select getOriginalSelect() { - return originalSelect; - } + public Select getOriginalSelect() { + return originalSelect; + } - public void setOriginalSelect(Select originalSelect) { - this.originalSelect = originalSelect; - } + public void setOriginalSelect(Select originalSelect) { + this.originalSelect = originalSelect; + } - public Integer getHintLimit() { - return hintLimit; - } + public Integer getHintLimit() { + return hintLimit; + } - public void setHintLimit(Integer hintLimit) { - this.hintLimit = hintLimit; - } + public void setHintLimit(Integer hintLimit) { + this.hintLimit = hintLimit; + } } diff --git a/legacy/src/main/java/org/opensearch/sql/legacy/query/planner/converter/SQLAggregationParser.java b/legacy/src/main/java/org/opensearch/sql/legacy/query/planner/converter/SQLAggregationParser.java index ac9a173212..b54e260fd4 100644 --- a/legacy/src/main/java/org/opensearch/sql/legacy/query/planner/converter/SQLAggregationParser.java +++ b/legacy/src/main/java/org/opensearch/sql/legacy/query/planner/converter/SQLAggregationParser.java @@ -3,7 +3,6 @@ * SPDX-License-Identifier: Apache-2.0 */ - package org.opensearch.sql.legacy.query.planner.converter; import com.alibaba.druid.sql.ast.SQLExpr; @@ -31,253 +30,281 @@ import org.opensearch.sql.legacy.query.planner.core.ColumnNode; /** - * The definition of SQL Aggregation Converter which will parse the query to project column node list and - * aggregation list - * e.g. parse the query: SELECT age, MAX(balance) - MIN(balance) FROM T GROUP BY age. - * will generate the - * node list: age, max_0 - min_0 - * aggregation list: age, max(balance) as max_0, min(balance) as min_0 - * + * The definition of SQL Aggregation Converter which will parse the query to project column node + * list and aggregation list e.g. parse the query: SELECT age, MAX(balance) - MIN(balance) FROM T + * GROUP BY age. will generate the node list: age, max_0 - min_0 aggregation list: age, max(balance) + * as max_0, min(balance) as min_0 */ @RequiredArgsConstructor public class SQLAggregationParser { - private final ColumnTypeProvider columnTypeProvider; - private Context context; - @Getter - private List columnNodes = new ArrayList<>(); - - public void parse(MySqlSelectQueryBlock queryBlock) { - context = new Context(constructSQLExprAliasMapFromSelect(queryBlock)); - - //1. extract raw names of selectItems - List selectItemNames = extractSelectItemNames(queryBlock.getSelectList()); - - //2. rewrite all the function name to lower case. - rewriteFunctionNameToLowerCase(queryBlock); - - //2. find all GroupKeyExpr from GroupBy expression. - findAllGroupKeyExprFromGroupByAndSelect(queryBlock); - findAllAggregationExprFromSelect(queryBlock); - - //3. parse the select list to expression - parseExprInSelectList(queryBlock, selectItemNames, new SQLExprToExpressionConverter(context)); - } - - public List selectItemList() { - List sqlSelectItems = new ArrayList<>(); - context.getGroupKeyExprMap().entrySet().forEach(entry -> sqlSelectItems - .add(new SQLSelectItem(entry.getKey(), entry.getValue().getExpression().toString()))); - context.getAggregationExprMap().entrySet().forEach(entry -> sqlSelectItems - .add(new SQLSelectItem(entry.getKey(), entry.getValue().getExpression().toString()))); - return sqlSelectItems; + private final ColumnTypeProvider columnTypeProvider; + private Context context; + @Getter private List columnNodes = new ArrayList<>(); + + public void parse(MySqlSelectQueryBlock queryBlock) { + context = new Context(constructSQLExprAliasMapFromSelect(queryBlock)); + + // 1. extract raw names of selectItems + List selectItemNames = extractSelectItemNames(queryBlock.getSelectList()); + + // 2. rewrite all the function name to lower case. + rewriteFunctionNameToLowerCase(queryBlock); + + // 2. find all GroupKeyExpr from GroupBy expression. + findAllGroupKeyExprFromGroupByAndSelect(queryBlock); + findAllAggregationExprFromSelect(queryBlock); + + // 3. parse the select list to expression + parseExprInSelectList(queryBlock, selectItemNames, new SQLExprToExpressionConverter(context)); + } + + public List selectItemList() { + List sqlSelectItems = new ArrayList<>(); + context + .getGroupKeyExprMap() + .entrySet() + .forEach( + entry -> + sqlSelectItems.add( + new SQLSelectItem( + entry.getKey(), entry.getValue().getExpression().toString()))); + context + .getAggregationExprMap() + .entrySet() + .forEach( + entry -> + sqlSelectItems.add( + new SQLSelectItem( + entry.getKey(), entry.getValue().getExpression().toString()))); + return sqlSelectItems; + } + + private Map constructSQLExprAliasMapFromSelect( + MySqlSelectQueryBlock queryBlock) { + return queryBlock.getSelectList().stream() + .filter(item -> !Strings.isNullOrEmpty(item.getAlias())) + .collect(Collectors.toMap(SQLSelectItem::getExpr, SQLSelectItem::getAlias)); + } + + /** + * The SQL-92 require nonaggregated name column in the select list must appear in the GROUP BY, + * But the existing uses cases violate this require. e.g. AggregationIT. countGroupByDateTest Ref + * the https://dev.mysql.com/doc/refman/8.0/en/group-by-handling.html for detail information + */ + private void findAllGroupKeyExprFromGroupByAndSelect(MySqlSelectQueryBlock queryBlock) { + if (queryBlock.getGroupBy() == null) { + return; } - - private Map constructSQLExprAliasMapFromSelect(MySqlSelectQueryBlock queryBlock) { - return queryBlock.getSelectList().stream().filter(item -> !Strings.isNullOrEmpty(item.getAlias())) - .collect(Collectors.toMap(SQLSelectItem::getExpr, SQLSelectItem::getAlias)); - } - - /** - * The SQL-92 require nonaggregated name column in the select list must appear in the GROUP BY, But the - * existing uses cases violate this require. e.g. AggregationIT. countGroupByDateTest - * Ref the https://dev.mysql.com/doc/refman/8.0/en/group-by-handling.html for detail information - */ - private void findAllGroupKeyExprFromGroupByAndSelect(MySqlSelectQueryBlock queryBlock) { - if (queryBlock.getGroupBy() == null) { - return; - } - // 1. fetch the expr from groupby clause. - List groupByKeyExprList = - queryBlock.getGroupBy().getItems().stream().map(item -> ((MySqlSelectGroupByExpr) item).getExpr()) - .collect(Collectors.toList()); - - // 2. find the group expr from select. - for (SQLSelectItem selectItem : queryBlock.getSelectList()) { - SQLExpr selectItemExpr = selectItem.getExpr(); - // extension, group key in select could not in group by. - if (selectItemExpr instanceof SQLIdentifierExpr) { - context.addGroupKeyExpr(selectItemExpr); - } else { - for (SQLExpr groupByExpr : groupByKeyExprList) { - // SQL-92,nonaggregated name column in the select list must appear in the GROUP BY - if (compareSelectExprAndGroupByExpr(selectItemExpr, selectItem.getAlias(), groupByExpr)) { - context.addGroupKeyExpr(selectItemExpr); - } else if (groupByExpr instanceof SQLIdentifierExpr) { - // support expression over group key, e.g. SELECT log(G), max(A) FROM T GROUP BY G. - String groupByName = ((SQLIdentifierExpr) groupByExpr).getName(); - selectItemExpr.accept(new MySqlASTVisitorAdapter() { - @Override - public boolean visit(SQLAggregateExpr x) { - return false; - } - - @Override - public boolean visit(SQLIdentifierExpr expr) { - if (groupByName.equalsIgnoreCase(expr.getName())) { - expr.setParent(selectItem.getParent()); - context.addGroupKeyExpr(expr); - } - return false; - } - }); + // 1. fetch the expr from groupby clause. + List groupByKeyExprList = + queryBlock.getGroupBy().getItems().stream() + .map(item -> ((MySqlSelectGroupByExpr) item).getExpr()) + .collect(Collectors.toList()); + + // 2. find the group expr from select. + for (SQLSelectItem selectItem : queryBlock.getSelectList()) { + SQLExpr selectItemExpr = selectItem.getExpr(); + // extension, group key in select could not in group by. + if (selectItemExpr instanceof SQLIdentifierExpr) { + context.addGroupKeyExpr(selectItemExpr); + } else { + for (SQLExpr groupByExpr : groupByKeyExprList) { + // SQL-92,nonaggregated name column in the select list must appear in the GROUP BY + if (compareSelectExprAndGroupByExpr(selectItemExpr, selectItem.getAlias(), groupByExpr)) { + context.addGroupKeyExpr(selectItemExpr); + } else if (groupByExpr instanceof SQLIdentifierExpr) { + // support expression over group key, e.g. SELECT log(G), max(A) FROM T GROUP BY G. + String groupByName = ((SQLIdentifierExpr) groupByExpr).getName(); + selectItemExpr.accept( + new MySqlASTVisitorAdapter() { + @Override + public boolean visit(SQLAggregateExpr x) { + return false; + } + + @Override + public boolean visit(SQLIdentifierExpr expr) { + if (groupByName.equalsIgnoreCase(expr.getName())) { + expr.setParent(selectItem.getParent()); + context.addGroupKeyExpr(expr); } - } - } + return false; + } + }); + } } + } + } + } + + private boolean compareSelectExprAndGroupByExpr( + SQLExpr selectItemExpr, String alias, SQLExpr groupByExpr) { + if (groupByExpr.equals(selectItemExpr)) { + return true; + } else if (groupByExpr instanceof SQLIdentifierExpr + && ((SQLIdentifierExpr) groupByExpr).getName().equalsIgnoreCase(alias)) { + return true; } + return false; + } + + private void findAllAggregationExprFromSelect(MySqlSelectQueryBlock queryBlock) { + queryBlock + .getSelectList() + .forEach( + selectItem -> + selectItem.accept( + new MySqlASTVisitorAdapter() { + @Override + public boolean visit(SQLAggregateExpr expr) { + context.addAggregationExpr(expr); + return true; + } + })); + } + + private void parseExprInSelectList( + MySqlSelectQueryBlock queryBlock, + List selectItemNames, + SQLExprToExpressionConverter exprConverter) { + List selectItems = queryBlock.getSelectList(); + for (int i = 0; i < selectItems.size(); i++) { + Expression expression = exprConverter.convert(selectItems.get(i).getExpr()); + ColumnNode columnNode = + ColumnNode.builder() + .name(selectItemNames.get(i)) + .alias(selectItems.get(i).getAlias()) + .type(columnTypeProvider.get(i)) + .expr(expression) + .build(); + columnNodes.add(columnNode); + } + } - private boolean compareSelectExprAndGroupByExpr(SQLExpr selectItemExpr, String alias, SQLExpr groupByExpr) { - if (groupByExpr.equals(selectItemExpr)) { + private List extractSelectItemNames(List selectItems) { + List selectItemNames = new ArrayList<>(); + for (SQLSelectItem selectItem : selectItems) { + selectItemNames.add(nameOfSelectItem(selectItem)); + } + return selectItemNames; + } + + private void rewriteFunctionNameToLowerCase(MySqlSelectQueryBlock query) { + query.accept( + new MySqlASTVisitorAdapter() { + @Override + public boolean visit(SQLMethodInvokeExpr x) { + x.setMethodName(x.getMethodName().toLowerCase()); return true; - } else if (groupByExpr instanceof SQLIdentifierExpr - && ((SQLIdentifierExpr) groupByExpr).getName().equalsIgnoreCase(alias)) { - return true; - } - return false; + } + }); + } + + private String nameOfSelectItem(SQLSelectItem selectItem) { + return Strings.isNullOrEmpty(selectItem.getAlias()) + ? Context.nameOfExpr(selectItem.getExpr()) + : selectItem.getAlias(); + } + + @RequiredArgsConstructor + public static class Context { + private final AliasGenerator aliasGenerator = new AliasGenerator(); + + private final Map selectSQLExprAliasMap; + + @Getter private final Map groupKeyExprMap = new LinkedHashMap<>(); + @Getter private final Map aggregationExprMap = new LinkedHashMap<>(); + + Optional resolve(SQLExpr expr) { + if (groupKeyExprMap.containsKey(expr)) { + return Optional.of(groupKeyExprMap.get(expr).getExpression()); + } else if (aggregationExprMap.containsKey(expr)) { + return Optional.of(aggregationExprMap.get(expr).getExpression()); + } else { + return Optional.empty(); + } } - private void findAllAggregationExprFromSelect(MySqlSelectQueryBlock queryBlock) { - queryBlock.getSelectList().forEach(selectItem -> selectItem.accept(new MySqlASTVisitorAdapter() { - @Override - public boolean visit(SQLAggregateExpr expr) { - context.addAggregationExpr(expr); - return true; - } - })); + public void addGroupKeyExpr(SQLExpr groupKeyExpr) { + if (!groupKeyExprMap.containsKey(groupKeyExpr)) { + groupKeyExprMap.put(groupKeyExpr, new GroupKeyExpr(groupKeyExpr)); + } } - private void parseExprInSelectList( - MySqlSelectQueryBlock queryBlock, List selectItemNames, - SQLExprToExpressionConverter exprConverter) { - List selectItems = queryBlock.getSelectList(); - for (int i = 0; i < selectItems.size(); i++) { - Expression expression = exprConverter.convert(selectItems.get(i).getExpr()); - ColumnNode columnNode = ColumnNode.builder() - .name(selectItemNames.get(i)) - .alias(selectItems.get(i).getAlias()) - .type(columnTypeProvider.get(i)) - .expr(expression) - .build(); - columnNodes.add(columnNode); - } + public void addAggregationExpr(SQLAggregateExpr aggregationExpr) { + if (!aggregationExprMap.containsKey(aggregationExpr)) { + aggregationExprMap.put(aggregationExpr, new AggregationExpr(aggregationExpr)); + } } - private List extractSelectItemNames(List selectItems) { - List selectItemNames = new ArrayList<>(); - for (SQLSelectItem selectItem: selectItems){ - selectItemNames.add(nameOfSelectItem(selectItem)); + @Getter + public class GroupKeyExpr { + private final SQLExpr expr; + private final Expression expression; + + public GroupKeyExpr(SQLExpr expr) { + this.expr = expr; + String exprName = nameOfExpr(expr).replace(".", "#"); + if (expr instanceof SQLIdentifierExpr + && selectSQLExprAliasMap.values().contains(((SQLIdentifierExpr) expr).getName())) { + exprName = ((SQLIdentifierExpr) expr).getName(); } - return selectItemNames; + this.expression = ExpressionFactory.ref(selectSQLExprAliasMap.getOrDefault(expr, exprName)); + } } - private void rewriteFunctionNameToLowerCase(MySqlSelectQueryBlock query) { - query.accept(new MySqlASTVisitorAdapter() { - @Override - public boolean visit(SQLMethodInvokeExpr x) { - x.setMethodName(x.getMethodName().toLowerCase()); - return true; - } - }); + @Getter + public class AggregationExpr { + private final SQLAggregateExpr expr; + private final Expression expression; + + public AggregationExpr(SQLAggregateExpr expr) { + this.expr = expr; + this.expression = + ExpressionFactory.ref( + selectSQLExprAliasMap.getOrDefault( + expr, aliasGenerator.nextAlias(expr.getMethodName()))); + } } - private String nameOfSelectItem(SQLSelectItem selectItem) { - return Strings.isNullOrEmpty(selectItem.getAlias()) ? Context - .nameOfExpr(selectItem.getExpr()) : selectItem.getAlias(); + public static String nameOfExpr(SQLExpr expr) { + String exprName = expr.toString().toLowerCase(); + if (expr instanceof SQLAggregateExpr) { + SQLAggregateExpr aggExpr = (SQLAggregateExpr) expr; + SQLAggregateOption option = aggExpr.getOption(); + exprName = + option == null + ? String.format("%s(%s)", aggExpr.getMethodName(), aggExpr.getArguments().get(0)) + : String.format( + "%s(%s %s)", + aggExpr.getMethodName(), option.name(), aggExpr.getArguments().get(0)); + } else if (expr instanceof SQLMethodInvokeExpr) { + exprName = + String.format( + "%s(%s)", + ((SQLMethodInvokeExpr) expr).getMethodName(), + nameOfExpr(((SQLMethodInvokeExpr) expr).getParameters().get(0))); + } else if (expr instanceof SQLIdentifierExpr) { + exprName = ((SQLIdentifierExpr) expr).getName(); + } else if (expr instanceof SQLCastExpr) { + exprName = + String.format( + "CAST(%s AS %s)", + ((SQLCastExpr) expr).getExpr(), ((SQLCastExpr) expr).getDataType().getName()); + } + return exprName; } - @RequiredArgsConstructor - public static class Context { - private final AliasGenerator aliasGenerator = new AliasGenerator(); - - private final Map selectSQLExprAliasMap; - - @Getter - private final Map groupKeyExprMap = new LinkedHashMap<>(); - @Getter - private final Map aggregationExprMap = new LinkedHashMap<>(); - - Optional resolve(SQLExpr expr) { - if (groupKeyExprMap.containsKey(expr)) { - return Optional.of(groupKeyExprMap.get(expr).getExpression()); - } else if (aggregationExprMap.containsKey(expr)) { - return Optional.of(aggregationExprMap.get(expr).getExpression()); - } else { - return Optional.empty(); - } - } + static class AliasGenerator { + private int aliasSuffix = 0; - public void addGroupKeyExpr(SQLExpr groupKeyExpr) { - if (!groupKeyExprMap.containsKey(groupKeyExpr)) { - groupKeyExprMap.put(groupKeyExpr, new GroupKeyExpr(groupKeyExpr)); - } - } - - public void addAggregationExpr(SQLAggregateExpr aggregationExpr) { - if (!aggregationExprMap.containsKey(aggregationExpr)) { - aggregationExprMap.put(aggregationExpr, new AggregationExpr(aggregationExpr)); - } - } + private String nextAlias(String name) { + return String.format("%s_%d", name, next()); + } - @Getter - public class GroupKeyExpr { - private final SQLExpr expr; - private final Expression expression; - - public GroupKeyExpr(SQLExpr expr) { - this.expr = expr; - String exprName = nameOfExpr(expr).replace(".", "#"); - if (expr instanceof SQLIdentifierExpr - && selectSQLExprAliasMap.values().contains(((SQLIdentifierExpr) expr).getName())) { - exprName = ((SQLIdentifierExpr) expr).getName(); - } - this.expression = ExpressionFactory.ref(selectSQLExprAliasMap.getOrDefault(expr, exprName)); - } - } - - @Getter - public class AggregationExpr { - private final SQLAggregateExpr expr; - private final Expression expression; - - public AggregationExpr(SQLAggregateExpr expr) { - this.expr = expr; - this.expression = - ExpressionFactory.ref(selectSQLExprAliasMap.getOrDefault(expr, aliasGenerator - .nextAlias(expr.getMethodName()))); - } - } - - public static String nameOfExpr(SQLExpr expr) { - String exprName = expr.toString().toLowerCase(); - if (expr instanceof SQLAggregateExpr) { - SQLAggregateExpr aggExpr = (SQLAggregateExpr) expr; - SQLAggregateOption option = aggExpr.getOption(); - exprName = option == null - ? String.format("%s(%s)", aggExpr.getMethodName(), aggExpr.getArguments().get(0)) - : String.format("%s(%s %s)", aggExpr.getMethodName(), option.name(), - aggExpr.getArguments().get(0)); - } else if (expr instanceof SQLMethodInvokeExpr) { - exprName = String.format("%s(%s)", ((SQLMethodInvokeExpr) expr).getMethodName(), - nameOfExpr(((SQLMethodInvokeExpr) expr).getParameters().get(0))); - } else if (expr instanceof SQLIdentifierExpr) { - exprName = ((SQLIdentifierExpr) expr).getName(); - } else if (expr instanceof SQLCastExpr) { - exprName = String.format("CAST(%s AS %s)", ((SQLCastExpr) expr).getExpr(), - ((SQLCastExpr) expr).getDataType().getName()); - } - return exprName; - } - - static class AliasGenerator { - private int aliasSuffix = 0; - - private String nextAlias(String name) { - return String.format("%s_%d", name, next()); - } - - private Integer next() { - return aliasSuffix++; - } - } + private Integer next() { + return aliasSuffix++; + } } + } } diff --git a/legacy/src/main/java/org/opensearch/sql/legacy/query/planner/converter/SQLExprToExpressionConverter.java b/legacy/src/main/java/org/opensearch/sql/legacy/query/planner/converter/SQLExprToExpressionConverter.java index 0315fef900..800dac8426 100644 --- a/legacy/src/main/java/org/opensearch/sql/legacy/query/planner/converter/SQLExprToExpressionConverter.java +++ b/legacy/src/main/java/org/opensearch/sql/legacy/query/planner/converter/SQLExprToExpressionConverter.java @@ -3,7 +3,6 @@ * SPDX-License-Identifier: Apache-2.0 */ - package org.opensearch.sql.legacy.query.planner.converter; import static org.opensearch.sql.legacy.expression.core.ExpressionFactory.cast; @@ -27,86 +26,86 @@ import org.opensearch.sql.legacy.expression.core.operator.ScalarOperation; import org.opensearch.sql.legacy.expression.model.ExprValueFactory; -/** - * The definition of {@link SQLExpr} to {@link Expression} converter. - */ +/** The definition of {@link SQLExpr} to {@link Expression} converter. */ @RequiredArgsConstructor public class SQLExprToExpressionConverter { - private static final Map binaryOperatorOperationMap = - new ImmutableMap.Builder() - .put(SQLBinaryOperator.Add, ScalarOperation.ADD) - .put(SQLBinaryOperator.Subtract, ScalarOperation.SUBTRACT) - .put(SQLBinaryOperator.Multiply, ScalarOperation.MULTIPLY) - .put(SQLBinaryOperator.Divide, ScalarOperation.DIVIDE) - .put(SQLBinaryOperator.Modulus, ScalarOperation.MODULES) - .build(); - private static final Map methodOperationMap = - new ImmutableMap.Builder() - .put(ScalarOperation.ABS.getName(), ScalarOperation.ABS) - .put(ScalarOperation.ACOS.getName(), ScalarOperation.ACOS) - .put(ScalarOperation.ASIN.getName(), ScalarOperation.ASIN) - .put(ScalarOperation.ATAN.getName(), ScalarOperation.ATAN) - .put(ScalarOperation.ATAN2.getName(), ScalarOperation.ATAN2) - .put(ScalarOperation.TAN.getName(), ScalarOperation.TAN) - .put(ScalarOperation.CBRT.getName(), ScalarOperation.CBRT) - .put(ScalarOperation.CEIL.getName(), ScalarOperation.CEIL) - .put(ScalarOperation.COS.getName(), ScalarOperation.COS) - .put(ScalarOperation.COSH.getName(), ScalarOperation.COSH) - .put(ScalarOperation.EXP.getName(), ScalarOperation.EXP) - .put(ScalarOperation.FLOOR.getName(), ScalarOperation.FLOOR) - .put(ScalarOperation.LN.getName(), ScalarOperation.LN) - .put(ScalarOperation.LOG.getName(), ScalarOperation.LOG) - .put(ScalarOperation.LOG2.getName(), ScalarOperation.LOG2) - .put(ScalarOperation.LOG10.getName(), ScalarOperation.LOG10) - .build(); - + private static final Map binaryOperatorOperationMap = + new ImmutableMap.Builder() + .put(SQLBinaryOperator.Add, ScalarOperation.ADD) + .put(SQLBinaryOperator.Subtract, ScalarOperation.SUBTRACT) + .put(SQLBinaryOperator.Multiply, ScalarOperation.MULTIPLY) + .put(SQLBinaryOperator.Divide, ScalarOperation.DIVIDE) + .put(SQLBinaryOperator.Modulus, ScalarOperation.MODULES) + .build(); + private static final Map methodOperationMap = + new ImmutableMap.Builder() + .put(ScalarOperation.ABS.getName(), ScalarOperation.ABS) + .put(ScalarOperation.ACOS.getName(), ScalarOperation.ACOS) + .put(ScalarOperation.ASIN.getName(), ScalarOperation.ASIN) + .put(ScalarOperation.ATAN.getName(), ScalarOperation.ATAN) + .put(ScalarOperation.ATAN2.getName(), ScalarOperation.ATAN2) + .put(ScalarOperation.TAN.getName(), ScalarOperation.TAN) + .put(ScalarOperation.CBRT.getName(), ScalarOperation.CBRT) + .put(ScalarOperation.CEIL.getName(), ScalarOperation.CEIL) + .put(ScalarOperation.COS.getName(), ScalarOperation.COS) + .put(ScalarOperation.COSH.getName(), ScalarOperation.COSH) + .put(ScalarOperation.EXP.getName(), ScalarOperation.EXP) + .put(ScalarOperation.FLOOR.getName(), ScalarOperation.FLOOR) + .put(ScalarOperation.LN.getName(), ScalarOperation.LN) + .put(ScalarOperation.LOG.getName(), ScalarOperation.LOG) + .put(ScalarOperation.LOG2.getName(), ScalarOperation.LOG2) + .put(ScalarOperation.LOG10.getName(), ScalarOperation.LOG10) + .build(); - private final SQLAggregationParser.Context context; + private final SQLAggregationParser.Context context; - /** - * Convert the {@link SQLExpr} to {@link Expression} - * - * @param expr {@link SQLExpr} - * @return expression {@link Expression} - */ - public Expression convert(SQLExpr expr) { - Optional resolvedExpression = context.resolve(expr); - if (resolvedExpression.isPresent()) { - return resolvedExpression.get(); - } else { - if (expr instanceof SQLBinaryOpExpr) { - return binaryOperatorToExpression((SQLBinaryOpExpr) expr, this::convert); - } else if (expr instanceof SQLMethodInvokeExpr) { - return methodToExpression((SQLMethodInvokeExpr) expr, this::convert); - } else if (expr instanceof SQLValuableExpr) { - return literal(ExprValueFactory.from(((SQLValuableExpr) expr).getValue())); - } else if (expr instanceof SQLCastExpr) { - return cast(convert(((SQLCastExpr) expr).getExpr())); - } else { - throw new RuntimeException("unsupported expr: " + expr); - } - } + /** + * Convert the {@link SQLExpr} to {@link Expression} + * + * @param expr {@link SQLExpr} + * @return expression {@link Expression} + */ + public Expression convert(SQLExpr expr) { + Optional resolvedExpression = context.resolve(expr); + if (resolvedExpression.isPresent()) { + return resolvedExpression.get(); + } else { + if (expr instanceof SQLBinaryOpExpr) { + return binaryOperatorToExpression((SQLBinaryOpExpr) expr, this::convert); + } else if (expr instanceof SQLMethodInvokeExpr) { + return methodToExpression((SQLMethodInvokeExpr) expr, this::convert); + } else if (expr instanceof SQLValuableExpr) { + return literal(ExprValueFactory.from(((SQLValuableExpr) expr).getValue())); + } else if (expr instanceof SQLCastExpr) { + return cast(convert(((SQLCastExpr) expr).getExpr())); + } else { + throw new RuntimeException("unsupported expr: " + expr); + } } + } - private Expression binaryOperatorToExpression(SQLBinaryOpExpr expr, - Function converter) { - if (binaryOperatorOperationMap.containsKey(expr.getOperator())) { - return ExpressionFactory.of(binaryOperatorOperationMap.get(expr.getOperator()), - Arrays.asList(converter.apply(expr.getLeft()), - converter.apply(expr.getRight()))); - } else { - throw new UnsupportedOperationException("unsupported operator: " + expr.getOperator().getName()); - } + private Expression binaryOperatorToExpression( + SQLBinaryOpExpr expr, Function converter) { + if (binaryOperatorOperationMap.containsKey(expr.getOperator())) { + return ExpressionFactory.of( + binaryOperatorOperationMap.get(expr.getOperator()), + Arrays.asList(converter.apply(expr.getLeft()), converter.apply(expr.getRight()))); + } else { + throw new UnsupportedOperationException( + "unsupported operator: " + expr.getOperator().getName()); } + } - private Expression methodToExpression(SQLMethodInvokeExpr expr, Function converter) { - String methodName = expr.getMethodName().toLowerCase(); - if (methodOperationMap.containsKey(methodName)) { + private Expression methodToExpression( + SQLMethodInvokeExpr expr, Function converter) { + String methodName = expr.getMethodName().toLowerCase(); + if (methodOperationMap.containsKey(methodName)) { - return ExpressionFactory.of(methodOperationMap.get(methodName), - expr.getParameters().stream().map(converter).collect(Collectors.toList())); - } else { - throw new UnsupportedOperationException("unsupported operator: " + expr.getMethodName()); - } + return ExpressionFactory.of( + methodOperationMap.get(methodName), + expr.getParameters().stream().map(converter).collect(Collectors.toList())); + } else { + throw new UnsupportedOperationException("unsupported operator: " + expr.getMethodName()); } + } } diff --git a/legacy/src/main/java/org/opensearch/sql/legacy/query/planner/converter/SQLToOperatorConverter.java b/legacy/src/main/java/org/opensearch/sql/legacy/query/planner/converter/SQLToOperatorConverter.java index fbaff0ba18..4d1ab58160 100644 --- a/legacy/src/main/java/org/opensearch/sql/legacy/query/planner/converter/SQLToOperatorConverter.java +++ b/legacy/src/main/java/org/opensearch/sql/legacy/query/planner/converter/SQLToOperatorConverter.java @@ -3,7 +3,6 @@ * SPDX-License-Identifier: Apache-2.0 */ - package org.opensearch.sql.legacy.query.planner.converter; import com.alibaba.druid.sql.dialect.mysql.ast.statement.MySqlSelectQueryBlock; @@ -24,53 +23,49 @@ import org.opensearch.sql.legacy.query.planner.physical.node.project.PhysicalProject; import org.opensearch.sql.legacy.query.planner.physical.node.scroll.PhysicalScroll; -/** - * Definition of SQL to PhysicalOperator converter. - */ +/** Definition of SQL to PhysicalOperator converter. */ public class SQLToOperatorConverter extends MySqlASTVisitorAdapter { - private static final Logger LOG = LogManager.getLogger(SQLToOperatorConverter.class); - - private final Client client; - private final SQLAggregationParser aggregationParser; + private static final Logger LOG = LogManager.getLogger(SQLToOperatorConverter.class); - @Getter - private PhysicalOperator physicalOperator; + private final Client client; + private final SQLAggregationParser aggregationParser; - public SQLToOperatorConverter(Client client, ColumnTypeProvider columnTypeProvider) { - this.client = client; - this.aggregationParser = new SQLAggregationParser(columnTypeProvider); - } + @Getter private PhysicalOperator physicalOperator; - @Override - public boolean visit(MySqlSelectQueryBlock query) { + public SQLToOperatorConverter(Client client, ColumnTypeProvider columnTypeProvider) { + this.client = client; + this.aggregationParser = new SQLAggregationParser(columnTypeProvider); + } - //1. parse the aggregation - aggregationParser.parse(query); + @Override + public boolean visit(MySqlSelectQueryBlock query) { + // 1. parse the aggregation + aggregationParser.parse(query); - //2. construct the PhysicalOperator - physicalOperator = project(scroll(query)); - return false; - } + // 2. construct the PhysicalOperator + physicalOperator = project(scroll(query)); + return false; + } - /** - * Get list of {@link ColumnNode}. - * - * @return list of {@link ColumnNode}. - */ - public List getColumnNodes() { - return aggregationParser.getColumnNodes(); - } + /** + * Get list of {@link ColumnNode}. + * + * @return list of {@link ColumnNode}. + */ + public List getColumnNodes() { + return aggregationParser.getColumnNodes(); + } - private PhysicalOperator project(PhysicalOperator input) { - return new PhysicalProject(input, aggregationParser.getColumnNodes()); - } + private PhysicalOperator project(PhysicalOperator input) { + return new PhysicalProject(input, aggregationParser.getColumnNodes()); + } - @SneakyThrows - private PhysicalOperator scroll(MySqlSelectQueryBlock query) { - query.getSelectList().clear(); - query.getSelectList().addAll(aggregationParser.selectItemList()); - Select select = new SqlParser().parseSelect(query); - return new PhysicalScroll(new AggregationQueryAction(client, select)); - } + @SneakyThrows + private PhysicalOperator scroll(MySqlSelectQueryBlock query) { + query.getSelectList().clear(); + query.getSelectList().addAll(aggregationParser.selectItemList()); + Select select = new SqlParser().parseSelect(query); + return new PhysicalScroll(new AggregationQueryAction(client, select)); + } } diff --git a/legacy/src/main/java/org/opensearch/sql/legacy/query/planner/core/QueryParams.java b/legacy/src/main/java/org/opensearch/sql/legacy/query/planner/core/QueryParams.java index ae5f0fb9c8..0bf93f5787 100644 --- a/legacy/src/main/java/org/opensearch/sql/legacy/query/planner/core/QueryParams.java +++ b/legacy/src/main/java/org/opensearch/sql/legacy/query/planner/core/QueryParams.java @@ -23,17 +23,19 @@ public class QueryParams { /** Join type, ex. inner join, left join */ private final SQLJoinTableSource.JoinType joinType; - /** - *
    -     * Join conditions in ON clause grouped by OR.
    -     * For example, "ON (a.name = b.id AND a.age = b.age) OR a.location = b.address"
    -     * => list: [
    -     * [ (a.name, b.id), (a.age, b.age) ],
    -     * [ (a.location, b.address) ]
    -     * ]
    -     * 
    - */ - private final List>> joinConditions; + /** + * + * + *
    +   * Join conditions in ON clause grouped by OR.
    +   * For example, "ON (a.name = b.id AND a.age = b.age) OR a.location = b.address"
    +   * => list: [
    +   * [ (a.name, b.id), (a.age, b.age) ],
    +   * [ (a.location, b.address) ]
    +   * ]
    +   * 
    + */ + private final List>> joinConditions; public QueryParams( TableInJoinRequestBuilder request1, diff --git a/legacy/src/main/java/org/opensearch/sql/legacy/query/planner/logical/node/Join.java b/legacy/src/main/java/org/opensearch/sql/legacy/query/planner/logical/node/Join.java index 405a8a9f72..c23786d6b3 100644 --- a/legacy/src/main/java/org/opensearch/sql/legacy/query/planner/logical/node/Join.java +++ b/legacy/src/main/java/org/opensearch/sql/legacy/query/planner/logical/node/Join.java @@ -71,23 +71,25 @@ public String toString() { return "Join [ conditions=" + condition + " type=" + type + " ]"; } - /** - *
    -     * Join condition in ON clause grouped by OR.
    -     * 

    - * For example, "ON (a.name = b.id AND a.age = b.age) OR a.location = b.address" - * => input list: [ - * [ (a.name, b.id), (a.age, b.age) ], - * [ (a.location, b.address) ] - * ] - *

    - * => JoinCondition: - * leftTableAlias: "a", rightTableAlias: "b" - * leftColumnNames: [ ["name", "age"], ["location"] ] - * rightColumnNames: [ ["id", "age"], ["address" ] ] - *

    - */ - public static class JoinCondition { + /** + * + * + *
    +   * Join condition in ON clause grouped by OR.
    +   * 

    + * For example, "ON (a.name = b.id AND a.age = b.age) OR a.location = b.address" + * => input list: [ + * [ (a.name, b.id), (a.age, b.age) ], + * [ (a.location, b.address) ] + * ] + *

    + * => JoinCondition: + * leftTableAlias: "a", rightTableAlias: "b" + * leftColumnNames: [ ["name", "age"], ["location"] ] + * rightColumnNames: [ ["id", "age"], ["address" ] ] + *

    + */ + public static class JoinCondition { private final String leftTableAlias; private final String rightTableAlias; diff --git a/legacy/src/main/java/org/opensearch/sql/legacy/query/planner/logical/node/Sort.java b/legacy/src/main/java/org/opensearch/sql/legacy/query/planner/logical/node/Sort.java index 670be71de5..f9033ce90f 100644 --- a/legacy/src/main/java/org/opensearch/sql/legacy/query/planner/logical/node/Sort.java +++ b/legacy/src/main/java/org/opensearch/sql/legacy/query/planner/logical/node/Sort.java @@ -3,7 +3,6 @@ * SPDX-License-Identifier: Apache-2.0 */ - package org.opensearch.sql.legacy.query.planner.logical.node; import java.util.List; @@ -13,45 +12,37 @@ import org.opensearch.sql.legacy.query.planner.physical.PhysicalOperator; import org.opensearch.sql.legacy.query.planner.physical.node.sort.QuickSort; -/** - * Logical operator for Sort. - */ +/** Logical operator for Sort. */ public class Sort implements LogicalOperator { - private final LogicalOperator next; - - /** - * Column name list in ORDER BY - */ - private final List orderByColNames; - - /** - * Order by type, ex. ASC, DESC - */ - private final String orderByType; + private final LogicalOperator next; + /** Column name list in ORDER BY */ + private final List orderByColNames; - public Sort(LogicalOperator next, List orderByColNames, String orderByType) { - this.next = next; - this.orderByColNames = orderByColNames; - this.orderByType = orderByType.toUpperCase(); - } + /** Order by type, ex. ASC, DESC */ + private final String orderByType; - @Override - public PlanNode[] children() { - return new PlanNode[]{next}; - } + public Sort(LogicalOperator next, List orderByColNames, String orderByType) { + this.next = next; + this.orderByColNames = orderByColNames; + this.orderByType = orderByType.toUpperCase(); + } - @Override - public PhysicalOperator[] toPhysical(Map> optimalOps) { - return new PhysicalOperator[]{ - new QuickSort<>(optimalOps.get(next), orderByColNames, orderByType) - }; - } + @Override + public PlanNode[] children() { + return new PlanNode[] {next}; + } - @Override - public String toString() { - return "Sort [ columns=" + orderByColNames + " order=" + orderByType + " ]"; - } + @Override + public PhysicalOperator[] toPhysical(Map> optimalOps) { + return new PhysicalOperator[] { + new QuickSort<>(optimalOps.get(next), orderByColNames, orderByType) + }; + } + @Override + public String toString() { + return "Sort [ columns=" + orderByColNames + " order=" + orderByType + " ]"; + } } diff --git a/legacy/src/main/java/org/opensearch/sql/legacy/query/planner/logical/node/TableScan.java b/legacy/src/main/java/org/opensearch/sql/legacy/query/planner/logical/node/TableScan.java index 466779faae..16af199ed7 100644 --- a/legacy/src/main/java/org/opensearch/sql/legacy/query/planner/logical/node/TableScan.java +++ b/legacy/src/main/java/org/opensearch/sql/legacy/query/planner/logical/node/TableScan.java @@ -3,7 +3,6 @@ * SPDX-License-Identifier: Apache-2.0 */ - package org.opensearch.sql.legacy.query.planner.logical.node; import java.util.Map; @@ -13,54 +12,44 @@ import org.opensearch.sql.legacy.query.planner.physical.PhysicalOperator; import org.opensearch.sql.legacy.query.planner.physical.node.scroll.Scroll; -/** - * Table scan - */ +/** Table scan */ public class TableScan implements LogicalOperator { - /** - * Request builder for the table - */ - private final TableInJoinRequestBuilder request; - - /** - * Page size for physical operator - */ - private final int pageSize; - - public TableScan(TableInJoinRequestBuilder request, int pageSize) { - this.request = request; - this.pageSize = pageSize; - } + /** Request builder for the table */ + private final TableInJoinRequestBuilder request; - @Override - public PlanNode[] children() { - return new PlanNode[0]; - } + /** Page size for physical operator */ + private final int pageSize; - @Override - public PhysicalOperator[] toPhysical(Map> optimalOps) { - return new PhysicalOperator[]{ - new Scroll(request, pageSize) - }; - } + public TableScan(TableInJoinRequestBuilder request, int pageSize) { + this.request = request; + this.pageSize = pageSize; + } - @Override - public String toString() { - return "TableScan"; - } + @Override + public PlanNode[] children() { + return new PlanNode[0]; + } + @Override + public PhysicalOperator[] toPhysical(Map> optimalOps) { + return new PhysicalOperator[] {new Scroll(request, pageSize)}; + } - /********************************************* - * Getters for Explain - *********************************************/ + @Override + public String toString() { + return "TableScan"; + } - public String getTableAlias() { - return request.getAlias(); - } + /********************************************* + * Getters for Explain + *********************************************/ - public String getTableName() { - return request.getOriginalSelect().getFrom().get(0).getIndex(); - } + public String getTableAlias() { + return request.getAlias(); + } + public String getTableName() { + return request.getOriginalSelect().getFrom().get(0).getIndex(); + } } diff --git a/legacy/src/main/java/org/opensearch/sql/legacy/query/planner/logical/node/Top.java b/legacy/src/main/java/org/opensearch/sql/legacy/query/planner/logical/node/Top.java index e39f36ed5a..978d996ad0 100644 --- a/legacy/src/main/java/org/opensearch/sql/legacy/query/planner/logical/node/Top.java +++ b/legacy/src/main/java/org/opensearch/sql/legacy/query/planner/logical/node/Top.java @@ -3,7 +3,6 @@ * SPDX-License-Identifier: Apache-2.0 */ - package org.opensearch.sql.legacy.query.planner.logical.node; import java.util.Map; @@ -20,52 +19,51 @@ */ public class Top implements LogicalOperator, PhysicalOperator { - private final PlanNode next; + private final PlanNode next; - /** - * Number of rows to return in total - */ - private int count; + /** Number of rows to return in total */ + private int count; - @SuppressWarnings("unchecked") - public Top(PlanNode next, int count) { - this.next = next; - this.count = count; - } + @SuppressWarnings("unchecked") + public Top(PlanNode next, int count) { + this.next = next; + this.count = count; + } - @Override - public PlanNode[] children() { - return new PlanNode[]{next}; - } + @Override + public PlanNode[] children() { + return new PlanNode[] {next}; + } - @SuppressWarnings("unchecked") - @Override - public boolean hasNext() { - return count > 0 && ((PhysicalOperator) next).hasNext(); - } + @SuppressWarnings("unchecked") + @Override + public boolean hasNext() { + return count > 0 && ((PhysicalOperator) next).hasNext(); + } - @SuppressWarnings("unchecked") - @Override - public Row next() { - count--; - return ((PhysicalOperator) next).next(); - } + @SuppressWarnings("unchecked") + @Override + public Row next() { + count--; + return ((PhysicalOperator) next).next(); + } - @Override - public PhysicalOperator[] toPhysical(Map> optimalOps) { - if (!(next instanceof LogicalOperator)) { - throw new IllegalStateException("Only logical operator can perform this toPhysical() operation"); - } - return new PhysicalOperator[]{new Top<>(optimalOps.get(next), count)}; + @Override + public PhysicalOperator[] toPhysical(Map> optimalOps) { + if (!(next instanceof LogicalOperator)) { + throw new IllegalStateException( + "Only logical operator can perform this toPhysical() operation"); } + return new PhysicalOperator[] {new Top<>(optimalOps.get(next), count)}; + } - @Override - public Cost estimate() { - return new Cost(); - } + @Override + public Cost estimate() { + return new Cost(); + } - @Override - public String toString() { - return "Top [ " + "count=" + count + " ]"; - } + @Override + public String toString() { + return "Top [ count=" + count + " ]"; + } } diff --git a/legacy/src/main/java/org/opensearch/sql/legacy/query/planner/physical/node/join/JoinAlgorithm.java b/legacy/src/main/java/org/opensearch/sql/legacy/query/planner/physical/node/join/JoinAlgorithm.java index 9fcb977beb..9f2c9e4174 100644 --- a/legacy/src/main/java/org/opensearch/sql/legacy/query/planner/physical/node/join/JoinAlgorithm.java +++ b/legacy/src/main/java/org/opensearch/sql/legacy/query/planner/physical/node/join/JoinAlgorithm.java @@ -91,19 +91,22 @@ public void close() { LOG.debug("Cleared all resources used by join"); } - /** - * Build-probe left and right block by block to prefetch next matches (and mismatches if outer join). - *
      - *
    1. Build hash table and open right side. - *
    2. Keep probing right to find matched rows (meanwhile update mismatched set) - *
    3. Check if any row in mismatched set to return in the case of outer join. - *
    4. Nothing remained now, move on to next block of left. Go back to step 1. - *
    - * This is a new run AND no block from left means algorithm should stop and return empty. - */ - @Override - protected Collection> prefetch() throws Exception { - while (!isNewRunButNoMoreBlockFromLeft()) { + /** + * Build-probe left and right block by block to prefetch next matches (and mismatches if outer + * join). + * + *
      + *
    1. Build hash table and open right side. + *
    2. Keep probing right to find matched rows (meanwhile update mismatched set) + *
    3. Check if any row in mismatched set to return in the case of outer join. + *
    4. Nothing remained now, move on to next block of left. Go back to step 1. + *
    + * + * This is a new run AND no block from left means algorithm should stop and return empty. + */ + @Override + protected Collection> prefetch() throws Exception { + while (!isNewRunButNoMoreBlockFromLeft()) { // 1.Build hash table and (re-)open right side for the new run if (isNewRun()) { diff --git a/legacy/src/main/java/org/opensearch/sql/legacy/query/planner/physical/node/scroll/SearchHitRow.java b/legacy/src/main/java/org/opensearch/sql/legacy/query/planner/physical/node/scroll/SearchHitRow.java index 1750563e47..d03dd5af40 100644 --- a/legacy/src/main/java/org/opensearch/sql/legacy/query/planner/physical/node/scroll/SearchHitRow.java +++ b/legacy/src/main/java/org/opensearch/sql/legacy/query/planner/physical/node/scroll/SearchHitRow.java @@ -14,6 +14,8 @@ import org.opensearch.sql.legacy.query.planner.physical.Row; /** + * + * *
      * Search hit row that implements basic accessor for SearchHit.
      * Encapsulate all OpenSearch specific knowledge: how to parse source including nested path.
    diff --git a/legacy/src/main/java/org/opensearch/sql/legacy/query/planner/resource/Stats.java b/legacy/src/main/java/org/opensearch/sql/legacy/query/planner/resource/Stats.java
    index ec03eeaccb..3ff4662ce4 100644
    --- a/legacy/src/main/java/org/opensearch/sql/legacy/query/planner/resource/Stats.java
    +++ b/legacy/src/main/java/org/opensearch/sql/legacy/query/planner/resource/Stats.java
    @@ -3,67 +3,61 @@
      * SPDX-License-Identifier: Apache-2.0
      */
     
    -
     package org.opensearch.sql.legacy.query.planner.resource;
     
     import org.opensearch.client.Client;
     
     /**
      * Statistics collector collects from OpenSearch stats, JVM etc for other components:
    - * 

    - * 1) Resource monitor - * 2) Cost estimation - * 3) Block size calculation + * + *

      + *
    1. Resource monitor + *
    2. Cost estimation + *
    3. Block size calculation + *
    */ public class Stats { - /** - * Client connection to OpenSearch cluster (unused now) - */ - private Client client; - - public Stats(Client client) { - this.client = client; - } - - public MemStats collectMemStats() { - return new MemStats( - Runtime.getRuntime().freeMemory(), - Runtime.getRuntime().totalMemory() - ); - } + /** Client connection to OpenSearch cluster (unused now) */ + private Client client; - /** - * Statistics data class for memory usage - */ - public static class MemStats { - private long free; - private long total; + public Stats(Client client) { + this.client = client; + } - public MemStats(long free, long total) { - this.free = free; - this.total = total; - } + public MemStats collectMemStats() { + return new MemStats(Runtime.getRuntime().freeMemory(), Runtime.getRuntime().totalMemory()); + } - public long getFree() { - return free; - } + /** Statistics data class for memory usage */ + public static class MemStats { + private long free; + private long total; - public long getTotal() { - return total; - } + public MemStats(long free, long total) { + this.free = free; + this.total = total; } - /* - public class IndexStats { - private long size; - private long docNum; + public long getFree() { + return free; + } - public IndexStats(long size, long docNum) { - this.size = size; - this.docNum = docNum; - } + public long getTotal() { + return total; } - */ + } + + /* + public class IndexStats { + private long size; + private long docNum; + + public IndexStats(long size, long docNum) { + this.size = size; + this.docNum = docNum; + } + } + */ } diff --git a/legacy/src/main/java/org/opensearch/sql/legacy/query/planner/resource/monitor/TotalMemoryMonitor.java b/legacy/src/main/java/org/opensearch/sql/legacy/query/planner/resource/monitor/TotalMemoryMonitor.java index 961729867d..76a8c5902c 100644 --- a/legacy/src/main/java/org/opensearch/sql/legacy/query/planner/resource/monitor/TotalMemoryMonitor.java +++ b/legacy/src/main/java/org/opensearch/sql/legacy/query/planner/resource/monitor/TotalMemoryMonitor.java @@ -3,7 +3,6 @@ * SPDX-License-Identifier: Apache-2.0 */ - package org.opensearch.sql.legacy.query.planner.resource.monitor; import org.apache.logging.log4j.LogManager; @@ -12,46 +11,39 @@ import org.opensearch.sql.legacy.query.planner.resource.Stats; import org.opensearch.sql.legacy.query.planner.resource.Stats.MemStats; -/** - * Circuit breaker for total memory usage in JVM on current OpenSearch node. - */ +/** Circuit breaker for total memory usage in JVM on current OpenSearch node. */ public class TotalMemoryMonitor implements Monitor { - private static final Logger LOG = LogManager.getLogger(); - - /** - * Statistic collector - */ - private final Stats stats; + private static final Logger LOG = LogManager.getLogger(); - /** - * Upper limit for memory usage percentage - */ - private final int limit; + /** Statistic collector */ + private final Stats stats; - public TotalMemoryMonitor(Stats stats, Config config) { - this.stats = stats; - this.limit = config.circuitBreakLimit(); - } + /** Upper limit for memory usage percentage */ + private final int limit; - @Override - public boolean isHealthy() { - MemStats memStats = stats.collectMemStats(); - int usage = percentage(memUsage(memStats)); + public TotalMemoryMonitor(Stats stats, Config config) { + this.stats = stats; + this.limit = config.circuitBreakLimit(); + } - if (LOG.isDebugEnabled()) { - LOG.debug("Memory usage and limit: {}%, {}%", usage, limit); - } + @Override + public boolean isHealthy() { + MemStats memStats = stats.collectMemStats(); + int usage = percentage(memUsage(memStats)); - return usage < limit; + if (LOG.isDebugEnabled()) { + LOG.debug("Memory usage and limit: {}%, {}%", usage, limit); } - private int percentage(double usage) { - return (int) Math.round(usage * 100); - } + return usage < limit; + } - private double memUsage(MemStats memStats) { - return (1.0 * (memStats.getTotal() - memStats.getFree())) / memStats.getTotal(); - } + private int percentage(double usage) { + return (int) Math.round(usage * 100); + } + private double memUsage(MemStats memStats) { + return (1.0 * (memStats.getTotal() - memStats.getFree())) / memStats.getTotal(); + } } diff --git a/legacy/src/main/java/org/opensearch/sql/legacy/request/SqlRequest.java b/legacy/src/main/java/org/opensearch/sql/legacy/request/SqlRequest.java index 8ac66e4b70..bffdd36688 100644 --- a/legacy/src/main/java/org/opensearch/sql/legacy/request/SqlRequest.java +++ b/legacy/src/main/java/org/opensearch/sql/legacy/request/SqlRequest.java @@ -3,7 +3,6 @@ * SPDX-License-Identifier: Apache-2.0 */ - package org.opensearch.sql.legacy.request; import com.fasterxml.jackson.core.JsonFactory; @@ -22,95 +21,96 @@ public class SqlRequest { - public static final SqlRequest NULL = new SqlRequest("", null); - - String sql; - JSONObject jsonContent; - String cursor; - Integer fetchSize; - - public SqlRequest(final String sql, final JSONObject jsonContent) { - this.sql = sql; - this.jsonContent = jsonContent; - } - - public SqlRequest(final String cursor) { - this.cursor = cursor; - } - - public SqlRequest(final String sql, final Integer fetchSize, final JSONObject jsonContent) { - this.sql = sql; - this.fetchSize = fetchSize; - this.jsonContent = jsonContent; - } - - private static boolean isValidJson(String json) { - try { - new JSONObject(json); - } catch (JSONException e) { - return false; - } - return true; + public static final SqlRequest NULL = new SqlRequest("", null); + + String sql; + JSONObject jsonContent; + String cursor; + Integer fetchSize; + + public SqlRequest(final String sql, final JSONObject jsonContent) { + this.sql = sql; + this.jsonContent = jsonContent; + } + + public SqlRequest(final String cursor) { + this.cursor = cursor; + } + + public SqlRequest(final String sql, final Integer fetchSize, final JSONObject jsonContent) { + this.sql = sql; + this.fetchSize = fetchSize; + this.jsonContent = jsonContent; + } + + private static boolean isValidJson(String json) { + try { + new JSONObject(json); + } catch (JSONException e) { + return false; } - - public String getSql() { - return this.sql; - } - - public String cursor() { - return this.cursor; - } - - public Integer fetchSize() { - return this.fetchSize; - } - - public JSONObject getJsonContent() { - return this.jsonContent; - } - - /** - * JSONObject's getJSONObject method will return just the value, this helper method is to extract the key and - * value of 'filter' and return the JSON as a string. - */ - private String getFilterObjectAsString(JSONObject jsonContent) { - String filterVal = jsonContent.getJSONObject("filter").toString(); - return "{\"filter\":" + filterVal + "}"; - } - - private boolean hasFilterInRequest() { - return jsonContent != null && jsonContent.has("filter"); - } - - /** - * Takes 'filter' parameter from JSON request if JSON request and 'filter' were given and creates a QueryBuilder - * object out of it to add to the filterClauses of the BoolQueryBuilder. - */ - private void addFilterFromJson(BoolQueryBuilder boolQuery) throws SqlParseException { - try { - String filter = getFilterObjectAsString(jsonContent); - SearchModule searchModule = new SearchModule(Settings.EMPTY, Collections.emptyList()); - XContentParser parser = new JsonXContentParser( - new NamedXContentRegistry(searchModule.getNamedXContents()), - LoggingDeprecationHandler.INSTANCE, - new JsonFactory().createParser(filter)); - - // nextToken is called before passing the parser to fromXContent since the fieldName will be null if the - // first token it parses is START_OBJECT resulting in an exception - parser.nextToken(); - boolQuery.filter(BoolQueryBuilder.fromXContent(parser)); - } catch (IOException e) { - throw new SqlParseException("Unable to parse 'filter' in JSON request: " + e.getMessage()); - } - + return true; + } + + public String getSql() { + return this.sql; + } + + public String cursor() { + return this.cursor; + } + + public Integer fetchSize() { + return this.fetchSize; + } + + public JSONObject getJsonContent() { + return this.jsonContent; + } + + /** + * JSONObject's getJSONObject method will return just the value, this helper method is to extract + * the key and value of 'filter' and return the JSON as a string. + */ + private String getFilterObjectAsString(JSONObject jsonContent) { + String filterVal = jsonContent.getJSONObject("filter").toString(); + return "{\"filter\":" + filterVal + "}"; + } + + private boolean hasFilterInRequest() { + return jsonContent != null && jsonContent.has("filter"); + } + + /** + * Takes 'filter' parameter from JSON request if JSON request and 'filter' were given and creates + * a QueryBuilder object out of it to add to the filterClauses of the BoolQueryBuilder. + */ + private void addFilterFromJson(BoolQueryBuilder boolQuery) throws SqlParseException { + try { + String filter = getFilterObjectAsString(jsonContent); + SearchModule searchModule = new SearchModule(Settings.EMPTY, Collections.emptyList()); + XContentParser parser = + new JsonXContentParser( + new NamedXContentRegistry(searchModule.getNamedXContents()), + LoggingDeprecationHandler.INSTANCE, + new JsonFactory().createParser(filter)); + + // nextToken is called before passing the parser to fromXContent since the fieldName will be + // null if the + // first token it parses is START_OBJECT resulting in an exception + parser.nextToken(); + boolQuery.filter(BoolQueryBuilder.fromXContent(parser)); + } catch (IOException e) { + throw new SqlParseException("Unable to parse 'filter' in JSON request: " + e.getMessage()); } + } - public BoolQueryBuilder checkAndAddFilter(BoolQueryBuilder boolQuery) throws SqlParseException { - if (hasFilterInRequest()) { - // if WHERE was not given, create a new BoolQuery to add "filter" to - boolQuery = boolQuery == null ? new BoolQueryBuilder() : boolQuery; - addFilterFromJson(boolQuery); - } - return boolQuery; + public BoolQueryBuilder checkAndAddFilter(BoolQueryBuilder boolQuery) throws SqlParseException { + if (hasFilterInRequest()) { + // if WHERE was not given, create a new BoolQuery to add "filter" to + boolQuery = boolQuery == null ? new BoolQueryBuilder() : boolQuery; + addFilterFromJson(boolQuery); } + return boolQuery; + } } diff --git a/legacy/src/main/java/org/opensearch/sql/legacy/request/SqlRequestFactory.java b/legacy/src/main/java/org/opensearch/sql/legacy/request/SqlRequestFactory.java index 4c5d207be8..0fee6cff86 100644 --- a/legacy/src/main/java/org/opensearch/sql/legacy/request/SqlRequestFactory.java +++ b/legacy/src/main/java/org/opensearch/sql/legacy/request/SqlRequestFactory.java @@ -3,7 +3,6 @@ * SPDX-License-Identifier: Apache-2.0 */ - package org.opensearch.sql.legacy.request; import java.util.ArrayList; @@ -16,128 +15,134 @@ public class SqlRequestFactory { - private static final String SQL_URL_PARAM_KEY = "sql"; - private static final String SQL_FIELD_NAME = "query"; - private static final String PARAM_FIELD_NAME = "parameters"; - private static final String PARAM_TYPE_FIELD_NAME = "type"; - private static final String PARAM_VALUE_FIELD_NAME = "value"; + private static final String SQL_URL_PARAM_KEY = "sql"; + private static final String SQL_FIELD_NAME = "query"; + private static final String PARAM_FIELD_NAME = "parameters"; + private static final String PARAM_TYPE_FIELD_NAME = "type"; + private static final String PARAM_VALUE_FIELD_NAME = "value"; - public static final String SQL_CURSOR_FIELD_NAME = "cursor"; - public static final String SQL_FETCH_FIELD_NAME = "fetch_size"; + public static final String SQL_CURSOR_FIELD_NAME = "cursor"; + public static final String SQL_FETCH_FIELD_NAME = "fetch_size"; - public static SqlRequest getSqlRequest(RestRequest request) { - switch (request.method()) { - case POST: - return parseSqlRequestFromPayload(request); - default: - throw new IllegalArgumentException("OpenSearch SQL doesn't supported HTTP " + request.method().name()); - } + public static SqlRequest getSqlRequest(RestRequest request) { + switch (request.method()) { + case POST: + return parseSqlRequestFromPayload(request); + default: + throw new IllegalArgumentException( + "OpenSearch SQL doesn't supported HTTP " + request.method().name()); } + } - private static SqlRequest parseSqlRequestFromUrl(RestRequest restRequest) { - String sql; + private static SqlRequest parseSqlRequestFromUrl(RestRequest restRequest) { + String sql; - sql = restRequest.param(SQL_URL_PARAM_KEY); - if (sql == null) { - throw new IllegalArgumentException("Cannot find sql parameter from the URL"); - } - return new SqlRequest(sql, null); + sql = restRequest.param(SQL_URL_PARAM_KEY); + if (sql == null) { + throw new IllegalArgumentException("Cannot find sql parameter from the URL"); } + return new SqlRequest(sql, null); + } - private static SqlRequest parseSqlRequestFromPayload(RestRequest restRequest) { - String content = restRequest.content().utf8ToString(); + private static SqlRequest parseSqlRequestFromPayload(RestRequest restRequest) { + String content = restRequest.content().utf8ToString(); - JSONObject jsonContent; - try { - jsonContent = new JSONObject(content); - if (jsonContent.has(SQL_CURSOR_FIELD_NAME)) { - return new SqlRequest(jsonContent.getString(SQL_CURSOR_FIELD_NAME)); - } - } catch (JSONException e) { - throw new IllegalArgumentException("Failed to parse request payload", e); - } - String sql = jsonContent.getString(SQL_FIELD_NAME); - - if (jsonContent.has(PARAM_FIELD_NAME)) { // is a PreparedStatement - JSONArray paramArray = jsonContent.getJSONArray(PARAM_FIELD_NAME); - List parameters = parseParameters(paramArray); - return new PreparedStatementRequest(sql, validateAndGetFetchSize(jsonContent), jsonContent, parameters); - } - return new SqlRequest(sql, validateAndGetFetchSize(jsonContent), jsonContent); + JSONObject jsonContent; + try { + jsonContent = new JSONObject(content); + if (jsonContent.has(SQL_CURSOR_FIELD_NAME)) { + return new SqlRequest(jsonContent.getString(SQL_CURSOR_FIELD_NAME)); + } + } catch (JSONException e) { + throw new IllegalArgumentException("Failed to parse request payload", e); } + String sql = jsonContent.getString(SQL_FIELD_NAME); + if (jsonContent.has(PARAM_FIELD_NAME)) { // is a PreparedStatement + JSONArray paramArray = jsonContent.getJSONArray(PARAM_FIELD_NAME); + List parameters = + parseParameters(paramArray); + return new PreparedStatementRequest( + sql, validateAndGetFetchSize(jsonContent), jsonContent, parameters); + } + return new SqlRequest(sql, validateAndGetFetchSize(jsonContent), jsonContent); + } - private static Integer validateAndGetFetchSize(JSONObject jsonContent) { - Optional fetchSize = Optional.empty(); - try { - if (jsonContent.has(SQL_FETCH_FIELD_NAME)) { - fetchSize = Optional.of(jsonContent.getInt(SQL_FETCH_FIELD_NAME)); - if (fetchSize.get() < 0) { - throw new IllegalArgumentException("Fetch_size must be greater or equal to 0"); - } - } - } catch (JSONException e) { - throw new IllegalArgumentException("Failed to parse field [" + SQL_FETCH_FIELD_NAME +"]", e); + private static Integer validateAndGetFetchSize(JSONObject jsonContent) { + Optional fetchSize = Optional.empty(); + try { + if (jsonContent.has(SQL_FETCH_FIELD_NAME)) { + fetchSize = Optional.of(jsonContent.getInt(SQL_FETCH_FIELD_NAME)); + if (fetchSize.get() < 0) { + throw new IllegalArgumentException("Fetch_size must be greater or equal to 0"); } - return fetchSize.orElse(0); + } + } catch (JSONException e) { + throw new IllegalArgumentException("Failed to parse field [" + SQL_FETCH_FIELD_NAME + "]", e); } + return fetchSize.orElse(0); + } - private static List parseParameters( - JSONArray paramsJsonArray) { - List parameters = new ArrayList<>(); - for (int i = 0; i < paramsJsonArray.length(); i++) { - JSONObject paramJson = paramsJsonArray.getJSONObject(i); - String typeString = paramJson.getString(PARAM_TYPE_FIELD_NAME); - if (typeString == null) { - throw new IllegalArgumentException("Parameter type cannot be null. parameter json: " - + paramJson.toString()); - } - PreparedStatementRequest.ParameterType type; - try { - type = PreparedStatementRequest.ParameterType.valueOf(typeString.toUpperCase()); - } catch (IllegalArgumentException e) { - throw new IllegalArgumentException("Unsupported parameter type " + typeString, e); - } - try { - PreparedStatementRequest.PreparedStatementParameter parameter; - switch (type) { - case BOOLEAN: - parameter = new PreparedStatementRequest.PreparedStatementParameter<>( - paramJson.getBoolean(PARAM_VALUE_FIELD_NAME)); - parameters.add(parameter); - break; - case KEYWORD: - case STRING: - case DATE: - parameter = new PreparedStatementRequest.StringParameter( - paramJson.getString(PARAM_VALUE_FIELD_NAME)); - parameters.add(parameter); - break; - case BYTE: - case SHORT: - case INTEGER: - case LONG: - parameter = new PreparedStatementRequest.PreparedStatementParameter<>( - paramJson.getLong(PARAM_VALUE_FIELD_NAME)); - parameters.add(parameter); - break; - case FLOAT: - case DOUBLE: - parameter = new PreparedStatementRequest.PreparedStatementParameter<>( - paramJson.getDouble(PARAM_VALUE_FIELD_NAME)); - parameters.add(parameter); - break; - case NULL: - parameter = new PreparedStatementRequest.NullParameter(); - parameters.add(parameter); - break; - default: - throw new IllegalArgumentException("Failed to handle parameter type " + type.name()); - } - } catch (JSONException e) { - throw new IllegalArgumentException("Failed to parse PreparedStatement parameters", e); - } + private static List parseParameters( + JSONArray paramsJsonArray) { + List parameters = new ArrayList<>(); + for (int i = 0; i < paramsJsonArray.length(); i++) { + JSONObject paramJson = paramsJsonArray.getJSONObject(i); + String typeString = paramJson.getString(PARAM_TYPE_FIELD_NAME); + if (typeString == null) { + throw new IllegalArgumentException( + "Parameter type cannot be null. parameter json: " + paramJson.toString()); + } + PreparedStatementRequest.ParameterType type; + try { + type = PreparedStatementRequest.ParameterType.valueOf(typeString.toUpperCase()); + } catch (IllegalArgumentException e) { + throw new IllegalArgumentException("Unsupported parameter type " + typeString, e); + } + try { + PreparedStatementRequest.PreparedStatementParameter parameter; + switch (type) { + case BOOLEAN: + parameter = + new PreparedStatementRequest.PreparedStatementParameter<>( + paramJson.getBoolean(PARAM_VALUE_FIELD_NAME)); + parameters.add(parameter); + break; + case KEYWORD: + case STRING: + case DATE: + parameter = + new PreparedStatementRequest.StringParameter( + paramJson.getString(PARAM_VALUE_FIELD_NAME)); + parameters.add(parameter); + break; + case BYTE: + case SHORT: + case INTEGER: + case LONG: + parameter = + new PreparedStatementRequest.PreparedStatementParameter<>( + paramJson.getLong(PARAM_VALUE_FIELD_NAME)); + parameters.add(parameter); + break; + case FLOAT: + case DOUBLE: + parameter = + new PreparedStatementRequest.PreparedStatementParameter<>( + paramJson.getDouble(PARAM_VALUE_FIELD_NAME)); + parameters.add(parameter); + break; + case NULL: + parameter = new PreparedStatementRequest.NullParameter(); + parameters.add(parameter); + break; + default: + throw new IllegalArgumentException("Failed to handle parameter type " + type.name()); } - return parameters; + } catch (JSONException e) { + throw new IllegalArgumentException("Failed to parse PreparedStatement parameters", e); + } } + return parameters; + } } diff --git a/legacy/src/main/java/org/opensearch/sql/legacy/request/SqlRequestParam.java b/legacy/src/main/java/org/opensearch/sql/legacy/request/SqlRequestParam.java index c9d3abb320..b151fabde6 100644 --- a/legacy/src/main/java/org/opensearch/sql/legacy/request/SqlRequestParam.java +++ b/legacy/src/main/java/org/opensearch/sql/legacy/request/SqlRequestParam.java @@ -3,57 +3,56 @@ * SPDX-License-Identifier: Apache-2.0 */ - package org.opensearch.sql.legacy.request; import java.util.Map; import java.util.Optional; import org.opensearch.sql.legacy.executor.Format; -/** - * Utils class for parse the request params. - */ +/** Utils class for parse the request params. */ public class SqlRequestParam { - public static final String QUERY_PARAMS_FORMAT = "format"; - public static final String QUERY_PARAMS_PRETTY = "pretty"; - public static final String QUERY_PARAMS_ESCAPE = "escape"; - - private static final String DEFAULT_RESPONSE_FORMAT = "jdbc"; - - /** - * Parse the pretty params to decide whether the response should be pretty formatted. - * @param requestParams request params. - * @return return true if the response required pretty format, otherwise return false. - */ - public static boolean isPrettyFormat(Map requestParams) { - return requestParams.containsKey(QUERY_PARAMS_PRETTY) - && ("".equals(requestParams.get(QUERY_PARAMS_PRETTY)) - || "true".equals(requestParams.get(QUERY_PARAMS_PRETTY))); - } - - /** - * Parse the request params and return the {@link Format} of the response - * @param requestParams request params - * @return The response Format. - */ - public static Format getFormat(Map requestParams) { - String formatName = - requestParams.containsKey(QUERY_PARAMS_FORMAT) - ? requestParams.get(QUERY_PARAMS_FORMAT).toLowerCase() - : DEFAULT_RESPONSE_FORMAT; - Optional optionalFormat = Format.of(formatName); - if (optionalFormat.isPresent()) { - return optionalFormat.get(); - } else { - throw new IllegalArgumentException("Failed to create executor due to unknown response format: " - + formatName); - } + public static final String QUERY_PARAMS_FORMAT = "format"; + public static final String QUERY_PARAMS_PRETTY = "pretty"; + public static final String QUERY_PARAMS_ESCAPE = "escape"; + + private static final String DEFAULT_RESPONSE_FORMAT = "jdbc"; + + /** + * Parse the pretty params to decide whether the response should be pretty formatted. + * + * @param requestParams request params. + * @return return true if the response required pretty format, otherwise return false. + */ + public static boolean isPrettyFormat(Map requestParams) { + return requestParams.containsKey(QUERY_PARAMS_PRETTY) + && ("".equals(requestParams.get(QUERY_PARAMS_PRETTY)) + || "true".equals(requestParams.get(QUERY_PARAMS_PRETTY))); + } + + /** + * Parse the request params and return the {@link Format} of the response + * + * @param requestParams request params + * @return The response Format. + */ + public static Format getFormat(Map requestParams) { + String formatName = + requestParams.containsKey(QUERY_PARAMS_FORMAT) + ? requestParams.get(QUERY_PARAMS_FORMAT).toLowerCase() + : DEFAULT_RESPONSE_FORMAT; + Optional optionalFormat = Format.of(formatName); + if (optionalFormat.isPresent()) { + return optionalFormat.get(); + } else { + throw new IllegalArgumentException( + "Failed to create executor due to unknown response format: " + formatName); } + } - public static boolean getEscapeOption(Map requestParams) { - if (requestParams.containsKey(QUERY_PARAMS_ESCAPE)) { - return Boolean.parseBoolean(requestParams.get(QUERY_PARAMS_ESCAPE)); - } - return false; + public static boolean getEscapeOption(Map requestParams) { + if (requestParams.containsKey(QUERY_PARAMS_ESCAPE)) { + return Boolean.parseBoolean(requestParams.get(QUERY_PARAMS_ESCAPE)); } + return false; + } } diff --git a/legacy/src/main/java/org/opensearch/sql/legacy/rewriter/alias/Table.java b/legacy/src/main/java/org/opensearch/sql/legacy/rewriter/alias/Table.java index 63c33d4721..015d8d8858 100644 --- a/legacy/src/main/java/org/opensearch/sql/legacy/rewriter/alias/Table.java +++ b/legacy/src/main/java/org/opensearch/sql/legacy/rewriter/alias/Table.java @@ -3,7 +3,6 @@ * SPDX-License-Identifier: Apache-2.0 */ - package org.opensearch.sql.legacy.rewriter.alias; import static com.alibaba.druid.sql.ast.expr.SQLBinaryOperator.Divide; @@ -14,44 +13,42 @@ import com.alibaba.druid.sql.ast.statement.SQLExprTableSource; import com.google.common.base.Strings; -/** - * Util class for table expression parsing - */ +/** Util class for table expression parsing */ class Table { - private final SQLExprTableSource tableExpr; - - Table(SQLExprTableSource tableExpr) { - this.tableExpr = tableExpr; - } + private final SQLExprTableSource tableExpr; - boolean hasAlias() { - return !alias().isEmpty(); - } + Table(SQLExprTableSource tableExpr) { + this.tableExpr = tableExpr; + } - String alias() { - return Strings.nullToEmpty(tableExpr.getAlias()); - } + boolean hasAlias() { + return !alias().isEmpty(); + } - void removeAlias() { - tableExpr.setAlias(null); - } + String alias() { + return Strings.nullToEmpty(tableExpr.getAlias()); + } - /** Extract table name in table expression */ - String name() { - SQLExpr expr = tableExpr.getExpr(); - if (expr instanceof SQLIdentifierExpr) { - return ((SQLIdentifierExpr) expr).getName(); - } else if (isTableWithType(expr)) { - return ((SQLIdentifierExpr) ((SQLBinaryOpExpr) expr).getLeft()).getName(); - } - return expr.toString(); - } + void removeAlias() { + tableExpr.setAlias(null); + } - /** Return true for table name along with type name, for example 'accounts/_doc' */ - private boolean isTableWithType(SQLExpr expr) { - return expr instanceof SQLBinaryOpExpr - && ((SQLBinaryOpExpr) expr).getLeft() instanceof SQLIdentifierExpr - && ((SQLBinaryOpExpr) expr).getOperator() == Divide; + /** Extract table name in table expression */ + String name() { + SQLExpr expr = tableExpr.getExpr(); + if (expr instanceof SQLIdentifierExpr) { + return ((SQLIdentifierExpr) expr).getName(); + } else if (isTableWithType(expr)) { + return ((SQLIdentifierExpr) ((SQLBinaryOpExpr) expr).getLeft()).getName(); } + return expr.toString(); + } + + /** Return true for table name along with type name, for example 'accounts/_doc' */ + private boolean isTableWithType(SQLExpr expr) { + return expr instanceof SQLBinaryOpExpr + && ((SQLBinaryOpExpr) expr).getLeft() instanceof SQLIdentifierExpr + && ((SQLBinaryOpExpr) expr).getOperator() == Divide; + } } diff --git a/legacy/src/main/java/org/opensearch/sql/legacy/rewriter/alias/TableAliasPrefixRemoveRule.java b/legacy/src/main/java/org/opensearch/sql/legacy/rewriter/alias/TableAliasPrefixRemoveRule.java index b8500454cd..80190a5889 100644 --- a/legacy/src/main/java/org/opensearch/sql/legacy/rewriter/alias/TableAliasPrefixRemoveRule.java +++ b/legacy/src/main/java/org/opensearch/sql/legacy/rewriter/alias/TableAliasPrefixRemoveRule.java @@ -3,7 +3,6 @@ * SPDX-License-Identifier: Apache-2.0 */ - package org.opensearch.sql.legacy.rewriter.alias; import com.alibaba.druid.sql.ast.expr.SQLIdentifierExpr; @@ -17,86 +16,87 @@ import org.opensearch.sql.legacy.rewriter.RewriteRule; import org.opensearch.sql.legacy.rewriter.subquery.utils.FindSubQuery; -/** - * Rewrite rule for removing table alias or table name prefix in field name. - */ +/** Rewrite rule for removing table alias or table name prefix in field name. */ public class TableAliasPrefixRemoveRule implements RewriteRule { - /** Table aliases in FROM clause. Store table name for those without alias. */ - private final Set tableAliasesToRemove = new HashSet<>(); + /** Table aliases in FROM clause. Store table name for those without alias. */ + private final Set tableAliasesToRemove = new HashSet<>(); - @Override - public boolean match(SQLQueryExpr root) { - if (hasSubQuery(root)) { - return false; - } - collectTableAliasesThatCanBeRemoved(root); - return !tableAliasesToRemove.isEmpty(); + @Override + public boolean match(SQLQueryExpr root) { + if (hasSubQuery(root)) { + return false; } + collectTableAliasesThatCanBeRemoved(root); + return !tableAliasesToRemove.isEmpty(); + } - @Override - public void rewrite(SQLQueryExpr root) { - removeTableAliasPrefixInColumnName(root); - } + @Override + public void rewrite(SQLQueryExpr root) { + removeTableAliasPrefixInColumnName(root); + } - private boolean hasSubQuery(SQLQueryExpr root) { - FindSubQuery visitor = new FindSubQuery(); - root.accept(visitor); - return visitor.hasSubQuery(); - } + private boolean hasSubQuery(SQLQueryExpr root) { + FindSubQuery visitor = new FindSubQuery(); + root.accept(visitor); + return visitor.hasSubQuery(); + } - private void collectTableAliasesThatCanBeRemoved(SQLQueryExpr root) { - visitNonJoinedTable(root, tableExpr -> { - Table table = new Table(tableExpr); - if (table.hasAlias()) { - tableAliasesToRemove.add(table.alias()); - table.removeAlias(); - } else { - tableAliasesToRemove.add(table.name()); - } + private void collectTableAliasesThatCanBeRemoved(SQLQueryExpr root) { + visitNonJoinedTable( + root, + tableExpr -> { + Table table = new Table(tableExpr); + if (table.hasAlias()) { + tableAliasesToRemove.add(table.alias()); + table.removeAlias(); + } else { + tableAliasesToRemove.add(table.name()); + } }); - } + } - private void removeTableAliasPrefixInColumnName(SQLQueryExpr root) { - visitColumnName(root, idExpr -> { - Identifier field = new Identifier(idExpr); - if (field.hasPrefix() && tableAliasesToRemove.contains(field.prefix())) { - field.removePrefix(); - } + private void removeTableAliasPrefixInColumnName(SQLQueryExpr root) { + visitColumnName( + root, + idExpr -> { + Identifier field = new Identifier(idExpr); + if (field.hasPrefix() && tableAliasesToRemove.contains(field.prefix())) { + field.removePrefix(); + } }); - } + } - private void visitNonJoinedTable(SQLQueryExpr root, - Consumer visit) { - root.accept(new MySqlASTVisitorAdapter() { - @Override - public boolean visit(SQLJoinTableSource x) { - // Avoid visiting table name in any JOIN including comma/inner/left join - // between 2 indices or between index and nested field. - // For the latter case, alias is taken care of in {@link NestedFieldRewriter}. - return false; - } + private void visitNonJoinedTable(SQLQueryExpr root, Consumer visit) { + root.accept( + new MySqlASTVisitorAdapter() { + @Override + public boolean visit(SQLJoinTableSource x) { + // Avoid visiting table name in any JOIN including comma/inner/left join + // between 2 indices or between index and nested field. + // For the latter case, alias is taken care of in {@link NestedFieldRewriter}. + return false; + } - @Override - public void endVisit(SQLExprTableSource tableExpr) { - visit.accept(tableExpr); - } + @Override + public void endVisit(SQLExprTableSource tableExpr) { + visit.accept(tableExpr); + } }); - } + } - private void visitColumnName(SQLQueryExpr expr, - Consumer visit) { - expr.accept(new MySqlASTVisitorAdapter() { - @Override - public boolean visit(SQLExprTableSource x) { - return false; // Avoid rewriting identifier in table name - } + private void visitColumnName(SQLQueryExpr expr, Consumer visit) { + expr.accept( + new MySqlASTVisitorAdapter() { + @Override + public boolean visit(SQLExprTableSource x) { + return false; // Avoid rewriting identifier in table name + } - @Override - public void endVisit(SQLIdentifierExpr idExpr) { - visit.accept(idExpr); - } + @Override + public void endVisit(SQLIdentifierExpr idExpr) { + visit.accept(idExpr); + } }); - } - + } } diff --git a/legacy/src/main/java/org/opensearch/sql/legacy/rewriter/identifier/UnquoteIdentifierRule.java b/legacy/src/main/java/org/opensearch/sql/legacy/rewriter/identifier/UnquoteIdentifierRule.java index 31fc732879..b0258420eb 100644 --- a/legacy/src/main/java/org/opensearch/sql/legacy/rewriter/identifier/UnquoteIdentifierRule.java +++ b/legacy/src/main/java/org/opensearch/sql/legacy/rewriter/identifier/UnquoteIdentifierRule.java @@ -3,7 +3,6 @@ * SPDX-License-Identifier: Apache-2.0 */ - package org.opensearch.sql.legacy.rewriter.identifier; import static org.opensearch.sql.legacy.utils.StringUtils.unquoteFullColumn; @@ -16,53 +15,52 @@ import com.alibaba.druid.sql.dialect.mysql.visitor.MySqlASTVisitorAdapter; import org.opensearch.sql.legacy.rewriter.RewriteRule; -/** - * Quoted Identifiers Rewriter Rule - */ -public class UnquoteIdentifierRule extends MySqlASTVisitorAdapter implements RewriteRule { +/** Quoted Identifiers Rewriter Rule */ +public class UnquoteIdentifierRule extends MySqlASTVisitorAdapter + implements RewriteRule { - /** - * - * This method is to adjust the AST in the cases where the field is quoted, - * and the full name in the SELECT field is in the format of indexAlias.fieldName - * (e.g. SELECT b.`lastname` FROM bank AS b). - * - * In this case, the druid parser constructs a SQLSelectItem for the field "b.`lastname`", with SQLIdentifierExpr of - * "b." and alias of "`lastname`". - * - * This method corrects the SQLSelectItem object to have SQLIdentifier of "b.lastname" and alias of null. - */ - @Override - public boolean visit(SQLSelectItem selectItem) { - if (selectItem.getExpr() instanceof SQLIdentifierExpr) { - String identifier = ((SQLIdentifierExpr) selectItem.getExpr()).getName(); - if (identifier.endsWith(".")) { - String correctedIdentifier = identifier + unquoteSingleField(selectItem.getAlias(), "`"); - selectItem.setExpr(new SQLIdentifierExpr(correctedIdentifier)); - selectItem.setAlias(null); - } - } - selectItem.setAlias(unquoteSingleField(selectItem.getAlias(), "`")); - return true; + /** + * This method is to adjust the AST in the cases where the field is quoted, and the full name in + * the SELECT field is in the format of indexAlias.fieldName (e.g. SELECT b.`lastname` FROM bank + * AS b). + * + *

    In this case, the druid parser constructs a SQLSelectItem for the field "b.`lastname`", with + * SQLIdentifierExpr of "b." and alias of "`lastname`". + * + *

    This method corrects the SQLSelectItem object to have SQLIdentifier of "b.lastname" and + * alias of null. + */ + @Override + public boolean visit(SQLSelectItem selectItem) { + if (selectItem.getExpr() instanceof SQLIdentifierExpr) { + String identifier = ((SQLIdentifierExpr) selectItem.getExpr()).getName(); + if (identifier.endsWith(".")) { + String correctedIdentifier = identifier + unquoteSingleField(selectItem.getAlias(), "`"); + selectItem.setExpr(new SQLIdentifierExpr(correctedIdentifier)); + selectItem.setAlias(null); + } } + selectItem.setAlias(unquoteSingleField(selectItem.getAlias(), "`")); + return true; + } - @Override - public void endVisit(SQLIdentifierExpr identifierExpr) { - identifierExpr.setName(unquoteFullColumn(identifierExpr.getName())); - } + @Override + public void endVisit(SQLIdentifierExpr identifierExpr) { + identifierExpr.setName(unquoteFullColumn(identifierExpr.getName())); + } - @Override - public void endVisit(SQLExprTableSource tableSource) { - tableSource.setAlias(unquoteSingleField(tableSource.getAlias())); - } + @Override + public void endVisit(SQLExprTableSource tableSource) { + tableSource.setAlias(unquoteSingleField(tableSource.getAlias())); + } - @Override - public boolean match(SQLQueryExpr root) { - return true; - } + @Override + public boolean match(SQLQueryExpr root) { + return true; + } - @Override - public void rewrite(SQLQueryExpr root) { - root.accept(new UnquoteIdentifierRule()); - } + @Override + public void rewrite(SQLQueryExpr root) { + root.accept(new UnquoteIdentifierRule()); + } } diff --git a/legacy/src/main/java/org/opensearch/sql/legacy/rewriter/join/JoinRewriteRule.java b/legacy/src/main/java/org/opensearch/sql/legacy/rewriter/join/JoinRewriteRule.java index 69178b7e83..884784ed42 100644 --- a/legacy/src/main/java/org/opensearch/sql/legacy/rewriter/join/JoinRewriteRule.java +++ b/legacy/src/main/java/org/opensearch/sql/legacy/rewriter/join/JoinRewriteRule.java @@ -27,6 +27,8 @@ import org.opensearch.sql.legacy.utils.StringUtils; /** + * + * *

      *  Rewrite rule to add table alias to columnNames for JOIN queries without table alias.
      * 

    diff --git a/legacy/src/main/java/org/opensearch/sql/legacy/rewriter/matchtoterm/TermFieldRewriter.java b/legacy/src/main/java/org/opensearch/sql/legacy/rewriter/matchtoterm/TermFieldRewriter.java index 5890befbca..2c837a7b2b 100644 --- a/legacy/src/main/java/org/opensearch/sql/legacy/rewriter/matchtoterm/TermFieldRewriter.java +++ b/legacy/src/main/java/org/opensearch/sql/legacy/rewriter/matchtoterm/TermFieldRewriter.java @@ -3,7 +3,6 @@ * SPDX-License-Identifier: Apache-2.0 */ - package org.opensearch.sql.legacy.rewriter.matchtoterm; import com.alibaba.druid.sql.ast.SQLExpr; @@ -35,233 +34,233 @@ /** * Visitor to rewrite AST (abstract syntax tree) for supporting term_query in WHERE and IN condition - * Simple changing the matchQuery() to termQuery() will not work when mapping is both text and keyword - * The approach is to implement SQLIdentifier.visit() based on the correct field mapping. + * Simple changing the matchQuery() to termQuery() will not work when mapping is both text and + * keyword The approach is to implement SQLIdentifier.visit() based on the correct field mapping. */ - public class TermFieldRewriter extends MySqlASTVisitorAdapter { - private Deque environment = new ArrayDeque<>(); - private TermRewriterFilter filterType; + private Deque environment = new ArrayDeque<>(); + private TermRewriterFilter filterType; - public TermFieldRewriter() { - this.filterType = TermRewriterFilter.COMMA; - } - - public TermFieldRewriter(TermRewriterFilter filterType) { - this.filterType = filterType; - } - - @Override - public boolean visit(MySqlSelectQueryBlock query) { - environment.push(new TermFieldScope()); - if (query.getFrom() == null) { - return false; - } - - Map indexToType = new HashMap<>(); - collect(query.getFrom(), indexToType, curScope().getAliases()); - if (indexToType.isEmpty()) { - // no index found for current scope, continue. - return true; - } - curScope().setMapper(getMappings(indexToType)); + public TermFieldRewriter() { + this.filterType = TermRewriterFilter.COMMA; + } - if (this.filterType == TermRewriterFilter.COMMA || this.filterType == TermRewriterFilter.MULTI_QUERY) { - checkMappingCompatibility(curScope(), indexToType); - } + public TermFieldRewriter(TermRewriterFilter filterType) { + this.filterType = filterType; + } - return true; + @Override + public boolean visit(MySqlSelectQueryBlock query) { + environment.push(new TermFieldScope()); + if (query.getFrom() == null) { + return false; } - @Override - public void endVisit(MySqlSelectQueryBlock query) { - environment.pop(); + Map indexToType = new HashMap<>(); + collect(query.getFrom(), indexToType, curScope().getAliases()); + if (indexToType.isEmpty()) { + // no index found for current scope, continue. + return true; } + curScope().setMapper(getMappings(indexToType)); - @Override - public boolean visit(SQLSelectItem sqlSelectItem) { - return false; + if (this.filterType == TermRewriterFilter.COMMA + || this.filterType == TermRewriterFilter.MULTI_QUERY) { + checkMappingCompatibility(curScope(), indexToType); } - @Override - public boolean visit(SQLJoinTableSource tableSource) { - return false; - } + return true; + } + + @Override + public void endVisit(MySqlSelectQueryBlock query) { + environment.pop(); + } + + @Override + public boolean visit(SQLSelectItem sqlSelectItem) { + return false; + } + + @Override + public boolean visit(SQLJoinTableSource tableSource) { + return false; + } + + @Override + public boolean visit(SQLExprTableSource tableSource) { + return false; + } + + /** Fix null parent problem which is required when visiting SQLIdentifier */ + public boolean visit(SQLInListExpr inListExpr) { + inListExpr.getExpr().setParent(inListExpr); + return true; + } + + @SuppressWarnings("unchecked") + @Override + public boolean visit(SQLIdentifierExpr expr) { + if (isValidIdentifierForTerm(expr)) { + Map source = null; + if (this.filterType == TermRewriterFilter.COMMA + || this.filterType == TermRewriterFilter.MULTI_QUERY) { + Optional> optionalMap = curScope().resolveFieldMapping(expr.getName()); + if (optionalMap.isPresent()) { + source = optionalMap.get(); + } else { + return true; + } - @Override - public boolean visit(SQLExprTableSource tableSource) { - return false; - } + } else if (this.filterType == TermRewriterFilter.JOIN) { + String[] arr = expr.getName().split("\\.", 2); + if (arr.length < 2) { + throw new VerificationException("table alias or field name missing"); + } + String alias = arr[0]; + String fullFieldName = arr[1]; + + String index = curScope().getAliases().get(alias); + FieldMappings fieldMappings = curScope().getMapper().mapping(index); + if (fieldMappings.has(fullFieldName)) { + source = fieldMappings.mapping(fullFieldName); + } else { + return true; + } + } - /** - * Fix null parent problem which is required when visiting SQLIdentifier - */ - public boolean visit(SQLInListExpr inListExpr) { - inListExpr.getExpr().setParent(inListExpr); - return true; + String keywordAlias = isBothTextAndKeyword(source); + if (keywordAlias != null) { + expr.setName(expr.getName() + "." + keywordAlias); + } } - - - @SuppressWarnings("unchecked") - @Override - public boolean visit(SQLIdentifierExpr expr) { - if (isValidIdentifierForTerm(expr)) { - Map source = null; - if (this.filterType == TermRewriterFilter.COMMA || this.filterType == TermRewriterFilter.MULTI_QUERY) { - Optional> optionalMap = curScope().resolveFieldMapping(expr.getName()); - if (optionalMap.isPresent()) { - source = optionalMap.get(); - } else { - return true; - } - - } else if (this.filterType == TermRewriterFilter.JOIN) { - String[] arr = expr.getName().split("\\.", 2); - if (arr.length < 2) { - throw new VerificationException("table alias or field name missing"); - } - String alias = arr[0]; - String fullFieldName = arr[1]; - - String index = curScope().getAliases().get(alias); - FieldMappings fieldMappings = curScope().getMapper().mapping(index); - if (fieldMappings.has(fullFieldName)) { - source = fieldMappings.mapping(fullFieldName); - } else { - return true; - } - } - - String keywordAlias = isBothTextAndKeyword(source); - if (keywordAlias != null) { - expr.setName(expr.getName() + "." + keywordAlias); - } + return true; + } + + public void collect( + SQLTableSource tableSource, Map indexToType, Map aliases) { + if (tableSource instanceof SQLExprTableSource) { + + String tableName = null; + SQLExprTableSource sqlExprTableSource = (SQLExprTableSource) tableSource; + + if (sqlExprTableSource.getExpr() instanceof SQLIdentifierExpr) { + SQLIdentifierExpr sqlIdentifier = (SQLIdentifierExpr) sqlExprTableSource.getExpr(); + tableName = sqlIdentifier.getName(); + indexToType.put(tableName, null); + } else if (sqlExprTableSource.getExpr() instanceof SQLBinaryOpExpr) { + SQLBinaryOpExpr sqlBinaryOpExpr = (SQLBinaryOpExpr) sqlExprTableSource.getExpr(); + tableName = ((SQLIdentifierExpr) sqlBinaryOpExpr.getLeft()).getName(); + SQLExpr rightSideOfExpression = sqlBinaryOpExpr.getRight(); + + // This assumes that right side of the expression is different name in query + if (rightSideOfExpression instanceof SQLIdentifierExpr) { + SQLIdentifierExpr right = (SQLIdentifierExpr) rightSideOfExpression; + indexToType.put(tableName, right.getName()); + } else { + throw new ParserException( + "Right side of the expression [" + + rightSideOfExpression.toString() + + "] is expected to be an identifier"); } - return true; + } + if (tableSource.getAlias() != null) { + aliases.put(tableSource.getAlias(), tableName); + } else { + aliases.put(tableName, tableName); + } + + } else if (tableSource instanceof SQLJoinTableSource) { + collect(((SQLJoinTableSource) tableSource).getLeft(), indexToType, aliases); + collect(((SQLJoinTableSource) tableSource).getRight(), indexToType, aliases); } - - public void collect(SQLTableSource tableSource, Map indexToType, Map aliases) { - if (tableSource instanceof SQLExprTableSource) { - - String tableName = null; - SQLExprTableSource sqlExprTableSource = (SQLExprTableSource) tableSource; - - if (sqlExprTableSource.getExpr() instanceof SQLIdentifierExpr) { - SQLIdentifierExpr sqlIdentifier = (SQLIdentifierExpr) sqlExprTableSource.getExpr(); - tableName = sqlIdentifier.getName(); - indexToType.put(tableName, null); - } else if (sqlExprTableSource.getExpr() instanceof SQLBinaryOpExpr) { - SQLBinaryOpExpr sqlBinaryOpExpr = (SQLBinaryOpExpr) sqlExprTableSource.getExpr(); - tableName = ((SQLIdentifierExpr) sqlBinaryOpExpr.getLeft()).getName(); - SQLExpr rightSideOfExpression = sqlBinaryOpExpr.getRight(); - - // This assumes that right side of the expression is different name in query - if (rightSideOfExpression instanceof SQLIdentifierExpr) { - SQLIdentifierExpr right = (SQLIdentifierExpr) rightSideOfExpression; - indexToType.put(tableName, right.getName()); - } else { - throw new ParserException("Right side of the expression [" + rightSideOfExpression.toString() - + "] is expected to be an identifier"); - } - } - if (tableSource.getAlias() != null) { - aliases.put(tableSource.getAlias(), tableName); - } else { - aliases.put(tableName, tableName); - } - - } else if (tableSource instanceof SQLJoinTableSource) { - collect(((SQLJoinTableSource) tableSource).getLeft(), indexToType, aliases); - collect(((SQLJoinTableSource) tableSource).getRight(), indexToType, aliases); + } + + /** Current scope which is top of the stack */ + private TermFieldScope curScope() { + return environment.peek(); + } + + public String isBothTextAndKeyword(Map source) { + if (source.containsKey("fields")) { + for (Object key : ((Map) source.get("fields")).keySet()) { + if (key instanceof String + && ((Map) ((Map) source.get("fields")).get(key)).get("type").equals("keyword")) { + return (String) key; } + } } + return null; + } + public boolean isValidIdentifierForTerm(SQLIdentifierExpr expr) { /** - * Current scope which is top of the stack + * + * + *

    +     * Only for following conditions Identifier will be modified
    +     *  Where:  WHERE identifier = 'something'
    +     *  IN list: IN ('Tom', 'Dick', 'Harry')
    +     *  IN subquery: IN (SELECT firstname from accounts/account where firstname = 'John')
    +     *  Group by: GROUP BY state , employer , ...
    +     *  Order by: ORDER BY firstname, lastname , ...
    +     *
    +     * NOTE: Does not impact fields on ON condition clause in JOIN as we skip visiting SQLJoinTableSource
    +     * 
    */ - private TermFieldScope curScope() { - return environment.peek(); + return !expr.getName().startsWith("_") + && (isValidIdentifier(expr) || checkIfNestedIdentifier(expr)); + } + + private boolean checkIfNestedIdentifier(SQLIdentifierExpr expr) { + return expr.getParent() instanceof SQLMethodInvokeExpr + && ((SQLMethodInvokeExpr) expr.getParent()).getMethodName().equals("nested") + && isValidIdentifier(expr.getParent()); + } + + private boolean isValidIdentifier(SQLObject expr) { + SQLObject parent = expr.getParent(); + return isBinaryExprWithValidOperators(parent) + || parent instanceof SQLInListExpr + || parent instanceof SQLInSubQueryExpr + || parent instanceof SQLSelectOrderByItem + || parent instanceof MySqlSelectGroupByExpr; + } + + private boolean isBinaryExprWithValidOperators(SQLObject expr) { + if (!(expr instanceof SQLBinaryOpExpr)) { + return false; } + return Stream.of(SQLBinaryOperator.Equality, SQLBinaryOperator.Is, SQLBinaryOperator.IsNot) + .anyMatch(operator -> operator == ((SQLBinaryOpExpr) expr).getOperator()); + } - public String isBothTextAndKeyword(Map source) { - if (source.containsKey("fields")) { - for (Object key : ((Map) source.get("fields")).keySet()) { - if (key instanceof String - && ((Map) ((Map) source.get("fields")).get(key)).get("type").equals("keyword")) { - return (String) key; - } - } - } - return null; + private void checkMappingCompatibility(TermFieldScope scope, Map indexToType) { + if (scope.getMapper().isEmpty()) { + throw new VerificationException("Unknown index " + indexToType.keySet()); } + } - public boolean isValidIdentifierForTerm(SQLIdentifierExpr expr) { - /** - * Only for following conditions Identifier will be modified - * Where: WHERE identifier = 'something' - * IN list: IN ('Tom', 'Dick', 'Harry') - * IN subquery: IN (SELECT firstname from accounts/account where firstname = 'John') - * Group by: GROUP BY state , employer , ... - * Order by: ORDER BY firstname, lastname , ... - * - * NOTE: Does not impact fields on ON condition clause in JOIN as we skip visiting SQLJoinTableSource - */ - return !expr.getName().startsWith("_") && (isValidIdentifier(expr) || checkIfNestedIdentifier(expr)); - } + public IndexMappings getMappings(Map indexToType) { + String[] allIndexes = indexToType.keySet().stream().toArray(String[]::new); + // GetFieldMappingsRequest takes care of wildcard index expansion + return LocalClusterState.state().getFieldMappings(allIndexes); + } - private boolean checkIfNestedIdentifier(SQLIdentifierExpr expr) { - return - expr.getParent() instanceof SQLMethodInvokeExpr - && ((SQLMethodInvokeExpr) expr.getParent()).getMethodName().equals("nested") - && isValidIdentifier(expr.getParent()); - } + public enum TermRewriterFilter { + COMMA(","), // No joins, multiple tables in SELECT + JOIN("JOIN"), // All JOINs + MULTI_QUERY("MULTI_QUERY"); // MINUS and UNION - private boolean isValidIdentifier(SQLObject expr) { - SQLObject parent = expr.getParent(); - return isBinaryExprWithValidOperators(parent) - || parent instanceof SQLInListExpr - || parent instanceof SQLInSubQueryExpr - || parent instanceof SQLSelectOrderByItem - || parent instanceof MySqlSelectGroupByExpr; - } + public final String name; - private boolean isBinaryExprWithValidOperators(SQLObject expr) { - if (!(expr instanceof SQLBinaryOpExpr)) { - return false; - } - return Stream.of( - SQLBinaryOperator.Equality, - SQLBinaryOperator.Is, - SQLBinaryOperator.IsNot - ).anyMatch(operator -> operator == ((SQLBinaryOpExpr) expr).getOperator()); + TermRewriterFilter(String name) { + this.name = name; } - private void checkMappingCompatibility(TermFieldScope scope, Map indexToType) { - if (scope.getMapper().isEmpty()) { - throw new VerificationException("Unknown index " + indexToType.keySet()); - } - } - - public IndexMappings getMappings(Map indexToType) { - String[] allIndexes = indexToType.keySet().stream().toArray(String[]::new); - // GetFieldMappingsRequest takes care of wildcard index expansion - return LocalClusterState.state().getFieldMappings(allIndexes); - } - - public enum TermRewriterFilter { - COMMA(","), // No joins, multiple tables in SELECT - JOIN("JOIN"), // All JOINs - MULTI_QUERY("MULTI_QUERY"); // MINUS and UNION - - public final String name; - - TermRewriterFilter(String name) { - this.name = name; - } - - public static String toString(TermRewriterFilter filter) { - return filter.name; - } + public static String toString(TermRewriterFilter filter) { + return filter.name; } + } } diff --git a/legacy/src/main/java/org/opensearch/sql/legacy/rewriter/matchtoterm/TermFieldScope.java b/legacy/src/main/java/org/opensearch/sql/legacy/rewriter/matchtoterm/TermFieldScope.java index f8b6e9b05e..29f8ed82b8 100644 --- a/legacy/src/main/java/org/opensearch/sql/legacy/rewriter/matchtoterm/TermFieldScope.java +++ b/legacy/src/main/java/org/opensearch/sql/legacy/rewriter/matchtoterm/TermFieldScope.java @@ -3,7 +3,6 @@ * SPDX-License-Identifier: Apache-2.0 */ - package org.opensearch.sql.legacy.rewriter.matchtoterm; import java.util.*; @@ -11,57 +10,62 @@ import org.opensearch.sql.legacy.esdomain.mapping.FieldMappings; import org.opensearch.sql.legacy.esdomain.mapping.IndexMappings; -/** - * Index Mapping information in current query being visited. - */ +/** Index Mapping information in current query being visited. */ public class TermFieldScope { - // mapper => index, type, field_name, FieldMappingMetaData - private IndexMappings mapper; - private FieldMappings finalMapping; - private Map aliases; + // mapper => index, type, field_name, FieldMappingMetaData + private IndexMappings mapper; + private FieldMappings finalMapping; + private Map aliases; - public TermFieldScope() { - this.mapper = IndexMappings.EMPTY; - this.aliases = new HashMap<>(); - } + public TermFieldScope() { + this.mapper = IndexMappings.EMPTY; + this.aliases = new HashMap<>(); + } - public Map getAliases() { - return aliases; - } + public Map getAliases() { + return aliases; + } - public void setAliases(Map aliases) { - this.aliases = aliases; - } + public void setAliases(Map aliases) { + this.aliases = aliases; + } - public IndexMappings getMapper() { - return this.mapper; - } + public IndexMappings getMapper() { + return this.mapper; + } - public void setMapper(IndexMappings mapper) { - this.mapper = mapper; - } + public void setMapper(IndexMappings mapper) { + this.mapper = mapper; + } - public Optional> resolveFieldMapping(String fieldName) { - Set indexMappings = new HashSet<>(mapper.allMappings()); - Optional> resolvedMapping = - indexMappings.stream() - .filter(mapping -> mapping.has(fieldName)) - .map(mapping -> mapping.mapping(fieldName)).reduce((map1, map2) -> { - if (!map1.equals(map2)) { - // TODO: Merge mappings if they are compatible, for text and text/keyword to text/keyword. - String exceptionReason = String.format(Locale.ROOT, "Different mappings are not allowed " - + "for the same field[%s]: found [%s] and [%s] ", - fieldName, pretty(map1), pretty(map2)); - throw new VerificationException(exceptionReason); - } - return map1; + public Optional> resolveFieldMapping(String fieldName) { + Set indexMappings = new HashSet<>(mapper.allMappings()); + Optional> resolvedMapping = + indexMappings.stream() + .filter(mapping -> mapping.has(fieldName)) + .map(mapping -> mapping.mapping(fieldName)) + .reduce( + (map1, map2) -> { + if (!map1.equals(map2)) { + // TODO: Merge mappings if they are compatible, for text and text/keyword to + // text/keyword. + String exceptionReason = + String.format( + Locale.ROOT, + "Different mappings are not allowed " + + "for the same field[%s]: found [%s] and [%s] ", + fieldName, + pretty(map1), + pretty(map2)); + throw new VerificationException(exceptionReason); + } + return map1; }); - return resolvedMapping; - } - - private static String pretty(Map mapping) { - return new JSONObject(mapping).toString().replaceAll("\"", ""); - } + return resolvedMapping; + } + private static String pretty(Map mapping) { + return new JSONObject(mapping).toString().replaceAll("\"", ""); + } } diff --git a/legacy/src/main/java/org/opensearch/sql/legacy/rewriter/matchtoterm/VerificationException.java b/legacy/src/main/java/org/opensearch/sql/legacy/rewriter/matchtoterm/VerificationException.java index 51b936bdc3..f8ec8ad215 100644 --- a/legacy/src/main/java/org/opensearch/sql/legacy/rewriter/matchtoterm/VerificationException.java +++ b/legacy/src/main/java/org/opensearch/sql/legacy/rewriter/matchtoterm/VerificationException.java @@ -3,7 +3,6 @@ * SPDX-License-Identifier: Apache-2.0 */ - package org.opensearch.sql.legacy.rewriter.matchtoterm; import org.opensearch.OpenSearchException; @@ -11,12 +10,12 @@ public class VerificationException extends OpenSearchException { - public VerificationException(String message) { - super(message); - } + public VerificationException(String message) { + super(message); + } - @Override - public RestStatus status() { - return RestStatus.BAD_REQUEST; - } + @Override + public RestStatus status() { + return RestStatus.BAD_REQUEST; + } } diff --git a/legacy/src/main/java/org/opensearch/sql/legacy/rewriter/nestedfield/From.java b/legacy/src/main/java/org/opensearch/sql/legacy/rewriter/nestedfield/From.java index b39907366e..2c7b074e0a 100644 --- a/legacy/src/main/java/org/opensearch/sql/legacy/rewriter/nestedfield/From.java +++ b/legacy/src/main/java/org/opensearch/sql/legacy/rewriter/nestedfield/From.java @@ -70,29 +70,31 @@ private void keepParentTableOnly() { left().expr.setParent(query); } - /** - *
    -     * Collect path alias and full path mapping of nested field in FROM clause.
    -     * Sample:
    -     * FROM team t, t.employees e ...
    -     * 

    - * Join - * / \ - * team t Join - * / \ - * t.employees e ... - *

    - * t.employees is nested because path "t" == parentAlias "t" - * Save path alias to full path name mapping {"e": "employees"} to Scope - *

    - */ - private void collectNestedFields(Scope scope) { - From clause = this; - for (; clause.isCommaJoin(); clause = clause.right()) { - clause.left().addIfNestedField(scope); - } - clause.addIfNestedField(scope); + /** + * + * + *
    +   * Collect path alias and full path mapping of nested field in FROM clause.
    +   * Sample:
    +   * FROM team t, t.employees e ...
    +   * 

    + * Join + * / \ + * team t Join + * / \ + * t.employees e ... + *

    + * t.employees is nested because path "t" == parentAlias "t" + * Save path alias to full path name mapping {"e": "employees"} to Scope + *

    + */ + private void collectNestedFields(Scope scope) { + From clause = this; + for (; clause.isCommaJoin(); clause = clause.right()) { + clause.left().addIfNestedField(scope); } + clause.addIfNestedField(scope); + } private boolean isCommaJoin() { return expr instanceof SQLJoinTableSource && ((SQLJoinTableSource) expr).getJoinType() == COMMA; diff --git a/legacy/src/main/java/org/opensearch/sql/legacy/rewriter/nestedfield/Identifier.java b/legacy/src/main/java/org/opensearch/sql/legacy/rewriter/nestedfield/Identifier.java index e3e1cfb7ce..6c5867b864 100644 --- a/legacy/src/main/java/org/opensearch/sql/legacy/rewriter/nestedfield/Identifier.java +++ b/legacy/src/main/java/org/opensearch/sql/legacy/rewriter/nestedfield/Identifier.java @@ -9,6 +9,8 @@ import com.alibaba.druid.sql.ast.expr.SQLIdentifierExpr; /** + * + * *
      * Identifier expression in SELECT, FROM, WHERE, GROUP BY, ORDER BY etc.
      *
    diff --git a/legacy/src/main/java/org/opensearch/sql/legacy/rewriter/nestedfield/NestedFieldProjection.java b/legacy/src/main/java/org/opensearch/sql/legacy/rewriter/nestedfield/NestedFieldProjection.java
    index 83a94b1e9b..590ed8fb4d 100644
    --- a/legacy/src/main/java/org/opensearch/sql/legacy/rewriter/nestedfield/NestedFieldProjection.java
    +++ b/legacy/src/main/java/org/opensearch/sql/legacy/rewriter/nestedfield/NestedFieldProjection.java
    @@ -109,29 +109,30 @@ private Map> groupFieldNamesByPath(List fields) {
             .collect(groupingBy(Field::getNestedPath, mapping(Field::getName, toList())));
       }
     
    -    /**
    -     * Why search for NestedQueryBuilder recursively?
    -     * Because
    -     * 
      - *
    1. it was added and wrapped by BoolQuery when WHERE explained (far from here) - *
    2. InnerHit must be added to the NestedQueryBuilder related - *
    - *

    - * Either we store it to global data structure (which requires to be thread-safe or ThreadLocal) - * or we peel off BoolQuery to find it (the way we followed here because recursion tree should be very thin). - */ - private List extractNestedQueries(QueryBuilder query) { - List result = new ArrayList<>(); - if (query instanceof NestedQueryBuilder) { - result.add((NestedQueryBuilder) query); - } else if (query instanceof BoolQueryBuilder) { - BoolQueryBuilder boolQ = (BoolQueryBuilder) query; - Stream.of(boolQ.filter(), boolQ.must(), boolQ.should()). - flatMap(Collection::stream). - forEach(q -> result.addAll(extractNestedQueries(q))); - } - return result; + /** + * Why search for NestedQueryBuilder recursively? Because + * + *

      + *
    1. it was added and wrapped by BoolQuery when WHERE explained (far from here) + *
    2. InnerHit must be added to the NestedQueryBuilder related + *
    + * + *

    Either we store it to global data structure (which requires to be thread-safe or + * ThreadLocal) or we peel off BoolQuery to find it (the way we followed here because recursion + * tree should be very thin). + */ + private List extractNestedQueries(QueryBuilder query) { + List result = new ArrayList<>(); + if (query instanceof NestedQueryBuilder) { + result.add((NestedQueryBuilder) query); + } else if (query instanceof BoolQueryBuilder) { + BoolQueryBuilder boolQ = (BoolQueryBuilder) query; + Stream.of(boolQ.filter(), boolQ.must(), boolQ.should()) + .flatMap(Collection::stream) + .forEach(q -> result.addAll(extractNestedQueries(q))); } + return result; + } private void buildInnerHit(List fieldNames, NestedQueryBuilder query) { query.innerHit( diff --git a/legacy/src/main/java/org/opensearch/sql/legacy/rewriter/nestedfield/NestedFieldRewriter.java b/legacy/src/main/java/org/opensearch/sql/legacy/rewriter/nestedfield/NestedFieldRewriter.java index 976075a72d..46afbb8ca1 100644 --- a/legacy/src/main/java/org/opensearch/sql/legacy/rewriter/nestedfield/NestedFieldRewriter.java +++ b/legacy/src/main/java/org/opensearch/sql/legacy/rewriter/nestedfield/NestedFieldRewriter.java @@ -15,6 +15,8 @@ import java.util.Deque; /** + * + * *

      * Visitor to rewrite AST (abstract syntax tree) for nested type fields to support implicit nested() function call.
      * Intuitively, the approach is to implement SQLIdentifier.visit() and wrap nested() function for nested field.
    diff --git a/legacy/src/main/java/org/opensearch/sql/legacy/rewriter/nestedfield/SQLClause.java b/legacy/src/main/java/org/opensearch/sql/legacy/rewriter/nestedfield/SQLClause.java
    index 160403ab11..fb4c1b9fe9 100644
    --- a/legacy/src/main/java/org/opensearch/sql/legacy/rewriter/nestedfield/SQLClause.java
    +++ b/legacy/src/main/java/org/opensearch/sql/legacy/rewriter/nestedfield/SQLClause.java
    @@ -3,7 +3,6 @@
      * SPDX-License-Identifier: Apache-2.0
      */
     
    -
     package org.opensearch.sql.legacy.rewriter.nestedfield;
     
     import com.alibaba.druid.sql.ast.SQLExpr;
    @@ -28,71 +27,69 @@
      */
     abstract class SQLClause {
     
    -    protected final T expr;
    -
    -    SQLClause(T expr) {
    -        this.expr = expr;
    -    }
    +  protected final T expr;
     
    -    /**
    -     * Rewrite nested fields in query according to/fill into information in scope.
    -     *
    -     * @param scope Scope of current query
    -     */
    -    abstract void rewrite(Scope scope);
    +  SQLClause(T expr) {
    +    this.expr = expr;
    +  }
     
    -    SQLMethodInvokeExpr replaceByNestedFunction(SQLExpr expr, String nestedPath) {
    -        final int nestedPathIndex = 1;
    -        SQLMethodInvokeExpr nestedFunc = replaceByNestedFunction(expr);
    -        nestedFunc.getParameters().add(nestedPathIndex, new SQLCharExpr(nestedPath));
    -        return nestedFunc;
    -    }
    +  /**
    +   * Rewrite nested fields in query according to/fill into information in scope.
    +   *
    +   * @param scope Scope of current query
    +   */
    +  abstract void rewrite(Scope scope);
     
    -    /**
    -     * Replace expr by nested(expr) and set pointer in parent properly
    -     */
    -    SQLMethodInvokeExpr replaceByNestedFunction(SQLExpr expr) {
    -        SQLObject parent = expr.getParent();
    -        SQLMethodInvokeExpr nestedFunc = wrapNestedFunction(expr);
    -        if (parent instanceof SQLAggregateExpr) {
    -            List args = ((SQLAggregateExpr) parent).getArguments();
    -            args.set(args.indexOf(expr), nestedFunc);
    -        } else if (parent instanceof SQLSelectItem) {
    -            ((SQLSelectItem) parent).setExpr(nestedFunc);
    -        } else if (parent instanceof MySqlSelectGroupByExpr) {
    -            ((MySqlSelectGroupByExpr) parent).setExpr(nestedFunc);
    -        } else if (parent instanceof SQLSelectOrderByItem) {
    -            ((SQLSelectOrderByItem) parent).setExpr(nestedFunc);
    -        } else if (parent instanceof SQLInSubQueryExpr) {
    -            ((SQLInSubQueryExpr) parent).setExpr(nestedFunc);
    -        } else if (parent instanceof SQLBinaryOpExpr) {
    -            SQLBinaryOpExpr parentOp = (SQLBinaryOpExpr) parent;
    -            if (parentOp.getLeft() == expr) {
    -                parentOp.setLeft(nestedFunc);
    -            } else {
    -                parentOp.setRight(nestedFunc);
    -            }
    -        } else if (parent instanceof MySqlSelectQueryBlock) {
    -            ((MySqlSelectQueryBlock) parent).setWhere(nestedFunc);
    -        } else if (parent instanceof SQLNotExpr) {
    -              ((SQLNotExpr) parent).setExpr(nestedFunc);
    -        } else {
    -            throw new IllegalStateException("Unsupported place to use nested field under parent: " + parent);
    -        }
    -        return nestedFunc;
    -    }
    +  SQLMethodInvokeExpr replaceByNestedFunction(SQLExpr expr, String nestedPath) {
    +    final int nestedPathIndex = 1;
    +    SQLMethodInvokeExpr nestedFunc = replaceByNestedFunction(expr);
    +    nestedFunc.getParameters().add(nestedPathIndex, new SQLCharExpr(nestedPath));
    +    return nestedFunc;
    +  }
     
    -    private SQLMethodInvokeExpr wrapNestedFunction(SQLExpr expr) {
    -        SQLMethodInvokeExpr nestedFunc = new SQLMethodInvokeExpr("nested");
    -        nestedFunc.setParent(expr.getParent());
    -        nestedFunc.addParameter(expr);  // this will auto set parent of expr
    -        return nestedFunc;
    +  /** Replace expr by nested(expr) and set pointer in parent properly */
    +  SQLMethodInvokeExpr replaceByNestedFunction(SQLExpr expr) {
    +    SQLObject parent = expr.getParent();
    +    SQLMethodInvokeExpr nestedFunc = wrapNestedFunction(expr);
    +    if (parent instanceof SQLAggregateExpr) {
    +      List args = ((SQLAggregateExpr) parent).getArguments();
    +      args.set(args.indexOf(expr), nestedFunc);
    +    } else if (parent instanceof SQLSelectItem) {
    +      ((SQLSelectItem) parent).setExpr(nestedFunc);
    +    } else if (parent instanceof MySqlSelectGroupByExpr) {
    +      ((MySqlSelectGroupByExpr) parent).setExpr(nestedFunc);
    +    } else if (parent instanceof SQLSelectOrderByItem) {
    +      ((SQLSelectOrderByItem) parent).setExpr(nestedFunc);
    +    } else if (parent instanceof SQLInSubQueryExpr) {
    +      ((SQLInSubQueryExpr) parent).setExpr(nestedFunc);
    +    } else if (parent instanceof SQLBinaryOpExpr) {
    +      SQLBinaryOpExpr parentOp = (SQLBinaryOpExpr) parent;
    +      if (parentOp.getLeft() == expr) {
    +        parentOp.setLeft(nestedFunc);
    +      } else {
    +        parentOp.setRight(nestedFunc);
    +      }
    +    } else if (parent instanceof MySqlSelectQueryBlock) {
    +      ((MySqlSelectQueryBlock) parent).setWhere(nestedFunc);
    +    } else if (parent instanceof SQLNotExpr) {
    +      ((SQLNotExpr) parent).setExpr(nestedFunc);
    +    } else {
    +      throw new IllegalStateException(
    +          "Unsupported place to use nested field under parent: " + parent);
         }
    +    return nestedFunc;
    +  }
     
    -    String pathFromIdentifier(SQLExpr identifier) {
    -        String field = Util.extendedToString(identifier);
    -        int lastDot = field.lastIndexOf(".");
    -        return lastDot == -1 ? field :field.substring(0, lastDot);
    -    }
    +  private SQLMethodInvokeExpr wrapNestedFunction(SQLExpr expr) {
    +    SQLMethodInvokeExpr nestedFunc = new SQLMethodInvokeExpr("nested");
    +    nestedFunc.setParent(expr.getParent());
    +    nestedFunc.addParameter(expr); // this will auto set parent of expr
    +    return nestedFunc;
    +  }
     
    +  String pathFromIdentifier(SQLExpr identifier) {
    +    String field = Util.extendedToString(identifier);
    +    int lastDot = field.lastIndexOf(".");
    +    return lastDot == -1 ? field : field.substring(0, lastDot);
    +  }
     }
    diff --git a/legacy/src/main/java/org/opensearch/sql/legacy/rewriter/nestedfield/Where.java b/legacy/src/main/java/org/opensearch/sql/legacy/rewriter/nestedfield/Where.java
    index c126bb264f..36dc3263b4 100644
    --- a/legacy/src/main/java/org/opensearch/sql/legacy/rewriter/nestedfield/Where.java
    +++ b/legacy/src/main/java/org/opensearch/sql/legacy/rewriter/nestedfield/Where.java
    @@ -3,7 +3,6 @@
      * SPDX-License-Identifier: Apache-2.0
      */
     
    -
     package org.opensearch.sql.legacy.rewriter.nestedfield;
     
     import com.alibaba.druid.sql.ast.expr.SQLBinaryOpExpr;
    @@ -11,116 +10,114 @@
     import com.alibaba.druid.sql.ast.expr.SQLNotExpr;
     import com.alibaba.druid.sql.dialect.mysql.ast.statement.MySqlSelectQueryBlock;
     
    -/**
    - * Condition expression in WHERE statement.
    - */
    +/** Condition expression in WHERE statement. */
     class Where extends SQLClause {
     
    -    Where(SQLBinaryOpExpr expr) {
    -        super(expr);
    -    }
    +  Where(SQLBinaryOpExpr expr) {
    +    super(expr);
    +  }
     
    -    /**
    -     * Rewrite if left and right tag is different (or reach root of WHERE).
    -     * Otherwise continue delaying the rewrite.
    -     * 

    - * Assumption: there are only 2 forms of condition - * 1) BinaryOp: Left=Identifier, right=value - * 2) BinaryOp: Left=BinaryOp, right=BinaryOp - */ - @Override - void rewrite(Scope scope) { - if (isLeftChildCondition()) { - if (isChildTagEquals(scope)) { - useAnyChildTag(scope); - } else { - left().mergeNestedField(scope); - right().mergeNestedField(scope); - } - } - mergeIfHaveTagAndIsRootOfWhereOrNot(scope); + /** + * Rewrite if left and right tag is different (or reach root of WHERE). Otherwise continue + * delaying the rewrite. + * + *

    Assumption: there are only 2 forms of condition 1) BinaryOp: Left=Identifier, right=value 2) + * BinaryOp: Left=BinaryOp, right=BinaryOp + */ + @Override + void rewrite(Scope scope) { + if (isLeftChildCondition()) { + if (isChildTagEquals(scope)) { + useAnyChildTag(scope); + } else { + left().mergeNestedField(scope); + right().mergeNestedField(scope); + } } + mergeIfHaveTagAndIsRootOfWhereOrNot(scope); + } - private boolean isLeftChildCondition() { - return expr.getLeft() instanceof SQLBinaryOpExpr; - } + private boolean isLeftChildCondition() { + return expr.getLeft() instanceof SQLBinaryOpExpr; + } - private boolean isChildTagEquals(Scope scope) { - String left = scope.getConditionTag((SQLBinaryOpExpr) expr.getLeft()); - String right = scope.getConditionTag((SQLBinaryOpExpr) expr.getRight()); - return left.equals(right); - } + private boolean isChildTagEquals(Scope scope) { + String left = scope.getConditionTag((SQLBinaryOpExpr) expr.getLeft()); + String right = scope.getConditionTag((SQLBinaryOpExpr) expr.getRight()); + return left.equals(right); + } - private void useAnyChildTag(Scope scope) { - scope.addConditionTag(expr, scope.getConditionTag((SQLBinaryOpExpr) expr.getLeft())); - } + private void useAnyChildTag(Scope scope) { + scope.addConditionTag(expr, scope.getConditionTag((SQLBinaryOpExpr) expr.getLeft())); + } - /** - * Merge anyway if the root of WHERE clause or {@link SQLNotExpr} be reached. - */ - private void mergeIfHaveTagAndIsRootOfWhereOrNot(Scope scope) { - if (scope.getConditionTag(expr).isEmpty()) { - return; - } - if (expr.getParent() instanceof MySqlSelectQueryBlock - || expr.getParent() instanceof SQLNotExpr) { - mergeNestedField(scope); - } + /** Merge anyway if the root of WHERE clause or {@link SQLNotExpr} be reached. */ + private void mergeIfHaveTagAndIsRootOfWhereOrNot(Scope scope) { + if (scope.getConditionTag(expr).isEmpty()) { + return; } - - private Where left() { - return new Where((SQLBinaryOpExpr) expr.getLeft()); + if (expr.getParent() instanceof MySqlSelectQueryBlock + || expr.getParent() instanceof SQLNotExpr) { + mergeNestedField(scope); } + } - private Where right() { - return new Where((SQLBinaryOpExpr) expr.getRight()); - } + private Where left() { + return new Where((SQLBinaryOpExpr) expr.getLeft()); + } - /** - * There are 2 cases: - * 1) For a single condition, just wrap nested() function. That's it. - *

    - * BinaryOp - * / \ - * Identifier Value - * "employees.age" "30" - *

    - * to - *

    - * BinaryOp - * / \ - * Method Value - * "nested" "30" - * | - * Identifier - * "employees.age" - *

    - * 2) For multiple conditions, put entire BinaryOp to the parameter and add function name "nested()" first - *

    - * BinaryOp (a) - * / \ - * BinaryOp BinaryOp - * | | - * ... ... - *

    - * to - *

    - * Method - * "nested" - * | - * BinaryOp (a) - * / \ - * ... ... - */ - private void mergeNestedField(Scope scope) { - String tag = scope.getConditionTag(expr); - if (!tag.isEmpty()) { - if (isLeftChildCondition()) { - replaceByNestedFunction(expr).getParameters().add(0, new SQLCharExpr(tag)); - } else { - replaceByNestedFunction(expr.getLeft(), pathFromIdentifier(expr.getLeft())); - } - } - } + private Where right() { + return new Where((SQLBinaryOpExpr) expr.getRight()); + } + /** + * + * + *

    +   * There are 2 cases:
    +   * 1) For a single condition, just wrap nested() function. That's it.
    +   * 

    + * BinaryOp + * / \ + * Identifier Value + * "employees.age" "30" + *

    + * to + *

    + * BinaryOp + * / \ + * Method Value + * "nested" "30" + * | + * Identifier + * "employees.age" + *

    + * 2) For multiple conditions, put entire BinaryOp to the parameter and add function name "nested()" first + *

    + * BinaryOp (a) + * / \ + * BinaryOp BinaryOp + * | | + * ... ... + *

    + * to + *

    + * Method + * "nested" + * | + * BinaryOp (a) + * / \ + * ... ... + *

    + */ + private void mergeNestedField(Scope scope) { + String tag = scope.getConditionTag(expr); + if (!tag.isEmpty()) { + if (isLeftChildCondition()) { + replaceByNestedFunction(expr).getParameters().add(0, new SQLCharExpr(tag)); + } else { + replaceByNestedFunction(expr.getLeft(), pathFromIdentifier(expr.getLeft())); + } + } + } } diff --git a/legacy/src/main/java/org/opensearch/sql/legacy/rewriter/ordinal/OrdinalRewriterRule.java b/legacy/src/main/java/org/opensearch/sql/legacy/rewriter/ordinal/OrdinalRewriterRule.java index 03ff07b1b8..ed853823ce 100644 --- a/legacy/src/main/java/org/opensearch/sql/legacy/rewriter/ordinal/OrdinalRewriterRule.java +++ b/legacy/src/main/java/org/opensearch/sql/legacy/rewriter/ordinal/OrdinalRewriterRule.java @@ -145,25 +145,26 @@ private boolean hasOrderByWithOrdinals(MySqlSelectQueryBlock query) { return false; } - /** - *
    -         * The second condition checks valid AST that meets ORDER BY IS NULL/NOT NULL condition
    -         *
    -         *            SQLSelectOrderByItem
    -         *                      |
    -         *             SQLBinaryOpExpr (Is || IsNot)
    -         *                    /  \
    -         *    SQLIdentifierExpr  SQLNullExpr
    -         *  
    - */ - return query.getOrderBy().getItems().stream().anyMatch(x -> - x.getExpr() instanceof SQLIntegerExpr - || ( - x.getExpr() instanceof SQLBinaryOpExpr - && ((SQLBinaryOpExpr) x.getExpr()).getLeft() instanceof SQLIntegerExpr - ) - ); - } + /** + * + * + *
    +     * The second condition checks valid AST that meets ORDER BY IS NULL/NOT NULL condition
    +     *
    +     *            SQLSelectOrderByItem
    +     *                      |
    +     *             SQLBinaryOpExpr (Is || IsNot)
    +     *                    /  \
    +     *    SQLIdentifierExpr  SQLNullExpr
    +     *  
    + */ + return query.getOrderBy().getItems().stream() + .anyMatch( + x -> + x.getExpr() instanceof SQLIntegerExpr + || (x.getExpr() instanceof SQLBinaryOpExpr + && ((SQLBinaryOpExpr) x.getExpr()).getLeft() instanceof SQLIntegerExpr)); + } private SQLQueryExpr toSqlExpr() { SQLExprParser parser = new ElasticSqlExprParser(sql); diff --git a/legacy/src/main/java/org/opensearch/sql/legacy/rewriter/parent/SQLExprParentSetter.java b/legacy/src/main/java/org/opensearch/sql/legacy/rewriter/parent/SQLExprParentSetter.java index 9de81f2ab1..3ad2955798 100644 --- a/legacy/src/main/java/org/opensearch/sql/legacy/rewriter/parent/SQLExprParentSetter.java +++ b/legacy/src/main/java/org/opensearch/sql/legacy/rewriter/parent/SQLExprParentSetter.java @@ -3,7 +3,6 @@ * SPDX-License-Identifier: Apache-2.0 */ - package org.opensearch.sql.legacy.rewriter.parent; import com.alibaba.druid.sql.ast.expr.SQLInListExpr; @@ -12,35 +11,27 @@ import com.alibaba.druid.sql.ast.expr.SQLQueryExpr; import com.alibaba.druid.sql.dialect.mysql.visitor.MySqlASTVisitorAdapter; -/** - * Add the parent for the node in {@link SQLQueryExpr} if it is missing. - */ +/** Add the parent for the node in {@link SQLQueryExpr} if it is missing. */ public class SQLExprParentSetter extends MySqlASTVisitorAdapter { - /** - * Fix null parent problem which is required by SQLIdentifier.visit() - */ - @Override - public boolean visit(SQLInSubQueryExpr subQuery) { - subQuery.getExpr().setParent(subQuery); - return true; - } + /** Fix null parent problem which is required by SQLIdentifier.visit() */ + @Override + public boolean visit(SQLInSubQueryExpr subQuery) { + subQuery.getExpr().setParent(subQuery); + return true; + } - /** - * Fix null parent problem which is required by SQLIdentifier.visit() - */ - @Override - public boolean visit(SQLInListExpr expr) { - expr.getExpr().setParent(expr); - return true; - } + /** Fix null parent problem which is required by SQLIdentifier.visit() */ + @Override + public boolean visit(SQLInListExpr expr) { + expr.getExpr().setParent(expr); + return true; + } - /** - * Fix the expr in {@link SQLNotExpr} without parent. - */ - @Override - public boolean visit(SQLNotExpr notExpr) { - notExpr.getExpr().setParent(notExpr); - return true; - } + /** Fix the expr in {@link SQLNotExpr} without parent. */ + @Override + public boolean visit(SQLNotExpr notExpr) { + notExpr.getExpr().setParent(notExpr); + return true; + } } diff --git a/legacy/src/main/java/org/opensearch/sql/legacy/rewriter/parent/SQLExprParentSetterRule.java b/legacy/src/main/java/org/opensearch/sql/legacy/rewriter/parent/SQLExprParentSetterRule.java index 62ad0765d8..b623998b6e 100644 --- a/legacy/src/main/java/org/opensearch/sql/legacy/rewriter/parent/SQLExprParentSetterRule.java +++ b/legacy/src/main/java/org/opensearch/sql/legacy/rewriter/parent/SQLExprParentSetterRule.java @@ -3,24 +3,21 @@ * SPDX-License-Identifier: Apache-2.0 */ - package org.opensearch.sql.legacy.rewriter.parent; import com.alibaba.druid.sql.ast.expr.SQLQueryExpr; import org.opensearch.sql.legacy.rewriter.RewriteRule; -/** - * The {@link RewriteRule} which will apply {@link SQLExprParentSetter} for {@link SQLQueryExpr} - */ +/** The {@link RewriteRule} which will apply {@link SQLExprParentSetter} for {@link SQLQueryExpr} */ public class SQLExprParentSetterRule implements RewriteRule { - @Override - public boolean match(SQLQueryExpr expr) { - return true; - } + @Override + public boolean match(SQLQueryExpr expr) { + return true; + } - @Override - public void rewrite(SQLQueryExpr expr) { - expr.accept(new SQLExprParentSetter()); - } + @Override + public void rewrite(SQLQueryExpr expr) { + expr.accept(new SQLExprParentSetter()); + } } diff --git a/legacy/src/main/java/org/opensearch/sql/legacy/rewriter/subquery/NestedQueryContext.java b/legacy/src/main/java/org/opensearch/sql/legacy/rewriter/subquery/NestedQueryContext.java index b300015d49..17eaf72865 100644 --- a/legacy/src/main/java/org/opensearch/sql/legacy/rewriter/subquery/NestedQueryContext.java +++ b/legacy/src/main/java/org/opensearch/sql/legacy/rewriter/subquery/NestedQueryContext.java @@ -14,9 +14,9 @@ /** * {@link NestedQueryContext} build the context with Query to detected the specified table is nested - * or not. - *
    Todo current implementation doesn't rely on the index mapping which should be added after - * the semantics is built. + * or not.
    + * Todo current implementation doesn't rely on the index mapping which should be added after the + * semantics is built. */ public class NestedQueryContext { private static final String SEPARATOR = "."; @@ -54,17 +54,17 @@ private void process(SQLExprTableSource table) { } } - /** - * Extract the parent alias from the tableName. For example
    - * SELECT * FROM employee e, e.project as p,
    - * For expr: employee, the parent alias is "".
    - * For expr: e.project, the parent alias is e. - */ - private String parent(SQLExprTableSource table) { - String tableName = table.getExpr().toString(); - int index = tableName.indexOf(SEPARATOR); - return index == -1 ? EMPTY : tableName.substring(0, index); - } + /** + * Extract the parent alias from the tableName. For example
    + * SELECT * FROM employee e, e.project as p,
    + * For expr: employee, the parent alias is "".
    + * For expr: e.project, the parent alias is e. + */ + private String parent(SQLExprTableSource table) { + String tableName = table.getExpr().toString(); + int index = tableName.indexOf(SEPARATOR); + return index == -1 ? EMPTY : tableName.substring(0, index); + } private String alias(SQLExprTableSource table) { if (Strings.isNullOrEmpty(table.getAlias())) { diff --git a/legacy/src/main/java/org/opensearch/sql/legacy/rewriter/subquery/SubQueryRewriteRule.java b/legacy/src/main/java/org/opensearch/sql/legacy/rewriter/subquery/SubQueryRewriteRule.java index 44a68b1bbb..5177b2d6d3 100644 --- a/legacy/src/main/java/org/opensearch/sql/legacy/rewriter/subquery/SubQueryRewriteRule.java +++ b/legacy/src/main/java/org/opensearch/sql/legacy/rewriter/subquery/SubQueryRewriteRule.java @@ -3,7 +3,6 @@ * SPDX-License-Identifier: Apache-2.0 */ - package org.opensearch.sql.legacy.rewriter.subquery; import com.alibaba.druid.sql.ast.expr.SQLQueryExpr; @@ -12,42 +11,44 @@ import org.opensearch.sql.legacy.rewriter.subquery.rewriter.SubqueryAliasRewriter; import org.opensearch.sql.legacy.rewriter.subquery.utils.FindSubQuery; -/** - * Subquery Rewriter Rule. - */ +/** Subquery Rewriter Rule. */ public class SubQueryRewriteRule implements RewriteRule { - private FindSubQuery findAllSubQuery = new FindSubQuery(); - - @Override - public boolean match(SQLQueryExpr expr) throws SQLFeatureNotSupportedException { - expr.accept(findAllSubQuery); - - if (isContainSubQuery(findAllSubQuery)) { - if (isSupportedSubQuery(findAllSubQuery)) { - return true; - } else { - throw new SQLFeatureNotSupportedException("Unsupported subquery. Only one EXISTS or IN is supported"); - } - } else { - return false; - } - } - - @Override - public void rewrite(SQLQueryExpr expr) { - expr.accept(new SubqueryAliasRewriter()); - new SubQueryRewriter().convert(expr.getSubQuery()); + private FindSubQuery findAllSubQuery = new FindSubQuery(); + + @Override + public boolean match(SQLQueryExpr expr) throws SQLFeatureNotSupportedException { + expr.accept(findAllSubQuery); + + if (isContainSubQuery(findAllSubQuery)) { + if (isSupportedSubQuery(findAllSubQuery)) { + return true; + } else { + throw new SQLFeatureNotSupportedException( + "Unsupported subquery. Only one EXISTS or IN is supported"); + } + } else { + return false; } - - private boolean isContainSubQuery(FindSubQuery allSubQuery) { - return !allSubQuery.getSqlExistsExprs().isEmpty() || !allSubQuery.getSqlInSubQueryExprs().isEmpty(); - } - - private boolean isSupportedSubQuery(FindSubQuery allSubQuery) { - if ((allSubQuery.getSqlInSubQueryExprs().size() == 1 && allSubQuery.getSqlExistsExprs().size() == 0) - || (allSubQuery.getSqlInSubQueryExprs().size() == 0 && allSubQuery.getSqlExistsExprs().size() == 1)) { - return true; - } - return false; + } + + @Override + public void rewrite(SQLQueryExpr expr) { + expr.accept(new SubqueryAliasRewriter()); + new SubQueryRewriter().convert(expr.getSubQuery()); + } + + private boolean isContainSubQuery(FindSubQuery allSubQuery) { + return !allSubQuery.getSqlExistsExprs().isEmpty() + || !allSubQuery.getSqlInSubQueryExprs().isEmpty(); + } + + private boolean isSupportedSubQuery(FindSubQuery allSubQuery) { + if ((allSubQuery.getSqlInSubQueryExprs().size() == 1 + && allSubQuery.getSqlExistsExprs().size() == 0) + || (allSubQuery.getSqlInSubQueryExprs().size() == 0 + && allSubQuery.getSqlExistsExprs().size() == 1)) { + return true; } + return false; + } } diff --git a/legacy/src/main/java/org/opensearch/sql/legacy/rewriter/subquery/SubQueryRewriter.java b/legacy/src/main/java/org/opensearch/sql/legacy/rewriter/subquery/SubQueryRewriter.java index fd503a0e9b..c788e8f559 100644 --- a/legacy/src/main/java/org/opensearch/sql/legacy/rewriter/subquery/SubQueryRewriter.java +++ b/legacy/src/main/java/org/opensearch/sql/legacy/rewriter/subquery/SubQueryRewriter.java @@ -3,7 +3,6 @@ * SPDX-License-Identifier: Apache-2.0 */ - package org.opensearch.sql.legacy.rewriter.subquery; import com.alibaba.druid.sql.ast.SQLExpr; @@ -19,73 +18,73 @@ import org.opensearch.sql.legacy.rewriter.subquery.utils.FindSubQuery; public class SubQueryRewriter { - private final RewriterContext ctx = new RewriterContext(); + private final RewriterContext ctx = new RewriterContext(); - public void convert(SQLSelect query) { - SQLSelectQuery queryExpr = query.getQuery(); - if (queryExpr instanceof MySqlSelectQueryBlock) { - MySqlSelectQueryBlock queryBlock = (MySqlSelectQueryBlock) queryExpr; - ctx.addTable(queryBlock.getFrom()); + public void convert(SQLSelect query) { + SQLSelectQuery queryExpr = query.getQuery(); + if (queryExpr instanceof MySqlSelectQueryBlock) { + MySqlSelectQueryBlock queryBlock = (MySqlSelectQueryBlock) queryExpr; + ctx.addTable(queryBlock.getFrom()); - queryBlock.setWhere(convertWhere(queryBlock.getWhere())); - queryBlock.setFrom(convertFrom(queryBlock.getFrom())); - } + queryBlock.setWhere(convertWhere(queryBlock.getWhere())); + queryBlock.setFrom(convertFrom(queryBlock.getFrom())); } + } - private SQLTableSource convertFrom(SQLTableSource expr) { - SQLTableSource join = ctx.popJoin(); - if (join != null) { - return join; - } - return expr; + private SQLTableSource convertFrom(SQLTableSource expr) { + SQLTableSource join = ctx.popJoin(); + if (join != null) { + return join; } + return expr; + } - private SQLExpr convertWhere(SQLExpr expr) { - if (expr instanceof SQLExistsExpr) { - ctx.setExistsSubQuery((SQLExistsExpr) expr); - rewriteSubQuery(expr, ((SQLExistsExpr) expr).getSubQuery()); - return ctx.popWhere(); - } else if (expr instanceof SQLInSubQueryExpr) { - ctx.setInSubQuery((SQLInSubQueryExpr) expr); - rewriteSubQuery(expr, ((SQLInSubQueryExpr) expr).getSubQuery()); - return ctx.popWhere(); - } else if (expr instanceof SQLBinaryOpExpr) { - SQLBinaryOpExpr binaryOpExpr = (SQLBinaryOpExpr) expr; - SQLExpr left = convertWhere(binaryOpExpr.getLeft()); - left.setParent(binaryOpExpr); - binaryOpExpr.setLeft(left); - SQLExpr right = convertWhere(binaryOpExpr.getRight()); - right.setParent(binaryOpExpr); - binaryOpExpr.setRight(right); - } - return expr; + private SQLExpr convertWhere(SQLExpr expr) { + if (expr instanceof SQLExistsExpr) { + ctx.setExistsSubQuery((SQLExistsExpr) expr); + rewriteSubQuery(expr, ((SQLExistsExpr) expr).getSubQuery()); + return ctx.popWhere(); + } else if (expr instanceof SQLInSubQueryExpr) { + ctx.setInSubQuery((SQLInSubQueryExpr) expr); + rewriteSubQuery(expr, ((SQLInSubQueryExpr) expr).getSubQuery()); + return ctx.popWhere(); + } else if (expr instanceof SQLBinaryOpExpr) { + SQLBinaryOpExpr binaryOpExpr = (SQLBinaryOpExpr) expr; + SQLExpr left = convertWhere(binaryOpExpr.getLeft()); + left.setParent(binaryOpExpr); + binaryOpExpr.setLeft(left); + SQLExpr right = convertWhere(binaryOpExpr.getRight()); + right.setParent(binaryOpExpr); + binaryOpExpr.setRight(right); } + return expr; + } - private void rewriteSubQuery(SQLExpr subQueryExpr, SQLSelect subQuerySelect) { - if (containSubQuery(subQuerySelect)) { - convert(subQuerySelect); - } else if (isSupportedSubQuery(ctx)){ - for (Rewriter rewriter : RewriterFactory.createRewriterList(subQueryExpr, ctx)) { - if (rewriter.canRewrite()) { - rewriter.rewrite(); - return; - } - } + private void rewriteSubQuery(SQLExpr subQueryExpr, SQLSelect subQuerySelect) { + if (containSubQuery(subQuerySelect)) { + convert(subQuerySelect); + } else if (isSupportedSubQuery(ctx)) { + for (Rewriter rewriter : RewriterFactory.createRewriterList(subQueryExpr, ctx)) { + if (rewriter.canRewrite()) { + rewriter.rewrite(); + return; } - throw new IllegalStateException("Unsupported subquery"); + } } + throw new IllegalStateException("Unsupported subquery"); + } - private boolean containSubQuery(SQLSelect query) { - FindSubQuery findSubQuery = new FindSubQuery().continueVisitWhenFound(false); - query.accept(findSubQuery); - return findSubQuery.hasSubQuery(); - } + private boolean containSubQuery(SQLSelect query) { + FindSubQuery findSubQuery = new FindSubQuery().continueVisitWhenFound(false); + query.accept(findSubQuery); + return findSubQuery.hasSubQuery(); + } - private boolean isSupportedSubQuery(RewriterContext ctx) { - if ((ctx.getSqlInSubQueryExprs().size() == 1 && ctx.getSqlExistsExprs().size() == 0) - || (ctx.getSqlInSubQueryExprs().size() == 0 && ctx.getSqlExistsExprs().size() == 1)) { - return true; - } - return false; + private boolean isSupportedSubQuery(RewriterContext ctx) { + if ((ctx.getSqlInSubQueryExprs().size() == 1 && ctx.getSqlExistsExprs().size() == 0) + || (ctx.getSqlInSubQueryExprs().size() == 0 && ctx.getSqlExistsExprs().size() == 1)) { + return true; } + return false; + } } diff --git a/legacy/src/main/java/org/opensearch/sql/legacy/rewriter/subquery/rewriter/InRewriter.java b/legacy/src/main/java/org/opensearch/sql/legacy/rewriter/subquery/rewriter/InRewriter.java index 281918d52c..f598ab9a97 100644 --- a/legacy/src/main/java/org/opensearch/sql/legacy/rewriter/subquery/rewriter/InRewriter.java +++ b/legacy/src/main/java/org/opensearch/sql/legacy/rewriter/subquery/rewriter/InRewriter.java @@ -16,6 +16,8 @@ import org.opensearch.sql.legacy.rewriter.subquery.RewriterContext; /** + * + * *
      * IN Subquery Rewriter.
      * For example,
    @@ -39,29 +41,31 @@ public boolean canRewrite() {
         return !inExpr.isNot();
       }
     
    -    /**
    -     * 
    -     * Build Where clause from input query.
    -     * 

    - * With the input query. - * Query - * / | \ - * SELECT FROM WHERE - * | | / | \ - * * A c>10 AND INSubquery - * / \ - * a Query - * / \ - * SELECT FROM - * | | - * b B - *

    - *

    - */ - @Override - public void rewrite() { - SQLTableSource from = queryBlock.getFrom(); - addJoinTable(from); + /** + * + * + *
    +   * Build Where clause from input query.
    +   * 

    + * With the input query. + * Query + * / | \ + * SELECT FROM WHERE + * | | / | \ + * * A c>10 AND INSubquery + * / \ + * a Query + * / \ + * SELECT FROM + * | | + * b B + *

    + *

    + */ + @Override + public void rewrite() { + SQLTableSource from = queryBlock.getFrom(); + addJoinTable(from); SQLExpr where = queryBlock.getWhere(); if (null == where) { @@ -73,16 +77,16 @@ public void rewrite() { } } - /** - * Build the Null check expression. For example,
    - * SELECT * FROM A WHERE a IN (SELECT b FROM B)
    - * should return B.b IS NOT NULL - */ - private SQLBinaryOpExpr generateNullOp() { - SQLBinaryOpExpr binaryOpExpr = new SQLBinaryOpExpr(); - binaryOpExpr.setLeft(fetchJoinExpr()); - binaryOpExpr.setRight(new SQLNullExpr()); - binaryOpExpr.setOperator(SQLBinaryOperator.IsNot); + /** + * Build the Null check expression. For example,
    + * SELECT * FROM A WHERE a IN (SELECT b FROM B)
    + * should return B.b IS NOT NULL + */ + private SQLBinaryOpExpr generateNullOp() { + SQLBinaryOpExpr binaryOpExpr = new SQLBinaryOpExpr(); + binaryOpExpr.setLeft(fetchJoinExpr()); + binaryOpExpr.setRight(new SQLNullExpr()); + binaryOpExpr.setOperator(SQLBinaryOperator.IsNot); return binaryOpExpr; } diff --git a/legacy/src/main/java/org/opensearch/sql/legacy/rewriter/subquery/rewriter/NestedExistsRewriter.java b/legacy/src/main/java/org/opensearch/sql/legacy/rewriter/subquery/rewriter/NestedExistsRewriter.java index 26684f4f61..f504b4760d 100644 --- a/legacy/src/main/java/org/opensearch/sql/legacy/rewriter/subquery/rewriter/NestedExistsRewriter.java +++ b/legacy/src/main/java/org/opensearch/sql/legacy/rewriter/subquery/rewriter/NestedExistsRewriter.java @@ -19,6 +19,7 @@ /** * Nested EXISTS SQL Rewriter. The EXISTS clause will be remove from the SQL. The translated SQL * will use ElasticSearch's nested query logic. + * *
      * For example,
      * 

    diff --git a/legacy/src/main/java/org/opensearch/sql/legacy/rewriter/subquery/rewriter/SubqueryAliasRewriter.java b/legacy/src/main/java/org/opensearch/sql/legacy/rewriter/subquery/rewriter/SubqueryAliasRewriter.java index e47027f024..7176bd030c 100644 --- a/legacy/src/main/java/org/opensearch/sql/legacy/rewriter/subquery/rewriter/SubqueryAliasRewriter.java +++ b/legacy/src/main/java/org/opensearch/sql/legacy/rewriter/subquery/rewriter/SubqueryAliasRewriter.java @@ -3,7 +3,6 @@ * SPDX-License-Identifier: Apache-2.0 */ - package org.opensearch.sql.legacy.rewriter.subquery.rewriter; import com.alibaba.druid.sql.ast.expr.SQLAllColumnExpr; @@ -20,6 +19,9 @@ import java.util.Deque; /** + * + * + *

      * Add the alias for identifier the subquery query.
      * Use the table alias if it already has one, Auto generate if it doesn't has one.
      * 

    @@ -41,126 +43,123 @@ * | Identifier in Select | (TbA,TbA_0) | SELECT TbA.* FROM TbA as TbA_0 WHERE TbA_0.a IN (SELECT TbB_0.b FROM TbB as TbB_0) and TbA_0.c > 10 | * | | (TbB,TbB_0) | | * +-----------------------+-------------+-----------------------------------------------------------------------------------------------------+ + *

    */ public class SubqueryAliasRewriter extends MySqlASTVisitorAdapter { - private final Deque tableScope = new ArrayDeque<>(); - private int aliasSuffix = 0; - private static final String DOT = "."; - - @Override - public boolean visit(MySqlSelectQueryBlock query) { - SQLTableSource from = query.getFrom(); - if (from instanceof SQLExprTableSource) { - SQLExprTableSource expr = (SQLExprTableSource) from; - String tableName = expr.getExpr().toString().replaceAll(" ", ""); - - if (expr.getAlias() != null) { - tableScope.push(new Table(tableName, expr.getAlias())); - } else { - expr.setAlias(createAlias(tableName)); - tableScope.push(new Table(tableName, expr.getAlias())); - } - } - return true; + private final Deque
    tableScope = new ArrayDeque<>(); + private int aliasSuffix = 0; + private static final String DOT = "."; + + @Override + public boolean visit(MySqlSelectQueryBlock query) { + SQLTableSource from = query.getFrom(); + if (from instanceof SQLExprTableSource) { + SQLExprTableSource expr = (SQLExprTableSource) from; + String tableName = expr.getExpr().toString().replaceAll(" ", ""); + + if (expr.getAlias() != null) { + tableScope.push(new Table(tableName, expr.getAlias())); + } else { + expr.setAlias(createAlias(tableName)); + tableScope.push(new Table(tableName, expr.getAlias())); + } } + return true; + } - @Override - public boolean visit(SQLIdentifierExpr expr) { - if (!tableScope.isEmpty() && (inSelect(expr) || inWhere(expr) || inSubquery(expr))) { - rewrite(tableScope.peek(), expr); - } - return true; + @Override + public boolean visit(SQLIdentifierExpr expr) { + if (!tableScope.isEmpty() && (inSelect(expr) || inWhere(expr) || inSubquery(expr))) { + rewrite(tableScope.peek(), expr); } + return true; + } - @Override - public boolean visit(SQLAllColumnExpr expr) { - if (!tableScope.isEmpty() && inSelect(expr)) { - ((SQLSelectItem) expr.getParent()).setExpr(createIdentifierExpr(tableScope.peek())); - } - return true; + @Override + public boolean visit(SQLAllColumnExpr expr) { + if (!tableScope.isEmpty() && inSelect(expr)) { + ((SQLSelectItem) expr.getParent()).setExpr(createIdentifierExpr(tableScope.peek())); } - - private boolean inSelect(SQLIdentifierExpr expr) { - return expr.getParent() instanceof SQLSelectItem; + return true; + } + + private boolean inSelect(SQLIdentifierExpr expr) { + return expr.getParent() instanceof SQLSelectItem; + } + + private boolean inSelect(SQLAllColumnExpr expr) { + return expr.getParent() instanceof SQLSelectItem; + } + + private boolean inWhere(SQLIdentifierExpr expr) { + return expr.getParent() instanceof SQLBinaryOpExpr + && !isESTable((SQLBinaryOpExpr) expr.getParent()); + } + + /** + * The table name in OpenSearch could be "index/type". Which represent as SQLBinaryOpExpr in AST. + */ + private boolean isESTable(SQLBinaryOpExpr expr) { + return expr.getOperator() == SQLBinaryOperator.Divide + && expr.getParent() instanceof SQLExprTableSource; + } + + private boolean inSubquery(SQLIdentifierExpr expr) { + return expr.getParent() instanceof SQLInSubQueryExpr; + } + + @Override + public void endVisit(MySqlSelectQueryBlock query) { + if (!tableScope.isEmpty()) { + tableScope.pop(); } + } - private boolean inSelect(SQLAllColumnExpr expr) { - return expr.getParent() instanceof SQLSelectItem; - } + private void rewrite(Table table, SQLIdentifierExpr expr) { + String tableAlias = table.getAlias(); + String tableName = table.getName(); - private boolean inWhere(SQLIdentifierExpr expr) { - return expr.getParent() instanceof SQLBinaryOpExpr && !isESTable((SQLBinaryOpExpr) expr.getParent()); + String exprName = expr.getName(); + if (exprName.startsWith(tableName + DOT) || exprName.startsWith(tableAlias + DOT)) { + expr.setName(exprName.replace(tableName + DOT, tableAlias + DOT)); + } else { + expr.setName(String.join(DOT, tableAlias, exprName)); } + } - /** - * The table name in OpenSearch could be "index/type". Which represent as SQLBinaryOpExpr in AST. - */ - private boolean isESTable(SQLBinaryOpExpr expr) { - return expr.getOperator() == SQLBinaryOperator.Divide && expr.getParent() instanceof SQLExprTableSource; - } + private SQLIdentifierExpr createIdentifierExpr(Table table) { + String newIdentifierName = String.join(DOT, table.getAlias(), "*"); + return new SQLIdentifierExpr(newIdentifierName); + } - private boolean inSubquery(SQLIdentifierExpr expr) { - return expr.getParent() instanceof SQLInSubQueryExpr; - } + private String createAlias(String alias) { + return String.format("%s_%d", alias, next()); + } - @Override - public void endVisit(MySqlSelectQueryBlock query) { - if (!tableScope.isEmpty()) { - tableScope.pop(); - } - } + private Integer next() { + return aliasSuffix++; + } - private void rewrite(Table table, SQLIdentifierExpr expr) { - String tableAlias = table.getAlias(); - String tableName = table.getName(); + /** Table Bean. */ + private static class Table { - String exprName = expr.getName(); - if (exprName.startsWith(tableName + DOT) || exprName.startsWith(tableAlias + DOT)) { - expr.setName(exprName.replace(tableName + DOT, tableAlias + DOT)); - } else { - expr.setName(String.join(DOT, tableAlias, exprName)); - } + public String getName() { + return name; } - private SQLIdentifierExpr createIdentifierExpr(Table table) { - String newIdentifierName = String.join(DOT, table.getAlias(), "*"); - return new SQLIdentifierExpr(newIdentifierName); + public String getAlias() { + return alias; } - private String createAlias(String alias) { - return String.format("%s_%d", alias, next()); - } + /** Table Name. */ + private String name; - private Integer next() { - return aliasSuffix++; - } + /** Table Alias. */ + private String alias; - /** - * Table Bean. - */ - private static class Table { - - public String getName() { - return name; - } - - public String getAlias() { - return alias; - } - - /** - * Table Name. - */ - private String name; - - /** - * Table Alias. - */ - private String alias; - - Table(String name, String alias) { - this.name = name; - this.alias = alias; - } + Table(String name, String alias) { + this.name = name; + this.alias = alias; } + } } diff --git a/legacy/src/main/java/org/opensearch/sql/legacy/spatial/SpatialParamsFactory.java b/legacy/src/main/java/org/opensearch/sql/legacy/spatial/SpatialParamsFactory.java index 7b99d52e68..5e1102994e 100644 --- a/legacy/src/main/java/org/opensearch/sql/legacy/spatial/SpatialParamsFactory.java +++ b/legacy/src/main/java/org/opensearch/sql/legacy/spatial/SpatialParamsFactory.java @@ -3,103 +3,105 @@ * SPDX-License-Identifier: Apache-2.0 */ - package org.opensearch.sql.legacy.spatial; - import com.alibaba.druid.sql.ast.SQLExpr; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Set; -/** - * Created by Eliran on 1/8/2015. - */ +/** Created by Eliran on 1/8/2015. */ public class SpatialParamsFactory { - public static Set allowedMethods; + public static Set allowedMethods; - static { - allowedMethods = new HashSet<>(); - allowedMethods.add("GEO_INTERSECTS"); - allowedMethods.add("GEO_BOUNDING_BOX"); - allowedMethods.add("GEO_DISTANCE"); - allowedMethods.add("GEO_DISTANCE_RANGE"); - allowedMethods.add("GEO_POLYGON"); - allowedMethods.add("GEO_CELL"); - } + static { + allowedMethods = new HashSet<>(); + allowedMethods.add("GEO_INTERSECTS"); + allowedMethods.add("GEO_BOUNDING_BOX"); + allowedMethods.add("GEO_DISTANCE"); + allowedMethods.add("GEO_DISTANCE_RANGE"); + allowedMethods.add("GEO_POLYGON"); + allowedMethods.add("GEO_CELL"); + } - public static boolean isAllowedMethod(String name) { - return allowedMethods.contains(name); - } + public static boolean isAllowedMethod(String name) { + return allowedMethods.contains(name); + } - public static Object generateSpatialParamsObject(String methodName, List params) { - switch (methodName) { - case "GEO_INTERSECTS": - if (params.size() != 2) { - throw new RuntimeException("GEO_INTERSECTS should have exactly 2 parameters : (fieldName,'WKT') "); - } - return params.get(1).toString(); - case "GEO_BOUNDING_BOX": - if (params.size() != 5) { - throw new RuntimeException("GEO_BOUNDING_BOX should have exactly 5 parameters : " - + "(fieldName,topLeftLon,topLeftLan,bottomRightLon,bottomRightLan) "); - } - double topLeftLon = Double.parseDouble(params.get(1).toString()); - double topLeftLat = Double.parseDouble(params.get(2).toString()); - double bottomRightLon = Double.parseDouble(params.get(3).toString()); - double bottomRightLat = Double.parseDouble(params.get(4).toString()); - return new BoundingBoxFilterParams(new Point(topLeftLon, topLeftLat), - new Point(bottomRightLon, bottomRightLat)); - case "GEO_DISTANCE": - if (params.size() != 4) { - throw new RuntimeException("GEO_DISTANCE should have exactly 4 parameters : " - + "(fieldName,distance,fromLon,fromLat) "); - } - String distance = params.get(1).toString(); - double lon = Double.parseDouble(params.get(2).toString()); - double lat = Double.parseDouble(params.get(3).toString()); - return new DistanceFilterParams(distance, new Point(lon, lat)); - case "GEO_DISTANCE_RANGE": - if (params.size() != 5) { - throw new RuntimeException("GEO_DISTANCE should have exactly 5 parameters : " - + "(fieldName,distanceFrom,distanceTo,fromLon,fromLat) "); - } - String distanceFrom = params.get(1).toString(); - String distanceTo = params.get(2).toString(); - lon = Double.parseDouble(params.get(3).toString()); - lat = Double.parseDouble(params.get(4).toString()); - return new RangeDistanceFilterParams(distanceFrom, distanceTo, new Point(lon, lat)); - case "GEO_POLYGON": - if (params.size() % 2 == 0 || params.size() <= 5) { - throw new RuntimeException("GEO_POLYGON should have odd num of parameters and > 5 : " - + "(fieldName,lon1,lat1,lon2,lat2,lon3,lat3,...) "); - } - int numberOfPoints = (params.size() - 1) / 2; - List points = new LinkedList<>(); - for (int i = 0; i < numberOfPoints; i++) { - int currentPointLocation = 1 + i * 2; - lon = Double.parseDouble(params.get(currentPointLocation).toString()); - lat = Double.parseDouble(params.get(currentPointLocation + 1).toString()); - points.add(new Point(lon, lat)); - } - return new PolygonFilterParams(points); - case "GEO_CELL": - if (params.size() < 4 || params.size() > 5) { - throw new RuntimeException("GEO_CELL should have 4 or 5 params " - + "(fieldName,lon,lat,precision,neighbors(optional)) "); - } - lon = Double.parseDouble(params.get(1).toString()); - lat = Double.parseDouble(params.get(2).toString()); - Point geoHashPoint = new Point(lon, lat); - int precision = Integer.parseInt(params.get(3).toString()); - if (params.size() == 4) { - return new CellFilterParams(geoHashPoint, precision); - } - boolean neighbors = Boolean.parseBoolean(params.get(4).toString()); - return new CellFilterParams(geoHashPoint, precision, neighbors); - default: - throw new RuntimeException(String.format("Unknown method name: %s", methodName)); + public static Object generateSpatialParamsObject(String methodName, List params) { + switch (methodName) { + case "GEO_INTERSECTS": + if (params.size() != 2) { + throw new RuntimeException( + "GEO_INTERSECTS should have exactly 2 parameters : (fieldName,'WKT') "); + } + return params.get(1).toString(); + case "GEO_BOUNDING_BOX": + if (params.size() != 5) { + throw new RuntimeException( + "GEO_BOUNDING_BOX should have exactly 5 parameters : " + + "(fieldName,topLeftLon,topLeftLan,bottomRightLon,bottomRightLan) "); + } + double topLeftLon = Double.parseDouble(params.get(1).toString()); + double topLeftLat = Double.parseDouble(params.get(2).toString()); + double bottomRightLon = Double.parseDouble(params.get(3).toString()); + double bottomRightLat = Double.parseDouble(params.get(4).toString()); + return new BoundingBoxFilterParams( + new Point(topLeftLon, topLeftLat), new Point(bottomRightLon, bottomRightLat)); + case "GEO_DISTANCE": + if (params.size() != 4) { + throw new RuntimeException( + "GEO_DISTANCE should have exactly 4 parameters : " + + "(fieldName,distance,fromLon,fromLat) "); + } + String distance = params.get(1).toString(); + double lon = Double.parseDouble(params.get(2).toString()); + double lat = Double.parseDouble(params.get(3).toString()); + return new DistanceFilterParams(distance, new Point(lon, lat)); + case "GEO_DISTANCE_RANGE": + if (params.size() != 5) { + throw new RuntimeException( + "GEO_DISTANCE should have exactly 5 parameters : " + + "(fieldName,distanceFrom,distanceTo,fromLon,fromLat) "); + } + String distanceFrom = params.get(1).toString(); + String distanceTo = params.get(2).toString(); + lon = Double.parseDouble(params.get(3).toString()); + lat = Double.parseDouble(params.get(4).toString()); + return new RangeDistanceFilterParams(distanceFrom, distanceTo, new Point(lon, lat)); + case "GEO_POLYGON": + if (params.size() % 2 == 0 || params.size() <= 5) { + throw new RuntimeException( + "GEO_POLYGON should have odd num of parameters and > 5 : " + + "(fieldName,lon1,lat1,lon2,lat2,lon3,lat3,...) "); + } + int numberOfPoints = (params.size() - 1) / 2; + List points = new LinkedList<>(); + for (int i = 0; i < numberOfPoints; i++) { + int currentPointLocation = 1 + i * 2; + lon = Double.parseDouble(params.get(currentPointLocation).toString()); + lat = Double.parseDouble(params.get(currentPointLocation + 1).toString()); + points.add(new Point(lon, lat)); + } + return new PolygonFilterParams(points); + case "GEO_CELL": + if (params.size() < 4 || params.size() > 5) { + throw new RuntimeException( + "GEO_CELL should have 4 or 5 params " + + "(fieldName,lon,lat,precision,neighbors(optional)) "); + } + lon = Double.parseDouble(params.get(1).toString()); + lat = Double.parseDouble(params.get(2).toString()); + Point geoHashPoint = new Point(lon, lat); + int precision = Integer.parseInt(params.get(3).toString()); + if (params.size() == 4) { + return new CellFilterParams(geoHashPoint, precision); } + boolean neighbors = Boolean.parseBoolean(params.get(4).toString()); + return new CellFilterParams(geoHashPoint, precision, neighbors); + default: + throw new RuntimeException(String.format("Unknown method name: %s", methodName)); } + } } diff --git a/legacy/src/main/java/org/opensearch/sql/legacy/spatial/WktToGeoJsonConverter.java b/legacy/src/main/java/org/opensearch/sql/legacy/spatial/WktToGeoJsonConverter.java index 99bc8f0742..13f51ed777 100644 --- a/legacy/src/main/java/org/opensearch/sql/legacy/spatial/WktToGeoJsonConverter.java +++ b/legacy/src/main/java/org/opensearch/sql/legacy/spatial/WktToGeoJsonConverter.java @@ -3,7 +3,6 @@ * SPDX-License-Identifier: Apache-2.0 */ - package org.opensearch.sql.legacy.spatial; import com.google.common.base.Joiner; @@ -11,160 +10,156 @@ import java.util.List; import org.opensearch.sql.legacy.utils.StringUtils; -/** - * Created by Eliran on 4/8/2015. - */ +/** Created by Eliran on 4/8/2015. */ public class WktToGeoJsonConverter { - public static String toGeoJson(String wkt) { - wkt = wkt.toLowerCase(); - int startOfCoordinates = wkt.indexOf("("); - if (startOfCoordinates == -1) { - throw new IllegalArgumentException( - StringUtils.format("Failed to convert well-known-text [%s] to geometry type", wkt)); - } - - String wktType = wkt.substring(0, startOfCoordinates).trim(); - wkt = wkt.substring(startOfCoordinates); - - String type = ""; - String coordinates = ""; - switch (wktType) { - case ("point"): - type = "Point"; - coordinates = pointCoordinatesFromWkt(wkt); - break; - case ("polygon"): - type = "Polygon"; - coordinates = polygonCoordinatesFromWkt(wkt); - break; - case ("linestring"): - type = "LineString"; - coordinates = lineStringCoordinatesFromWkt(wkt); - break; - case ("multipolygon"): - type = "MultiPolygon"; - coordinates = multiPolygonCoordinatesFromWkt(wkt); - break; - case ("multipoint"): - type = "MultiPoint"; - coordinates = multiPointCoordinatesFromWkt(wkt); - break; - case ("multilinestring"): - type = "MultiLineString"; - coordinates = multiLineStringCoordinatesFromWkt(wkt); - break; - default: - throw new IllegalArgumentException("Unsupported well-known-text type: " + wktType); - - } - - return buildGeoJson(type, coordinates); + public static String toGeoJson(String wkt) { + wkt = wkt.toLowerCase(); + int startOfCoordinates = wkt.indexOf("("); + if (startOfCoordinates == -1) { + throw new IllegalArgumentException( + StringUtils.format("Failed to convert well-known-text [%s] to geometry type", wkt)); } - //input: ((10 10, 20 20, 10 40),(40 40, 30 30, 40 20, 30 10)) - private static String multiLineStringCoordinatesFromWkt(String wkt) { - wkt = removeBrackets(wkt, 1); - String lineStringsWithPipeSeparator = wkt.replaceAll("\\s*\\)\\s*,\\s*\\(", ")|("); - String[] lineStrings = lineStringsWithPipeSeparator.split("\\|"); - String[] coordinates = new String[lineStrings.length]; - for (int i = 0; i < lineStrings.length; i++) { - coordinates[i] = lineStringCoordinatesFromWkt(lineStrings[i]); - } - String multiLineStringCoordinates = Joiner.on(",").join(coordinates); - return String.format("[%s]", multiLineStringCoordinates); - + String wktType = wkt.substring(0, startOfCoordinates).trim(); + wkt = wkt.substring(startOfCoordinates); + + String type = ""; + String coordinates = ""; + switch (wktType) { + case ("point"): + type = "Point"; + coordinates = pointCoordinatesFromWkt(wkt); + break; + case ("polygon"): + type = "Polygon"; + coordinates = polygonCoordinatesFromWkt(wkt); + break; + case ("linestring"): + type = "LineString"; + coordinates = lineStringCoordinatesFromWkt(wkt); + break; + case ("multipolygon"): + type = "MultiPolygon"; + coordinates = multiPolygonCoordinatesFromWkt(wkt); + break; + case ("multipoint"): + type = "MultiPoint"; + coordinates = multiPointCoordinatesFromWkt(wkt); + break; + case ("multilinestring"): + type = "MultiLineString"; + coordinates = multiLineStringCoordinatesFromWkt(wkt); + break; + default: + throw new IllegalArgumentException("Unsupported well-known-text type: " + wktType); } - //input v1:MULTIPOINT (10 40, 40 30, 20 20, 30 10) - //v2:MULTIPOINT ((10 40), (40 30), (20 20), (30 10)) - private static String multiPointCoordinatesFromWkt(String wkt) { - wkt = removeBrackets(wkt, 1); - boolean isSecondVersionMultiPoint = wkt.contains("("); - String coordinates = ""; - if (isSecondVersionMultiPoint) { - //(10 40), (40 30), (20 20)-> 10 40, 40 30, 20 20 - wkt = wkt.replaceAll("\\(|\\)", ""); - } - coordinates = getJsonArrayFromListOfPoints(wkt); - return coordinates; + return buildGeoJson(type, coordinates); + } + + // input: ((10 10, 20 20, 10 40),(40 40, 30 30, 40 20, 30 10)) + private static String multiLineStringCoordinatesFromWkt(String wkt) { + wkt = removeBrackets(wkt, 1); + String lineStringsWithPipeSeparator = wkt.replaceAll("\\s*\\)\\s*,\\s*\\(", ")|("); + String[] lineStrings = lineStringsWithPipeSeparator.split("\\|"); + String[] coordinates = new String[lineStrings.length]; + for (int i = 0; i < lineStrings.length; i++) { + coordinates[i] = lineStringCoordinatesFromWkt(lineStrings[i]); } - - //input (((30 20, 45 40, 10 40, 30 20)),((15 5, 40 10, 10 20, 5 10, 15 5))) - private static String multiPolygonCoordinatesFromWkt(String wkt) { - wkt = removeBrackets(wkt, 1); - String polygonsWithPipeSeparator = wkt.replaceAll("\\s*\\)\\s*\\)\\s*,\\s*\\(\\s*\\(\\s*", "))|(("); - String[] polygons = polygonsWithPipeSeparator.split("\\|"); - String[] polygonsCoordinates = new String[polygons.length]; - for (int i = 0; i < polygons.length; i++) { - polygonsCoordinates[i] = polygonCoordinatesFromWkt(polygons[i]); - } - String coordinates = Joiner.on(",").join(polygonsCoordinates); - return String.format("[%s]", coordinates); + String multiLineStringCoordinates = Joiner.on(",").join(coordinates); + return String.format("[%s]", multiLineStringCoordinates); + } + + // input v1:MULTIPOINT (10 40, 40 30, 20 20, 30 10) + // v2:MULTIPOINT ((10 40), (40 30), (20 20), (30 10)) + private static String multiPointCoordinatesFromWkt(String wkt) { + wkt = removeBrackets(wkt, 1); + boolean isSecondVersionMultiPoint = wkt.contains("("); + String coordinates = ""; + if (isSecondVersionMultiPoint) { + // (10 40), (40 30), (20 20)-> 10 40, 40 30, 20 20 + wkt = wkt.replaceAll("\\(|\\)", ""); } - - //input : (30 10, 10 30, 40 40) - private static String lineStringCoordinatesFromWkt(String wkt) { - wkt = removeBrackets(wkt, 1); - return getJsonArrayFromListOfPoints(wkt); + coordinates = getJsonArrayFromListOfPoints(wkt); + return coordinates; + } + + // input (((30 20, 45 40, 10 40, 30 20)),((15 5, 40 10, 10 20, 5 10, 15 5))) + private static String multiPolygonCoordinatesFromWkt(String wkt) { + wkt = removeBrackets(wkt, 1); + String polygonsWithPipeSeparator = + wkt.replaceAll("\\s*\\)\\s*\\)\\s*,\\s*\\(\\s*\\(\\s*", "))|(("); + String[] polygons = polygonsWithPipeSeparator.split("\\|"); + String[] polygonsCoordinates = new String[polygons.length]; + for (int i = 0; i < polygons.length; i++) { + polygonsCoordinates[i] = polygonCoordinatesFromWkt(polygons[i]); } - - //input: v1:((35 10, 45 45, 15 40, 10 20, 35 10)) - //v2:((35 10, 45 45, 15 40, 10 20, 35 10),(20 30, 35 35, 30 20, 20 30)) - private static String polygonCoordinatesFromWkt(String wkt) { - wkt = removeBrackets(wkt, 2); - String coordinates; - boolean polygonContainsInnerHoles = wkt.contains("("); - if (polygonContainsInnerHoles) { - String[] polygons = wkt.split("\\s*\\)\\s*,\\s*\\(\\s*"); - String[] coordinatesOfPolygons = new String[polygons.length]; - for (int i = 0; i < polygons.length; i++) { - String polygonCoordinates = getJsonArrayFromListOfPoints(polygons[i]); - coordinatesOfPolygons[i] = polygonCoordinates; - } - coordinates = Joiner.on(",").join(coordinatesOfPolygons); - } else { - coordinates = getJsonArrayFromListOfPoints(wkt); - } - return String.format("[%s]", coordinates); + String coordinates = Joiner.on(",").join(polygonsCoordinates); + return String.format("[%s]", coordinates); + } + + // input : (30 10, 10 30, 40 40) + private static String lineStringCoordinatesFromWkt(String wkt) { + wkt = removeBrackets(wkt, 1); + return getJsonArrayFromListOfPoints(wkt); + } + + // input: v1:((35 10, 45 45, 15 40, 10 20, 35 10)) + // v2:((35 10, 45 45, 15 40, 10 20, 35 10),(20 30, 35 35, 30 20, 20 30)) + private static String polygonCoordinatesFromWkt(String wkt) { + wkt = removeBrackets(wkt, 2); + String coordinates; + boolean polygonContainsInnerHoles = wkt.contains("("); + if (polygonContainsInnerHoles) { + String[] polygons = wkt.split("\\s*\\)\\s*,\\s*\\(\\s*"); + String[] coordinatesOfPolygons = new String[polygons.length]; + for (int i = 0; i < polygons.length; i++) { + String polygonCoordinates = getJsonArrayFromListOfPoints(polygons[i]); + coordinatesOfPolygons[i] = polygonCoordinates; + } + coordinates = Joiner.on(",").join(coordinatesOfPolygons); + } else { + coordinates = getJsonArrayFromListOfPoints(wkt); } - - private static String getJsonArrayFromListOfPoints(String pointsInWkt) { - String[] points = pointsInWkt.split(","); - List coordinates = new ArrayList<>(); - for (String point : points) { - coordinates.add(extractCoordinateFromPoint(point)); - } - - String joinedCoordinates = Joiner.on(",").join(coordinates); - return String.format("[%s]", joinedCoordinates); + return String.format("[%s]", coordinates); + } + + private static String getJsonArrayFromListOfPoints(String pointsInWkt) { + String[] points = pointsInWkt.split(","); + List coordinates = new ArrayList<>(); + for (String point : points) { + coordinates.add(extractCoordinateFromPoint(point)); } - private static String buildGeoJson(String type, String coordinates) { - return String.format("{\"type\":\"%s\", \"coordinates\": %s}", type, coordinates); + String joinedCoordinates = Joiner.on(",").join(coordinates); + return String.format("[%s]", joinedCoordinates); + } + + private static String buildGeoJson(String type, String coordinates) { + return String.format("{\"type\":\"%s\", \"coordinates\": %s}", type, coordinates); + } + + // input : (30 10) + public static String pointCoordinatesFromWkt(String wkt) { + wkt = removeBrackets(wkt, 1); + return extractCoordinateFromPoint(wkt); + } + + private static String extractCoordinateFromPoint(String point) { + String pointPattern = "(\\s*)([0-9\\.-]+)(\\s*)([0-9\\.-]+)(\\s*)"; + return point.replaceAll(pointPattern, "[$2,$4]"); + } + + private static String removeBrackets(String wkt, int num) { + String result = wkt; + for (int i = 0; i < num; i++) { + int lastClosingBrackets = result.lastIndexOf(")"); + int firstOpenBrackets = result.indexOf("("); + if (lastClosingBrackets == -1 || firstOpenBrackets == -1) { + throw new IllegalArgumentException("Illegal syntax: " + wkt); + } + result = result.substring(firstOpenBrackets + 1, lastClosingBrackets); } - - //input : (30 10) - public static String pointCoordinatesFromWkt(String wkt) { - wkt = removeBrackets(wkt, 1); - return extractCoordinateFromPoint(wkt); - } - - private static String extractCoordinateFromPoint(String point) { - String pointPattern = "(\\s*)([0-9\\.-]+)(\\s*)([0-9\\.-]+)(\\s*)"; - return point.replaceAll(pointPattern, "[$2,$4]"); - } - - private static String removeBrackets(String wkt, int num) { - String result = wkt; - for (int i = 0; i < num; i++) { - int lastClosingBrackets = result.lastIndexOf(")"); - int firstOpenBrackets = result.indexOf("("); - if (lastClosingBrackets == -1 || firstOpenBrackets == -1) { - throw new IllegalArgumentException("Illegal syntax: " + wkt); - } - result = result.substring(firstOpenBrackets + 1, lastClosingBrackets); - } - return result; - } - + return result; + } } diff --git a/legacy/src/main/java/org/opensearch/sql/legacy/utils/SQLFunctions.java b/legacy/src/main/java/org/opensearch/sql/legacy/utils/SQLFunctions.java index de8e6eb0fa..d46a80f6d3 100644 --- a/legacy/src/main/java/org/opensearch/sql/legacy/utils/SQLFunctions.java +++ b/legacy/src/main/java/org/opensearch/sql/legacy/utils/SQLFunctions.java @@ -3,7 +3,6 @@ * SPDX-License-Identifier: Apache-2.0 */ - package org.opensearch.sql.legacy.utils; import static org.opensearch.sql.legacy.utils.StringUtils.format; @@ -39,1019 +38,1171 @@ import org.opensearch.sql.legacy.exception.SqlParseException; import org.opensearch.sql.legacy.executor.format.Schema; -/** - * Created by allwefantasy on 8/19/16. - */ +/** Created by allwefantasy on 8/19/16. */ public class SQLFunctions { - private static final Set numberOperators = Sets.newHashSet( - "exp", "expm1", "log", "log2", "log10", "ln", "sqrt", "cbrt", "ceil", "floor", "rint", "pow", "power", - "round", "rand", "abs", "sign", "signum" - ); - - private static final Set mathConstants = Sets.newHashSet("e", "pi"); - - private static final Set trigFunctions = Sets.newHashSet( - "degrees", "radians", "sin", "cos", "tan", "asin", "acos", "atan", "atan2", "sinh", "cosh", "cot" - ); - - private static final Set stringOperators = Sets.newHashSet( - "split", "concat_ws", "substring", "trim", "lower", "upper", "rtrim", "ltrim", "replace", - "left", "right" - ); - - private static final Set stringFunctions = Sets.newHashSet( - "length", "locate", "ascii" - ); - - private static final Set binaryOperators = Sets.newHashSet( - "add", "multiply", "divide", "subtract", "modulus" - ); - - private static final Set dateFunctions = Sets.newHashSet( - "date_format", "year", "month_of_year", "week_of_year", "day_of_year", "day_of_month", - "day_of_week", "hour_of_day", "minute_of_day", "minute_of_hour", "second_of_minute", "month", "dayofmonth", - "date", "monthname", "timestamp", "maketime", "now", "curdate" - ); - - private static final Set conditionalFunctions = Sets.newHashSet( - "if", "ifnull", "isnull" - ); - - private static final Set utilityFunctions = Sets.newHashSet("field", "assign", "cast"); - - public static final Set builtInFunctions = Stream.of( - numberOperators, - mathConstants, - trigFunctions, - stringOperators, - stringFunctions, - binaryOperators, - dateFunctions, - conditionalFunctions, - utilityFunctions) - .flatMap(Set::stream).collect(Collectors.toSet()); - - private Map generatedIds = new HashMap<>(); - - /** - * Generates next id for given method name. The id's are increasing for each method name, so - * nextId("a"), nextId("a"), nextId("b") will return a_1, a_2, b_1 - */ - public String nextId(String methodName) { - return methodName + "_" + generatedIds.merge(methodName, 1, Integer::sum); - } - - - /** - * Is the function actually translated into Elastic DSL script during execution? - */ - public static boolean isFunctionTranslatedToScript(String function) { - return builtInFunctions.contains(function.toLowerCase()); - } - - public Tuple function(String methodName, List paramers, String name, - boolean returnValue) throws SqlParseException { - Tuple functionStr = null; - switch (methodName.toLowerCase()) { - case "cast": { - SQLCastExpr castExpr = (SQLCastExpr) ((SQLIdentifierExpr) paramers.get(0).value).getParent(); - String typeName = castExpr.getDataType().getName(); - functionStr = cast(typeName, paramers); - break; - } - case "lower": { - functionStr = lower( - (SQLExpr) paramers.get(0).value, - getLocaleForCaseChangingFunction(paramers), - name - ); - break; - } - case "upper": { - functionStr = upper( - (SQLExpr) paramers.get(0).value, - getLocaleForCaseChangingFunction(paramers), - name); - break; - } - - // Split is currently not supported since its using .split() in painless which is not allow-listed - case "split": - if (paramers.size() == 3) { - functionStr = split((SQLExpr) paramers.get(0).value, - Util.expr2Object((SQLExpr) paramers.get(1).value).toString(), - Integer.parseInt(Util.expr2Object((SQLExpr) paramers.get(2).value).toString()), name); - } else { - functionStr = split((SQLExpr) paramers.get(0).value, - paramers.get(1).value.toString(), - name); - } - - break; - - case "concat_ws": - List result = Lists.newArrayList(); - for (int i = 1; i < paramers.size(); i++) { - result.add((SQLExpr) paramers.get(i).value); - } - functionStr = concat_ws(paramers.get(0).value.toString(), result); - - break; - - - case "date_format": - functionStr = date_format( - (SQLExpr) paramers.get(0).value, - Util.expr2Object((SQLExpr) paramers.get(1).value).toString(), - paramers.size() > 2 ? Util.expr2Object((SQLExpr) paramers.get(2).value).toString() : null, - name); - break; - - case "year": - functionStr = dateFunctionTemplate("year", (SQLExpr) paramers.get(0).value); - break; - case "month_of_year": - case "month": - functionStr = dateFunctionTemplate("monthValue", (SQLExpr) paramers.get(0).value); - break; - case "monthname": - functionStr = dateFunctionTemplate("month", (SQLExpr) paramers.get(0).value); - break; - case "week_of_year": - functionStr = dateFunctionTemplate("weekOfWeekyear", - "get(WeekFields.ISO.weekOfWeekBasedYear())", - (SQLExpr) paramers.get(0).value); - break; - case "day_of_year": - functionStr = dateFunctionTemplate("dayOfYear", (SQLExpr) paramers.get(0).value); - break; - case "day_of_month": - case "dayofmonth": - functionStr = dateFunctionTemplate("dayOfMonth", (SQLExpr) paramers.get(0).value); - break; - case "day_of_week": - functionStr = dateFunctionTemplate("dayOfWeek", - "getDayOfWeekEnum().getValue()", - (SQLExpr) paramers.get(0).value); - break; - case "date": - functionStr = date((SQLExpr) paramers.get(0).value); - break; - case "hour_of_day": - functionStr = dateFunctionTemplate("hour", (SQLExpr) paramers.get(0).value); - break; - case "minute_of_day": - functionStr = dateFunctionTemplate("minuteOfDay", - "get(ChronoField.MINUTE_OF_DAY)", - (SQLExpr) paramers.get(0).value); - break; - case "minute_of_hour": - functionStr = dateFunctionTemplate("minute", (SQLExpr) paramers.get(0).value); - break; - case "second_of_minute": - functionStr = dateFunctionTemplate("second", (SQLExpr) paramers.get(0).value); - break; - case "timestamp": - functionStr = timestamp((SQLExpr) paramers.get(0).value); - break; - case "maketime": - functionStr = maketime((SQLExpr) paramers.get(0).value, (SQLExpr) paramers.get(1).value, - (SQLExpr) paramers.get(2).value); - break; - case "now": - functionStr = now(); - break; - case "curdate": - functionStr = curdate(); - break; - - case "e": - case "pi": - methodName = methodName.toUpperCase(); - functionStr = mathConstantTemplate("Math." + methodName, methodName); - break; - - case "abs": - case "round": - case "floor": - case "ceil": - case "cbrt": - case "rint": - case "exp": - case "expm1": - case "sqrt": - case "sin": - case "cos": - case "tan": - case "asin": - case "acos": - case "atan": - case "sinh": - case "cosh": - functionStr = mathSingleValueTemplate("Math." + methodName, methodName, - (SQLExpr) paramers.get(0).value, name); - break; - - case "rand": - if (paramers.isEmpty()) { - functionStr = rand(); - } else { - functionStr = rand((SQLExpr) paramers.get(0).value); - } - break; - - case "cot": - // OpenSearch does not support the function name cot - functionStr = mathSingleValueTemplate("1 / Math.tan", methodName, - (SQLExpr) paramers.get(0).value, name); - break; - - case "sign": - case "signum": - methodName = "signum"; - functionStr = mathSingleValueTemplate("Math." + methodName, methodName, - (SQLExpr) paramers.get(0).value, name); - break; - - case "pow": - case "power": - methodName = "pow"; - functionStr = mathDoubleValueTemplate("Math." + methodName, methodName, - (SQLExpr) paramers.get(0).value, Util.expr2Object((SQLExpr) paramers.get(1).value).toString(), - name); - break; - - case "atan2": - functionStr = mathDoubleValueTemplate("Math." + methodName, methodName, - (SQLExpr) paramers.get(0).value, (SQLExpr) paramers.get(1).value); - break; - - case "substring": - functionStr = substring((SQLExpr) paramers.get(0).value, - Integer.parseInt(Util.expr2Object((SQLExpr) paramers.get(1).value).toString()), - Integer.parseInt(Util.expr2Object((SQLExpr) paramers.get(2).value).toString())); - break; - - case "degrees": - functionStr = degrees((SQLExpr) paramers.get(0).value, name); - break; - case "radians": - functionStr = radians((SQLExpr) paramers.get(0).value, name); - break; - - case "trim": - functionStr = trim((SQLExpr) paramers.get(0).value, name); - break; - - case "add": - functionStr = add((SQLExpr) paramers.get(0).value, (SQLExpr) paramers.get(1).value); - break; - - case "subtract": - functionStr = subtract((SQLExpr) paramers.get(0).value, (SQLExpr) paramers.get(1).value); - break; - case "divide": - functionStr = divide((SQLExpr) paramers.get(0).value, (SQLExpr) paramers.get(1).value); - break; - - case "multiply": - functionStr = multiply((SQLExpr) paramers.get(0).value, (SQLExpr) paramers.get(1).value); - break; - case "modulus": - functionStr = modulus((SQLExpr) paramers.get(0).value, (SQLExpr) paramers.get(1).value); - break; - - case "field": - functionStr = field(Util.expr2Object((SQLExpr) paramers.get(0).value).toString()); - break; - - case "log2": - functionStr = log(SQLUtils.toSQLExpr("2"), (SQLExpr) paramers.get(0).value, name); - break; - case "log10": - functionStr = log10((SQLExpr) paramers.get(0).value); - break; - case "log": - if (paramers.size() > 1) { - functionStr = log((SQLExpr) paramers.get(0).value, (SQLExpr) paramers.get(1).value, name); - } else { - functionStr = ln((SQLExpr) paramers.get(0).value); - } - break; - case "ln": - functionStr = ln((SQLExpr) paramers.get(0).value); - break; - case "assign": - functionStr = assign((SQLExpr) paramers.get(0).value); - break; - case "length": - functionStr = length((SQLExpr) paramers.get(0).value); - break; - case "replace": - functionStr = replace((SQLExpr) paramers.get(0).value, paramers.get(1).value.toString(), - paramers.get(2).value.toString()); - break; - case "locate": - int start = 0; - if (paramers.size() > 2) { - start = Integer.parseInt(paramers.get(2).value.toString()); - } - functionStr = locate(paramers.get(0).value.toString(), (SQLExpr) paramers.get(1).value, start); - break; - case "rtrim": - functionStr = rtrim((SQLExpr) paramers.get(0).value); - break; - case "ltrim": - functionStr = ltrim((SQLExpr) paramers.get(0).value); - break; - case "ascii": - functionStr = ascii((SQLExpr) paramers.get(0).value); - break; - case "left": - functionStr = left((SQLExpr) paramers.get(0).value, (SQLExpr) paramers.get(1).value); - break; - case "right": - functionStr = right((SQLExpr) paramers.get(0).value, (SQLExpr) paramers.get(1).value); - break; - - case "if": - functionStr = ifFunc(paramers); - break; - case "ifnull": - functionStr = ifnull((SQLExpr) paramers.get(0).value, (SQLExpr) paramers.get(1).value); - break; - case "isnull": - functionStr = isnull((SQLExpr) paramers.get(0).value); - break; - - default: - - } - if (returnValue) { - String generatedFieldName = functionStr.v1(); - String returnCommand = ";return " + generatedFieldName + ";"; - String newScript = functionStr.v2() + returnCommand; - functionStr = new Tuple<>(generatedFieldName, newScript); - } - return functionStr; - } - - public String getLocaleForCaseChangingFunction(List paramers) { - String locale; - if (paramers.size() == 1) { - locale = Locale.getDefault().getLanguage(); - } else { - locale = Util.expr2Object((SQLExpr) paramers.get(1).value).toString(); + private static final Set numberOperators = + Sets.newHashSet( + "exp", "expm1", "log", "log2", "log10", "ln", "sqrt", "cbrt", "ceil", "floor", "rint", + "pow", "power", "round", "rand", "abs", "sign", "signum"); + + private static final Set mathConstants = Sets.newHashSet("e", "pi"); + + private static final Set trigFunctions = + Sets.newHashSet( + "degrees", "radians", "sin", "cos", "tan", "asin", "acos", "atan", "atan2", "sinh", + "cosh", "cot"); + + private static final Set stringOperators = + Sets.newHashSet( + "split", + "concat_ws", + "substring", + "trim", + "lower", + "upper", + "rtrim", + "ltrim", + "replace", + "left", + "right"); + + private static final Set stringFunctions = Sets.newHashSet("length", "locate", "ascii"); + + private static final Set binaryOperators = + Sets.newHashSet("add", "multiply", "divide", "subtract", "modulus"); + + private static final Set dateFunctions = + Sets.newHashSet( + "date_format", + "year", + "month_of_year", + "week_of_year", + "day_of_year", + "day_of_month", + "day_of_week", + "hour_of_day", + "minute_of_day", + "minute_of_hour", + "second_of_minute", + "month", + "dayofmonth", + "date", + "monthname", + "timestamp", + "maketime", + "now", + "curdate"); + + private static final Set conditionalFunctions = Sets.newHashSet("if", "ifnull", "isnull"); + + private static final Set utilityFunctions = Sets.newHashSet("field", "assign", "cast"); + + public static final Set builtInFunctions = + Stream.of( + numberOperators, + mathConstants, + trigFunctions, + stringOperators, + stringFunctions, + binaryOperators, + dateFunctions, + conditionalFunctions, + utilityFunctions) + .flatMap(Set::stream) + .collect(Collectors.toSet()); + + private Map generatedIds = new HashMap<>(); + + /** + * Generates next id for given method name. The id's are increasing for each method name, so + * nextId("a"), nextId("a"), nextId("b") will return a_1, a_2, b_1 + */ + public String nextId(String methodName) { + return methodName + "_" + generatedIds.merge(methodName, 1, Integer::sum); + } + + /** Is the function actually translated into Elastic DSL script during execution? */ + public static boolean isFunctionTranslatedToScript(String function) { + return builtInFunctions.contains(function.toLowerCase()); + } + + public Tuple function( + String methodName, List paramers, String name, boolean returnValue) + throws SqlParseException { + Tuple functionStr = null; + switch (methodName.toLowerCase()) { + case "cast": + { + SQLCastExpr castExpr = + (SQLCastExpr) ((SQLIdentifierExpr) paramers.get(0).value).getParent(); + String typeName = castExpr.getDataType().getName(); + functionStr = cast(typeName, paramers); + break; } - return locale; - } - - public Tuple cast(String castType, List paramers) throws SqlParseException { - String name = nextId("cast"); - return new Tuple<>(name, getCastScriptStatement(name, castType, paramers)); - } - - - public Tuple upper(SQLExpr field, String locale, String valueName) { - String name = nextId("upper"); - - if (valueName == null) { - return new Tuple<>(name, def(name, upper(getPropertyOrStringValue(field), locale))); - } else { - return new Tuple<>(name, getPropertyOrStringValue(field) + "; " - + def(name, valueName + "." + upper(getPropertyOrStringValue(field), locale))); + case "lower": + { + functionStr = + lower( + (SQLExpr) paramers.get(0).value, + getLocaleForCaseChangingFunction(paramers), + name); + break; } - } - - public Tuple lower(SQLExpr field, String locale, String valueName) { - String name = nextId("lower"); - - if (valueName == null) { - return new Tuple<>(name, def(name, lower(getPropertyOrStringValue(field), locale))); - } else { - return new Tuple<>(name, getPropertyOrStringValue(field) + "; " - + def(name, valueName + "." + lower(getPropertyOrStringValue(field), locale))); + case "upper": + { + functionStr = + upper( + (SQLExpr) paramers.get(0).value, + getLocaleForCaseChangingFunction(paramers), + name); + break; } - } - - private static String def(String name, String value) { - return "def " + name + " = " + value; - } - - private static String doc(SQLExpr field) { - return "doc['" + exprString(field) + "']"; - } - - private static String doc(String field) { - return "doc['" + field + "']"; - } - - private static String exprString(SQLExpr expr) { - return Util.expr2Object(expr).toString(); - } - - private static String func(String methodName, boolean quotes, String... params) { - if (quotes) { - return methodName + "(" + quoteParams(params) + ")"; - } - - return methodName + "(" + String.join(", ", params) + ")"; - } - - /** - * Helper method to surround each param with '' (single quotes) for painless script - */ - private static String quoteParams(String... params) { - return Stream.of(params).collect(Collectors.joining("', '", "'", "'")); - } - - private Tuple concat_ws(String split, List columns) { - String name = nextId("concat_ws"); - List result = Lists.newArrayList(); - - for (SQLExpr column : columns) { - String strColumn = exprString(column); - if (strColumn.startsWith("def ")) { - result.add(strColumn); - } else if (isProperty(column)) { - result.add("doc['" + strColumn + "'].value"); - } else { - result.add("'" + strColumn + "'"); - } - - } - return new Tuple<>(name, def(name, Joiner.on("+ " + split + " +").join(result))); - } - - - //split(Column expr, java.lang.String pattern) - public Tuple split(SQLExpr field, String pattern, int index, String valueName) { - String name = nextId("split"); - final String script; - if (valueName == null) { - script = def(name, - getPropertyOrValue(field) + "." - + func("split", true, pattern) + "[" + index + "]"); - } else { - script = "; " + def(name, valueName + "." - + func("split", true, pattern) + "[" + index + "]"); - } - return new Tuple<>(name, script); - } - - //split(Column expr, java.lang.String pattern) - public Tuple split(SQLExpr field, String pattern, String valueName) { - String name = nextId("split"); - if (valueName == null) { - return new Tuple<>(name, - def(name, getPropertyOrValue(field) + "." - + func("split", true, pattern))); - } else { - return new Tuple<>(name, getPropertyOrValue(field) + "; " - + def(name, valueName + "." + func("split", true, pattern))); - } - } - - private Tuple date_format(SQLExpr field, String pattern, String zoneId, String valueName) { - String name = nextId("date_format"); - if (valueName == null) { - return new Tuple<>(name, "def " + name + " = DateTimeFormatter.ofPattern('" + pattern + "').withZone(" - + (zoneId != null ? "ZoneId.of('" + zoneId + "')" : "ZoneId.of(\"UTC\")") - + ").format(Instant.ofEpochMilli(" + getPropertyOrValue(field) + ".toInstant().toEpochMilli()))"); - } else { - return new Tuple<>(name, exprString(field) + "; " - + "def " + name + " = new SimpleDateFormat('" + pattern + "').format(" - + "new Date(" + valueName + " - 8*1000*60*60))"); - } - } - - /** - * Explicitly pass in name used to generate variable ID because methodName is not always valid - * - * For example, - * - * functionStr = dateFunctionTemplate("weekOfWeekyear", - * "get(WeekFields.ISO.weekOfWeekBasedYear())", - * (SQLExpr) paramers.get(0).value); - * - * - * The old dateFunctionTemplate(methodName, field) passes string "get(WeekFields.ISO.weekOfWeekBasedYear())" - * to nextId() which generates an invalid variable name in painless script. - */ - private Tuple dateFunctionTemplate(String name, String methodName, SQLExpr field) { - String id = nextId(name); - return new Tuple<>(id, def(id, doc(field) + ".value." + methodName)); - } - - private Tuple dateFunctionTemplate(String methodName, SQLExpr field) { - return dateFunctionTemplate(methodName, methodName, field); - } - - public Tuple add(SQLExpr a, SQLExpr b) { - return binaryOpertator("add", "+", a, b); - } - - public Tuple assign(SQLExpr a) { - String name = nextId("assign"); - return new Tuple<>(name, - def(name, extractName(a))); - } - - private Tuple modulus(SQLExpr a, SQLExpr b) { - return binaryOpertator("modulus", "%", a, b); - } - - public Tuple field(String a) { - String name = nextId("field"); - return new Tuple<>(name, def(name, doc(a) + ".value")); - } - - private Tuple subtract(SQLExpr a, SQLExpr b) { - return binaryOpertator("subtract", "-", a, b); - } - - private Tuple multiply(SQLExpr a, SQLExpr b) { - return binaryOpertator("multiply", "*", a, b); - } - - private Tuple divide(SQLExpr a, SQLExpr b) { - return binaryOpertator("divide", "/", a, b); - } - - private Tuple binaryOpertator(String methodName, String operator, SQLExpr a, SQLExpr b) { - String name = nextId(methodName); - return new Tuple<>(name, - scriptDeclare(a) + scriptDeclare(b) + convertType(a) + convertType(b) - + def(name, extractName(a) + " " + operator + " " + extractName(b))); - } - - private static boolean isProperty(SQLExpr expr) { - return (expr instanceof SQLIdentifierExpr || expr instanceof SQLPropertyExpr - || expr instanceof SQLVariantRefExpr); - } - - private static String getPropertyOrValue(SQLExpr expr) { - if (isProperty(expr)) { - return doc(expr) + ".value"; - } else { - return exprString(expr); - } - } - - private static String getPropertyOrValue(String expr) { - if (isQuoted(expr, "'")) { - return expr; - } else if (StringUtils.isNumeric(expr)) { - return expr; - } else { - return doc(expr) + ".value"; - } - } - - private static String getPropertyOrStringValue(SQLExpr expr) { - if (isProperty(expr)) { - return doc(expr) + ".value"; - } else { - return "'" + exprString(expr) + "'"; - } - } - - private static String scriptDeclare(SQLExpr a) { - - if (isProperty(a) || a instanceof SQLNumericLiteralExpr) { - return ""; - } else { - return exprString(a) + ";"; - } - } - - private static String extractName(SQLExpr script) { - if (isProperty(script)) { - return doc(script) + ".value"; - } - String scriptStr = exprString(script); - String[] variance = scriptStr.split(";"); - String newScript = variance[variance.length - 1]; - if (newScript.trim().startsWith("def ")) { - //for now ,if variant is string,then change to double. - return newScript.trim().substring(4).split("=")[0].trim(); - } else { - return scriptStr; - } - } - //cast(year as int) - - private static String convertType(SQLExpr script) { - String[] variance = exprString(script).split(";"); - String newScript = variance[variance.length - 1]; - if (newScript.trim().startsWith("def ")) { - //for now ,if variant is string,then change to double. - String temp = newScript.trim().substring(4).split("=")[0].trim(); - - return " if( " + temp + " instanceof String) " + temp + "= Double.parseDouble(" + temp.trim() + "); "; + // Split is currently not supported since its using .split() in painless which is not + // allow-listed + case "split": + if (paramers.size() == 3) { + functionStr = + split( + (SQLExpr) paramers.get(0).value, + Util.expr2Object((SQLExpr) paramers.get(1).value).toString(), + Integer.parseInt(Util.expr2Object((SQLExpr) paramers.get(2).value).toString()), + name); } else { - return ""; + functionStr = + split((SQLExpr) paramers.get(0).value, paramers.get(1).value.toString(), name); } + break; - } - - private String getScriptText(MethodField field) { - String content = ((SQLTextLiteralExpr) field.getParams().get(1).value).getText(); - return content; - } - - /** - * Using exprString() rather than getPropertyOrValue() for "base" since something like "Math.E" gets evaluated - * incorrectly in getPropertyOrValue(), returning it as a doc value instead of the literal string - */ - public Tuple log(SQLExpr base, SQLExpr field, String valueName) { - String name = nextId("log"); - String result; - if (valueName == null) { - result = def(name, func("Math.log", false, getPropertyOrValue(field)) - + "/" + func("Math.log", false, exprString(base))); - } else { - result = getPropertyOrValue(field) + "; " - + def(name, func("Math.log", false, valueName) + "/" - + func("Math.log", false, exprString(base))); + case "concat_ws": + List result = Lists.newArrayList(); + for (int i = 1; i < paramers.size(); i++) { + result.add((SQLExpr) paramers.get(i).value); } - return new Tuple<>(name, result); - } - - public Tuple log10(SQLExpr field) { - String name = nextId("log10"); - return new Tuple<>(name, def(name, StringUtils.format("Math.log10(%s)", getPropertyOrValue(field)))); - } - - public Tuple ln(SQLExpr field) { - String name = nextId("ln"); - return new Tuple<>(name, def(name, StringUtils.format("Math.log(%s)", getPropertyOrValue(field)))); - } - - public Tuple trim(SQLExpr field, String valueName) { - return strSingleValueTemplate("trim", field, valueName); - } - - private Tuple degrees(SQLExpr field, String valueName) { - return mathSingleValueTemplate("Math.toDegrees", "degrees", field, valueName); - } - - private Tuple radians(SQLExpr field, String valueName) { - return mathSingleValueTemplate("Math.toRadians", "radians", field, valueName); - } - - private Tuple rand(SQLExpr expr) { - String name = nextId("rand"); - return new Tuple<>(name, def(name, format("new Random(%s).nextDouble()", getPropertyOrValue(expr)))); - } - - private Tuple rand() { - String name = nextId("rand"); - return new Tuple<>(name, def(name, "new Random().nextDouble()")); - } - - private Tuple mathDoubleValueTemplate(String methodName, String fieldName, SQLExpr val1, - String val2, String valueName) { - String name = nextId(fieldName); - if (valueName == null) { - return new Tuple<>(name, def(name, func(methodName, false, getPropertyOrValue(val1), - getPropertyOrValue(val2)))); + functionStr = concat_ws(paramers.get(0).value.toString(), result); + + break; + + case "date_format": + functionStr = + date_format( + (SQLExpr) paramers.get(0).value, + Util.expr2Object((SQLExpr) paramers.get(1).value).toString(), + paramers.size() > 2 + ? Util.expr2Object((SQLExpr) paramers.get(2).value).toString() + : null, + name); + break; + + case "year": + functionStr = dateFunctionTemplate("year", (SQLExpr) paramers.get(0).value); + break; + case "month_of_year": + case "month": + functionStr = dateFunctionTemplate("monthValue", (SQLExpr) paramers.get(0).value); + break; + case "monthname": + functionStr = dateFunctionTemplate("month", (SQLExpr) paramers.get(0).value); + break; + case "week_of_year": + functionStr = + dateFunctionTemplate( + "weekOfWeekyear", + "get(WeekFields.ISO.weekOfWeekBasedYear())", + (SQLExpr) paramers.get(0).value); + break; + case "day_of_year": + functionStr = dateFunctionTemplate("dayOfYear", (SQLExpr) paramers.get(0).value); + break; + case "day_of_month": + case "dayofmonth": + functionStr = dateFunctionTemplate("dayOfMonth", (SQLExpr) paramers.get(0).value); + break; + case "day_of_week": + functionStr = + dateFunctionTemplate( + "dayOfWeek", "getDayOfWeekEnum().getValue()", (SQLExpr) paramers.get(0).value); + break; + case "date": + functionStr = date((SQLExpr) paramers.get(0).value); + break; + case "hour_of_day": + functionStr = dateFunctionTemplate("hour", (SQLExpr) paramers.get(0).value); + break; + case "minute_of_day": + functionStr = + dateFunctionTemplate( + "minuteOfDay", "get(ChronoField.MINUTE_OF_DAY)", (SQLExpr) paramers.get(0).value); + break; + case "minute_of_hour": + functionStr = dateFunctionTemplate("minute", (SQLExpr) paramers.get(0).value); + break; + case "second_of_minute": + functionStr = dateFunctionTemplate("second", (SQLExpr) paramers.get(0).value); + break; + case "timestamp": + functionStr = timestamp((SQLExpr) paramers.get(0).value); + break; + case "maketime": + functionStr = + maketime( + (SQLExpr) paramers.get(0).value, + (SQLExpr) paramers.get(1).value, + (SQLExpr) paramers.get(2).value); + break; + case "now": + functionStr = now(); + break; + case "curdate": + functionStr = curdate(); + break; + + case "e": + case "pi": + methodName = methodName.toUpperCase(); + functionStr = mathConstantTemplate("Math." + methodName, methodName); + break; + + case "abs": + case "round": + case "floor": + case "ceil": + case "cbrt": + case "rint": + case "exp": + case "expm1": + case "sqrt": + case "sin": + case "cos": + case "tan": + case "asin": + case "acos": + case "atan": + case "sinh": + case "cosh": + functionStr = + mathSingleValueTemplate( + "Math." + methodName, methodName, (SQLExpr) paramers.get(0).value, name); + break; + + case "rand": + if (paramers.isEmpty()) { + functionStr = rand(); } else { - return new Tuple<>(name, getPropertyOrValue(val1) + "; " - + def(name, func(methodName, false, valueName, getPropertyOrValue(val2)))); + functionStr = rand((SQLExpr) paramers.get(0).value); } - } - - private Tuple mathDoubleValueTemplate(String methodName, String fieldName, SQLExpr val1, - SQLExpr val2) { - String name = nextId(fieldName); - return new Tuple<>(name, def(name, func(methodName, false, - getPropertyOrValue(val1), getPropertyOrValue(val2)))); - } - - private Tuple mathSingleValueTemplate(String methodName, String fieldName, SQLExpr field, - String valueName) { - String name = nextId(fieldName); - if (valueName == null) { - return new Tuple<>(name, def(name, func(methodName, false, getPropertyOrValue(field)))); + break; + + case "cot": + // OpenSearch does not support the function name cot + functionStr = + mathSingleValueTemplate( + "1 / Math.tan", methodName, (SQLExpr) paramers.get(0).value, name); + break; + + case "sign": + case "signum": + methodName = "signum"; + functionStr = + mathSingleValueTemplate( + "Math." + methodName, methodName, (SQLExpr) paramers.get(0).value, name); + break; + + case "pow": + case "power": + methodName = "pow"; + functionStr = + mathDoubleValueTemplate( + "Math." + methodName, + methodName, + (SQLExpr) paramers.get(0).value, + Util.expr2Object((SQLExpr) paramers.get(1).value).toString(), + name); + break; + + case "atan2": + functionStr = + mathDoubleValueTemplate( + "Math." + methodName, + methodName, + (SQLExpr) paramers.get(0).value, + (SQLExpr) paramers.get(1).value); + break; + + case "substring": + functionStr = + substring( + (SQLExpr) paramers.get(0).value, + Integer.parseInt(Util.expr2Object((SQLExpr) paramers.get(1).value).toString()), + Integer.parseInt(Util.expr2Object((SQLExpr) paramers.get(2).value).toString())); + break; + + case "degrees": + functionStr = degrees((SQLExpr) paramers.get(0).value, name); + break; + case "radians": + functionStr = radians((SQLExpr) paramers.get(0).value, name); + break; + + case "trim": + functionStr = trim((SQLExpr) paramers.get(0).value, name); + break; + + case "add": + functionStr = add((SQLExpr) paramers.get(0).value, (SQLExpr) paramers.get(1).value); + break; + + case "subtract": + functionStr = subtract((SQLExpr) paramers.get(0).value, (SQLExpr) paramers.get(1).value); + break; + case "divide": + functionStr = divide((SQLExpr) paramers.get(0).value, (SQLExpr) paramers.get(1).value); + break; + + case "multiply": + functionStr = multiply((SQLExpr) paramers.get(0).value, (SQLExpr) paramers.get(1).value); + break; + case "modulus": + functionStr = modulus((SQLExpr) paramers.get(0).value, (SQLExpr) paramers.get(1).value); + break; + + case "field": + functionStr = field(Util.expr2Object((SQLExpr) paramers.get(0).value).toString()); + break; + + case "log2": + functionStr = log(SQLUtils.toSQLExpr("2"), (SQLExpr) paramers.get(0).value, name); + break; + case "log10": + functionStr = log10((SQLExpr) paramers.get(0).value); + break; + case "log": + if (paramers.size() > 1) { + functionStr = log((SQLExpr) paramers.get(0).value, (SQLExpr) paramers.get(1).value, name); } else { - return new Tuple<>(name, getPropertyOrValue(field) + "; " - + def(name, func(methodName, false, valueName))); + functionStr = ln((SQLExpr) paramers.get(0).value); } - - } - - private Tuple mathConstantTemplate(String methodName, String fieldName) { - String name = nextId(fieldName); - return new Tuple<>(name, def(name, methodName)); - } - - private Tuple strSingleValueTemplate(String methodName, SQLExpr field, String valueName) { - String name = nextId(methodName); - if (valueName == null) { - return new Tuple<>(name, def(name, getPropertyOrStringValue(field) + "." + func(methodName, false))); - } else { - return new Tuple<>(name, getPropertyOrStringValue(field) + "; " - + def(name, valueName + "." + func(methodName, false))); + break; + case "ln": + functionStr = ln((SQLExpr) paramers.get(0).value); + break; + case "assign": + functionStr = assign((SQLExpr) paramers.get(0).value); + break; + case "length": + functionStr = length((SQLExpr) paramers.get(0).value); + break; + case "replace": + functionStr = + replace( + (SQLExpr) paramers.get(0).value, + paramers.get(1).value.toString(), + paramers.get(2).value.toString()); + break; + case "locate": + int start = 0; + if (paramers.size() > 2) { + start = Integer.parseInt(paramers.get(2).value.toString()); } - - } - - // query: substring(Column expr, int pos, int len) - // painless script: substring(int begin, int end) - // OpenSearch behavior: 1-index, supports out-of-bound index - public Tuple substring(SQLExpr field, int pos, int len) { - String name = nextId("substring"); - // start and end are 0-indexes - int start = pos < 1 ? 0 : pos - 1; - return new Tuple<>(name, StringUtils.format( - "def end = (int) Math.min(%s + %s, %s.length()); " - + def(name, getPropertyOrStringValue(field) + "." - + func("substring", false, Integer.toString(start), "end")), - Integer.toString(start), Integer.toString(len), getPropertyOrStringValue(field) - )); - } - - private String lower(String property, String culture) { - return property + ".toLowerCase(Locale.forLanguageTag(\"" + culture + "\"))"; - } - - private String upper(String property, String culture) { - return property + ".toUpperCase(Locale.forLanguageTag(\"" + culture + "\"))"; - } - - private Tuple length(SQLExpr field) { - String name = nextId("length"); - return new Tuple<>(name, def(name, getPropertyOrStringValue(field) + ".length()")); - } - - private Tuple replace(SQLExpr field, String target, String replacement) { - String name = nextId("replace"); - return new Tuple<>(name, def(name, getPropertyOrStringValue(field) - + ".replace(" + target + "," + replacement + ")")); - } - - // OpenSearch behavior: both 'start' and return value are 1-index; return 0 if pattern does not exist; - // support out-of-bound index - private Tuple locate(String pattern, SQLExpr source, int start) { - String name = nextId("locate"); - String docSource = getPropertyOrStringValue(source); - start = start < 1 ? 0 : start - 1; - return new Tuple<>(name, def(name, StringUtils.format("%s.indexOf(%s,%d)+1", docSource, pattern, start))); - } - - private Tuple rtrim(SQLExpr field) { - String name = nextId("rtrim"); - String fieldString = getPropertyOrStringValue(field); - return new Tuple<>(name, StringUtils.format( - "int pos=%s.length()-1;" + functionStr = + locate(paramers.get(0).value.toString(), (SQLExpr) paramers.get(1).value, start); + break; + case "rtrim": + functionStr = rtrim((SQLExpr) paramers.get(0).value); + break; + case "ltrim": + functionStr = ltrim((SQLExpr) paramers.get(0).value); + break; + case "ascii": + functionStr = ascii((SQLExpr) paramers.get(0).value); + break; + case "left": + functionStr = left((SQLExpr) paramers.get(0).value, (SQLExpr) paramers.get(1).value); + break; + case "right": + functionStr = right((SQLExpr) paramers.get(0).value, (SQLExpr) paramers.get(1).value); + break; + + case "if": + functionStr = ifFunc(paramers); + break; + case "ifnull": + functionStr = ifnull((SQLExpr) paramers.get(0).value, (SQLExpr) paramers.get(1).value); + break; + case "isnull": + functionStr = isnull((SQLExpr) paramers.get(0).value); + break; + + default: + } + if (returnValue) { + String generatedFieldName = functionStr.v1(); + String returnCommand = ";return " + generatedFieldName + ";"; + String newScript = functionStr.v2() + returnCommand; + functionStr = new Tuple<>(generatedFieldName, newScript); + } + return functionStr; + } + + public String getLocaleForCaseChangingFunction(List paramers) { + String locale; + if (paramers.size() == 1) { + locale = Locale.getDefault().getLanguage(); + } else { + locale = Util.expr2Object((SQLExpr) paramers.get(1).value).toString(); + } + return locale; + } + + public Tuple cast(String castType, List paramers) + throws SqlParseException { + String name = nextId("cast"); + return new Tuple<>(name, getCastScriptStatement(name, castType, paramers)); + } + + public Tuple upper(SQLExpr field, String locale, String valueName) { + String name = nextId("upper"); + + if (valueName == null) { + return new Tuple<>(name, def(name, upper(getPropertyOrStringValue(field), locale))); + } else { + return new Tuple<>( + name, + getPropertyOrStringValue(field) + + "; " + + def(name, valueName + "." + upper(getPropertyOrStringValue(field), locale))); + } + } + + public Tuple lower(SQLExpr field, String locale, String valueName) { + String name = nextId("lower"); + + if (valueName == null) { + return new Tuple<>(name, def(name, lower(getPropertyOrStringValue(field), locale))); + } else { + return new Tuple<>( + name, + getPropertyOrStringValue(field) + + "; " + + def(name, valueName + "." + lower(getPropertyOrStringValue(field), locale))); + } + } + + private static String def(String name, String value) { + return "def " + name + " = " + value; + } + + private static String doc(SQLExpr field) { + return "doc['" + exprString(field) + "']"; + } + + private static String doc(String field) { + return "doc['" + field + "']"; + } + + private static String exprString(SQLExpr expr) { + return Util.expr2Object(expr).toString(); + } + + private static String func(String methodName, boolean quotes, String... params) { + if (quotes) { + return methodName + "(" + quoteParams(params) + ")"; + } + + return methodName + "(" + String.join(", ", params) + ")"; + } + + /** Helper method to surround each param with '' (single quotes) for painless script */ + private static String quoteParams(String... params) { + return Stream.of(params).collect(Collectors.joining("', '", "'", "'")); + } + + private Tuple concat_ws(String split, List columns) { + String name = nextId("concat_ws"); + List result = Lists.newArrayList(); + + for (SQLExpr column : columns) { + String strColumn = exprString(column); + if (strColumn.startsWith("def ")) { + result.add(strColumn); + } else if (isProperty(column)) { + result.add("doc['" + strColumn + "'].value"); + } else { + result.add("'" + strColumn + "'"); + } + } + return new Tuple<>(name, def(name, Joiner.on("+ " + split + " +").join(result))); + } + + // split(Column expr, java.lang.String pattern) + public Tuple split(SQLExpr field, String pattern, int index, String valueName) { + String name = nextId("split"); + final String script; + if (valueName == null) { + script = + def( + name, + getPropertyOrValue(field) + "." + func("split", true, pattern) + "[" + index + "]"); + } else { + script = "; " + def(name, valueName + "." + func("split", true, pattern) + "[" + index + "]"); + } + return new Tuple<>(name, script); + } + + // split(Column expr, java.lang.String pattern) + public Tuple split(SQLExpr field, String pattern, String valueName) { + String name = nextId("split"); + if (valueName == null) { + return new Tuple<>( + name, def(name, getPropertyOrValue(field) + "." + func("split", true, pattern))); + } else { + return new Tuple<>( + name, + getPropertyOrValue(field) + + "; " + + def(name, valueName + "." + func("split", true, pattern))); + } + } + + private Tuple date_format( + SQLExpr field, String pattern, String zoneId, String valueName) { + String name = nextId("date_format"); + if (valueName == null) { + return new Tuple<>( + name, + "def " + + name + + " = DateTimeFormatter.ofPattern('" + + pattern + + "').withZone(" + + (zoneId != null ? "ZoneId.of('" + zoneId + "')" : "ZoneId.of(\"UTC\")") + + ").format(Instant.ofEpochMilli(" + + getPropertyOrValue(field) + + ".toInstant().toEpochMilli()))"); + } else { + return new Tuple<>( + name, + exprString(field) + + "; " + + "def " + + name + + " = new SimpleDateFormat('" + + pattern + + "').format(" + + "new Date(" + + valueName + + " - 8*1000*60*60))"); + } + } + + /** + * Explicitly pass in name used to generate variable ID because methodName is not always valid + * + *

    For example, + * functionStr = dateFunctionTemplate("weekOfWeekyear", + * "get(WeekFields.ISO.weekOfWeekBasedYear())", + * (SQLExpr) paramers.get(0).value); + * The old dateFunctionTemplate(methodName, field) passes string + * "get(WeekFields.ISO.weekOfWeekBasedYear())" to nextId() which generates an invalid variable + * name in painless script. + */ + private Tuple dateFunctionTemplate( + String name, String methodName, SQLExpr field) { + String id = nextId(name); + return new Tuple<>(id, def(id, doc(field) + ".value." + methodName)); + } + + private Tuple dateFunctionTemplate(String methodName, SQLExpr field) { + return dateFunctionTemplate(methodName, methodName, field); + } + + public Tuple add(SQLExpr a, SQLExpr b) { + return binaryOpertator("add", "+", a, b); + } + + public Tuple assign(SQLExpr a) { + String name = nextId("assign"); + return new Tuple<>(name, def(name, extractName(a))); + } + + private Tuple modulus(SQLExpr a, SQLExpr b) { + return binaryOpertator("modulus", "%", a, b); + } + + public Tuple field(String a) { + String name = nextId("field"); + return new Tuple<>(name, def(name, doc(a) + ".value")); + } + + private Tuple subtract(SQLExpr a, SQLExpr b) { + return binaryOpertator("subtract", "-", a, b); + } + + private Tuple multiply(SQLExpr a, SQLExpr b) { + return binaryOpertator("multiply", "*", a, b); + } + + private Tuple divide(SQLExpr a, SQLExpr b) { + return binaryOpertator("divide", "/", a, b); + } + + private Tuple binaryOpertator( + String methodName, String operator, SQLExpr a, SQLExpr b) { + String name = nextId(methodName); + return new Tuple<>( + name, + scriptDeclare(a) + + scriptDeclare(b) + + convertType(a) + + convertType(b) + + def(name, extractName(a) + " " + operator + " " + extractName(b))); + } + + private static boolean isProperty(SQLExpr expr) { + return (expr instanceof SQLIdentifierExpr + || expr instanceof SQLPropertyExpr + || expr instanceof SQLVariantRefExpr); + } + + private static String getPropertyOrValue(SQLExpr expr) { + if (isProperty(expr)) { + return doc(expr) + ".value"; + } else { + return exprString(expr); + } + } + + private static String getPropertyOrValue(String expr) { + if (isQuoted(expr, "'")) { + return expr; + } else if (StringUtils.isNumeric(expr)) { + return expr; + } else { + return doc(expr) + ".value"; + } + } + + private static String getPropertyOrStringValue(SQLExpr expr) { + if (isProperty(expr)) { + return doc(expr) + ".value"; + } else { + return "'" + exprString(expr) + "'"; + } + } + + private static String scriptDeclare(SQLExpr a) { + + if (isProperty(a) || a instanceof SQLNumericLiteralExpr) { + return ""; + } else { + return exprString(a) + ";"; + } + } + + private static String extractName(SQLExpr script) { + if (isProperty(script)) { + return doc(script) + ".value"; + } + String scriptStr = exprString(script); + String[] variance = scriptStr.split(";"); + String newScript = variance[variance.length - 1]; + if (newScript.trim().startsWith("def ")) { + // for now ,if variant is string,then change to double. + return newScript.trim().substring(4).split("=")[0].trim(); + } else { + return scriptStr; + } + } + + // cast(year as int) + + private static String convertType(SQLExpr script) { + String[] variance = exprString(script).split(";"); + String newScript = variance[variance.length - 1]; + if (newScript.trim().startsWith("def ")) { + // for now ,if variant is string,then change to double. + String temp = newScript.trim().substring(4).split("=")[0].trim(); + + return " if( " + + temp + + " instanceof String) " + + temp + + "= Double.parseDouble(" + + temp.trim() + + "); "; + } else { + return ""; + } + } + + private String getScriptText(MethodField field) { + String content = ((SQLTextLiteralExpr) field.getParams().get(1).value).getText(); + return content; + } + + /** + * Using exprString() rather than getPropertyOrValue() for "base" since something like "Math.E" + * gets evaluated incorrectly in getPropertyOrValue(), returning it as a doc value instead of the + * literal string + */ + public Tuple log(SQLExpr base, SQLExpr field, String valueName) { + String name = nextId("log"); + String result; + if (valueName == null) { + result = + def( + name, + func("Math.log", false, getPropertyOrValue(field)) + + "/" + + func("Math.log", false, exprString(base))); + } else { + result = + getPropertyOrValue(field) + + "; " + + def( + name, + func("Math.log", false, valueName) + + "/" + + func("Math.log", false, exprString(base))); + } + return new Tuple<>(name, result); + } + + public Tuple log10(SQLExpr field) { + String name = nextId("log10"); + return new Tuple<>( + name, def(name, StringUtils.format("Math.log10(%s)", getPropertyOrValue(field)))); + } + + public Tuple ln(SQLExpr field) { + String name = nextId("ln"); + return new Tuple<>( + name, def(name, StringUtils.format("Math.log(%s)", getPropertyOrValue(field)))); + } + + public Tuple trim(SQLExpr field, String valueName) { + return strSingleValueTemplate("trim", field, valueName); + } + + private Tuple degrees(SQLExpr field, String valueName) { + return mathSingleValueTemplate("Math.toDegrees", "degrees", field, valueName); + } + + private Tuple radians(SQLExpr field, String valueName) { + return mathSingleValueTemplate("Math.toRadians", "radians", field, valueName); + } + + private Tuple rand(SQLExpr expr) { + String name = nextId("rand"); + return new Tuple<>( + name, def(name, format("new Random(%s).nextDouble()", getPropertyOrValue(expr)))); + } + + private Tuple rand() { + String name = nextId("rand"); + return new Tuple<>(name, def(name, "new Random().nextDouble()")); + } + + private Tuple mathDoubleValueTemplate( + String methodName, String fieldName, SQLExpr val1, String val2, String valueName) { + String name = nextId(fieldName); + if (valueName == null) { + return new Tuple<>( + name, + def(name, func(methodName, false, getPropertyOrValue(val1), getPropertyOrValue(val2)))); + } else { + return new Tuple<>( + name, + getPropertyOrValue(val1) + + "; " + + def(name, func(methodName, false, valueName, getPropertyOrValue(val2)))); + } + } + + private Tuple mathDoubleValueTemplate( + String methodName, String fieldName, SQLExpr val1, SQLExpr val2) { + String name = nextId(fieldName); + return new Tuple<>( + name, + def(name, func(methodName, false, getPropertyOrValue(val1), getPropertyOrValue(val2)))); + } + + private Tuple mathSingleValueTemplate( + String methodName, String fieldName, SQLExpr field, String valueName) { + String name = nextId(fieldName); + if (valueName == null) { + return new Tuple<>(name, def(name, func(methodName, false, getPropertyOrValue(field)))); + } else { + return new Tuple<>( + name, getPropertyOrValue(field) + "; " + def(name, func(methodName, false, valueName))); + } + } + + private Tuple mathConstantTemplate(String methodName, String fieldName) { + String name = nextId(fieldName); + return new Tuple<>(name, def(name, methodName)); + } + + private Tuple strSingleValueTemplate( + String methodName, SQLExpr field, String valueName) { + String name = nextId(methodName); + if (valueName == null) { + return new Tuple<>( + name, def(name, getPropertyOrStringValue(field) + "." + func(methodName, false))); + } else { + return new Tuple<>( + name, + getPropertyOrStringValue(field) + + "; " + + def(name, valueName + "." + func(methodName, false))); + } + } + + // query: substring(Column expr, int pos, int len) + // painless script: substring(int begin, int end) + // OpenSearch behavior: 1-index, supports out-of-bound index + public Tuple substring(SQLExpr field, int pos, int len) { + String name = nextId("substring"); + // start and end are 0-indexes + int start = pos < 1 ? 0 : pos - 1; + return new Tuple<>( + name, + StringUtils.format( + "def end = (int) Math.min(%s + %s, %s.length()); " + + def( + name, + getPropertyOrStringValue(field) + + "." + + func("substring", false, Integer.toString(start), "end")), + Integer.toString(start), + Integer.toString(len), + getPropertyOrStringValue(field))); + } + + private String lower(String property, String culture) { + return property + ".toLowerCase(Locale.forLanguageTag(\"" + culture + "\"))"; + } + + private String upper(String property, String culture) { + return property + ".toUpperCase(Locale.forLanguageTag(\"" + culture + "\"))"; + } + + private Tuple length(SQLExpr field) { + String name = nextId("length"); + return new Tuple<>(name, def(name, getPropertyOrStringValue(field) + ".length()")); + } + + private Tuple replace(SQLExpr field, String target, String replacement) { + String name = nextId("replace"); + return new Tuple<>( + name, + def( + name, + getPropertyOrStringValue(field) + ".replace(" + target + "," + replacement + ")")); + } + + // OpenSearch behavior: both 'start' and return value are 1-index; return 0 if pattern does not + // exist; + // support out-of-bound index + private Tuple locate(String pattern, SQLExpr source, int start) { + String name = nextId("locate"); + String docSource = getPropertyOrStringValue(source); + start = start < 1 ? 0 : start - 1; + return new Tuple<>( + name, def(name, StringUtils.format("%s.indexOf(%s,%d)+1", docSource, pattern, start))); + } + + private Tuple rtrim(SQLExpr field) { + String name = nextId("rtrim"); + String fieldString = getPropertyOrStringValue(field); + return new Tuple<>( + name, + StringUtils.format( + "int pos=%s.length()-1;" + "while(pos >= 0 && Character.isWhitespace(%s.charAt(pos))) {pos --;} " + def(name, "%s.substring(0, pos+1)"), - fieldString, fieldString, fieldString - )); - } - - private Tuple ltrim(SQLExpr field) { - String name = nextId("ltrim"); - String fieldString = getPropertyOrStringValue(field); - return new Tuple<>(name, StringUtils.format( - "int pos=0;" + fieldString, + fieldString, + fieldString)); + } + + private Tuple ltrim(SQLExpr field) { + String name = nextId("ltrim"); + String fieldString = getPropertyOrStringValue(field); + return new Tuple<>( + name, + StringUtils.format( + "int pos=0;" + "while(pos < %s.length() && Character.isWhitespace(%s.charAt(pos))) {pos ++;} " + def(name, "%s.substring(pos, %s.length())"), - fieldString, fieldString, fieldString, fieldString - )); - } - - private Tuple ascii(SQLExpr field) { - String name = nextId("ascii"); - return new Tuple<>(name, def(name, "(int) " + getPropertyOrStringValue(field) + ".charAt(0)")); - } - - private Tuple left(SQLExpr expr, SQLExpr length) { - String name = nextId("left"); - return new Tuple<>(name, StringUtils.format( - "def len = (int) Math.min(%s, %s.length()); def %s = %s.substring(0, len)", - exprString(length), getPropertyOrStringValue(expr), name, getPropertyOrStringValue(expr))); - } - - private Tuple right(SQLExpr expr, SQLExpr length) { - String name = nextId("right"); - return new Tuple<>(name, StringUtils.format( - "def start = (int) Math.max(0, %s.length()-%s); def %s = %s.substring(start)", - getPropertyOrStringValue(expr), exprString(length), name, getPropertyOrStringValue(expr))); - } - - private Tuple date(SQLExpr field) { - String name = nextId("date"); - return new Tuple<>(name, def(name, - "LocalDate.parse(" + getPropertyOrStringValue(field) + ".toString()," - + "DateTimeFormatter.ISO_DATE_TIME)")); - } - - private Tuple timestamp(SQLExpr field) { - String name = nextId("timestamp"); - return new Tuple<>(name, def(name, - "DateTimeFormatter.ofPattern('yyyy-MM-dd HH:mm:ss').format(" - + "DateTimeFormatter.ISO_DATE_TIME.parse(" - + getPropertyOrStringValue(field) + ".toString()))")); - } - - private Tuple maketime(SQLExpr hr, SQLExpr min, SQLExpr sec) { - String name = nextId("maketime"); - return new Tuple<>(name, def(name, StringUtils.format( + fieldString, + fieldString, + fieldString, + fieldString)); + } + + private Tuple ascii(SQLExpr field) { + String name = nextId("ascii"); + return new Tuple<>(name, def(name, "(int) " + getPropertyOrStringValue(field) + ".charAt(0)")); + } + + private Tuple left(SQLExpr expr, SQLExpr length) { + String name = nextId("left"); + return new Tuple<>( + name, + StringUtils.format( + "def len = (int) Math.min(%s, %s.length()); def %s = %s.substring(0, len)", + exprString(length), + getPropertyOrStringValue(expr), + name, + getPropertyOrStringValue(expr))); + } + + private Tuple right(SQLExpr expr, SQLExpr length) { + String name = nextId("right"); + return new Tuple<>( + name, + StringUtils.format( + "def start = (int) Math.max(0, %s.length()-%s); def %s = %s.substring(start)", + getPropertyOrStringValue(expr), + exprString(length), + name, + getPropertyOrStringValue(expr))); + } + + private Tuple date(SQLExpr field) { + String name = nextId("date"); + return new Tuple<>( + name, + def( + name, + "LocalDate.parse(" + + getPropertyOrStringValue(field) + + ".toString()," + + "DateTimeFormatter.ISO_DATE_TIME)")); + } + + private Tuple timestamp(SQLExpr field) { + String name = nextId("timestamp"); + return new Tuple<>( + name, + def( + name, + "DateTimeFormatter.ofPattern('yyyy-MM-dd HH:mm:ss').format(" + + "DateTimeFormatter.ISO_DATE_TIME.parse(" + + getPropertyOrStringValue(field) + + ".toString()))")); + } + + private Tuple maketime(SQLExpr hr, SQLExpr min, SQLExpr sec) { + String name = nextId("maketime"); + return new Tuple<>( + name, + def( + name, + StringUtils.format( "LocalTime.of(%s, %s, %s).format(DateTimeFormatter.ofPattern('HH:mm:ss'))", hr.toString(), min.toString(), sec.toString()))); - } - - private Tuple now() { - String name = nextId("now"); - return new Tuple<>(name, def(name, "new SimpleDateFormat('HH:mm:ss').format(System.currentTimeMillis())")); - } - - private Tuple curdate() { - String name = nextId("curdate"); - return new Tuple<>(name, def(name, "new SimpleDateFormat('yyyy-MM-dd').format(System.currentTimeMillis())")); - } - - private Tuple ifFunc(List paramers) { - String expr1 = paramers.get(1).value.toString(); - String expr2 = paramers.get(2).value.toString(); - String name = nextId("if"); - - /** Input with null is regarded as false */ - if (paramers.get(0).value instanceof SQLNullExpr) { - return new Tuple<>(name, def(name, expr2)); - } - if (paramers.get(0).value instanceof MethodField) { - String condition = getScriptText((MethodField) paramers.get(0).value); - return new Tuple<>(name, "boolean cond = " + condition + ";" - + def(name, "cond ? " + expr1 + " : " + expr2)); - } else if (paramers.get(0).value instanceof SQLBooleanExpr) { - Boolean condition = ((SQLBooleanExpr) paramers.get(0).value).getValue(); - if (condition) { - return new Tuple<>(name, def(name, expr1)); - } else { - return new Tuple<>(name, def(name, expr2)); - } - } else { - /** - * Detailed explanation of cases that come here: - * the condition expression would be in the format of a=b: - * a is parsed as the key (String) of a KVValue (get from paramers.get(0)) - * and b is parsed as the value (Object) of this KVValue. - * - * Either a or b could be a column name, literal, or a number: - * - if isNumeric is true --> number - * - else if this string is single quoted --> literal - * - else --> column name - */ - String key = getPropertyOrValue(paramers.get(0).key); - String value = getPropertyOrValue(paramers.get(0).value.toString()); - String condition = key + " == " + value; - return new Tuple<>(name, "boolean cond = " + condition + ";" - + def(name, "cond ? " + expr1 + " : " + expr2)); - } - } - - private Tuple ifnull(SQLExpr condition, SQLExpr expr) { - String name = nextId("ifnull"); - if (condition instanceof SQLNullExpr) { - return new Tuple<>(name, def(name, expr.toString())); - } - if (isProperty(condition)) { - return new Tuple<>(name, def(name, doc(condition) + ".size()==0 ? " + expr.toString() + " : " - + getPropertyOrValue(condition))); - } else { - String condStr = Strings.isNullOrEmpty(condition.toString()) ? null : getPropertyOrStringValue(condition); - return new Tuple<>(name, def(name, condStr)); - } - } - - private Tuple isnull(SQLExpr expr) { - String name = nextId("isnull"); - if (expr instanceof SQLNullExpr) { - return new Tuple<>(name, def(name, "1")); - } - if (isProperty(expr)) { - return new Tuple<>(name, def(name, doc(expr) + ".size()==0 ? 1 : 0")); - } - // cases that return 1: - // expr is null || expr is math func but tends to throw "divided by zero" arithmetic exception - String resultStr = "0"; - if (Strings.isNullOrEmpty(expr.toString())) { - resultStr = "1"; - } - if (expr instanceof SQLCharExpr && this.generatedIds.size() > 1) { - // the expr is a math expression - String mathExpr = ((SQLCharExpr) expr).getText(); - return new Tuple<>(name, StringUtils.format( - "try {%s;} " - + "catch(ArithmeticException e) " - + "{return 1;} " - + "def %s=0", - mathExpr, name, name) - ); - } - return new Tuple<>(name, def(name, resultStr)); - } - - public String getCastScriptStatement(String name, String castType, List paramers) - throws SqlParseException { - String castFieldName = String.format("doc['%s'].value", paramers.get(0).toString()); - switch (StringUtils.toUpper(castType)) { - case "INT": - case "LONG": - case "FLOAT": - case "DOUBLE": - return getCastToNumericValueScript(name, castFieldName, StringUtils.toLower(castType)); - case "STRING": - return String.format("def %s = %s.toString()", name, castFieldName); - case "DATETIME": - return String.format("def %s = DateTimeFormatter.ofPattern(\"yyyy-MM-dd'T'HH:mm:ss.SSS'Z'\").format(" - + "DateTimeFormatter.ISO_DATE_TIME.parse(%s.toString()))", name, castFieldName); - default: - throw new SqlParseException("Unsupported cast type " + castType); - } - } - - private String getCastToNumericValueScript(String varName, String docValue, String targetType) { - String script = - "def %1$s = (%2$s instanceof boolean) " - + "? (%2$s ? 1 : 0) " - + ": Double.parseDouble(%2$s.toString()).%3$sValue()"; - return StringUtils.format(script, varName, docValue, targetType); - } - - /** - * Returns return type of script function. This is simple approach, that might be not the best solution in the long - * term. For example - for JDBC, if the column type in index is INTEGER, and the query is "select column+5", current - * approach will return type of result column as DOUBLE, although there is enough information to understand that - * it might be safely treated as INTEGER. - */ - public static Schema.Type getScriptFunctionReturnType(MethodField field, Schema.Type resolvedType) { - String functionName = ((ScriptMethodField) field).getFunctionName().toLowerCase(); - if (functionName.equals("cast")) { - String castType = ((SQLCastExpr) field.getExpression()).getDataType().getName(); - return getCastFunctionReturnType(castType); - } - return resolvedType; - } - - public static Schema.Type getCastFunctionReturnType(String castType) { - switch (StringUtils.toUpper(castType)) { - case "FLOAT": - return Schema.Type.FLOAT; - case "DOUBLE": - return Schema.Type.DOUBLE; - case "INT": - return Schema.Type.INTEGER; - case "STRING": - return Schema.Type.TEXT; - case "DATETIME": - return Schema.Type.DATE; - case "LONG": - return Schema.Type.LONG; - default: - throw new UnsupportedOperationException( - StringUtils.format("The following type is not supported by cast(): %s", castType) - ); - } - } - - /** - * - * @param field - * @return Schema.Type.TEXT or DOUBLE - * There are only two ORDER BY types (TEXT, NUMBER) in OpenSearch, so the Type that is returned here essentially - * indicates the category of the function as opposed to the actual return type. - */ - public static Schema.Type getOrderByFieldType(Field field) { - String functionName = ((ScriptMethodField) field).getFunctionName().toLowerCase(); - if (functionName.equals("cast")) { - String castType = ((SQLCastExpr) field.getExpression()).getDataType().getName(); - return getCastFunctionReturnType(castType); - } - - if (numberOperators.contains(functionName) || mathConstants.contains(functionName) - || trigFunctions.contains(functionName) || binaryOperators.contains(functionName)) { - return Schema.Type.DOUBLE; - } else if (dateFunctions.contains(functionName)) { - if (functionName.equals("date_format") || functionName.equals("now") - || functionName.equals("curdate") || functionName.equals("date") - || functionName.equals("timestamp") || functionName.equals("monthname")) { - return Schema.Type.TEXT; - } - return Schema.Type.DOUBLE; - } else if (stringFunctions.contains(functionName) || stringOperators.contains(functionName)) { - return Schema.Type.TEXT; - } - + } + + private Tuple now() { + String name = nextId("now"); + return new Tuple<>( + name, def(name, "new SimpleDateFormat('HH:mm:ss').format(System.currentTimeMillis())")); + } + + private Tuple curdate() { + String name = nextId("curdate"); + return new Tuple<>( + name, def(name, "new SimpleDateFormat('yyyy-MM-dd').format(System.currentTimeMillis())")); + } + + private Tuple ifFunc(List paramers) { + String expr1 = paramers.get(1).value.toString(); + String expr2 = paramers.get(2).value.toString(); + String name = nextId("if"); + + /** Input with null is regarded as false */ + if (paramers.get(0).value instanceof SQLNullExpr) { + return new Tuple<>(name, def(name, expr2)); + } + if (paramers.get(0).value instanceof MethodField) { + String condition = getScriptText((MethodField) paramers.get(0).value); + return new Tuple<>( + name, "boolean cond = " + condition + ";" + def(name, "cond ? " + expr1 + " : " + expr2)); + } else if (paramers.get(0).value instanceof SQLBooleanExpr) { + Boolean condition = ((SQLBooleanExpr) paramers.get(0).value).getValue(); + if (condition) { + return new Tuple<>(name, def(name, expr1)); + } else { + return new Tuple<>(name, def(name, expr2)); + } + } else { + /** + * Detailed explanation of cases that come here: the condition expression would be in the + * format of a=b: a is parsed as the key (String) of a KVValue (get from paramers.get(0)) and + * b is parsed as the value (Object) of this KVValue. + * + *

    Either a or b could be a column name, literal, or a number: - if isNumeric is true --> + * number - else if this string is single quoted --> literal - else --> column name + */ + String key = getPropertyOrValue(paramers.get(0).key); + String value = getPropertyOrValue(paramers.get(0).value.toString()); + String condition = key + " == " + value; + return new Tuple<>( + name, "boolean cond = " + condition + ";" + def(name, "cond ? " + expr1 + " : " + expr2)); + } + } + + private Tuple ifnull(SQLExpr condition, SQLExpr expr) { + String name = nextId("ifnull"); + if (condition instanceof SQLNullExpr) { + return new Tuple<>(name, def(name, expr.toString())); + } + if (isProperty(condition)) { + return new Tuple<>( + name, + def( + name, + doc(condition) + + ".size()==0 ? " + + expr.toString() + + " : " + + getPropertyOrValue(condition))); + } else { + String condStr = + Strings.isNullOrEmpty(condition.toString()) ? null : getPropertyOrStringValue(condition); + return new Tuple<>(name, def(name, condStr)); + } + } + + private Tuple isnull(SQLExpr expr) { + String name = nextId("isnull"); + if (expr instanceof SQLNullExpr) { + return new Tuple<>(name, def(name, "1")); + } + if (isProperty(expr)) { + return new Tuple<>(name, def(name, doc(expr) + ".size()==0 ? 1 : 0")); + } + // cases that return 1: + // expr is null || expr is math func but tends to throw "divided by zero" arithmetic exception + String resultStr = "0"; + if (Strings.isNullOrEmpty(expr.toString())) { + resultStr = "1"; + } + if (expr instanceof SQLCharExpr && this.generatedIds.size() > 1) { + // the expr is a math expression + String mathExpr = ((SQLCharExpr) expr).getText(); + return new Tuple<>( + name, + StringUtils.format( + "try {%s;} " + "catch(ArithmeticException e) " + "{return 1;} " + "def %s=0", + mathExpr, name, name)); + } + return new Tuple<>(name, def(name, resultStr)); + } + + public String getCastScriptStatement(String name, String castType, List paramers) + throws SqlParseException { + String castFieldName = String.format("doc['%s'].value", paramers.get(0).toString()); + switch (StringUtils.toUpper(castType)) { + case "INT": + case "LONG": + case "FLOAT": + case "DOUBLE": + return getCastToNumericValueScript(name, castFieldName, StringUtils.toLower(castType)); + case "STRING": + return String.format("def %s = %s.toString()", name, castFieldName); + case "DATETIME": + return String.format( + "def %s = DateTimeFormatter.ofPattern(\"yyyy-MM-dd'T'HH:mm:ss.SSS'Z'\").format(" + + "DateTimeFormatter.ISO_DATE_TIME.parse(%s.toString()))", + name, castFieldName); + default: + throw new SqlParseException("Unsupported cast type " + castType); + } + } + + private String getCastToNumericValueScript(String varName, String docValue, String targetType) { + String script = + "def %1$s = (%2$s instanceof boolean) " + + "? (%2$s ? 1 : 0) " + + ": Double.parseDouble(%2$s.toString()).%3$sValue()"; + return StringUtils.format(script, varName, docValue, targetType); + } + + /** + * Returns return type of script function. This is simple approach, that might be not the best + * solution in the long term. For example - for JDBC, if the column type in index is INTEGER, and + * the query is "select column+5", current approach will return type of result column as DOUBLE, + * although there is enough information to understand that it might be safely treated as INTEGER. + */ + public static Schema.Type getScriptFunctionReturnType( + MethodField field, Schema.Type resolvedType) { + String functionName = ((ScriptMethodField) field).getFunctionName().toLowerCase(); + if (functionName.equals("cast")) { + String castType = ((SQLCastExpr) field.getExpression()).getDataType().getName(); + return getCastFunctionReturnType(castType); + } + return resolvedType; + } + + public static Schema.Type getCastFunctionReturnType(String castType) { + switch (StringUtils.toUpper(castType)) { + case "FLOAT": + return Schema.Type.FLOAT; + case "DOUBLE": + return Schema.Type.DOUBLE; + case "INT": + return Schema.Type.INTEGER; + case "STRING": + return Schema.Type.TEXT; + case "DATETIME": + return Schema.Type.DATE; + case "LONG": + return Schema.Type.LONG; + default: throw new UnsupportedOperationException( - String.format( - "The following method is not supported in Schema for Order By: %s", - functionName)); - } + StringUtils.format("The following type is not supported by cast(): %s", castType)); + } + } + + /** + * @param field + * @return Schema.Type.TEXT or DOUBLE There are only two ORDER BY types (TEXT, NUMBER) in + * OpenSearch, so the Type that is returned here essentially indicates the category of the + * function as opposed to the actual return type. + */ + public static Schema.Type getOrderByFieldType(Field field) { + String functionName = ((ScriptMethodField) field).getFunctionName().toLowerCase(); + if (functionName.equals("cast")) { + String castType = ((SQLCastExpr) field.getExpression()).getDataType().getName(); + return getCastFunctionReturnType(castType); + } + + if (numberOperators.contains(functionName) + || mathConstants.contains(functionName) + || trigFunctions.contains(functionName) + || binaryOperators.contains(functionName)) { + return Schema.Type.DOUBLE; + } else if (dateFunctions.contains(functionName)) { + if (functionName.equals("date_format") + || functionName.equals("now") + || functionName.equals("curdate") + || functionName.equals("date") + || functionName.equals("timestamp") + || functionName.equals("monthname")) { + return Schema.Type.TEXT; + } + return Schema.Type.DOUBLE; + } else if (stringFunctions.contains(functionName) || stringOperators.contains(functionName)) { + return Schema.Type.TEXT; + } + + throw new UnsupportedOperationException( + String.format( + "The following method is not supported in Schema for Order By: %s", functionName)); + } } diff --git a/legacy/src/main/java/org/opensearch/sql/legacy/utils/StringUtils.java b/legacy/src/main/java/org/opensearch/sql/legacy/utils/StringUtils.java index 515d980db9..8a3975713b 100644 --- a/legacy/src/main/java/org/opensearch/sql/legacy/utils/StringUtils.java +++ b/legacy/src/main/java/org/opensearch/sql/legacy/utils/StringUtils.java @@ -3,7 +3,6 @@ * SPDX-License-Identifier: Apache-2.0 */ - package org.opensearch.sql.legacy.utils; import com.google.common.base.Strings; @@ -17,106 +16,102 @@ */ public class StringUtils { - /** - * Returns a formatted string using the specified format string and - * arguments, as well as the {@link Locale#ROOT} locale. - * - * @param format format string - * @param args arguments referenced by the format specifiers in the format string - * @return A formatted string - * @throws java.util.IllegalFormatException If a format string contains an illegal syntax, a format - * specifier that is incompatible with the given arguments, - * insufficient arguments given the format string, or other - * illegal conditions. - * @see java.lang.String#format(Locale, String, Object...) - */ - public static String format(final String format, Object... args) { - return String.format(Locale.ROOT, format, args); - } + /** + * Returns a formatted string using the specified format string and arguments, as well as the + * {@link Locale#ROOT} locale. + * + * @param format format string + * @param args arguments referenced by the format specifiers in the format string + * @return A formatted string + * @throws java.util.IllegalFormatException If a format string contains an illegal syntax, a + * format specifier that is incompatible with the given arguments, insufficient arguments + * given the format string, or other illegal conditions. + * @see java.lang.String#format(Locale, String, Object...) + */ + public static String format(final String format, Object... args) { + return String.format(Locale.ROOT, format, args); + } - /** - * Converts all of the characters in this {@code String} to lower - * case using the rules of the {@link Locale#ROOT} locale. This is equivalent to calling - * {@link String#toLowerCase(Locale)} with {@link Locale#ROOT}. - * - * @param input the input String - * @return the {@code String}, converted to lowercase - * @see java.lang.String#toLowerCase(Locale) - */ - public static String toLower(final String input) { - return input.toLowerCase(Locale.ROOT); - } + /** + * Converts all of the characters in this {@code String} to lower case using the rules of the + * {@link Locale#ROOT} locale. This is equivalent to calling {@link String#toLowerCase(Locale)} + * with {@link Locale#ROOT}. + * + * @param input the input String + * @return the {@code String}, converted to lowercase + * @see java.lang.String#toLowerCase(Locale) + */ + public static String toLower(final String input) { + return input.toLowerCase(Locale.ROOT); + } - /** - * Converts all of the characters in this {@code String} to upper - * case using the rules of the {@link Locale#ROOT} locale. This is equivalent to calling - * {@link String#toUpperCase(Locale)} with {@link Locale#ROOT}. - * - * @param input the input String - * @return the {@code String}, converted to uppercase - * @see java.lang.String#toUpperCase(Locale) - */ - public static String toUpper(final String input) { - return input.toUpperCase(Locale.ROOT); - } + /** + * Converts all the characters in this {@code String} to upper case using the rules of the {@link + * Locale#ROOT} locale. This is equivalent to calling {@link String#toUpperCase(Locale)} with + * {@link Locale#ROOT}. + * + * @param input the input String + * @return the {@code String}, converted to uppercase + * @see java.lang.String#toUpperCase(Locale) + */ + public static String toUpper(final String input) { + return input.toUpperCase(Locale.ROOT); + } - /** - * Count how many occurrences of character in this input {@code Sequence}. - * - * @param input the input string - * @param match char to be matched - * @return number of occurrences - */ - public static int countMatches(CharSequence input, char match) { - return Math.toIntExact(input.chars(). - filter(c -> c == match). - count()); - } + /** + * Count how many occurrences of character in this input {@code Sequence}. + * + * @param input the input string + * @param match char to be matched + * @return number of occurrences + */ + public static int countMatches(CharSequence input, char match) { + return Math.toIntExact(input.chars().filter(c -> c == match).count()); + } - /** - * - * @param text string - * @param quote - * @return An unquoted string whose outer pair of back-ticks (if any) has been removed - */ - public static String unquoteSingleField(String text, String quote) { - if (isQuoted(text, quote)) { - return text.substring(quote.length(), text.length() - quote.length()); - } - return text; + /** + * @param text string + * @param quote + * @return An unquoted string whose outer pair of back-ticks (if any) has been removed + */ + public static String unquoteSingleField(String text, String quote) { + if (isQuoted(text, quote)) { + return text.substring(quote.length(), text.length() - quote.length()); } + return text; + } - public static String unquoteSingleField(String text) { - return unquoteSingleField(text, "`"); - } + public static String unquoteSingleField(String text) { + return unquoteSingleField(text, "`"); + } - /** - * - * @param text - * @return A string whose each dot-seperated field has been unquoted from back-ticks (if any) - */ - public static String unquoteFullColumn(String text, String quote) { - String[] strs = text.split("\\."); - for (int i = 0; i < strs.length; i++) { - String unquotedSubstr = unquoteSingleField(strs[i], quote); - strs[i] = unquotedSubstr; - } - return String.join(".", strs); + /** + * @param text + * @return A string whose each dot-separated field has been unquoted from back-ticks (if any) + */ + public static String unquoteFullColumn(String text, String quote) { + String[] strs = text.split("\\."); + for (int i = 0; i < strs.length; i++) { + String unquotedSubstr = unquoteSingleField(strs[i], quote); + strs[i] = unquotedSubstr; } + return String.join(".", strs); + } - public static String unquoteFullColumn(String text) { - return unquoteFullColumn(text, "`"); - } + public static String unquoteFullColumn(String text) { + return unquoteFullColumn(text, "`"); + } - public static boolean isQuoted(String text, String quote) { - return !Strings.isNullOrEmpty(text) && text.startsWith(quote) && text.endsWith(quote); - } + public static boolean isQuoted(String text, String quote) { + return !Strings.isNullOrEmpty(text) && text.startsWith(quote) && text.endsWith(quote); + } - public static boolean isNumeric(String text) { - return Doubles.tryParse(text) != null; - } + public static boolean isNumeric(String text) { + return Doubles.tryParse(text) != null; + } - private StringUtils() { - throw new AssertionError(getClass().getCanonicalName() + " is a utility class and must not be initialized"); - } + private StringUtils() { + throw new AssertionError( + getClass().getCanonicalName() + " is a utility class and must not be initialized"); + } } diff --git a/legacy/src/main/java/org/opensearch/sql/legacy/utils/Util.java b/legacy/src/main/java/org/opensearch/sql/legacy/utils/Util.java index bd1b7f3865..632074bbbe 100644 --- a/legacy/src/main/java/org/opensearch/sql/legacy/utils/Util.java +++ b/legacy/src/main/java/org/opensearch/sql/legacy/utils/Util.java @@ -3,7 +3,6 @@ * SPDX-License-Identifier: Apache-2.0 */ - package org.opensearch.sql.legacy.utils; import com.alibaba.druid.sql.ast.SQLExpr; @@ -38,231 +37,247 @@ import org.opensearch.sql.legacy.exception.SqlParseException; import org.opensearch.sql.legacy.parser.ElasticSqlExprParser; - public class Util { - public static final String NESTED_JOIN_TYPE = "NestedJoinType"; - - public static String joiner(List lists, String oper) { - - if (lists.size() == 0) { - return null; - } + public static final String NESTED_JOIN_TYPE = "NestedJoinType"; - StringBuilder sb = new StringBuilder(lists.get(0).toString()); - for (int i = 1; i < lists.size(); i++) { - sb.append(oper); - sb.append(lists.get(i).toString()); - } + public static String joiner(List lists, String oper) { - return sb.toString(); + if (lists.size() == 0) { + return null; } - public static Object removeTableAilasFromField(Object expr, String tableAlias) { - - if (expr instanceof SQLIdentifierExpr || expr instanceof SQLPropertyExpr || expr instanceof SQLVariantRefExpr) { - String name = expr.toString().replace("`", ""); - if (tableAlias != null) { - String aliasPrefix = tableAlias + "."; - if (name.startsWith(aliasPrefix)) { - String newFieldName = name.replaceFirst(aliasPrefix, ""); - return new SQLIdentifierExpr(newFieldName); - } - } - } - return expr; + StringBuilder sb = new StringBuilder(lists.get(0).toString()); + for (int i = 1; i < lists.size(); i++) { + sb.append(oper); + sb.append(lists.get(i).toString()); } + return sb.toString(); + } - public static Object expr2Object(SQLExpr expr) { - return expr2Object(expr, ""); - } + public static Object removeTableAilasFromField(Object expr, String tableAlias) { - public static Object expr2Object(SQLExpr expr, String charWithQuote) { - Object value = null; - if (expr instanceof SQLNumericLiteralExpr) { - value = ((SQLNumericLiteralExpr) expr).getNumber(); - } else if (expr instanceof SQLCharExpr) { - value = charWithQuote + ((SQLCharExpr) expr).getText() + charWithQuote; - } else if (expr instanceof SQLIdentifierExpr) { - value = expr.toString(); - } else if (expr instanceof SQLPropertyExpr) { - value = expr.toString(); - } else if (expr instanceof SQLVariantRefExpr) { - value = expr.toString(); - } else if (expr instanceof SQLAllColumnExpr) { - value = "*"; - } else if (expr instanceof SQLValuableExpr) { - value = ((SQLValuableExpr) expr).getValue(); - } else { - //throw new SqlParseException("can not support this type " + expr.getClass()); + if (expr instanceof SQLIdentifierExpr + || expr instanceof SQLPropertyExpr + || expr instanceof SQLVariantRefExpr) { + String name = expr.toString().replace("`", ""); + if (tableAlias != null) { + String aliasPrefix = tableAlias + "."; + if (name.startsWith(aliasPrefix)) { + String newFieldName = name.replaceFirst(aliasPrefix, ""); + return new SQLIdentifierExpr(newFieldName); } - return value; + } } - - public static Object getScriptValue(SQLExpr expr) throws SqlParseException { - if (expr instanceof SQLIdentifierExpr || expr instanceof SQLPropertyExpr || expr instanceof SQLVariantRefExpr) { - return "doc['" + expr.toString() + "'].value"; - } else if (expr instanceof SQLValuableExpr) { - return ((SQLValuableExpr) expr).getValue(); - } - throw new SqlParseException("could not parse sqlBinaryOpExpr need to be identifier/valuable got" - + expr.getClass().toString() + " with value:" + expr.toString()); + return expr; + } + + public static Object expr2Object(SQLExpr expr) { + return expr2Object(expr, ""); + } + + public static Object expr2Object(SQLExpr expr, String charWithQuote) { + Object value = null; + if (expr instanceof SQLNumericLiteralExpr) { + value = ((SQLNumericLiteralExpr) expr).getNumber(); + } else if (expr instanceof SQLCharExpr) { + value = charWithQuote + ((SQLCharExpr) expr).getText() + charWithQuote; + } else if (expr instanceof SQLIdentifierExpr) { + value = expr.toString(); + } else if (expr instanceof SQLPropertyExpr) { + value = expr.toString(); + } else if (expr instanceof SQLVariantRefExpr) { + value = expr.toString(); + } else if (expr instanceof SQLAllColumnExpr) { + value = "*"; + } else if (expr instanceof SQLValuableExpr) { + value = ((SQLValuableExpr) expr).getValue(); + } else { + // throw new SqlParseException("can not support this type " + expr.getClass()); } - - public static Object getScriptValueWithQuote(SQLExpr expr, String quote) throws SqlParseException { - if (expr instanceof SQLIdentifierExpr || expr instanceof SQLPropertyExpr || expr instanceof SQLVariantRefExpr) { - return "doc['" + expr.toString() + "'].value"; - } else if (expr instanceof SQLCharExpr) { - return quote + ((SQLCharExpr) expr).getValue() + quote; - } else if (expr instanceof SQLIntegerExpr) { - return ((SQLIntegerExpr) expr).getValue(); - } else if (expr instanceof SQLNumericLiteralExpr) { - return ((SQLNumericLiteralExpr) expr).getNumber(); - } else if (expr instanceof SQLNullExpr) { - return ((SQLNullExpr) expr).toString().toLowerCase(); - } - throw new SqlParseException("could not parse sqlBinaryOpExpr need to be identifier/valuable got" - + expr.getClass().toString() + " with value:" + expr.toString()); + return value; + } + + public static Object getScriptValue(SQLExpr expr) throws SqlParseException { + if (expr instanceof SQLIdentifierExpr + || expr instanceof SQLPropertyExpr + || expr instanceof SQLVariantRefExpr) { + return "doc['" + expr.toString() + "'].value"; + } else if (expr instanceof SQLValuableExpr) { + return ((SQLValuableExpr) expr).getValue(); } - - public static boolean isFromJoinOrUnionTable(SQLExpr expr) { - SQLObject temp = expr; - AtomicInteger counter = new AtomicInteger(10); - while (temp != null && !(expr instanceof SQLSelectQueryBlock) - && !(expr instanceof SQLJoinTableSource) && !(expr instanceof SQLUnionQuery) && counter.get() > 0) { - counter.decrementAndGet(); - temp = temp.getParent(); - if (temp instanceof SQLSelectQueryBlock) { - SQLTableSource from = ((SQLSelectQueryBlock) temp).getFrom(); - if (from instanceof SQLJoinTableSource || from instanceof SQLUnionQuery) { - return true; - } - } - if (temp instanceof SQLJoinTableSource || temp instanceof SQLUnionQuery) { - return true; - } - } - return false; + throw new SqlParseException( + "could not parse sqlBinaryOpExpr need to be identifier/valuable got" + + expr.getClass().toString() + + " with value:" + + expr.toString()); + } + + public static Object getScriptValueWithQuote(SQLExpr expr, String quote) + throws SqlParseException { + if (expr instanceof SQLIdentifierExpr + || expr instanceof SQLPropertyExpr + || expr instanceof SQLVariantRefExpr) { + return "doc['" + expr.toString() + "'].value"; + } else if (expr instanceof SQLCharExpr) { + return quote + ((SQLCharExpr) expr).getValue() + quote; + } else if (expr instanceof SQLIntegerExpr) { + return ((SQLIntegerExpr) expr).getValue(); + } else if (expr instanceof SQLNumericLiteralExpr) { + return ((SQLNumericLiteralExpr) expr).getNumber(); + } else if (expr instanceof SQLNullExpr) { + return ((SQLNullExpr) expr).toString().toLowerCase(); } - - public static double[] KV2DoubleArr(List params) { - double[] ds = new double[params.size()]; - int i = 0; - for (KVValue v : params) { - ds[i] = Double.parseDouble(v.value.toString()); - i++; + throw new SqlParseException( + "could not parse sqlBinaryOpExpr need to be identifier/valuable got" + + expr.getClass().toString() + + " with value:" + + expr.toString()); + } + + public static boolean isFromJoinOrUnionTable(SQLExpr expr) { + SQLObject temp = expr; + AtomicInteger counter = new AtomicInteger(10); + while (temp != null + && !(expr instanceof SQLSelectQueryBlock) + && !(expr instanceof SQLJoinTableSource) + && !(expr instanceof SQLUnionQuery) + && counter.get() > 0) { + counter.decrementAndGet(); + temp = temp.getParent(); + if (temp instanceof SQLSelectQueryBlock) { + SQLTableSource from = ((SQLSelectQueryBlock) temp).getFrom(); + if (from instanceof SQLJoinTableSource || from instanceof SQLUnionQuery) { + return true; } - return ds; + } + if (temp instanceof SQLJoinTableSource || temp instanceof SQLUnionQuery) { + return true; + } } - - - public static String extendedToString(SQLExpr sqlExpr) { - if (sqlExpr instanceof SQLTextLiteralExpr) { - return ((SQLTextLiteralExpr) sqlExpr).getText(); - } - return sqlExpr.toString(); + return false; + } + + public static double[] KV2DoubleArr(List params) { + double[] ds = new double[params.size()]; + int i = 0; + for (KVValue v : params) { + ds[i] = Double.parseDouble(v.value.toString()); + i++; } + return ds; + } - public static String[] concatStringsArrays(String[] a1, String[] a2) { - String[] strings = new String[a1.length + a2.length]; - for (int i = 0; i < a1.length; i++) { - strings[i] = a1[i]; - } - for (int i = 0; i < a2.length; i++) { - strings[a1.length + i] = a2[i]; - } - return strings; + public static String extendedToString(SQLExpr sqlExpr) { + if (sqlExpr instanceof SQLTextLiteralExpr) { + return ((SQLTextLiteralExpr) sqlExpr).getText(); } + return sqlExpr.toString(); + } - public static Object searchPathInMap(Map fieldsMap, String[] path) { - Map currentObject = fieldsMap; - for (int i = 0; i < path.length - 1; i++) { - Object valueFromCurrentMap = currentObject.get(path[i]); - if (valueFromCurrentMap == null) { - return null; - } - if (!Map.class.isAssignableFrom(valueFromCurrentMap.getClass())) { - return null; - } - currentObject = (Map) valueFromCurrentMap; - } - return currentObject.get(path[path.length - 1]); + public static String[] concatStringsArrays(String[] a1, String[] a2) { + String[] strings = new String[a1.length + a2.length]; + for (int i = 0; i < a1.length; i++) { + strings[i] = a1[i]; + } + for (int i = 0; i < a2.length; i++) { + strings[a1.length + i] = a2[i]; } + return strings; + } + + public static Object searchPathInMap(Map fieldsMap, String[] path) { + Map currentObject = fieldsMap; + for (int i = 0; i < path.length - 1; i++) { + Object valueFromCurrentMap = currentObject.get(path[i]); + if (valueFromCurrentMap == null) { + return null; + } + if (!Map.class.isAssignableFrom(valueFromCurrentMap.getClass())) { + return null; + } + currentObject = (Map) valueFromCurrentMap; + } + return currentObject.get(path[path.length - 1]); + } - public static Object deepSearchInMap(Map fieldsMap, String field) { - if (field.contains(".")) { - String[] split = field.split("\\."); - return searchPathInMap(fieldsMap, split); - } - return fieldsMap.get(field); + public static Object deepSearchInMap(Map fieldsMap, String field) { + if (field.contains(".")) { + String[] split = field.split("\\."); + return searchPathInMap(fieldsMap, split); } + return fieldsMap.get(field); + } - public static boolean clearEmptyPaths(Map map) { - if (map.size() == 0) { - return true; - } - Set keysToDelete = new HashSet<>(); - for (Map.Entry entry : map.entrySet()) { - Object value = entry.getValue(); - if (Map.class.isAssignableFrom(value.getClass())) { - if (clearEmptyPaths((Map) value)) { - keysToDelete.add(entry.getKey()); - } - } - } - if (keysToDelete.size() != 0) { - if (map.size() == keysToDelete.size()) { - map.clear(); - return true; - } - for (String key : keysToDelete) { - // TODO: seems like a bug, either fix, or just get rid of for loop and remove the first key - map.remove(key); - return false; - } + public static boolean clearEmptyPaths(Map map) { + if (map.size() == 0) { + return true; + } + Set keysToDelete = new HashSet<>(); + for (Map.Entry entry : map.entrySet()) { + Object value = entry.getValue(); + if (Map.class.isAssignableFrom(value.getClass())) { + if (clearEmptyPaths((Map) value)) { + keysToDelete.add(entry.getKey()); } + } + } + if (keysToDelete.size() != 0) { + if (map.size() == keysToDelete.size()) { + map.clear(); + return true; + } + for (String key : keysToDelete) { + // TODO: seems like a bug, either fix, or just get rid of for loop and remove the first key + map.remove(key); return false; + } } - - public static GetIndexRequestBuilder prepareIndexRequestBuilder(Client client, IndexStatement statement) { - /* - * indexPattern represents wildcard as '.*' which is the regex syntax for matching anything but - * indexRequestBuilder uses the file-match syntax like UNIX which is just '*', so the pattern is converted - * in case its added to the request below - */ - String indexPattern = statement.getIndexPattern().replace(".*", "*"); - - /* - * Ideally all features should be removed from the indexRequest used in SHOW to prevent wasted data - * since only the index name is required in the JDBC format response. However, the type is obtained from the - * mappings response so this feature will need to be set if retrieving type is necessary in other formats. - * (For the time being it is included since the GUI returns types for SHOW queries) - */ - GetIndexRequestBuilder indexRequestBuilder = client.admin().indices() - .prepareGetIndex() - .setFeatures(GetIndexRequest.Feature.MAPPINGS) - .setLocal(true); - - /* - * Since the index request supports index names with wildcard (*) but not (.) it is checked for here so that the - * results returned can be reduced if possible (the regex checks in the ResultSet classes handle the rest). - */ - if (!indexPattern.contains(".")) { - indexRequestBuilder.addIndices(indexPattern); - } - - return indexRequestBuilder; + return false; + } + + public static GetIndexRequestBuilder prepareIndexRequestBuilder( + Client client, IndexStatement statement) { + /* + * indexPattern represents wildcard as '.*' which is the regex syntax for matching anything but + * indexRequestBuilder uses the file-match syntax like UNIX which is just '*', so the pattern is converted + * in case its added to the request below + */ + String indexPattern = statement.getIndexPattern().replace(".*", "*"); + + /* + * Ideally all features should be removed from the indexRequest used in SHOW to prevent wasted data + * since only the index name is required in the JDBC format response. However, the type is obtained from the + * mappings response so this feature will need to be set if retrieving type is necessary in other formats. + * (For the time being it is included since the GUI returns types for SHOW queries) + */ + GetIndexRequestBuilder indexRequestBuilder = + client + .admin() + .indices() + .prepareGetIndex() + .setFeatures(GetIndexRequest.Feature.MAPPINGS) + .setLocal(true); + + /* + * Since the index request supports index names with wildcard (*) but not (.) it is checked for here so that the + * results returned can be reduced if possible (the regex checks in the ResultSet classes handle the rest). + */ + if (!indexPattern.contains(".")) { + indexRequestBuilder.addIndices(indexPattern); } - public static SQLExpr toSqlExpr(String sql) { - SQLExprParser parser = new ElasticSqlExprParser(sql); - SQLExpr expr = parser.expr(); + return indexRequestBuilder; + } - if (parser.getLexer().token() != Token.EOF) { - throw new ParserException("Illegal SQL expression : " + sql); - } - return expr; - } + public static SQLExpr toSqlExpr(String sql) { + SQLExprParser parser = new ElasticSqlExprParser(sql); + SQLExpr expr = parser.expr(); + if (parser.getLexer().token() != Token.EOF) { + throw new ParserException("Illegal SQL expression : " + sql); + } + return expr; + } } diff --git a/legacy/src/test/java/org/opensearch/sql/legacy/antlr/SymbolSimilarityTest.java b/legacy/src/test/java/org/opensearch/sql/legacy/antlr/SymbolSimilarityTest.java index a894f4311a..fbdcca2bb0 100644 --- a/legacy/src/test/java/org/opensearch/sql/legacy/antlr/SymbolSimilarityTest.java +++ b/legacy/src/test/java/org/opensearch/sql/legacy/antlr/SymbolSimilarityTest.java @@ -3,7 +3,6 @@ * SPDX-License-Identifier: Apache-2.0 */ - package org.opensearch.sql.legacy.antlr; import static java.util.Collections.emptyList; @@ -14,42 +13,39 @@ import org.junit.Assert; import org.junit.Test; -/** - * Test cases for symbol similarity - */ +/** Test cases for symbol similarity */ public class SymbolSimilarityTest { - @Test - public void noneCandidateShouldReturnTargetStringItself() { - String target = "test"; - String mostSimilarSymbol = new SimilarSymbols(emptyList()).mostSimilarTo(target); - Assert.assertEquals(target, mostSimilarSymbol); - } - - @Test - public void singleCandidateShouldReturnTheOnlyCandidate() { - String target = "test"; - String candidate = "hello"; - String mostSimilarSymbol = new SimilarSymbols(singletonList(candidate)).mostSimilarTo(target); - Assert.assertEquals(candidate, mostSimilarSymbol); - } - - @Test - public void twoCandidatesShouldReturnMostSimilarCandidate() { - String target = "test"; - String mostSimilar = "tests"; - List candidates = Arrays.asList("hello", mostSimilar); - String mostSimilarSymbol = new SimilarSymbols(candidates).mostSimilarTo(target); - Assert.assertEquals(mostSimilar, mostSimilarSymbol); - } - - @Test - public void manyCandidatesShouldReturnMostSimilarCandidate() { - String target = "test"; - String mostSimilar = "tests"; - List candidates = Arrays.asList("hello", mostSimilar, "world"); - String mostSimilarSymbol = new SimilarSymbols(candidates).mostSimilarTo(target); - Assert.assertEquals(mostSimilar, mostSimilarSymbol); - } - + @Test + public void noneCandidateShouldReturnTargetStringItself() { + String target = "test"; + String mostSimilarSymbol = new SimilarSymbols(emptyList()).mostSimilarTo(target); + Assert.assertEquals(target, mostSimilarSymbol); + } + + @Test + public void singleCandidateShouldReturnTheOnlyCandidate() { + String target = "test"; + String candidate = "hello"; + String mostSimilarSymbol = new SimilarSymbols(singletonList(candidate)).mostSimilarTo(target); + Assert.assertEquals(candidate, mostSimilarSymbol); + } + + @Test + public void twoCandidatesShouldReturnMostSimilarCandidate() { + String target = "test"; + String mostSimilar = "tests"; + List candidates = Arrays.asList("hello", mostSimilar); + String mostSimilarSymbol = new SimilarSymbols(candidates).mostSimilarTo(target); + Assert.assertEquals(mostSimilar, mostSimilarSymbol); + } + + @Test + public void manyCandidatesShouldReturnMostSimilarCandidate() { + String target = "test"; + String mostSimilar = "tests"; + List candidates = Arrays.asList("hello", mostSimilar, "world"); + String mostSimilarSymbol = new SimilarSymbols(candidates).mostSimilarTo(target); + Assert.assertEquals(mostSimilar, mostSimilarSymbol); + } } diff --git a/legacy/src/test/java/org/opensearch/sql/legacy/antlr/SyntaxAnalysisTest.java b/legacy/src/test/java/org/opensearch/sql/legacy/antlr/SyntaxAnalysisTest.java index bd71fd2500..765bb0616e 100644 --- a/legacy/src/test/java/org/opensearch/sql/legacy/antlr/SyntaxAnalysisTest.java +++ b/legacy/src/test/java/org/opensearch/sql/legacy/antlr/SyntaxAnalysisTest.java @@ -3,7 +3,6 @@ * SPDX-License-Identifier: Apache-2.0 */ - package org.opensearch.sql.legacy.antlr; import static java.util.stream.Collectors.toList; @@ -17,124 +16,115 @@ import org.opensearch.sql.legacy.antlr.syntax.SyntaxAnalysisException; /** - * Test cases focused on illegal syntax testing (denylist) along with a few normal cases not covered previously. - * All other normal cases should be covered in existing unit test and IT. + * Test cases focused on illegal syntax testing (denylist) along with a few normal cases not covered + * previously. All other normal cases should be covered in existing unit test and IT. */ public class SyntaxAnalysisTest { - /** public accessor is required by @Rule annotation */ - @Rule - public ExpectedException exception = ExpectedException.none(); - - private OpenSearchLegacySqlAnalyzer - analyzer = new OpenSearchLegacySqlAnalyzer(new SqlAnalysisConfig(true, true, 1000)); - - /** In reality exception occurs before reaching new parser for now */ - @Test - public void unsupportedKeywordShouldThrowException() { - expectValidationFailWithErrorMessage( - "INSERT INTO accounts VALUES ('a')", - "offending symbol [INSERT]" - ); - } - - /** - * Why we need to let it go and verify in semantic analyzer? - * Parser treats LOG123 a valid column and stops at '(' which gives wrong location and expected token - * In this case it's hard for parser to figure out if this is a wrong function name indeed or not. - * So we let it pass as an UDF and fail in semantic analyzer with meaningful message. - */ - @Test //(expected = SyntaxAnalysisException.class) - public void unsupportedFunctionShouldThrowException() { - validate("SELECT * FROM accounts WHERE LOG123(balance) = 1"); - } - - @Test - public void unsupportedOperatorShouldPassSyntaxCheck() { - expectValidationFailWithErrorMessage( - "SELECT * FROM accounts WHERE age <=> 1", - "offending symbol [>]" - ); - } - - @Test - public void missingFromClauseShouldThrowException() { - expectValidationFailWithErrorMessage( - "SELECT 1", - "offending symbol []" // parsing was unable to terminate normally - ); - } - - @Test - public void missingWhereKeywordShouldThrowException() { - expectValidationFailWithErrorMessage( - "SELECT * FROM accounts age = 1", - "offending symbol [=]", // parser thought 'age' is alias of 'accounts' and failed at '=' - "Expecting", ";" // "Expecting tokens in {, ';'}" + /** public accessor is required by @Rule annotation */ + @Rule public ExpectedException exception = ExpectedException.none(); + + private OpenSearchLegacySqlAnalyzer analyzer = + new OpenSearchLegacySqlAnalyzer(new SqlAnalysisConfig(true, true, 1000)); + + /** In reality exception occurs before reaching new parser for now */ + @Test + public void unsupportedKeywordShouldThrowException() { + expectValidationFailWithErrorMessage( + "INSERT INTO accounts VALUES ('a')", "offending symbol [INSERT]"); + } + + /** + * Why we need to let it go and verify in semantic analyzer? Parser treats LOG123 a valid column + * and stops at '(' which gives wrong location and expected token In this case it's hard for + * parser to figure out if this is a wrong function name indeed or not. So we let it pass as an + * UDF and fail in semantic analyzer with meaningful message. + */ + @Test // (expected = SyntaxAnalysisException.class) + public void unsupportedFunctionShouldThrowException() { + validate("SELECT * FROM accounts WHERE LOG123(balance) = 1"); + } + + @Test + public void unsupportedOperatorShouldPassSyntaxCheck() { + expectValidationFailWithErrorMessage( + "SELECT * FROM accounts WHERE age <=> 1", "offending symbol [>]"); + } + + @Test + public void missingFromClauseShouldThrowException() { + expectValidationFailWithErrorMessage( + "SELECT 1", "offending symbol []" // parsing was unable to terminate normally ); - } - - @Test - public void someKeywordsShouldBeAbleToUseAsIdentifier() { - validate("SELECT AVG(balance) AS avg FROM accounts"); - } - - @Test - public void specialIndexNameShouldPass() { - validate("SELECT * FROM accounts/temp"); - validate("SELECT * FROM account*"); - validate("SELECT * FROM opensearch-accounts"); - validate("SELECT * FROM opensearch-account*"); - } - - @Test - public void typeNamePatternShouldThrowException() { - expectValidationFailWithErrorMessage( - "SELECT * FROM accounts/tem*", - "offending symbol [*]" + } + + @Test + public void missingWhereKeywordShouldThrowException() { + expectValidationFailWithErrorMessage( + "SELECT * FROM accounts age = 1", + "offending symbol [=]", // parser thought 'age' is alias of 'accounts' and failed at '=' + "Expecting", + ";" // "Expecting tokens in {, ';'}" ); - } - - @Test - public void systemIndexNameShouldPass() { - validate("SELECT * FROM .opensearch_dashboards"); - } - - @Test - public void useMetadataFieldShouldPass() { - validate("SELECT @timestamp FROM accounts"); - } - - @Test - public void leftJoinOnNestedFieldWithoutOnClauseShouldPass() { - validate("SELECT * FROM accounts a LEFT JOIN a.projects p"); - } - - @Test - public void useDeepNestedFieldShouldPass() { - validate("SELECT a.projects.name FROM accounts a"); - } - - /** As the translation is not supported for now, check this in semantic analyzer */ - @Test - public void arithmeticExpressionInWhereClauseShouldPass() { - validate("SELECT * FROM accounts WHERE age + 1 = 10"); - } - - @Test - public void queryEndWithSemiColonShouldPass() { - validate("SELECT * FROM accounts;"); - } - - private void expectValidationFailWithErrorMessage(String query, String... messages) { - exception.expect(SyntaxAnalysisException.class); - exception.expectMessage(allOf(Arrays.stream(messages). - map(Matchers::containsString). - collect(toList()))); - validate(query); - } - - private void validate(String sql) { - analyzer.analyzeSyntax(sql); - } + } + + @Test + public void someKeywordsShouldBeAbleToUseAsIdentifier() { + validate("SELECT AVG(balance) AS avg FROM accounts"); + } + + @Test + public void specialIndexNameShouldPass() { + validate("SELECT * FROM accounts/temp"); + validate("SELECT * FROM account*"); + validate("SELECT * FROM opensearch-accounts"); + validate("SELECT * FROM opensearch-account*"); + } + + @Test + public void typeNamePatternShouldThrowException() { + expectValidationFailWithErrorMessage("SELECT * FROM accounts/tem*", "offending symbol [*]"); + } + + @Test + public void systemIndexNameShouldPass() { + validate("SELECT * FROM .opensearch_dashboards"); + } + + @Test + public void useMetadataFieldShouldPass() { + validate("SELECT @timestamp FROM accounts"); + } + + @Test + public void leftJoinOnNestedFieldWithoutOnClauseShouldPass() { + validate("SELECT * FROM accounts a LEFT JOIN a.projects p"); + } + + @Test + public void useDeepNestedFieldShouldPass() { + validate("SELECT a.projects.name FROM accounts a"); + } + + /** As the translation is not supported for now, check this in semantic analyzer */ + @Test + public void arithmeticExpressionInWhereClauseShouldPass() { + validate("SELECT * FROM accounts WHERE age + 1 = 10"); + } + + @Test + public void queryEndWithSemiColonShouldPass() { + validate("SELECT * FROM accounts;"); + } + + private void expectValidationFailWithErrorMessage(String query, String... messages) { + exception.expect(SyntaxAnalysisException.class); + exception.expectMessage( + allOf(Arrays.stream(messages).map(Matchers::containsString).collect(toList()))); + validate(query); + } + + private void validate(String sql) { + analyzer.analyzeSyntax(sql); + } } diff --git a/legacy/src/test/java/org/opensearch/sql/legacy/antlr/semantic/SemanticAnalyzerAggregateFunctionTest.java b/legacy/src/test/java/org/opensearch/sql/legacy/antlr/semantic/SemanticAnalyzerAggregateFunctionTest.java index 6671542298..df258270b9 100644 --- a/legacy/src/test/java/org/opensearch/sql/legacy/antlr/semantic/SemanticAnalyzerAggregateFunctionTest.java +++ b/legacy/src/test/java/org/opensearch/sql/legacy/antlr/semantic/SemanticAnalyzerAggregateFunctionTest.java @@ -3,154 +3,147 @@ * SPDX-License-Identifier: Apache-2.0 */ - package org.opensearch.sql.legacy.antlr.semantic; import org.junit.Ignore; import org.junit.Test; -/** - * Semantic analysis test for aggregate functions. - */ +/** Semantic analysis test for aggregate functions. */ public class SemanticAnalyzerAggregateFunctionTest extends SemanticAnalyzerTestBase { - @Ignore("To be implemented") - @Test(expected = SemanticAnalysisException.class) - public void useAggregateFunctionInWhereClauseShouldFail() { - validate("SELECT * FROM semantics WHERE AVG(balance) > 10000"); - } - - @Test - public void useAggregateFunctionInSelectClauseShouldPass() { - validate( - "SELECT" + - " city," + - " COUNT(*)," + - " MAX(age)," + - " MIN(balance)," + - " AVG(manager.salary)," + - " SUM(balance)" + - "FROM semantics " + - "GROUP BY city"); - } - - @Test - public void useAggregateFunctionInSelectClauseWithoutGroupByShouldPass() { - validate( - "SELECT" + - " COUNT(*)," + - " MAX(age)," + - " MIN(balance)," + - " AVG(manager.salary)," + - " SUM(balance)" + - "FROM semantics"); - } - - @Test - public void countFunctionCallOnAnyFieldShouldPass() { - validate( - "SELECT" + - " COUNT(address)," + - " COUNT(age)," + - " COUNT(birthday)," + - " COUNT(location)," + - " COUNT(manager.address)," + - " COUNT(employer)" + - "FROM semantics"); - } - - @Test - public void maxFunctionCallOnTextFieldShouldFail() { - expectValidationFailWithErrorMessages( - "SELECT MAX(address) FROM semantics", - "Function [MAX] cannot work with [TEXT].", - "Usage: MAX(NUMBER T) -> T" - ); - } - - @Test - public void minFunctionCallOnDateFieldShouldFail() { - expectValidationFailWithErrorMessages( - "SELECT MIN(birthday) FROM semantics", - "Function [MIN] cannot work with [DATE].", - "Usage: MIN(NUMBER T) -> T" - ); - } - - @Test - public void avgFunctionCallOnBooleanFieldShouldFail() { - expectValidationFailWithErrorMessages( - "SELECT AVG(p.active) FROM semantics s, s.projects p", - "Function [AVG] cannot work with [BOOLEAN].", - "Usage: AVG(NUMBER T) -> DOUBLE" - ); - } - - @Test - public void sumFunctionCallOnBooleanFieldShouldFail() { - expectValidationFailWithErrorMessages( - "SELECT SUM(city) FROM semantics", - "Function [SUM] cannot work with [KEYWORD].", - "Usage: SUM(NUMBER T) -> T" - ); - } - - @Test - public void useAvgFunctionCallAliasInHavingClauseShouldPass() { - validate("SELECT city, AVG(age) AS avg FROM semantics GROUP BY city HAVING avg > 10"); - } - - @Test - public void useAvgAndMaxFunctionCallAliasInHavingClauseShouldPass() { - validate( - "SELECT city, AVG(age) AS avg, MAX(balance) AS bal FROM semantics " + - "GROUP BY city HAVING avg > 10 AND bal > 10000" - ); - } - - @Test - public void useAvgFunctionCallWithoutAliasInHavingShouldPass() { - validate("SELECT city, AVG(age) FROM semantics GROUP BY city HAVING AVG(age) > 10"); - } - - @Test - public void useDifferentAggregateFunctionInHavingClauseShouldPass() { - validate("SELECT city, AVG(age) FROM semantics GROUP BY city HAVING COUNT(*) > 10 AND SUM(balance) <= 10000"); - } - - @Test - public void useAvgFunctionCallAliasInOrderByClauseShouldPass() { - validate("SELECT city, AVG(age) AS avg FROM semantics GROUP BY city ORDER BY avg"); - } - - @Test - public void useAvgFunctionCallAliasInGroupByAndOrderByClauseShouldPass() { - validate("SELECT SUBSTRING(address, 0, 3) AS add FROM semantics GROUP BY add ORDER BY add"); - } - - @Test - public void useColumnNameAliasInOrderByClauseShouldPass() { - validate("SELECT age AS a, AVG(balance) FROM semantics GROUP BY age ORDER BY a"); - } - - @Test - public void useExpressionAliasInOrderByClauseShouldPass() { - validate("SELECT age + 1 AS a FROM semantics GROUP BY age ORDER BY a"); - } - - @Test - public void useAvgFunctionCallWithTextFieldInHavingClauseShouldFail() { - expectValidationFailWithErrorMessages( - "SELECT city FROM semantics GROUP BY city HAVING AVG(address) > 10", - "Function [AVG] cannot work with [TEXT].", - "Usage: AVG(NUMBER T) -> DOUBLE" - ); - } - - @Test - public void useCountFunctionCallWithNestedFieldShouldPass() { - validate("SELECT * FROM semantics s, s.projects p GROUP BY city HAVING COUNT(p) > 1"); - validate("SELECT * FROM semantics s, s.projects p, p.members m GROUP BY city HAVING COUNT(m) > 1"); - } - + @Ignore("To be implemented") + @Test(expected = SemanticAnalysisException.class) + public void useAggregateFunctionInWhereClauseShouldFail() { + validate("SELECT * FROM semantics WHERE AVG(balance) > 10000"); + } + + @Test + public void useAggregateFunctionInSelectClauseShouldPass() { + validate( + "SELECT" + + " city," + + " COUNT(*)," + + " MAX(age)," + + " MIN(balance)," + + " AVG(manager.salary)," + + " SUM(balance)" + + "FROM semantics " + + "GROUP BY city"); + } + + @Test + public void useAggregateFunctionInSelectClauseWithoutGroupByShouldPass() { + validate( + "SELECT" + + " COUNT(*)," + + " MAX(age)," + + " MIN(balance)," + + " AVG(manager.salary)," + + " SUM(balance)" + + "FROM semantics"); + } + + @Test + public void countFunctionCallOnAnyFieldShouldPass() { + validate( + "SELECT" + + " COUNT(address)," + + " COUNT(age)," + + " COUNT(birthday)," + + " COUNT(location)," + + " COUNT(manager.address)," + + " COUNT(employer)" + + "FROM semantics"); + } + + @Test + public void maxFunctionCallOnTextFieldShouldFail() { + expectValidationFailWithErrorMessages( + "SELECT MAX(address) FROM semantics", + "Function [MAX] cannot work with [TEXT].", + "Usage: MAX(NUMBER T) -> T"); + } + + @Test + public void minFunctionCallOnDateFieldShouldFail() { + expectValidationFailWithErrorMessages( + "SELECT MIN(birthday) FROM semantics", + "Function [MIN] cannot work with [DATE].", + "Usage: MIN(NUMBER T) -> T"); + } + + @Test + public void avgFunctionCallOnBooleanFieldShouldFail() { + expectValidationFailWithErrorMessages( + "SELECT AVG(p.active) FROM semantics s, s.projects p", + "Function [AVG] cannot work with [BOOLEAN].", + "Usage: AVG(NUMBER T) -> DOUBLE"); + } + + @Test + public void sumFunctionCallOnBooleanFieldShouldFail() { + expectValidationFailWithErrorMessages( + "SELECT SUM(city) FROM semantics", + "Function [SUM] cannot work with [KEYWORD].", + "Usage: SUM(NUMBER T) -> T"); + } + + @Test + public void useAvgFunctionCallAliasInHavingClauseShouldPass() { + validate("SELECT city, AVG(age) AS avg FROM semantics GROUP BY city HAVING avg > 10"); + } + + @Test + public void useAvgAndMaxFunctionCallAliasInHavingClauseShouldPass() { + validate( + "SELECT city, AVG(age) AS avg, MAX(balance) AS bal FROM semantics " + + "GROUP BY city HAVING avg > 10 AND bal > 10000"); + } + + @Test + public void useAvgFunctionCallWithoutAliasInHavingShouldPass() { + validate("SELECT city, AVG(age) FROM semantics GROUP BY city HAVING AVG(age) > 10"); + } + + @Test + public void useDifferentAggregateFunctionInHavingClauseShouldPass() { + validate( + "SELECT city, AVG(age) FROM semantics GROUP BY city HAVING COUNT(*) > 10 AND SUM(balance)" + + " <= 10000"); + } + + @Test + public void useAvgFunctionCallAliasInOrderByClauseShouldPass() { + validate("SELECT city, AVG(age) AS avg FROM semantics GROUP BY city ORDER BY avg"); + } + + @Test + public void useAvgFunctionCallAliasInGroupByAndOrderByClauseShouldPass() { + validate("SELECT SUBSTRING(address, 0, 3) AS add FROM semantics GROUP BY add ORDER BY add"); + } + + @Test + public void useColumnNameAliasInOrderByClauseShouldPass() { + validate("SELECT age AS a, AVG(balance) FROM semantics GROUP BY age ORDER BY a"); + } + + @Test + public void useExpressionAliasInOrderByClauseShouldPass() { + validate("SELECT age + 1 AS a FROM semantics GROUP BY age ORDER BY a"); + } + + @Test + public void useAvgFunctionCallWithTextFieldInHavingClauseShouldFail() { + expectValidationFailWithErrorMessages( + "SELECT city FROM semantics GROUP BY city HAVING AVG(address) > 10", + "Function [AVG] cannot work with [TEXT].", + "Usage: AVG(NUMBER T) -> DOUBLE"); + } + + @Test + public void useCountFunctionCallWithNestedFieldShouldPass() { + validate("SELECT * FROM semantics s, s.projects p GROUP BY city HAVING COUNT(p) > 1"); + validate( + "SELECT * FROM semantics s, s.projects p, p.members m GROUP BY city HAVING COUNT(m) > 1"); + } } diff --git a/legacy/src/test/java/org/opensearch/sql/legacy/antlr/semantic/SemanticAnalyzerBasicTest.java b/legacy/src/test/java/org/opensearch/sql/legacy/antlr/semantic/SemanticAnalyzerBasicTest.java index 6f6b09b737..1d5ff595f3 100644 --- a/legacy/src/test/java/org/opensearch/sql/legacy/antlr/semantic/SemanticAnalyzerBasicTest.java +++ b/legacy/src/test/java/org/opensearch/sql/legacy/antlr/semantic/SemanticAnalyzerBasicTest.java @@ -3,7 +3,6 @@ * SPDX-License-Identifier: Apache-2.0 */ - package org.opensearch.sql.legacy.antlr.semantic; import static org.hamcrest.MatcherAssert.assertThat; @@ -37,561 +36,576 @@ import org.opensearch.sql.legacy.esdomain.LocalClusterState; /** - * Semantic analysis test cases focused on basic scope building logic which is the cornerstone of analysis followed. - * The low abstraction here enumerating all present field names in each test case is intentional for better demonstration. + * Semantic analysis test cases focused on basic scope building logic which is the cornerstone of + * analysis followed. The low abstraction here enumerating all present field names in each test case + * is intentional for better demonstration. */ public class SemanticAnalyzerBasicTest extends SemanticAnalyzerTestBase { - private SemanticContext context; - - private OpenSearchMappingLoader analyzer; + private SemanticContext context; - @Before - public void setUp() { - context = new SemanticContext(); - analyzer = new OpenSearchMappingLoader(context, LocalClusterState.state(), 1000); - } + private OpenSearchMappingLoader analyzer; - @Test - public void contextShouldIncludeAllFieldsAfterVisitingIndexNameInFromClause() { - analyzer.visitIndexName("semantics"); + @Before + public void setUp() { + context = new SemanticContext(); + analyzer = new OpenSearchMappingLoader(context, LocalClusterState.state(), 1000); + } - Map typeByName = context.peek().resolveAll(Namespace.FIELD_NAME); - assertThat( - typeByName, - allOf( - aMapWithSize(21), - hasEntry("semantics", (Type) new OpenSearchIndex("semantics", INDEX)), - hasEntry("address", TEXT), - hasEntry("age", INTEGER), - hasEntry("balance", DOUBLE), - hasEntry("city", KEYWORD), - hasEntry("birthday", DATE), - hasEntry("location", GEO_POINT), - hasEntry("new_field", UNKNOWN), - hasEntry("field with spaces", TEXT), - hasEntry("employer", TEXT), - hasEntry("employer.keyword", KEYWORD), - hasEntry("projects", (Type) new OpenSearchIndex("projects", NESTED_FIELD)), - hasEntry("projects.active", BOOLEAN), - hasEntry("projects.release", DATE), - hasEntry("projects.members", (Type) new OpenSearchIndex("projects.members", NESTED_FIELD)), - hasEntry("projects.members.name", TEXT), - hasEntry("manager", OBJECT), - hasEntry("manager.name", TEXT), - hasEntry("manager.name.keyword", KEYWORD), - hasEntry("manager.address", KEYWORD), - hasEntry("manager.salary", LONG) - ) - ); + @Test + public void contextShouldIncludeAllFieldsAfterVisitingIndexNameInFromClause() { + analyzer.visitIndexName("semantics"); - analyzer.visitAs("", new OpenSearchIndex("semantics", INDEX)); - typeByName = context.peek().resolveAll(Namespace.FIELD_NAME); - assertThat( - typeByName, - allOf( - aMapWithSize(41), - hasEntry("semantics", (Type) new OpenSearchIndex("semantics", INDEX)), - hasEntry("address", TEXT), - hasEntry("age", INTEGER), - hasEntry("balance", DOUBLE), - hasEntry("city", KEYWORD), - hasEntry("birthday", DATE), - hasEntry("location", GEO_POINT), - hasEntry("new_field", UNKNOWN), - hasEntry("field with spaces", TEXT), - hasEntry("employer", TEXT), - hasEntry("employer.keyword", KEYWORD), - hasEntry("projects", (Type) new OpenSearchIndex("projects", NESTED_FIELD)), - hasEntry("projects.active", BOOLEAN), - hasEntry("projects.release", DATE), - hasEntry("projects.members", (Type) new OpenSearchIndex("projects.members", NESTED_FIELD)), - hasEntry("projects.members.name", TEXT), - hasEntry("manager", OBJECT), - hasEntry("manager.name", TEXT), - hasEntry("manager.name.keyword", KEYWORD), - hasEntry("manager.address", KEYWORD), - hasEntry("manager.salary", LONG), - // These are also valid identifier in SQL - hasEntry("semantics.address", TEXT), - hasEntry("semantics.age", INTEGER), - hasEntry("semantics.balance", DOUBLE), - hasEntry("semantics.city", KEYWORD), - hasEntry("semantics.birthday", DATE), - hasEntry("semantics.location", GEO_POINT), - hasEntry("semantics.new_field", UNKNOWN), - hasEntry("semantics.field with spaces", TEXT), - hasEntry("semantics.employer", TEXT), - hasEntry("semantics.employer.keyword", KEYWORD), - hasEntry("semantics.projects", (Type) new OpenSearchIndex("semantics.projects", NESTED_FIELD)), - hasEntry("semantics.projects.active", BOOLEAN), - hasEntry("semantics.projects.release", DATE), - hasEntry("semantics.projects.members", (Type) new OpenSearchIndex("semantics.projects.members", NESTED_FIELD)), - hasEntry("semantics.projects.members.name", TEXT), - hasEntry("semantics.manager", OBJECT), - hasEntry("semantics.manager.name", TEXT), - hasEntry("semantics.manager.name.keyword", KEYWORD), - hasEntry("semantics.manager.address", KEYWORD), - hasEntry("semantics.manager.salary", LONG) - ) - ); - } + Map typeByName = context.peek().resolveAll(Namespace.FIELD_NAME); + assertThat( + typeByName, + allOf( + aMapWithSize(21), + hasEntry("semantics", (Type) new OpenSearchIndex("semantics", INDEX)), + hasEntry("address", TEXT), + hasEntry("age", INTEGER), + hasEntry("balance", DOUBLE), + hasEntry("city", KEYWORD), + hasEntry("birthday", DATE), + hasEntry("location", GEO_POINT), + hasEntry("new_field", UNKNOWN), + hasEntry("field with spaces", TEXT), + hasEntry("employer", TEXT), + hasEntry("employer.keyword", KEYWORD), + hasEntry("projects", (Type) new OpenSearchIndex("projects", NESTED_FIELD)), + hasEntry("projects.active", BOOLEAN), + hasEntry("projects.release", DATE), + hasEntry( + "projects.members", (Type) new OpenSearchIndex("projects.members", NESTED_FIELD)), + hasEntry("projects.members.name", TEXT), + hasEntry("manager", OBJECT), + hasEntry("manager.name", TEXT), + hasEntry("manager.name.keyword", KEYWORD), + hasEntry("manager.address", KEYWORD), + hasEntry("manager.salary", LONG))); - @Test - public void contextShouldIncludeAllFieldsPrefixedByIndexAliasAfterVisitingIndexNameWithAliasInFromClause() { - OpenSearchIndex indexType = new OpenSearchIndex("semantics", INDEX); - analyzer.visitIndexName("semantics"); - analyzer.visitAs("s", indexType); + analyzer.visitAs("", new OpenSearchIndex("semantics", INDEX)); + typeByName = context.peek().resolveAll(Namespace.FIELD_NAME); + assertThat( + typeByName, + allOf( + aMapWithSize(41), + hasEntry("semantics", (Type) new OpenSearchIndex("semantics", INDEX)), + hasEntry("address", TEXT), + hasEntry("age", INTEGER), + hasEntry("balance", DOUBLE), + hasEntry("city", KEYWORD), + hasEntry("birthday", DATE), + hasEntry("location", GEO_POINT), + hasEntry("new_field", UNKNOWN), + hasEntry("field with spaces", TEXT), + hasEntry("employer", TEXT), + hasEntry("employer.keyword", KEYWORD), + hasEntry("projects", (Type) new OpenSearchIndex("projects", NESTED_FIELD)), + hasEntry("projects.active", BOOLEAN), + hasEntry("projects.release", DATE), + hasEntry( + "projects.members", (Type) new OpenSearchIndex("projects.members", NESTED_FIELD)), + hasEntry("projects.members.name", TEXT), + hasEntry("manager", OBJECT), + hasEntry("manager.name", TEXT), + hasEntry("manager.name.keyword", KEYWORD), + hasEntry("manager.address", KEYWORD), + hasEntry("manager.salary", LONG), + // These are also valid identifier in SQL + hasEntry("semantics.address", TEXT), + hasEntry("semantics.age", INTEGER), + hasEntry("semantics.balance", DOUBLE), + hasEntry("semantics.city", KEYWORD), + hasEntry("semantics.birthday", DATE), + hasEntry("semantics.location", GEO_POINT), + hasEntry("semantics.new_field", UNKNOWN), + hasEntry("semantics.field with spaces", TEXT), + hasEntry("semantics.employer", TEXT), + hasEntry("semantics.employer.keyword", KEYWORD), + hasEntry( + "semantics.projects", + (Type) new OpenSearchIndex("semantics.projects", NESTED_FIELD)), + hasEntry("semantics.projects.active", BOOLEAN), + hasEntry("semantics.projects.release", DATE), + hasEntry( + "semantics.projects.members", + (Type) new OpenSearchIndex("semantics.projects.members", NESTED_FIELD)), + hasEntry("semantics.projects.members.name", TEXT), + hasEntry("semantics.manager", OBJECT), + hasEntry("semantics.manager.name", TEXT), + hasEntry("semantics.manager.name.keyword", KEYWORD), + hasEntry("semantics.manager.address", KEYWORD), + hasEntry("semantics.manager.salary", LONG))); + } - Map typeByName = context.peek().resolveAll(Namespace.FIELD_NAME); - assertThat( - typeByName, - allOf( - aMapWithSize(41), - hasEntry("semantics", (Type) indexType), - // These are also valid because alias is optional in SQL - hasEntry("address", TEXT), - hasEntry("age", INTEGER), - hasEntry("balance", DOUBLE), - hasEntry("city", KEYWORD), - hasEntry("birthday", DATE), - hasEntry("location", GEO_POINT), - hasEntry("new_field", UNKNOWN), - hasEntry("field with spaces", TEXT), - hasEntry("employer", TEXT), - hasEntry("employer.keyword", KEYWORD), - hasEntry("projects", (Type) new OpenSearchIndex("projects", NESTED_FIELD)), - hasEntry("projects.active", BOOLEAN), - hasEntry("projects.release", DATE), - hasEntry("projects.members", (Type) new OpenSearchIndex("projects.members", NESTED_FIELD)), - hasEntry("projects.members.name", TEXT), - hasEntry("manager", OBJECT), - hasEntry("manager.name", TEXT), - hasEntry("manager.name.keyword", KEYWORD), - hasEntry("manager.address", KEYWORD), - hasEntry("manager.salary", LONG), - // These are valid because of alias specified - hasEntry("s.address", TEXT), - hasEntry("s.age", INTEGER), - hasEntry("s.balance", DOUBLE), - hasEntry("s.city", KEYWORD), - hasEntry("s.birthday", DATE), - hasEntry("s.location", GEO_POINT), - hasEntry("s.new_field", UNKNOWN), - hasEntry("s.field with spaces", TEXT), - hasEntry("s.employer", TEXT), - hasEntry("s.employer.keyword", KEYWORD), - hasEntry("s.projects", (Type) new OpenSearchIndex("s.projects", NESTED_FIELD)), - hasEntry("s.projects.active", BOOLEAN), - hasEntry("s.projects.release", DATE), - hasEntry("s.projects.members", (Type) new OpenSearchIndex("s.projects.members", NESTED_FIELD)), - hasEntry("s.projects.members.name", TEXT), - hasEntry("s.manager", OBJECT), - hasEntry("s.manager.name", TEXT), - hasEntry("s.manager.name.keyword", KEYWORD), - hasEntry("s.manager.address", KEYWORD), - hasEntry("s.manager.salary", LONG) - ) - ); - } + @Test + public void + contextShouldIncludeAllFieldsPrefixedByIndexAliasAfterVisitingIndexNameWithAliasInFromClause() { + OpenSearchIndex indexType = new OpenSearchIndex("semantics", INDEX); + analyzer.visitIndexName("semantics"); + analyzer.visitAs("s", indexType); - @Test - public void contextShouldIncludeSameFieldsAfterVisitingNestedFieldWithoutAliasInFromClause() { - OpenSearchIndex indexType = new OpenSearchIndex("semantics", INDEX); - analyzer.visitIndexName("semantics"); - analyzer.visitAs("s", indexType); - analyzer.visitIndexName("s.projects"); - analyzer.visitAs("", new OpenSearchIndex("s.projects", NESTED_FIELD)); + Map typeByName = context.peek().resolveAll(Namespace.FIELD_NAME); + assertThat( + typeByName, + allOf( + aMapWithSize(41), + hasEntry("semantics", (Type) indexType), + // These are also valid because alias is optional in SQL + hasEntry("address", TEXT), + hasEntry("age", INTEGER), + hasEntry("balance", DOUBLE), + hasEntry("city", KEYWORD), + hasEntry("birthday", DATE), + hasEntry("location", GEO_POINT), + hasEntry("new_field", UNKNOWN), + hasEntry("field with spaces", TEXT), + hasEntry("employer", TEXT), + hasEntry("employer.keyword", KEYWORD), + hasEntry("projects", (Type) new OpenSearchIndex("projects", NESTED_FIELD)), + hasEntry("projects.active", BOOLEAN), + hasEntry("projects.release", DATE), + hasEntry( + "projects.members", (Type) new OpenSearchIndex("projects.members", NESTED_FIELD)), + hasEntry("projects.members.name", TEXT), + hasEntry("manager", OBJECT), + hasEntry("manager.name", TEXT), + hasEntry("manager.name.keyword", KEYWORD), + hasEntry("manager.address", KEYWORD), + hasEntry("manager.salary", LONG), + // These are valid because of alias specified + hasEntry("s.address", TEXT), + hasEntry("s.age", INTEGER), + hasEntry("s.balance", DOUBLE), + hasEntry("s.city", KEYWORD), + hasEntry("s.birthday", DATE), + hasEntry("s.location", GEO_POINT), + hasEntry("s.new_field", UNKNOWN), + hasEntry("s.field with spaces", TEXT), + hasEntry("s.employer", TEXT), + hasEntry("s.employer.keyword", KEYWORD), + hasEntry("s.projects", (Type) new OpenSearchIndex("s.projects", NESTED_FIELD)), + hasEntry("s.projects.active", BOOLEAN), + hasEntry("s.projects.release", DATE), + hasEntry( + "s.projects.members", + (Type) new OpenSearchIndex("s.projects.members", NESTED_FIELD)), + hasEntry("s.projects.members.name", TEXT), + hasEntry("s.manager", OBJECT), + hasEntry("s.manager.name", TEXT), + hasEntry("s.manager.name.keyword", KEYWORD), + hasEntry("s.manager.address", KEYWORD), + hasEntry("s.manager.salary", LONG))); + } - Map typeByName = context.peek().resolveAll(Namespace.FIELD_NAME); - assertThat( - typeByName, - allOf( - aMapWithSize(41), - hasEntry("semantics", (Type) indexType), - // These are also valid because alias is optional in SQL - hasEntry("address", TEXT), - hasEntry("age", INTEGER), - hasEntry("balance", DOUBLE), - hasEntry("city", KEYWORD), - hasEntry("birthday", DATE), - hasEntry("location", GEO_POINT), - hasEntry("new_field", UNKNOWN), - hasEntry("field with spaces", TEXT), - hasEntry("employer", TEXT), - hasEntry("employer.keyword", KEYWORD), - hasEntry("projects", (Type) new OpenSearchIndex("projects", NESTED_FIELD)), - hasEntry("projects.active", BOOLEAN), - hasEntry("projects.release", DATE), - hasEntry("projects.members", (Type) new OpenSearchIndex("projects.members", NESTED_FIELD)), - hasEntry("projects.members.name", TEXT), - hasEntry("manager", OBJECT), - hasEntry("manager.name", TEXT), - hasEntry("manager.name.keyword", KEYWORD), - hasEntry("manager.address", KEYWORD), - hasEntry("manager.salary", LONG), - // These are valid because of alias specified - hasEntry("s.address", TEXT), - hasEntry("s.age", INTEGER), - hasEntry("s.balance", DOUBLE), - hasEntry("s.city", KEYWORD), - hasEntry("s.birthday", DATE), - hasEntry("s.location", GEO_POINT), - hasEntry("s.new_field", UNKNOWN), - hasEntry("s.field with spaces", TEXT), - hasEntry("s.employer", TEXT), - hasEntry("s.employer.keyword", KEYWORD), - hasEntry("s.projects", (Type) new OpenSearchIndex("s.projects", NESTED_FIELD)), - hasEntry("s.projects.active", BOOLEAN), - hasEntry("s.projects.release", DATE), - hasEntry("s.projects.members", (Type) new OpenSearchIndex("s.projects.members", NESTED_FIELD)), - hasEntry("s.projects.members.name", TEXT), - hasEntry("s.manager", OBJECT), - hasEntry("s.manager.name", TEXT), - hasEntry("s.manager.name.keyword", KEYWORD), - hasEntry("s.manager.address", KEYWORD), - hasEntry("s.manager.salary", LONG) - ) - ); - } + @Test + public void contextShouldIncludeSameFieldsAfterVisitingNestedFieldWithoutAliasInFromClause() { + OpenSearchIndex indexType = new OpenSearchIndex("semantics", INDEX); + analyzer.visitIndexName("semantics"); + analyzer.visitAs("s", indexType); + analyzer.visitIndexName("s.projects"); + analyzer.visitAs("", new OpenSearchIndex("s.projects", NESTED_FIELD)); - @Test - public void contextShouldIncludeMoreFieldsPrefixedByNestedFieldAliasAfterVisitingNestedFieldWithAliasInFromClause() { - OpenSearchIndex indexType = new OpenSearchIndex("semantics", INDEX); - analyzer.visitIndexName("semantics"); - analyzer.visitAs("s", indexType); - analyzer.visitIndexName("s.projects"); - analyzer.visitAs("p", new OpenSearchIndex("s.projects", NESTED_FIELD)); + Map typeByName = context.peek().resolveAll(Namespace.FIELD_NAME); + assertThat( + typeByName, + allOf( + aMapWithSize(41), + hasEntry("semantics", (Type) indexType), + // These are also valid because alias is optional in SQL + hasEntry("address", TEXT), + hasEntry("age", INTEGER), + hasEntry("balance", DOUBLE), + hasEntry("city", KEYWORD), + hasEntry("birthday", DATE), + hasEntry("location", GEO_POINT), + hasEntry("new_field", UNKNOWN), + hasEntry("field with spaces", TEXT), + hasEntry("employer", TEXT), + hasEntry("employer.keyword", KEYWORD), + hasEntry("projects", (Type) new OpenSearchIndex("projects", NESTED_FIELD)), + hasEntry("projects.active", BOOLEAN), + hasEntry("projects.release", DATE), + hasEntry( + "projects.members", (Type) new OpenSearchIndex("projects.members", NESTED_FIELD)), + hasEntry("projects.members.name", TEXT), + hasEntry("manager", OBJECT), + hasEntry("manager.name", TEXT), + hasEntry("manager.name.keyword", KEYWORD), + hasEntry("manager.address", KEYWORD), + hasEntry("manager.salary", LONG), + // These are valid because of alias specified + hasEntry("s.address", TEXT), + hasEntry("s.age", INTEGER), + hasEntry("s.balance", DOUBLE), + hasEntry("s.city", KEYWORD), + hasEntry("s.birthday", DATE), + hasEntry("s.location", GEO_POINT), + hasEntry("s.new_field", UNKNOWN), + hasEntry("s.field with spaces", TEXT), + hasEntry("s.employer", TEXT), + hasEntry("s.employer.keyword", KEYWORD), + hasEntry("s.projects", (Type) new OpenSearchIndex("s.projects", NESTED_FIELD)), + hasEntry("s.projects.active", BOOLEAN), + hasEntry("s.projects.release", DATE), + hasEntry( + "s.projects.members", + (Type) new OpenSearchIndex("s.projects.members", NESTED_FIELD)), + hasEntry("s.projects.members.name", TEXT), + hasEntry("s.manager", OBJECT), + hasEntry("s.manager.name", TEXT), + hasEntry("s.manager.name.keyword", KEYWORD), + hasEntry("s.manager.address", KEYWORD), + hasEntry("s.manager.salary", LONG))); + } - Map typeByName = context.peek().resolveAll(Namespace.FIELD_NAME); - assertThat( - typeByName, - allOf( - aMapWithSize(46), - // These are also valid because alias is optional in SQL - hasEntry("semantics", (Type) indexType), - // These are also valid because alias is optional in SQL - hasEntry("address", TEXT), - hasEntry("age", INTEGER), - hasEntry("balance", DOUBLE), - hasEntry("city", KEYWORD), - hasEntry("birthday", DATE), - hasEntry("location", GEO_POINT), - hasEntry("new_field", UNKNOWN), - hasEntry("field with spaces", TEXT), - hasEntry("employer", TEXT), - hasEntry("employer.keyword", KEYWORD), - hasEntry("projects", (Type) new OpenSearchIndex("projects", NESTED_FIELD)), - hasEntry("projects.active", BOOLEAN), - hasEntry("projects.release", DATE), - hasEntry("projects.members", (Type) new OpenSearchIndex("projects.members", NESTED_FIELD)), - hasEntry("projects.members.name", TEXT), - hasEntry("manager", OBJECT), - hasEntry("manager.name", TEXT), - hasEntry("manager.name.keyword", KEYWORD), - hasEntry("manager.address", KEYWORD), - hasEntry("manager.salary", LONG), - // These are valid because of alias specified - hasEntry("s.address", TEXT), - hasEntry("s.age", INTEGER), - hasEntry("s.balance", DOUBLE), - hasEntry("s.city", KEYWORD), - hasEntry("s.birthday", DATE), - hasEntry("s.location", GEO_POINT), - hasEntry("s.new_field", UNKNOWN), - hasEntry("s.field with spaces", TEXT), - hasEntry("s.employer", TEXT), - hasEntry("s.employer.keyword", KEYWORD), - hasEntry("s.projects", (Type) new OpenSearchIndex("s.projects", NESTED_FIELD)), - hasEntry("s.projects.active", BOOLEAN), - hasEntry("s.projects.release", DATE), - hasEntry("s.projects.members", (Type) new OpenSearchIndex("s.projects.members", NESTED_FIELD)), - hasEntry("s.projects.members.name", TEXT), - hasEntry("s.manager", OBJECT), - hasEntry("s.manager.name", TEXT), - hasEntry("s.manager.name.keyword", KEYWORD), - hasEntry("s.manager.address", KEYWORD), - hasEntry("s.manager.salary", LONG), - // Valid because of nested field alias specified - hasEntry("p", (Type) new OpenSearchIndex("s.projects", NESTED_FIELD)), - hasEntry("p.active", BOOLEAN), - hasEntry("p.release", DATE), - hasEntry("p.members", (Type) new OpenSearchIndex("s.projects.members", NESTED_FIELD)), - hasEntry("p.members.name", TEXT) - ) - ); - } + @Test + public void + contextShouldIncludeMoreFieldsPrefixedByNestedFieldAliasAfterVisitingNestedFieldWithAliasInFromClause() { + OpenSearchIndex indexType = new OpenSearchIndex("semantics", INDEX); + analyzer.visitIndexName("semantics"); + analyzer.visitAs("s", indexType); + analyzer.visitIndexName("s.projects"); + analyzer.visitAs("p", new OpenSearchIndex("s.projects", NESTED_FIELD)); - @Test - public void contextShouldIncludeMoreFieldsPrefixedByNestedFieldAliasAfterVisitingDeepNestedFieldWithAliasInFromClause() { - OpenSearchIndex indexType = new OpenSearchIndex("semantics", INDEX); - analyzer.visitIndexName("semantics"); - analyzer.visitAs("s", indexType); - analyzer.visitIndexName("s.projects.members"); - analyzer.visitAs("m", new OpenSearchIndex("s.projects.members", NESTED_FIELD)); + Map typeByName = context.peek().resolveAll(Namespace.FIELD_NAME); + assertThat( + typeByName, + allOf( + aMapWithSize(46), + // These are also valid because alias is optional in SQL + hasEntry("semantics", (Type) indexType), + // These are also valid because alias is optional in SQL + hasEntry("address", TEXT), + hasEntry("age", INTEGER), + hasEntry("balance", DOUBLE), + hasEntry("city", KEYWORD), + hasEntry("birthday", DATE), + hasEntry("location", GEO_POINT), + hasEntry("new_field", UNKNOWN), + hasEntry("field with spaces", TEXT), + hasEntry("employer", TEXT), + hasEntry("employer.keyword", KEYWORD), + hasEntry("projects", (Type) new OpenSearchIndex("projects", NESTED_FIELD)), + hasEntry("projects.active", BOOLEAN), + hasEntry("projects.release", DATE), + hasEntry( + "projects.members", (Type) new OpenSearchIndex("projects.members", NESTED_FIELD)), + hasEntry("projects.members.name", TEXT), + hasEntry("manager", OBJECT), + hasEntry("manager.name", TEXT), + hasEntry("manager.name.keyword", KEYWORD), + hasEntry("manager.address", KEYWORD), + hasEntry("manager.salary", LONG), + // These are valid because of alias specified + hasEntry("s.address", TEXT), + hasEntry("s.age", INTEGER), + hasEntry("s.balance", DOUBLE), + hasEntry("s.city", KEYWORD), + hasEntry("s.birthday", DATE), + hasEntry("s.location", GEO_POINT), + hasEntry("s.new_field", UNKNOWN), + hasEntry("s.field with spaces", TEXT), + hasEntry("s.employer", TEXT), + hasEntry("s.employer.keyword", KEYWORD), + hasEntry("s.projects", (Type) new OpenSearchIndex("s.projects", NESTED_FIELD)), + hasEntry("s.projects.active", BOOLEAN), + hasEntry("s.projects.release", DATE), + hasEntry( + "s.projects.members", + (Type) new OpenSearchIndex("s.projects.members", NESTED_FIELD)), + hasEntry("s.projects.members.name", TEXT), + hasEntry("s.manager", OBJECT), + hasEntry("s.manager.name", TEXT), + hasEntry("s.manager.name.keyword", KEYWORD), + hasEntry("s.manager.address", KEYWORD), + hasEntry("s.manager.salary", LONG), + // Valid because of nested field alias specified + hasEntry("p", (Type) new OpenSearchIndex("s.projects", NESTED_FIELD)), + hasEntry("p.active", BOOLEAN), + hasEntry("p.release", DATE), + hasEntry("p.members", (Type) new OpenSearchIndex("s.projects.members", NESTED_FIELD)), + hasEntry("p.members.name", TEXT))); + } - Map typeByName = context.peek().resolveAll(Namespace.FIELD_NAME); + @Test + public void + contextShouldIncludeMoreFieldsPrefixedByNestedFieldAliasAfterVisitingDeepNestedFieldWithAliasInFromClause() { + OpenSearchIndex indexType = new OpenSearchIndex("semantics", INDEX); + analyzer.visitIndexName("semantics"); + analyzer.visitAs("s", indexType); + analyzer.visitIndexName("s.projects.members"); + analyzer.visitAs("m", new OpenSearchIndex("s.projects.members", NESTED_FIELD)); - assertThat( - typeByName, - allOf( - aMapWithSize(43), - hasEntry("semantics", (Type) indexType), - // These are also valid because alias is optional in SQL - hasEntry("address", TEXT), - hasEntry("age", INTEGER), - hasEntry("balance", DOUBLE), - hasEntry("city", KEYWORD), - hasEntry("birthday", DATE), - hasEntry("location", GEO_POINT), - hasEntry("new_field", UNKNOWN), - hasEntry("field with spaces", TEXT), - hasEntry("employer", TEXT), - hasEntry("employer.keyword", KEYWORD), - hasEntry("projects", (Type) new OpenSearchIndex("projects", NESTED_FIELD)), - hasEntry("projects.active", BOOLEAN), - hasEntry("projects.release", DATE), - hasEntry("projects.members", (Type) new OpenSearchIndex("projects.members", NESTED_FIELD)), - hasEntry("projects.members.name", TEXT), - hasEntry("manager", OBJECT), - hasEntry("manager.name", TEXT), - hasEntry("manager.name.keyword", KEYWORD), - hasEntry("manager.address", KEYWORD), - hasEntry("manager.salary", LONG), - // These are valid because of alias specified - hasEntry("s.address", TEXT), - hasEntry("s.age", INTEGER), - hasEntry("s.balance", DOUBLE), - hasEntry("s.city", KEYWORD), - hasEntry("s.birthday", DATE), - hasEntry("s.location", GEO_POINT), - hasEntry("s.new_field", UNKNOWN), - hasEntry("s.field with spaces", TEXT), - hasEntry("s.employer", TEXT), - hasEntry("s.employer.keyword", KEYWORD), - hasEntry("s.projects", (Type) new OpenSearchIndex("s.projects", NESTED_FIELD)), - hasEntry("s.projects.active", BOOLEAN), - hasEntry("s.projects.release", DATE), - hasEntry("s.projects.members", (Type) new OpenSearchIndex("s.projects.members", NESTED_FIELD)), - hasEntry("s.projects.members.name", TEXT), - hasEntry("s.manager", OBJECT), - hasEntry("s.manager.name", TEXT), - hasEntry("s.manager.name.keyword", KEYWORD), - hasEntry("s.manager.address", KEYWORD), - hasEntry("s.manager.salary", LONG), - // Valid because of deep nested field alias specified - hasEntry("m", (Type) new OpenSearchIndex("s.projects.members", NESTED_FIELD)), - hasEntry("m.name", TEXT) - ) - ); - } + Map typeByName = context.peek().resolveAll(Namespace.FIELD_NAME); - @Test - public void contextShouldIncludeMoreFieldsPrefixedByNestedFieldAliasAfterVisitingAllNestedFieldsWithAliasInFromClause() { - OpenSearchIndex indexType = new OpenSearchIndex("semantics", INDEX); - analyzer.visitIndexName("semantics"); - analyzer.visitAs("s", indexType); - analyzer.visitIndexName("s.projects"); - analyzer.visitAs("p", new OpenSearchIndex("s.projects", NESTED_FIELD)); - analyzer.visitIndexName("s.projects.members"); - analyzer.visitAs("m", new OpenSearchIndex("s.projects.members", NESTED_FIELD)); + assertThat( + typeByName, + allOf( + aMapWithSize(43), + hasEntry("semantics", (Type) indexType), + // These are also valid because alias is optional in SQL + hasEntry("address", TEXT), + hasEntry("age", INTEGER), + hasEntry("balance", DOUBLE), + hasEntry("city", KEYWORD), + hasEntry("birthday", DATE), + hasEntry("location", GEO_POINT), + hasEntry("new_field", UNKNOWN), + hasEntry("field with spaces", TEXT), + hasEntry("employer", TEXT), + hasEntry("employer.keyword", KEYWORD), + hasEntry("projects", (Type) new OpenSearchIndex("projects", NESTED_FIELD)), + hasEntry("projects.active", BOOLEAN), + hasEntry("projects.release", DATE), + hasEntry( + "projects.members", (Type) new OpenSearchIndex("projects.members", NESTED_FIELD)), + hasEntry("projects.members.name", TEXT), + hasEntry("manager", OBJECT), + hasEntry("manager.name", TEXT), + hasEntry("manager.name.keyword", KEYWORD), + hasEntry("manager.address", KEYWORD), + hasEntry("manager.salary", LONG), + // These are valid because of alias specified + hasEntry("s.address", TEXT), + hasEntry("s.age", INTEGER), + hasEntry("s.balance", DOUBLE), + hasEntry("s.city", KEYWORD), + hasEntry("s.birthday", DATE), + hasEntry("s.location", GEO_POINT), + hasEntry("s.new_field", UNKNOWN), + hasEntry("s.field with spaces", TEXT), + hasEntry("s.employer", TEXT), + hasEntry("s.employer.keyword", KEYWORD), + hasEntry("s.projects", (Type) new OpenSearchIndex("s.projects", NESTED_FIELD)), + hasEntry("s.projects.active", BOOLEAN), + hasEntry("s.projects.release", DATE), + hasEntry( + "s.projects.members", + (Type) new OpenSearchIndex("s.projects.members", NESTED_FIELD)), + hasEntry("s.projects.members.name", TEXT), + hasEntry("s.manager", OBJECT), + hasEntry("s.manager.name", TEXT), + hasEntry("s.manager.name.keyword", KEYWORD), + hasEntry("s.manager.address", KEYWORD), + hasEntry("s.manager.salary", LONG), + // Valid because of deep nested field alias specified + hasEntry("m", (Type) new OpenSearchIndex("s.projects.members", NESTED_FIELD)), + hasEntry("m.name", TEXT))); + } - Map typeByName = context.peek().resolveAll(Namespace.FIELD_NAME); - assertThat( - typeByName, - allOf( - aMapWithSize(48), - hasEntry("semantics", (Type) indexType), - // These are also valid because alias is optional in SQL - hasEntry("address", TEXT), - hasEntry("age", INTEGER), - hasEntry("balance", DOUBLE), - hasEntry("city", KEYWORD), - hasEntry("birthday", DATE), - hasEntry("location", GEO_POINT), - hasEntry("new_field", UNKNOWN), - hasEntry("field with spaces", TEXT), - hasEntry("employer", TEXT), - hasEntry("employer.keyword", KEYWORD), - hasEntry("projects", (Type) new OpenSearchIndex("projects", NESTED_FIELD)), - hasEntry("projects.active", BOOLEAN), - hasEntry("projects.release", DATE), - hasEntry("projects.members", (Type) new OpenSearchIndex("projects.members", NESTED_FIELD)), - hasEntry("projects.members.name", TEXT), - hasEntry("manager", OBJECT), - hasEntry("manager.name", TEXT), - hasEntry("manager.name.keyword", KEYWORD), - hasEntry("manager.address", KEYWORD), - hasEntry("manager.salary", LONG), - // These are valid because of alias specified - hasEntry("s.address", TEXT), - hasEntry("s.age", INTEGER), - hasEntry("s.balance", DOUBLE), - hasEntry("s.city", KEYWORD), - hasEntry("s.birthday", DATE), - hasEntry("s.location", GEO_POINT), - hasEntry("s.new_field", UNKNOWN), - hasEntry("s.field with spaces", TEXT), - hasEntry("s.employer", TEXT), - hasEntry("s.employer.keyword", KEYWORD), - hasEntry("s.projects", (Type) new OpenSearchIndex("s.projects", NESTED_FIELD)), - hasEntry("s.projects.active", BOOLEAN), - hasEntry("s.projects.release", DATE), - hasEntry("s.projects.members", (Type) new OpenSearchIndex("s.projects.members", NESTED_FIELD)), - hasEntry("s.projects.members.name", TEXT), - hasEntry("s.manager", OBJECT), - hasEntry("s.manager.name", TEXT), - hasEntry("s.manager.name.keyword", KEYWORD), - hasEntry("s.manager.address", KEYWORD), - hasEntry("s.manager.salary", LONG), - // Valid because of nested field alias specified - hasEntry("p", (Type) new OpenSearchIndex("s.projects", NESTED_FIELD)), - hasEntry("p.active", BOOLEAN), - hasEntry("p.release", DATE), - hasEntry("p.members", (Type) new OpenSearchIndex("s.projects.members", NESTED_FIELD)), - hasEntry("p.members.name", TEXT), - // Valid because of deep nested field alias specified - hasEntry("m", (Type) new OpenSearchIndex("s.projects.members", NESTED_FIELD)), - hasEntry("m.name", TEXT) - ) - ); - } + @Test + public void + contextShouldIncludeMoreFieldsPrefixedByNestedFieldAliasAfterVisitingAllNestedFieldsWithAliasInFromClause() { + OpenSearchIndex indexType = new OpenSearchIndex("semantics", INDEX); + analyzer.visitIndexName("semantics"); + analyzer.visitAs("s", indexType); + analyzer.visitIndexName("s.projects"); + analyzer.visitAs("p", new OpenSearchIndex("s.projects", NESTED_FIELD)); + analyzer.visitIndexName("s.projects.members"); + analyzer.visitAs("m", new OpenSearchIndex("s.projects.members", NESTED_FIELD)); - @Test - public void contextShouldIncludeMoreFieldsPrefixedByNestedFieldAliasAfterVisitingNestedFieldWithAliasInSubqueryFromClause() { - OpenSearchIndex indexType = new OpenSearchIndex("semantics", INDEX); - analyzer.visitIndexName("semantics"); - analyzer.visitAs("s", indexType); + Map typeByName = context.peek().resolveAll(Namespace.FIELD_NAME); + assertThat( + typeByName, + allOf( + aMapWithSize(48), + hasEntry("semantics", (Type) indexType), + // These are also valid because alias is optional in SQL + hasEntry("address", TEXT), + hasEntry("age", INTEGER), + hasEntry("balance", DOUBLE), + hasEntry("city", KEYWORD), + hasEntry("birthday", DATE), + hasEntry("location", GEO_POINT), + hasEntry("new_field", UNKNOWN), + hasEntry("field with spaces", TEXT), + hasEntry("employer", TEXT), + hasEntry("employer.keyword", KEYWORD), + hasEntry("projects", (Type) new OpenSearchIndex("projects", NESTED_FIELD)), + hasEntry("projects.active", BOOLEAN), + hasEntry("projects.release", DATE), + hasEntry( + "projects.members", (Type) new OpenSearchIndex("projects.members", NESTED_FIELD)), + hasEntry("projects.members.name", TEXT), + hasEntry("manager", OBJECT), + hasEntry("manager.name", TEXT), + hasEntry("manager.name.keyword", KEYWORD), + hasEntry("manager.address", KEYWORD), + hasEntry("manager.salary", LONG), + // These are valid because of alias specified + hasEntry("s.address", TEXT), + hasEntry("s.age", INTEGER), + hasEntry("s.balance", DOUBLE), + hasEntry("s.city", KEYWORD), + hasEntry("s.birthday", DATE), + hasEntry("s.location", GEO_POINT), + hasEntry("s.new_field", UNKNOWN), + hasEntry("s.field with spaces", TEXT), + hasEntry("s.employer", TEXT), + hasEntry("s.employer.keyword", KEYWORD), + hasEntry("s.projects", (Type) new OpenSearchIndex("s.projects", NESTED_FIELD)), + hasEntry("s.projects.active", BOOLEAN), + hasEntry("s.projects.release", DATE), + hasEntry( + "s.projects.members", + (Type) new OpenSearchIndex("s.projects.members", NESTED_FIELD)), + hasEntry("s.projects.members.name", TEXT), + hasEntry("s.manager", OBJECT), + hasEntry("s.manager.name", TEXT), + hasEntry("s.manager.name.keyword", KEYWORD), + hasEntry("s.manager.address", KEYWORD), + hasEntry("s.manager.salary", LONG), + // Valid because of nested field alias specified + hasEntry("p", (Type) new OpenSearchIndex("s.projects", NESTED_FIELD)), + hasEntry("p.active", BOOLEAN), + hasEntry("p.release", DATE), + hasEntry("p.members", (Type) new OpenSearchIndex("s.projects.members", NESTED_FIELD)), + hasEntry("p.members.name", TEXT), + // Valid because of deep nested field alias specified + hasEntry("m", (Type) new OpenSearchIndex("s.projects.members", NESTED_FIELD)), + hasEntry("m.name", TEXT))); + } - context.push(); - analyzer.visitIndexName("s.projects"); - analyzer.visitAs("p", new OpenSearchIndex("s.projects", NESTED_FIELD)); + @Test + public void + contextShouldIncludeMoreFieldsPrefixedByNestedFieldAliasAfterVisitingNestedFieldWithAliasInSubqueryFromClause() { + OpenSearchIndex indexType = new OpenSearchIndex("semantics", INDEX); + analyzer.visitIndexName("semantics"); + analyzer.visitAs("s", indexType); - Map typeByName = context.peek().resolveAll(Namespace.FIELD_NAME); - assertThat( - typeByName, - allOf( - aMapWithSize(46), - // These are also valid because alias is optional in SQL - hasEntry("semantics", (Type) indexType), - // These are also valid because alias is optional in SQL - hasEntry("address", TEXT), - hasEntry("age", INTEGER), - hasEntry("balance", DOUBLE), - hasEntry("city", KEYWORD), - hasEntry("birthday", DATE), - hasEntry("location", GEO_POINT), - hasEntry("new_field", UNKNOWN), - hasEntry("field with spaces", TEXT), - hasEntry("employer", TEXT), - hasEntry("employer.keyword", KEYWORD), - hasEntry("projects", (Type) new OpenSearchIndex("projects", NESTED_FIELD)), - hasEntry("projects.active", BOOLEAN), - hasEntry("projects.release", DATE), - hasEntry("projects.members", (Type) new OpenSearchIndex("projects.members", NESTED_FIELD)), - hasEntry("projects.members.name", TEXT), - hasEntry("manager", OBJECT), - hasEntry("manager.name", TEXT), - hasEntry("manager.name.keyword", KEYWORD), - hasEntry("manager.address", KEYWORD), - hasEntry("manager.salary", LONG), - // These are valid because of alias specified - hasEntry("s.address", TEXT), - hasEntry("s.age", INTEGER), - hasEntry("s.balance", DOUBLE), - hasEntry("s.city", KEYWORD), - hasEntry("s.birthday", DATE), - hasEntry("s.location", GEO_POINT), - hasEntry("s.new_field", UNKNOWN), - hasEntry("s.field with spaces", TEXT), - hasEntry("s.employer", TEXT), - hasEntry("s.employer.keyword", KEYWORD), - hasEntry("s.projects", (Type) new OpenSearchIndex("s.projects", NESTED_FIELD)), - hasEntry("s.projects.active", BOOLEAN), - hasEntry("s.projects.release", DATE), - hasEntry("s.projects.members", (Type) new OpenSearchIndex("s.projects.members", NESTED_FIELD)), - hasEntry("s.projects.members.name", TEXT), - hasEntry("s.manager", OBJECT), - hasEntry("s.manager.name", TEXT), - hasEntry("s.manager.name.keyword", KEYWORD), - hasEntry("s.manager.address", KEYWORD), - hasEntry("s.manager.salary", LONG), - // Valid because of nested field alias specified - hasEntry("p", (Type) new OpenSearchIndex("s.projects", NESTED_FIELD)), - hasEntry("p.active", BOOLEAN), - hasEntry("p.release", DATE), - hasEntry("p.members", (Type) new OpenSearchIndex("s.projects.members", NESTED_FIELD)), - hasEntry("p.members.name", TEXT) - ) - ); + context.push(); + analyzer.visitIndexName("s.projects"); + analyzer.visitAs("p", new OpenSearchIndex("s.projects", NESTED_FIELD)); - context.pop(); - typeByName = context.peek().resolveAll(Namespace.FIELD_NAME); - assertThat( - typeByName, - allOf( - aMapWithSize(41), - hasEntry("semantics", (Type) indexType), - // These are also valid because alias is optional in SQL - hasEntry("address", TEXT), - hasEntry("age", INTEGER), - hasEntry("balance", DOUBLE), - hasEntry("city", KEYWORD), - hasEntry("birthday", DATE), - hasEntry("location", GEO_POINT), - hasEntry("new_field", UNKNOWN), - hasEntry("field with spaces", TEXT), - hasEntry("employer", TEXT), - hasEntry("employer.keyword", KEYWORD), - hasEntry("projects", (Type) new OpenSearchIndex("projects", NESTED_FIELD)), - hasEntry("projects.active", BOOLEAN), - hasEntry("projects.release", DATE), - hasEntry("projects.members", (Type) new OpenSearchIndex("projects.members", NESTED_FIELD)), - hasEntry("projects.members.name", TEXT), - hasEntry("manager", OBJECT), - hasEntry("manager.name", TEXT), - hasEntry("manager.name.keyword", KEYWORD), - hasEntry("manager.address", KEYWORD), - hasEntry("manager.salary", LONG), - // These are valid because of alias specified - hasEntry("s.address", TEXT), - hasEntry("s.age", INTEGER), - hasEntry("s.balance", DOUBLE), - hasEntry("s.city", KEYWORD), - hasEntry("s.birthday", DATE), - hasEntry("s.location", GEO_POINT), - hasEntry("s.new_field", UNKNOWN), - hasEntry("s.field with spaces", TEXT), - hasEntry("s.employer", TEXT), - hasEntry("s.employer.keyword", KEYWORD), - hasEntry("s.projects", (Type) new OpenSearchIndex("s.projects", NESTED_FIELD)), - hasEntry("s.projects.active", BOOLEAN), - hasEntry("s.projects.release", DATE), - hasEntry("s.projects.members", (Type) new OpenSearchIndex("s.projects.members", NESTED_FIELD)), - hasEntry("s.projects.members.name", TEXT), - hasEntry("s.manager", OBJECT), - hasEntry("s.manager.name", TEXT), - hasEntry("s.manager.name.keyword", KEYWORD), - hasEntry("s.manager.address", KEYWORD), - hasEntry("s.manager.salary", LONG) - ) - ); - } + Map typeByName = context.peek().resolveAll(Namespace.FIELD_NAME); + assertThat( + typeByName, + allOf( + aMapWithSize(46), + // These are also valid because alias is optional in SQL + hasEntry("semantics", (Type) indexType), + // These are also valid because alias is optional in SQL + hasEntry("address", TEXT), + hasEntry("age", INTEGER), + hasEntry("balance", DOUBLE), + hasEntry("city", KEYWORD), + hasEntry("birthday", DATE), + hasEntry("location", GEO_POINT), + hasEntry("new_field", UNKNOWN), + hasEntry("field with spaces", TEXT), + hasEntry("employer", TEXT), + hasEntry("employer.keyword", KEYWORD), + hasEntry("projects", (Type) new OpenSearchIndex("projects", NESTED_FIELD)), + hasEntry("projects.active", BOOLEAN), + hasEntry("projects.release", DATE), + hasEntry( + "projects.members", (Type) new OpenSearchIndex("projects.members", NESTED_FIELD)), + hasEntry("projects.members.name", TEXT), + hasEntry("manager", OBJECT), + hasEntry("manager.name", TEXT), + hasEntry("manager.name.keyword", KEYWORD), + hasEntry("manager.address", KEYWORD), + hasEntry("manager.salary", LONG), + // These are valid because of alias specified + hasEntry("s.address", TEXT), + hasEntry("s.age", INTEGER), + hasEntry("s.balance", DOUBLE), + hasEntry("s.city", KEYWORD), + hasEntry("s.birthday", DATE), + hasEntry("s.location", GEO_POINT), + hasEntry("s.new_field", UNKNOWN), + hasEntry("s.field with spaces", TEXT), + hasEntry("s.employer", TEXT), + hasEntry("s.employer.keyword", KEYWORD), + hasEntry("s.projects", (Type) new OpenSearchIndex("s.projects", NESTED_FIELD)), + hasEntry("s.projects.active", BOOLEAN), + hasEntry("s.projects.release", DATE), + hasEntry( + "s.projects.members", + (Type) new OpenSearchIndex("s.projects.members", NESTED_FIELD)), + hasEntry("s.projects.members.name", TEXT), + hasEntry("s.manager", OBJECT), + hasEntry("s.manager.name", TEXT), + hasEntry("s.manager.name.keyword", KEYWORD), + hasEntry("s.manager.address", KEYWORD), + hasEntry("s.manager.salary", LONG), + // Valid because of nested field alias specified + hasEntry("p", (Type) new OpenSearchIndex("s.projects", NESTED_FIELD)), + hasEntry("p.active", BOOLEAN), + hasEntry("p.release", DATE), + hasEntry("p.members", (Type) new OpenSearchIndex("s.projects.members", NESTED_FIELD)), + hasEntry("p.members.name", TEXT))); - @Test - public void fieldWithUnknownEsTypeShouldPass() { - analyzer.visitIndexName("semantics"); - Optional type = context.peek().resolve(new Symbol(Namespace.FIELD_NAME, "new_field")); - Assert.assertTrue(type.isPresent()); - Assert.assertSame(UNKNOWN, type.get()); - } + context.pop(); + typeByName = context.peek().resolveAll(Namespace.FIELD_NAME); + assertThat( + typeByName, + allOf( + aMapWithSize(41), + hasEntry("semantics", (Type) indexType), + // These are also valid because alias is optional in SQL + hasEntry("address", TEXT), + hasEntry("age", INTEGER), + hasEntry("balance", DOUBLE), + hasEntry("city", KEYWORD), + hasEntry("birthday", DATE), + hasEntry("location", GEO_POINT), + hasEntry("new_field", UNKNOWN), + hasEntry("field with spaces", TEXT), + hasEntry("employer", TEXT), + hasEntry("employer.keyword", KEYWORD), + hasEntry("projects", (Type) new OpenSearchIndex("projects", NESTED_FIELD)), + hasEntry("projects.active", BOOLEAN), + hasEntry("projects.release", DATE), + hasEntry( + "projects.members", (Type) new OpenSearchIndex("projects.members", NESTED_FIELD)), + hasEntry("projects.members.name", TEXT), + hasEntry("manager", OBJECT), + hasEntry("manager.name", TEXT), + hasEntry("manager.name.keyword", KEYWORD), + hasEntry("manager.address", KEYWORD), + hasEntry("manager.salary", LONG), + // These are valid because of alias specified + hasEntry("s.address", TEXT), + hasEntry("s.age", INTEGER), + hasEntry("s.balance", DOUBLE), + hasEntry("s.city", KEYWORD), + hasEntry("s.birthday", DATE), + hasEntry("s.location", GEO_POINT), + hasEntry("s.new_field", UNKNOWN), + hasEntry("s.field with spaces", TEXT), + hasEntry("s.employer", TEXT), + hasEntry("s.employer.keyword", KEYWORD), + hasEntry("s.projects", (Type) new OpenSearchIndex("s.projects", NESTED_FIELD)), + hasEntry("s.projects.active", BOOLEAN), + hasEntry("s.projects.release", DATE), + hasEntry( + "s.projects.members", + (Type) new OpenSearchIndex("s.projects.members", NESTED_FIELD)), + hasEntry("s.projects.members.name", TEXT), + hasEntry("s.manager", OBJECT), + hasEntry("s.manager.name", TEXT), + hasEntry("s.manager.name.keyword", KEYWORD), + hasEntry("s.manager.address", KEYWORD), + hasEntry("s.manager.salary", LONG))); + } - @Test - public void fieldWithSpacesInNameShouldPass() { - analyzer.visitIndexName("semantics"); - Optional type = context.peek().resolve(new Symbol(Namespace.FIELD_NAME, "field with spaces")); - Assert.assertTrue(type.isPresent()); - Assert.assertSame(TEXT, type.get()); - } + @Test + public void fieldWithUnknownEsTypeShouldPass() { + analyzer.visitIndexName("semantics"); + Optional type = context.peek().resolve(new Symbol(Namespace.FIELD_NAME, "new_field")); + Assert.assertTrue(type.isPresent()); + Assert.assertSame(UNKNOWN, type.get()); + } + @Test + public void fieldWithSpacesInNameShouldPass() { + analyzer.visitIndexName("semantics"); + Optional type = + context.peek().resolve(new Symbol(Namespace.FIELD_NAME, "field with spaces")); + Assert.assertTrue(type.isPresent()); + Assert.assertSame(TEXT, type.get()); + } } diff --git a/legacy/src/test/java/org/opensearch/sql/legacy/antlr/semantic/SemanticAnalyzerConfigTest.java b/legacy/src/test/java/org/opensearch/sql/legacy/antlr/semantic/SemanticAnalyzerConfigTest.java index 18253bd71f..2b9a5e418c 100644 --- a/legacy/src/test/java/org/opensearch/sql/legacy/antlr/semantic/SemanticAnalyzerConfigTest.java +++ b/legacy/src/test/java/org/opensearch/sql/legacy/antlr/semantic/SemanticAnalyzerConfigTest.java @@ -3,7 +3,6 @@ * SPDX-License-Identifier: Apache-2.0 */ - package org.opensearch.sql.legacy.antlr.semantic; import static org.hamcrest.Matchers.allOf; @@ -17,54 +16,47 @@ import org.opensearch.sql.legacy.antlr.SqlAnalysisConfig; import org.opensearch.sql.legacy.esdomain.LocalClusterState; -/** - * Test cases for semantic analysis configuration - */ +/** Test cases for semantic analysis configuration */ public class SemanticAnalyzerConfigTest extends SemanticAnalyzerTestBase { - @Rule - public ExpectedException exceptionWithoutSuggestion = ExpectedException.none(); - - @Test - public void noAnalysisShouldPerformForNonSelectStatement() { - String sql = "DELETE FROM semantics WHERE age12 = 123"; - expectValidationPassWithConfig(sql, new SqlAnalysisConfig(true, true, 1000)); - } - - @Test - public void noAnalysisShouldPerformIfDisabledAnalysis() { - String sql = "SELECT * FROM semantics WHERE age12 = 123"; - expectValidationFailWithErrorMessages(sql, "Field [age12] cannot be found or used here."); - expectValidationPassWithConfig(sql, new SqlAnalysisConfig(false, true, 1000)); - } - - @Test - public void noFieldNameSuggestionIfDisabledSuggestion() { - String sql = "SELECT * FROM semantics WHERE age12 = 123"; - expectValidationFailWithErrorMessages(sql, - "Field [age12] cannot be found or used here.", - "Did you mean [age]?"); - - exceptionWithoutSuggestion.expect(SemanticAnalysisException.class); - exceptionWithoutSuggestion.expectMessage( - allOf( - containsString("Field [age12] cannot be found or used here"), - not(containsString("Did you mean")) - ) - ); - new OpenSearchLegacySqlAnalyzer(new SqlAnalysisConfig(true, false, 1000)). - analyze(sql, LocalClusterState.state()); - } - - @Test - public void noAnalysisShouldPerformIfIndexMappingIsLargerThanThreshold() { - String sql = "SELECT * FROM semantics WHERE test = 123"; - expectValidationFailWithErrorMessages(sql, "Field [test] cannot be found or used here."); - expectValidationPassWithConfig(sql, new SqlAnalysisConfig(true, true, 1)); - } - - private void expectValidationPassWithConfig(String sql, SqlAnalysisConfig config) { - new OpenSearchLegacySqlAnalyzer(config).analyze(sql, LocalClusterState.state()); - } - + @Rule public ExpectedException exceptionWithoutSuggestion = ExpectedException.none(); + + @Test + public void noAnalysisShouldPerformForNonSelectStatement() { + String sql = "DELETE FROM semantics WHERE age12 = 123"; + expectValidationPassWithConfig(sql, new SqlAnalysisConfig(true, true, 1000)); + } + + @Test + public void noAnalysisShouldPerformIfDisabledAnalysis() { + String sql = "SELECT * FROM semantics WHERE age12 = 123"; + expectValidationFailWithErrorMessages(sql, "Field [age12] cannot be found or used here."); + expectValidationPassWithConfig(sql, new SqlAnalysisConfig(false, true, 1000)); + } + + @Test + public void noFieldNameSuggestionIfDisabledSuggestion() { + String sql = "SELECT * FROM semantics WHERE age12 = 123"; + expectValidationFailWithErrorMessages( + sql, "Field [age12] cannot be found or used here.", "Did you mean [age]?"); + + exceptionWithoutSuggestion.expect(SemanticAnalysisException.class); + exceptionWithoutSuggestion.expectMessage( + allOf( + containsString("Field [age12] cannot be found or used here"), + not(containsString("Did you mean")))); + new OpenSearchLegacySqlAnalyzer(new SqlAnalysisConfig(true, false, 1000)) + .analyze(sql, LocalClusterState.state()); + } + + @Test + public void noAnalysisShouldPerformIfIndexMappingIsLargerThanThreshold() { + String sql = "SELECT * FROM semantics WHERE test = 123"; + expectValidationFailWithErrorMessages(sql, "Field [test] cannot be found or used here."); + expectValidationPassWithConfig(sql, new SqlAnalysisConfig(true, true, 1)); + } + + private void expectValidationPassWithConfig(String sql, SqlAnalysisConfig config) { + new OpenSearchLegacySqlAnalyzer(config).analyze(sql, LocalClusterState.state()); + } } diff --git a/legacy/src/test/java/org/opensearch/sql/legacy/antlr/semantic/SemanticAnalyzerConstantTest.java b/legacy/src/test/java/org/opensearch/sql/legacy/antlr/semantic/SemanticAnalyzerConstantTest.java index 5ff8875f0c..48d9b6e36c 100644 --- a/legacy/src/test/java/org/opensearch/sql/legacy/antlr/semantic/SemanticAnalyzerConstantTest.java +++ b/legacy/src/test/java/org/opensearch/sql/legacy/antlr/semantic/SemanticAnalyzerConstantTest.java @@ -3,21 +3,19 @@ * SPDX-License-Identifier: Apache-2.0 */ - package org.opensearch.sql.legacy.antlr.semantic; import org.junit.Test; public class SemanticAnalyzerConstantTest extends SemanticAnalyzerTestBase { - @Test - public void useNegativeIntegerShouldPass() { - validate("SELECT * FROM test WHERE age > -1"); - } - - @Test - public void useNegativeFloatingPointNumberShouldPass() { - validate("SELECT * FROM test WHERE balance > -1.23456"); - } + @Test + public void useNegativeIntegerShouldPass() { + validate("SELECT * FROM test WHERE age > -1"); + } + @Test + public void useNegativeFloatingPointNumberShouldPass() { + validate("SELECT * FROM test WHERE balance > -1.23456"); + } } diff --git a/legacy/src/test/java/org/opensearch/sql/legacy/antlr/semantic/SemanticAnalyzerESScalarFunctionTest.java b/legacy/src/test/java/org/opensearch/sql/legacy/antlr/semantic/SemanticAnalyzerESScalarFunctionTest.java index 32c322f8c2..c16ecc33e3 100644 --- a/legacy/src/test/java/org/opensearch/sql/legacy/antlr/semantic/SemanticAnalyzerESScalarFunctionTest.java +++ b/legacy/src/test/java/org/opensearch/sql/legacy/antlr/semantic/SemanticAnalyzerESScalarFunctionTest.java @@ -3,54 +3,50 @@ * SPDX-License-Identifier: Apache-2.0 */ - package org.opensearch.sql.legacy.antlr.semantic; import org.junit.Test; -/** - * Semantic analysis test for Elaticsearch special scalar functions - */ +/** Semantic analysis test for Elaticsearch special scalar functions */ public class SemanticAnalyzerESScalarFunctionTest extends SemanticAnalyzerTestBase { - @Test - public void dateFunctionCallWithDateInSelectClauseShouldPass() { - validate("SELECT DAY_OF_MONTH(birthday) FROM semantics"); - validate("SELECT DAY_OF_WEEK(birthday) FROM semantics"); - validate("SELECT DAY_OF_YEAR(birthday) FROM semantics"); - validate("SELECT MINUTE_OF_DAY(birthday) FROM semantics"); - validate("SELECT MINUTE_OF_HOUR(birthday) FROM semantics"); - validate("SELECT MONTH_OF_YEAR(birthday) FROM semantics"); - validate("SELECT WEEK_OF_YEAR(birthday) FROM semantics"); - } - - @Test - public void dateFunctionCallWithDateInWhereClauseShouldPass() { - validate("SELECT * FROM semantics WHERE DAY_OF_MONTH(birthday) = 1"); - validate("SELECT * FROM semantics WHERE DAY_OF_WEEK(birthday) = 1"); - validate("SELECT * FROM semantics WHERE DAY_OF_YEAR(birthday) = 1"); - validate("SELECT * FROM semantics WHERE MINUTE_OF_DAY(birthday) = 1"); - validate("SELECT * FROM semantics WHERE MINUTE_OF_HOUR(birthday) = 1"); - validate("SELECT * FROM semantics WHERE MONTH_OF_YEAR(birthday) = 1"); - validate("SELECT * FROM semantics WHERE WEEK_OF_YEAR(birthday) = 1"); - } - - @Test - public void geoFunctionCallWithGeoPointInWhereClauseShouldPass() { - validate("SELECT * FROM semantics WHERE GEO_BOUNDING_BOX(location, 100.0, 1.0, 101, 0.0)"); - validate("SELECT * FROM semantics WHERE GEO_DISTANCE(location, '1km', 100.5, 0.500001)"); - validate("SELECT * FROM semantics WHERE GEO_DISTANCE_RANGE(location, '1km', 100.5, 0.500001)"); - } - - @Test - public void fullTextMatchFunctionCallWithStringInWhereClauseShouldPass() { - validate("SELECT * FROM semantics WHERE MATCH_PHRASE(address, 'Seattle')"); - validate("SELECT * FROM semantics WHERE MATCHPHRASE(employer, 'Seattle')"); - validate("SELECT * FROM semantics WHERE MATCH_QUERY(manager.name, 'Seattle')"); - validate("SELECT * FROM semantics WHERE MATCHQUERY(manager.name, 'Seattle')"); - validate("SELECT * FROM semantics WHERE QUERY('Seattle')"); - validate("SELECT * FROM semantics WHERE WILDCARD_QUERY(manager.name, 'Sea*')"); - validate("SELECT * FROM semantics WHERE WILDCARDQUERY(manager.name, 'Sea*')"); - } - + @Test + public void dateFunctionCallWithDateInSelectClauseShouldPass() { + validate("SELECT DAY_OF_MONTH(birthday) FROM semantics"); + validate("SELECT DAY_OF_WEEK(birthday) FROM semantics"); + validate("SELECT DAY_OF_YEAR(birthday) FROM semantics"); + validate("SELECT MINUTE_OF_DAY(birthday) FROM semantics"); + validate("SELECT MINUTE_OF_HOUR(birthday) FROM semantics"); + validate("SELECT MONTH_OF_YEAR(birthday) FROM semantics"); + validate("SELECT WEEK_OF_YEAR(birthday) FROM semantics"); + } + + @Test + public void dateFunctionCallWithDateInWhereClauseShouldPass() { + validate("SELECT * FROM semantics WHERE DAY_OF_MONTH(birthday) = 1"); + validate("SELECT * FROM semantics WHERE DAY_OF_WEEK(birthday) = 1"); + validate("SELECT * FROM semantics WHERE DAY_OF_YEAR(birthday) = 1"); + validate("SELECT * FROM semantics WHERE MINUTE_OF_DAY(birthday) = 1"); + validate("SELECT * FROM semantics WHERE MINUTE_OF_HOUR(birthday) = 1"); + validate("SELECT * FROM semantics WHERE MONTH_OF_YEAR(birthday) = 1"); + validate("SELECT * FROM semantics WHERE WEEK_OF_YEAR(birthday) = 1"); + } + + @Test + public void geoFunctionCallWithGeoPointInWhereClauseShouldPass() { + validate("SELECT * FROM semantics WHERE GEO_BOUNDING_BOX(location, 100.0, 1.0, 101, 0.0)"); + validate("SELECT * FROM semantics WHERE GEO_DISTANCE(location, '1km', 100.5, 0.500001)"); + validate("SELECT * FROM semantics WHERE GEO_DISTANCE_RANGE(location, '1km', 100.5, 0.500001)"); + } + + @Test + public void fullTextMatchFunctionCallWithStringInWhereClauseShouldPass() { + validate("SELECT * FROM semantics WHERE MATCH_PHRASE(address, 'Seattle')"); + validate("SELECT * FROM semantics WHERE MATCHPHRASE(employer, 'Seattle')"); + validate("SELECT * FROM semantics WHERE MATCH_QUERY(manager.name, 'Seattle')"); + validate("SELECT * FROM semantics WHERE MATCHQUERY(manager.name, 'Seattle')"); + validate("SELECT * FROM semantics WHERE QUERY('Seattle')"); + validate("SELECT * FROM semantics WHERE WILDCARD_QUERY(manager.name, 'Sea*')"); + validate("SELECT * FROM semantics WHERE WILDCARDQUERY(manager.name, 'Sea*')"); + } } diff --git a/legacy/src/test/java/org/opensearch/sql/legacy/antlr/semantic/SemanticAnalyzerFieldTypeTest.java b/legacy/src/test/java/org/opensearch/sql/legacy/antlr/semantic/SemanticAnalyzerFieldTypeTest.java index 3e4d3e6eb5..1b9b0dde45 100644 --- a/legacy/src/test/java/org/opensearch/sql/legacy/antlr/semantic/SemanticAnalyzerFieldTypeTest.java +++ b/legacy/src/test/java/org/opensearch/sql/legacy/antlr/semantic/SemanticAnalyzerFieldTypeTest.java @@ -3,7 +3,6 @@ * SPDX-License-Identifier: Apache-2.0 */ - package org.opensearch.sql.legacy.antlr.semantic; import static org.opensearch.sql.legacy.util.MultipleIndexClusterUtils.mockMultipleIndexEnv; @@ -12,87 +11,69 @@ import org.junit.Test; public class SemanticAnalyzerFieldTypeTest extends SemanticAnalyzerTestBase { - @Before - public void setup() { - mockMultipleIndexEnv(); - } + @Before + public void setup() { + mockMultipleIndexEnv(); + } - /** - * id has same type in account1 and account2. - */ - @Test - public void accessFieldTypeNotInQueryPassSemanticCheck() { - validate("SELECT id FROM account* WHERE id = 1"); - } + /** id has same type in account1 and account2. */ + @Test + public void accessFieldTypeNotInQueryPassSemanticCheck() { + validate("SELECT id FROM account* WHERE id = 1"); + } - /** - * address doesn't exist in account1. - */ - @Test - public void accessFieldTypeOnlyInOneIndexPassSemanticCheck() { - validate("SELECT address FROM account* WHERE id = 30"); - } + /** address doesn't exist in account1. */ + @Test + public void accessFieldTypeOnlyInOneIndexPassSemanticCheck() { + validate("SELECT address FROM account* WHERE id = 30"); + } - /** - * age has different type in account1 and account2. - */ - @Test - public void accessConflictFieldTypeShouldFailSemanticCheck() { - expectValidationFailWithErrorMessages("SELECT age FROM account* WHERE age = 30", - "Field [age] have conflict type"); - } + /** age has different type in account1 and account2. */ + @Test + public void accessConflictFieldTypeShouldFailSemanticCheck() { + expectValidationFailWithErrorMessages( + "SELECT age FROM account* WHERE age = 30", "Field [age] have conflict type"); + } - /** - * age has different type in account1 and account2. - */ - @Test - public void mixNonConflictTypeAndConflictFieldTypeShouldFailSemanticCheck() { - expectValidationFailWithErrorMessages("SELECT id, age FROM account* WHERE id = 1", - "Field [age] have conflict type"); - } + /** age has different type in account1 and account2. */ + @Test + public void mixNonConflictTypeAndConflictFieldTypeShouldFailSemanticCheck() { + expectValidationFailWithErrorMessages( + "SELECT id, age FROM account* WHERE id = 1", "Field [age] have conflict type"); + } - /** - * age has different type in account1 and account2. - */ - @Test - public void conflictFieldTypeWithAliasShouldFailSemanticCheck() { - expectValidationFailWithErrorMessages("SELECT a.age FROM account* as a", - "Field [a.age] have conflict type"); - } + /** age has different type in account1 and account2. */ + @Test + public void conflictFieldTypeWithAliasShouldFailSemanticCheck() { + expectValidationFailWithErrorMessages( + "SELECT a.age FROM account* as a", "Field [a.age] have conflict type"); + } - /** - * age has different type in account1 and account2. - * Todo, the error message is not accurate. - */ - @Test - public void selectAllFieldTypeShouldFailSemanticCheck() { - expectValidationFailWithErrorMessages("SELECT * FROM account*", - "Field [account*.age] have conflict type"); - } + /** age has different type in account1 and account2. Todo, the error message is not accurate. */ + @Test + public void selectAllFieldTypeShouldFailSemanticCheck() { + expectValidationFailWithErrorMessages( + "SELECT * FROM account*", "Field [account*.age] have conflict type"); + } - /** - * age has different type in account1 and account2. - */ - @Test - public void selectAllFieldTypeWithAliasShouldFailSemanticCheck() { - expectValidationFailWithErrorMessages("SELECT a.* FROM account* as a", - "Field [a.age] have conflict type"); - } + /** age has different type in account1 and account2. */ + @Test + public void selectAllFieldTypeWithAliasShouldFailSemanticCheck() { + expectValidationFailWithErrorMessages( + "SELECT a.* FROM account* as a", "Field [a.age] have conflict type"); + } - /** - * a.projects.name has same type in account1 and account2. - */ - @Test - public void selectNestedNoneConflictTypeShouldPassSemanticCheck() { - validate("SELECT a.projects.name FROM account* as a"); - } + /** a.projects.name has same type in account1 and account2. */ + @Test + public void selectNestedNoneConflictTypeShouldPassSemanticCheck() { + validate("SELECT a.projects.name FROM account* as a"); + } - /** - * a.projects.started_year has conflict type in account1 and account2. - */ - @Test - public void selectNestedConflictTypeShouldFailSemanticCheck() { - expectValidationFailWithErrorMessages("SELECT a.projects.started_year FROM account* as a", - "Field [a.projects.started_year] have conflict type"); - } + /** a.projects.started_year has conflict type in account1 and account2. */ + @Test + public void selectNestedConflictTypeShouldFailSemanticCheck() { + expectValidationFailWithErrorMessages( + "SELECT a.projects.started_year FROM account* as a", + "Field [a.projects.started_year] have conflict type"); + } } diff --git a/legacy/src/test/java/org/opensearch/sql/legacy/antlr/semantic/SemanticAnalyzerFromClauseTest.java b/legacy/src/test/java/org/opensearch/sql/legacy/antlr/semantic/SemanticAnalyzerFromClauseTest.java index a487a7afaa..2a04321f2f 100644 --- a/legacy/src/test/java/org/opensearch/sql/legacy/antlr/semantic/SemanticAnalyzerFromClauseTest.java +++ b/legacy/src/test/java/org/opensearch/sql/legacy/antlr/semantic/SemanticAnalyzerFromClauseTest.java @@ -3,191 +3,173 @@ * SPDX-License-Identifier: Apache-2.0 */ - package org.opensearch.sql.legacy.antlr.semantic; import org.junit.Ignore; import org.junit.Test; /** - * Semantic analyzer tests for FROM clause, including parse single index, multiple indices, - * index + (deep) nested field and multiple statements like UNION/MINUS etc. Basically, we - * need to make sure the environment be set up properly so that semantic analysis followed - * can be performed correctly. + * Semantic analyzer tests for FROM clause, including parse single index, multiple indices, index + + * (deep) nested field and multiple statements like UNION/MINUS etc. Basically, we need to make sure + * the environment be set up properly so that semantic analysis followed can be performed correctly. */ public class SemanticAnalyzerFromClauseTest extends SemanticAnalyzerTestBase { - @Ignore("IndexNotFoundException should be thrown from OpenSearch API directly") - @Test - public void nonExistingIndexNameShouldFail() { - expectValidationFailWithErrorMessages( - "SELECT * FROM semantics1" - ); - } - - @Test - public void useNotExistFieldInIndexPatternShouldFail() { - expectValidationFailWithErrorMessages( - "SELECT abc FROM semant* WHERE def = 1", - "Field [def] cannot be found or used here.", - "Did you mean [address]?" - ); - } - - @Test - public void useNotExistFieldInIndexAndIndexPatternShouldFail() { - expectValidationFailWithErrorMessages( - "SELECT abc FROM semantics, semant* WHERE def = 1", - "Field [def] cannot be found or used here.", - "Did you mean [address]?" - ); - } - - /** - * As shown below, there are multiple cases for alias: - * 1. Alias is not present: either use full index name as prefix or not. - * 2. Alias is present: either use alias as prefix or not. Full index name is illegal. - */ - @Test - public void indexNameAliasShouldBeOptional() { - validate("SELECT address FROM semantics"); - validate("SELECT address FROM semantics s"); - validate("SELECT * FROM semantics WHERE semantics.address LIKE 'Seattle'"); - } - - @Test - public void useFullIndexNameShouldFailIfAliasIsPresent() { - expectValidationFailWithErrorMessages( - "SELECT * FROM semantics s WHERE semantics.address LIKE 'Seattle'", - "Field [semantics.address] cannot be found or used here", - "Did you mean [s.manager.address]?" - ); - } - - @Test - public void invalidIndexNameAliasInFromClauseShouldFail() { - expectValidationFailWithErrorMessages( - "SELECT * FROM semantics s, a.projects p", - "Field [a.projects] cannot be found or used here", - "Did you mean [s.projects]?" - ); - } - - @Test - public void invalidIndexNameAliasInWhereClauseShouldFail() { - expectValidationFailWithErrorMessages( - "SELECT * FROM semantics s WHERE a.balance = 10000", - "Field [a.balance] cannot be found or used here", - "Did you mean [s.balance]?" - ); - } - - @Test - public void invalidIndexNameAliasInGroupByClauseShouldFail() { - expectValidationFailWithErrorMessages( - "SELECT * FROM semantics s GROUP BY a.balance", - "Field [a.balance] cannot be found or used here", - "Did you mean [s.balance]?" - ); - } - - @Test - public void invalidIndexNameAliasInHavingClauseShouldFail() { - expectValidationFailWithErrorMessages( - "SELECT * FROM semantics s HAVING COUNT(a.balance) > 5", - "Field [a.balance] cannot be found or used here", - "Did you mean [s.balance]?" - ); - } - - @Test - public void invalidIndexNameAliasInOrderByClauseShouldFail() { - expectValidationFailWithErrorMessages( - "SELECT * FROM semantics s ORDER BY a.balance", - "Field [a.balance] cannot be found or used here", - "Did you mean [s.balance]?" - ); - } - - @Test - public void invalidIndexNameAliasInOnClauseShouldFail() { - expectValidationFailWithErrorMessages( - "SELECT * FROM semantics sem JOIN semantic tic ON sem.age = t.age", - "Field [t.age] cannot be found or used here", - "Did you mean [tic.age]?" - ); - } - - @Test - public void nonNestedFieldInFromClauseShouldFail() { - expectValidationFailWithErrorMessages( - "SELECT * FROM semantics s, s.manager m", - "Operator [JOIN] cannot work with [INDEX, OBJECT]." - ); - } - - @Test - public void nonExistingNestedFieldInFromClauseShouldFail() { - expectValidationFailWithErrorMessages( - "SELECT * FROM semantics s, s.project p", - "Field [s.project] cannot be found or used here", - "Did you mean [s.projects]?" - ); - } - - @Ignore("Need to figure out a better way to detect naming conflict") - @Test - public void duplicateIndexNameAliasInFromClauseShouldFail() { - expectValidationFailWithErrorMessages( - "SELECT * FROM semantics s, s.projects s", - "Field [s] is conflicting with field of same name defined by other index" - ); - } - - @Ignore("Need to figure out a better way to detect naming conflict") - @Test - public void duplicateFieldNameFromDifferentIndexShouldFail() { - expectValidationFailWithErrorMessages( - "SELECT * FROM semantics INNER JOIN semantics", - "is conflicting with field of same name defined by other index" - ); - } - - @Test - public void validIndexNameAliasShouldPass() { - validate("SELECT * FROM semantics s, s.projects p"); - validate("SELECT * FROM semantics s WHERE s.balance = 10000"); - } - - @Test - public void indexNameWithTypeShouldPass() { - validate("SELECT * FROM semantics/docs WHERE balance = 10000"); - validate("SELECT * FROM semantics/docs s WHERE s.balance = 10000"); - validate("SELECT * FROM semantics/docs s, s.projects p WHERE p.active IS TRUE"); - } - - @Test - public void noIndexAliasShouldPass() { - validate("SELECT * FROM semantics"); - validate("SELECT * FROM semantics, semantics.projects"); - } - - @Test - public void regularJoinShouldPass() { - validate("SELECT * FROM semantics s1, semantics s2"); - validate("SELECT * FROM semantics s1 JOIN semantics s2"); - validate("SELECT * FROM semantics s1 LEFT JOIN semantics s2 ON s1.balance = s2.balance"); - } - - @Test - public void deepNestedFieldInFromClauseShouldPass() { - validate("SELECT * FROM semantics s, s.projects p, p.members m"); - } - - @Test - public void duplicateFieldNameFromDifferentStatementShouldPass() { - validate("SELECT age FROM semantics UNION SELECT age FROM semantic"); - validate("SELECT s.age FROM semantics s UNION SELECT s.age FROM semantic s"); - } - + @Ignore("IndexNotFoundException should be thrown from OpenSearch API directly") + @Test + public void nonExistingIndexNameShouldFail() { + expectValidationFailWithErrorMessages("SELECT * FROM semantics1"); + } + + @Test + public void useNotExistFieldInIndexPatternShouldFail() { + expectValidationFailWithErrorMessages( + "SELECT abc FROM semant* WHERE def = 1", + "Field [def] cannot be found or used here.", + "Did you mean [address]?"); + } + + @Test + public void useNotExistFieldInIndexAndIndexPatternShouldFail() { + expectValidationFailWithErrorMessages( + "SELECT abc FROM semantics, semant* WHERE def = 1", + "Field [def] cannot be found or used here.", + "Did you mean [address]?"); + } + + /** + * As shown below, there are multiple cases for alias: 1. Alias is not present: either use full + * index name as prefix or not. 2. Alias is present: either use alias as prefix or not. Full index + * name is illegal. + */ + @Test + public void indexNameAliasShouldBeOptional() { + validate("SELECT address FROM semantics"); + validate("SELECT address FROM semantics s"); + validate("SELECT * FROM semantics WHERE semantics.address LIKE 'Seattle'"); + } + + @Test + public void useFullIndexNameShouldFailIfAliasIsPresent() { + expectValidationFailWithErrorMessages( + "SELECT * FROM semantics s WHERE semantics.address LIKE 'Seattle'", + "Field [semantics.address] cannot be found or used here", + "Did you mean [s.manager.address]?"); + } + + @Test + public void invalidIndexNameAliasInFromClauseShouldFail() { + expectValidationFailWithErrorMessages( + "SELECT * FROM semantics s, a.projects p", + "Field [a.projects] cannot be found or used here", + "Did you mean [s.projects]?"); + } + + @Test + public void invalidIndexNameAliasInWhereClauseShouldFail() { + expectValidationFailWithErrorMessages( + "SELECT * FROM semantics s WHERE a.balance = 10000", + "Field [a.balance] cannot be found or used here", + "Did you mean [s.balance]?"); + } + + @Test + public void invalidIndexNameAliasInGroupByClauseShouldFail() { + expectValidationFailWithErrorMessages( + "SELECT * FROM semantics s GROUP BY a.balance", + "Field [a.balance] cannot be found or used here", + "Did you mean [s.balance]?"); + } + + @Test + public void invalidIndexNameAliasInHavingClauseShouldFail() { + expectValidationFailWithErrorMessages( + "SELECT * FROM semantics s HAVING COUNT(a.balance) > 5", + "Field [a.balance] cannot be found or used here", + "Did you mean [s.balance]?"); + } + + @Test + public void invalidIndexNameAliasInOrderByClauseShouldFail() { + expectValidationFailWithErrorMessages( + "SELECT * FROM semantics s ORDER BY a.balance", + "Field [a.balance] cannot be found or used here", + "Did you mean [s.balance]?"); + } + + @Test + public void invalidIndexNameAliasInOnClauseShouldFail() { + expectValidationFailWithErrorMessages( + "SELECT * FROM semantics sem JOIN semantic tic ON sem.age = t.age", + "Field [t.age] cannot be found or used here", + "Did you mean [tic.age]?"); + } + + @Test + public void nonNestedFieldInFromClauseShouldFail() { + expectValidationFailWithErrorMessages( + "SELECT * FROM semantics s, s.manager m", + "Operator [JOIN] cannot work with [INDEX, OBJECT]."); + } + + @Test + public void nonExistingNestedFieldInFromClauseShouldFail() { + expectValidationFailWithErrorMessages( + "SELECT * FROM semantics s, s.project p", + "Field [s.project] cannot be found or used here", + "Did you mean [s.projects]?"); + } + + @Ignore("Need to figure out a better way to detect naming conflict") + @Test + public void duplicateIndexNameAliasInFromClauseShouldFail() { + expectValidationFailWithErrorMessages( + "SELECT * FROM semantics s, s.projects s", + "Field [s] is conflicting with field of same name defined by other index"); + } + + @Ignore("Need to figure out a better way to detect naming conflict") + @Test + public void duplicateFieldNameFromDifferentIndexShouldFail() { + expectValidationFailWithErrorMessages( + "SELECT * FROM semantics INNER JOIN semantics", + "is conflicting with field of same name defined by other index"); + } + + @Test + public void validIndexNameAliasShouldPass() { + validate("SELECT * FROM semantics s, s.projects p"); + validate("SELECT * FROM semantics s WHERE s.balance = 10000"); + } + + @Test + public void indexNameWithTypeShouldPass() { + validate("SELECT * FROM semantics/docs WHERE balance = 10000"); + validate("SELECT * FROM semantics/docs s WHERE s.balance = 10000"); + validate("SELECT * FROM semantics/docs s, s.projects p WHERE p.active IS TRUE"); + } + + @Test + public void noIndexAliasShouldPass() { + validate("SELECT * FROM semantics"); + validate("SELECT * FROM semantics, semantics.projects"); + } + + @Test + public void regularJoinShouldPass() { + validate("SELECT * FROM semantics s1, semantics s2"); + validate("SELECT * FROM semantics s1 JOIN semantics s2"); + validate("SELECT * FROM semantics s1 LEFT JOIN semantics s2 ON s1.balance = s2.balance"); + } + + @Test + public void deepNestedFieldInFromClauseShouldPass() { + validate("SELECT * FROM semantics s, s.projects p, p.members m"); + } + + @Test + public void duplicateFieldNameFromDifferentStatementShouldPass() { + validate("SELECT age FROM semantics UNION SELECT age FROM semantic"); + validate("SELECT s.age FROM semantics s UNION SELECT s.age FROM semantic s"); + } } diff --git a/legacy/src/test/java/org/opensearch/sql/legacy/antlr/semantic/SemanticAnalyzerIdentifierTest.java b/legacy/src/test/java/org/opensearch/sql/legacy/antlr/semantic/SemanticAnalyzerIdentifierTest.java index 3d9133c937..35bcde3f76 100644 --- a/legacy/src/test/java/org/opensearch/sql/legacy/antlr/semantic/SemanticAnalyzerIdentifierTest.java +++ b/legacy/src/test/java/org/opensearch/sql/legacy/antlr/semantic/SemanticAnalyzerIdentifierTest.java @@ -3,169 +3,158 @@ * SPDX-License-Identifier: Apache-2.0 */ - package org.opensearch.sql.legacy.antlr.semantic; import org.junit.Ignore; import org.junit.Test; -/** - * Semantic analyzer tests for identifier - */ +/** Semantic analyzer tests for identifier */ public class SemanticAnalyzerIdentifierTest extends SemanticAnalyzerTestBase { - @Ignore("To be implemented") - @Test - public void duplicateFieldAliasInSelectClauseShouldFail() { - expectValidationFailWithErrorMessages( - "SELECT age a, COUNT(*) a FROM semantics s, a.projects p", - "Field [a.projects] cannot be found or used here" - ); - } - - @Test - public void fieldWithDifferentCaseInSelectClauseShouldFail() { - expectValidationFailWithErrorMessages( - "SELECT Age a FROM semantics", - "Field [Age] cannot be found or used here", - "Did you mean [age]?" - ); - } - - @Test - public void useHiddenFieldShouldPass() { - validate("SELECT _score FROM semantics WHERE _id = 1 AND _type = '_doc'"); - } - - @Ignore("Need to remove single quote or back ticks") - @Test - public void useFieldNameWithSpaceShouldPass() { - validate("SELECT ['field with spaces'] FROM semantics"); - validate("SELECT `field with spaces` FROM semantics"); - } - - @Test - public void nonExistingFieldNameInSelectClauseShouldFail() { - expectValidationFailWithErrorMessages( - "SELECT age1 FROM semantics s", - "Field [age1] cannot be found or used here.", - "Did you mean [age]?" - ); - } - - @Test - public void invalidIndexAliasInFromClauseShouldFail() { - expectValidationFailWithErrorMessages( - "SELECT * FROM semantics s, a.projects p", - "Field [a.projects] cannot be found or used here.", - "Did you mean [s.projects]?" - ); - } - - @Test - public void nonExistingFieldNameInWhereClauseShouldFail() { - expectValidationFailWithErrorMessages( - "SELECT * FROM semantics s WHERE s.balce = 10000", - "Field [s.balce] cannot be found or used here.", - "Did you mean [s.balance]?" - ); - } - - @Test - public void nonExistingFieldNameInGroupByClauseShouldFail() { - expectValidationFailWithErrorMessages( - "SELECT * FROM semantics s GROUP BY s.balce", - "Field [s.balce] cannot be found or used here.", - "Did you mean [s.balance]?" - ); - } - - @Test - public void nonExistingFieldNameInHavingClauseShouldFail() { - expectValidationFailWithErrorMessages( - "SELECT * FROM semantics s HAVING COUNT(s.balce) > 5", - "Field [s.balce] cannot be found or used here.", - "Did you mean [s.balance]?" - ); - } - - @Test - public void nonExistingFieldNameInOrderByClauseShouldFail() { - expectValidationFailWithErrorMessages( - "SELECT * FROM semantics s ORDER BY s.balce", - "Field [s.balce] cannot be found or used here.", - "Did you mean [s.balance]?" - ); - } - - @Test - public void nonExistingFieldNameInFunctionShouldFail() { - expectValidationFailWithErrorMessages( - "SELECT * FROM semantics s WHERE LOG(s.balce) = 1", - "Field [s.balce] cannot be found or used here.", - "Did you mean [s.balance]?" - ); - } - - @Test - public void nonExistingNestedFieldNameInWhereClauseShouldFail() { - expectValidationFailWithErrorMessages( - "SELECT * FROM semantics s, s.projects p, p.members m WHERE m.nam = 'John'", - "Field [m.nam] cannot be found or used here.", - "Did you mean [m.name]?" - ); - } - - @Test - public void nonExistingNestedFieldNameInFunctionShouldFail() { - expectValidationFailWithErrorMessages( - "SELECT * FROM semantics WHERE nested(projects.actives) = TRUE", - "Field [projects.actives] cannot be found or used here.", - "Did you mean [projects.active]?" - ); - } - - @Test - public void useKeywordInMultiFieldShouldPass() { - validate("SELECT employer.keyword FROM semantics WHERE employer.keyword LIKE 'AWS' GROUP BY employer.keyword"); - validate("SELECT * FROM semantics s WHERE s.manager.name.keyword LIKE 'John'"); - } - - @Test - public void useDeepNestedFieldNameShouldPass() { - validate("SELECT p.* FROM semantics s, s.projects p WHERE p IS NULL"); - validate("SELECT p.active FROM semantics s, s.projects p WHERE p.active = TRUE"); - validate("SELECT m.name FROM semantics s, s.projects p, p.members m WHERE m.name = 'John'"); - } - - @Test - public void useConstantLiteralInSelectClauseShouldPass() { - validate("SELECT 1 FROM semantics"); - validate("SELECT 2.0 FROM semantics"); - //validate("SELECT 'test' FROM semantics"); TODO: why 'test' goes to fullColumnName that can be string literal - validate("SELECT TRUE FROM semantics"); - } - - @Test - public void queryWithBackticksQuotedIndexShouldPass() { - validate("SELECT age FROM `semantics`"); - } - - @Test - public void queryWithBackticksQuotedIndexAliasShouldPass() { - validate("SELECT `s`.age FROM semantics AS `s`"); - validate("SELECT `s t`.age FROM semantics AS `s t`"); - } - - @Test - public void queryWithBackticksQuotedFieldNameShouldPass() { - validate("SELECT `age` FROM semantics"); - validate("SELECT s.`age` FROM semantics AS s"); - validate("SELECT `s`.`age` FROM semantics AS `s`"); - } - - @Test - public void queryWithBackticksQuotedFieldNameInFunctionShouldPass() { - validate("SELECT SUM(`age`) FROM semantics"); - } + @Ignore("To be implemented") + @Test + public void duplicateFieldAliasInSelectClauseShouldFail() { + expectValidationFailWithErrorMessages( + "SELECT age a, COUNT(*) a FROM semantics s, a.projects p", + "Field [a.projects] cannot be found or used here"); + } + + @Test + public void fieldWithDifferentCaseInSelectClauseShouldFail() { + expectValidationFailWithErrorMessages( + "SELECT Age a FROM semantics", + "Field [Age] cannot be found or used here", + "Did you mean [age]?"); + } + + @Test + public void useHiddenFieldShouldPass() { + validate("SELECT _score FROM semantics WHERE _id = 1 AND _type = '_doc'"); + } + + @Ignore("Need to remove single quote or back ticks") + @Test + public void useFieldNameWithSpaceShouldPass() { + validate("SELECT ['field with spaces'] FROM semantics"); + validate("SELECT `field with spaces` FROM semantics"); + } + + @Test + public void nonExistingFieldNameInSelectClauseShouldFail() { + expectValidationFailWithErrorMessages( + "SELECT age1 FROM semantics s", + "Field [age1] cannot be found or used here.", + "Did you mean [age]?"); + } + + @Test + public void invalidIndexAliasInFromClauseShouldFail() { + expectValidationFailWithErrorMessages( + "SELECT * FROM semantics s, a.projects p", + "Field [a.projects] cannot be found or used here.", + "Did you mean [s.projects]?"); + } + + @Test + public void nonExistingFieldNameInWhereClauseShouldFail() { + expectValidationFailWithErrorMessages( + "SELECT * FROM semantics s WHERE s.balce = 10000", + "Field [s.balce] cannot be found or used here.", + "Did you mean [s.balance]?"); + } + + @Test + public void nonExistingFieldNameInGroupByClauseShouldFail() { + expectValidationFailWithErrorMessages( + "SELECT * FROM semantics s GROUP BY s.balce", + "Field [s.balce] cannot be found or used here.", + "Did you mean [s.balance]?"); + } + + @Test + public void nonExistingFieldNameInHavingClauseShouldFail() { + expectValidationFailWithErrorMessages( + "SELECT * FROM semantics s HAVING COUNT(s.balce) > 5", + "Field [s.balce] cannot be found or used here.", + "Did you mean [s.balance]?"); + } + + @Test + public void nonExistingFieldNameInOrderByClauseShouldFail() { + expectValidationFailWithErrorMessages( + "SELECT * FROM semantics s ORDER BY s.balce", + "Field [s.balce] cannot be found or used here.", + "Did you mean [s.balance]?"); + } + + @Test + public void nonExistingFieldNameInFunctionShouldFail() { + expectValidationFailWithErrorMessages( + "SELECT * FROM semantics s WHERE LOG(s.balce) = 1", + "Field [s.balce] cannot be found or used here.", + "Did you mean [s.balance]?"); + } + + @Test + public void nonExistingNestedFieldNameInWhereClauseShouldFail() { + expectValidationFailWithErrorMessages( + "SELECT * FROM semantics s, s.projects p, p.members m WHERE m.nam = 'John'", + "Field [m.nam] cannot be found or used here.", + "Did you mean [m.name]?"); + } + + @Test + public void nonExistingNestedFieldNameInFunctionShouldFail() { + expectValidationFailWithErrorMessages( + "SELECT * FROM semantics WHERE nested(projects.actives) = TRUE", + "Field [projects.actives] cannot be found or used here.", + "Did you mean [projects.active]?"); + } + + @Test + public void useKeywordInMultiFieldShouldPass() { + validate( + "SELECT employer.keyword FROM semantics WHERE employer.keyword LIKE 'AWS' GROUP BY" + + " employer.keyword"); + validate("SELECT * FROM semantics s WHERE s.manager.name.keyword LIKE 'John'"); + } + + @Test + public void useDeepNestedFieldNameShouldPass() { + validate("SELECT p.* FROM semantics s, s.projects p WHERE p IS NULL"); + validate("SELECT p.active FROM semantics s, s.projects p WHERE p.active = TRUE"); + validate("SELECT m.name FROM semantics s, s.projects p, p.members m WHERE m.name = 'John'"); + } + + @Test + public void useConstantLiteralInSelectClauseShouldPass() { + validate("SELECT 1 FROM semantics"); + validate("SELECT 2.0 FROM semantics"); + // validate("SELECT 'test' FROM semantics"); TODO: why 'test' goes to fullColumnName that can be + // string literal + validate("SELECT TRUE FROM semantics"); + } + + @Test + public void queryWithBackticksQuotedIndexShouldPass() { + validate("SELECT age FROM `semantics`"); + } + + @Test + public void queryWithBackticksQuotedIndexAliasShouldPass() { + validate("SELECT `s`.age FROM semantics AS `s`"); + validate("SELECT `s t`.age FROM semantics AS `s t`"); + } + + @Test + public void queryWithBackticksQuotedFieldNameShouldPass() { + validate("SELECT `age` FROM semantics"); + validate("SELECT s.`age` FROM semantics AS s"); + validate("SELECT `s`.`age` FROM semantics AS `s`"); + } + + @Test + public void queryWithBackticksQuotedFieldNameInFunctionShouldPass() { + validate("SELECT SUM(`age`) FROM semantics"); + } } diff --git a/legacy/src/test/java/org/opensearch/sql/legacy/antlr/semantic/SemanticAnalyzerMultiQueryTest.java b/legacy/src/test/java/org/opensearch/sql/legacy/antlr/semantic/SemanticAnalyzerMultiQueryTest.java index 3c4c71c6ea..319f6c5cfa 100644 --- a/legacy/src/test/java/org/opensearch/sql/legacy/antlr/semantic/SemanticAnalyzerMultiQueryTest.java +++ b/legacy/src/test/java/org/opensearch/sql/legacy/antlr/semantic/SemanticAnalyzerMultiQueryTest.java @@ -3,93 +3,87 @@ * SPDX-License-Identifier: Apache-2.0 */ - package org.opensearch.sql.legacy.antlr.semantic; import org.junit.Ignore; import org.junit.Test; -/** - * Semantic analyzer tests for multi query like UNION and MINUS - */ +/** Semantic analyzer tests for multi query like UNION and MINUS */ public class SemanticAnalyzerMultiQueryTest extends SemanticAnalyzerTestBase { - @Test - public void unionDifferentResultTypeOfTwoQueriesShouldFail() { - expectValidationFailWithErrorMessages( - "SELECT balance FROM semantics UNION SELECT address FROM semantics", - "Operator [UNION] cannot work with [DOUBLE, TEXT]." - ); - } - - @Test - public void unionDifferentNumberOfResultTypeOfTwoQueriesShouldFail() { - expectValidationFailWithErrorMessages( - "SELECT balance FROM semantics UNION SELECT balance, age FROM semantics", - "Operator [UNION] cannot work with [DOUBLE, (DOUBLE, INTEGER)]." - ); - } - - @Test - public void minusDifferentResultTypeOfTwoQueriesShouldFail() { - expectValidationFailWithErrorMessages( - "SELECT p.active FROM semantics s, s.projects p MINUS SELECT address FROM semantics", - "Operator [MINUS] cannot work with [BOOLEAN, TEXT]." - ); - } - - @Test - public void unionSameResultTypeOfTwoQueriesShouldPass() { - validate("SELECT balance FROM semantics UNION SELECT balance FROM semantics"); - } - - @Test - public void unionCompatibleResultTypeOfTwoQueriesShouldPass() { - validate("SELECT balance FROM semantics UNION SELECT age FROM semantics"); - validate("SELECT address FROM semantics UNION ALL SELECT city FROM semantics"); - } - - @Test - public void minusSameResultTypeOfTwoQueriesShouldPass() { - validate("SELECT s.projects.active FROM semantics s UNION SELECT p.active FROM semantics s, s.projects p"); - } - - @Test - public void minusCompatibleResultTypeOfTwoQueriesShouldPass() { - validate("SELECT address FROM semantics MINUS SELECT manager.name.keyword FROM semantics"); - } - - @Test - public void unionSelectStarWithExtraFieldOfTwoQueriesShouldFail() { - expectValidationFailWithErrorMessages( - "SELECT * FROM semantics UNION SELECT *, city FROM semantics", - "Operator [UNION] cannot work with [(*), KEYWORD]." - ); - } - - @Test - public void minusSelectStarWithExtraFieldOfTwoQueriesShouldFail() { - expectValidationFailWithErrorMessages( - "SELECT *, address, balance FROM semantics MINUS SELECT * FROM semantics", - "Operator [MINUS] cannot work with [(TEXT, DOUBLE), (*)]." - ); - } - - @Test - public void unionSelectStarOfTwoQueriesShouldPass() { - validate("SELECT * FROM semantics UNION SELECT * FROM semantics"); - validate("SELECT *, age FROM semantics UNION SELECT *, balance FROM semantics"); - } - - @Test - public void unionSelectFunctionCallWithSameReturnTypeOfTwoQueriesShouldPass() { - validate("SELECT LOG(balance) FROM semantics UNION SELECT ABS(age) FROM semantics"); - } - - @Ignore("* is empty and ignored in product of select items for now") - @Test - public void unionSelectFieldWithExtraStarOfTwoQueriesShouldFail() { - expectValidationFailWithErrorMessages("SELECT age FROM semantics UNION SELECT *, age FROM semantics"); - } - + @Test + public void unionDifferentResultTypeOfTwoQueriesShouldFail() { + expectValidationFailWithErrorMessages( + "SELECT balance FROM semantics UNION SELECT address FROM semantics", + "Operator [UNION] cannot work with [DOUBLE, TEXT]."); + } + + @Test + public void unionDifferentNumberOfResultTypeOfTwoQueriesShouldFail() { + expectValidationFailWithErrorMessages( + "SELECT balance FROM semantics UNION SELECT balance, age FROM semantics", + "Operator [UNION] cannot work with [DOUBLE, (DOUBLE, INTEGER)]."); + } + + @Test + public void minusDifferentResultTypeOfTwoQueriesShouldFail() { + expectValidationFailWithErrorMessages( + "SELECT p.active FROM semantics s, s.projects p MINUS SELECT address FROM semantics", + "Operator [MINUS] cannot work with [BOOLEAN, TEXT]."); + } + + @Test + public void unionSameResultTypeOfTwoQueriesShouldPass() { + validate("SELECT balance FROM semantics UNION SELECT balance FROM semantics"); + } + + @Test + public void unionCompatibleResultTypeOfTwoQueriesShouldPass() { + validate("SELECT balance FROM semantics UNION SELECT age FROM semantics"); + validate("SELECT address FROM semantics UNION ALL SELECT city FROM semantics"); + } + + @Test + public void minusSameResultTypeOfTwoQueriesShouldPass() { + validate( + "SELECT s.projects.active FROM semantics s UNION SELECT p.active FROM semantics s," + + " s.projects p"); + } + + @Test + public void minusCompatibleResultTypeOfTwoQueriesShouldPass() { + validate("SELECT address FROM semantics MINUS SELECT manager.name.keyword FROM semantics"); + } + + @Test + public void unionSelectStarWithExtraFieldOfTwoQueriesShouldFail() { + expectValidationFailWithErrorMessages( + "SELECT * FROM semantics UNION SELECT *, city FROM semantics", + "Operator [UNION] cannot work with [(*), KEYWORD]."); + } + + @Test + public void minusSelectStarWithExtraFieldOfTwoQueriesShouldFail() { + expectValidationFailWithErrorMessages( + "SELECT *, address, balance FROM semantics MINUS SELECT * FROM semantics", + "Operator [MINUS] cannot work with [(TEXT, DOUBLE), (*)]."); + } + + @Test + public void unionSelectStarOfTwoQueriesShouldPass() { + validate("SELECT * FROM semantics UNION SELECT * FROM semantics"); + validate("SELECT *, age FROM semantics UNION SELECT *, balance FROM semantics"); + } + + @Test + public void unionSelectFunctionCallWithSameReturnTypeOfTwoQueriesShouldPass() { + validate("SELECT LOG(balance) FROM semantics UNION SELECT ABS(age) FROM semantics"); + } + + @Ignore("* is empty and ignored in product of select items for now") + @Test + public void unionSelectFieldWithExtraStarOfTwoQueriesShouldFail() { + expectValidationFailWithErrorMessages( + "SELECT age FROM semantics UNION SELECT *, age FROM semantics"); + } } diff --git a/legacy/src/test/java/org/opensearch/sql/legacy/antlr/semantic/SemanticAnalyzerOperatorTest.java b/legacy/src/test/java/org/opensearch/sql/legacy/antlr/semantic/SemanticAnalyzerOperatorTest.java index 36046aa0ad..bd5aeba507 100644 --- a/legacy/src/test/java/org/opensearch/sql/legacy/antlr/semantic/SemanticAnalyzerOperatorTest.java +++ b/legacy/src/test/java/org/opensearch/sql/legacy/antlr/semantic/SemanticAnalyzerOperatorTest.java @@ -3,71 +3,62 @@ * SPDX-License-Identifier: Apache-2.0 */ - package org.opensearch.sql.legacy.antlr.semantic; import org.junit.Test; -/** - * Semantic analysis test cases for operator - */ +/** Semantic analysis test cases for operator */ public class SemanticAnalyzerOperatorTest extends SemanticAnalyzerTestBase { - @Test - public void compareNumberIsBooleanShouldFail() { - expectValidationFailWithErrorMessages( - "SELECT * FROM semantics WHERE age IS FALSE", - "Operator [IS] cannot work with [INTEGER, BOOLEAN]." - ); - } - - @Test - public void compareTextIsNotBooleanShouldFail() { - expectValidationFailWithErrorMessages( - "SELECT * FROM semantics WHERE address IS NOT TRUE", - "Operator [IS] cannot work with [TEXT, BOOLEAN]." - ); - } + @Test + public void compareNumberIsBooleanShouldFail() { + expectValidationFailWithErrorMessages( + "SELECT * FROM semantics WHERE age IS FALSE", + "Operator [IS] cannot work with [INTEGER, BOOLEAN]."); + } - @Test - public void compareNumberEqualsToStringShouldFail() { - expectValidationFailWithErrorMessages( - "SELECT * FROM semantics WHERE balance = 'test'", - "Operator [=] cannot work with [DOUBLE, STRING]." - ); - } + @Test + public void compareTextIsNotBooleanShouldFail() { + expectValidationFailWithErrorMessages( + "SELECT * FROM semantics WHERE address IS NOT TRUE", + "Operator [IS] cannot work with [TEXT, BOOLEAN]."); + } - @Test - public void compareSubstringFunctionCallEqualsToNumberShouldFail() { - expectValidationFailWithErrorMessages( - "SELECT * FROM semantics WHERE SUBSTRING(address, 0, 3) = 1", - "Operator [=] cannot work with [TEXT, INTEGER]." - ); - } + @Test + public void compareNumberEqualsToStringShouldFail() { + expectValidationFailWithErrorMessages( + "SELECT * FROM semantics WHERE balance = 'test'", + "Operator [=] cannot work with [DOUBLE, STRING]."); + } - @Test - public void compareLogFunctionCallWithIntegerSmallerThanStringShouldFail() { - expectValidationFailWithErrorMessages( - "SELECT * FROM semantics WHERE LOG(age) < 'test'", - "Operator [<] cannot work with [DOUBLE, STRING]." - ); - } + @Test + public void compareSubstringFunctionCallEqualsToNumberShouldFail() { + expectValidationFailWithErrorMessages( + "SELECT * FROM semantics WHERE SUBSTRING(address, 0, 3) = 1", + "Operator [=] cannot work with [TEXT, INTEGER]."); + } - @Test - public void compareDoubleWithIntegerShouldPass() { - validate("SELECT * FROM semantics WHERE balance >= 1000"); - validate("SELECT * FROM semantics WHERE balance <> 1000"); - validate("SELECT * FROM semantics WHERE balance != 1000"); - } + @Test + public void compareLogFunctionCallWithIntegerSmallerThanStringShouldFail() { + expectValidationFailWithErrorMessages( + "SELECT * FROM semantics WHERE LOG(age) < 'test'", + "Operator [<] cannot work with [DOUBLE, STRING]."); + } - @Test - public void compareDateWithStringShouldPass() { - validate("SELECT * FROM semantics WHERE birthday = '2019-09-30'"); - } + @Test + public void compareDoubleWithIntegerShouldPass() { + validate("SELECT * FROM semantics WHERE balance >= 1000"); + validate("SELECT * FROM semantics WHERE balance <> 1000"); + validate("SELECT * FROM semantics WHERE balance != 1000"); + } - @Test - public void namedArgumentShouldSkipOperatorTypeCheck() { - validate("SELECT TOPHITS('size'=3, age='desc') FROM semantics GROUP BY city"); - } + @Test + public void compareDateWithStringShouldPass() { + validate("SELECT * FROM semantics WHERE birthday = '2019-09-30'"); + } + @Test + public void namedArgumentShouldSkipOperatorTypeCheck() { + validate("SELECT TOPHITS('size'=3, age='desc') FROM semantics GROUP BY city"); + } } diff --git a/legacy/src/test/java/org/opensearch/sql/legacy/antlr/semantic/SemanticAnalyzerScalarFunctionTest.java b/legacy/src/test/java/org/opensearch/sql/legacy/antlr/semantic/SemanticAnalyzerScalarFunctionTest.java index 83454b9549..8017c49548 100644 --- a/legacy/src/test/java/org/opensearch/sql/legacy/antlr/semantic/SemanticAnalyzerScalarFunctionTest.java +++ b/legacy/src/test/java/org/opensearch/sql/legacy/antlr/semantic/SemanticAnalyzerScalarFunctionTest.java @@ -3,270 +3,255 @@ * SPDX-License-Identifier: Apache-2.0 */ - package org.opensearch.sql.legacy.antlr.semantic; import org.junit.Ignore; import org.junit.Test; import org.opensearch.sql.legacy.antlr.semantic.types.base.OpenSearchDataType; -/** - * Semantic analysis tests for scalar function. - */ +/** Semantic analysis tests for scalar function. */ public class SemanticAnalyzerScalarFunctionTest extends SemanticAnalyzerTestBase { - @Test - public void unsupportedScalarFunctionCallInSelectClauseShouldFail() { - expectValidationFailWithErrorMessages( - "SELECT DAY() FROM semantics", - "Function [DAY] cannot be found or used here." - ); - } + @Test + public void unsupportedScalarFunctionCallInSelectClauseShouldFail() { + expectValidationFailWithErrorMessages( + "SELECT DAY() FROM semantics", "Function [DAY] cannot be found or used here."); + } - @Test - public void unsupportedScalarFunctionCallInWhereClauseShouldFail() { - expectValidationFailWithErrorMessages( - "SELECT * FROM semantics WHERE LOG100(balance) = 1", - "Function [LOG100] cannot be found or used here.", - "Did you mean [LOG10]?" - ); - } + @Test + public void unsupportedScalarFunctionCallInWhereClauseShouldFail() { + expectValidationFailWithErrorMessages( + "SELECT * FROM semantics WHERE LOG100(balance) = 1", + "Function [LOG100] cannot be found or used here.", + "Did you mean [LOG10]?"); + } - @Test - public void scalarFunctionCallWithLessArgumentsInWhereClauseShouldFail() { - expectValidationFailWithErrorMessages( - "SELECT * FROM semantics WHERE LOG() = 1", - "Function [LOG] cannot work with [].", - "Usage: LOG(NUMBER T) -> DOUBLE" - ); - } + @Test + public void scalarFunctionCallWithLessArgumentsInWhereClauseShouldFail() { + expectValidationFailWithErrorMessages( + "SELECT * FROM semantics WHERE LOG() = 1", + "Function [LOG] cannot work with [].", + "Usage: LOG(NUMBER T) -> DOUBLE"); + } - @Test - public void scalarFunctionCallWithMoreArgumentsInWhereClauseShouldFail() { - expectValidationFailWithErrorMessages( - "SELECT * FROM semantics WHERE LOG(age, city) = 1", - "Function [LOG] cannot work with [INTEGER, KEYWORD].", - "Usage: LOG(NUMBER T) -> DOUBLE" - ); - } + @Test + public void scalarFunctionCallWithMoreArgumentsInWhereClauseShouldFail() { + expectValidationFailWithErrorMessages( + "SELECT * FROM semantics WHERE LOG(age, city) = 1", + "Function [LOG] cannot work with [INTEGER, KEYWORD].", + "Usage: LOG(NUMBER T) -> DOUBLE"); + } - @Test - public void logFunctionCallWithOneNestedInSelectClauseShouldFail() { - expectValidationFailWithErrorMessages( - "SELECT LOG(projects) FROM semantics", - "Function [LOG] cannot work with [NESTED_FIELD].", - "Usage: LOG(NUMBER T) -> DOUBLE" - ); - } + @Test + public void logFunctionCallWithOneNestedInSelectClauseShouldFail() { + expectValidationFailWithErrorMessages( + "SELECT LOG(projects) FROM semantics", + "Function [LOG] cannot work with [NESTED_FIELD].", + "Usage: LOG(NUMBER T) -> DOUBLE"); + } - @Test - public void logFunctionCallWithOneTextInWhereClauseShouldFail() { - expectValidationFailWithErrorMessages( - "SELECT * FROM semantics WHERE LOG(city) = 1", - "Function [LOG] cannot work with [KEYWORD].", - "Usage: LOG(NUMBER T) -> DOUBLE" - ); - } + @Test + public void logFunctionCallWithOneTextInWhereClauseShouldFail() { + expectValidationFailWithErrorMessages( + "SELECT * FROM semantics WHERE LOG(city) = 1", + "Function [LOG] cannot work with [KEYWORD].", + "Usage: LOG(NUMBER T) -> DOUBLE"); + } - @Test - public void logFunctionCallWithOneNumberShouldPass() { - validate("SELECT LOG(age) FROM semantics"); - validate("SELECT * FROM semantics s WHERE LOG(s.balance) = 1000"); - validate("SELECT LOG(s.manager.salary) FROM semantics s"); - } + @Test + public void logFunctionCallWithOneNumberShouldPass() { + validate("SELECT LOG(age) FROM semantics"); + validate("SELECT * FROM semantics s WHERE LOG(s.balance) = 1000"); + validate("SELECT LOG(s.manager.salary) FROM semantics s"); + } - @Test - public void logFunctionCallInDifferentCaseShouldPass() { - validate("SELECT log(age) FROM semantics"); - validate("SELECT Log(age) FROM semantics"); - validate("SELECT loG(age) FROM semantics"); - } + @Test + public void logFunctionCallInDifferentCaseShouldPass() { + validate("SELECT log(age) FROM semantics"); + validate("SELECT Log(age) FROM semantics"); + validate("SELECT loG(age) FROM semantics"); + } - @Test - public void logFunctionCallWithUnknownFieldShouldPass() { - validate("SELECT LOG(new_field) FROM semantics"); - } + @Test + public void logFunctionCallWithUnknownFieldShouldPass() { + validate("SELECT LOG(new_field) FROM semantics"); + } - @Ignore("Test set to ignore due to nested functions not supported and blocked by throwing SqlFeatureNotImplementedException") - @Test - public void substringWithLogFunctionCallWithUnknownFieldShouldPass() { - expectValidationFailWithErrorMessages( - "SELECT SUBSTRING(LOG(new_field), 0, 1) FROM semantics", - "Function [SUBSTRING] cannot work with [DOUBLE, INTEGER, INTEGER]." - ," Usage: SUBSTRING(STRING T, INTEGER, INTEGER) -> T" - ); - } + @Ignore( + "Test set to ignore due to nested functions not supported and blocked by throwing" + + " SqlFeatureNotImplementedException") + @Test + public void substringWithLogFunctionCallWithUnknownFieldShouldPass() { + expectValidationFailWithErrorMessages( + "SELECT SUBSTRING(LOG(new_field), 0, 1) FROM semantics", + "Function [SUBSTRING] cannot work with [DOUBLE, INTEGER, INTEGER].", + " Usage: SUBSTRING(STRING T, INTEGER, INTEGER) -> T"); + } - @Ignore("Test set to ignore due to nested functions not supported and blocked by throwing SqlFeatureNotImplementedException") - @Test - public void logFunctionCallWithResultOfAbsFunctionCallWithOneNumberShouldPass() { - validate("SELECT LOG(ABS(age)) FROM semantics"); - } + @Ignore( + "Test set to ignore due to nested functions not supported and blocked by throwing" + + " SqlFeatureNotImplementedException") + @Test + public void logFunctionCallWithResultOfAbsFunctionCallWithOneNumberShouldPass() { + validate("SELECT LOG(ABS(age)) FROM semantics"); + } - @Ignore("Test set to ignore due to nested functions not supported and blocked by throwing SqlFeatureNotImplementedException") - @Test - public void logFunctionCallWithMoreNestedFunctionCallWithOneNumberShouldPass() { - validate("SELECT LOG(ABS(SQRT(balance))) FROM semantics"); - } + @Ignore( + "Test set to ignore due to nested functions not supported and blocked by throwing" + + " SqlFeatureNotImplementedException") + @Test + public void logFunctionCallWithMoreNestedFunctionCallWithOneNumberShouldPass() { + validate("SELECT LOG(ABS(SQRT(balance))) FROM semantics"); + } - @Ignore("Test set to ignore due to nested functions not supported and blocked by throwing SqlFeatureNotImplementedException") - @Test - public void substringFunctionCallWithResultOfAnotherSubstringAndAbsFunctionCallShouldPass() { - validate("SELECT SUBSTRING(SUBSTRING(city, ABS(age), 1), 2, ABS(1)) FROM semantics"); - } + @Ignore( + "Test set to ignore due to nested functions not supported and blocked by throwing" + + " SqlFeatureNotImplementedException") + @Test + public void substringFunctionCallWithResultOfAnotherSubstringAndAbsFunctionCallShouldPass() { + validate("SELECT SUBSTRING(SUBSTRING(city, ABS(age), 1), 2, ABS(1)) FROM semantics"); + } - @Ignore("Test set to ignore due to nested functions not supported and blocked by throwing SqlFeatureNotImplementedException") - @Test - public void substringFunctionCallWithResultOfMathFunctionCallShouldFail() { - expectValidationFailWithErrorMessages( - "SELECT SUBSTRING(LOG(balance), 2, 3) FROM semantics", - "Function [SUBSTRING] cannot work with [DOUBLE, INTEGER, INTEGER].", - "Usage: SUBSTRING(STRING T, INTEGER, INTEGER) -> T" - ); - } + @Ignore( + "Test set to ignore due to nested functions not supported and blocked by throwing" + + " SqlFeatureNotImplementedException") + @Test + public void substringFunctionCallWithResultOfMathFunctionCallShouldFail() { + expectValidationFailWithErrorMessages( + "SELECT SUBSTRING(LOG(balance), 2, 3) FROM semantics", + "Function [SUBSTRING] cannot work with [DOUBLE, INTEGER, INTEGER].", + "Usage: SUBSTRING(STRING T, INTEGER, INTEGER) -> T"); + } - @Ignore("Test set to ignore due to nested functions not supported and blocked by throwing SqlFeatureNotImplementedException") - @Test - public void logFunctionCallWithResultOfSubstringFunctionCallShouldFail() { - expectValidationFailWithErrorMessages( - "SELECT LOG(SUBSTRING(address, 0, 1)) FROM semantics", - "Function [LOG] cannot work with [TEXT].", - "Usage: LOG(NUMBER T) -> DOUBLE or LOG(NUMBER T, NUMBER) -> DOUBLE" - ); - } + @Ignore( + "Test set to ignore due to nested functions not supported and blocked by throwing" + + " SqlFeatureNotImplementedException") + @Test + public void logFunctionCallWithResultOfSubstringFunctionCallShouldFail() { + expectValidationFailWithErrorMessages( + "SELECT LOG(SUBSTRING(address, 0, 1)) FROM semantics", + "Function [LOG] cannot work with [TEXT].", + "Usage: LOG(NUMBER T) -> DOUBLE or LOG(NUMBER T, NUMBER) -> DOUBLE"); + } - @Test - public void allSupportedMathFunctionCallInSelectClauseShouldPass() { - validate( - "SELECT" + - " ABS(age), " + - " ASIN(age), " + - " ATAN(age), " + - " ATAN2(age, age), " + - " CBRT(age), " + - " CEIL(age), " + - " COS(age), " + - " COSH(age), " + - " DEGREES(age), " + - " EXP(age), " + - " EXPM1(age), " + - " FLOOR(age), " + - " LOG(age), " + - " LOG2(age), " + - " LOG10(age), " + - " LN(age), " + - " POW(age), " + - " RADIANS(age), " + - " RINT(age), " + - " ROUND(age), " + - " SIN(age), " + - " SINH(age), " + - " SQRT(age), " + - " TAN(age) " + - "FROM semantics" - ); - } + @Test + public void allSupportedMathFunctionCallInSelectClauseShouldPass() { + validate( + "SELECT" + + " ABS(age), " + + " ASIN(age), " + + " ATAN(age), " + + " ATAN2(age, age), " + + " CBRT(age), " + + " CEIL(age), " + + " COS(age), " + + " COSH(age), " + + " DEGREES(age), " + + " EXP(age), " + + " EXPM1(age), " + + " FLOOR(age), " + + " LOG(age), " + + " LOG2(age), " + + " LOG10(age), " + + " LN(age), " + + " POW(age), " + + " RADIANS(age), " + + " RINT(age), " + + " ROUND(age), " + + " SIN(age), " + + " SINH(age), " + + " SQRT(age), " + + " TAN(age) " + + "FROM semantics"); + } - @Test - public void allSupportedMathFunctionCallInWhereClauseShouldPass() { - validate( - "SELECT * FROM semantics WHERE " + - " ABS(age) = 1 AND " + - " ASIN(age) = 1 AND " + - " ATAN(age) = 1 AND " + - " ATAN2(age, age) = 1 AND " + - " CBRT(age) = 1 AND " + - " CEIL(age) = 1 AND " + - " COS(age) = 1 AND " + - " COSH(age) = 1 AND " + - " DEGREES(age) = 1 AND " + - " EXP(age) = 1 AND " + - " EXPM1(age) = 1 AND " + - " FLOOR(age) = 1 AND " + - " LOG(age) = 1 AND " + - " LOG2(age) = 1 AND " + - " LOG10(age) = 1 AND " + - " LN(age) = 1 AND " + - " POW(age) = 1 AND " + - " RADIANS(age) = 1 AND " + - " RINT(age) = 1 AND " + - " ROUND(age) = 1 AND " + - " SIN(age) = 1 AND " + - " SINH(age) = 1 AND " + - " SQRT(age) = 1 AND " + - " TAN(age) = 1 " - ); - } + @Test + public void allSupportedMathFunctionCallInWhereClauseShouldPass() { + validate( + "SELECT * FROM semantics WHERE " + + " ABS(age) = 1 AND " + + " ASIN(age) = 1 AND " + + " ATAN(age) = 1 AND " + + " ATAN2(age, age) = 1 AND " + + " CBRT(age) = 1 AND " + + " CEIL(age) = 1 AND " + + " COS(age) = 1 AND " + + " COSH(age) = 1 AND " + + " DEGREES(age) = 1 AND " + + " EXP(age) = 1 AND " + + " EXPM1(age) = 1 AND " + + " FLOOR(age) = 1 AND " + + " LOG(age) = 1 AND " + + " LOG2(age) = 1 AND " + + " LOG10(age) = 1 AND " + + " LN(age) = 1 AND " + + " POW(age) = 1 AND " + + " RADIANS(age) = 1 AND " + + " RINT(age) = 1 AND " + + " ROUND(age) = 1 AND " + + " SIN(age) = 1 AND " + + " SINH(age) = 1 AND " + + " SQRT(age) = 1 AND " + + " TAN(age) = 1 "); + } - @Test - public void allSupportedConstantsUseInSelectClauseShouldPass() { - validate( - "SELECT " + - " E(), " + - " PI() " + - "FROM semantics" - ); - } + @Test + public void allSupportedConstantsUseInSelectClauseShouldPass() { + validate("SELECT " + " E(), " + " PI() " + "FROM semantics"); + } - @Test - public void allSupportedConstantsUseInWhereClauseShouldPass() { - validate( - "SELECT * FROM semantics WHERE " + - " E() > 1 OR " + - " PI() > 1" - ); - } + @Test + public void allSupportedConstantsUseInWhereClauseShouldPass() { + validate("SELECT * FROM semantics WHERE " + " E() > 1 OR " + " PI() > 1"); + } - @Test - public void allSupportedStringFunctionCallInSelectClauseShouldPass() { - validate( - "SELECT * FROM semantics WHERE " + - " SUBSTRING(city, 0, 3) = 'Sea' AND " + - " UPPER(city) = 'SEATTLE' AND " + - " LOWER(city) = 'seattle'" - ); - } + @Test + public void allSupportedStringFunctionCallInSelectClauseShouldPass() { + validate( + "SELECT * FROM semantics WHERE " + + " SUBSTRING(city, 0, 3) = 'Sea' AND " + + " UPPER(city) = 'SEATTLE' AND " + + " LOWER(city) = 'seattle'"); + } - @Test - public void allSupportedStringFunctionCallInWhereClauseShouldPass() { - validate( - "SELECT" + - " SUBSTRING(city, 0, 3), " + - " UPPER(address), " + - " LOWER(manager.name) " + - "FROM semantics " - ); - } + @Test + public void allSupportedStringFunctionCallInWhereClauseShouldPass() { + validate( + "SELECT" + + " SUBSTRING(city, 0, 3), " + + " UPPER(address), " + + " LOWER(manager.name) " + + "FROM semantics "); + } - @Test - public void dateFormatFunctionCallWithNumberShouldFail() { - expectValidationFailWithErrorMessages( - "SELECT DATE_FORMAT(balance, 'yyyy-MM') FROM semantics", - "Function [DATE_FORMAT] cannot work with [DOUBLE, STRING].", - "Usage: DATE_FORMAT(DATE, STRING) -> STRING or DATE_FORMAT(DATE, STRING, STRING) -> STRING" - ); - } + @Test + public void dateFormatFunctionCallWithNumberShouldFail() { + expectValidationFailWithErrorMessages( + "SELECT DATE_FORMAT(balance, 'yyyy-MM') FROM semantics", + "Function [DATE_FORMAT] cannot work with [DOUBLE, STRING].", + "Usage: DATE_FORMAT(DATE, STRING) -> STRING or DATE_FORMAT(DATE, STRING, STRING) ->" + + " STRING"); + } - @Test - public void allSupportedDateFunctionCallShouldPass() { - validate( - "SELECT date_format(birthday, 'yyyy-MM') " + - "FROM semantics " + - "WHERE date_format(birthday, 'yyyy-MM') > '1980-01' " + - "GROUP BY date_format(birthday, 'yyyy-MM') " + - "ORDER BY date_format(birthday, 'yyyy-MM') DESC" - ); - } + @Test + public void allSupportedDateFunctionCallShouldPass() { + validate( + "SELECT date_format(birthday, 'yyyy-MM') " + + "FROM semantics " + + "WHERE date_format(birthday, 'yyyy-MM') > '1980-01' " + + "GROUP BY date_format(birthday, 'yyyy-MM') " + + "ORDER BY date_format(birthday, 'yyyy-MM') DESC"); + } - @Test - public void concatRequiresVarargSupportShouldPassAnyway() { - validate("SELECT CONCAT('aaa') FROM semantics"); - validate("SELECT CONCAT('aaa', 'bbb') FROM semantics"); - validate("SELECT CONCAT('aaa', 'bbb', 123) FROM semantics"); - } + @Test + public void concatRequiresVarargSupportShouldPassAnyway() { + validate("SELECT CONCAT('aaa') FROM semantics"); + validate("SELECT CONCAT('aaa', 'bbb') FROM semantics"); + validate("SELECT CONCAT('aaa', 'bbb', 123) FROM semantics"); + } - @Test - public void castFunctionShouldPass() { - validateWithType("SELECT CAST(age AS DOUBLE) FROM semantics", OpenSearchDataType.DOUBLE); - } + @Test + public void castFunctionShouldPass() { + validateWithType("SELECT CAST(age AS DOUBLE) FROM semantics", OpenSearchDataType.DOUBLE); + } } diff --git a/legacy/src/test/java/org/opensearch/sql/legacy/antlr/semantic/SemanticAnalyzerSubqueryTest.java b/legacy/src/test/java/org/opensearch/sql/legacy/antlr/semantic/SemanticAnalyzerSubqueryTest.java index f34af4fe3a..7613806df7 100644 --- a/legacy/src/test/java/org/opensearch/sql/legacy/antlr/semantic/SemanticAnalyzerSubqueryTest.java +++ b/legacy/src/test/java/org/opensearch/sql/legacy/antlr/semantic/SemanticAnalyzerSubqueryTest.java @@ -3,105 +3,94 @@ * SPDX-License-Identifier: Apache-2.0 */ - package org.opensearch.sql.legacy.antlr.semantic; import org.junit.Test; import org.opensearch.sql.legacy.antlr.visitor.EarlyExitAnalysisException; -/** - * Semantic analysis test for subquery - */ +/** Semantic analysis test for subquery */ public class SemanticAnalyzerSubqueryTest extends SemanticAnalyzerTestBase { - @Test - public void useExistClauseOnNestedFieldShouldPass() { - validate( - "SELECT * FROM semantics AS s WHERE EXISTS " + - " ( SELECT * FROM s.projects AS p WHERE p.active IS TRUE ) " + - " AND s.age > 10" - ); - } - - @Test - public void useNotExistClauseOnNestedFieldShouldPass() { - validate( - "SELECT * FROM semantics AS s WHERE NOT EXISTS " + - " ( SELECT * FROM s.projects AS p WHERE p.active IS TRUE ) " + - " AND s.age > 10" - ); - } - - @Test - public void useInClauseOnAgeWithIntegerLiteralListShouldPass() { - validate("SELECT * FROM semantics WHERE age IN (30, 40)"); - } - - @Test - public void useAliasInSubqueryShouldFail() { - expectValidationFailWithErrorMessages( - "SELECT * FROM semantics s WHERE EXISTS (SELECT * FROM s.projects p) AND p.active IS TRUE", - "Field [p.active] cannot be found or used here.", - "Did you mean [projects.active]?" - ); - } - - @Test - public void useInClauseWithIncompatibleFieldTypesShouldFail() { - expectValidationFailWithErrorMessages( - "SELECT * FROM semantics s WHERE age IN (SELECT p.active FROM s.projects p)", - "Operator [IN] cannot work with [INTEGER, BOOLEAN]." - ); - } - - @Test - public void useInClauseWithCompatibleFieldTypesShouldPass() { - validate("SELECT * FROM semantics s WHERE address IN (SELECT city FROM s.projects p)"); - } - - @Test - public void useNotInClauseWithCompatibleFieldTypesShouldPass() { - validate("SELECT * FROM semantics s WHERE address NOT IN (SELECT city FROM s.projects p)"); - } - - @Test - public void useInClauseWithCompatibleConstantShouldPass() { - validate("SELECT * FROM semantics WHERE age IN (10, 20, 30)"); - validate("SELECT * FROM semantics WHERE city IN ('Seattle', 'Bellevue')"); - validate("SELECT * FROM semantics WHERE birthday IN ('2000-01-01', '2010-01-01')"); - } - - @Test - public void useInClauseWithIncompatibleConstantShouldPass() { - expectValidationFailWithErrorMessages( - "SELECT * FROM semantics s WHERE age IN ('abc', 'def')", - "Operator [IN] cannot work with [INTEGER, STRING]." - ); - } - - @Test - public void useInClauseWithSelectStarShouldFail() { - expectValidationFailWithErrorMessages( - "SELECT * FROM semantics s WHERE address IN (SELECT * FROM s.projects p)", - "Operator [IN] cannot work with [TEXT, (*)]" - ); - } - - @Test - public void useExistsClauseWithSelectStarShouldPass() { - validate("SELECT * FROM semantics s WHERE EXISTS (SELECT * FROM s.projects p)"); - } - - @Test - public void useExistsClauseWithSelectConstantShouldPass() { - validate("SELECT * FROM semantics s WHERE EXISTS (SELECT 1 FROM s.projects p)"); - } - - /** - * Ignore the semantic analyzer by using {@link EarlyExitAnalysisException} - */ - @Test - public void useSubqueryInFromClauseWithSelectConstantShouldPass() { - validate("SELECT t.TEMP as count FROM (SELECT COUNT(*) as TEMP FROM semantics) t"); - } + @Test + public void useExistClauseOnNestedFieldShouldPass() { + validate( + "SELECT * FROM semantics AS s WHERE EXISTS " + + " ( SELECT * FROM s.projects AS p WHERE p.active IS TRUE ) " + + " AND s.age > 10"); + } + + @Test + public void useNotExistClauseOnNestedFieldShouldPass() { + validate( + "SELECT * FROM semantics AS s WHERE NOT EXISTS " + + " ( SELECT * FROM s.projects AS p WHERE p.active IS TRUE ) " + + " AND s.age > 10"); + } + + @Test + public void useInClauseOnAgeWithIntegerLiteralListShouldPass() { + validate("SELECT * FROM semantics WHERE age IN (30, 40)"); + } + + @Test + public void useAliasInSubqueryShouldFail() { + expectValidationFailWithErrorMessages( + "SELECT * FROM semantics s WHERE EXISTS (SELECT * FROM s.projects p) AND p.active IS TRUE", + "Field [p.active] cannot be found or used here.", + "Did you mean [projects.active]?"); + } + + @Test + public void useInClauseWithIncompatibleFieldTypesShouldFail() { + expectValidationFailWithErrorMessages( + "SELECT * FROM semantics s WHERE age IN (SELECT p.active FROM s.projects p)", + "Operator [IN] cannot work with [INTEGER, BOOLEAN]."); + } + + @Test + public void useInClauseWithCompatibleFieldTypesShouldPass() { + validate("SELECT * FROM semantics s WHERE address IN (SELECT city FROM s.projects p)"); + } + + @Test + public void useNotInClauseWithCompatibleFieldTypesShouldPass() { + validate("SELECT * FROM semantics s WHERE address NOT IN (SELECT city FROM s.projects p)"); + } + + @Test + public void useInClauseWithCompatibleConstantShouldPass() { + validate("SELECT * FROM semantics WHERE age IN (10, 20, 30)"); + validate("SELECT * FROM semantics WHERE city IN ('Seattle', 'Bellevue')"); + validate("SELECT * FROM semantics WHERE birthday IN ('2000-01-01', '2010-01-01')"); + } + + @Test + public void useInClauseWithIncompatibleConstantShouldPass() { + expectValidationFailWithErrorMessages( + "SELECT * FROM semantics s WHERE age IN ('abc', 'def')", + "Operator [IN] cannot work with [INTEGER, STRING]."); + } + + @Test + public void useInClauseWithSelectStarShouldFail() { + expectValidationFailWithErrorMessages( + "SELECT * FROM semantics s WHERE address IN (SELECT * FROM s.projects p)", + "Operator [IN] cannot work with [TEXT, (*)]"); + } + + @Test + public void useExistsClauseWithSelectStarShouldPass() { + validate("SELECT * FROM semantics s WHERE EXISTS (SELECT * FROM s.projects p)"); + } + + @Test + public void useExistsClauseWithSelectConstantShouldPass() { + validate("SELECT * FROM semantics s WHERE EXISTS (SELECT 1 FROM s.projects p)"); + } + + /** Ignore the semantic analyzer by using {@link EarlyExitAnalysisException} */ + @Test + public void useSubqueryInFromClauseWithSelectConstantShouldPass() { + validate("SELECT t.TEMP as count FROM (SELECT COUNT(*) as TEMP FROM semantics) t"); + } } diff --git a/legacy/src/test/java/org/opensearch/sql/legacy/antlr/semantic/SemanticAnalyzerTestBase.java b/legacy/src/test/java/org/opensearch/sql/legacy/antlr/semantic/SemanticAnalyzerTestBase.java index 7b53619d9c..403c2f49b7 100644 --- a/legacy/src/test/java/org/opensearch/sql/legacy/antlr/semantic/SemanticAnalyzerTestBase.java +++ b/legacy/src/test/java/org/opensearch/sql/legacy/antlr/semantic/SemanticAnalyzerTestBase.java @@ -3,7 +3,6 @@ * SPDX-License-Identifier: Apache-2.0 */ - package org.opensearch.sql.legacy.antlr.semantic; import static java.util.stream.Collectors.toList; @@ -28,49 +27,45 @@ import org.opensearch.sql.legacy.antlr.semantic.types.Type; import org.opensearch.sql.legacy.esdomain.LocalClusterState; -/** - * Test cases for semantic analysis focused on semantic check which was missing in the past. - */ +/** Test cases for semantic analysis focused on semantic check which was missing in the past. */ public abstract class SemanticAnalyzerTestBase { - private static final String TEST_MAPPING_FILE = "mappings/semantics.json"; + private static final String TEST_MAPPING_FILE = "mappings/semantics.json"; - /** public accessor is required by @Rule annotation */ - @Rule - public ExpectedException exception = ExpectedException.none(); + /** public accessor is required by @Rule annotation */ + @Rule public ExpectedException exception = ExpectedException.none(); - private OpenSearchLegacySqlAnalyzer - analyzer = new OpenSearchLegacySqlAnalyzer(new SqlAnalysisConfig(true, true, 1000)); + private OpenSearchLegacySqlAnalyzer analyzer = + new OpenSearchLegacySqlAnalyzer(new SqlAnalysisConfig(true, true, 1000)); - @SuppressWarnings("UnstableApiUsage") - @BeforeClass - public static void init() throws IOException { - URL url = Resources.getResource(TEST_MAPPING_FILE); - String mappings = Resources.toString(url, Charsets.UTF_8); - LocalClusterState.state(null); - mockLocalClusterState(mappings); - } + @SuppressWarnings("UnstableApiUsage") + @BeforeClass + public static void init() throws IOException { + URL url = Resources.getResource(TEST_MAPPING_FILE); + String mappings = Resources.toString(url, Charsets.UTF_8); + LocalClusterState.state(null); + mockLocalClusterState(mappings); + } - @AfterClass - public static void cleanUp() { - LocalClusterState.state(null); - } + @AfterClass + public static void cleanUp() { + LocalClusterState.state(null); + } - protected void expectValidationFailWithErrorMessages(String query, String... messages) { - exception.expect(SemanticAnalysisException.class); - exception.expectMessage(allOf(Arrays.stream(messages). - map(Matchers::containsString). - collect(toList()))); - validate(query); - } + protected void expectValidationFailWithErrorMessages(String query, String... messages) { + exception.expect(SemanticAnalysisException.class); + exception.expectMessage( + allOf(Arrays.stream(messages).map(Matchers::containsString).collect(toList()))); + validate(query); + } - protected void validate(String sql) { - analyzer.analyze(sql, LocalClusterState.state()); - } + protected void validate(String sql) { + analyzer.analyze(sql, LocalClusterState.state()); + } - protected void validateWithType(String sql, Type type) { - Optional analyze = analyzer.analyze(sql, LocalClusterState.state()); - assertTrue(analyze.isPresent()); - assertEquals(type, analyze.get()); - } + protected void validateWithType(String sql, Type type) { + Optional analyze = analyzer.analyze(sql, LocalClusterState.state()); + assertTrue(analyze.isPresent()); + assertEquals(type, analyze.get()); + } } diff --git a/legacy/src/test/java/org/opensearch/sql/legacy/antlr/semantic/SemanticAnalyzerTests.java b/legacy/src/test/java/org/opensearch/sql/legacy/antlr/semantic/SemanticAnalyzerTests.java index 56a27b780f..7585152a4d 100644 --- a/legacy/src/test/java/org/opensearch/sql/legacy/antlr/semantic/SemanticAnalyzerTests.java +++ b/legacy/src/test/java/org/opensearch/sql/legacy/antlr/semantic/SemanticAnalyzerTests.java @@ -3,29 +3,27 @@ * SPDX-License-Identifier: Apache-2.0 */ - package org.opensearch.sql.legacy.antlr.semantic; import org.junit.runner.RunWith; import org.junit.runners.Suite; /** - * Semantic analyzer test suite to prepare mapping and avoid load from file every time. - * But Gradle seems not work well with suite. So move common logic to test base class - * and keep this for quick testing in IDE. + * Semantic analyzer test suite to prepare mapping and avoid load from file every time. But Gradle + * seems not work well with suite. So move common logic to test base class and keep this for quick + * testing in IDE. */ @RunWith(Suite.class) @Suite.SuiteClasses({ - SemanticAnalyzerBasicTest.class, - SemanticAnalyzerConfigTest.class, - SemanticAnalyzerFromClauseTest.class, - SemanticAnalyzerIdentifierTest.class, - SemanticAnalyzerScalarFunctionTest.class, - SemanticAnalyzerESScalarFunctionTest.class, - SemanticAnalyzerAggregateFunctionTest.class, - SemanticAnalyzerOperatorTest.class, - SemanticAnalyzerSubqueryTest.class, - SemanticAnalyzerMultiQueryTest.class, + SemanticAnalyzerBasicTest.class, + SemanticAnalyzerConfigTest.class, + SemanticAnalyzerFromClauseTest.class, + SemanticAnalyzerIdentifierTest.class, + SemanticAnalyzerScalarFunctionTest.class, + SemanticAnalyzerESScalarFunctionTest.class, + SemanticAnalyzerAggregateFunctionTest.class, + SemanticAnalyzerOperatorTest.class, + SemanticAnalyzerSubqueryTest.class, + SemanticAnalyzerMultiQueryTest.class, }) -public class SemanticAnalyzerTests { -} +public class SemanticAnalyzerTests {} diff --git a/legacy/src/test/java/org/opensearch/sql/legacy/antlr/semantic/scope/SemanticContextTest.java b/legacy/src/test/java/org/opensearch/sql/legacy/antlr/semantic/scope/SemanticContextTest.java index 689fdd20f6..e19b48f2a0 100644 --- a/legacy/src/test/java/org/opensearch/sql/legacy/antlr/semantic/scope/SemanticContextTest.java +++ b/legacy/src/test/java/org/opensearch/sql/legacy/antlr/semantic/scope/SemanticContextTest.java @@ -3,42 +3,37 @@ * SPDX-License-Identifier: Apache-2.0 */ - package org.opensearch.sql.legacy.antlr.semantic.scope; import org.junit.Assert; import org.junit.Test; -/** - * Test cases for semantic context - */ +/** Test cases for semantic context */ public class SemanticContextTest { - private final SemanticContext context = new SemanticContext(); - - @Test - public void rootEnvironmentShouldBeThereInitially() { - Assert.assertNotNull( - "Didn't find root environment. Context is NOT supposed to be empty initially", - context.peek() - ); - } - - @Test - public void pushAndPopEnvironmentShouldPass() { - context.push(); - context.pop(); - } - - @Test - public void popRootEnvironmentShouldPass() { - context.pop(); - } - - @Test(expected = NullPointerException.class) - public void popEmptyEnvironmentStackShouldFail() { - context.pop(); - context.pop(); - } - + private final SemanticContext context = new SemanticContext(); + + @Test + public void rootEnvironmentShouldBeThereInitially() { + Assert.assertNotNull( + "Didn't find root environment. Context is NOT supposed to be empty initially", + context.peek()); + } + + @Test + public void pushAndPopEnvironmentShouldPass() { + context.push(); + context.pop(); + } + + @Test + public void popRootEnvironmentShouldPass() { + context.pop(); + } + + @Test(expected = NullPointerException.class) + public void popEmptyEnvironmentStackShouldFail() { + context.pop(); + context.pop(); + } } diff --git a/legacy/src/test/java/org/opensearch/sql/legacy/antlr/semantic/scope/SymbolTableTest.java b/legacy/src/test/java/org/opensearch/sql/legacy/antlr/semantic/scope/SymbolTableTest.java index fcbc9bf7b6..8fde3bdc3c 100644 --- a/legacy/src/test/java/org/opensearch/sql/legacy/antlr/semantic/scope/SymbolTableTest.java +++ b/legacy/src/test/java/org/opensearch/sql/legacy/antlr/semantic/scope/SymbolTableTest.java @@ -3,7 +3,6 @@ * SPDX-License-Identifier: Apache-2.0 */ - package org.opensearch.sql.legacy.antlr.semantic.scope; import static org.hamcrest.MatcherAssert.assertThat; @@ -25,65 +24,62 @@ import org.opensearch.sql.legacy.antlr.semantic.types.TypeExpression; import org.opensearch.sql.legacy.antlr.semantic.types.base.OpenSearchIndex; -/** - * Test cases for symbol table - */ +/** Test cases for symbol table */ public class SymbolTableTest { - private final SymbolTable symbolTable = new SymbolTable(); + private final SymbolTable symbolTable = new SymbolTable(); - @Test - public void defineFieldSymbolShouldBeAbleToResolve() { - defineSymbolShouldBeAbleToResolve(new Symbol(Namespace.FIELD_NAME, "birthday"), DATE); - } + @Test + public void defineFieldSymbolShouldBeAbleToResolve() { + defineSymbolShouldBeAbleToResolve(new Symbol(Namespace.FIELD_NAME, "birthday"), DATE); + } - @Test - public void defineFunctionSymbolShouldBeAbleToResolve() { - String funcName = "LOG"; - Type expectedType = new TypeExpression() { - @Override - public String getName() { - return "Temp type expression with [NUMBER] -> NUMBER specification"; - } + @Test + public void defineFunctionSymbolShouldBeAbleToResolve() { + String funcName = "LOG"; + Type expectedType = + new TypeExpression() { + @Override + public String getName() { + return "Temp type expression with [NUMBER] -> NUMBER specification"; + } - @Override - public TypeExpressionSpec[] specifications() { - return new TypeExpressionSpec[] { - new TypeExpressionSpec().map(NUMBER).to(NUMBER) - }; - } + @Override + public TypeExpressionSpec[] specifications() { + return new TypeExpressionSpec[] {new TypeExpressionSpec().map(NUMBER).to(NUMBER)}; + } }; - Symbol symbol = new Symbol(Namespace.FUNCTION_NAME, funcName); - defineSymbolShouldBeAbleToResolve(symbol, expectedType); - } - - @Test - public void defineFieldSymbolShouldBeAbleToResolveByPrefix() { - symbolTable.store(new Symbol(Namespace.FIELD_NAME, "s.projects"), new OpenSearchIndex("s.projects", NESTED_FIELD)); - symbolTable.store(new Symbol(Namespace.FIELD_NAME, "s.projects.release"), DATE); - symbolTable.store(new Symbol(Namespace.FIELD_NAME, "s.projects.active"), BOOLEAN); - symbolTable.store(new Symbol(Namespace.FIELD_NAME, "s.address"), TEXT); - symbolTable.store(new Symbol(Namespace.FIELD_NAME, "s.city"), KEYWORD); - symbolTable.store(new Symbol(Namespace.FIELD_NAME, "s.manager.name"), TEXT); + Symbol symbol = new Symbol(Namespace.FUNCTION_NAME, funcName); + defineSymbolShouldBeAbleToResolve(symbol, expectedType); + } - Map typeByName = symbolTable.lookupByPrefix(new Symbol(Namespace.FIELD_NAME, "s.projects")); - assertThat( - typeByName, - allOf( - aMapWithSize(3), - hasEntry("s.projects", (Type) new OpenSearchIndex("s.projects", NESTED_FIELD)), - hasEntry("s.projects.release", DATE), - hasEntry("s.projects.active", BOOLEAN) - ) - ); - } + @Test + public void defineFieldSymbolShouldBeAbleToResolveByPrefix() { + symbolTable.store( + new Symbol(Namespace.FIELD_NAME, "s.projects"), + new OpenSearchIndex("s.projects", NESTED_FIELD)); + symbolTable.store(new Symbol(Namespace.FIELD_NAME, "s.projects.release"), DATE); + symbolTable.store(new Symbol(Namespace.FIELD_NAME, "s.projects.active"), BOOLEAN); + symbolTable.store(new Symbol(Namespace.FIELD_NAME, "s.address"), TEXT); + symbolTable.store(new Symbol(Namespace.FIELD_NAME, "s.city"), KEYWORD); + symbolTable.store(new Symbol(Namespace.FIELD_NAME, "s.manager.name"), TEXT); - private void defineSymbolShouldBeAbleToResolve(Symbol symbol, Type expectedType) { - symbolTable.store(symbol, expectedType); + Map typeByName = + symbolTable.lookupByPrefix(new Symbol(Namespace.FIELD_NAME, "s.projects")); + assertThat( + typeByName, + allOf( + aMapWithSize(3), + hasEntry("s.projects", (Type) new OpenSearchIndex("s.projects", NESTED_FIELD)), + hasEntry("s.projects.release", DATE), + hasEntry("s.projects.active", BOOLEAN))); + } - Optional actualType = symbolTable.lookup(symbol); - Assert.assertTrue(actualType.isPresent()); - Assert.assertEquals(expectedType, actualType.get()); - } + private void defineSymbolShouldBeAbleToResolve(Symbol symbol, Type expectedType) { + symbolTable.store(symbol, expectedType); + Optional actualType = symbolTable.lookup(symbol); + Assert.assertTrue(actualType.isPresent()); + Assert.assertEquals(expectedType, actualType.get()); + } } diff --git a/legacy/src/test/java/org/opensearch/sql/legacy/antlr/semantic/scope/TypeSupplierTest.java b/legacy/src/test/java/org/opensearch/sql/legacy/antlr/semantic/scope/TypeSupplierTest.java index a0b60de4be..e6090117c1 100644 --- a/legacy/src/test/java/org/opensearch/sql/legacy/antlr/semantic/scope/TypeSupplierTest.java +++ b/legacy/src/test/java/org/opensearch/sql/legacy/antlr/semantic/scope/TypeSupplierTest.java @@ -3,7 +3,6 @@ * SPDX-License-Identifier: Apache-2.0 */ - package org.opensearch.sql.legacy.antlr.semantic.scope; import static org.junit.Assert.assertEquals; @@ -15,31 +14,30 @@ import org.opensearch.sql.legacy.antlr.semantic.types.base.OpenSearchDataType; public class TypeSupplierTest { - @Rule - public ExpectedException exception = ExpectedException.none(); + @Rule public ExpectedException exception = ExpectedException.none(); - @Test - public void haveOneTypeShouldPass() { - TypeSupplier age = new TypeSupplier("age", OpenSearchDataType.INTEGER); + @Test + public void haveOneTypeShouldPass() { + TypeSupplier age = new TypeSupplier("age", OpenSearchDataType.INTEGER); - assertEquals(OpenSearchDataType.INTEGER, age.get()); - } + assertEquals(OpenSearchDataType.INTEGER, age.get()); + } - @Test - public void addSameTypeShouldPass() { - TypeSupplier age = new TypeSupplier("age", OpenSearchDataType.INTEGER); - age.add(OpenSearchDataType.INTEGER); + @Test + public void addSameTypeShouldPass() { + TypeSupplier age = new TypeSupplier("age", OpenSearchDataType.INTEGER); + age.add(OpenSearchDataType.INTEGER); - assertEquals(OpenSearchDataType.INTEGER, age.get()); - } + assertEquals(OpenSearchDataType.INTEGER, age.get()); + } - @Test - public void haveTwoTypesShouldThrowException() { - TypeSupplier age = new TypeSupplier("age", OpenSearchDataType.INTEGER); - age.add(OpenSearchDataType.TEXT); + @Test + public void haveTwoTypesShouldThrowException() { + TypeSupplier age = new TypeSupplier("age", OpenSearchDataType.INTEGER); + age.add(OpenSearchDataType.TEXT); - exception.expect(SemanticAnalysisException.class); - exception.expectMessage("Field [age] have conflict type"); - age.get(); - } + exception.expect(SemanticAnalysisException.class); + exception.expectMessage("Field [age] have conflict type"); + age.get(); + } } diff --git a/legacy/src/test/java/org/opensearch/sql/legacy/antlr/semantic/types/TypeExpressionTest.java b/legacy/src/test/java/org/opensearch/sql/legacy/antlr/semantic/types/TypeExpressionTest.java index d1d1d7799b..55c184bcaa 100644 --- a/legacy/src/test/java/org/opensearch/sql/legacy/antlr/semantic/types/TypeExpressionTest.java +++ b/legacy/src/test/java/org/opensearch/sql/legacy/antlr/semantic/types/TypeExpressionTest.java @@ -3,7 +3,6 @@ * SPDX-License-Identifier: Apache-2.0 */ - package org.opensearch.sql.legacy.antlr.semantic.types; import static org.junit.Assert.assertEquals; @@ -21,59 +20,58 @@ import java.util.Arrays; import org.junit.Test; -/** - * Test cases for default implementation methods in interface TypeExpression - */ +/** Test cases for default implementation methods in interface TypeExpression */ public class TypeExpressionTest { - private final TypeExpression test123 = new TypeExpression() { + private final TypeExpression test123 = + new TypeExpression() { @Override public String getName() { - return "TEST123"; + return "TEST123"; } @Override public TypeExpressionSpec[] specifications() { - return new TypeExpressionSpec[] { - new TypeExpressionSpec().map(T(NUMBER)).to(T), - new TypeExpressionSpec().map(STRING, BOOLEAN).to(DATE) - }; + return new TypeExpressionSpec[] { + new TypeExpressionSpec().map(T(NUMBER)).to(T), + new TypeExpressionSpec().map(STRING, BOOLEAN).to(DATE) + }; } - }; + }; - @Test - public void emptySpecificationShouldAlwaysReturnUnknown() { - TypeExpression expr = new TypeExpression() { - @Override - public TypeExpressionSpec[] specifications() { - return new TypeExpressionSpec[0]; - } + @Test + public void emptySpecificationShouldAlwaysReturnUnknown() { + TypeExpression expr = + new TypeExpression() { + @Override + public TypeExpressionSpec[] specifications() { + return new TypeExpressionSpec[0]; + } - @Override - public String getName() { - return "Temp type expression with empty specification"; - } + @Override + public String getName() { + return "Temp type expression with empty specification"; + } }; - assertEquals(UNKNOWN, expr.construct(Arrays.asList(NUMBER))); - assertEquals(UNKNOWN, expr.construct(Arrays.asList(STRING, BOOLEAN))); - assertEquals(UNKNOWN, expr.construct(Arrays.asList(INTEGER, DOUBLE, GEO_POINT))); - } - - @Test - public void compatibilityCheckShouldPassIfAnySpecificationCompatible() { - assertEquals(DOUBLE, test123.construct(Arrays.asList(DOUBLE))); - assertEquals(DATE, test123.construct(Arrays.asList(STRING, BOOLEAN))); - } + assertEquals(UNKNOWN, expr.construct(Arrays.asList(NUMBER))); + assertEquals(UNKNOWN, expr.construct(Arrays.asList(STRING, BOOLEAN))); + assertEquals(UNKNOWN, expr.construct(Arrays.asList(INTEGER, DOUBLE, GEO_POINT))); + } - @Test - public void compatibilityCheckShouldFailIfNoSpecificationCompatible() { - assertEquals(TYPE_ERROR, test123.construct(Arrays.asList(BOOLEAN))); - } + @Test + public void compatibilityCheckShouldPassIfAnySpecificationCompatible() { + assertEquals(DOUBLE, test123.construct(Arrays.asList(DOUBLE))); + assertEquals(DATE, test123.construct(Arrays.asList(STRING, BOOLEAN))); + } - @Test - public void usageShouldPrintAllSpecifications() { - assertEquals("TEST123(NUMBER T) -> T or TEST123(STRING, BOOLEAN) -> DATE", test123.usage()); - } + @Test + public void compatibilityCheckShouldFailIfNoSpecificationCompatible() { + assertEquals(TYPE_ERROR, test123.construct(Arrays.asList(BOOLEAN))); + } + @Test + public void usageShouldPrintAllSpecifications() { + assertEquals("TEST123(NUMBER T) -> T or TEST123(STRING, BOOLEAN) -> DATE", test123.usage()); + } } diff --git a/legacy/src/test/java/org/opensearch/sql/legacy/executor/format/ResultSetTest.java b/legacy/src/test/java/org/opensearch/sql/legacy/executor/format/ResultSetTest.java index 7cfada0b78..3310bdc1e1 100644 --- a/legacy/src/test/java/org/opensearch/sql/legacy/executor/format/ResultSetTest.java +++ b/legacy/src/test/java/org/opensearch/sql/legacy/executor/format/ResultSetTest.java @@ -21,12 +21,13 @@ public Schema getSchema() { }; /** - * Case #1: - * LIKE 'test%' is converted to: + * Case #1: LIKE 'test%' is converted to: + * *

      - *
    1. Regex pattern: test.* - *
    2. OpenSearch search pattern: test* - *
    + *
  • Regex pattern: test.* + *
  • OpenSearch search pattern: test* + * + * * In this case, what OpenSearch returns is the final result. */ @Test @@ -35,15 +36,17 @@ public void testWildcardForZeroOrMoreCharacters() { } /** - * Case #2: - * LIKE 'test_123' is converted to: - *
      x - *
    1. Regex pattern: test.123 - *
    2. OpenSearch search pattern: (all) + * Case #2: LIKE 'test_123' is converted to: + * + *
        + * x + *
      1. Regex pattern: test.123 + *
      2. OpenSearch search pattern: (all) *
      - * Because OpenSearch doesn't support single wildcard character, in this case, none is passed - * as OpenSearch search pattern. So all index names are returned and need to be filtered by - * regex pattern again. + * + * Because OpenSearch doesn't support single wildcard character, in this case, none is passed as + * OpenSearch search pattern. So all index names are returned and need to be filtered by regex + * pattern again. */ @Test public void testWildcardForSingleCharacter() { diff --git a/legacy/src/test/java/org/opensearch/sql/legacy/rewriter/alias/TableAliasPrefixRemoveRuleTest.java b/legacy/src/test/java/org/opensearch/sql/legacy/rewriter/alias/TableAliasPrefixRemoveRuleTest.java index b59bd218e0..4a4161a585 100644 --- a/legacy/src/test/java/org/opensearch/sql/legacy/rewriter/alias/TableAliasPrefixRemoveRuleTest.java +++ b/legacy/src/test/java/org/opensearch/sql/legacy/rewriter/alias/TableAliasPrefixRemoveRuleTest.java @@ -3,7 +3,6 @@ * SPDX-License-Identifier: Apache-2.0 */ - package org.opensearch.sql.legacy.rewriter.alias; import com.alibaba.druid.sql.SQLUtils; @@ -12,122 +11,116 @@ import org.junit.Test; import org.opensearch.sql.legacy.util.SqlParserUtils; -/** - * Test cases for field name prefix remove rule. - */ +/** Test cases for field name prefix remove rule. */ public class TableAliasPrefixRemoveRuleTest { - @Test - public void queryWithUnAliasedTableNameShouldMatch() { - query("SELECT account.age FROM accounts").shouldMatchRule(); - } - - @Test - public void queryWithUnAliasedTableNameInSubQueryShouldNotMatch() { - query("SELECT * FROM test t WHERE t.name IN (SELECT accounts.name FROM accounts)").shouldNotMatchRule(); - } - - @Test - public void queryWithoutUnAliasedTableNameShouldMatch() { - query("SELECT a.age FROM accounts a WHERE a.balance > 1000").shouldMatchRule(); - } - - @Test - public void joinQueryWithoutUnAliasedTableNameShouldNotMatch() { - query("SELECT * FROM accounts a1 JOIN accounts a2 ON a1.city = a2.city").shouldNotMatchRule(); - } - - @Test - public void nestedFieldQueryWithoutUnAliasedTableNameShouldNotMatch() { - query("SELECT * FROM accounts a, a.project p").shouldNotMatchRule(); - } - - @Test - public void selectedFieldNamePrefixedByUnAliasedTableNameShouldRemoveTableNamePrefix() { - query("SELECT accounts.age FROM accounts").shouldBeAfterRewrite("SELECT age FROM accounts"); - query("SELECT accounts.age FROM accounts/temp").shouldBeAfterRewrite("SELECT age FROM accounts/temp"); - query("SELECT age FROM accounts/temp a").shouldBeAfterRewrite("SELECT age FROM accounts/temp"); - } - - @Test - public void allFieldNamePrefixedByUnAliasedTableNameEverywhereShouldRemoveTableNamePrefix() { - query( - "SELECT accounts.age, AVG(accounts.salary) FROM accounts WHERE accounts.age > 10 " + - "GROUP BY accounts.age HAVING AVG(accounts.balance) > 1000 ORDER BY accounts.age" - ).shouldBeAfterRewrite( - "SELECT age, AVG(salary) FROM accounts WHERE age > 10 " + - "GROUP BY age HAVING AVG(balance) > 1000 ORDER BY age" - ); - } - - @Test - public void selectedFieldNamePrefixedByTableAliasShouldRemoveTableAliasPrefix() { - query("SELECT a.age FROM accounts a").shouldBeAfterRewrite("SELECT age FROM accounts"); - query("SELECT a.age FROM accounts/temp a").shouldBeAfterRewrite("SELECT age FROM accounts/temp"); + @Test + public void queryWithUnAliasedTableNameShouldMatch() { + query("SELECT account.age FROM accounts").shouldMatchRule(); + } + + @Test + public void queryWithUnAliasedTableNameInSubQueryShouldNotMatch() { + query("SELECT * FROM test t WHERE t.name IN (SELECT accounts.name FROM accounts)") + .shouldNotMatchRule(); + } + + @Test + public void queryWithoutUnAliasedTableNameShouldMatch() { + query("SELECT a.age FROM accounts a WHERE a.balance > 1000").shouldMatchRule(); + } + + @Test + public void joinQueryWithoutUnAliasedTableNameShouldNotMatch() { + query("SELECT * FROM accounts a1 JOIN accounts a2 ON a1.city = a2.city").shouldNotMatchRule(); + } + + @Test + public void nestedFieldQueryWithoutUnAliasedTableNameShouldNotMatch() { + query("SELECT * FROM accounts a, a.project p").shouldNotMatchRule(); + } + + @Test + public void selectedFieldNamePrefixedByUnAliasedTableNameShouldRemoveTableNamePrefix() { + query("SELECT accounts.age FROM accounts").shouldBeAfterRewrite("SELECT age FROM accounts"); + query("SELECT accounts.age FROM accounts/temp") + .shouldBeAfterRewrite("SELECT age FROM accounts/temp"); + query("SELECT age FROM accounts/temp a").shouldBeAfterRewrite("SELECT age FROM accounts/temp"); + } + + @Test + public void allFieldNamePrefixedByUnAliasedTableNameEverywhereShouldRemoveTableNamePrefix() { + query( + "SELECT accounts.age, AVG(accounts.salary) FROM accounts WHERE accounts.age > 10 " + + "GROUP BY accounts.age HAVING AVG(accounts.balance) > 1000 ORDER BY accounts.age") + .shouldBeAfterRewrite( + "SELECT age, AVG(salary) FROM accounts WHERE age > 10 " + + "GROUP BY age HAVING AVG(balance) > 1000 ORDER BY age"); + } + + @Test + public void selectedFieldNamePrefixedByTableAliasShouldRemoveTableAliasPrefix() { + query("SELECT a.age FROM accounts a").shouldBeAfterRewrite("SELECT age FROM accounts"); + query("SELECT a.age FROM accounts/temp a") + .shouldBeAfterRewrite("SELECT age FROM accounts/temp"); + } + + @Test + public void allFieldNamePrefixedByTableAliasShouldRemoveTableAliasPrefix() { + query( + "SELECT a.age, AVG(a.salary) FROM accounts a WHERE a.age > 10 " + + "GROUP BY a.age HAVING AVG(a.balance) > 1000 ORDER BY a.age") + .shouldBeAfterRewrite( + "SELECT age, AVG(salary) FROM accounts WHERE age > 10 " + + "GROUP BY age HAVING AVG(balance) > 1000 ORDER BY age"); + } + + @Test + public void allFieldNamePrefixedByTableAliasInMultiQueryShouldRemoveTableAliasPrefix() { + query("SELECT t.name FROM test t UNION SELECT a.age FROM accounts a WHERE a.age > 10") + .shouldBeAfterRewrite( + "SELECT name FROM test UNION SELECT age FROM accounts WHERE age > 10"); + } + + @Test + public void unAliasedFieldNameShouldNotBeChanged() { + query("SELECT a.age, name FROM accounts a WHERE balance > 1000") + .shouldBeAfterRewrite("SELECT age, name FROM accounts WHERE balance > 1000"); + query("SELECT accounts.age, name FROM accounts WHERE balance > 1000") + .shouldBeAfterRewrite("SELECT age, name FROM accounts WHERE balance > 1000"); + } + + private QueryAssertion query(String sql) { + return new QueryAssertion(sql); + } + + private static class QueryAssertion { + + private final TableAliasPrefixRemoveRule rule = new TableAliasPrefixRemoveRule(); + + private final SQLQueryExpr expr; + + QueryAssertion(String sql) { + this.expr = SqlParserUtils.parse(sql); } - @Test - public void allFieldNamePrefixedByTableAliasShouldRemoveTableAliasPrefix() { - query( - "SELECT a.age, AVG(a.salary) FROM accounts a WHERE a.age > 10 " + - "GROUP BY a.age HAVING AVG(a.balance) > 1000 ORDER BY a.age" - ).shouldBeAfterRewrite( - "SELECT age, AVG(salary) FROM accounts WHERE age > 10 " + - "GROUP BY age HAVING AVG(balance) > 1000 ORDER BY age" - ); + void shouldMatchRule() { + Assert.assertTrue(match()); } - @Test - public void allFieldNamePrefixedByTableAliasInMultiQueryShouldRemoveTableAliasPrefix() { - query( - "SELECT t.name FROM test t UNION SELECT a.age FROM accounts a WHERE a.age > 10" - ).shouldBeAfterRewrite( - "SELECT name FROM test UNION SELECT age FROM accounts WHERE age > 10" - ); + void shouldNotMatchRule() { + Assert.assertFalse(match()); } - @Test - public void unAliasedFieldNameShouldNotBeChanged() { - query("SELECT a.age, name FROM accounts a WHERE balance > 1000"). - shouldBeAfterRewrite("SELECT age, name FROM accounts WHERE balance > 1000"); - query("SELECT accounts.age, name FROM accounts WHERE balance > 1000"). - shouldBeAfterRewrite("SELECT age, name FROM accounts WHERE balance > 1000"); + void shouldBeAfterRewrite(String expected) { + shouldMatchRule(); + rule.rewrite(expr); + Assert.assertEquals( + SQLUtils.toMySqlString(SqlParserUtils.parse(expected)), SQLUtils.toMySqlString(expr)); } - private QueryAssertion query(String sql) { - return new QueryAssertion(sql); + private boolean match() { + return rule.match(expr); } - - private static class QueryAssertion { - - private final TableAliasPrefixRemoveRule rule = new TableAliasPrefixRemoveRule(); - - private final SQLQueryExpr expr; - - QueryAssertion(String sql) { - this.expr = SqlParserUtils.parse(sql); - } - - void shouldMatchRule() { - Assert.assertTrue(match()); - } - - void shouldNotMatchRule() { - Assert.assertFalse(match()); - } - - void shouldBeAfterRewrite(String expected) { - shouldMatchRule(); - rule.rewrite(expr); - Assert.assertEquals( - SQLUtils.toMySqlString(SqlParserUtils.parse(expected)), - SQLUtils.toMySqlString(expr) - ); - } - - private boolean match() { - return rule.match(expr); - } - } - + } } diff --git a/legacy/src/test/java/org/opensearch/sql/legacy/rewriter/alias/TableTest.java b/legacy/src/test/java/org/opensearch/sql/legacy/rewriter/alias/TableTest.java index 5fc677785d..ab5c6b3d10 100644 --- a/legacy/src/test/java/org/opensearch/sql/legacy/rewriter/alias/TableTest.java +++ b/legacy/src/test/java/org/opensearch/sql/legacy/rewriter/alias/TableTest.java @@ -3,7 +3,6 @@ * SPDX-License-Identifier: Apache-2.0 */ - package org.opensearch.sql.legacy.rewriter.alias; import com.alibaba.druid.sql.ast.expr.SQLBinaryOpExpr; @@ -13,27 +12,24 @@ import org.junit.Assert; import org.junit.Test; -/** - * Test cases for util class {@link Table}. - */ +/** Test cases for util class {@link Table}. */ public class TableTest { - @Test - public void identifierOfTableNameShouldReturnTheTableName() { - Table table = new Table(new SQLExprTableSource(new SQLIdentifierExpr("accounts"))); - Assert.assertEquals("accounts", table.name()); - } - - @Test - public void identifierOfTableAndTypeNameShouldReturnTheTableNameOnly() { - Table table = new Table(new SQLExprTableSource( - new SQLBinaryOpExpr( - new SQLIdentifierExpr("accounts"), - SQLBinaryOperator.Divide, - new SQLIdentifierExpr("test") - ) - )); - Assert.assertEquals("accounts", table.name()); - } + @Test + public void identifierOfTableNameShouldReturnTheTableName() { + Table table = new Table(new SQLExprTableSource(new SQLIdentifierExpr("accounts"))); + Assert.assertEquals("accounts", table.name()); + } + @Test + public void identifierOfTableAndTypeNameShouldReturnTheTableNameOnly() { + Table table = + new Table( + new SQLExprTableSource( + new SQLBinaryOpExpr( + new SQLIdentifierExpr("accounts"), + SQLBinaryOperator.Divide, + new SQLIdentifierExpr("test")))); + Assert.assertEquals("accounts", table.name()); + } } diff --git a/legacy/src/test/java/org/opensearch/sql/legacy/unittest/NestedFieldRewriterTest.java b/legacy/src/test/java/org/opensearch/sql/legacy/unittest/NestedFieldRewriterTest.java index 2593f25379..c303a97fbc 100644 --- a/legacy/src/test/java/org/opensearch/sql/legacy/unittest/NestedFieldRewriterTest.java +++ b/legacy/src/test/java/org/opensearch/sql/legacy/unittest/NestedFieldRewriterTest.java @@ -518,14 +518,13 @@ private void noImpact(String sql) { /** * The intention for this assert method is: - *
        - *
      1. MySqlSelectQueryBlock.equals() doesn't call super.equals(). But select items, from, where - * and group by are all held by parent class SQLSelectQueryBlock. - * - *
      2. SQLSelectGroupByClause doesn't implement equals() at all.. MySqlSelectGroupByExpr - * compares identity of expression.. * - *
      3. MySqlUnionQuery doesn't implement equals() at all + *
          + *
        1. MySqlSelectQueryBlock.equals() doesn't call super.equals(). But select items, from, where + * and group by are all held by parent class SQLSelectQueryBlock. + *
        2. SQLSelectGroupByClause doesn't implement equals() at all.. MySqlSelectGroupByExpr + * compares identity of expression.. + *
        3. MySqlUnionQuery doesn't implement equals() at all *
        */ private void same(SQLQueryExpr actual, SQLQueryExpr expected) { diff --git a/legacy/src/test/java/org/opensearch/sql/legacy/unittest/SqlRequestFactoryTest.java b/legacy/src/test/java/org/opensearch/sql/legacy/unittest/SqlRequestFactoryTest.java index ec13789d28..63fcd98524 100644 --- a/legacy/src/test/java/org/opensearch/sql/legacy/unittest/SqlRequestFactoryTest.java +++ b/legacy/src/test/java/org/opensearch/sql/legacy/unittest/SqlRequestFactoryTest.java @@ -3,7 +3,6 @@ * SPDX-License-Identifier: Apache-2.0 */ - package org.opensearch.sql.legacy.unittest; import static java.util.Collections.emptyList; @@ -28,168 +27,186 @@ @RunWith(MockitoJUnitRunner.class) public class SqlRequestFactoryTest { - @Mock - private RestRequest restRequest; - - @Mock - private OpenSearchSettings settings; - - @Before - public void setup() { - // Force return empty list to avoid ClusterSettings be invoked which is a final class and hard to mock. - // In this case, default value in Setting will be returned all the time. - doReturn(emptyList()).when(settings).getSettings(); - LocalClusterState.state().setPluginSettings(settings); - } - - @Ignore("RestRequest is a final method, and Mockito 1.x cannot mock it." + - "Ignore this test case till we can upgrade to Mockito 2.x") - @Test - public void testGenerateSqlRequest_fromUrlParams() { - String sql = "select * from table"; - Mockito.when(restRequest.method()).thenReturn(RestRequest.Method.GET); - Mockito.when(restRequest.param("sql")).thenReturn(sql); - - SqlRequest sqlRequest = SqlRequestFactory.getSqlRequest(restRequest); - - Assert.assertFalse(sqlRequest instanceof PreparedStatementRequest); - Assert.assertEquals(sql, sqlRequest.getSql()); - } - - @Test - public void testGenerateSqlRequest_sqlRequestFromPayload() { - String payload = "{ \"query\": \"select * from my_table\" }"; - - Mockito.when(this.restRequest.content()).thenReturn(new BytesArray(payload)); - Mockito.when(this.restRequest.method()).thenReturn(RestRequest.Method.POST); - - SqlRequest sqlRequest = SqlRequestFactory.getSqlRequest(this.restRequest); - Assert.assertFalse(sqlRequest instanceof PreparedStatementRequest); - Assert.assertEquals("select * from my_table", sqlRequest.getSql()); - } - - @Test - public void testGenerateSqlRequest_preparedStatementFromPayload() { - String payload = "{\n" + - " \"query\": \"select * from my_table where int_param = ? and double_param = ? and string_param = ? and date_param = ? and null_param = ?\",\n" + - " \"parameters\": [\n" + - " {\n" + - " \"type\": \"integer\",\n" + - " \"value\": 1\n" + - " },\n" + - " {\n" + - " \"type\": \"double\",\n" + - " \"value\": \"2.0\"\n" + - " },\n" + - " {\n" + - " \"type\": \"string\",\n" + - " \"value\": \"string_value\"\n" + - " },\n" + - " {\n" + - " \"type\": \"date\",\n" + - " \"value\": \"2000-01-01\"\n" + - " },\n" + - " {\n" + - " \"type\": \"null\",\n" + - " \"value\": null\n" + - " }\n" + - " ]\n" + - "}"; - Mockito.when(this.restRequest.content()).thenReturn(new BytesArray(payload)); - Mockito.when(this.restRequest.method()).thenReturn(RestRequest.Method.POST); - - SqlRequest sqlRequest = SqlRequestFactory.getSqlRequest(this.restRequest); - - Assert.assertTrue(sqlRequest instanceof PreparedStatementRequest); - PreparedStatementRequest preparedStatementRequest = (PreparedStatementRequest) sqlRequest; - Assert.assertEquals("select * from my_table where int_param = ? and double_param = ? and string_param = ? and date_param = ? and null_param = ?", preparedStatementRequest.getPreparedStatement()); - Assert.assertEquals("select * from my_table where int_param = 1 and double_param = 2.0 and string_param = 'string_value' and date_param = '2000-01-01' and null_param = null", preparedStatementRequest.getSql()); - Assert.assertEquals(5, preparedStatementRequest.getParameters().size()); - Assert.assertTrue(preparedStatementRequest.getParameters().get(0).getValue() instanceof Long); - Assert.assertTrue(preparedStatementRequest.getParameters().get(1).getValue() instanceof Double); - Assert.assertTrue(preparedStatementRequest.getParameters().get(2) instanceof PreparedStatementRequest.StringParameter); - Assert.assertTrue(preparedStatementRequest.getParameters().get(3) instanceof PreparedStatementRequest.StringParameter); - Assert.assertTrue(preparedStatementRequest.getParameters().get(4) instanceof PreparedStatementRequest.NullParameter); - } - - @Test - public void testGenerateSqlRequest_prearedStatementFromPayload2() { - // type not covered in above test case - String payload = "{\n" + - " \"query\": \"select * from my_table where long_param = ? and float_param = ? and keyword_param = ? and boolean_param = ? and byte_param = ?\",\n" + - " \"parameters\": [\n" + - " {\n" + - " \"type\": \"long\",\n" + - " \"value\": 1\n" + - " },\n" + - " {\n" + - " \"type\": \"float\",\n" + - " \"value\": \"2.0\"\n" + - " },\n" + - " {\n" + - " \"type\": \"keyword\",\n" + - " \"value\": \"string_value\"\n" + - " },\n" + - " {\n" + - " \"type\": \"boolean\",\n" + - " \"value\": true\n" + - " },\n" + - " {\n" + - " \"type\": \"byte\",\n" + - " \"value\": 91\n" + - " }\n" + - " ]\n" + - "}"; - Mockito.when(this.restRequest.content()).thenReturn(new BytesArray(payload)); - Mockito.when(this.restRequest.method()).thenReturn(RestRequest.Method.POST); - SqlRequest sqlRequest = SqlRequestFactory.getSqlRequest(this.restRequest); - - Assert.assertTrue(sqlRequest instanceof PreparedStatementRequest); - PreparedStatementRequest preparedStatementRequest = (PreparedStatementRequest) sqlRequest; - Assert.assertEquals(5, preparedStatementRequest.getParameters().size()); - Assert.assertTrue(preparedStatementRequest.getParameters().get(0).getValue() instanceof Long); - Assert.assertTrue(preparedStatementRequest.getParameters().get(1).getValue() instanceof Double); - Assert.assertTrue(preparedStatementRequest.getParameters().get(2) instanceof PreparedStatementRequest.StringParameter); - System.out.println(preparedStatementRequest.getParameters().get(3)); - Assert.assertTrue(preparedStatementRequest.getParameters().get(3).getValue() instanceof Boolean); - Assert.assertTrue(preparedStatementRequest.getParameters().get(4).getValue() instanceof Long); - - } - - @Test(expected = IllegalArgumentException.class) - public void testGenerateSqlRequest_unsupportedHttpMethod() { - Mockito.when(this.restRequest.method()).thenReturn(RestRequest.Method.PUT); - SqlRequest sqlRequest = SqlRequestFactory.getSqlRequest(this.restRequest); - } - - @Test(expected = IllegalArgumentException.class) - public void testGenerateSqlRequest_invalidJson() { - String payload = "{\n" + - " \"query\": \"select * from my_table where param1 = ?\",\n"; - Mockito.when(this.restRequest.content()).thenReturn(new BytesArray(payload)); - Mockito.when(this.restRequest.method()).thenReturn(RestRequest.Method.POST); - - SqlRequest sqlRequest = SqlRequestFactory.getSqlRequest(this.restRequest); - } - - @Test(expected = IllegalArgumentException.class) - public void testGenerateSqlRequest_unsupportedType() { - String payload = "{\n" + - " \"query\": \"select * from my_table where param1 = ?\",\n" + - " \"parameters\": [\n" + - " {\n" + - " \"type\": \"unsupported_type\",\n" + - " \"value\": 1\n" + - " },\n" + - " {\n" + - " \"type\": \"string\",\n" + - " \"value\": \"string_value\"\n" + - " }\n" + - " ]\n" + - "}"; - Mockito.when(this.restRequest.content()).thenReturn(new BytesArray(payload)); - Mockito.when(this.restRequest.method()).thenReturn(RestRequest.Method.POST); - - SqlRequest sqlRequest = SqlRequestFactory.getSqlRequest(this.restRequest); - } + @Mock private RestRequest restRequest; + + @Mock private OpenSearchSettings settings; + + @Before + public void setup() { + // Force return empty list to avoid ClusterSettings be invoked which is a final class and hard + // to mock. + // In this case, default value in Setting will be returned all the time. + doReturn(emptyList()).when(settings).getSettings(); + LocalClusterState.state().setPluginSettings(settings); + } + + @Ignore( + "RestRequest is a final method, and Mockito 1.x cannot mock it." + + "Ignore this test case till we can upgrade to Mockito 2.x") + @Test + public void testGenerateSqlRequest_fromUrlParams() { + String sql = "select * from table"; + Mockito.when(restRequest.method()).thenReturn(RestRequest.Method.GET); + Mockito.when(restRequest.param("sql")).thenReturn(sql); + + SqlRequest sqlRequest = SqlRequestFactory.getSqlRequest(restRequest); + + Assert.assertFalse(sqlRequest instanceof PreparedStatementRequest); + Assert.assertEquals(sql, sqlRequest.getSql()); + } + + @Test + public void testGenerateSqlRequest_sqlRequestFromPayload() { + String payload = "{ \"query\": \"select * from my_table\" }"; + + Mockito.when(this.restRequest.content()).thenReturn(new BytesArray(payload)); + Mockito.when(this.restRequest.method()).thenReturn(RestRequest.Method.POST); + + SqlRequest sqlRequest = SqlRequestFactory.getSqlRequest(this.restRequest); + Assert.assertFalse(sqlRequest instanceof PreparedStatementRequest); + Assert.assertEquals("select * from my_table", sqlRequest.getSql()); + } + + @Test + public void testGenerateSqlRequest_preparedStatementFromPayload() { + String payload = + "{\n" + + " \"query\": \"select * from my_table where int_param = ? and double_param = ? and" + + " string_param = ? and date_param = ? and null_param = ?\",\n" + + " \"parameters\": [\n" + + " {\n" + + " \"type\": \"integer\",\n" + + " \"value\": 1\n" + + " },\n" + + " {\n" + + " \"type\": \"double\",\n" + + " \"value\": \"2.0\"\n" + + " },\n" + + " {\n" + + " \"type\": \"string\",\n" + + " \"value\": \"string_value\"\n" + + " },\n" + + " {\n" + + " \"type\": \"date\",\n" + + " \"value\": \"2000-01-01\"\n" + + " },\n" + + " {\n" + + " \"type\": \"null\",\n" + + " \"value\": null\n" + + " }\n" + + " ]\n" + + "}"; + Mockito.when(this.restRequest.content()).thenReturn(new BytesArray(payload)); + Mockito.when(this.restRequest.method()).thenReturn(RestRequest.Method.POST); + + SqlRequest sqlRequest = SqlRequestFactory.getSqlRequest(this.restRequest); + + Assert.assertTrue(sqlRequest instanceof PreparedStatementRequest); + PreparedStatementRequest preparedStatementRequest = (PreparedStatementRequest) sqlRequest; + Assert.assertEquals( + "select * from my_table where int_param = ? and double_param = ? and string_param = ? and" + + " date_param = ? and null_param = ?", + preparedStatementRequest.getPreparedStatement()); + Assert.assertEquals( + "select * from my_table where int_param = 1 and double_param = 2.0 and string_param =" + + " 'string_value' and date_param = '2000-01-01' and null_param = null", + preparedStatementRequest.getSql()); + Assert.assertEquals(5, preparedStatementRequest.getParameters().size()); + Assert.assertTrue(preparedStatementRequest.getParameters().get(0).getValue() instanceof Long); + Assert.assertTrue(preparedStatementRequest.getParameters().get(1).getValue() instanceof Double); + Assert.assertTrue( + preparedStatementRequest.getParameters().get(2) + instanceof PreparedStatementRequest.StringParameter); + Assert.assertTrue( + preparedStatementRequest.getParameters().get(3) + instanceof PreparedStatementRequest.StringParameter); + Assert.assertTrue( + preparedStatementRequest.getParameters().get(4) + instanceof PreparedStatementRequest.NullParameter); + } + + @Test + public void testGenerateSqlRequest_prearedStatementFromPayload2() { + // type not covered in above test case + String payload = + "{\n" + + " \"query\": \"select * from my_table where long_param = ? and float_param = ? and" + + " keyword_param = ? and boolean_param = ? and byte_param = ?\",\n" + + " \"parameters\": [\n" + + " {\n" + + " \"type\": \"long\",\n" + + " \"value\": 1\n" + + " },\n" + + " {\n" + + " \"type\": \"float\",\n" + + " \"value\": \"2.0\"\n" + + " },\n" + + " {\n" + + " \"type\": \"keyword\",\n" + + " \"value\": \"string_value\"\n" + + " },\n" + + " {\n" + + " \"type\": \"boolean\",\n" + + " \"value\": true\n" + + " },\n" + + " {\n" + + " \"type\": \"byte\",\n" + + " \"value\": 91\n" + + " }\n" + + " ]\n" + + "}"; + Mockito.when(this.restRequest.content()).thenReturn(new BytesArray(payload)); + Mockito.when(this.restRequest.method()).thenReturn(RestRequest.Method.POST); + SqlRequest sqlRequest = SqlRequestFactory.getSqlRequest(this.restRequest); + + Assert.assertTrue(sqlRequest instanceof PreparedStatementRequest); + PreparedStatementRequest preparedStatementRequest = (PreparedStatementRequest) sqlRequest; + Assert.assertEquals(5, preparedStatementRequest.getParameters().size()); + Assert.assertTrue(preparedStatementRequest.getParameters().get(0).getValue() instanceof Long); + Assert.assertTrue(preparedStatementRequest.getParameters().get(1).getValue() instanceof Double); + Assert.assertTrue( + preparedStatementRequest.getParameters().get(2) + instanceof PreparedStatementRequest.StringParameter); + System.out.println(preparedStatementRequest.getParameters().get(3)); + Assert.assertTrue( + preparedStatementRequest.getParameters().get(3).getValue() instanceof Boolean); + Assert.assertTrue(preparedStatementRequest.getParameters().get(4).getValue() instanceof Long); + } + + @Test(expected = IllegalArgumentException.class) + public void testGenerateSqlRequest_unsupportedHttpMethod() { + Mockito.when(this.restRequest.method()).thenReturn(RestRequest.Method.PUT); + SqlRequest sqlRequest = SqlRequestFactory.getSqlRequest(this.restRequest); + } + + @Test(expected = IllegalArgumentException.class) + public void testGenerateSqlRequest_invalidJson() { + String payload = "{\n" + " \"query\": \"select * from my_table where param1 = ?\",\n"; + Mockito.when(this.restRequest.content()).thenReturn(new BytesArray(payload)); + Mockito.when(this.restRequest.method()).thenReturn(RestRequest.Method.POST); + + SqlRequest sqlRequest = SqlRequestFactory.getSqlRequest(this.restRequest); + } + + @Test(expected = IllegalArgumentException.class) + public void testGenerateSqlRequest_unsupportedType() { + String payload = + "{\n" + + " \"query\": \"select * from my_table where param1 = ?\",\n" + + " \"parameters\": [\n" + + " {\n" + + " \"type\": \"unsupported_type\",\n" + + " \"value\": 1\n" + + " },\n" + + " {\n" + + " \"type\": \"string\",\n" + + " \"value\": \"string_value\"\n" + + " }\n" + + " ]\n" + + "}"; + Mockito.when(this.restRequest.content()).thenReturn(new BytesArray(payload)); + Mockito.when(this.restRequest.method()).thenReturn(RestRequest.Method.POST); + + SqlRequest sqlRequest = SqlRequestFactory.getSqlRequest(this.restRequest); + } } diff --git a/legacy/src/test/java/org/opensearch/sql/legacy/unittest/SqlRequestParamTest.java b/legacy/src/test/java/org/opensearch/sql/legacy/unittest/SqlRequestParamTest.java index 103d43d95c..3c47832761 100644 --- a/legacy/src/test/java/org/opensearch/sql/legacy/unittest/SqlRequestParamTest.java +++ b/legacy/src/test/java/org/opensearch/sql/legacy/unittest/SqlRequestParamTest.java @@ -3,7 +3,6 @@ * SPDX-License-Identifier: Apache-2.0 */ - package org.opensearch.sql.legacy.unittest; import static org.junit.Assert.assertEquals; @@ -20,52 +19,52 @@ import org.opensearch.sql.legacy.request.SqlRequestParam; public class SqlRequestParamTest { - @Rule - public ExpectedException exceptionRule = ExpectedException.none(); + @Rule public ExpectedException exceptionRule = ExpectedException.none(); - @Test - public void shouldReturnTrueIfPrettyParamsIsTrue() { - assertTrue(SqlRequestParam.isPrettyFormat(ImmutableMap.of(QUERY_PARAMS_PRETTY, "true"))); - } + @Test + public void shouldReturnTrueIfPrettyParamsIsTrue() { + assertTrue(SqlRequestParam.isPrettyFormat(ImmutableMap.of(QUERY_PARAMS_PRETTY, "true"))); + } - @Test - public void shouldReturnTrueIfPrettyParamsIsEmpty() { - assertTrue(SqlRequestParam.isPrettyFormat(ImmutableMap.of(QUERY_PARAMS_PRETTY, ""))); - } + @Test + public void shouldReturnTrueIfPrettyParamsIsEmpty() { + assertTrue(SqlRequestParam.isPrettyFormat(ImmutableMap.of(QUERY_PARAMS_PRETTY, ""))); + } - @Test - public void shouldReturnFalseIfNoPrettyParams() { - assertFalse(SqlRequestParam.isPrettyFormat(ImmutableMap.of())); - } + @Test + public void shouldReturnFalseIfNoPrettyParams() { + assertFalse(SqlRequestParam.isPrettyFormat(ImmutableMap.of())); + } - @Test - public void shouldReturnFalseIfPrettyParamsIsUnknownValue() { - assertFalse(SqlRequestParam.isPrettyFormat(ImmutableMap.of(QUERY_PARAMS_PRETTY, "unknown"))); - } + @Test + public void shouldReturnFalseIfPrettyParamsIsUnknownValue() { + assertFalse(SqlRequestParam.isPrettyFormat(ImmutableMap.of(QUERY_PARAMS_PRETTY, "unknown"))); + } - @Test - public void shouldReturnJSONIfFormatParamsIsJSON() { - assertEquals(Format.JSON, SqlRequestParam.getFormat(ImmutableMap.of(QUERY_PARAMS_FORMAT, "json"))); - } + @Test + public void shouldReturnJSONIfFormatParamsIsJSON() { + assertEquals( + Format.JSON, SqlRequestParam.getFormat(ImmutableMap.of(QUERY_PARAMS_FORMAT, "json"))); + } - @Test - public void shouldReturnDefaultFormatIfNoFormatParams() { - assertEquals(Format.JDBC, SqlRequestParam.getFormat(ImmutableMap.of())); - } + @Test + public void shouldReturnDefaultFormatIfNoFormatParams() { + assertEquals(Format.JDBC, SqlRequestParam.getFormat(ImmutableMap.of())); + } - @Test - public void shouldThrowExceptionIfFormatParamsIsEmpty() { - exceptionRule.expect(IllegalArgumentException.class); - exceptionRule.expectMessage("Failed to create executor due to unknown response format: "); + @Test + public void shouldThrowExceptionIfFormatParamsIsEmpty() { + exceptionRule.expect(IllegalArgumentException.class); + exceptionRule.expectMessage("Failed to create executor due to unknown response format: "); - assertEquals(Format.JDBC, SqlRequestParam.getFormat(ImmutableMap.of(QUERY_PARAMS_FORMAT, ""))); - } + assertEquals(Format.JDBC, SqlRequestParam.getFormat(ImmutableMap.of(QUERY_PARAMS_FORMAT, ""))); + } - @Test - public void shouldThrowExceptionIfFormatParamsIsNotSupported() { - exceptionRule.expect(IllegalArgumentException.class); - exceptionRule.expectMessage("Failed to create executor due to unknown response format: xml"); + @Test + public void shouldThrowExceptionIfFormatParamsIsNotSupported() { + exceptionRule.expect(IllegalArgumentException.class); + exceptionRule.expectMessage("Failed to create executor due to unknown response format: xml"); - SqlRequestParam.getFormat(ImmutableMap.of(QUERY_PARAMS_FORMAT, "xml")); - } + SqlRequestParam.getFormat(ImmutableMap.of(QUERY_PARAMS_FORMAT, "xml")); + } } diff --git a/legacy/src/test/java/org/opensearch/sql/legacy/unittest/StringOperatorsTest.java b/legacy/src/test/java/org/opensearch/sql/legacy/unittest/StringOperatorsTest.java index b2d13f3ead..27b8e7f2c6 100644 --- a/legacy/src/test/java/org/opensearch/sql/legacy/unittest/StringOperatorsTest.java +++ b/legacy/src/test/java/org/opensearch/sql/legacy/unittest/StringOperatorsTest.java @@ -3,7 +3,6 @@ * SPDX-License-Identifier: Apache-2.0 */ - package org.opensearch.sql.legacy.unittest; import static org.junit.Assert.assertTrue; @@ -17,196 +16,154 @@ public class StringOperatorsTest { - private static SqlParser parser; - - @BeforeClass - public static void init() { parser = new SqlParser(); } - - @Test - public void substringTest() { - String query = "SELECT substring(lastname, 2, 1) FROM accounts WHERE substring(lastname, 2, 1) = 'a' " + - "GROUP BY substring(lastname, 2, 1) ORDER BY substring(lastname, 2, 1)"; - - ScriptField scriptField = CheckScriptContents.getScriptFieldFromQuery(query); - assertTrue( - CheckScriptContents.scriptContainsString( - scriptField, - "doc['lastname'].value.substring(1, end)")); - - ScriptFilter scriptFilter = CheckScriptContents.getScriptFilterFromQuery(query, parser); - assertTrue( - CheckScriptContents.scriptContainsString( - scriptFilter, - "doc['lastname'].value.substring(1, end)" - ) - ); - } - - @Test - public void substringIndexOutOfBoundTest() { - String query = "SELECT substring('sampleName', 0, 20) FROM accounts"; - ScriptField scriptField = CheckScriptContents.getScriptFieldFromQuery(query); - assertTrue( - CheckScriptContents.scriptContainsString( - scriptField, - "def end = (int) Math.min(0 + 20, 'sampleName'.length())" - ) - ); - } - - @Test - public void lengthTest() { - String query = "SELECT length(lastname) FROM accounts WHERE length(lastname) = 5 " + - "GROUP BY length(lastname) ORDER BY length(lastname)"; - - ScriptField scriptField = CheckScriptContents.getScriptFieldFromQuery(query); - assertTrue( - CheckScriptContents.scriptContainsString( - scriptField, - "doc['lastname'].value.length()" - ) - ); - - ScriptFilter scriptFilter = CheckScriptContents.getScriptFilterFromQuery(query, parser); - assertTrue( - CheckScriptContents.scriptContainsString( - scriptFilter, - "doc['lastname'].value.length()" - ) - ); - } - - @Test - public void replaceTest() { - String query = "SELECT replace(lastname, 'a', 'A') FROM accounts WHERE replace(lastname, 'a', 'A') = 'aba' " + - "GROUP BY replace(lastname, 'a', 'A') ORDER BY replace(lastname, 'a', 'A')"; - - ScriptField scriptField = CheckScriptContents.getScriptFieldFromQuery(query); - assertTrue( - CheckScriptContents.scriptContainsString( - scriptField, - "doc['lastname'].value.replace('a','A')" - ) - ); - - ScriptFilter scriptFilter = CheckScriptContents.getScriptFilterFromQuery(query, parser); - assertTrue( - CheckScriptContents.scriptContainsString( - scriptFilter, - "doc['lastname'].value.replace('a','A')" - ) - ); - } - - @Test - public void locateTest() { - String query = "SELECT locate('a', lastname, 1) FROM accounts WHERE locate('a', lastname, 1) = 4 " + - "GROUP BY locate('a', lastname, 1) ORDER BY locate('a', lastname, 1)"; - - ScriptField scriptField = CheckScriptContents.getScriptFieldFromQuery(query); - assertTrue( - CheckScriptContents.scriptContainsString( - scriptField, - "doc['lastname'].value.indexOf('a',0)+1" - ) - ); - - ScriptFilter scriptFilter = CheckScriptContents.getScriptFilterFromQuery(query, parser); - assertTrue( - CheckScriptContents.scriptContainsString( - scriptFilter, - "doc['lastname'].value.indexOf('a',0)+1" - ) - ); - } - - @Test - public void ltrimTest() { - String query = "SELECT ltrim(lastname) FROM accounts WHERE ltrim(lastname) = 'abc' " + - "GROUP BY ltrim(lastname) ORDER BY ltrim(lastname)"; - - ScriptField scriptField = CheckScriptContents.getScriptFieldFromQuery(query); - assertTrue( - CheckScriptContents.scriptContainsString( - scriptField, - "Character.isWhitespace(doc['lastname'].value.charAt(pos))" - ) - ); - - ScriptFilter scriptFilter = CheckScriptContents.getScriptFilterFromQuery(query, parser); - assertTrue( - CheckScriptContents.scriptContainsString( - scriptFilter, - "Character.isWhitespace(doc['lastname'].value.charAt(pos))" - ) - ); - } - - @Test - public void rtrimTest() { - String query = "SELECT rtrim(lastname) FROM accounts WHERE rtrim(lastname) = 'cba' " + - "GROUP BY rtrim(lastname) ORDER BY rtrim(lastname)"; - - ScriptField scriptField = CheckScriptContents.getScriptFieldFromQuery(query); - assertTrue( - CheckScriptContents.scriptContainsString( - scriptField, - "Character.isWhitespace(doc['lastname'].value.charAt(pos))" - ) - ); - - ScriptFilter scriptFilter = CheckScriptContents.getScriptFilterFromQuery(query, parser); - assertTrue( - CheckScriptContents.scriptContainsString( - scriptFilter, - "Character.isWhitespace(doc['lastname'].value.charAt(pos))" - ) - ); - } - - @Test - public void asciiTest() { - String query = "SELECT ascii(lastname) FROM accounts WHERE ascii(lastname) = 108 " + - "GROUP BY ascii(lastname) ORDER BY ascii(lastname)"; - - ScriptField scriptField = CheckScriptContents.getScriptFieldFromQuery(query); - assertTrue( - CheckScriptContents.scriptContainsString( - scriptField, - "(int) doc['lastname'].value.charAt(0)" - ) - ); - - ScriptFilter scriptFilter = CheckScriptContents.getScriptFilterFromQuery(query, parser); - assertTrue( - CheckScriptContents.scriptContainsString( - scriptFilter, - "(int) doc['lastname'].value.charAt(0)" - ) - ); - } - - @Test - public void left() { - String query = "SELECT left(lastname, 1) FROM accounts"; - ScriptField scriptField = CheckScriptContents.getScriptFieldFromQuery(query); - assertTrue( - CheckScriptContents.scriptContainsString( - scriptField, - "doc['lastname'].value.substring(0, len)" - ) - ); - } - - @Test - public void right() { - String query = "SELECT right(lastname, 2) FROM accounts"; - ScriptField scriptField = CheckScriptContents.getScriptFieldFromQuery(query); - assertTrue( - CheckScriptContents.scriptContainsString( - scriptField, - "doc['lastname'].value.substring(start)" - ) - ); - } + private static SqlParser parser; + + @BeforeClass + public static void init() { + parser = new SqlParser(); + } + + @Test + public void substringTest() { + String query = + "SELECT substring(lastname, 2, 1) FROM accounts WHERE substring(lastname, 2, 1) = 'a' " + + "GROUP BY substring(lastname, 2, 1) ORDER BY substring(lastname, 2, 1)"; + + ScriptField scriptField = CheckScriptContents.getScriptFieldFromQuery(query); + assertTrue( + CheckScriptContents.scriptContainsString( + scriptField, "doc['lastname'].value.substring(1, end)")); + + ScriptFilter scriptFilter = CheckScriptContents.getScriptFilterFromQuery(query, parser); + assertTrue( + CheckScriptContents.scriptContainsString( + scriptFilter, "doc['lastname'].value.substring(1, end)")); + } + + @Test + public void substringIndexOutOfBoundTest() { + String query = "SELECT substring('sampleName', 0, 20) FROM accounts"; + ScriptField scriptField = CheckScriptContents.getScriptFieldFromQuery(query); + assertTrue( + CheckScriptContents.scriptContainsString( + scriptField, "def end = (int) Math.min(0 + 20, 'sampleName'.length())")); + } + + @Test + public void lengthTest() { + String query = + "SELECT length(lastname) FROM accounts WHERE length(lastname) = 5 " + + "GROUP BY length(lastname) ORDER BY length(lastname)"; + + ScriptField scriptField = CheckScriptContents.getScriptFieldFromQuery(query); + assertTrue( + CheckScriptContents.scriptContainsString(scriptField, "doc['lastname'].value.length()")); + + ScriptFilter scriptFilter = CheckScriptContents.getScriptFilterFromQuery(query, parser); + assertTrue( + CheckScriptContents.scriptContainsString(scriptFilter, "doc['lastname'].value.length()")); + } + + @Test + public void replaceTest() { + String query = + "SELECT replace(lastname, 'a', 'A') FROM accounts WHERE replace(lastname, 'a', 'A') = 'aba'" + + " GROUP BY replace(lastname, 'a', 'A') ORDER BY replace(lastname, 'a', 'A')"; + + ScriptField scriptField = CheckScriptContents.getScriptFieldFromQuery(query); + assertTrue( + CheckScriptContents.scriptContainsString( + scriptField, "doc['lastname'].value.replace('a','A')")); + + ScriptFilter scriptFilter = CheckScriptContents.getScriptFilterFromQuery(query, parser); + assertTrue( + CheckScriptContents.scriptContainsString( + scriptFilter, "doc['lastname'].value.replace('a','A')")); + } + + @Test + public void locateTest() { + String query = + "SELECT locate('a', lastname, 1) FROM accounts WHERE locate('a', lastname, 1) = 4 " + + "GROUP BY locate('a', lastname, 1) ORDER BY locate('a', lastname, 1)"; + + ScriptField scriptField = CheckScriptContents.getScriptFieldFromQuery(query); + assertTrue( + CheckScriptContents.scriptContainsString( + scriptField, "doc['lastname'].value.indexOf('a',0)+1")); + + ScriptFilter scriptFilter = CheckScriptContents.getScriptFilterFromQuery(query, parser); + assertTrue( + CheckScriptContents.scriptContainsString( + scriptFilter, "doc['lastname'].value.indexOf('a',0)+1")); + } + + @Test + public void ltrimTest() { + String query = + "SELECT ltrim(lastname) FROM accounts WHERE ltrim(lastname) = 'abc' " + + "GROUP BY ltrim(lastname) ORDER BY ltrim(lastname)"; + + ScriptField scriptField = CheckScriptContents.getScriptFieldFromQuery(query); + assertTrue( + CheckScriptContents.scriptContainsString( + scriptField, "Character.isWhitespace(doc['lastname'].value.charAt(pos))")); + + ScriptFilter scriptFilter = CheckScriptContents.getScriptFilterFromQuery(query, parser); + assertTrue( + CheckScriptContents.scriptContainsString( + scriptFilter, "Character.isWhitespace(doc['lastname'].value.charAt(pos))")); + } + + @Test + public void rtrimTest() { + String query = + "SELECT rtrim(lastname) FROM accounts WHERE rtrim(lastname) = 'cba' " + + "GROUP BY rtrim(lastname) ORDER BY rtrim(lastname)"; + + ScriptField scriptField = CheckScriptContents.getScriptFieldFromQuery(query); + assertTrue( + CheckScriptContents.scriptContainsString( + scriptField, "Character.isWhitespace(doc['lastname'].value.charAt(pos))")); + + ScriptFilter scriptFilter = CheckScriptContents.getScriptFilterFromQuery(query, parser); + assertTrue( + CheckScriptContents.scriptContainsString( + scriptFilter, "Character.isWhitespace(doc['lastname'].value.charAt(pos))")); + } + + @Test + public void asciiTest() { + String query = + "SELECT ascii(lastname) FROM accounts WHERE ascii(lastname) = 108 " + + "GROUP BY ascii(lastname) ORDER BY ascii(lastname)"; + + ScriptField scriptField = CheckScriptContents.getScriptFieldFromQuery(query); + assertTrue( + CheckScriptContents.scriptContainsString( + scriptField, "(int) doc['lastname'].value.charAt(0)")); + + ScriptFilter scriptFilter = CheckScriptContents.getScriptFilterFromQuery(query, parser); + assertTrue( + CheckScriptContents.scriptContainsString( + scriptFilter, "(int) doc['lastname'].value.charAt(0)")); + } + + @Test + public void left() { + String query = "SELECT left(lastname, 1) FROM accounts"; + ScriptField scriptField = CheckScriptContents.getScriptFieldFromQuery(query); + assertTrue( + CheckScriptContents.scriptContainsString( + scriptField, "doc['lastname'].value.substring(0, len)")); + } + + @Test + public void right() { + String query = "SELECT right(lastname, 2) FROM accounts"; + ScriptField scriptField = CheckScriptContents.getScriptFieldFromQuery(query); + assertTrue( + CheckScriptContents.scriptContainsString( + scriptField, "doc['lastname'].value.substring(start)")); + } } diff --git a/legacy/src/test/java/org/opensearch/sql/legacy/unittest/WhereWithBoolConditionTest.java b/legacy/src/test/java/org/opensearch/sql/legacy/unittest/WhereWithBoolConditionTest.java index e7df57ce31..de6f2c8dda 100644 --- a/legacy/src/test/java/org/opensearch/sql/legacy/unittest/WhereWithBoolConditionTest.java +++ b/legacy/src/test/java/org/opensearch/sql/legacy/unittest/WhereWithBoolConditionTest.java @@ -3,7 +3,6 @@ * SPDX-License-Identifier: Apache-2.0 */ - package org.opensearch.sql.legacy.unittest; import static org.hamcrest.MatcherAssert.assertThat; @@ -25,58 +24,60 @@ import org.opensearch.sql.legacy.util.TestsConstants; import org.opensearch.sql.legacy.utils.StringUtils; - public class WhereWithBoolConditionTest { - @Test - public void whereWithBoolCompilationTest() - throws SQLFeatureNotSupportedException, SqlParseException, SQLFeatureDisabledException { - query(StringUtils.format("SELECT * FROM %s WHERE male = false", TestsConstants.TEST_INDEX_BANK)); - } + @Test + public void whereWithBoolCompilationTest() + throws SQLFeatureNotSupportedException, SqlParseException, SQLFeatureDisabledException { + query( + StringUtils.format("SELECT * FROM %s WHERE male = false", TestsConstants.TEST_INDEX_BANK)); + } - @Test - public void selectAllTest() - throws SQLFeatureNotSupportedException, SqlParseException, IOException, - SQLFeatureDisabledException { - String expectedOutput = Files.toString( - new File(getResourcePath() + "src/test/resources/expectedOutput/select_where_true.json"), StandardCharsets.UTF_8) - .replaceAll("\r", ""); + @Test + public void selectAllTest() + throws SQLFeatureNotSupportedException, + SqlParseException, + IOException, + SQLFeatureDisabledException { + String expectedOutput = + Files.toString( + new File( + getResourcePath() + "src/test/resources/expectedOutput/select_where_true.json"), + StandardCharsets.UTF_8) + .replaceAll("\r", ""); - assertThat(removeSpaces( - query( - StringUtils.format( - "SELECT * " + - "FROM %s " + - "WHERE male = true", - TestsConstants.TEST_INDEX_BANK)) - ), - equalTo(removeSpaces(expectedOutput)) - ); - } + assertThat( + removeSpaces( + query( + StringUtils.format( + "SELECT * " + "FROM %s " + "WHERE male = true", + TestsConstants.TEST_INDEX_BANK))), + equalTo(removeSpaces(expectedOutput))); + } - private String query(String query) - throws SQLFeatureNotSupportedException, SqlParseException, SQLFeatureDisabledException { - return explain(query); - } + private String query(String query) + throws SQLFeatureNotSupportedException, SqlParseException, SQLFeatureDisabledException { + return explain(query); + } - private String explain(String sql) - throws SQLFeatureNotSupportedException, SqlParseException, SQLFeatureDisabledException { - Client mockClient = Mockito.mock(Client.class); - CheckScriptContents.stubMockClient(mockClient); - QueryAction queryAction = OpenSearchActionFactory.create(mockClient, sql); - return queryAction.explain().explain(); - } + private String explain(String sql) + throws SQLFeatureNotSupportedException, SqlParseException, SQLFeatureDisabledException { + Client mockClient = Mockito.mock(Client.class); + CheckScriptContents.stubMockClient(mockClient); + QueryAction queryAction = OpenSearchActionFactory.create(mockClient, sql); + return queryAction.explain().explain(); + } - private String removeSpaces(String s) { - return s.replaceAll("\\s+", ""); - } + private String removeSpaces(String s) { + return s.replaceAll("\\s+", ""); + } - private String getResourcePath() { - String projectRoot = System.getProperty("project.root"); - if ( projectRoot!= null && projectRoot.trim().length() > 0) { - return projectRoot.trim() + "/"; - } else { - return ""; - } + private String getResourcePath() { + String projectRoot = System.getProperty("project.root"); + if (projectRoot != null && projectRoot.trim().length() > 0) { + return projectRoot.trim() + "/"; + } else { + return ""; } + } } diff --git a/legacy/src/test/java/org/opensearch/sql/legacy/unittest/expression/core/UnaryExpressionTest.java b/legacy/src/test/java/org/opensearch/sql/legacy/unittest/expression/core/UnaryExpressionTest.java index 04196bab0a..c8582ecb05 100644 --- a/legacy/src/test/java/org/opensearch/sql/legacy/unittest/expression/core/UnaryExpressionTest.java +++ b/legacy/src/test/java/org/opensearch/sql/legacy/unittest/expression/core/UnaryExpressionTest.java @@ -3,7 +3,6 @@ * SPDX-License-Identifier: Apache-2.0 */ - package org.opensearch.sql.legacy.unittest.expression.core; import static org.junit.Assert.assertEquals; @@ -21,108 +20,98 @@ @RunWith(MockitoJUnitRunner.class) public class UnaryExpressionTest extends ExpressionTest { - @Rule - public ExpectedException exceptionRule = ExpectedException.none(); - - @Test - public void absShouldPass() { - assertEquals(2.0d, apply(ScalarOperation.ABS, literal(doubleValue(-2d)))); - } - - @Test - public void asinShouldPass() { - assertEquals(0.1001674211615598d, apply(ScalarOperation.ASIN, literal(doubleValue(0.1d)))); - } - - @Test - public void atanShouldPass() { - assertEquals(1.1071487177940904d, apply(ScalarOperation.ATAN, literal(doubleValue(2d)))); - } - - @Test - public void tanShouldPass() { - assertEquals(-2.185039863261519, apply(ScalarOperation.TAN, literal(doubleValue(2d)))); - } - - @Test - public void atan2ShouldPass() { - assertEquals(1.1071487177940904d, - apply(ScalarOperation.ATAN2, literal(doubleValue(2d)), literal(doubleValue(1d)))); - } - - @Test - public void cbrtShouldPass() { - assertEquals(1.2599210498948732d, - apply(ScalarOperation.CBRT, literal(doubleValue(2d)))); - } - - @Test - public void ceilShouldPass() { - assertEquals(3.0d, - apply(ScalarOperation.CEIL, literal(doubleValue(2.1d)))); - } - - @Test - public void floorShouldPass() { - assertEquals(2.0d, - apply(ScalarOperation.FLOOR, literal(doubleValue(2.1d)))); - } - - @Test - public void cosShouldPass() { - assertEquals(-0.4161468365471424d, - apply(ScalarOperation.COS, literal(doubleValue(2d)))); - } - - @Test - public void coshShouldPass() { - assertEquals(3.7621956910836314d, - apply(ScalarOperation.COSH, literal(doubleValue(2d)))); - } - - @Test - public void expShouldPass() { - assertEquals(7.38905609893065d, - apply(ScalarOperation.EXP, literal(doubleValue(2d)))); - } - - @Test - public void lnShouldPass() { - assertEquals(0.6931471805599453d, - apply(ScalarOperation.LN, literal(doubleValue(2d)))); - } - - @Test - public void logShouldPass() { - assertEquals(0.6931471805599453d, - apply(ScalarOperation.LOG, literal(doubleValue(2d)))); - } - - @Test - public void log2ShouldPass() { - assertEquals(1.0d, - apply(ScalarOperation.LOG2, literal(doubleValue(2d)))); - } - - @Test - public void log10ShouldPass() { - assertEquals(0.3010299956639812, - apply(ScalarOperation.LOG10, literal(doubleValue(2d)))); - } - - @Test - public void absWithStringShouldThrowException() { - exceptionRule.expect(RuntimeException.class); - exceptionRule.expectMessage("unexpected operation type: ABS(STRING_VALUE)"); - - apply(ScalarOperation.ABS, literal(stringValue("stringValue"))); - } - - @Test - public void atan2WithStringShouldThrowException() { - exceptionRule.expect(RuntimeException.class); - exceptionRule.expectMessage("unexpected operation type: ATAN2(DOUBLE_VALUE,STRING_VALUE)"); - - apply(ScalarOperation.ATAN2, literal(doubleValue(2d)), literal(stringValue("stringValue"))); - } + @Rule public ExpectedException exceptionRule = ExpectedException.none(); + + @Test + public void absShouldPass() { + assertEquals(2.0d, apply(ScalarOperation.ABS, literal(doubleValue(-2d)))); + } + + @Test + public void asinShouldPass() { + assertEquals(0.1001674211615598d, apply(ScalarOperation.ASIN, literal(doubleValue(0.1d)))); + } + + @Test + public void atanShouldPass() { + assertEquals(1.1071487177940904d, apply(ScalarOperation.ATAN, literal(doubleValue(2d)))); + } + + @Test + public void tanShouldPass() { + assertEquals(-2.185039863261519, apply(ScalarOperation.TAN, literal(doubleValue(2d)))); + } + + @Test + public void atan2ShouldPass() { + assertEquals( + 1.1071487177940904d, + apply(ScalarOperation.ATAN2, literal(doubleValue(2d)), literal(doubleValue(1d)))); + } + + @Test + public void cbrtShouldPass() { + assertEquals(1.2599210498948732d, apply(ScalarOperation.CBRT, literal(doubleValue(2d)))); + } + + @Test + public void ceilShouldPass() { + assertEquals(3.0d, apply(ScalarOperation.CEIL, literal(doubleValue(2.1d)))); + } + + @Test + public void floorShouldPass() { + assertEquals(2.0d, apply(ScalarOperation.FLOOR, literal(doubleValue(2.1d)))); + } + + @Test + public void cosShouldPass() { + assertEquals(-0.4161468365471424d, apply(ScalarOperation.COS, literal(doubleValue(2d)))); + } + + @Test + public void coshShouldPass() { + assertEquals(3.7621956910836314d, apply(ScalarOperation.COSH, literal(doubleValue(2d)))); + } + + @Test + public void expShouldPass() { + assertEquals(7.38905609893065d, apply(ScalarOperation.EXP, literal(doubleValue(2d)))); + } + + @Test + public void lnShouldPass() { + assertEquals(0.6931471805599453d, apply(ScalarOperation.LN, literal(doubleValue(2d)))); + } + + @Test + public void logShouldPass() { + assertEquals(0.6931471805599453d, apply(ScalarOperation.LOG, literal(doubleValue(2d)))); + } + + @Test + public void log2ShouldPass() { + assertEquals(1.0d, apply(ScalarOperation.LOG2, literal(doubleValue(2d)))); + } + + @Test + public void log10ShouldPass() { + assertEquals(0.3010299956639812, apply(ScalarOperation.LOG10, literal(doubleValue(2d)))); + } + + @Test + public void absWithStringShouldThrowException() { + exceptionRule.expect(RuntimeException.class); + exceptionRule.expectMessage("unexpected operation type: ABS(STRING_VALUE)"); + + apply(ScalarOperation.ABS, literal(stringValue("stringValue"))); + } + + @Test + public void atan2WithStringShouldThrowException() { + exceptionRule.expect(RuntimeException.class); + exceptionRule.expectMessage("unexpected operation type: ATAN2(DOUBLE_VALUE,STRING_VALUE)"); + + apply(ScalarOperation.ATAN2, literal(doubleValue(2d)), literal(stringValue("stringValue"))); + } } diff --git a/legacy/src/test/java/org/opensearch/sql/legacy/unittest/parser/SqlParserTest.java b/legacy/src/test/java/org/opensearch/sql/legacy/unittest/parser/SqlParserTest.java index 354c6ff8a1..38eefaaec1 100644 --- a/legacy/src/test/java/org/opensearch/sql/legacy/unittest/parser/SqlParserTest.java +++ b/legacy/src/test/java/org/opensearch/sql/legacy/unittest/parser/SqlParserTest.java @@ -3,7 +3,6 @@ * SPDX-License-Identifier: Apache-2.0 */ - package org.opensearch.sql.legacy.unittest.parser; import static org.hamcrest.Matchers.equalTo; @@ -56,1366 +55,1460 @@ public class SqlParserTest { - private SqlParser parser; - - @Before - public void init() { - parser = new SqlParser(); - } - - @Rule - public ExpectedException thrown= ExpectedException.none(); - - @Test - public void whereConditionLeftFunctionRightPropertyGreatTest() throws Exception { - - String query = "SELECT " + - " * from " + - TEST_INDEX_ACCOUNT + "/account " + - " where floor(split(address,' ')[0]+0) > b limit 1000 "; - - Select select = parser.parseSelect((SQLQueryExpr) queryToExpr(query)); - Where where = select.getWhere(); - Assert.assertTrue((where.getWheres().size() == 1)); - Assert.assertTrue(((Condition) (where.getWheres().get(0))).getValue() instanceof ScriptFilter); - ScriptFilter scriptFilter = (ScriptFilter) (((Condition) (where.getWheres().get(0))).getValue()); - - Assert.assertTrue(scriptFilter.getScript().contains("doc['address'].value.split(' ')[0]")); - Pattern pattern = Pattern.compile("floor_\\d+ > doc\\['b'].value"); - java.util.regex.Matcher matcher = pattern.matcher(scriptFilter.getScript()); - Assert.assertTrue(matcher.find()); - } - - @Test() - public void failingQueryTest() throws SqlParseException { - thrown.expect(SqlFeatureNotImplementedException.class); - thrown.expectMessage( - "The complex aggregate expressions are not implemented yet: MAX(FlightDelayMin) - MIN(FlightDelayMin)"); - - Select select = - parser.parseSelect((SQLQueryExpr) queryToExpr( - "SELECT DestCountry, dayOfWeek, max(FlightDelayMin) - min(FlightDelayMin)" + - " FROM opensearch_dashboards_sample_data_flights\n" + - " GROUP BY DestCountry, dayOfWeek\n")); - - AggregationQueryAction queryAction = new AggregationQueryAction(mock(Client.class), select); - String elasticDsl = queryAction.explain().explain(); - } - - @Test() - public void failingQueryTest2() throws SqlParseException { - thrown.expect(SqlFeatureNotImplementedException.class); - thrown.expectMessage( - "Function calls of form 'log(MAX(...))' are not implemented yet"); - - Select select = - parser.parseSelect((SQLQueryExpr) queryToExpr( - "SELECT DestCountry, dayOfWeek, log(max(FlightDelayMin))" + - " FROM opensearch_dashboards_sample_data_flights\n" + - " GROUP BY DestCountry, dayOfWeek\n")); - - AggregationQueryAction queryAction = new AggregationQueryAction(mock(Client.class), select); - String elasticDsl = queryAction.explain().explain(); - } - - @Test() - public void failingQueryWithHavingTest() throws SqlParseException { - thrown.expect(SqlFeatureNotImplementedException.class); - thrown.expectMessage( - "The complex aggregate expressions are not implemented yet: MAX(FlightDelayMin) - MIN(FlightDelayMin)"); - - Select select = - parser.parseSelect((SQLQueryExpr) queryToExpr( - "SELECT DestCountry, dayOfWeek, max(FlightDelayMin) - min(FlightDelayMin) " + - " FROM opensearch_dashboards_sample_data_flights\n" + - " GROUP BY DestCountry, dayOfWeek\n" + - " HAVING max(FlightDelayMin) - min(FlightDelayMin)) * count(FlightDelayMin) + 14 > 100")); - - AggregationQueryAction queryAction = new AggregationQueryAction(mock(Client.class), select); - String elasticDsl = queryAction.explain().explain(); - } - - @Test() - @Ignore("Github issues: https://github.com/opendistro-for-elasticsearch/sql/issues/194, " + - "https://github.com/opendistro-for-elasticsearch/sql/issues/234") - public void failingQueryWithHavingTest2() throws SqlParseException { - Select select = - parser.parseSelect((SQLQueryExpr) queryToExpr( - "SELECT DestCountry, dayOfWeek, max(FlightDelayMin) " + - " FROM opensearch_dashboards_sample_data_flights\n" + - " GROUP BY DestCountry, dayOfWeek\n" + - " HAVING max(FlightDelayMin) - min(FlightDelayMin) > 100")); - - AggregationQueryAction queryAction = new AggregationQueryAction(mock(Client.class), select); - - String elasticDsl = queryAction.explain().explain(); - } - - @Test - public void whereConditionLeftFunctionRightFunctionEqualTest() throws Exception { - - String query = "SELECT " + - " * from " + - TEST_INDEX_ACCOUNT + "/account " + - " where floor(split(address,' ')[0]+0) = floor(split(address,' ')[0]+0) limit 1000 "; - - Select select = parser.parseSelect((SQLQueryExpr) queryToExpr(query)); - Where where = select.getWhere(); - Assert.assertTrue((where.getWheres().size() == 1)); - Assert.assertTrue(((Condition) (where.getWheres().get(0))).getValue() instanceof ScriptFilter); - ScriptFilter scriptFilter = (ScriptFilter) (((Condition) (where.getWheres().get(0))).getValue()); - Assert.assertTrue(scriptFilter.getScript().contains("doc['address'].value.split(' ')[0]")); - Pattern pattern = Pattern.compile("floor_\\d+ == floor_\\d+"); - java.util.regex.Matcher matcher = pattern.matcher(scriptFilter.getScript()); - Assert.assertTrue(matcher.find()); - } - - @Test - public void whereConditionVariableRightVariableEqualTest() throws Exception { - - String query = "SELECT " + - " * from " + - TEST_INDEX_ACCOUNT + "/account " + - " where a = b limit 1000 "; - - Select select = parser.parseSelect((SQLQueryExpr) queryToExpr(query)); - Where where = select.getWhere(); - Assert.assertTrue((where.getWheres().size() == 1)); - Assert.assertTrue(((Condition) (where.getWheres().get(0))).getValue() instanceof ScriptFilter); - ScriptFilter scriptFilter = (ScriptFilter) (((Condition) (where.getWheres().get(0))).getValue()); - Assert.assertTrue(scriptFilter.getScript().contains("doc['a'].value == doc['b'].value")); - } - - @Test - public void joinParseCheckSelectedFieldsSplit() throws SqlParseException { - String query = "SELECT a.firstname ,a.lastname , a.gender , d.holdersName ,d.name FROM " + - TestsConstants.TEST_INDEX_ACCOUNT + - "/account a " + - "LEFT JOIN " + - TEST_INDEX_DOG + - "/dog d on d.holdersName = a.firstname " + - " AND d.age < a.age " + - " WHERE a.firstname = 'eliran' AND " + - " (a.age > 10 OR a.balance > 2000)" + - " AND d.age > 1"; - - JoinSelect joinSelect = parser.parseJoinSelect((SQLQueryExpr) queryToExpr(query)); - - List t1Fields = joinSelect.getFirstTable().getSelectedFields(); - Assert.assertEquals(t1Fields.size(), 3); - Assert.assertTrue(fieldExist(t1Fields, "firstname")); - Assert.assertTrue(fieldExist(t1Fields, "lastname")); - Assert.assertTrue(fieldExist(t1Fields, "gender")); - - List t2Fields = joinSelect.getSecondTable().getSelectedFields(); - Assert.assertEquals(t2Fields.size(), 2); - Assert.assertTrue(fieldExist(t2Fields, "holdersName")); - Assert.assertTrue(fieldExist(t2Fields, "name")); - } - - @Test - public void joinParseCheckConnectedFields() throws SqlParseException { - String query = "SELECT a.firstname ,a.lastname , a.gender , d.holdersName ,d.name FROM " + - TestsConstants.TEST_INDEX_ACCOUNT + - "/account a " + - "LEFT JOIN " + - TEST_INDEX_DOG + - "/dog d on d.holdersName = a.firstname " + - " AND d.age < a.age " + - " WHERE a.firstname = 'eliran' AND " + - " (a.age > 10 OR a.balance > 2000)" + - " AND d.age > 1"; - - JoinSelect joinSelect = parser.parseJoinSelect((SQLQueryExpr) queryToExpr(query)); - - List t1Fields = joinSelect.getFirstTable().getConnectedFields(); - Assert.assertEquals(t1Fields.size(), 2); - Assert.assertTrue(fieldExist(t1Fields, "firstname")); - Assert.assertTrue(fieldExist(t1Fields, "age")); - - List t2Fields = joinSelect.getSecondTable().getConnectedFields(); - Assert.assertEquals(t2Fields.size(), 2); - Assert.assertTrue(fieldExist(t2Fields, "holdersName")); - Assert.assertTrue(fieldExist(t2Fields, "age")); - } - - private boolean fieldExist(List fields, String fieldName) { - for (Field field : fields) - if (field.getName().equals(fieldName)) return true; - - return false; - } - - - @Test - public void joinParseFromsAreSplitedCorrectly() throws SqlParseException { - String query = "SELECT a.firstname ,a.lastname , a.gender , d.holdersName ,d.name FROM " + - TestsConstants.TEST_INDEX_ACCOUNT + - " a " + - "LEFT JOIN " + - TEST_INDEX_DOG + - " d on d.holdersName = a.firstname" + - " WHERE a.firstname = 'eliran' AND " + - " (a.age > 10 OR a.balance > 2000)" + - " AND d.age > 1"; - - JoinSelect joinSelect = parser.parseJoinSelect((SQLQueryExpr) queryToExpr(query)); - List t1From = joinSelect.getFirstTable().getFrom(); - - Assert.assertNotNull(t1From); - Assert.assertEquals(1, t1From.size()); - Assert.assertTrue(checkFrom(t1From.get(0), TestsConstants.TEST_INDEX_ACCOUNT, "a")); - - List t2From = joinSelect.getSecondTable().getFrom(); - Assert.assertNotNull(t2From); - Assert.assertEquals(1, t2From.size()); - Assert.assertTrue(checkFrom(t2From.get(0), TEST_INDEX_DOG, "d")); - } - - private boolean checkFrom(From from, String index, String alias) { - return from.getAlias().equals(alias) && from.getIndex().equals(index); - } - - @Test - public void joinParseConditionsTestOneCondition() throws SqlParseException { - String query = "SELECT a.*, a.firstname ,a.lastname , a.gender , d.holdersName ,d.name FROM " + - TestsConstants.TEST_INDEX_ACCOUNT + - "/account a " + - "LEFT JOIN " + - TEST_INDEX_DOG + - "/dog d on d.holdersName = a.firstname" + - " WHERE a.firstname = 'eliran' AND " + - " (a.age > 10 OR a.balance > 2000)" + - " AND d.age > 1"; - - JoinSelect joinSelect = parser.parseJoinSelect((SQLQueryExpr) queryToExpr(query)); - List conditions = joinSelect.getConnectedConditions(); - Assert.assertNotNull(conditions); - Assert.assertEquals(1, conditions.size()); - Assert.assertTrue("condition not exist: d.holdersName = a.firstname", - conditionExist(conditions, "d.holdersName", "a.firstname", Condition.OPERATOR.EQ)); - } - - @Test - public void joinParseConditionsTestTwoConditions() throws SqlParseException { - String query = "SELECT a.*, a.firstname ,a.lastname , a.gender , d.holdersName ,d.name FROM " + - TestsConstants.TEST_INDEX_ACCOUNT + - "/account a " + - "LEFT JOIN " + - TEST_INDEX_DOG + - "/dog d on d.holdersName = a.firstname " + - " AND d.age < a.age " + - " WHERE a.firstname = 'eliran' AND " + - " (a.age > 10 OR a.balance > 2000)" + - " AND d.age > 1"; - - JoinSelect joinSelect = parser.parseJoinSelect((SQLQueryExpr) queryToExpr(query)); - List conditions = joinSelect.getConnectedConditions(); - Assert.assertNotNull(conditions); - Assert.assertEquals(2, conditions.size()); - Assert.assertTrue("condition not exist: d.holdersName = a.firstname", - conditionExist(conditions, "d.holdersName", "a.firstname", Condition.OPERATOR.EQ)); - Assert.assertTrue("condition not exist: d.age < a.age", - conditionExist(conditions, "d.age", "a.age", Condition.OPERATOR.LT)); - } - - - @Test - public void joinSplitWhereCorrectly() throws SqlParseException { - String query = "SELECT a.*, a.firstname ,a.lastname , a.gender , d.holdersName ,d.name FROM " + - TestsConstants.TEST_INDEX_ACCOUNT + - "/account a " + - "LEFT JOIN " + - TEST_INDEX_DOG + - "/dog d on d.holdersName = a.firstname" + - " WHERE a.firstname = 'eliran' AND " + - " (a.age > 10 OR a.balance > 2000)" + - " AND d.age > 1"; - - JoinSelect joinSelect = parser.parseJoinSelect((SQLQueryExpr) queryToExpr(query)); - String s1Where = joinSelect.getFirstTable().getWhere().toString(); - Assert.assertEquals("AND ( AND firstname EQ eliran, AND ( OR age GT 10, OR balance GT 2000 ) ) ", s1Where); - String s2Where = joinSelect.getSecondTable().getWhere().toString(); - Assert.assertEquals("AND age GT 1", s2Where); - } - - @Test - public void joinConditionWithComplexObjectComparisonRightSide() throws SqlParseException { - String query = String.format(Locale.ROOT, "select c.name.firstname,c.parents.father , h.name,h.words " + - "from %s/gotCharacters c " + - "JOIN %s/gotCharacters h " + - "on h.name = c.name.lastname " + - "where c.name.firstname='Daenerys'", TEST_INDEX_GAME_OF_THRONES, TEST_INDEX_GAME_OF_THRONES); - JoinSelect joinSelect = parser.parseJoinSelect((SQLQueryExpr) queryToExpr(query)); - List conditions = joinSelect.getConnectedConditions(); - Assert.assertNotNull(conditions); - Assert.assertEquals(1, conditions.size()); - Assert.assertTrue("condition not exist: h.name = c.name.lastname", - conditionExist(conditions, "h.name", "c.name.lastname", Condition.OPERATOR.EQ)); - } - - @Test - public void joinConditionWithComplexObjectComparisonLeftSide() throws SqlParseException { - String query = String.format(Locale.ROOT, - "select c.name.firstname,c.parents.father , h.name,h.words from %s/gotCharacters c " + - "JOIN %s/gotCharacters h " + - "on c.name.lastname = h.name " + - "where c.name.firstname='Daenerys'", TEST_INDEX_GAME_OF_THRONES, TEST_INDEX_GAME_OF_THRONES); - JoinSelect joinSelect = parser.parseJoinSelect((SQLQueryExpr) queryToExpr(query)); - List conditions = joinSelect.getConnectedConditions(); - Assert.assertNotNull(conditions); - Assert.assertEquals(1, conditions.size()); - Assert.assertTrue("condition not exist: c.name.lastname = h.name", - conditionExist(conditions, "c.name.lastname", "h.name", Condition.OPERATOR.EQ)); - } - - - @Test - public void limitHintsOnJoin() throws SqlParseException { - String query = String.format(Locale.ROOT,"select /*! JOIN_TABLES_LIMIT(1000,null) */ " + - "c.name.firstname,c.parents.father , h.name,h.words from %s/gotCharacters c " + - "use KEY (termsFilter) " + - "JOIN %s/gotCharacters h " + - "on c.name.lastname = h.name " + - "where c.name.firstname='Daenerys'", TEST_INDEX_GAME_OF_THRONES, TEST_INDEX_GAME_OF_THRONES); - JoinSelect joinSelect = parser.parseJoinSelect((SQLQueryExpr) queryToExpr(query)); - List hints = joinSelect.getHints(); - Assert.assertNotNull(hints); - Assert.assertEquals("hints size was not 1", 1, hints.size()); - Hint hint = hints.get(0); - Assert.assertEquals(HintType.JOIN_LIMIT, hint.getType()); - Object[] params = hint.getParams(); - Assert.assertNotNull(params); - Assert.assertEquals("params size was not 2", 2, params.length); - Assert.assertEquals(1000, params[0]); - Assert.assertNull(params[1]); - } - - @Test - public void hashTermsFilterHint() throws SqlParseException { - String query = String.format(Locale.ROOT, "select /*! HASH_WITH_TERMS_FILTER*/ " + - "c.name.firstname,c.parents.father , h.name,h.words from %s/gotCharacters c " + - "use KEY (termsFilter) " + - "JOIN %s/gotCharacters h " + - "on c.name.lastname = h.name " + - "where c.name.firstname='Daenerys'", TEST_INDEX_GAME_OF_THRONES, TEST_INDEX_GAME_OF_THRONES); - JoinSelect joinSelect = parser.parseJoinSelect((SQLQueryExpr) queryToExpr(query)); - List hints = joinSelect.getHints(); - Assert.assertNotNull(hints); - Assert.assertEquals("hints size was not 1", 1, hints.size()); - Hint hint = hints.get(0); - Assert.assertEquals(HintType.HASH_WITH_TERMS_FILTER, hint.getType()); - } - - @Test - public void multipleHints() throws SqlParseException { - String query = String.format(Locale.ROOT, "select /*! HASH_WITH_TERMS_FILTER*/ " + - "/*! JOIN_TABLES_LIMIT(1000,null) */ " + - " /*! JOIN_TABLES_LIMIT(100,200) */ " + - "c.name.firstname,c.parents.father , h.name,h.words from %s/gotCharacters c " + - "use KEY (termsFilter) " + - "JOIN %s/gotCharacters h " + - "on c.name.lastname = h.name " + - "where c.name.firstname='Daenerys'", TEST_INDEX_GAME_OF_THRONES, TEST_INDEX_GAME_OF_THRONES); - - JoinSelect joinSelect = parser.parseJoinSelect((SQLQueryExpr) queryToExpr(query)); - List hints = joinSelect.getHints(); - - Assert.assertNotNull(hints); - Assert.assertEquals("hints size was not 3", 3, hints.size()); - Hint firstHint = hints.get(0); - Assert.assertEquals(HintType.HASH_WITH_TERMS_FILTER, firstHint.getType()); - Hint secondHint = hints.get(1); - Assert.assertEquals(HintType.JOIN_LIMIT, secondHint.getType()); - Assert.assertEquals(1000, secondHint.getParams()[0]); - Assert.assertNull(secondHint.getParams()[1]); - Hint thirdHint = hints.get(2); - Assert.assertEquals(100, thirdHint.getParams()[0]); - Assert.assertEquals(200, thirdHint.getParams()[1]); - Assert.assertEquals(HintType.JOIN_LIMIT, thirdHint.getType()); - } - - @Test - public void searchWithOdbcTimeFormatParse() throws SqlParseException { - String query = String.format(Locale.ROOT, "SELECT insert_time FROM %s/odbc " + - "WHERE insert_time < {ts '2015-03-15 00:00:00.000'}", TEST_INDEX_ODBC); - SQLExpr sqlExpr = queryToExpr(query); - Select select = parser.parseSelect((SQLQueryExpr) sqlExpr); - LinkedList wheres = select.getWhere().getWheres(); - Assert.assertEquals(1, wheres.size()); - Condition condition = (Condition) wheres.get(0); - Assert.assertEquals("{ts '2015-03-15 00:00:00.000'}", condition.getValue().toString()); - - } - - @Test - public void indexWithSpacesWithinBrackets() throws SqlParseException { - String query = "SELECT insert_time FROM [Test Index] WHERE age > 3"; - SQLExpr sqlExpr = queryToExpr(query); - Select select = parser.parseSelect((SQLQueryExpr) sqlExpr); - List fromList = select.getFrom(); - Assert.assertEquals(1, fromList.size()); - From from = fromList.get(0); - Assert.assertEquals("Test Index", from.getIndex()); - } - - @Test - public void indexWithSpacesWithTypeWithinBrackets() throws SqlParseException { - String query = "SELECT insert_time FROM [Test Index] WHERE age > 3"; - SQLExpr sqlExpr = queryToExpr(query); - Select select = parser.parseSelect((SQLQueryExpr) sqlExpr); - List fromList = select.getFrom(); - Assert.assertEquals(1, fromList.size()); - From from = fromList.get(0); - Assert.assertEquals("Test Index", from.getIndex()); - } - - - @Test - public void fieldWithSpacesWithinBrackets() throws SqlParseException { - String query = "SELECT insert_time FROM name/type1 WHERE [first name] = 'Name'"; - SQLExpr sqlExpr = queryToExpr(query); - Select select = parser.parseSelect((SQLQueryExpr) sqlExpr); - List where = select.getWhere().getWheres(); - Assert.assertEquals(1, where.size()); - Condition condition = (Condition) where.get(0); - Assert.assertEquals("first name", condition.getName()); - Assert.assertEquals("Name", condition.getValue()); - } - - @Test - public void twoIndices() throws SqlParseException { - String query = "SELECT insert_time FROM index1, index2 WHERE age > 3"; - SQLExpr sqlExpr = queryToExpr(query); - Select select = parser.parseSelect((SQLQueryExpr) sqlExpr); - List fromList = select.getFrom(); - Assert.assertEquals(2, fromList.size()); - From from1 = fromList.get(0); - From from2 = fromList.get(1); - boolean preservedOrder = from1.getIndex().equals("index1") - && from2.getIndex().equals("index2"); - boolean notPreservedOrder = from1.getIndex().equals("index2") - && from2.getIndex().equals("index1"); - Assert.assertTrue(preservedOrder || notPreservedOrder); - } - - @Test - public void fieldWithATcharAtWhere() throws SqlParseException { - String query = "SELECT * FROM index/type where @field = 6 "; - SQLExpr sqlExpr = queryToExpr(query); - Select select = parser.parseSelect((SQLQueryExpr) sqlExpr); - LinkedList wheres = select.getWhere().getWheres(); - Assert.assertEquals(1, wheres.size()); - Condition condition = (Condition) wheres.get(0); - Assert.assertEquals("@field", condition.getName()); - } - - @Test - public void fieldWithATcharAtSelect() throws SqlParseException { - String query = "SELECT @field FROM index/type where field2 = 6 "; - SQLExpr sqlExpr = queryToExpr(query); - Select select = parser.parseSelect((SQLQueryExpr) sqlExpr); - List fields = select.getFields(); - Assert.assertEquals(1, fields.size()); - Field field = fields.get(0); - Assert.assertEquals(field.getName(), "@field"); - } - - @Test - public void fieldWithATcharAtSelectOnAgg() throws SqlParseException { - String query = "SELECT max(@field) FROM index/type where field2 = 6 "; - SQLExpr sqlExpr = queryToExpr(query); - Select select = parser.parseSelect((SQLQueryExpr) sqlExpr); - List fields = select.getFields(); - Assert.assertEquals(1, fields.size()); - Field field = fields.get(0); - Assert.assertEquals("MAX(@field)", field.toString()); - } - - @Test - public void fieldWithColonCharAtSelect() throws SqlParseException { - String query = "SELECT a:b FROM index/type where field2 = 6 "; - SQLExpr sqlExpr = queryToExpr(query); - Select select = parser.parseSelect((SQLQueryExpr) sqlExpr); - List fields = select.getFields(); - Assert.assertEquals(1, fields.size()); - Field field = fields.get(0); - Assert.assertEquals(field.getName(), "a:b"); - } - - @Test - public void fieldWithColonCharAtWhere() throws SqlParseException { - String query = "SELECT * FROM index/type where a:b = 6 "; - SQLExpr sqlExpr = queryToExpr(query); - Select select = parser.parseSelect((SQLQueryExpr) sqlExpr); - LinkedList wheres = select.getWhere().getWheres(); - Assert.assertEquals(1, wheres.size()); - Condition condition = (Condition) wheres.get(0); - Assert.assertEquals("a:b", condition.getName()); - } - - @Test - public void fieldIsNull() throws SqlParseException { - String query = "SELECT * FROM index/type where a IS NOT NULL"; - SQLExpr sqlExpr = queryToExpr(query); - Select select = parser.parseSelect((SQLQueryExpr) sqlExpr); - LinkedList wheres = select.getWhere().getWheres(); - Assert.assertEquals(1, wheres.size()); - Condition condition = (Condition) wheres.get(0); - Assert.assertEquals("a", condition.getName()); - Assert.assertNull(condition.getValue()); - } - - @Test - public void innerQueryTest() throws SqlParseException { - String query = String.format(Locale.ROOT, "select * from %s/dog where holdersName " + - "IN (select firstname from %s/account where firstname = 'eliran')", - TEST_INDEX_DOG, TestsConstants.TEST_INDEX_ACCOUNT); - SQLExpr sqlExpr = queryToExpr(query); - Select select = parser.parseSelect((SQLQueryExpr) sqlExpr); - Assert.assertTrue(select.containsSubQueries()); - Assert.assertEquals(1, select.getSubQueries().size()); - } - - @Test - public void inTermsSubQueryTest() throws SqlParseException { - String query = String.format(Locale.ROOT, "select * from %s/dog where " + - "holdersName = IN_TERMS (select firstname from %s/account where firstname = 'eliran')", - TEST_INDEX_DOG, TestsConstants.TEST_INDEX_ACCOUNT); - SQLExpr sqlExpr = queryToExpr(query); - Select select = parser.parseSelect((SQLQueryExpr) sqlExpr); - Assert.assertTrue(select.containsSubQueries()); - Assert.assertEquals(1, select.getSubQueries().size()); - } - - - @Test - public void innerQueryTestTwoQueries() throws SqlParseException { - String query = String.format(Locale.ROOT, "select * from %s/dog where holdersName IN " + - "(select firstname from %s/account where firstname = 'eliran') and " + - "age IN (select name.ofHisName from %s/gotCharacters) ", - TEST_INDEX_DOG, TestsConstants.TEST_INDEX_ACCOUNT, TEST_INDEX_GAME_OF_THRONES); - SQLExpr sqlExpr = queryToExpr(query); - Select select = parser.parseSelect((SQLQueryExpr) sqlExpr); - Assert.assertTrue(select.containsSubQueries()); - Assert.assertEquals(2, select.getSubQueries().size()); - } - - @Test - public void indexWithDotsAndHyphen() throws SqlParseException { - String query = "select * from data-2015.08.22"; - SQLExpr sqlExpr = queryToExpr(query); - Select select = parser.parseSelect((SQLQueryExpr) sqlExpr); - Assert.assertEquals(1, select.getFrom().size()); - Assert.assertEquals("data-2015.08.22", select.getFrom().get(0).getIndex()); - } - - @Test - public void indexNameWithDotAtTheStart() throws SqlParseException { - String query = "SELECT * FROM .opensearch_dashboards"; - SQLExpr sqlExpr = queryToExpr(query); - Select select = parser.parseSelect((SQLQueryExpr) sqlExpr); - Assert.assertEquals(".opensearch_dashboards", select.getFrom().get(0).getIndex()); - } - - @Test - public void indexWithSemiColons() throws SqlParseException { - String query = "select * from some;index"; - SQLExpr sqlExpr = queryToExpr(query); - Select select = parser.parseSelect((SQLQueryExpr) sqlExpr); - Assert.assertEquals(1, select.getFrom().size()); - Assert.assertEquals("some;index", select.getFrom().get(0).getIndex()); - } - - @Test - public void scriptFiledPlusLiteralTest() throws SqlParseException { - String query = "SELECT field1 + 3 FROM index/type"; - SQLExpr sqlExpr = queryToExpr(query); - Select select = parser.parseSelect((SQLQueryExpr) sqlExpr); - List fields = select.getFields(); - Assert.assertEquals(1, fields.size()); - Field field = fields.get(0); - Assert.assertTrue(field instanceof MethodField); - MethodField scriptMethod = (MethodField) field; - Assert.assertEquals("script", scriptMethod.getName().toLowerCase()); - Assert.assertEquals(2, scriptMethod.getParams().size()); - Assert.assertTrue(scriptMethod.getParams().get(1).toString().contains("doc['field1'].value + 3")); - } - - @Test - public void scriptFieldPlusFieldTest() throws SqlParseException { - String query = "SELECT field1 + field2 FROM index/type"; - SQLExpr sqlExpr = queryToExpr(query); - Select select = parser.parseSelect((SQLQueryExpr) sqlExpr); - List fields = select.getFields(); - Assert.assertEquals(1, fields.size()); - Field field = fields.get(0); - Assert.assertTrue(field instanceof MethodField); - MethodField scriptMethod = (MethodField) field; - Assert.assertEquals("script", scriptMethod.getName().toLowerCase()); - Assert.assertEquals(2, scriptMethod.getParams().size()); - Assert.assertTrue(scriptMethod.getParams().get(1).toString() - .contains("doc['field1'].value + doc['field2'].value")); - } - - - @Test - public void scriptLiteralPlusLiteralTest() throws SqlParseException { - String query = "SELECT 1 + 2 FROM index/type"; - SQLExpr sqlExpr = queryToExpr(query); - Select select = parser.parseSelect((SQLQueryExpr) sqlExpr); - List fields = select.getFields(); - Assert.assertEquals(1, fields.size()); - Field field = fields.get(0); - Assert.assertTrue(field instanceof MethodField); - MethodField scriptMethod = (MethodField) field; - Assert.assertEquals("script", scriptMethod.getName().toLowerCase()); - Assert.assertEquals(2, scriptMethod.getParams().size()); - Assert.assertTrue(scriptMethod.getParams().get(1).toString().contains("1 + 2")); - } - - - @Test - public void explicitScriptOnAggregation() throws SqlParseException { - String query = "SELECT avg( script('add','doc[\\'field1\\'].value + doc[\\'field2\\'].value') )" + - " FROM index/type"; - SQLExpr sqlExpr = queryToExpr(query); - Select select = parser.parseSelect((SQLQueryExpr) sqlExpr); - List fields = select.getFields(); - Assert.assertEquals(1, fields.size()); - Field field = fields.get(0); - Assert.assertTrue(field instanceof MethodField); - MethodField avgMethodField = (MethodField) field; - Assert.assertEquals("avg", avgMethodField.getName().toLowerCase()); - Assert.assertEquals(1, avgMethodField.getParams().size()); - MethodField scriptMethod = (MethodField) avgMethodField.getParams().get(0).value; - Assert.assertEquals("script", scriptMethod.getName().toLowerCase()); - Assert.assertEquals(2, scriptMethod.getParams().size()); - Assert.assertEquals("doc['field1'].value + doc['field2'].value", - scriptMethod.getParams().get(1).toString()); - } - - @Test - public void implicitScriptOnAggregation() throws SqlParseException { - String query = "SELECT avg(field(field1) + field(field2)) FROM index/type"; - SQLExpr sqlExpr = queryToExpr(query); - Select select = parser.parseSelect((SQLQueryExpr) sqlExpr); - List fields = select.getFields(); - Assert.assertEquals(1, fields.size()); - Field field = fields.get(0); - Assert.assertTrue(field instanceof MethodField); - MethodField avgMethodField = (MethodField) field; - Assert.assertEquals("avg", avgMethodField.getName().toLowerCase()); - Assert.assertEquals(1, avgMethodField.getParams().size()); - Assert.assertTrue(avgMethodField.getParams().get(0).value.toString().contains("doc['field1'].value")); - Assert.assertTrue(avgMethodField.getParams().get(0).value.toString().contains("doc['field2'].value")); - - } - - @Test - public void nestedFieldOnWhereNoPathSimpleField() throws SqlParseException { - String query = "select * from myIndex where nested(message.name) = 'hey'"; - SQLExpr sqlExpr = queryToExpr(query); - Select select = parser.parseSelect((SQLQueryExpr) sqlExpr); - Where where = select.getWhere().getWheres().get(0); - Assert.assertTrue("where should be condition", where instanceof Condition); - Condition condition = (Condition) where; - Assert.assertTrue("condition should be nested", condition.isNested()); - Assert.assertEquals("message", condition.getNestedPath()); - Assert.assertEquals("message.name", condition.getName()); - } - - - @Test - public void nestedFieldOnWhereNoPathComplexField() throws SqlParseException { - String query = "select * from myIndex where nested(message.moreNested.name) = 'hey'"; - SQLExpr sqlExpr = queryToExpr(query); - Select select = parser.parseSelect((SQLQueryExpr) sqlExpr); - Where where = select.getWhere().getWheres().get(0); - Assert.assertTrue("where should be condition", where instanceof Condition); - Condition condition = (Condition) where; - Assert.assertTrue("condition should be nested", condition.isNested()); - Assert.assertEquals("message.moreNested", condition.getNestedPath()); - Assert.assertEquals("message.moreNested.name", condition.getName()); - } - - - @Test - public void aggFieldWithAliasTableAliasShouldBeRemoved() throws SqlParseException { - String query = "select count(t.*) as counts,sum(t.size) from xxx/locs as t group by t.kk"; - SQLExpr sqlExpr = queryToExpr(query); - Select select = parser.parseSelect((SQLQueryExpr) sqlExpr); - List fields = select.getFields(); - Assert.assertThat(fields.size(), equalTo(2)); - Assert.assertEquals("COUNT(*)", fields.get(0).toString()); - Assert.assertEquals("SUM(size)", fields.get(1).toString()); - List> groups = select.getGroupBys(); - Assert.assertThat(groups.size(), equalTo(1)); - Assert.assertThat(groups.get(0).size(), equalTo(1)); - Assert.assertEquals("kk", groups.get(0).get(0).getName()); - } - - @Test - public void nestedFieldOnWhereGivenPath() throws SqlParseException { - String query = "select * from myIndex where nested(message.name,message) = 'hey'"; - SQLExpr sqlExpr = queryToExpr(query); - Select select = parser.parseSelect((SQLQueryExpr) sqlExpr); - Where where = select.getWhere().getWheres().get(0); - Assert.assertTrue("where should be condition", where instanceof Condition); - Condition condition = (Condition) where; - Assert.assertTrue("condition should be nested", condition.isNested()); - Assert.assertEquals("message", condition.getNestedPath()); - Assert.assertEquals("message.name", condition.getName()); - } - - @Test - public void nestedFieldOnGroupByNoPath() throws SqlParseException { - String query = "select * from myIndex group by nested(message.name)"; - SQLExpr sqlExpr = queryToExpr(query); - Select select = parser.parseSelect((SQLQueryExpr) sqlExpr); - Field field = select.getGroupBys().get(0).get(0); - Assert.assertTrue("condition should be nested", field.isNested()); - Assert.assertEquals("message", field.getNestedPath()); - Assert.assertEquals("message.name", field.getName()); - } - - @Test - public void nestedFieldOnGroupByWithPath() throws SqlParseException { - String query = "select * from myIndex group by nested(message.name,message)"; - SQLExpr sqlExpr = queryToExpr(query); - Select select = parser.parseSelect((SQLQueryExpr) sqlExpr); - Field field = select.getGroupBys().get(0).get(0); - Assert.assertTrue("condition should be nested", field.isNested()); - Assert.assertEquals("message", field.getNestedPath()); - Assert.assertEquals("message.name", field.getName()); - } - - @Test - public void filterAggTestNoAlias() throws SqlParseException { - String query = "select * from myIndex group by a , filter( a > 3 AND b='3' )"; - SQLExpr sqlExpr = queryToExpr(query); - Select select = parser.parseSelect((SQLQueryExpr) sqlExpr); - List> groupBys = select.getGroupBys(); - Assert.assertEquals(1, groupBys.size()); - Field aAgg = groupBys.get(0).get(0); - Assert.assertEquals("a", aAgg.getName()); - Field field = groupBys.get(0).get(1); - Assert.assertTrue("filter field should be method field", field instanceof MethodField); - MethodField filterAgg = (MethodField) field; - Assert.assertEquals("filter", filterAgg.getName()); - Map params = filterAgg.getParamsAsMap(); - Assert.assertEquals(2, params.size()); - Object alias = params.get("alias"); - Assert.assertEquals("filter(a > 3 AND b = '3')@FILTER", alias); - - Assert.assertTrue(params.get("where") instanceof Where); - Where where = (Where) params.get("where"); - Assert.assertEquals(2, where.getWheres().size()); - } - - @Test - public void filterAggTestWithAlias() throws SqlParseException { - String query = "select * from myIndex group by a , filter(myFilter, a > 3 AND b='3' )"; - SQLExpr sqlExpr = queryToExpr(query); - Select select = parser.parseSelect((SQLQueryExpr) sqlExpr); - List> groupBys = select.getGroupBys(); - Assert.assertEquals(1, groupBys.size()); - Field aAgg = groupBys.get(0).get(0); - Assert.assertEquals("a", aAgg.getName()); - Field field = groupBys.get(0).get(1); - Assert.assertTrue("filter field should be method field", field instanceof MethodField); - MethodField filterAgg = (MethodField) field; - Assert.assertEquals("filter", filterAgg.getName()); - Map params = filterAgg.getParamsAsMap(); - Assert.assertEquals(2, params.size()); - Object alias = params.get("alias"); - Assert.assertEquals("myFilter@FILTER", alias); - - Assert.assertTrue(params.get("where") instanceof Where); - Where where = (Where) params.get("where"); - Assert.assertEquals(2, where.getWheres().size()); - } - - - @Test - public void filterAggTestWithAliasAsString() throws SqlParseException { - String query = "select * from myIndex group by a , filter('my filter', a > 3 AND b='3' )"; - SQLExpr sqlExpr = queryToExpr(query); - Select select = parser.parseSelect((SQLQueryExpr) sqlExpr); - List> groupBys = select.getGroupBys(); - Assert.assertEquals(1, groupBys.size()); - Field aAgg = groupBys.get(0).get(0); - Assert.assertEquals("a", aAgg.getName()); - Field field = groupBys.get(0).get(1); - Assert.assertTrue("filter field should be method field", field instanceof MethodField); - MethodField filterAgg = (MethodField) field; - Assert.assertEquals("filter", filterAgg.getName()); - Map params = filterAgg.getParamsAsMap(); - Assert.assertEquals(2, params.size()); - Object alias = params.get("alias"); - Assert.assertEquals("my filter@FILTER", alias); - - Assert.assertTrue(params.get("where") instanceof Where); - Where where = (Where) params.get("where"); - Assert.assertEquals(2, where.getWheres().size()); - } - - @Test - public void doubleOrderByTest() throws SqlParseException { - String query = "select * from indexName order by a asc, b desc"; - SQLExpr sqlExpr = queryToExpr(query); - Select select = parser.parseSelect((SQLQueryExpr) sqlExpr); - List orderBys = select.getOrderBys(); - Assert.assertEquals(2, orderBys.size()); - Assert.assertEquals("a", orderBys.get(0).getName()); - Assert.assertEquals("ASC", orderBys.get(0).getType()); - - Assert.assertEquals("b", orderBys.get(1).getName()); - Assert.assertEquals("DESC", orderBys.get(1).getType()); - } - - @Test - public void parseJoinWithOneTableOrderByAttachToCorrectTable() throws SqlParseException { - String query = String.format(Locale.ROOT, "select c.name.firstname , d.words from %s/gotCharacters c " + - "JOIN %s/gotCharacters d on d.name = c.house " + - "order by c.name.firstname" - , TEST_INDEX_GAME_OF_THRONES, TEST_INDEX_GAME_OF_THRONES); - - JoinSelect joinSelect = parser.parseJoinSelect((SQLQueryExpr) queryToExpr(query)); - Assert.assertTrue("first table should be ordered", joinSelect.getFirstTable().isOrderdSelect()); - Assert.assertFalse("second table should not be ordered", joinSelect.getSecondTable().isOrderdSelect()); - - } - - @Test - public void parseJoinWithOneTableOrderByRemoveAlias() throws SqlParseException { - String query = String.format(Locale.ROOT, "select c.name.firstname , d.words from %s/gotCharacters c " + - "JOIN %s/gotCharacters d on d.name = c.house " + - "order by c.name.firstname" - , TEST_INDEX_GAME_OF_THRONES, TEST_INDEX_GAME_OF_THRONES); - - JoinSelect joinSelect = parser.parseJoinSelect((SQLQueryExpr) queryToExpr(query)); - List orderBys = joinSelect.getFirstTable().getOrderBys(); - Assert.assertEquals(1, orderBys.size()); - Order order = orderBys.get(0); - Assert.assertEquals("name.firstname", order.getName()); - - } - - @Test - public void termsWithStringTest() throws SqlParseException { - String query = "select * from x where y = IN_TERMS('a','b')"; - Select select = parser.parseSelect((SQLQueryExpr) queryToExpr(query)); - Condition condition = (Condition) select.getWhere().getWheres().get(0); - Object[] values = (Object[]) condition.getValue(); - Assert.assertEquals("a", values[0]); - Assert.assertEquals("b", values[1]); - } - - @Test - public void termWithStringTest() throws SqlParseException { - String query = "select * from x where y = TERM('a')"; - Select select = parser.parseSelect((SQLQueryExpr) queryToExpr(query)); - Condition condition = (Condition) select.getWhere().getWheres().get(0); - Object[] values = (Object[]) condition.getValue(); - Assert.assertEquals("a", values[0]); - } - - @Test - public void complexNestedTest() throws SqlParseException { - String query = "select * from x where nested('y',y.b = 'a' and y.c = 'd') "; - Select select = parser.parseSelect((SQLQueryExpr) queryToExpr(query)); - Condition condition = (Condition) select.getWhere().getWheres().get(0); - Assert.assertEquals(Condition.OPERATOR.NESTED_COMPLEX, condition.getOPERATOR()); - Assert.assertEquals("y", condition.getName()); - Assert.assertTrue(condition.getValue() instanceof Where); - Where where = (Where) condition.getValue(); - Assert.assertEquals(2, where.getWheres().size()); - } - - @Test - public void scriptOnFilterNoParams() throws SqlParseException { - String query = "select * from x where script('doc[\\'field\\'].date.hourOfDay == 3') "; - Select select = parser.parseSelect((SQLQueryExpr) queryToExpr(query)); - Condition condition = (Condition) select.getWhere().getWheres().get(0); - Assert.assertEquals(Condition.OPERATOR.SCRIPT, condition.getOPERATOR()); - Assert.assertNull(condition.getName()); - Assert.assertTrue(condition.getValue() instanceof ScriptFilter); - ScriptFilter scriptFilter = (ScriptFilter) condition.getValue(); - Assert.assertEquals("doc['field'].date.hourOfDay == 3", scriptFilter.getScript()); - Assert.assertFalse(scriptFilter.containsParameters()); - } - - @Test - public void scriptOnFilterWithParams() throws SqlParseException { - String query = "select * from x where script('doc[\\'field\\'].date.hourOfDay == x','x'=3) "; - Select select = parser.parseSelect((SQLQueryExpr) queryToExpr(query)); - Condition condition = (Condition) select.getWhere().getWheres().get(0); - Assert.assertEquals(Condition.OPERATOR.SCRIPT, condition.getOPERATOR()); - Assert.assertNull(condition.getName()); - Assert.assertTrue(condition.getValue() instanceof ScriptFilter); - ScriptFilter scriptFilter = (ScriptFilter) condition.getValue(); - Assert.assertEquals("doc['field'].date.hourOfDay == x", scriptFilter.getScript()); - Assert.assertTrue(scriptFilter.containsParameters()); - Map args = scriptFilter.getArgs(); - Assert.assertEquals(1, args.size()); - Assert.assertTrue(args.containsKey("x")); - Assert.assertEquals(3, args.get("x")); - - } - - @Test - public void fieldsAsNumbersOnWhere() throws SqlParseException { - String query = "select * from x where ['3'] > 2"; - Select select = parser.parseSelect((SQLQueryExpr) queryToExpr(query)); - LinkedList wheres = select.getWhere().getWheres(); - Assert.assertEquals(1, wheres.size()); - Where where = wheres.get(0); - Assert.assertEquals(Condition.class, where.getClass()); - Condition condition = (Condition) where; - Assert.assertEquals("3", condition.getName()); - } - - @Test - public void likeTestWithEscaped() throws SqlParseException { - String query = "select * from x where name like '&UNDERSCOREhey_%&PERCENT'"; - Select select = parser.parseSelect((SQLQueryExpr) queryToExpr(query)); - BoolQueryBuilder explan = QueryMaker.explain(select.getWhere()); - String filterAsString = explan.toString(); - Assert.assertTrue(filterAsString.contains("_hey?*%")); - } - - - @Test - public void complexNestedAndOtherQuery() throws SqlParseException { - String query = "select * from x where nested('path',path.x=3) and y=3"; - Select select = parser.parseSelect((SQLQueryExpr) queryToExpr(query)); - LinkedList wheres = select.getWhere().getWheres(); - Assert.assertEquals(2, wheres.size()); - Assert.assertEquals("AND path NESTED_COMPLEX AND ( AND path.x EQ 3 ) ", wheres.get(0).toString()); - Assert.assertEquals("AND y EQ 3", wheres.get(1).toString()); - } - - - @Test - public void numberEqualConditionWithoutProperty() throws SqlParseException { - SQLExpr sqlExpr = queryToExpr("select * from xxx/locs where 1 = 1"); - Select select = parser.parseSelect((SQLQueryExpr) sqlExpr); - List wheres = select.getWhere().getWheres(); - Assert.assertThat(wheres.size(), equalTo(1)); - Condition condition = (Condition) wheres.get(0); - Assert.assertTrue(condition.getValue() instanceof ScriptFilter); - ScriptFilter sf = (ScriptFilter) condition.getValue(); - Assert.assertEquals(sf.getScript(), "1 == 1"); - } - - @Test - public void numberGreatConditionWithoutProperty() throws SqlParseException { - SQLExpr sqlExpr = queryToExpr("select * from xxx/locs where 1 > 1"); - Select select = parser.parseSelect((SQLQueryExpr) sqlExpr); - List wheres = select.getWhere().getWheres(); - Assert.assertThat(wheres.size(), equalTo(1)); - Condition condition = (Condition) wheres.get(0); - Assert.assertTrue(condition.getValue() instanceof ScriptFilter); - ScriptFilter sf = (ScriptFilter) condition.getValue(); - Assert.assertEquals(sf.getScript(), "1 > 1"); - } - - @Test - public void stringEqualConditionWithoutProperty() throws SqlParseException { - SQLExpr sqlExpr = queryToExpr("select * from xxx/locs where 'a' = 'b'"); - Select select = parser.parseSelect((SQLQueryExpr) sqlExpr); - List wheres = select.getWhere().getWheres(); - Assert.assertThat(wheres.size(), equalTo(1)); - Condition condition = (Condition) wheres.get(0); - Assert.assertTrue(condition.getValue() instanceof ScriptFilter); - ScriptFilter sf = (ScriptFilter) condition.getValue(); - Assert.assertEquals(sf.getScript(), "'a' == 'b'"); - } - - @Test - public void propertyEqualCondition() throws SqlParseException { - SQLExpr sqlExpr = queryToExpr("select * from xxx/locs where a = b"); - Select select = parser.parseSelect((SQLQueryExpr) sqlExpr); - List wheres = select.getWhere().getWheres(); - Assert.assertThat(wheres.size(), equalTo(1)); - Condition condition = (Condition) wheres.get(0); - Assert.assertTrue(condition.getValue() instanceof ScriptFilter); - ScriptFilter sf = (ScriptFilter) condition.getValue(); - Assert.assertEquals(sf.getScript(), "doc['a'].value == doc['b'].value"); - } - - - @Test - public void propertyWithTableAliasEqualCondition() throws SqlParseException { - SQLExpr sqlExpr = queryToExpr("select t.* from xxx/locs where t.a = t.b"); - Select select = parser.parseSelect((SQLQueryExpr) sqlExpr); - List wheres = select.getWhere().getWheres(); - Assert.assertThat(wheres.size(), equalTo(1)); - Condition condition = (Condition) wheres.get(0); - Assert.assertTrue(condition.getValue() instanceof ScriptFilter); - ScriptFilter sf = (ScriptFilter) condition.getValue(); - Assert.assertEquals(sf.getScript(), "doc['a'].value == doc['b'].value"); - } - - @Test - public void propertyGreatCondition() throws SqlParseException { - SQLExpr sqlExpr = queryToExpr("select * from xxx/locs where a > b"); - Select select = parser.parseSelect((SQLQueryExpr) sqlExpr); - List wheres = select.getWhere().getWheres(); - Assert.assertThat(wheres.size(), equalTo(1)); - Condition condition = (Condition) wheres.get(0); - Assert.assertTrue(condition.getValue() instanceof ScriptFilter); - ScriptFilter sf = (ScriptFilter) condition.getValue(); - Assert.assertEquals(sf.getScript(), "doc['a'].value > doc['b'].value"); - } - - @Test - public void stringAndNumberEqualConditionWithoutProperty() throws SqlParseException { - SQLExpr sqlExpr = queryToExpr("select * from xxx/locs where 'a' = 1"); - Select select = parser.parseSelect((SQLQueryExpr) sqlExpr); - List wheres = select.getWhere().getWheres(); - Assert.assertThat(wheres.size(), equalTo(1)); - Condition condition = (Condition) wheres.get(0); - Assert.assertTrue(condition.getValue() instanceof ScriptFilter); - ScriptFilter sf = (ScriptFilter) condition.getValue(); - Assert.assertEquals(sf.getScript(), "'a' == 1"); - } - - - @Test - public void caseWhenTest() throws SqlParseException { - String query = "Select k,\n" + - "Case \n" + - "When floor(testBase)>=90 then 'A'\n" + - "When testBase = '80' then 'B'\n" + - "Else 'E' end as testBaseLevel\n" + - "from t"; - SQLExpr sqlExpr = queryToExpr(query); - Select select = parser.parseSelect((SQLQueryExpr) sqlExpr); - for (Field field : select.getFields()) { - if (field instanceof MethodField) { - MethodField methodField = (MethodField) field; - String alias = (String) methodField.getParams().get(0).value; - String scriptCode = (String) methodField.getParams().get(1).value; - Assert.assertEquals(alias, "testBaseLevel"); - Matcher docValue = Pattern.compile("doc\\['testBase'].value").matcher(scriptCode); - Matcher number = Pattern.compile(" (\\s+90) | (\\s+'80')").matcher(scriptCode); - - AtomicInteger docValueCounter = new AtomicInteger(); - - while (docValue.find()) { - docValueCounter.incrementAndGet(); - } - - Assert.assertThat(docValueCounter.get(), equalTo(2)); - Assert.assertThat(number.groupCount(), equalTo(2)); - - } - } - - } - - @Test - public void caseWhenTestWithFieldElseExpr() throws SqlParseException { - String query = "Select k,\n" + - "Case \n" + - "When floor(testBase)>=90 then 'A'\n" + - "When testBase = '80' then 'B'\n" + - "Else testBase end as testBaseLevel\n" + - "from t"; - SQLExpr sqlExpr = queryToExpr(query); - Select select = parser.parseSelect((SQLQueryExpr) sqlExpr); - for (Field field : select.getFields()) { - if (field instanceof MethodField) { - MethodField methodField = (MethodField) field; - String alias = (String) methodField.getParams().get(0).value; - String scriptCode = (String) methodField.getParams().get(1).value; - Assert.assertEquals(alias, "testBaseLevel"); - Matcher docValue = Pattern.compile("doc\\['testBase'].value").matcher(scriptCode); - Matcher number = Pattern.compile(" (\\s+90) | (\\s+'80')").matcher(scriptCode); - - AtomicInteger docValueCounter = new AtomicInteger(); - - while (docValue.find()) { - docValueCounter.incrementAndGet(); - } - - Assert.assertThat(docValueCounter.get(), equalTo(3)); - Assert.assertThat(number.groupCount(), equalTo(2)); - - } - } - - } - - @Test - public void caseWhenTestWithouhtElseExpr() throws SqlParseException { - String query = "Select k,\n" + - "Case \n" + - "When floor(testBase)>=90 then 'A'\n" + - "When testBase = '80' then 'B'\n" + - "end as testBaseLevel\n" + - "from t"; - SQLExpr sqlExpr = queryToExpr(query); - Select select = parser.parseSelect((SQLQueryExpr) sqlExpr); - for (Field field : select.getFields()) { - if (field instanceof MethodField) { - MethodField methodField = (MethodField) field; - String alias = (String) methodField.getParams().get(0).value; - String scriptCode = (String) methodField.getParams().get(1).value; - Assert.assertEquals(alias, "testBaseLevel"); - - Matcher docValue = Pattern.compile("\\{\\s+null\\s+}").matcher(scriptCode); - - AtomicInteger docValueCounter = new AtomicInteger(); - - while (docValue.find()) { - docValueCounter.incrementAndGet(); - } - - Assert.assertThat(docValueCounter.get(), equalTo(1)); - - } - } - - } - - @Test - public void caseWhenSwitchTest() { - String query = "SELECT CASE weather " - + "WHEN 'Sunny' THEN '0' " - + "WHEN 'Rainy' THEN '1' " - + "ELSE 'NA' END AS case " - + "FROM t"; - ScriptField scriptField = CheckScriptContents.getScriptFieldFromQuery(query); - Assert.assertTrue( - CheckScriptContents.scriptContainsString( - scriptField, - "doc['weather'].value=='Sunny'" - ) - ); - } - - @Test - public void castToIntTest() throws Exception { - String query = "select cast(age as int) from "+ TestsConstants.TEST_INDEX_ACCOUNT + "/account limit 10"; - SQLExpr sqlExpr = queryToExpr(query); - Select select = parser.parseSelect((SQLQueryExpr) sqlExpr); - Field castField = select.getFields().get(0); - Assert.assertTrue(castField instanceof MethodField); - - MethodField methodField = (MethodField) castField; - Assert.assertEquals("script",castField.getName()); - + private SqlParser parser; + + @Before + public void init() { + parser = new SqlParser(); + } + + @Rule public ExpectedException thrown = ExpectedException.none(); + + @Test + public void whereConditionLeftFunctionRightPropertyGreatTest() throws Exception { + + String query = + "SELECT " + + " * from " + + TEST_INDEX_ACCOUNT + + "/account " + + " where floor(split(address,' ')[0]+0) > b limit 1000 "; + + Select select = parser.parseSelect((SQLQueryExpr) queryToExpr(query)); + Where where = select.getWhere(); + Assert.assertTrue((where.getWheres().size() == 1)); + Assert.assertTrue(((Condition) (where.getWheres().get(0))).getValue() instanceof ScriptFilter); + ScriptFilter scriptFilter = + (ScriptFilter) (((Condition) (where.getWheres().get(0))).getValue()); + + Assert.assertTrue(scriptFilter.getScript().contains("doc['address'].value.split(' ')[0]")); + Pattern pattern = Pattern.compile("floor_\\d+ > doc\\['b'].value"); + java.util.regex.Matcher matcher = pattern.matcher(scriptFilter.getScript()); + Assert.assertTrue(matcher.find()); + } + + @Test() + public void failingQueryTest() throws SqlParseException { + thrown.expect(SqlFeatureNotImplementedException.class); + thrown.expectMessage( + "The complex aggregate expressions are not implemented yet: MAX(FlightDelayMin) -" + + " MIN(FlightDelayMin)"); + + Select select = + parser.parseSelect( + (SQLQueryExpr) + queryToExpr( + "SELECT DestCountry, dayOfWeek, max(FlightDelayMin) - min(FlightDelayMin)" + + " FROM opensearch_dashboards_sample_data_flights\n" + + " GROUP BY DestCountry, dayOfWeek\n")); + + AggregationQueryAction queryAction = new AggregationQueryAction(mock(Client.class), select); + String elasticDsl = queryAction.explain().explain(); + } + + @Test() + public void failingQueryTest2() throws SqlParseException { + thrown.expect(SqlFeatureNotImplementedException.class); + thrown.expectMessage("Function calls of form 'log(MAX(...))' are not implemented yet"); + + Select select = + parser.parseSelect( + (SQLQueryExpr) + queryToExpr( + "SELECT DestCountry, dayOfWeek, log(max(FlightDelayMin))" + + " FROM opensearch_dashboards_sample_data_flights\n" + + " GROUP BY DestCountry, dayOfWeek\n")); + + AggregationQueryAction queryAction = new AggregationQueryAction(mock(Client.class), select); + String elasticDsl = queryAction.explain().explain(); + } + + @Test() + public void failingQueryWithHavingTest() throws SqlParseException { + thrown.expect(SqlFeatureNotImplementedException.class); + thrown.expectMessage( + "The complex aggregate expressions are not implemented yet: MAX(FlightDelayMin) -" + + " MIN(FlightDelayMin)"); + + Select select = + parser.parseSelect( + (SQLQueryExpr) + queryToExpr( + "SELECT DestCountry, dayOfWeek, max(FlightDelayMin) - min(FlightDelayMin) " + + " FROM opensearch_dashboards_sample_data_flights\n" + + " GROUP BY DestCountry, dayOfWeek\n" + + " HAVING max(FlightDelayMin) - min(FlightDelayMin)) *" + + " count(FlightDelayMin) + 14 > 100")); + + AggregationQueryAction queryAction = new AggregationQueryAction(mock(Client.class), select); + String elasticDsl = queryAction.explain().explain(); + } + + @Test() + @Ignore( + "Github issues: https://github.com/opendistro-for-elasticsearch/sql/issues/194, " + + "https://github.com/opendistro-for-elasticsearch/sql/issues/234") + public void failingQueryWithHavingTest2() throws SqlParseException { + Select select = + parser.parseSelect( + (SQLQueryExpr) + queryToExpr( + "SELECT DestCountry, dayOfWeek, max(FlightDelayMin) " + + " FROM opensearch_dashboards_sample_data_flights\n" + + " GROUP BY DestCountry, dayOfWeek\n" + + " HAVING max(FlightDelayMin) - min(FlightDelayMin) > 100")); + + AggregationQueryAction queryAction = new AggregationQueryAction(mock(Client.class), select); + + String elasticDsl = queryAction.explain().explain(); + } + + @Test + public void whereConditionLeftFunctionRightFunctionEqualTest() throws Exception { + + String query = + "SELECT " + + " * from " + + TEST_INDEX_ACCOUNT + + "/account " + + " where floor(split(address,' ')[0]+0) = floor(split(address,' ')[0]+0) limit 1000 "; + + Select select = parser.parseSelect((SQLQueryExpr) queryToExpr(query)); + Where where = select.getWhere(); + Assert.assertTrue((where.getWheres().size() == 1)); + Assert.assertTrue(((Condition) (where.getWheres().get(0))).getValue() instanceof ScriptFilter); + ScriptFilter scriptFilter = + (ScriptFilter) (((Condition) (where.getWheres().get(0))).getValue()); + Assert.assertTrue(scriptFilter.getScript().contains("doc['address'].value.split(' ')[0]")); + Pattern pattern = Pattern.compile("floor_\\d+ == floor_\\d+"); + java.util.regex.Matcher matcher = pattern.matcher(scriptFilter.getScript()); + Assert.assertTrue(matcher.find()); + } + + @Test + public void whereConditionVariableRightVariableEqualTest() throws Exception { + + String query = + "SELECT " + " * from " + TEST_INDEX_ACCOUNT + "/account " + " where a = b limit 1000 "; + + Select select = parser.parseSelect((SQLQueryExpr) queryToExpr(query)); + Where where = select.getWhere(); + Assert.assertTrue((where.getWheres().size() == 1)); + Assert.assertTrue(((Condition) (where.getWheres().get(0))).getValue() instanceof ScriptFilter); + ScriptFilter scriptFilter = + (ScriptFilter) (((Condition) (where.getWheres().get(0))).getValue()); + Assert.assertTrue(scriptFilter.getScript().contains("doc['a'].value == doc['b'].value")); + } + + @Test + public void joinParseCheckSelectedFieldsSplit() throws SqlParseException { + String query = + "SELECT a.firstname ,a.lastname , a.gender , d.holdersName ,d.name FROM " + + TestsConstants.TEST_INDEX_ACCOUNT + + "/account a " + + "LEFT JOIN " + + TEST_INDEX_DOG + + "/dog d on d.holdersName = a.firstname " + + " AND d.age < a.age " + + " WHERE a.firstname = 'eliran' AND " + + " (a.age > 10 OR a.balance > 2000)" + + " AND d.age > 1"; + + JoinSelect joinSelect = parser.parseJoinSelect((SQLQueryExpr) queryToExpr(query)); + + List t1Fields = joinSelect.getFirstTable().getSelectedFields(); + Assert.assertEquals(t1Fields.size(), 3); + Assert.assertTrue(fieldExist(t1Fields, "firstname")); + Assert.assertTrue(fieldExist(t1Fields, "lastname")); + Assert.assertTrue(fieldExist(t1Fields, "gender")); + + List t2Fields = joinSelect.getSecondTable().getSelectedFields(); + Assert.assertEquals(t2Fields.size(), 2); + Assert.assertTrue(fieldExist(t2Fields, "holdersName")); + Assert.assertTrue(fieldExist(t2Fields, "name")); + } + + @Test + public void joinParseCheckConnectedFields() throws SqlParseException { + String query = + "SELECT a.firstname ,a.lastname , a.gender , d.holdersName ,d.name FROM " + + TestsConstants.TEST_INDEX_ACCOUNT + + "/account a " + + "LEFT JOIN " + + TEST_INDEX_DOG + + "/dog d on d.holdersName = a.firstname " + + " AND d.age < a.age " + + " WHERE a.firstname = 'eliran' AND " + + " (a.age > 10 OR a.balance > 2000)" + + " AND d.age > 1"; + + JoinSelect joinSelect = parser.parseJoinSelect((SQLQueryExpr) queryToExpr(query)); + + List t1Fields = joinSelect.getFirstTable().getConnectedFields(); + Assert.assertEquals(t1Fields.size(), 2); + Assert.assertTrue(fieldExist(t1Fields, "firstname")); + Assert.assertTrue(fieldExist(t1Fields, "age")); + + List t2Fields = joinSelect.getSecondTable().getConnectedFields(); + Assert.assertEquals(t2Fields.size(), 2); + Assert.assertTrue(fieldExist(t2Fields, "holdersName")); + Assert.assertTrue(fieldExist(t2Fields, "age")); + } + + private boolean fieldExist(List fields, String fieldName) { + for (Field field : fields) if (field.getName().equals(fieldName)) return true; + + return false; + } + + @Test + public void joinParseFromsAreSplitedCorrectly() throws SqlParseException { + String query = + "SELECT a.firstname ,a.lastname , a.gender , d.holdersName ,d.name FROM " + + TestsConstants.TEST_INDEX_ACCOUNT + + " a " + + "LEFT JOIN " + + TEST_INDEX_DOG + + " d on d.holdersName = a.firstname" + + " WHERE a.firstname = 'eliran' AND " + + " (a.age > 10 OR a.balance > 2000)" + + " AND d.age > 1"; + + JoinSelect joinSelect = parser.parseJoinSelect((SQLQueryExpr) queryToExpr(query)); + List t1From = joinSelect.getFirstTable().getFrom(); + + Assert.assertNotNull(t1From); + Assert.assertEquals(1, t1From.size()); + Assert.assertTrue(checkFrom(t1From.get(0), TestsConstants.TEST_INDEX_ACCOUNT, "a")); + + List t2From = joinSelect.getSecondTable().getFrom(); + Assert.assertNotNull(t2From); + Assert.assertEquals(1, t2From.size()); + Assert.assertTrue(checkFrom(t2From.get(0), TEST_INDEX_DOG, "d")); + } + + private boolean checkFrom(From from, String index, String alias) { + return from.getAlias().equals(alias) && from.getIndex().equals(index); + } + + @Test + public void joinParseConditionsTestOneCondition() throws SqlParseException { + String query = + "SELECT a.*, a.firstname ,a.lastname , a.gender , d.holdersName ,d.name FROM " + + TestsConstants.TEST_INDEX_ACCOUNT + + "/account a " + + "LEFT JOIN " + + TEST_INDEX_DOG + + "/dog d on d.holdersName = a.firstname" + + " WHERE a.firstname = 'eliran' AND " + + " (a.age > 10 OR a.balance > 2000)" + + " AND d.age > 1"; + + JoinSelect joinSelect = parser.parseJoinSelect((SQLQueryExpr) queryToExpr(query)); + List conditions = joinSelect.getConnectedConditions(); + Assert.assertNotNull(conditions); + Assert.assertEquals(1, conditions.size()); + Assert.assertTrue( + "condition not exist: d.holdersName = a.firstname", + conditionExist(conditions, "d.holdersName", "a.firstname", Condition.OPERATOR.EQ)); + } + + @Test + public void joinParseConditionsTestTwoConditions() throws SqlParseException { + String query = + "SELECT a.*, a.firstname ,a.lastname , a.gender , d.holdersName ,d.name FROM " + + TestsConstants.TEST_INDEX_ACCOUNT + + "/account a " + + "LEFT JOIN " + + TEST_INDEX_DOG + + "/dog d on d.holdersName = a.firstname " + + " AND d.age < a.age " + + " WHERE a.firstname = 'eliran' AND " + + " (a.age > 10 OR a.balance > 2000)" + + " AND d.age > 1"; + + JoinSelect joinSelect = parser.parseJoinSelect((SQLQueryExpr) queryToExpr(query)); + List conditions = joinSelect.getConnectedConditions(); + Assert.assertNotNull(conditions); + Assert.assertEquals(2, conditions.size()); + Assert.assertTrue( + "condition not exist: d.holdersName = a.firstname", + conditionExist(conditions, "d.holdersName", "a.firstname", Condition.OPERATOR.EQ)); + Assert.assertTrue( + "condition not exist: d.age < a.age", + conditionExist(conditions, "d.age", "a.age", Condition.OPERATOR.LT)); + } + + @Test + public void joinSplitWhereCorrectly() throws SqlParseException { + String query = + "SELECT a.*, a.firstname ,a.lastname , a.gender , d.holdersName ,d.name FROM " + + TestsConstants.TEST_INDEX_ACCOUNT + + "/account a " + + "LEFT JOIN " + + TEST_INDEX_DOG + + "/dog d on d.holdersName = a.firstname" + + " WHERE a.firstname = 'eliran' AND " + + " (a.age > 10 OR a.balance > 2000)" + + " AND d.age > 1"; + + JoinSelect joinSelect = parser.parseJoinSelect((SQLQueryExpr) queryToExpr(query)); + String s1Where = joinSelect.getFirstTable().getWhere().toString(); + Assert.assertEquals( + "AND ( AND firstname EQ eliran, AND ( OR age GT 10, OR balance GT 2000 ) ) ", s1Where); + String s2Where = joinSelect.getSecondTable().getWhere().toString(); + Assert.assertEquals("AND age GT 1", s2Where); + } + + @Test + public void joinConditionWithComplexObjectComparisonRightSide() throws SqlParseException { + String query = + String.format( + Locale.ROOT, + "select c.name.firstname,c.parents.father , h.name,h.words " + + "from %s/gotCharacters c " + + "JOIN %s/gotCharacters h " + + "on h.name = c.name.lastname " + + "where c.name.firstname='Daenerys'", + TEST_INDEX_GAME_OF_THRONES, + TEST_INDEX_GAME_OF_THRONES); + JoinSelect joinSelect = parser.parseJoinSelect((SQLQueryExpr) queryToExpr(query)); + List conditions = joinSelect.getConnectedConditions(); + Assert.assertNotNull(conditions); + Assert.assertEquals(1, conditions.size()); + Assert.assertTrue( + "condition not exist: h.name = c.name.lastname", + conditionExist(conditions, "h.name", "c.name.lastname", Condition.OPERATOR.EQ)); + } + + @Test + public void joinConditionWithComplexObjectComparisonLeftSide() throws SqlParseException { + String query = + String.format( + Locale.ROOT, + "select c.name.firstname,c.parents.father , h.name,h.words from %s/gotCharacters c " + + "JOIN %s/gotCharacters h " + + "on c.name.lastname = h.name " + + "where c.name.firstname='Daenerys'", + TEST_INDEX_GAME_OF_THRONES, + TEST_INDEX_GAME_OF_THRONES); + JoinSelect joinSelect = parser.parseJoinSelect((SQLQueryExpr) queryToExpr(query)); + List conditions = joinSelect.getConnectedConditions(); + Assert.assertNotNull(conditions); + Assert.assertEquals(1, conditions.size()); + Assert.assertTrue( + "condition not exist: c.name.lastname = h.name", + conditionExist(conditions, "c.name.lastname", "h.name", Condition.OPERATOR.EQ)); + } + + @Test + public void limitHintsOnJoin() throws SqlParseException { + String query = + String.format( + Locale.ROOT, + "select /*! JOIN_TABLES_LIMIT(1000,null) */ " + + "c.name.firstname,c.parents.father , h.name,h.words from %s/gotCharacters c " + + "use KEY (termsFilter) " + + "JOIN %s/gotCharacters h " + + "on c.name.lastname = h.name " + + "where c.name.firstname='Daenerys'", + TEST_INDEX_GAME_OF_THRONES, + TEST_INDEX_GAME_OF_THRONES); + JoinSelect joinSelect = parser.parseJoinSelect((SQLQueryExpr) queryToExpr(query)); + List hints = joinSelect.getHints(); + Assert.assertNotNull(hints); + Assert.assertEquals("hints size was not 1", 1, hints.size()); + Hint hint = hints.get(0); + Assert.assertEquals(HintType.JOIN_LIMIT, hint.getType()); + Object[] params = hint.getParams(); + Assert.assertNotNull(params); + Assert.assertEquals("params size was not 2", 2, params.length); + Assert.assertEquals(1000, params[0]); + Assert.assertNull(params[1]); + } + + @Test + public void hashTermsFilterHint() throws SqlParseException { + String query = + String.format( + Locale.ROOT, + "select /*! HASH_WITH_TERMS_FILTER*/ " + + "c.name.firstname,c.parents.father , h.name,h.words from %s/gotCharacters c " + + "use KEY (termsFilter) " + + "JOIN %s/gotCharacters h " + + "on c.name.lastname = h.name " + + "where c.name.firstname='Daenerys'", + TEST_INDEX_GAME_OF_THRONES, + TEST_INDEX_GAME_OF_THRONES); + JoinSelect joinSelect = parser.parseJoinSelect((SQLQueryExpr) queryToExpr(query)); + List hints = joinSelect.getHints(); + Assert.assertNotNull(hints); + Assert.assertEquals("hints size was not 1", 1, hints.size()); + Hint hint = hints.get(0); + Assert.assertEquals(HintType.HASH_WITH_TERMS_FILTER, hint.getType()); + } + + @Test + public void multipleHints() throws SqlParseException { + String query = + String.format( + Locale.ROOT, + "select /*! HASH_WITH_TERMS_FILTER*/ " + + "/*! JOIN_TABLES_LIMIT(1000,null) */ " + + " /*! JOIN_TABLES_LIMIT(100,200) */ " + + "c.name.firstname,c.parents.father , h.name,h.words from %s/gotCharacters c " + + "use KEY (termsFilter) " + + "JOIN %s/gotCharacters h " + + "on c.name.lastname = h.name " + + "where c.name.firstname='Daenerys'", + TEST_INDEX_GAME_OF_THRONES, + TEST_INDEX_GAME_OF_THRONES); + + JoinSelect joinSelect = parser.parseJoinSelect((SQLQueryExpr) queryToExpr(query)); + List hints = joinSelect.getHints(); + + Assert.assertNotNull(hints); + Assert.assertEquals("hints size was not 3", 3, hints.size()); + Hint firstHint = hints.get(0); + Assert.assertEquals(HintType.HASH_WITH_TERMS_FILTER, firstHint.getType()); + Hint secondHint = hints.get(1); + Assert.assertEquals(HintType.JOIN_LIMIT, secondHint.getType()); + Assert.assertEquals(1000, secondHint.getParams()[0]); + Assert.assertNull(secondHint.getParams()[1]); + Hint thirdHint = hints.get(2); + Assert.assertEquals(100, thirdHint.getParams()[0]); + Assert.assertEquals(200, thirdHint.getParams()[1]); + Assert.assertEquals(HintType.JOIN_LIMIT, thirdHint.getType()); + } + + @Test + public void searchWithOdbcTimeFormatParse() throws SqlParseException { + String query = + String.format( + Locale.ROOT, + "SELECT insert_time FROM %s/odbc " + + "WHERE insert_time < {ts '2015-03-15 00:00:00.000'}", + TEST_INDEX_ODBC); + SQLExpr sqlExpr = queryToExpr(query); + Select select = parser.parseSelect((SQLQueryExpr) sqlExpr); + LinkedList wheres = select.getWhere().getWheres(); + Assert.assertEquals(1, wheres.size()); + Condition condition = (Condition) wheres.get(0); + Assert.assertEquals("{ts '2015-03-15 00:00:00.000'}", condition.getValue().toString()); + } + + @Test + public void indexWithSpacesWithinBrackets() throws SqlParseException { + String query = "SELECT insert_time FROM [Test Index] WHERE age > 3"; + SQLExpr sqlExpr = queryToExpr(query); + Select select = parser.parseSelect((SQLQueryExpr) sqlExpr); + List fromList = select.getFrom(); + Assert.assertEquals(1, fromList.size()); + From from = fromList.get(0); + Assert.assertEquals("Test Index", from.getIndex()); + } + + @Test + public void indexWithSpacesWithTypeWithinBrackets() throws SqlParseException { + String query = "SELECT insert_time FROM [Test Index] WHERE age > 3"; + SQLExpr sqlExpr = queryToExpr(query); + Select select = parser.parseSelect((SQLQueryExpr) sqlExpr); + List fromList = select.getFrom(); + Assert.assertEquals(1, fromList.size()); + From from = fromList.get(0); + Assert.assertEquals("Test Index", from.getIndex()); + } + + @Test + public void fieldWithSpacesWithinBrackets() throws SqlParseException { + String query = "SELECT insert_time FROM name/type1 WHERE [first name] = 'Name'"; + SQLExpr sqlExpr = queryToExpr(query); + Select select = parser.parseSelect((SQLQueryExpr) sqlExpr); + List where = select.getWhere().getWheres(); + Assert.assertEquals(1, where.size()); + Condition condition = (Condition) where.get(0); + Assert.assertEquals("first name", condition.getName()); + Assert.assertEquals("Name", condition.getValue()); + } + + @Test + public void twoIndices() throws SqlParseException { + String query = "SELECT insert_time FROM index1, index2 WHERE age > 3"; + SQLExpr sqlExpr = queryToExpr(query); + Select select = parser.parseSelect((SQLQueryExpr) sqlExpr); + List fromList = select.getFrom(); + Assert.assertEquals(2, fromList.size()); + From from1 = fromList.get(0); + From from2 = fromList.get(1); + boolean preservedOrder = from1.getIndex().equals("index1") && from2.getIndex().equals("index2"); + boolean notPreservedOrder = + from1.getIndex().equals("index2") && from2.getIndex().equals("index1"); + Assert.assertTrue(preservedOrder || notPreservedOrder); + } + + @Test + public void fieldWithATcharAtWhere() throws SqlParseException { + String query = "SELECT * FROM index/type where @field = 6 "; + SQLExpr sqlExpr = queryToExpr(query); + Select select = parser.parseSelect((SQLQueryExpr) sqlExpr); + LinkedList wheres = select.getWhere().getWheres(); + Assert.assertEquals(1, wheres.size()); + Condition condition = (Condition) wheres.get(0); + Assert.assertEquals("@field", condition.getName()); + } + + @Test + public void fieldWithATcharAtSelect() throws SqlParseException { + String query = "SELECT @field FROM index/type where field2 = 6 "; + SQLExpr sqlExpr = queryToExpr(query); + Select select = parser.parseSelect((SQLQueryExpr) sqlExpr); + List fields = select.getFields(); + Assert.assertEquals(1, fields.size()); + Field field = fields.get(0); + Assert.assertEquals(field.getName(), "@field"); + } + + @Test + public void fieldWithATcharAtSelectOnAgg() throws SqlParseException { + String query = "SELECT max(@field) FROM index/type where field2 = 6 "; + SQLExpr sqlExpr = queryToExpr(query); + Select select = parser.parseSelect((SQLQueryExpr) sqlExpr); + List fields = select.getFields(); + Assert.assertEquals(1, fields.size()); + Field field = fields.get(0); + Assert.assertEquals("MAX(@field)", field.toString()); + } + + @Test + public void fieldWithColonCharAtSelect() throws SqlParseException { + String query = "SELECT a:b FROM index/type where field2 = 6 "; + SQLExpr sqlExpr = queryToExpr(query); + Select select = parser.parseSelect((SQLQueryExpr) sqlExpr); + List fields = select.getFields(); + Assert.assertEquals(1, fields.size()); + Field field = fields.get(0); + Assert.assertEquals(field.getName(), "a:b"); + } + + @Test + public void fieldWithColonCharAtWhere() throws SqlParseException { + String query = "SELECT * FROM index/type where a:b = 6 "; + SQLExpr sqlExpr = queryToExpr(query); + Select select = parser.parseSelect((SQLQueryExpr) sqlExpr); + LinkedList wheres = select.getWhere().getWheres(); + Assert.assertEquals(1, wheres.size()); + Condition condition = (Condition) wheres.get(0); + Assert.assertEquals("a:b", condition.getName()); + } + + @Test + public void fieldIsNull() throws SqlParseException { + String query = "SELECT * FROM index/type where a IS NOT NULL"; + SQLExpr sqlExpr = queryToExpr(query); + Select select = parser.parseSelect((SQLQueryExpr) sqlExpr); + LinkedList wheres = select.getWhere().getWheres(); + Assert.assertEquals(1, wheres.size()); + Condition condition = (Condition) wheres.get(0); + Assert.assertEquals("a", condition.getName()); + Assert.assertNull(condition.getValue()); + } + + @Test + public void innerQueryTest() throws SqlParseException { + String query = + String.format( + Locale.ROOT, + "select * from %s/dog where holdersName " + + "IN (select firstname from %s/account where firstname = 'eliran')", + TEST_INDEX_DOG, + TestsConstants.TEST_INDEX_ACCOUNT); + SQLExpr sqlExpr = queryToExpr(query); + Select select = parser.parseSelect((SQLQueryExpr) sqlExpr); + Assert.assertTrue(select.containsSubQueries()); + Assert.assertEquals(1, select.getSubQueries().size()); + } + + @Test + public void inTermsSubQueryTest() throws SqlParseException { + String query = + String.format( + Locale.ROOT, + "select * from %s/dog where holdersName = IN_TERMS (select firstname from %s/account" + + " where firstname = 'eliran')", + TEST_INDEX_DOG, + TestsConstants.TEST_INDEX_ACCOUNT); + SQLExpr sqlExpr = queryToExpr(query); + Select select = parser.parseSelect((SQLQueryExpr) sqlExpr); + Assert.assertTrue(select.containsSubQueries()); + Assert.assertEquals(1, select.getSubQueries().size()); + } + + @Test + public void innerQueryTestTwoQueries() throws SqlParseException { + String query = + String.format( + Locale.ROOT, + "select * from %s/dog where holdersName IN " + + "(select firstname from %s/account where firstname = 'eliran') and " + + "age IN (select name.ofHisName from %s/gotCharacters) ", + TEST_INDEX_DOG, + TestsConstants.TEST_INDEX_ACCOUNT, + TEST_INDEX_GAME_OF_THRONES); + SQLExpr sqlExpr = queryToExpr(query); + Select select = parser.parseSelect((SQLQueryExpr) sqlExpr); + Assert.assertTrue(select.containsSubQueries()); + Assert.assertEquals(2, select.getSubQueries().size()); + } + + @Test + public void indexWithDotsAndHyphen() throws SqlParseException { + String query = "select * from data-2015.08.22"; + SQLExpr sqlExpr = queryToExpr(query); + Select select = parser.parseSelect((SQLQueryExpr) sqlExpr); + Assert.assertEquals(1, select.getFrom().size()); + Assert.assertEquals("data-2015.08.22", select.getFrom().get(0).getIndex()); + } + + @Test + public void indexNameWithDotAtTheStart() throws SqlParseException { + String query = "SELECT * FROM .opensearch_dashboards"; + SQLExpr sqlExpr = queryToExpr(query); + Select select = parser.parseSelect((SQLQueryExpr) sqlExpr); + Assert.assertEquals(".opensearch_dashboards", select.getFrom().get(0).getIndex()); + } + + @Test + public void indexWithSemiColons() throws SqlParseException { + String query = "select * from some;index"; + SQLExpr sqlExpr = queryToExpr(query); + Select select = parser.parseSelect((SQLQueryExpr) sqlExpr); + Assert.assertEquals(1, select.getFrom().size()); + Assert.assertEquals("some;index", select.getFrom().get(0).getIndex()); + } + + @Test + public void scriptFiledPlusLiteralTest() throws SqlParseException { + String query = "SELECT field1 + 3 FROM index/type"; + SQLExpr sqlExpr = queryToExpr(query); + Select select = parser.parseSelect((SQLQueryExpr) sqlExpr); + List fields = select.getFields(); + Assert.assertEquals(1, fields.size()); + Field field = fields.get(0); + Assert.assertTrue(field instanceof MethodField); + MethodField scriptMethod = (MethodField) field; + Assert.assertEquals("script", scriptMethod.getName().toLowerCase()); + Assert.assertEquals(2, scriptMethod.getParams().size()); + Assert.assertTrue( + scriptMethod.getParams().get(1).toString().contains("doc['field1'].value + 3")); + } + + @Test + public void scriptFieldPlusFieldTest() throws SqlParseException { + String query = "SELECT field1 + field2 FROM index/type"; + SQLExpr sqlExpr = queryToExpr(query); + Select select = parser.parseSelect((SQLQueryExpr) sqlExpr); + List fields = select.getFields(); + Assert.assertEquals(1, fields.size()); + Field field = fields.get(0); + Assert.assertTrue(field instanceof MethodField); + MethodField scriptMethod = (MethodField) field; + Assert.assertEquals("script", scriptMethod.getName().toLowerCase()); + Assert.assertEquals(2, scriptMethod.getParams().size()); + Assert.assertTrue( + scriptMethod + .getParams() + .get(1) + .toString() + .contains("doc['field1'].value + doc['field2'].value")); + } + + @Test + public void scriptLiteralPlusLiteralTest() throws SqlParseException { + String query = "SELECT 1 + 2 FROM index/type"; + SQLExpr sqlExpr = queryToExpr(query); + Select select = parser.parseSelect((SQLQueryExpr) sqlExpr); + List fields = select.getFields(); + Assert.assertEquals(1, fields.size()); + Field field = fields.get(0); + Assert.assertTrue(field instanceof MethodField); + MethodField scriptMethod = (MethodField) field; + Assert.assertEquals("script", scriptMethod.getName().toLowerCase()); + Assert.assertEquals(2, scriptMethod.getParams().size()); + Assert.assertTrue(scriptMethod.getParams().get(1).toString().contains("1 + 2")); + } + + @Test + public void explicitScriptOnAggregation() throws SqlParseException { + String query = + "SELECT avg( script('add','doc[\\'field1\\'].value + doc[\\'field2\\'].value') )" + + " FROM index/type"; + SQLExpr sqlExpr = queryToExpr(query); + Select select = parser.parseSelect((SQLQueryExpr) sqlExpr); + List fields = select.getFields(); + Assert.assertEquals(1, fields.size()); + Field field = fields.get(0); + Assert.assertTrue(field instanceof MethodField); + MethodField avgMethodField = (MethodField) field; + Assert.assertEquals("avg", avgMethodField.getName().toLowerCase()); + Assert.assertEquals(1, avgMethodField.getParams().size()); + MethodField scriptMethod = (MethodField) avgMethodField.getParams().get(0).value; + Assert.assertEquals("script", scriptMethod.getName().toLowerCase()); + Assert.assertEquals(2, scriptMethod.getParams().size()); + Assert.assertEquals( + "doc['field1'].value + doc['field2'].value", scriptMethod.getParams().get(1).toString()); + } + + @Test + public void implicitScriptOnAggregation() throws SqlParseException { + String query = "SELECT avg(field(field1) + field(field2)) FROM index/type"; + SQLExpr sqlExpr = queryToExpr(query); + Select select = parser.parseSelect((SQLQueryExpr) sqlExpr); + List fields = select.getFields(); + Assert.assertEquals(1, fields.size()); + Field field = fields.get(0); + Assert.assertTrue(field instanceof MethodField); + MethodField avgMethodField = (MethodField) field; + Assert.assertEquals("avg", avgMethodField.getName().toLowerCase()); + Assert.assertEquals(1, avgMethodField.getParams().size()); + Assert.assertTrue( + avgMethodField.getParams().get(0).value.toString().contains("doc['field1'].value")); + Assert.assertTrue( + avgMethodField.getParams().get(0).value.toString().contains("doc['field2'].value")); + } + + @Test + public void nestedFieldOnWhereNoPathSimpleField() throws SqlParseException { + String query = "select * from myIndex where nested(message.name) = 'hey'"; + SQLExpr sqlExpr = queryToExpr(query); + Select select = parser.parseSelect((SQLQueryExpr) sqlExpr); + Where where = select.getWhere().getWheres().get(0); + Assert.assertTrue("where should be condition", where instanceof Condition); + Condition condition = (Condition) where; + Assert.assertTrue("condition should be nested", condition.isNested()); + Assert.assertEquals("message", condition.getNestedPath()); + Assert.assertEquals("message.name", condition.getName()); + } + + @Test + public void nestedFieldOnWhereNoPathComplexField() throws SqlParseException { + String query = "select * from myIndex where nested(message.moreNested.name) = 'hey'"; + SQLExpr sqlExpr = queryToExpr(query); + Select select = parser.parseSelect((SQLQueryExpr) sqlExpr); + Where where = select.getWhere().getWheres().get(0); + Assert.assertTrue("where should be condition", where instanceof Condition); + Condition condition = (Condition) where; + Assert.assertTrue("condition should be nested", condition.isNested()); + Assert.assertEquals("message.moreNested", condition.getNestedPath()); + Assert.assertEquals("message.moreNested.name", condition.getName()); + } + + @Test + public void aggFieldWithAliasTableAliasShouldBeRemoved() throws SqlParseException { + String query = "select count(t.*) as counts,sum(t.size) from xxx/locs as t group by t.kk"; + SQLExpr sqlExpr = queryToExpr(query); + Select select = parser.parseSelect((SQLQueryExpr) sqlExpr); + List fields = select.getFields(); + Assert.assertThat(fields.size(), equalTo(2)); + Assert.assertEquals("COUNT(*)", fields.get(0).toString()); + Assert.assertEquals("SUM(size)", fields.get(1).toString()); + List> groups = select.getGroupBys(); + Assert.assertThat(groups.size(), equalTo(1)); + Assert.assertThat(groups.get(0).size(), equalTo(1)); + Assert.assertEquals("kk", groups.get(0).get(0).getName()); + } + + @Test + public void nestedFieldOnWhereGivenPath() throws SqlParseException { + String query = "select * from myIndex where nested(message.name,message) = 'hey'"; + SQLExpr sqlExpr = queryToExpr(query); + Select select = parser.parseSelect((SQLQueryExpr) sqlExpr); + Where where = select.getWhere().getWheres().get(0); + Assert.assertTrue("where should be condition", where instanceof Condition); + Condition condition = (Condition) where; + Assert.assertTrue("condition should be nested", condition.isNested()); + Assert.assertEquals("message", condition.getNestedPath()); + Assert.assertEquals("message.name", condition.getName()); + } + + @Test + public void nestedFieldOnGroupByNoPath() throws SqlParseException { + String query = "select * from myIndex group by nested(message.name)"; + SQLExpr sqlExpr = queryToExpr(query); + Select select = parser.parseSelect((SQLQueryExpr) sqlExpr); + Field field = select.getGroupBys().get(0).get(0); + Assert.assertTrue("condition should be nested", field.isNested()); + Assert.assertEquals("message", field.getNestedPath()); + Assert.assertEquals("message.name", field.getName()); + } + + @Test + public void nestedFieldOnGroupByWithPath() throws SqlParseException { + String query = "select * from myIndex group by nested(message.name,message)"; + SQLExpr sqlExpr = queryToExpr(query); + Select select = parser.parseSelect((SQLQueryExpr) sqlExpr); + Field field = select.getGroupBys().get(0).get(0); + Assert.assertTrue("condition should be nested", field.isNested()); + Assert.assertEquals("message", field.getNestedPath()); + Assert.assertEquals("message.name", field.getName()); + } + + @Test + public void filterAggTestNoAlias() throws SqlParseException { + String query = "select * from myIndex group by a , filter( a > 3 AND b='3' )"; + SQLExpr sqlExpr = queryToExpr(query); + Select select = parser.parseSelect((SQLQueryExpr) sqlExpr); + List> groupBys = select.getGroupBys(); + Assert.assertEquals(1, groupBys.size()); + Field aAgg = groupBys.get(0).get(0); + Assert.assertEquals("a", aAgg.getName()); + Field field = groupBys.get(0).get(1); + Assert.assertTrue("filter field should be method field", field instanceof MethodField); + MethodField filterAgg = (MethodField) field; + Assert.assertEquals("filter", filterAgg.getName()); + Map params = filterAgg.getParamsAsMap(); + Assert.assertEquals(2, params.size()); + Object alias = params.get("alias"); + Assert.assertEquals("filter(a > 3 AND b = '3')@FILTER", alias); + + Assert.assertTrue(params.get("where") instanceof Where); + Where where = (Where) params.get("where"); + Assert.assertEquals(2, where.getWheres().size()); + } + + @Test + public void filterAggTestWithAlias() throws SqlParseException { + String query = "select * from myIndex group by a , filter(myFilter, a > 3 AND b='3' )"; + SQLExpr sqlExpr = queryToExpr(query); + Select select = parser.parseSelect((SQLQueryExpr) sqlExpr); + List> groupBys = select.getGroupBys(); + Assert.assertEquals(1, groupBys.size()); + Field aAgg = groupBys.get(0).get(0); + Assert.assertEquals("a", aAgg.getName()); + Field field = groupBys.get(0).get(1); + Assert.assertTrue("filter field should be method field", field instanceof MethodField); + MethodField filterAgg = (MethodField) field; + Assert.assertEquals("filter", filterAgg.getName()); + Map params = filterAgg.getParamsAsMap(); + Assert.assertEquals(2, params.size()); + Object alias = params.get("alias"); + Assert.assertEquals("myFilter@FILTER", alias); + + Assert.assertTrue(params.get("where") instanceof Where); + Where where = (Where) params.get("where"); + Assert.assertEquals(2, where.getWheres().size()); + } + + @Test + public void filterAggTestWithAliasAsString() throws SqlParseException { + String query = "select * from myIndex group by a , filter('my filter', a > 3 AND b='3' )"; + SQLExpr sqlExpr = queryToExpr(query); + Select select = parser.parseSelect((SQLQueryExpr) sqlExpr); + List> groupBys = select.getGroupBys(); + Assert.assertEquals(1, groupBys.size()); + Field aAgg = groupBys.get(0).get(0); + Assert.assertEquals("a", aAgg.getName()); + Field field = groupBys.get(0).get(1); + Assert.assertTrue("filter field should be method field", field instanceof MethodField); + MethodField filterAgg = (MethodField) field; + Assert.assertEquals("filter", filterAgg.getName()); + Map params = filterAgg.getParamsAsMap(); + Assert.assertEquals(2, params.size()); + Object alias = params.get("alias"); + Assert.assertEquals("my filter@FILTER", alias); + + Assert.assertTrue(params.get("where") instanceof Where); + Where where = (Where) params.get("where"); + Assert.assertEquals(2, where.getWheres().size()); + } + + @Test + public void doubleOrderByTest() throws SqlParseException { + String query = "select * from indexName order by a asc, b desc"; + SQLExpr sqlExpr = queryToExpr(query); + Select select = parser.parseSelect((SQLQueryExpr) sqlExpr); + List orderBys = select.getOrderBys(); + Assert.assertEquals(2, orderBys.size()); + Assert.assertEquals("a", orderBys.get(0).getName()); + Assert.assertEquals("ASC", orderBys.get(0).getType()); + + Assert.assertEquals("b", orderBys.get(1).getName()); + Assert.assertEquals("DESC", orderBys.get(1).getType()); + } + + @Test + public void parseJoinWithOneTableOrderByAttachToCorrectTable() throws SqlParseException { + String query = + String.format( + Locale.ROOT, + "select c.name.firstname , d.words from %s/gotCharacters c " + + "JOIN %s/gotCharacters d on d.name = c.house " + + "order by c.name.firstname", + TEST_INDEX_GAME_OF_THRONES, + TEST_INDEX_GAME_OF_THRONES); + + JoinSelect joinSelect = parser.parseJoinSelect((SQLQueryExpr) queryToExpr(query)); + Assert.assertTrue("first table should be ordered", joinSelect.getFirstTable().isOrderdSelect()); + Assert.assertFalse( + "second table should not be ordered", joinSelect.getSecondTable().isOrderdSelect()); + } + + @Test + public void parseJoinWithOneTableOrderByRemoveAlias() throws SqlParseException { + String query = + String.format( + Locale.ROOT, + "select c.name.firstname , d.words from %s/gotCharacters c " + + "JOIN %s/gotCharacters d on d.name = c.house " + + "order by c.name.firstname", + TEST_INDEX_GAME_OF_THRONES, + TEST_INDEX_GAME_OF_THRONES); + + JoinSelect joinSelect = parser.parseJoinSelect((SQLQueryExpr) queryToExpr(query)); + List orderBys = joinSelect.getFirstTable().getOrderBys(); + Assert.assertEquals(1, orderBys.size()); + Order order = orderBys.get(0); + Assert.assertEquals("name.firstname", order.getName()); + } + + @Test + public void termsWithStringTest() throws SqlParseException { + String query = "select * from x where y = IN_TERMS('a','b')"; + Select select = parser.parseSelect((SQLQueryExpr) queryToExpr(query)); + Condition condition = (Condition) select.getWhere().getWheres().get(0); + Object[] values = (Object[]) condition.getValue(); + Assert.assertEquals("a", values[0]); + Assert.assertEquals("b", values[1]); + } + + @Test + public void termWithStringTest() throws SqlParseException { + String query = "select * from x where y = TERM('a')"; + Select select = parser.parseSelect((SQLQueryExpr) queryToExpr(query)); + Condition condition = (Condition) select.getWhere().getWheres().get(0); + Object[] values = (Object[]) condition.getValue(); + Assert.assertEquals("a", values[0]); + } + + @Test + public void complexNestedTest() throws SqlParseException { + String query = "select * from x where nested('y',y.b = 'a' and y.c = 'd') "; + Select select = parser.parseSelect((SQLQueryExpr) queryToExpr(query)); + Condition condition = (Condition) select.getWhere().getWheres().get(0); + Assert.assertEquals(Condition.OPERATOR.NESTED_COMPLEX, condition.getOPERATOR()); + Assert.assertEquals("y", condition.getName()); + Assert.assertTrue(condition.getValue() instanceof Where); + Where where = (Where) condition.getValue(); + Assert.assertEquals(2, where.getWheres().size()); + } + + @Test + public void scriptOnFilterNoParams() throws SqlParseException { + String query = "select * from x where script('doc[\\'field\\'].date.hourOfDay == 3') "; + Select select = parser.parseSelect((SQLQueryExpr) queryToExpr(query)); + Condition condition = (Condition) select.getWhere().getWheres().get(0); + Assert.assertEquals(Condition.OPERATOR.SCRIPT, condition.getOPERATOR()); + Assert.assertNull(condition.getName()); + Assert.assertTrue(condition.getValue() instanceof ScriptFilter); + ScriptFilter scriptFilter = (ScriptFilter) condition.getValue(); + Assert.assertEquals("doc['field'].date.hourOfDay == 3", scriptFilter.getScript()); + Assert.assertFalse(scriptFilter.containsParameters()); + } + + @Test + public void scriptOnFilterWithParams() throws SqlParseException { + String query = "select * from x where script('doc[\\'field\\'].date.hourOfDay == x','x'=3) "; + Select select = parser.parseSelect((SQLQueryExpr) queryToExpr(query)); + Condition condition = (Condition) select.getWhere().getWheres().get(0); + Assert.assertEquals(Condition.OPERATOR.SCRIPT, condition.getOPERATOR()); + Assert.assertNull(condition.getName()); + Assert.assertTrue(condition.getValue() instanceof ScriptFilter); + ScriptFilter scriptFilter = (ScriptFilter) condition.getValue(); + Assert.assertEquals("doc['field'].date.hourOfDay == x", scriptFilter.getScript()); + Assert.assertTrue(scriptFilter.containsParameters()); + Map args = scriptFilter.getArgs(); + Assert.assertEquals(1, args.size()); + Assert.assertTrue(args.containsKey("x")); + Assert.assertEquals(3, args.get("x")); + } + + @Test + public void fieldsAsNumbersOnWhere() throws SqlParseException { + String query = "select * from x where ['3'] > 2"; + Select select = parser.parseSelect((SQLQueryExpr) queryToExpr(query)); + LinkedList wheres = select.getWhere().getWheres(); + Assert.assertEquals(1, wheres.size()); + Where where = wheres.get(0); + Assert.assertEquals(Condition.class, where.getClass()); + Condition condition = (Condition) where; + Assert.assertEquals("3", condition.getName()); + } + + @Test + public void likeTestWithEscaped() throws SqlParseException { + String query = "select * from x where name like '&UNDERSCOREhey_%&PERCENT'"; + Select select = parser.parseSelect((SQLQueryExpr) queryToExpr(query)); + BoolQueryBuilder explan = QueryMaker.explain(select.getWhere()); + String filterAsString = explan.toString(); + Assert.assertTrue(filterAsString.contains("_hey?*%")); + } + + @Test + public void complexNestedAndOtherQuery() throws SqlParseException { + String query = "select * from x where nested('path',path.x=3) and y=3"; + Select select = parser.parseSelect((SQLQueryExpr) queryToExpr(query)); + LinkedList wheres = select.getWhere().getWheres(); + Assert.assertEquals(2, wheres.size()); + Assert.assertEquals( + "AND path NESTED_COMPLEX AND ( AND path.x EQ 3 ) ", wheres.get(0).toString()); + Assert.assertEquals("AND y EQ 3", wheres.get(1).toString()); + } + + @Test + public void numberEqualConditionWithoutProperty() throws SqlParseException { + SQLExpr sqlExpr = queryToExpr("select * from xxx/locs where 1 = 1"); + Select select = parser.parseSelect((SQLQueryExpr) sqlExpr); + List wheres = select.getWhere().getWheres(); + Assert.assertThat(wheres.size(), equalTo(1)); + Condition condition = (Condition) wheres.get(0); + Assert.assertTrue(condition.getValue() instanceof ScriptFilter); + ScriptFilter sf = (ScriptFilter) condition.getValue(); + Assert.assertEquals(sf.getScript(), "1 == 1"); + } + + @Test + public void numberGreatConditionWithoutProperty() throws SqlParseException { + SQLExpr sqlExpr = queryToExpr("select * from xxx/locs where 1 > 1"); + Select select = parser.parseSelect((SQLQueryExpr) sqlExpr); + List wheres = select.getWhere().getWheres(); + Assert.assertThat(wheres.size(), equalTo(1)); + Condition condition = (Condition) wheres.get(0); + Assert.assertTrue(condition.getValue() instanceof ScriptFilter); + ScriptFilter sf = (ScriptFilter) condition.getValue(); + Assert.assertEquals(sf.getScript(), "1 > 1"); + } + + @Test + public void stringEqualConditionWithoutProperty() throws SqlParseException { + SQLExpr sqlExpr = queryToExpr("select * from xxx/locs where 'a' = 'b'"); + Select select = parser.parseSelect((SQLQueryExpr) sqlExpr); + List wheres = select.getWhere().getWheres(); + Assert.assertThat(wheres.size(), equalTo(1)); + Condition condition = (Condition) wheres.get(0); + Assert.assertTrue(condition.getValue() instanceof ScriptFilter); + ScriptFilter sf = (ScriptFilter) condition.getValue(); + Assert.assertEquals(sf.getScript(), "'a' == 'b'"); + } + + @Test + public void propertyEqualCondition() throws SqlParseException { + SQLExpr sqlExpr = queryToExpr("select * from xxx/locs where a = b"); + Select select = parser.parseSelect((SQLQueryExpr) sqlExpr); + List wheres = select.getWhere().getWheres(); + Assert.assertThat(wheres.size(), equalTo(1)); + Condition condition = (Condition) wheres.get(0); + Assert.assertTrue(condition.getValue() instanceof ScriptFilter); + ScriptFilter sf = (ScriptFilter) condition.getValue(); + Assert.assertEquals(sf.getScript(), "doc['a'].value == doc['b'].value"); + } + + @Test + public void propertyWithTableAliasEqualCondition() throws SqlParseException { + SQLExpr sqlExpr = queryToExpr("select t.* from xxx/locs where t.a = t.b"); + Select select = parser.parseSelect((SQLQueryExpr) sqlExpr); + List wheres = select.getWhere().getWheres(); + Assert.assertThat(wheres.size(), equalTo(1)); + Condition condition = (Condition) wheres.get(0); + Assert.assertTrue(condition.getValue() instanceof ScriptFilter); + ScriptFilter sf = (ScriptFilter) condition.getValue(); + Assert.assertEquals(sf.getScript(), "doc['a'].value == doc['b'].value"); + } + + @Test + public void propertyGreatCondition() throws SqlParseException { + SQLExpr sqlExpr = queryToExpr("select * from xxx/locs where a > b"); + Select select = parser.parseSelect((SQLQueryExpr) sqlExpr); + List wheres = select.getWhere().getWheres(); + Assert.assertThat(wheres.size(), equalTo(1)); + Condition condition = (Condition) wheres.get(0); + Assert.assertTrue(condition.getValue() instanceof ScriptFilter); + ScriptFilter sf = (ScriptFilter) condition.getValue(); + Assert.assertEquals(sf.getScript(), "doc['a'].value > doc['b'].value"); + } + + @Test + public void stringAndNumberEqualConditionWithoutProperty() throws SqlParseException { + SQLExpr sqlExpr = queryToExpr("select * from xxx/locs where 'a' = 1"); + Select select = parser.parseSelect((SQLQueryExpr) sqlExpr); + List wheres = select.getWhere().getWheres(); + Assert.assertThat(wheres.size(), equalTo(1)); + Condition condition = (Condition) wheres.get(0); + Assert.assertTrue(condition.getValue() instanceof ScriptFilter); + ScriptFilter sf = (ScriptFilter) condition.getValue(); + Assert.assertEquals(sf.getScript(), "'a' == 1"); + } + + @Test + public void caseWhenTest() throws SqlParseException { + String query = + "Select k,\n" + + "Case \n" + + "When floor(testBase)>=90 then 'A'\n" + + "When testBase = '80' then 'B'\n" + + "Else 'E' end as testBaseLevel\n" + + "from t"; + SQLExpr sqlExpr = queryToExpr(query); + Select select = parser.parseSelect((SQLQueryExpr) sqlExpr); + for (Field field : select.getFields()) { + if (field instanceof MethodField) { + MethodField methodField = (MethodField) field; String alias = (String) methodField.getParams().get(0).value; String scriptCode = (String) methodField.getParams().get(1).value; - Assert.assertEquals("cast_age",alias); - Assert.assertTrue(scriptCode.contains("doc['age'].value")); - Assert.assertTrue(scriptCode.contains("Double.parseDouble(doc['age'].value.toString()).intValue()")); - } + Assert.assertEquals(alias, "testBaseLevel"); + Matcher docValue = Pattern.compile("doc\\['testBase'].value").matcher(scriptCode); + Matcher number = Pattern.compile(" (\\s+90) | (\\s+'80')").matcher(scriptCode); - @Test - public void castToLongTest() throws Exception { - String query = "select cast(insert_time as long) from "+ TestsConstants.TEST_INDEX_ACCOUNT + " limit 10"; - SQLExpr sqlExpr = queryToExpr(query); - Select select = parser.parseSelect((SQLQueryExpr) sqlExpr); - Field castField = select.getFields().get(0); - Assert.assertTrue(castField instanceof MethodField); + AtomicInteger docValueCounter = new AtomicInteger(); - MethodField methodField = (MethodField) castField; - Assert.assertEquals("script",castField.getName()); - - String alias = (String) methodField.getParams().get(0).value; - String scriptCode = (String) methodField.getParams().get(1).value; - Assert.assertEquals("cast_insert_time",alias); - Assert.assertTrue(scriptCode.contains("doc['insert_time'].value")); - Assert.assertTrue(scriptCode.contains("Double.parseDouble(doc['insert_time'].value.toString()).longValue()")); - } - - @Test - public void castToFloatTest() throws Exception { - String query = "select cast(age as float) from "+ TestsConstants.TEST_INDEX_ACCOUNT + " limit 10"; - SQLExpr sqlExpr = queryToExpr(query); - Select select = parser.parseSelect((SQLQueryExpr) sqlExpr); - Field castField = select.getFields().get(0); - Assert.assertTrue(castField instanceof MethodField); - - MethodField methodField = (MethodField) castField; - Assert.assertEquals("script",castField.getName()); - - String alias = (String) methodField.getParams().get(0).value; - String scriptCode = (String) methodField.getParams().get(1).value; - Assert.assertEquals("cast_age",alias); - Assert.assertTrue(scriptCode.contains("doc['age'].value")); - Assert.assertTrue(scriptCode.contains("Double.parseDouble(doc['age'].value.toString()).floatValue()")); - } - - @Test - public void castToDoubleTest() throws Exception { - String query = "select cast(age as double) from "+ TestsConstants.TEST_INDEX_ACCOUNT + "/account limit 10"; - SQLExpr sqlExpr = queryToExpr(query); - Select select = parser.parseSelect((SQLQueryExpr) sqlExpr); - Field castField = select.getFields().get(0); - Assert.assertTrue(castField instanceof MethodField); - - MethodField methodField = (MethodField) castField; - Assert.assertEquals("script",castField.getName()); + while (docValue.find()) { + docValueCounter.incrementAndGet(); + } + Assert.assertThat(docValueCounter.get(), equalTo(2)); + Assert.assertThat(number.groupCount(), equalTo(2)); + } + } + } + + @Test + public void caseWhenTestWithFieldElseExpr() throws SqlParseException { + String query = + "Select k,\n" + + "Case \n" + + "When floor(testBase)>=90 then 'A'\n" + + "When testBase = '80' then 'B'\n" + + "Else testBase end as testBaseLevel\n" + + "from t"; + SQLExpr sqlExpr = queryToExpr(query); + Select select = parser.parseSelect((SQLQueryExpr) sqlExpr); + for (Field field : select.getFields()) { + if (field instanceof MethodField) { + MethodField methodField = (MethodField) field; String alias = (String) methodField.getParams().get(0).value; String scriptCode = (String) methodField.getParams().get(1).value; - Assert.assertEquals("cast_age",alias); - Assert.assertTrue(scriptCode.contains("doc['age'].value")); - Assert.assertTrue(scriptCode.contains("Double.parseDouble(doc['age'].value.toString()).doubleValue()")); - } - - @Test - public void castToStringTest() throws Exception { - String query = "select cast(age as string) from "+ TestsConstants.TEST_INDEX_ACCOUNT + "/account limit 10"; - SQLExpr sqlExpr = queryToExpr(query); - Select select = parser.parseSelect((SQLQueryExpr) sqlExpr); - Field castField = select.getFields().get(0); - Assert.assertTrue(castField instanceof MethodField); - - MethodField methodField = (MethodField) castField; - Assert.assertEquals("script",castField.getName()); + Assert.assertEquals(alias, "testBaseLevel"); + Matcher docValue = Pattern.compile("doc\\['testBase'].value").matcher(scriptCode); + Matcher number = Pattern.compile(" (\\s+90) | (\\s+'80')").matcher(scriptCode); - String alias = (String) methodField.getParams().get(0).value; - String scriptCode = (String) methodField.getParams().get(1).value; - Assert.assertEquals("cast_age",alias); - Assert.assertTrue(scriptCode.contains("doc['age'].value.toString()")); - } + AtomicInteger docValueCounter = new AtomicInteger(); - @Test - public void castToDateTimeTest() throws Exception { - String query = "select cast(age as datetime) from "+ TestsConstants.TEST_INDEX_ACCOUNT + "/account limit 10"; - SQLExpr sqlExpr = queryToExpr(query); - Select select = parser.parseSelect((SQLQueryExpr) sqlExpr); - Field castField = select.getFields().get(0); - Assert.assertTrue(castField instanceof MethodField); - - MethodField methodField = (MethodField) castField; - Assert.assertEquals("script",castField.getName()); + while (docValue.find()) { + docValueCounter.incrementAndGet(); + } + Assert.assertThat(docValueCounter.get(), equalTo(3)); + Assert.assertThat(number.groupCount(), equalTo(2)); + } + } + } + + @Test + public void caseWhenTestWithouhtElseExpr() throws SqlParseException { + String query = + "Select k,\n" + + "Case \n" + + "When floor(testBase)>=90 then 'A'\n" + + "When testBase = '80' then 'B'\n" + + "end as testBaseLevel\n" + + "from t"; + SQLExpr sqlExpr = queryToExpr(query); + Select select = parser.parseSelect((SQLQueryExpr) sqlExpr); + for (Field field : select.getFields()) { + if (field instanceof MethodField) { + MethodField methodField = (MethodField) field; String alias = (String) methodField.getParams().get(0).value; String scriptCode = (String) methodField.getParams().get(1).value; - Assert.assertEquals("cast_age",alias); - Assert.assertTrue(scriptCode.contains("doc['age'].value")); - Assert.assertTrue(scriptCode.contains("DateTimeFormatter.ofPattern(\"yyyy-MM-dd'T'HH:mm:ss.SSS'Z'\").format(" - + "DateTimeFormatter.ISO_DATE_TIME.parse(doc['age'].value.toString()))")); - } + Assert.assertEquals(alias, "testBaseLevel"); - @Test - public void castToDoubleThenDivideTest() throws Exception { - String query = "select cast(age as double)/2 from "+ TestsConstants.TEST_INDEX_ACCOUNT + "/account limit 10"; - SQLExpr sqlExpr = queryToExpr(query); - Select select = parser.parseSelect((SQLQueryExpr) sqlExpr); - Field castField = select.getFields().get(0); - Assert.assertTrue(castField instanceof MethodField); - - MethodField methodField = (MethodField) castField; - Assert.assertEquals("script",castField.getName()); - - String scriptCode = (String) methodField.getParams().get(1).value; - Assert.assertTrue(scriptCode.contains("doc['age'].value")); - Assert.assertTrue(scriptCode.contains("Double.parseDouble(doc['age'].value.toString()).doubleValue()")); - Assert.assertTrue(scriptCode.contains("/ 2")); - } + Matcher docValue = Pattern.compile("\\{\\s+null\\s+}").matcher(scriptCode); + AtomicInteger docValueCounter = new AtomicInteger(); - @Test - public void multiSelectMinusOperationCheckIndices() throws SqlParseException { - String query = "select pk from firstIndex minus select pk from secondIndex "; - MultiQuerySelect select = parser.parseMultiSelect((SQLUnionQuery) - ((SQLQueryExpr) queryToExpr(query)).getSubQuery().getQuery()); - Assert.assertEquals("firstIndex",select.getFirstSelect().getFrom().get(0).getIndex()); - Assert.assertEquals("secondIndex",select.getSecondSelect().getFrom().get(0).getIndex()); - Assert.assertEquals(SQLUnionOperator.MINUS,select.getOperation()); - } - - @Test - public void multiSelectMinusWithAliasCheckAliases() throws SqlParseException { - String query = "select pk as myId from firstIndex minus select myId from secondIndex "; - MultiQuerySelect select = parser.parseMultiSelect((com.alibaba.druid.sql.ast.statement.SQLUnionQuery) - ((SQLQueryExpr) queryToExpr(query)).getSubQuery().getQuery()); - Assert.assertEquals("myId",select.getFirstSelect().getFields().get(0).getAlias()); - Assert.assertEquals("myId",select.getSecondSelect().getFields().get(0).getName()); - Assert.assertEquals(SQLUnionOperator.MINUS,select.getOperation()); - } - @Test - public void multiSelectMinusTestMinusHints() throws SqlParseException { - String query = "select /*! MINUS_SCROLL_FETCH_AND_RESULT_LIMITS(1000,50,100)*/ " + - "/*! MINUS_USE_TERMS_OPTIMIZATION(true)*/ pk from firstIndex minus select pk from secondIndex "; - MultiQuerySelect select = parser.parseMultiSelect((SQLUnionQuery) - ((SQLQueryExpr) queryToExpr(query)).getSubQuery().getQuery()); - List hints = select.getFirstSelect().getHints(); - Assert.assertEquals(2,hints.size()); - for(Hint hint : hints) { - if (hint.getType() == HintType.MINUS_FETCH_AND_RESULT_LIMITS) { - Object[] params = hint.getParams(); - Assert.assertEquals(1000,params[0]); - Assert.assertEquals(50,params[1]); - Assert.assertEquals(100,params[2]); - } - if(hint.getType() == HintType.MINUS_USE_TERMS_OPTIMIZATION){ - Assert.assertEquals(true,hint.getParams()[0]); - } + while (docValue.find()) { + docValueCounter.incrementAndGet(); } - } - @Test - public void multiSelectMinusScrollCheckDefaultsAllDefaults() throws SqlParseException { - String query = "select /*! MINUS_SCROLL_FETCH_AND_RESULT_LIMITS*/ pk from firstIndex " + - "minus select pk from secondIndex "; - MultiQuerySelect select = parser.parseMultiSelect((com.alibaba.druid.sql.ast.statement.SQLUnionQuery) + Assert.assertThat(docValueCounter.get(), equalTo(1)); + } + } + } + + @Test + public void caseWhenSwitchTest() { + String query = + "SELECT CASE weather " + + "WHEN 'Sunny' THEN '0' " + + "WHEN 'Rainy' THEN '1' " + + "ELSE 'NA' END AS case " + + "FROM t"; + ScriptField scriptField = CheckScriptContents.getScriptFieldFromQuery(query); + Assert.assertTrue( + CheckScriptContents.scriptContainsString(scriptField, "doc['weather'].value=='Sunny'")); + } + + @Test + public void castToIntTest() throws Exception { + String query = + "select cast(age as int) from " + TestsConstants.TEST_INDEX_ACCOUNT + "/account limit 10"; + SQLExpr sqlExpr = queryToExpr(query); + Select select = parser.parseSelect((SQLQueryExpr) sqlExpr); + Field castField = select.getFields().get(0); + Assert.assertTrue(castField instanceof MethodField); + + MethodField methodField = (MethodField) castField; + Assert.assertEquals("script", castField.getName()); + + String alias = (String) methodField.getParams().get(0).value; + String scriptCode = (String) methodField.getParams().get(1).value; + Assert.assertEquals("cast_age", alias); + Assert.assertTrue(scriptCode.contains("doc['age'].value")); + Assert.assertTrue( + scriptCode.contains("Double.parseDouble(doc['age'].value.toString()).intValue()")); + } + + @Test + public void castToLongTest() throws Exception { + String query = + "select cast(insert_time as long) from " + TestsConstants.TEST_INDEX_ACCOUNT + " limit 10"; + SQLExpr sqlExpr = queryToExpr(query); + Select select = parser.parseSelect((SQLQueryExpr) sqlExpr); + Field castField = select.getFields().get(0); + Assert.assertTrue(castField instanceof MethodField); + + MethodField methodField = (MethodField) castField; + Assert.assertEquals("script", castField.getName()); + + String alias = (String) methodField.getParams().get(0).value; + String scriptCode = (String) methodField.getParams().get(1).value; + Assert.assertEquals("cast_insert_time", alias); + Assert.assertTrue(scriptCode.contains("doc['insert_time'].value")); + Assert.assertTrue( + scriptCode.contains("Double.parseDouble(doc['insert_time'].value.toString()).longValue()")); + } + + @Test + public void castToFloatTest() throws Exception { + String query = + "select cast(age as float) from " + TestsConstants.TEST_INDEX_ACCOUNT + " limit 10"; + SQLExpr sqlExpr = queryToExpr(query); + Select select = parser.parseSelect((SQLQueryExpr) sqlExpr); + Field castField = select.getFields().get(0); + Assert.assertTrue(castField instanceof MethodField); + + MethodField methodField = (MethodField) castField; + Assert.assertEquals("script", castField.getName()); + + String alias = (String) methodField.getParams().get(0).value; + String scriptCode = (String) methodField.getParams().get(1).value; + Assert.assertEquals("cast_age", alias); + Assert.assertTrue(scriptCode.contains("doc['age'].value")); + Assert.assertTrue( + scriptCode.contains("Double.parseDouble(doc['age'].value.toString()).floatValue()")); + } + + @Test + public void castToDoubleTest() throws Exception { + String query = + "select cast(age as double) from " + + TestsConstants.TEST_INDEX_ACCOUNT + + "/account limit 10"; + SQLExpr sqlExpr = queryToExpr(query); + Select select = parser.parseSelect((SQLQueryExpr) sqlExpr); + Field castField = select.getFields().get(0); + Assert.assertTrue(castField instanceof MethodField); + + MethodField methodField = (MethodField) castField; + Assert.assertEquals("script", castField.getName()); + + String alias = (String) methodField.getParams().get(0).value; + String scriptCode = (String) methodField.getParams().get(1).value; + Assert.assertEquals("cast_age", alias); + Assert.assertTrue(scriptCode.contains("doc['age'].value")); + Assert.assertTrue( + scriptCode.contains("Double.parseDouble(doc['age'].value.toString()).doubleValue()")); + } + + @Test + public void castToStringTest() throws Exception { + String query = + "select cast(age as string) from " + + TestsConstants.TEST_INDEX_ACCOUNT + + "/account limit 10"; + SQLExpr sqlExpr = queryToExpr(query); + Select select = parser.parseSelect((SQLQueryExpr) sqlExpr); + Field castField = select.getFields().get(0); + Assert.assertTrue(castField instanceof MethodField); + + MethodField methodField = (MethodField) castField; + Assert.assertEquals("script", castField.getName()); + + String alias = (String) methodField.getParams().get(0).value; + String scriptCode = (String) methodField.getParams().get(1).value; + Assert.assertEquals("cast_age", alias); + Assert.assertTrue(scriptCode.contains("doc['age'].value.toString()")); + } + + @Test + public void castToDateTimeTest() throws Exception { + String query = + "select cast(age as datetime) from " + + TestsConstants.TEST_INDEX_ACCOUNT + + "/account limit 10"; + SQLExpr sqlExpr = queryToExpr(query); + Select select = parser.parseSelect((SQLQueryExpr) sqlExpr); + Field castField = select.getFields().get(0); + Assert.assertTrue(castField instanceof MethodField); + + MethodField methodField = (MethodField) castField; + Assert.assertEquals("script", castField.getName()); + + String alias = (String) methodField.getParams().get(0).value; + String scriptCode = (String) methodField.getParams().get(1).value; + Assert.assertEquals("cast_age", alias); + Assert.assertTrue(scriptCode.contains("doc['age'].value")); + Assert.assertTrue( + scriptCode.contains( + "DateTimeFormatter.ofPattern(\"yyyy-MM-dd'T'HH:mm:ss.SSS'Z'\").format(" + + "DateTimeFormatter.ISO_DATE_TIME.parse(doc['age'].value.toString()))")); + } + + @Test + public void castToDoubleThenDivideTest() throws Exception { + String query = + "select cast(age as double)/2 from " + + TestsConstants.TEST_INDEX_ACCOUNT + + "/account limit 10"; + SQLExpr sqlExpr = queryToExpr(query); + Select select = parser.parseSelect((SQLQueryExpr) sqlExpr); + Field castField = select.getFields().get(0); + Assert.assertTrue(castField instanceof MethodField); + + MethodField methodField = (MethodField) castField; + Assert.assertEquals("script", castField.getName()); + + String scriptCode = (String) methodField.getParams().get(1).value; + Assert.assertTrue(scriptCode.contains("doc['age'].value")); + Assert.assertTrue( + scriptCode.contains("Double.parseDouble(doc['age'].value.toString()).doubleValue()")); + Assert.assertTrue(scriptCode.contains("/ 2")); + } + + @Test + public void multiSelectMinusOperationCheckIndices() throws SqlParseException { + String query = "select pk from firstIndex minus select pk from secondIndex "; + MultiQuerySelect select = + parser.parseMultiSelect( + (SQLUnionQuery) ((SQLQueryExpr) queryToExpr(query)).getSubQuery().getQuery()); + Assert.assertEquals("firstIndex", select.getFirstSelect().getFrom().get(0).getIndex()); + Assert.assertEquals("secondIndex", select.getSecondSelect().getFrom().get(0).getIndex()); + Assert.assertEquals(SQLUnionOperator.MINUS, select.getOperation()); + } + + @Test + public void multiSelectMinusWithAliasCheckAliases() throws SqlParseException { + String query = "select pk as myId from firstIndex minus select myId from secondIndex "; + MultiQuerySelect select = + parser.parseMultiSelect( + (com.alibaba.druid.sql.ast.statement.SQLUnionQuery) ((SQLQueryExpr) queryToExpr(query)).getSubQuery().getQuery()); - List hints = select.getFirstSelect().getHints(); - Assert.assertEquals(1, hints.size()); - Hint hint = hints.get(0); - Assert.assertEquals(HintType.MINUS_FETCH_AND_RESULT_LIMITS,hint.getType()); + Assert.assertEquals("myId", select.getFirstSelect().getFields().get(0).getAlias()); + Assert.assertEquals("myId", select.getSecondSelect().getFields().get(0).getName()); + Assert.assertEquals(SQLUnionOperator.MINUS, select.getOperation()); + } + + @Test + public void multiSelectMinusTestMinusHints() throws SqlParseException { + String query = + "select /*! MINUS_SCROLL_FETCH_AND_RESULT_LIMITS(1000,50,100)*/ /*!" + + " MINUS_USE_TERMS_OPTIMIZATION(true)*/ pk from firstIndex minus select pk from" + + " secondIndex "; + MultiQuerySelect select = + parser.parseMultiSelect( + (SQLUnionQuery) ((SQLQueryExpr) queryToExpr(query)).getSubQuery().getQuery()); + List hints = select.getFirstSelect().getHints(); + Assert.assertEquals(2, hints.size()); + for (Hint hint : hints) { + if (hint.getType() == HintType.MINUS_FETCH_AND_RESULT_LIMITS) { Object[] params = hint.getParams(); - Assert.assertEquals(100000, params[0]); - Assert.assertEquals(100000, params[1]); - Assert.assertEquals(1000, params[2]); - } - - @Test - public void multiSelectMinusScrollCheckDefaultsOneDefault() throws SqlParseException { - String query = "select /*! MINUS_SCROLL_FETCH_AND_RESULT_LIMITS(50,100)*/ pk " + - "from firstIndex minus select pk from secondIndex "; - MultiQuerySelect select = parser.parseMultiSelect((com.alibaba.druid.sql.ast.statement.SQLUnionQuery) + Assert.assertEquals(1000, params[0]); + Assert.assertEquals(50, params[1]); + Assert.assertEquals(100, params[2]); + } + if (hint.getType() == HintType.MINUS_USE_TERMS_OPTIMIZATION) { + Assert.assertEquals(true, hint.getParams()[0]); + } + } + } + + @Test + public void multiSelectMinusScrollCheckDefaultsAllDefaults() throws SqlParseException { + String query = + "select /*! MINUS_SCROLL_FETCH_AND_RESULT_LIMITS*/ pk from firstIndex " + + "minus select pk from secondIndex "; + MultiQuerySelect select = + parser.parseMultiSelect( + (com.alibaba.druid.sql.ast.statement.SQLUnionQuery) ((SQLQueryExpr) queryToExpr(query)).getSubQuery().getQuery()); - List hints = select.getFirstSelect().getHints(); - Assert.assertEquals(1, hints.size()); - Hint hint = hints.get(0); - Assert.assertEquals(HintType.MINUS_FETCH_AND_RESULT_LIMITS,hint.getType()); - Object[] params = hint.getParams(); - Assert.assertEquals(50, params[0]); - Assert.assertEquals(100, params[1]); - Assert.assertEquals(1000, params[2]); - } - - private SQLExpr queryToExpr(String query) { - return new ElasticSqlExprParser(query).expr(); - } - - private boolean conditionExist(List conditions, String from, String to, Condition.OPERATOR OPERATOR) { - String[] aliasAndField = to.split("\\.", 2); - String toAlias = aliasAndField[0]; - String toField = aliasAndField[1]; - for (Condition condition : conditions) { - if (condition.getOPERATOR() != OPERATOR) continue; - - boolean fromIsEqual = condition.getName().equals(from); - if (!fromIsEqual) continue; - - String[] valueAliasAndField = condition.getValue().toString().split("\\.", 2); - boolean toFieldNameIsEqual = valueAliasAndField[1].equals(toField); - boolean toAliasIsEqual = valueAliasAndField[0].equals(toAlias); - boolean toIsEqual = toAliasIsEqual && toFieldNameIsEqual; - - if (toIsEqual) return true; - } - return false; - } + List hints = select.getFirstSelect().getHints(); + Assert.assertEquals(1, hints.size()); + Hint hint = hints.get(0); + Assert.assertEquals(HintType.MINUS_FETCH_AND_RESULT_LIMITS, hint.getType()); + Object[] params = hint.getParams(); + Assert.assertEquals(100000, params[0]); + Assert.assertEquals(100000, params[1]); + Assert.assertEquals(1000, params[2]); + } + + @Test + public void multiSelectMinusScrollCheckDefaultsOneDefault() throws SqlParseException { + String query = + "select /*! MINUS_SCROLL_FETCH_AND_RESULT_LIMITS(50,100)*/ pk " + + "from firstIndex minus select pk from secondIndex "; + MultiQuerySelect select = + parser.parseMultiSelect( + (com.alibaba.druid.sql.ast.statement.SQLUnionQuery) + ((SQLQueryExpr) queryToExpr(query)).getSubQuery().getQuery()); + List hints = select.getFirstSelect().getHints(); + Assert.assertEquals(1, hints.size()); + Hint hint = hints.get(0); + Assert.assertEquals(HintType.MINUS_FETCH_AND_RESULT_LIMITS, hint.getType()); + Object[] params = hint.getParams(); + Assert.assertEquals(50, params[0]); + Assert.assertEquals(100, params[1]); + Assert.assertEquals(1000, params[2]); + } + + private SQLExpr queryToExpr(String query) { + return new ElasticSqlExprParser(query).expr(); + } + + private boolean conditionExist( + List conditions, String from, String to, Condition.OPERATOR OPERATOR) { + String[] aliasAndField = to.split("\\.", 2); + String toAlias = aliasAndField[0]; + String toField = aliasAndField[1]; + for (Condition condition : conditions) { + if (condition.getOPERATOR() != OPERATOR) continue; + + boolean fromIsEqual = condition.getName().equals(from); + if (!fromIsEqual) continue; + + String[] valueAliasAndField = condition.getValue().toString().split("\\.", 2); + boolean toFieldNameIsEqual = valueAliasAndField[1].equals(toField); + boolean toAliasIsEqual = valueAliasAndField[0].equals(toAlias); + boolean toIsEqual = toAliasIsEqual && toFieldNameIsEqual; + + if (toIsEqual) return true; + } + return false; + } } diff --git a/legacy/src/test/java/org/opensearch/sql/legacy/unittest/parser/SubQueryParserTest.java b/legacy/src/test/java/org/opensearch/sql/legacy/unittest/parser/SubQueryParserTest.java index ac614affdb..5713179b46 100644 --- a/legacy/src/test/java/org/opensearch/sql/legacy/unittest/parser/SubQueryParserTest.java +++ b/legacy/src/test/java/org/opensearch/sql/legacy/unittest/parser/SubQueryParserTest.java @@ -3,7 +3,6 @@ * SPDX-License-Identifier: Apache-2.0 */ - package org.opensearch.sql.legacy.unittest.parser; import static org.junit.Assert.assertEquals; @@ -20,127 +19,135 @@ public class SubQueryParserTest { - private static SqlParser parser = new SqlParser(); - - @Test - public void selectFromSubqueryShouldPass() throws SqlParseException { - Select select = parseSelect( - StringUtils.format( - "SELECT t.T1 as age1, t.T2 as balance1 " + - "FROM (SELECT age as T1, balance as T2 FROM %s/account) t", - TEST_INDEX_ACCOUNT)); - - assertEquals(2, select.getFields().size()); - assertEquals("age", select.getFields().get(0).getName()); - assertEquals("age1", select.getFields().get(0).getAlias()); - assertEquals("balance", select.getFields().get(1).getName()); - assertEquals("balance1", select.getFields().get(1).getAlias()); - } - - @Test - public void selectFromSubqueryWithoutAliasShouldPass() throws SqlParseException { - Select select = parseSelect( - StringUtils.format( - "SELECT t.age as finalAge, t.balance as finalBalance " + - "FROM (SELECT age, balance FROM %s/account) t", - TEST_INDEX_ACCOUNT)); - - assertEquals(2, select.getFields().size()); - assertEquals("age", select.getFields().get(0).getName()); - assertEquals("finalAge", select.getFields().get(0).getAlias()); - assertEquals("balance", select.getFields().get(1).getName()); - assertEquals("finalBalance", select.getFields().get(1).getAlias()); - } - - @Test - public void selectFromSubqueryShouldIgnoreUnusedField() throws SqlParseException { - Select select = parseSelect( - StringUtils.format( - "SELECT t.T1 as age1 " + - "FROM (SELECT age as T1, balance as T2 FROM %s/account) t", - TEST_INDEX_ACCOUNT)); - - assertEquals(1, select.getFields().size()); - assertEquals("age", select.getFields().get(0).getName()); - assertEquals("age1", select.getFields().get(0).getAlias()); - } - - @Test - public void selectFromSubqueryWithAggShouldPass() throws SqlParseException { - Select select = parseSelect( - StringUtils.format( - "SELECT t.TEMP as count " + - "FROM (SELECT COUNT(*) as TEMP FROM %s/account) t", - TEST_INDEX_ACCOUNT)); - assertEquals(1, select.getFields().size()); - assertEquals("COUNT", select.getFields().get(0).getName()); - assertEquals("count", select.getFields().get(0).getAlias()); - } - - @Test - public void selectFromSubqueryWithWhereAndCountShouldPass() throws SqlParseException { - Select select = parseSelect( - StringUtils.format( - "SELECT t.TEMP as count " + - "FROM (SELECT COUNT(*) as TEMP FROM %s/account WHERE age > 30) t", - TEST_INDEX_ACCOUNT)); - - assertEquals(1, select.getFields().size()); - assertEquals("COUNT", select.getFields().get(0).getName()); - assertEquals("count", select.getFields().get(0).getAlias()); - } - - @Test - public void selectFromSubqueryWithCountAndGroupByAndOrderByShouldPass() throws SqlParseException { - Select select = parseSelect( - StringUtils.format( - "SELECT t.TEMP as count " + - "FROM (SELECT COUNT(*) as TEMP FROM %s/account GROUP BY age ORDER BY TEMP) t", - TEST_INDEX_ACCOUNT)); - - assertEquals(1, select.getFields().size()); - assertEquals("COUNT", select.getFields().get(0).getName()); - assertEquals("count", select.getFields().get(0).getAlias()); - assertEquals(1, select.getOrderBys().size()); - assertEquals("count", select.getOrderBys().get(0).getName()); - assertEquals("count", select.getOrderBys().get(0).getSortField().getName()); - } - - @Test - public void selectFromSubqueryWithCountAndGroupByAndHavingShouldPass() throws Exception { - - Select select = parseSelect( - StringUtils.format("SELECT t.T1 as g, t.T2 as c " + - "FROM (SELECT gender as T1, COUNT(*) as T2 " + - " FROM %s/account " + - " GROUP BY gender " + - " HAVING T2 > 500) t", TEST_INDEX_ACCOUNT)); - - assertEquals(2, select.getFields().size()); - assertEquals("gender", select.getFields().get(0).getName()); - assertEquals("g", select.getFields().get(0).getAlias()); - assertEquals("COUNT", select.getFields().get(1).getName()); - assertEquals("c", select.getFields().get(1).getAlias()); - assertEquals(1, select.getHaving().getConditions().size()); - assertEquals("c", ((Condition) select.getHaving().getConditions().get(0)).getName()); - } - - @Test - public void selectFromSubqueryCountAndSum() throws Exception { - Select select = parseSelect( - StringUtils.format( - "SELECT t.TEMP1 as count, t.TEMP2 as balance " + - "FROM (SELECT COUNT(*) as TEMP1, SUM(balance) as TEMP2 " + - " FROM %s/account) t", - TEST_INDEX_ACCOUNT)); - assertEquals(2, select.getFields().size()); - assertEquals("COUNT", select.getFields().get(0).getName()); - assertEquals("count", select.getFields().get(0).getAlias()); - assertEquals("SUM", select.getFields().get(1).getName()); - assertEquals("balance", select.getFields().get(1).getAlias()); - } - - private Select parseSelect(String query) throws SqlParseException { - return parser.parseSelect((SQLQueryExpr) new ElasticSqlExprParser(query).expr()); - } + private static SqlParser parser = new SqlParser(); + + @Test + public void selectFromSubqueryShouldPass() throws SqlParseException { + Select select = + parseSelect( + StringUtils.format( + "SELECT t.T1 as age1, t.T2 as balance1 " + + "FROM (SELECT age as T1, balance as T2 FROM %s/account) t", + TEST_INDEX_ACCOUNT)); + + assertEquals(2, select.getFields().size()); + assertEquals("age", select.getFields().get(0).getName()); + assertEquals("age1", select.getFields().get(0).getAlias()); + assertEquals("balance", select.getFields().get(1).getName()); + assertEquals("balance1", select.getFields().get(1).getAlias()); + } + + @Test + public void selectFromSubqueryWithoutAliasShouldPass() throws SqlParseException { + Select select = + parseSelect( + StringUtils.format( + "SELECT t.age as finalAge, t.balance as finalBalance " + + "FROM (SELECT age, balance FROM %s/account) t", + TEST_INDEX_ACCOUNT)); + + assertEquals(2, select.getFields().size()); + assertEquals("age", select.getFields().get(0).getName()); + assertEquals("finalAge", select.getFields().get(0).getAlias()); + assertEquals("balance", select.getFields().get(1).getName()); + assertEquals("finalBalance", select.getFields().get(1).getAlias()); + } + + @Test + public void selectFromSubqueryShouldIgnoreUnusedField() throws SqlParseException { + Select select = + parseSelect( + StringUtils.format( + "SELECT t.T1 as age1 " + "FROM (SELECT age as T1, balance as T2 FROM %s/account) t", + TEST_INDEX_ACCOUNT)); + + assertEquals(1, select.getFields().size()); + assertEquals("age", select.getFields().get(0).getName()); + assertEquals("age1", select.getFields().get(0).getAlias()); + } + + @Test + public void selectFromSubqueryWithAggShouldPass() throws SqlParseException { + Select select = + parseSelect( + StringUtils.format( + "SELECT t.TEMP as count FROM (SELECT COUNT(*) as TEMP FROM %s/account) t", + TEST_INDEX_ACCOUNT)); + assertEquals(1, select.getFields().size()); + assertEquals("COUNT", select.getFields().get(0).getName()); + assertEquals("count", select.getFields().get(0).getAlias()); + } + + @Test + public void selectFromSubqueryWithWhereAndCountShouldPass() throws SqlParseException { + Select select = + parseSelect( + StringUtils.format( + "SELECT t.TEMP as count " + + "FROM (SELECT COUNT(*) as TEMP FROM %s/account WHERE age > 30) t", + TEST_INDEX_ACCOUNT)); + + assertEquals(1, select.getFields().size()); + assertEquals("COUNT", select.getFields().get(0).getName()); + assertEquals("count", select.getFields().get(0).getAlias()); + } + + @Test + public void selectFromSubqueryWithCountAndGroupByAndOrderByShouldPass() throws SqlParseException { + Select select = + parseSelect( + StringUtils.format( + "SELECT t.TEMP as count " + + "FROM (SELECT COUNT(*) as TEMP FROM %s/account GROUP BY age ORDER BY TEMP) t", + TEST_INDEX_ACCOUNT)); + + assertEquals(1, select.getFields().size()); + assertEquals("COUNT", select.getFields().get(0).getName()); + assertEquals("count", select.getFields().get(0).getAlias()); + assertEquals(1, select.getOrderBys().size()); + assertEquals("count", select.getOrderBys().get(0).getName()); + assertEquals("count", select.getOrderBys().get(0).getSortField().getName()); + } + + @Test + public void selectFromSubqueryWithCountAndGroupByAndHavingShouldPass() throws Exception { + + Select select = + parseSelect( + StringUtils.format( + "SELECT t.T1 as g, t.T2 as c " + + "FROM (SELECT gender as T1, COUNT(*) as T2 " + + " FROM %s/account " + + " GROUP BY gender " + + " HAVING T2 > 500) t", + TEST_INDEX_ACCOUNT)); + + assertEquals(2, select.getFields().size()); + assertEquals("gender", select.getFields().get(0).getName()); + assertEquals("g", select.getFields().get(0).getAlias()); + assertEquals("COUNT", select.getFields().get(1).getName()); + assertEquals("c", select.getFields().get(1).getAlias()); + assertEquals(1, select.getHaving().getConditions().size()); + assertEquals("c", ((Condition) select.getHaving().getConditions().get(0)).getName()); + } + + @Test + public void selectFromSubqueryCountAndSum() throws Exception { + Select select = + parseSelect( + StringUtils.format( + "SELECT t.TEMP1 as count, t.TEMP2 as balance " + + "FROM (SELECT COUNT(*) as TEMP1, SUM(balance) as TEMP2 " + + " FROM %s/account) t", + TEST_INDEX_ACCOUNT)); + assertEquals(2, select.getFields().size()); + assertEquals("COUNT", select.getFields().get(0).getName()); + assertEquals("count", select.getFields().get(0).getAlias()); + assertEquals("SUM", select.getFields().get(1).getName()); + assertEquals("balance", select.getFields().get(1).getAlias()); + } + + private Select parseSelect(String query) throws SqlParseException { + return parser.parseSelect((SQLQueryExpr) new ElasticSqlExprParser(query).expr()); + } } diff --git a/legacy/src/test/java/org/opensearch/sql/legacy/unittest/planner/converter/SQLAggregationParserTest.java b/legacy/src/test/java/org/opensearch/sql/legacy/unittest/planner/converter/SQLAggregationParserTest.java index bdf3c64fd8..855ed9e346 100644 --- a/legacy/src/test/java/org/opensearch/sql/legacy/unittest/planner/converter/SQLAggregationParserTest.java +++ b/legacy/src/test/java/org/opensearch/sql/legacy/unittest/planner/converter/SQLAggregationParserTest.java @@ -3,7 +3,6 @@ * SPDX-License-Identifier: Apache-2.0 */ - package org.opensearch.sql.legacy.unittest.planner.converter; import static org.hamcrest.MatcherAssert.assertThat; @@ -36,321 +35,367 @@ @RunWith(MockitoJUnitRunner.class) public class SQLAggregationParserTest { - @Rule - public ExpectedException exceptionRule = ExpectedException.none(); - - @Test - public void parseAggWithoutExpressionShouldPass() { - String sql = "SELECT dayOfWeek, max(FlightDelayMin), MIN(FlightDelayMin) as min " + - "FROM opensearch_dashboards_sample_data_flights " + - "GROUP BY dayOfWeek"; - SQLAggregationParser parser = new SQLAggregationParser(new ColumnTypeProvider()); - parser.parse(mYSqlSelectQueryBlock(sql)); - List sqlSelectItems = parser.selectItemList(); - List columnNodes = parser.getColumnNodes(); - - assertThat(sqlSelectItems, containsInAnyOrder(group("dayOfWeek", "dayOfWeek"), - agg("MAX", "FlightDelayMin", "MAX_0"), - agg("MIN", "FlightDelayMin", "min"))); - - assertThat(columnNodes, containsInAnyOrder(columnNode("dayOfWeek", null, ExpressionFactory.ref("dayOfWeek")), - columnNode("MAX(FlightDelayMin)", null, ExpressionFactory - .ref("MAX_0")), - columnNode("min", "min", ExpressionFactory.ref("min")))); - } - - @Test - public void parseAggWithFunctioniWithoutExpressionShouldPass() { - String sql = "SELECT dayOfWeek, max(FlightDelayMin), MIN(FlightDelayMin) as min " + - "FROM opensearch_dashboards_sample_data_flights " + - "GROUP BY dayOfWeek"; - SQLAggregationParser parser = new SQLAggregationParser(new ColumnTypeProvider()); - parser.parse(mYSqlSelectQueryBlock(sql)); - List sqlSelectItems = parser.selectItemList(); - List columnNodes = parser.getColumnNodes(); - - assertThat(sqlSelectItems, containsInAnyOrder(group("dayOfWeek", "dayOfWeek"), - agg("MAX", "FlightDelayMin", "MAX_0"), - agg("MIN", "FlightDelayMin", "min"))); - - assertThat(columnNodes, containsInAnyOrder(columnNode("dayOfWeek", null, ExpressionFactory.ref("dayOfWeek")), - columnNode("MAX(FlightDelayMin)", null, ExpressionFactory - .ref("MAX_0")), - columnNode("min", "min", ExpressionFactory.ref("min")))); - } - - @Test - public void parseAggWithExpressionShouldPass() { - String sql = "SELECT dayOfWeek, max(FlightDelayMin) + MIN(FlightDelayMin) as sub " + - "FROM opensearch_dashboards_sample_data_flights " + - "GROUP BY dayOfWeek"; - SQLAggregationParser parser = new SQLAggregationParser(new ColumnTypeProvider()); - parser.parse(mYSqlSelectQueryBlock(sql)); - List sqlSelectItems = parser.selectItemList(); - List columnNodes = parser.getColumnNodes(); - - assertThat(sqlSelectItems, containsInAnyOrder(group("dayOfWeek", "dayOfWeek"), - agg("MAX", "FlightDelayMin", "MAX_0"), - agg("MIN", "FlightDelayMin", "MIN_1"))); - - assertThat(columnNodes, containsInAnyOrder(columnNode("dayOfWeek", null, ExpressionFactory.ref("dayOfWeek")), - columnNode("sub", "sub", add(ExpressionFactory.ref("MAX_0"), ExpressionFactory - .ref("MIN_1"))))); - } - - @Test - public void parseWithRawSelectFuncnameShouldPass() { - String sql = "SELECT LOG(FlightDelayMin) " + - "FROM opensearch_dashboards_sample_data_flights " + - "GROUP BY log(FlightDelayMin)"; - SQLAggregationParser parser = new SQLAggregationParser(new ColumnTypeProvider()); - parser.parse(mYSqlSelectQueryBlock(sql)); - List sqlSelectItems = parser.selectItemList(); - List columnNodes = parser.getColumnNodes(); - - assertThat(sqlSelectItems, containsInAnyOrder(group("log(FlightDelayMin)", "log(FlightDelayMin)"))); - - assertThat( - columnNodes, - containsInAnyOrder( - columnNode( - "LOG(FlightDelayMin)", - null, - ExpressionFactory.ref("log(FlightDelayMin)") - ) - ) - ); - } - - @Test - public void functionOverFiledShouldPass() { - String sql = "SELECT dayOfWeek, max(FlightDelayMin) + MIN(FlightDelayMin) as sub " + - "FROM opensearch_dashboards_sample_data_flights " + - "GROUP BY dayOfWeek"; - SQLAggregationParser parser = new SQLAggregationParser(new ColumnTypeProvider()); - parser.parse(mYSqlSelectQueryBlock(sql)); - List sqlSelectItems = parser.selectItemList(); - List columnNodes = parser.getColumnNodes(); - - assertThat(sqlSelectItems, containsInAnyOrder(group("dayOfWeek", "dayOfWeek"), - agg("MAX", "FlightDelayMin", "MAX_0"), - agg("MIN", "FlightDelayMin", "MIN_1"))); - - assertThat(columnNodes, containsInAnyOrder(columnNode("dayOfWeek", null, ExpressionFactory.ref("dayOfWeek")), - columnNode("sub", "sub", add(ExpressionFactory.ref("MAX_0"), ExpressionFactory - .ref("MIN_1"))))); - } - - @Test - public void parseCompoundAggWithExpressionShouldPass() { - String sql = "SELECT ASCII(dayOfWeek), log(max(FlightDelayMin) + MIN(FlightDelayMin)) as log " + - "FROM opensearch_dashboards_sample_data_flights " + - "GROUP BY ASCII(dayOfWeek)"; - SQLAggregationParser parser = new SQLAggregationParser(new ColumnTypeProvider()); - parser.parse(mYSqlSelectQueryBlock(sql)); - List sqlSelectItems = parser.selectItemList(); - List columnNodes = parser.getColumnNodes(); - - assertThat(sqlSelectItems, containsInAnyOrder(group("ASCII(dayOfWeek)", "ASCII(dayOfWeek)"), - agg("MAX", "FlightDelayMin", "MAX_0"), - agg("MIN", "FlightDelayMin", "MIN_1"))); - - assertThat(columnNodes, containsInAnyOrder(columnNode("ASCII(dayOfWeek)", null, ExpressionFactory - .ref("ASCII(dayOfWeek)")), - columnNode("log", "log", log(add(ExpressionFactory.ref("MAX_0"), ExpressionFactory - .ref("MIN_1")))))); - } - - @Test - public void parseSingleFunctionOverAggShouldPass() { - String sql = "SELECT log(max(age)) FROM accounts"; - SQLAggregationParser parser = new SQLAggregationParser(new ColumnTypeProvider()); - parser.parse(mYSqlSelectQueryBlock(sql)); - List sqlSelectItems = parser.selectItemList(); - List columnNodes = parser.getColumnNodes(); - - assertThat(sqlSelectItems, containsInAnyOrder(agg("max", "age", "max_0"))); - assertThat(columnNodes, containsInAnyOrder(columnNode("log(max(age))", null, log( - ExpressionFactory.ref("max_0"))))); - } - - @Test - public void parseFunctionGroupColumnOverShouldPass() { - String sql = "SELECT CAST(balance AS FLOAT) FROM accounts GROUP BY balance"; - SQLAggregationParser parser = new SQLAggregationParser(new ColumnTypeProvider()); - parser.parse(mYSqlSelectQueryBlock(sql)); - List sqlSelectItems = parser.selectItemList(); - List columnNodes = parser.getColumnNodes(); - - assertThat(sqlSelectItems, containsInAnyOrder(group("balance", "balance"))); - assertThat(columnNodes, containsInAnyOrder(columnNode("CAST(balance AS FLOAT)", null, cast( - ExpressionFactory.ref("balance"))))); - } - - @Test - public void withoutAggregationShouldPass() { - String sql = "SELECT age, gender FROM accounts GROUP BY age, gender"; - SQLAggregationParser parser = new SQLAggregationParser(new ColumnTypeProvider()); - parser.parse(mYSqlSelectQueryBlock(sql)); - List sqlSelectItems = parser.selectItemList(); - List columnNodes = parser.getColumnNodes(); - - assertThat(sqlSelectItems, containsInAnyOrder( - group("age", "age"), - group("gender", "gender"))); - assertThat(columnNodes, containsInAnyOrder( - columnNode("age", null, ExpressionFactory.ref("age")), - columnNode("gender", null, ExpressionFactory.ref("gender")))); - } - - @Test - public void groupKeyInSelectWithFunctionShouldPass() { - String sql = "SELECT log(age), max(balance) FROM accounts GROUP BY age"; - SQLAggregationParser parser = new SQLAggregationParser(new ColumnTypeProvider()); - parser.parse(mYSqlSelectQueryBlock(sql)); - List sqlSelectItems = parser.selectItemList(); - List columnNodes = parser.getColumnNodes(); - - assertThat(sqlSelectItems, containsInAnyOrder( - group("age", "age"), - agg("max", "balance", "max_0"))); - assertThat(columnNodes, containsInAnyOrder( - columnNode("log(age)", null, log(ExpressionFactory.ref("age"))), - columnNode("max(balance)", null, ExpressionFactory.ref("max_0")))); - } - - @Test - public void theDotInFieldNameShouldBeReplaceWithSharp() { - String sql = "SELECT name.lastname, max(balance) FROM accounts GROUP BY name.lastname"; - SQLAggregationParser parser = new SQLAggregationParser(new ColumnTypeProvider()); - parser.parse(mYSqlSelectQueryBlock(sql)); - List sqlSelectItems = parser.selectItemList(); - List columnNodes = parser.getColumnNodes(); - - assertThat(sqlSelectItems, containsInAnyOrder( - group("name.lastname", "name#lastname"), - agg("max", "balance", "max_0"))); - assertThat(columnNodes, containsInAnyOrder( - columnNode("name.lastname", null, ExpressionFactory.ref("name#lastname")), - columnNode("max(balance)", null, ExpressionFactory.ref("max_0")))); - } - - @Test - public void noGroupKeyInSelectShouldPass() { - String sql = "SELECT AVG(age) FROM t GROUP BY age"; - SQLAggregationParser parser = new SQLAggregationParser(new ColumnTypeProvider()); - parser.parse(mYSqlSelectQueryBlock(sql)); - List sqlSelectItems = parser.selectItemList(); - List columnNodes = parser.getColumnNodes(); - - assertThat(sqlSelectItems, containsInAnyOrder( - agg("avg", "age", "avg_0"))); - assertThat(columnNodes, containsInAnyOrder( - columnNode("avg(age)", null, ExpressionFactory.ref("avg_0")))); - } - - @Test - public void aggWithDistinctShouldPass() { - String sql = "SELECT count(distinct gender) FROM t GROUP BY age"; - SQLAggregationParser parser = new SQLAggregationParser(new ColumnTypeProvider()); - parser.parse(mYSqlSelectQueryBlock(sql)); - List sqlSelectItems = parser.selectItemList(); - List columnNodes = parser.getColumnNodes(); - - assertThat(sqlSelectItems, containsInAnyOrder( - agg("count", "gender", "count_0"))); - assertThat(columnNodes, containsInAnyOrder( - columnNode("count(distinct gender)", null, ExpressionFactory.ref("count_0")))); - } - - /** - * TermQueryExplainIT.testNestedSingleGroupBy - */ - @Test - public void aggregationWithNestedShouldThrowException() { - exceptionRule.expect(RuntimeException.class); - exceptionRule.expectMessage("unsupported operator: nested"); - - String sql = "SELECT nested(projects.name, 'projects'),id " - + "FROM t " - + "GROUP BY nested(projects.name.keyword, 'projects')"; - SQLAggregationParser parser = new SQLAggregationParser(new ColumnTypeProvider()); - parser.parse(mYSqlSelectQueryBlock(sql)); - } - - private MySqlSelectQueryBlock mYSqlSelectQueryBlock(String sql) { - String dbType = JdbcConstants.MYSQL; - SQLQueryExpr sqlQueryExpr = (SQLQueryExpr) SQLUtils.toSQLExpr(sql, dbType); - return ((MySqlSelectQueryBlock) sqlQueryExpr.getSubQuery().getQuery()); - } - - private TypeSafeMatcher columnNode(String name, String alias, Expression expr) { - return new TypeSafeMatcher() { - @Override - public void describeTo(Description description) { - description.appendText(String.format("(name=%s,alias=%s,expression=%s)", name, alias, expr)); - } - - @Override - protected boolean matchesSafely(ColumnNode item) { - if (name == null) { - return false; - } - if (alias == null && item.getAlias() != null) { - return false; - } - - return name.equalsIgnoreCase(item.getName()) && - ((alias == null && item.getAlias() == null) || alias.equals(item.getAlias())) && - expr.toString().equalsIgnoreCase(item.getExpr().toString()); - } - }; - } - - private TypeSafeMatcher agg(String methodName, String name, String alias) { - return new TypeSafeMatcher() { - @Override - public void describeTo(Description description) { - description.appendText(String.format("(methodName=%s, name=%s, alias=%s)", methodName, name, alias)); - } - - @Override - protected boolean matchesSafely(SQLSelectItem item) { - if (item.getExpr() instanceof SQLAggregateExpr) { - return ((SQLAggregateExpr) item.getExpr()).getMethodName().equalsIgnoreCase(methodName) && - ((SQLAggregateExpr) item.getExpr()).getArguments() - .get(0) - .toString() - .equalsIgnoreCase(name) && - ((item.getAlias() == null && alias == null) || item.getAlias().equalsIgnoreCase(alias)); - } else { - return false; - } - } - }; - } - - private TypeSafeMatcher group(String name, String alias) { - return new TypeSafeMatcher() { - @Override - public void describeTo(Description description) { - description.appendText(String.format("(name=%s, alias=%s)", name, alias)); - } - - @Override - protected boolean matchesSafely(SQLSelectItem item) { - boolean b = item.getExpr().toString().equalsIgnoreCase(name) && - ((item.getAlias() == null && alias == null) || item.getAlias().equalsIgnoreCase(alias)); - return b; - } - }; - } - - private Expression add(Expression... expressions) { - return of(ADD, Arrays.asList(expressions)); - } - - private Expression log(Expression... expressions) { - return of(LOG, Arrays.asList(expressions)); - } + @Rule public ExpectedException exceptionRule = ExpectedException.none(); + + @Test + public void parseAggWithoutExpressionShouldPass() { + String sql = + "SELECT dayOfWeek, max(FlightDelayMin), MIN(FlightDelayMin) as min " + + "FROM opensearch_dashboards_sample_data_flights " + + "GROUP BY dayOfWeek"; + SQLAggregationParser parser = new SQLAggregationParser(new ColumnTypeProvider()); + parser.parse(mYSqlSelectQueryBlock(sql)); + List sqlSelectItems = parser.selectItemList(); + List columnNodes = parser.getColumnNodes(); + + assertThat( + sqlSelectItems, + containsInAnyOrder( + group("dayOfWeek", "dayOfWeek"), + agg("MAX", "FlightDelayMin", "MAX_0"), + agg("MIN", "FlightDelayMin", "min"))); + + assertThat( + columnNodes, + containsInAnyOrder( + columnNode("dayOfWeek", null, ExpressionFactory.ref("dayOfWeek")), + columnNode("MAX(FlightDelayMin)", null, ExpressionFactory.ref("MAX_0")), + columnNode("min", "min", ExpressionFactory.ref("min")))); + } + + @Test + public void parseAggWithFunctioniWithoutExpressionShouldPass() { + String sql = + "SELECT dayOfWeek, max(FlightDelayMin), MIN(FlightDelayMin) as min " + + "FROM opensearch_dashboards_sample_data_flights " + + "GROUP BY dayOfWeek"; + SQLAggregationParser parser = new SQLAggregationParser(new ColumnTypeProvider()); + parser.parse(mYSqlSelectQueryBlock(sql)); + List sqlSelectItems = parser.selectItemList(); + List columnNodes = parser.getColumnNodes(); + + assertThat( + sqlSelectItems, + containsInAnyOrder( + group("dayOfWeek", "dayOfWeek"), + agg("MAX", "FlightDelayMin", "MAX_0"), + agg("MIN", "FlightDelayMin", "min"))); + + assertThat( + columnNodes, + containsInAnyOrder( + columnNode("dayOfWeek", null, ExpressionFactory.ref("dayOfWeek")), + columnNode("MAX(FlightDelayMin)", null, ExpressionFactory.ref("MAX_0")), + columnNode("min", "min", ExpressionFactory.ref("min")))); + } + + @Test + public void parseAggWithExpressionShouldPass() { + String sql = + "SELECT dayOfWeek, max(FlightDelayMin) + MIN(FlightDelayMin) as sub " + + "FROM opensearch_dashboards_sample_data_flights " + + "GROUP BY dayOfWeek"; + SQLAggregationParser parser = new SQLAggregationParser(new ColumnTypeProvider()); + parser.parse(mYSqlSelectQueryBlock(sql)); + List sqlSelectItems = parser.selectItemList(); + List columnNodes = parser.getColumnNodes(); + + assertThat( + sqlSelectItems, + containsInAnyOrder( + group("dayOfWeek", "dayOfWeek"), + agg("MAX", "FlightDelayMin", "MAX_0"), + agg("MIN", "FlightDelayMin", "MIN_1"))); + + assertThat( + columnNodes, + containsInAnyOrder( + columnNode("dayOfWeek", null, ExpressionFactory.ref("dayOfWeek")), + columnNode( + "sub", + "sub", + add(ExpressionFactory.ref("MAX_0"), ExpressionFactory.ref("MIN_1"))))); + } + + @Test + public void parseWithRawSelectFuncnameShouldPass() { + String sql = + "SELECT LOG(FlightDelayMin) " + + "FROM opensearch_dashboards_sample_data_flights " + + "GROUP BY log(FlightDelayMin)"; + SQLAggregationParser parser = new SQLAggregationParser(new ColumnTypeProvider()); + parser.parse(mYSqlSelectQueryBlock(sql)); + List sqlSelectItems = parser.selectItemList(); + List columnNodes = parser.getColumnNodes(); + + assertThat( + sqlSelectItems, containsInAnyOrder(group("log(FlightDelayMin)", "log(FlightDelayMin)"))); + + assertThat( + columnNodes, + containsInAnyOrder( + columnNode("LOG(FlightDelayMin)", null, ExpressionFactory.ref("log(FlightDelayMin)")))); + } + + @Test + public void functionOverFiledShouldPass() { + String sql = + "SELECT dayOfWeek, max(FlightDelayMin) + MIN(FlightDelayMin) as sub " + + "FROM opensearch_dashboards_sample_data_flights " + + "GROUP BY dayOfWeek"; + SQLAggregationParser parser = new SQLAggregationParser(new ColumnTypeProvider()); + parser.parse(mYSqlSelectQueryBlock(sql)); + List sqlSelectItems = parser.selectItemList(); + List columnNodes = parser.getColumnNodes(); + + assertThat( + sqlSelectItems, + containsInAnyOrder( + group("dayOfWeek", "dayOfWeek"), + agg("MAX", "FlightDelayMin", "MAX_0"), + agg("MIN", "FlightDelayMin", "MIN_1"))); + + assertThat( + columnNodes, + containsInAnyOrder( + columnNode("dayOfWeek", null, ExpressionFactory.ref("dayOfWeek")), + columnNode( + "sub", + "sub", + add(ExpressionFactory.ref("MAX_0"), ExpressionFactory.ref("MIN_1"))))); + } + + @Test + public void parseCompoundAggWithExpressionShouldPass() { + String sql = + "SELECT ASCII(dayOfWeek), log(max(FlightDelayMin) + MIN(FlightDelayMin)) as log " + + "FROM opensearch_dashboards_sample_data_flights " + + "GROUP BY ASCII(dayOfWeek)"; + SQLAggregationParser parser = new SQLAggregationParser(new ColumnTypeProvider()); + parser.parse(mYSqlSelectQueryBlock(sql)); + List sqlSelectItems = parser.selectItemList(); + List columnNodes = parser.getColumnNodes(); + + assertThat( + sqlSelectItems, + containsInAnyOrder( + group("ASCII(dayOfWeek)", "ASCII(dayOfWeek)"), + agg("MAX", "FlightDelayMin", "MAX_0"), + agg("MIN", "FlightDelayMin", "MIN_1"))); + + assertThat( + columnNodes, + containsInAnyOrder( + columnNode("ASCII(dayOfWeek)", null, ExpressionFactory.ref("ASCII(dayOfWeek)")), + columnNode( + "log", + "log", + log(add(ExpressionFactory.ref("MAX_0"), ExpressionFactory.ref("MIN_1")))))); + } + + @Test + public void parseSingleFunctionOverAggShouldPass() { + String sql = "SELECT log(max(age)) FROM accounts"; + SQLAggregationParser parser = new SQLAggregationParser(new ColumnTypeProvider()); + parser.parse(mYSqlSelectQueryBlock(sql)); + List sqlSelectItems = parser.selectItemList(); + List columnNodes = parser.getColumnNodes(); + + assertThat(sqlSelectItems, containsInAnyOrder(agg("max", "age", "max_0"))); + assertThat( + columnNodes, + containsInAnyOrder(columnNode("log(max(age))", null, log(ExpressionFactory.ref("max_0"))))); + } + + @Test + public void parseFunctionGroupColumnOverShouldPass() { + String sql = "SELECT CAST(balance AS FLOAT) FROM accounts GROUP BY balance"; + SQLAggregationParser parser = new SQLAggregationParser(new ColumnTypeProvider()); + parser.parse(mYSqlSelectQueryBlock(sql)); + List sqlSelectItems = parser.selectItemList(); + List columnNodes = parser.getColumnNodes(); + + assertThat(sqlSelectItems, containsInAnyOrder(group("balance", "balance"))); + assertThat( + columnNodes, + containsInAnyOrder( + columnNode("CAST(balance AS FLOAT)", null, cast(ExpressionFactory.ref("balance"))))); + } + + @Test + public void withoutAggregationShouldPass() { + String sql = "SELECT age, gender FROM accounts GROUP BY age, gender"; + SQLAggregationParser parser = new SQLAggregationParser(new ColumnTypeProvider()); + parser.parse(mYSqlSelectQueryBlock(sql)); + List sqlSelectItems = parser.selectItemList(); + List columnNodes = parser.getColumnNodes(); + + assertThat(sqlSelectItems, containsInAnyOrder(group("age", "age"), group("gender", "gender"))); + assertThat( + columnNodes, + containsInAnyOrder( + columnNode("age", null, ExpressionFactory.ref("age")), + columnNode("gender", null, ExpressionFactory.ref("gender")))); + } + + @Test + public void groupKeyInSelectWithFunctionShouldPass() { + String sql = "SELECT log(age), max(balance) FROM accounts GROUP BY age"; + SQLAggregationParser parser = new SQLAggregationParser(new ColumnTypeProvider()); + parser.parse(mYSqlSelectQueryBlock(sql)); + List sqlSelectItems = parser.selectItemList(); + List columnNodes = parser.getColumnNodes(); + + assertThat( + sqlSelectItems, containsInAnyOrder(group("age", "age"), agg("max", "balance", "max_0"))); + assertThat( + columnNodes, + containsInAnyOrder( + columnNode("log(age)", null, log(ExpressionFactory.ref("age"))), + columnNode("max(balance)", null, ExpressionFactory.ref("max_0")))); + } + + @Test + public void theDotInFieldNameShouldBeReplaceWithSharp() { + String sql = "SELECT name.lastname, max(balance) FROM accounts GROUP BY name.lastname"; + SQLAggregationParser parser = new SQLAggregationParser(new ColumnTypeProvider()); + parser.parse(mYSqlSelectQueryBlock(sql)); + List sqlSelectItems = parser.selectItemList(); + List columnNodes = parser.getColumnNodes(); + + assertThat( + sqlSelectItems, + containsInAnyOrder( + group("name.lastname", "name#lastname"), agg("max", "balance", "max_0"))); + assertThat( + columnNodes, + containsInAnyOrder( + columnNode("name.lastname", null, ExpressionFactory.ref("name#lastname")), + columnNode("max(balance)", null, ExpressionFactory.ref("max_0")))); + } + + @Test + public void noGroupKeyInSelectShouldPass() { + String sql = "SELECT AVG(age) FROM t GROUP BY age"; + SQLAggregationParser parser = new SQLAggregationParser(new ColumnTypeProvider()); + parser.parse(mYSqlSelectQueryBlock(sql)); + List sqlSelectItems = parser.selectItemList(); + List columnNodes = parser.getColumnNodes(); + + assertThat(sqlSelectItems, containsInAnyOrder(agg("avg", "age", "avg_0"))); + assertThat( + columnNodes, + containsInAnyOrder(columnNode("avg(age)", null, ExpressionFactory.ref("avg_0")))); + } + + @Test + public void aggWithDistinctShouldPass() { + String sql = "SELECT count(distinct gender) FROM t GROUP BY age"; + SQLAggregationParser parser = new SQLAggregationParser(new ColumnTypeProvider()); + parser.parse(mYSqlSelectQueryBlock(sql)); + List sqlSelectItems = parser.selectItemList(); + List columnNodes = parser.getColumnNodes(); + + assertThat(sqlSelectItems, containsInAnyOrder(agg("count", "gender", "count_0"))); + assertThat( + columnNodes, + containsInAnyOrder( + columnNode("count(distinct gender)", null, ExpressionFactory.ref("count_0")))); + } + + /** TermQueryExplainIT.testNestedSingleGroupBy */ + @Test + public void aggregationWithNestedShouldThrowException() { + exceptionRule.expect(RuntimeException.class); + exceptionRule.expectMessage("unsupported operator: nested"); + + String sql = + "SELECT nested(projects.name, 'projects'),id " + + "FROM t " + + "GROUP BY nested(projects.name.keyword, 'projects')"; + SQLAggregationParser parser = new SQLAggregationParser(new ColumnTypeProvider()); + parser.parse(mYSqlSelectQueryBlock(sql)); + } + + private MySqlSelectQueryBlock mYSqlSelectQueryBlock(String sql) { + String dbType = JdbcConstants.MYSQL; + SQLQueryExpr sqlQueryExpr = (SQLQueryExpr) SQLUtils.toSQLExpr(sql, dbType); + return ((MySqlSelectQueryBlock) sqlQueryExpr.getSubQuery().getQuery()); + } + + private TypeSafeMatcher columnNode(String name, String alias, Expression expr) { + return new TypeSafeMatcher() { + @Override + public void describeTo(Description description) { + description.appendText( + String.format("(name=%s,alias=%s,expression=%s)", name, alias, expr)); + } + + @Override + protected boolean matchesSafely(ColumnNode item) { + if (name == null) { + return false; + } + if (alias == null && item.getAlias() != null) { + return false; + } + + return name.equalsIgnoreCase(item.getName()) + && ((alias == null && item.getAlias() == null) || alias.equals(item.getAlias())) + && expr.toString().equalsIgnoreCase(item.getExpr().toString()); + } + }; + } + + private TypeSafeMatcher agg(String methodName, String name, String alias) { + return new TypeSafeMatcher() { + @Override + public void describeTo(Description description) { + description.appendText( + String.format("(methodName=%s, name=%s, alias=%s)", methodName, name, alias)); + } + + @Override + protected boolean matchesSafely(SQLSelectItem item) { + if (item.getExpr() instanceof SQLAggregateExpr) { + return ((SQLAggregateExpr) item.getExpr()).getMethodName().equalsIgnoreCase(methodName) + && ((SQLAggregateExpr) item.getExpr()) + .getArguments() + .get(0) + .toString() + .equalsIgnoreCase(name) + && ((item.getAlias() == null && alias == null) + || item.getAlias().equalsIgnoreCase(alias)); + } else { + return false; + } + } + }; + } + + private TypeSafeMatcher group(String name, String alias) { + return new TypeSafeMatcher() { + @Override + public void describeTo(Description description) { + description.appendText(String.format("(name=%s, alias=%s)", name, alias)); + } + + @Override + protected boolean matchesSafely(SQLSelectItem item) { + boolean b = + item.getExpr().toString().equalsIgnoreCase(name) + && ((item.getAlias() == null && alias == null) + || item.getAlias().equalsIgnoreCase(alias)); + return b; + } + }; + } + + private Expression add(Expression... expressions) { + return of(ADD, Arrays.asList(expressions)); + } + + private Expression log(Expression... expressions) { + return of(LOG, Arrays.asList(expressions)); + } } diff --git a/legacy/src/test/java/org/opensearch/sql/legacy/unittest/planner/converter/SQLExprToExpressionConverterTest.java b/legacy/src/test/java/org/opensearch/sql/legacy/unittest/planner/converter/SQLExprToExpressionConverterTest.java index e297c2c1d4..ac949eb0d7 100644 --- a/legacy/src/test/java/org/opensearch/sql/legacy/unittest/planner/converter/SQLExprToExpressionConverterTest.java +++ b/legacy/src/test/java/org/opensearch/sql/legacy/unittest/planner/converter/SQLExprToExpressionConverterTest.java @@ -3,7 +3,6 @@ * SPDX-License-Identifier: Apache-2.0 */ - package org.opensearch.sql.legacy.unittest.planner.converter; import static org.junit.Assert.assertEquals; @@ -34,118 +33,125 @@ @RunWith(MockitoJUnitRunner.class) public class SQLExprToExpressionConverterTest { - @Rule - public ExpectedException exceptionRule = ExpectedException.none(); - - private SQLExprToExpressionConverter converter; - private SQLAggregationParser.Context context; - private final SQLAggregateExpr maxA = new SQLAggregateExpr("MAX"); - private final SQLAggregateExpr maxB = new SQLAggregateExpr("MAX"); - private final SQLAggregateExpr minA = new SQLAggregateExpr("MIN"); - private final SQLIdentifierExpr groupG = new SQLIdentifierExpr("A"); - private final SQLIdentifierExpr aggA = new SQLIdentifierExpr("A"); - private final SQLIdentifierExpr aggB = new SQLIdentifierExpr("B"); - private final SQLIntegerExpr one = new SQLIntegerExpr(1); - - @Before - public void setup() { - maxA.getArguments().add(aggA); - maxB.getArguments().add(aggB); - minA.getArguments().add(aggA); - context = new SQLAggregationParser.Context(ImmutableMap.of()); - converter = new SQLExprToExpressionConverter(context); - } - - @Test - public void identifierShouldReturnVarExpression() { - context.addGroupKeyExpr(groupG); - Expression expression = converter.convert(groupG); - - assertEquals(ref("A").toString(), expression.toString()); - } - - @Test - public void binaryOperatorAddShouldReturnAddExpression() { - context.addAggregationExpr(maxA); - context.addAggregationExpr(minA); - - Expression expression = converter.convert(new SQLBinaryOpExpr(maxA, SQLBinaryOperator.Add, minA)); - assertEquals(add(ref("MAX_0"), ref("MIN_1")).toString(), expression.toString()); - } - - @Test - public void compoundBinaryOperatorShouldReturnCorrectExpression() { - context.addAggregationExpr(maxA); - context.addAggregationExpr(minA); - - Expression expression = converter.convert(new SQLBinaryOpExpr(maxA, SQLBinaryOperator.Add, - new SQLBinaryOpExpr(maxA, SQLBinaryOperator.Add, - minA))); - assertEquals(add(ref("MAX_0"), add(ref("MAX_0"), ref("MIN_1"))).toString(), expression.toString()); - } - - @Test - public void functionOverCompoundBinaryOperatorShouldReturnCorrectExpression() { - context.addAggregationExpr(maxA); - context.addAggregationExpr(minA); - - SQLMethodInvokeExpr methodInvokeExpr = new SQLMethodInvokeExpr("LOG"); - methodInvokeExpr.addParameter(new SQLBinaryOpExpr(maxA, SQLBinaryOperator.Add, - new SQLBinaryOpExpr(maxA, SQLBinaryOperator.Add, - minA))); - - Expression expression = converter.convert(methodInvokeExpr); - assertEquals(log(add(ref("MAX_0"), add(ref("MAX_0"), ref("MIN_1")))).toString(), expression.toString()); - } - - @Test - public void functionOverGroupColumn() { - context.addAggregationExpr(maxA); - context.addAggregationExpr(minA); - - SQLMethodInvokeExpr methodInvokeExpr = new SQLMethodInvokeExpr("LOG"); - methodInvokeExpr.addParameter(new SQLBinaryOpExpr(maxA, SQLBinaryOperator.Add, - new SQLBinaryOpExpr(maxA, SQLBinaryOperator.Add, - minA))); - - Expression expression = converter.convert(methodInvokeExpr); - assertEquals(log(add(ref("MAX_0"), add(ref("MAX_0"), ref("MIN_1")))).toString(), expression.toString()); - } - - @Test - public void binaryOperatorWithLiteralAddShouldReturnAddExpression() { - context.addAggregationExpr(maxA); - - Expression expression = converter.convert(new SQLBinaryOpExpr(maxA, SQLBinaryOperator.Add, one)); - assertEquals(add(ref("MAX_0"), literal(integerValue(1))).toString(), expression.toString()); - } - - @Test - public void unknownIdentifierShouldThrowException() { - context.addAggregationExpr(maxA); - context.addAggregationExpr(minA); - - exceptionRule.expect(RuntimeException.class); - exceptionRule.expectMessage("unsupported expr"); - converter.convert(new SQLBinaryOpExpr(maxA, SQLBinaryOperator.Add, maxB)); - } - - @Test - public void unsupportOperationShouldThrowException() { - exceptionRule.expect(UnsupportedOperationException.class); - exceptionRule.expectMessage("unsupported operator: cot"); - - context.addAggregationExpr(maxA); - SQLMethodInvokeExpr methodInvokeExpr = new SQLMethodInvokeExpr("cot"); - methodInvokeExpr.addParameter(maxA); - converter.convert(methodInvokeExpr); - } - - private Expression add(Expression... expressions) { - return of(ADD, Arrays.asList(expressions)); - } - - private Expression log(Expression... expressions) { - return of(LOG, Arrays.asList(expressions)); - } + @Rule public ExpectedException exceptionRule = ExpectedException.none(); + + private SQLExprToExpressionConverter converter; + private SQLAggregationParser.Context context; + private final SQLAggregateExpr maxA = new SQLAggregateExpr("MAX"); + private final SQLAggregateExpr maxB = new SQLAggregateExpr("MAX"); + private final SQLAggregateExpr minA = new SQLAggregateExpr("MIN"); + private final SQLIdentifierExpr groupG = new SQLIdentifierExpr("A"); + private final SQLIdentifierExpr aggA = new SQLIdentifierExpr("A"); + private final SQLIdentifierExpr aggB = new SQLIdentifierExpr("B"); + private final SQLIntegerExpr one = new SQLIntegerExpr(1); + + @Before + public void setup() { + maxA.getArguments().add(aggA); + maxB.getArguments().add(aggB); + minA.getArguments().add(aggA); + context = new SQLAggregationParser.Context(ImmutableMap.of()); + converter = new SQLExprToExpressionConverter(context); + } + + @Test + public void identifierShouldReturnVarExpression() { + context.addGroupKeyExpr(groupG); + Expression expression = converter.convert(groupG); + + assertEquals(ref("A").toString(), expression.toString()); + } + + @Test + public void binaryOperatorAddShouldReturnAddExpression() { + context.addAggregationExpr(maxA); + context.addAggregationExpr(minA); + + Expression expression = + converter.convert(new SQLBinaryOpExpr(maxA, SQLBinaryOperator.Add, minA)); + assertEquals(add(ref("MAX_0"), ref("MIN_1")).toString(), expression.toString()); + } + + @Test + public void compoundBinaryOperatorShouldReturnCorrectExpression() { + context.addAggregationExpr(maxA); + context.addAggregationExpr(minA); + + Expression expression = + converter.convert( + new SQLBinaryOpExpr( + maxA, + SQLBinaryOperator.Add, + new SQLBinaryOpExpr(maxA, SQLBinaryOperator.Add, minA))); + assertEquals( + add(ref("MAX_0"), add(ref("MAX_0"), ref("MIN_1"))).toString(), expression.toString()); + } + + @Test + public void functionOverCompoundBinaryOperatorShouldReturnCorrectExpression() { + context.addAggregationExpr(maxA); + context.addAggregationExpr(minA); + + SQLMethodInvokeExpr methodInvokeExpr = new SQLMethodInvokeExpr("LOG"); + methodInvokeExpr.addParameter( + new SQLBinaryOpExpr( + maxA, SQLBinaryOperator.Add, new SQLBinaryOpExpr(maxA, SQLBinaryOperator.Add, minA))); + + Expression expression = converter.convert(methodInvokeExpr); + assertEquals( + log(add(ref("MAX_0"), add(ref("MAX_0"), ref("MIN_1")))).toString(), expression.toString()); + } + + @Test + public void functionOverGroupColumn() { + context.addAggregationExpr(maxA); + context.addAggregationExpr(minA); + + SQLMethodInvokeExpr methodInvokeExpr = new SQLMethodInvokeExpr("LOG"); + methodInvokeExpr.addParameter( + new SQLBinaryOpExpr( + maxA, SQLBinaryOperator.Add, new SQLBinaryOpExpr(maxA, SQLBinaryOperator.Add, minA))); + + Expression expression = converter.convert(methodInvokeExpr); + assertEquals( + log(add(ref("MAX_0"), add(ref("MAX_0"), ref("MIN_1")))).toString(), expression.toString()); + } + + @Test + public void binaryOperatorWithLiteralAddShouldReturnAddExpression() { + context.addAggregationExpr(maxA); + + Expression expression = + converter.convert(new SQLBinaryOpExpr(maxA, SQLBinaryOperator.Add, one)); + assertEquals(add(ref("MAX_0"), literal(integerValue(1))).toString(), expression.toString()); + } + + @Test + public void unknownIdentifierShouldThrowException() { + context.addAggregationExpr(maxA); + context.addAggregationExpr(minA); + + exceptionRule.expect(RuntimeException.class); + exceptionRule.expectMessage("unsupported expr"); + converter.convert(new SQLBinaryOpExpr(maxA, SQLBinaryOperator.Add, maxB)); + } + + @Test + public void unsupportOperationShouldThrowException() { + exceptionRule.expect(UnsupportedOperationException.class); + exceptionRule.expectMessage("unsupported operator: cot"); + + context.addAggregationExpr(maxA); + SQLMethodInvokeExpr methodInvokeExpr = new SQLMethodInvokeExpr("cot"); + methodInvokeExpr.addParameter(maxA); + converter.convert(methodInvokeExpr); + } + + private Expression add(Expression... expressions) { + return of(ADD, Arrays.asList(expressions)); + } + + private Expression log(Expression... expressions) { + return of(LOG, Arrays.asList(expressions)); + } } diff --git a/legacy/src/test/java/org/opensearch/sql/legacy/unittest/planner/converter/SQLToOperatorConverterTest.java b/legacy/src/test/java/org/opensearch/sql/legacy/unittest/planner/converter/SQLToOperatorConverterTest.java index f64a550a13..578fb9bcff 100644 --- a/legacy/src/test/java/org/opensearch/sql/legacy/unittest/planner/converter/SQLToOperatorConverterTest.java +++ b/legacy/src/test/java/org/opensearch/sql/legacy/unittest/planner/converter/SQLToOperatorConverterTest.java @@ -3,7 +3,6 @@ * SPDX-License-Identifier: Apache-2.0 */ - package org.opensearch.sql.legacy.unittest.planner.converter; import static org.junit.Assert.assertTrue; @@ -25,51 +24,53 @@ @RunWith(MockitoJUnitRunner.class) public class SQLToOperatorConverterTest { - @Mock - private Client client; + @Mock private Client client; - private SQLToOperatorConverter converter; + private SQLToOperatorConverter converter; - @Before - public void setup() { - converter = new SQLToOperatorConverter(client, new ColumnTypeProvider()); - } + @Before + public void setup() { + converter = new SQLToOperatorConverter(client, new ColumnTypeProvider()); + } - @Test - public void convertAggShouldPass() { - String sql = "SELECT dayOfWeek, max(FlightDelayMin), MIN(FlightDelayMin) as min " + - "FROM opensearch_dashboards_sample_data_flights " + - "GROUP BY dayOfWeek"; - toExpr(sql).accept(converter); - PhysicalOperator physicalOperator = converter.getPhysicalOperator(); + @Test + public void convertAggShouldPass() { + String sql = + "SELECT dayOfWeek, max(FlightDelayMin), MIN(FlightDelayMin) as min " + + "FROM opensearch_dashboards_sample_data_flights " + + "GROUP BY dayOfWeek"; + toExpr(sql).accept(converter); + PhysicalOperator physicalOperator = converter.getPhysicalOperator(); - assertTrue(physicalOperator instanceof PhysicalProject); - } + assertTrue(physicalOperator instanceof PhysicalProject); + } - @Test - public void convertMaxMinusMinShouldPass() { - String sql = "SELECT dayOfWeek, max(FlightDelayMin) - MIN(FlightDelayMin) as diff " + - "FROM opensearch_dashboards_sample_data_flights " + - "GROUP BY dayOfWeek"; - toExpr(sql).accept(converter); - PhysicalOperator physicalOperator = converter.getPhysicalOperator(); + @Test + public void convertMaxMinusMinShouldPass() { + String sql = + "SELECT dayOfWeek, max(FlightDelayMin) - MIN(FlightDelayMin) as diff " + + "FROM opensearch_dashboards_sample_data_flights " + + "GROUP BY dayOfWeek"; + toExpr(sql).accept(converter); + PhysicalOperator physicalOperator = converter.getPhysicalOperator(); - assertTrue(physicalOperator instanceof PhysicalProject); - } + assertTrue(physicalOperator instanceof PhysicalProject); + } - @Test - public void convertDistinctPass() { - String sql = "SELECT dayOfWeek, max(FlightDelayMin) - MIN(FlightDelayMin) as diff " + - "FROM opensearch_dashboards_sample_data_flights " + - "GROUP BY dayOfWeek"; - toExpr(sql).accept(converter); - PhysicalOperator physicalOperator = converter.getPhysicalOperator(); + @Test + public void convertDistinctPass() { + String sql = + "SELECT dayOfWeek, max(FlightDelayMin) - MIN(FlightDelayMin) as diff " + + "FROM opensearch_dashboards_sample_data_flights " + + "GROUP BY dayOfWeek"; + toExpr(sql).accept(converter); + PhysicalOperator physicalOperator = converter.getPhysicalOperator(); - assertTrue(physicalOperator instanceof PhysicalProject); - } + assertTrue(physicalOperator instanceof PhysicalProject); + } - private SQLQueryExpr toExpr(String sql) { - String dbType = JdbcConstants.MYSQL; - return (SQLQueryExpr) SQLUtils.toSQLExpr(sql, dbType); - } + private SQLQueryExpr toExpr(String sql) { + String dbType = JdbcConstants.MYSQL; + return (SQLQueryExpr) SQLUtils.toSQLExpr(sql, dbType); + } } diff --git a/legacy/src/test/java/org/opensearch/sql/legacy/unittest/rewriter/identifier/UnquoteIdentifierRuleTest.java b/legacy/src/test/java/org/opensearch/sql/legacy/unittest/rewriter/identifier/UnquoteIdentifierRuleTest.java index 41f7b111b0..30bbac861a 100644 --- a/legacy/src/test/java/org/opensearch/sql/legacy/unittest/rewriter/identifier/UnquoteIdentifierRuleTest.java +++ b/legacy/src/test/java/org/opensearch/sql/legacy/unittest/rewriter/identifier/UnquoteIdentifierRuleTest.java @@ -3,7 +3,6 @@ * SPDX-License-Identifier: Apache-2.0 */ - package org.opensearch.sql.legacy.unittest.rewriter.identifier; import com.alibaba.druid.sql.SQLUtils; @@ -13,75 +12,70 @@ import org.opensearch.sql.legacy.rewriter.identifier.UnquoteIdentifierRule; import org.opensearch.sql.legacy.util.SqlParserUtils; - -/** - * Test cases for backticks quoted identifiers - */ +/** Test cases for backticks quoted identifiers */ public class UnquoteIdentifierRuleTest { - @Test - public void queryWithQuotedIndex() { - query("SELECT lastname FROM `bank` WHERE balance > 1000 ORDER BY age" - ).shouldBeAfterRewrite("SELECT lastname FROM bank WHERE balance > 1000 ORDER BY age"); - } - - @Test - public void queryWithQuotedField() { - query("SELECT `lastname` FROM bank ORDER BY age" - ).shouldBeAfterRewrite("SELECT lastname FROM bank ORDER BY age"); - - query("SELECT b.`lastname` FROM bank AS b ORDER BY age" - ).shouldBeAfterRewrite("SELECT b.lastname FROM bank AS b ORDER BY age"); - } - - @Test - public void queryWithQuotedAlias() { - query("SELECT `b`.lastname FROM bank AS `b` ORDER BY age" - ).shouldBeAfterRewrite("SELECT b.lastname FROM bank AS b ORDER BY age"); - - query("SELECT `b`.`lastname` FROM bank AS `b` ORDER BY age" - ).shouldBeAfterRewrite("SELECT b.lastname FROM bank AS b ORDER BY age"); - - query("SELECT `b`.`lastname` AS `name` FROM bank AS `b` ORDER BY age" - ).shouldBeAfterRewrite("SELECT b.lastname AS name FROM bank AS b ORDER BY age"); + @Test + public void queryWithQuotedIndex() { + query("SELECT lastname FROM `bank` WHERE balance > 1000 ORDER BY age") + .shouldBeAfterRewrite("SELECT lastname FROM bank WHERE balance > 1000 ORDER BY age"); + } + + @Test + public void queryWithQuotedField() { + query("SELECT `lastname` FROM bank ORDER BY age") + .shouldBeAfterRewrite("SELECT lastname FROM bank ORDER BY age"); + + query("SELECT b.`lastname` FROM bank AS b ORDER BY age") + .shouldBeAfterRewrite("SELECT b.lastname FROM bank AS b ORDER BY age"); + } + + @Test + public void queryWithQuotedAlias() { + query("SELECT `b`.lastname FROM bank AS `b` ORDER BY age") + .shouldBeAfterRewrite("SELECT b.lastname FROM bank AS b ORDER BY age"); + + query("SELECT `b`.`lastname` FROM bank AS `b` ORDER BY age") + .shouldBeAfterRewrite("SELECT b.lastname FROM bank AS b ORDER BY age"); + + query("SELECT `b`.`lastname` AS `name` FROM bank AS `b` ORDER BY age") + .shouldBeAfterRewrite("SELECT b.lastname AS name FROM bank AS b ORDER BY age"); + } + + @Test + public void selectSpecificFieldsUsingQuotedTableNamePrefix() { + query("SELECT `bank`.`lastname` FROM `bank`") + .shouldBeAfterRewrite("SELECT bank.lastname FROM bank"); + } + + @Test + public void queryWithQuotedAggrAndFunc() { + query( + "" + + "SELECT `b`.`lastname` AS `name`, AVG(`b`.`balance`) FROM `bank` AS `b` " + + "WHERE ABS(`b`.`age`) > 20 GROUP BY `b`.`lastname` ORDER BY `b`.`lastname`") + .shouldBeAfterRewrite( + "SELECT b.lastname AS name, AVG(b.balance) FROM bank AS b " + + "WHERE ABS(b.age) > 20 GROUP BY b.lastname ORDER BY b.lastname"); + } + + private QueryAssertion query(String sql) { + return new QueryAssertion(sql); + } + + private static class QueryAssertion { + + private UnquoteIdentifierRule rule = new UnquoteIdentifierRule(); + private SQLQueryExpr expr; + + QueryAssertion(String sql) { + this.expr = SqlParserUtils.parse(sql); } - @Test - public void selectSpecificFieldsUsingQuotedTableNamePrefix() { - query("SELECT `bank`.`lastname` FROM `bank`" - ).shouldBeAfterRewrite("SELECT bank.lastname FROM bank"); - } - - @Test - public void queryWithQuotedAggrAndFunc() { - query("" + - "SELECT `b`.`lastname` AS `name`, AVG(`b`.`balance`) FROM `bank` AS `b` " + - "WHERE ABS(`b`.`age`) > 20 GROUP BY `b`.`lastname` ORDER BY `b`.`lastname`" - ).shouldBeAfterRewrite( - "SELECT b.lastname AS name, AVG(b.balance) FROM bank AS b " + - "WHERE ABS(b.age) > 20 GROUP BY b.lastname ORDER BY b.lastname" - ); - } - - private QueryAssertion query(String sql) { - return new QueryAssertion(sql); - } - - private static class QueryAssertion { - - private UnquoteIdentifierRule rule = new UnquoteIdentifierRule(); - private SQLQueryExpr expr; - - QueryAssertion(String sql) { - this.expr = SqlParserUtils.parse(sql); - } - - void shouldBeAfterRewrite(String expected) { - rule.rewrite(expr); - Assert.assertEquals( - SQLUtils.toMySqlString(SqlParserUtils.parse(expected)), - SQLUtils.toMySqlString(expr) - ); - } + void shouldBeAfterRewrite(String expected) { + rule.rewrite(expr); + Assert.assertEquals( + SQLUtils.toMySqlString(SqlParserUtils.parse(expected)), SQLUtils.toMySqlString(expr)); } + } } diff --git a/legacy/src/test/java/org/opensearch/sql/legacy/unittest/rewriter/parent/SQLExprParentSetterRuleTest.java b/legacy/src/test/java/org/opensearch/sql/legacy/unittest/rewriter/parent/SQLExprParentSetterRuleTest.java index 15d97d362d..0fdf16e40e 100644 --- a/legacy/src/test/java/org/opensearch/sql/legacy/unittest/rewriter/parent/SQLExprParentSetterRuleTest.java +++ b/legacy/src/test/java/org/opensearch/sql/legacy/unittest/rewriter/parent/SQLExprParentSetterRuleTest.java @@ -3,7 +3,6 @@ * SPDX-License-Identifier: Apache-2.0 */ - package org.opensearch.sql.legacy.unittest.rewriter.parent; import static org.junit.Assert.assertTrue; @@ -18,13 +17,12 @@ @RunWith(MockitoJUnitRunner.class) public class SQLExprParentSetterRuleTest { - @Mock - private SQLQueryExpr queryExpr; + @Mock private SQLQueryExpr queryExpr; - private SQLExprParentSetterRule rule = new SQLExprParentSetterRule(); + private SQLExprParentSetterRule rule = new SQLExprParentSetterRule(); - @Test - public void match() { - assertTrue(rule.match(queryExpr)); - } + @Test + public void match() { + assertTrue(rule.match(queryExpr)); + } } diff --git a/legacy/src/test/java/org/opensearch/sql/legacy/unittest/rewriter/parent/SQLExprParentSetterTest.java b/legacy/src/test/java/org/opensearch/sql/legacy/unittest/rewriter/parent/SQLExprParentSetterTest.java index 49023f522a..ccd440228b 100644 --- a/legacy/src/test/java/org/opensearch/sql/legacy/unittest/rewriter/parent/SQLExprParentSetterTest.java +++ b/legacy/src/test/java/org/opensearch/sql/legacy/unittest/rewriter/parent/SQLExprParentSetterTest.java @@ -3,7 +3,6 @@ * SPDX-License-Identifier: Apache-2.0 */ - package org.opensearch.sql.legacy.unittest.rewriter.parent; import static org.junit.Assert.assertNotNull; @@ -17,36 +16,31 @@ public class SQLExprParentSetterTest { - @Test - public void testSQLInSubQueryExprHasParent() { - String sql = - "SELECT * FROM TbA " + - "WHERE a IN (SELECT b FROM TbB)"; - SQLQueryExpr expr = SqlParserUtils.parse(sql); - expr.accept(new SQLExprParentExistsValidator()); - } - - @Test - public void testSQLInListExprHasParent() { - String sql = - "SELECT * FROM TbA " + - "WHERE a IN (10, 20)"; - SQLQueryExpr expr = SqlParserUtils.parse(sql); - expr.accept(new SQLExprParentExistsValidator()); + @Test + public void testSQLInSubQueryExprHasParent() { + String sql = "SELECT * FROM TbA WHERE a IN (SELECT b FROM TbB)"; + SQLQueryExpr expr = SqlParserUtils.parse(sql); + expr.accept(new SQLExprParentExistsValidator()); + } + + @Test + public void testSQLInListExprHasParent() { + String sql = "SELECT * FROM TbA WHERE a IN (10, 20)"; + SQLQueryExpr expr = SqlParserUtils.parse(sql); + expr.accept(new SQLExprParentExistsValidator()); + } + + static class SQLExprParentExistsValidator extends MySqlASTVisitorAdapter { + @Override + public boolean visit(SQLInSubQueryExpr expr) { + assertNotNull(expr.getExpr().getParent()); + return true; } - static class SQLExprParentExistsValidator extends MySqlASTVisitorAdapter { - @Override - public boolean visit(SQLInSubQueryExpr expr) { - assertNotNull(expr.getExpr().getParent()); - return true; - } - - @Override - public boolean visit(SQLInListExpr expr) { - assertNotNull(expr.getExpr().getParent()); - return true; - } + @Override + public boolean visit(SQLInListExpr expr) { + assertNotNull(expr.getExpr().getParent()); + return true; } - + } } diff --git a/legacy/src/test/java/org/opensearch/sql/legacy/unittest/rewriter/subquery/ExistsSubQueryRewriterTest.java b/legacy/src/test/java/org/opensearch/sql/legacy/unittest/rewriter/subquery/ExistsSubQueryRewriterTest.java index ed57335980..dd15fd6683 100644 --- a/legacy/src/test/java/org/opensearch/sql/legacy/unittest/rewriter/subquery/ExistsSubQueryRewriterTest.java +++ b/legacy/src/test/java/org/opensearch/sql/legacy/unittest/rewriter/subquery/ExistsSubQueryRewriterTest.java @@ -18,13 +18,12 @@ public class ExistsSubQueryRewriterTest extends SubQueryRewriterTestBase { @Test public void nonCorrelatedExists() { assertEquals( - sqlString( - expr("SELECT e.name FROM employee e, e.projects p WHERE p IS NOT MISSING")), + sqlString(expr("SELECT e.name FROM employee e, e.projects p WHERE p IS NOT MISSING")), sqlString( rewrite( expr( - "SELECT e.name FROM employee as e WHERE EXISTS (SELECT * FROM e.projects as p)" - )))); + "SELECT e.name FROM employee as e WHERE EXISTS (SELECT * FROM e.projects" + + " as p)")))); } @Test diff --git a/legacy/src/test/java/org/opensearch/sql/legacy/unittest/rewriter/subquery/SubQueryRewriteRuleTest.java b/legacy/src/test/java/org/opensearch/sql/legacy/unittest/rewriter/subquery/SubQueryRewriteRuleTest.java index a01988d965..7bd3dd847e 100644 --- a/legacy/src/test/java/org/opensearch/sql/legacy/unittest/rewriter/subquery/SubQueryRewriteRuleTest.java +++ b/legacy/src/test/java/org/opensearch/sql/legacy/unittest/rewriter/subquery/SubQueryRewriteRuleTest.java @@ -3,7 +3,6 @@ * SPDX-License-Identifier: Apache-2.0 */ - package org.opensearch.sql.legacy.unittest.rewriter.subquery; import static org.junit.Assert.assertFalse; @@ -18,67 +17,56 @@ public class SubQueryRewriteRuleTest { - final SubQueryRewriteRule rewriteRule = new SubQueryRewriteRule(); + final SubQueryRewriteRule rewriteRule = new SubQueryRewriteRule(); - @Rule - public ExpectedException exceptionRule = ExpectedException.none(); + @Rule public ExpectedException exceptionRule = ExpectedException.none(); - @Test - public void isInMatch() throws SQLFeatureNotSupportedException { - String sql = "SELECT * " + - "FROM A " + - "WHERE a IN (SELECT b FROM B)"; - assertTrue(rewriteRule.match(SqlParserUtils.parse(sql))); - } + @Test + public void isInMatch() throws SQLFeatureNotSupportedException { + String sql = "SELECT * FROM A WHERE a IN (SELECT b FROM B)"; + assertTrue(rewriteRule.match(SqlParserUtils.parse(sql))); + } - @Test - public void isNotInMatch() throws SQLFeatureNotSupportedException { - String sql = "SELECT * " + - "FROM A " + - "WHERE a NOT IN (SELECT b FROM B)"; - assertTrue(rewriteRule.match(SqlParserUtils.parse(sql))); - } + @Test + public void isNotInMatch() throws SQLFeatureNotSupportedException { + String sql = "SELECT * FROM A WHERE a NOT IN (SELECT b FROM B)"; + assertTrue(rewriteRule.match(SqlParserUtils.parse(sql))); + } - @Test - public void isExistsMatch() throws SQLFeatureNotSupportedException { - String sql = "SELECT * " + - "FROM A WHERE " + - "EXISTS (SELECT 1 FROM B WHERE A.a_v = B.b_v)"; - assertTrue(rewriteRule.match(SqlParserUtils.parse(sql))); - } + @Test + public void isExistsMatch() throws SQLFeatureNotSupportedException { + String sql = "SELECT * FROM A WHERE EXISTS (SELECT 1 FROM B WHERE A.a_v = B.b_v)"; + assertTrue(rewriteRule.match(SqlParserUtils.parse(sql))); + } - @Test - public void isNotExistsMatch() throws SQLFeatureNotSupportedException { - String sql = "SELECT * " + - "FROM A " + - "WHERE NOT EXISTS (SELECT 1 FROM B WHERE A.a_v = B.b_v)"; - assertTrue(rewriteRule.match(SqlParserUtils.parse(sql))); - } + @Test + public void isNotExistsMatch() throws SQLFeatureNotSupportedException { + String sql = "SELECT * " + "FROM A WHERE NOT EXISTS (SELECT 1 FROM B WHERE A.a_v = B.b_v)"; + assertTrue(rewriteRule.match(SqlParserUtils.parse(sql))); + } - @Test - public void subQueryInSelectNotMatch() throws SQLFeatureNotSupportedException { - String sql = "SELECT A.v as v, (SELECT MAX(b) FROM B WHERE A.id = B.id) as max_age " + - "FROM A"; - assertFalse(rewriteRule.match(SqlParserUtils.parse(sql))); - } + @Test + public void subQueryInSelectNotMatch() throws SQLFeatureNotSupportedException { + String sql = "SELECT A.v as v, (SELECT MAX(b) FROM B WHERE A.id = B.id) as max_age FROM A"; + assertFalse(rewriteRule.match(SqlParserUtils.parse(sql))); + } - @Test - public void moreThanOneInIsNotSupporeted() throws SQLFeatureNotSupportedException { - String sql = "SELECT * " + - "FROM A " + - "WHERE a IN (SELECT b FROM B) and d IN (SELECT e FROM F)"; - exceptionRule.expect(SQLFeatureNotSupportedException.class); - exceptionRule.expectMessage("Unsupported subquery. Only one EXISTS or IN is supported"); - rewriteRule.match(SqlParserUtils.parse(sql)); - } + @Test + public void moreThanOneInIsNotSupporeted() throws SQLFeatureNotSupportedException { + String sql = "SELECT * FROM A WHERE a IN (SELECT b FROM B) and d IN (SELECT e FROM F)"; + exceptionRule.expect(SQLFeatureNotSupportedException.class); + exceptionRule.expectMessage("Unsupported subquery. Only one EXISTS or IN is supported"); + rewriteRule.match(SqlParserUtils.parse(sql)); + } - @Test - public void moreThanOneExistsIsNotSupporeted() throws SQLFeatureNotSupportedException { - String sql = "SELECT * " + - "FROM A WHERE " + - "EXISTS (SELECT 1 FROM B WHERE A.a_v = B.b_v) AND EXISTS (SELECT 1 FROM C)"; - exceptionRule.expect(SQLFeatureNotSupportedException.class); - exceptionRule.expectMessage("Unsupported subquery. Only one EXISTS or IN is supported"); - rewriteRule.match(SqlParserUtils.parse(sql)); - } + @Test + public void moreThanOneExistsIsNotSupporeted() throws SQLFeatureNotSupportedException { + String sql = + "SELECT * " + + "FROM A WHERE " + + "EXISTS (SELECT 1 FROM B WHERE A.a_v = B.b_v) AND EXISTS (SELECT 1 FROM C)"; + exceptionRule.expect(SQLFeatureNotSupportedException.class); + exceptionRule.expectMessage("Unsupported subquery. Only one EXISTS or IN is supported"); + rewriteRule.match(SqlParserUtils.parse(sql)); + } } diff --git a/legacy/src/test/java/org/opensearch/sql/legacy/unittest/rewriter/subquery/SubQueryRewriterTestBase.java b/legacy/src/test/java/org/opensearch/sql/legacy/unittest/rewriter/subquery/SubQueryRewriterTestBase.java index 036d0fc86a..ef7098004f 100644 --- a/legacy/src/test/java/org/opensearch/sql/legacy/unittest/rewriter/subquery/SubQueryRewriterTestBase.java +++ b/legacy/src/test/java/org/opensearch/sql/legacy/unittest/rewriter/subquery/SubQueryRewriterTestBase.java @@ -3,7 +3,6 @@ * SPDX-License-Identifier: Apache-2.0 */ - package org.opensearch.sql.legacy.unittest.rewriter.subquery; import com.alibaba.druid.sql.SQLUtils; @@ -14,19 +13,19 @@ public abstract class SubQueryRewriterTestBase { - SQLQueryExpr expr(String query) { - return SqlParserUtils.parse(query); - } + SQLQueryExpr expr(String query) { + return SqlParserUtils.parse(query); + } - SQLQueryExpr rewrite(SQLQueryExpr expr) { - new SubQueryRewriteRule().rewrite(expr); - return expr; - } + SQLQueryExpr rewrite(SQLQueryExpr expr) { + new SubQueryRewriteRule().rewrite(expr); + return expr; + } - String sqlString(SQLObject expr) { - return SQLUtils.toMySqlString(expr) - .replaceAll("\n", " ") - .replaceAll("\t", " ") - .replaceAll(" +", " "); - } + String sqlString(SQLObject expr) { + return SQLUtils.toMySqlString(expr) + .replaceAll("\n", " ") + .replaceAll("\t", " ") + .replaceAll(" +", " "); + } } diff --git a/legacy/src/test/java/org/opensearch/sql/legacy/unittest/rewriter/subquery/rewriter/SubqueryAliasRewriterTest.java b/legacy/src/test/java/org/opensearch/sql/legacy/unittest/rewriter/subquery/rewriter/SubqueryAliasRewriterTest.java index b729b7ad59..5c5bc40bda 100644 --- a/legacy/src/test/java/org/opensearch/sql/legacy/unittest/rewriter/subquery/rewriter/SubqueryAliasRewriterTest.java +++ b/legacy/src/test/java/org/opensearch/sql/legacy/unittest/rewriter/subquery/rewriter/SubqueryAliasRewriterTest.java @@ -3,7 +3,6 @@ * SPDX-License-Identifier: Apache-2.0 */ - package org.opensearch.sql.legacy.unittest.rewriter.subquery.rewriter; import static org.junit.Assert.assertEquals; @@ -17,121 +16,137 @@ public class SubqueryAliasRewriterTest { - @Test - public void testWithoutAlias() { - assertEquals( - sqlString(parse( - "SELECT TbA_0.* " + - "FROM TbA as TbA_0 " + - "WHERE TbA_0.a IN (SELECT TbB_1.b FROM TbB as TbB_1) and TbA_0.c > 10")), - sqlString(rewrite(parse( - "SELECT * " + - "FROM TbA " + - "WHERE a IN (SELECT b FROM TbB) and c > 10")))); - } - - @Test - public void testWithAlias() { - assertEquals( - sqlString(parse( - "SELECT A.* " + - "FROM TbA as A " + - "WHERE A.a IN (SELECT B.b FROM TbB as B) " + - "AND A.c > 10")), - sqlString(rewrite(parse( - "SELECT A.* " + - "FROM TbA as A " + - "WHERE A.a IN (SELECT B.b FROM TbB as B) " + - "AND A.c > 10")))); - } - - @Test - public void testOuterWithoutAliasInnerWithAlias() { - assertEquals( - sqlString(parse( - "SELECT TbA_0.* " + - "FROM TbA as TbA_0 " + - "WHERE TbA_0.a IN (SELECT TbB.b FROM TbB as TbB) " + - "AND TbA_0.c > 10")), - sqlString(rewrite(parse( - "SELECT * " + - "FROM TbA " + - "WHERE a IN (SELECT TbB.b FROM TbB as TbB) " + - "AND c > 10")))); - } - - @Test - public void testOuterWithoutAliasInnerMixAlias() { - String expect = - "SELECT TbA_0.* " + - "FROM TbA as TbA_0 " + - "WHERE TbA_0.a IN (SELECT B.b FROM TbB as B) " + - "AND TbA_0.c > 10"; - - assertEquals( - sqlString(parse(expect)), - sqlString(rewrite(parse( - "SELECT * " + - "FROM TbA " + - "WHERE a IN (SELECT b FROM TbB as B) " + - "AND c > 10")))); - - assertEquals( - sqlString(parse(expect)), - sqlString(rewrite(parse( - "SELECT * " + - "FROM TbA " + - "WHERE a IN (SELECT TbB.b FROM TbB as B) " + - "AND c > 10")))); - } - - @Test - public void testOuterWithAliasInnerWithoutAlias() { - assertEquals( - sqlString(parse( - "SELECT TbA.* " + - "FROM TbA as TbA " + - "WHERE TbA.a IN (SELECT TbB_0.b FROM TbB as TbB_0) " + - "AND TbA.c > 10")), - sqlString(rewrite(parse( - "SELECT TbA.* " + - "FROM TbA as TbA " + - "WHERE TbA.a IN (SELECT b FROM TbB ) " + - "AND TbA.c > 10")))); - } - - @Test - public void testOuterMixAliasInnerWithoutAlias() { - String expect = - "SELECT A.* " + - "FROM TbA as A " + - "WHERE A.a IN (SELECT TbB_0.b FROM TbB as TbB_0) " + - "AND A.c > 10"; - - assertEquals( - sqlString(parse(expect)), - sqlString(rewrite(parse( - "SELECT TbA.* " + - "FROM TbA as A " + - "WHERE a IN (SELECT b FROM TbB ) " + - "AND TbA.c > 10")))); - - assertEquals( - sqlString(parse(expect)), - sqlString(rewrite(parse( - "SELECT * " + - "FROM TbA as A " + - "WHERE TbA.a IN (SELECT b FROM TbB ) " + - "AND TbA.c > 10")))); - } - - - private String sqlString(SQLExpr expr) { - return SQLUtils.toMySqlString(expr); - } - - private SQLQueryExpr rewrite(SQLQueryExpr expr) { - expr.accept(new SubqueryAliasRewriter()); - return expr; - } + @Test + public void testWithoutAlias() { + assertEquals( + sqlString( + parse( + "SELECT TbA_0.* " + + "FROM TbA as TbA_0 " + + "WHERE TbA_0.a IN (SELECT TbB_1.b FROM TbB as TbB_1) and TbA_0.c > 10")), + sqlString( + rewrite( + parse("SELECT * " + "FROM TbA " + "WHERE a IN (SELECT b FROM TbB) and c > 10")))); + } + + @Test + public void testWithAlias() { + assertEquals( + sqlString( + parse( + "SELECT A.* " + + "FROM TbA as A " + + "WHERE A.a IN (SELECT B.b FROM TbB as B) " + + "AND A.c > 10")), + sqlString( + rewrite( + parse( + "SELECT A.* " + + "FROM TbA as A " + + "WHERE A.a IN (SELECT B.b FROM TbB as B) " + + "AND A.c > 10")))); + } + + @Test + public void testOuterWithoutAliasInnerWithAlias() { + assertEquals( + sqlString( + parse( + "SELECT TbA_0.* " + + "FROM TbA as TbA_0 " + + "WHERE TbA_0.a IN (SELECT TbB.b FROM TbB as TbB) " + + "AND TbA_0.c > 10")), + sqlString( + rewrite( + parse( + "SELECT * " + + "FROM TbA " + + "WHERE a IN (SELECT TbB.b FROM TbB as TbB) " + + "AND c > 10")))); + } + + @Test + public void testOuterWithoutAliasInnerMixAlias() { + String expect = + "SELECT TbA_0.* " + + "FROM TbA as TbA_0 " + + "WHERE TbA_0.a IN (SELECT B.b FROM TbB as B) " + + "AND TbA_0.c > 10"; + + assertEquals( + sqlString(parse(expect)), + sqlString( + rewrite( + parse( + "SELECT * " + + "FROM TbA " + + "WHERE a IN (SELECT b FROM TbB as B) " + + "AND c > 10")))); + + assertEquals( + sqlString(parse(expect)), + sqlString( + rewrite( + parse( + "SELECT * " + + "FROM TbA " + + "WHERE a IN (SELECT TbB.b FROM TbB as B) " + + "AND c > 10")))); + } + + @Test + public void testOuterWithAliasInnerWithoutAlias() { + assertEquals( + sqlString( + parse( + "SELECT TbA.* " + + "FROM TbA as TbA " + + "WHERE TbA.a IN (SELECT TbB_0.b FROM TbB as TbB_0) " + + "AND TbA.c > 10")), + sqlString( + rewrite( + parse( + "SELECT TbA.* " + + "FROM TbA as TbA " + + "WHERE TbA.a IN (SELECT b FROM TbB ) " + + "AND TbA.c > 10")))); + } + + @Test + public void testOuterMixAliasInnerWithoutAlias() { + String expect = + "SELECT A.* " + + "FROM TbA as A " + + "WHERE A.a IN (SELECT TbB_0.b FROM TbB as TbB_0) " + + "AND A.c > 10"; + + assertEquals( + sqlString(parse(expect)), + sqlString( + rewrite( + parse( + "SELECT TbA.* " + + "FROM TbA as A " + + "WHERE a IN (SELECT b FROM TbB ) " + + "AND TbA.c > 10")))); + + assertEquals( + sqlString(parse(expect)), + sqlString( + rewrite( + parse( + "SELECT * " + + "FROM TbA as A " + + "WHERE TbA.a IN (SELECT b FROM TbB ) " + + "AND TbA.c > 10")))); + } + + private String sqlString(SQLExpr expr) { + return SQLUtils.toMySqlString(expr); + } + + private SQLQueryExpr rewrite(SQLQueryExpr expr) { + expr.accept(new SubqueryAliasRewriter()); + return expr; + } } diff --git a/legacy/src/test/java/org/opensearch/sql/legacy/unittest/rewriter/term/TermFieldRewriterTest.java b/legacy/src/test/java/org/opensearch/sql/legacy/unittest/rewriter/term/TermFieldRewriterTest.java index d001e0e1d0..44d3e2cbc0 100644 --- a/legacy/src/test/java/org/opensearch/sql/legacy/unittest/rewriter/term/TermFieldRewriterTest.java +++ b/legacy/src/test/java/org/opensearch/sql/legacy/unittest/rewriter/term/TermFieldRewriterTest.java @@ -3,10 +3,8 @@ * SPDX-License-Identifier: Apache-2.0 */ - package org.opensearch.sql.legacy.unittest.rewriter.term; - import static org.hamcrest.MatcherAssert.assertThat; import static org.opensearch.sql.legacy.util.MultipleIndexClusterUtils.mockMultipleIndexEnv; @@ -22,88 +20,93 @@ import org.opensearch.sql.legacy.util.SqlParserUtils; public class TermFieldRewriterTest { - @Rule - public ExpectedException exception = ExpectedException.none(); - - @Before - public void setup() { - mockMultipleIndexEnv(); - } - - @Test - public void testFromSubqueryShouldPass() { - String sql = "SELECT t.age as a FROM (SELECT age FROM account1 WHERE address = 'sea') t"; - String expected = "SELECT t.age as a FROM (SELECT age FROM account1 WHERE address.keyword = 'sea') t"; - - assertThat(rewriteTerm(sql), - MatcherUtils.IsEqualIgnoreCaseAndWhiteSpace.equalToIgnoreCaseAndWhiteSpace(expected)); - } - - @Test - public void testFromSubqueryWithoutTermShouldPass() { - String sql = "SELECT t.age as a FROM (SELECT age FROM account1 WHERE age = 10) t"; - String expected = sql; - - assertThat(rewriteTerm(sql), - MatcherUtils.IsEqualIgnoreCaseAndWhiteSpace.equalToIgnoreCaseAndWhiteSpace(expected)); - } - - @Test - public void testFieldShouldBeRewritten() { - String sql = "SELECT age FROM account1 WHERE address = 'sea'"; - String expected = "SELECT age FROM account1 WHERE address.keyword = 'sea'"; - - assertThat(rewriteTerm(sql), - MatcherUtils.IsEqualIgnoreCaseAndWhiteSpace.equalToIgnoreCaseAndWhiteSpace(expected)); - } - - @Test - public void testSelectTheFieldWithCompatibleMappingShouldPass() { - String sql = "SELECT id FROM account* WHERE id = 10"; - String expected = sql; - - assertThat(rewriteTerm(sql), - MatcherUtils.IsEqualIgnoreCaseAndWhiteSpace.equalToIgnoreCaseAndWhiteSpace(expected)); - } - - @Test - public void testSelectTheFieldOnlyInOneIndexShouldPass() { - String sql = "SELECT address FROM account*"; - String expected = sql; - - assertThat(rewriteTerm(sql), - MatcherUtils.IsEqualIgnoreCaseAndWhiteSpace.equalToIgnoreCaseAndWhiteSpace(expected)); - } - - /** - * Ideally, it should fail. There are two reasons we didn't cover it now. - * 1. The semantic check already done that. - * 2. The {@link TermFieldRewriter} didn't touch allcolumn case. - */ - @Test - public void testSelectAllFieldWithConflictMappingShouldPass() { - String sql = "SELECT * FROM account*"; - String expected = sql; - - assertThat(rewriteTerm(sql), - MatcherUtils.IsEqualIgnoreCaseAndWhiteSpace.equalToIgnoreCaseAndWhiteSpace(expected)); - } - - @Test - public void testSelectTheFieldWithConflictMappingShouldThrowException() { - String sql = "SELECT age FROM account* WHERE age = 10"; - exception.expect(VerificationException.class); - exception.expectMessage("Different mappings are not allowed for the same field[age]"); - rewriteTerm(sql); - } - - private String rewriteTerm(String sql) { - SQLQueryExpr sqlQueryExpr = SqlParserUtils.parse(sql); - sqlQueryExpr.accept(new TermFieldRewriter()); - return SQLUtils.toMySqlString(sqlQueryExpr) - .replaceAll("[\\n\\t]+", " ") - .replaceAll("^\\(", " ") - .replaceAll("\\)$", " ") - .trim(); - } + @Rule public ExpectedException exception = ExpectedException.none(); + + @Before + public void setup() { + mockMultipleIndexEnv(); + } + + @Test + public void testFromSubqueryShouldPass() { + String sql = "SELECT t.age as a FROM (SELECT age FROM account1 WHERE address = 'sea') t"; + String expected = + "SELECT t.age as a FROM (SELECT age FROM account1 WHERE address.keyword = 'sea') t"; + + assertThat( + rewriteTerm(sql), + MatcherUtils.IsEqualIgnoreCaseAndWhiteSpace.equalToIgnoreCaseAndWhiteSpace(expected)); + } + + @Test + public void testFromSubqueryWithoutTermShouldPass() { + String sql = "SELECT t.age as a FROM (SELECT age FROM account1 WHERE age = 10) t"; + String expected = sql; + + assertThat( + rewriteTerm(sql), + MatcherUtils.IsEqualIgnoreCaseAndWhiteSpace.equalToIgnoreCaseAndWhiteSpace(expected)); + } + + @Test + public void testFieldShouldBeRewritten() { + String sql = "SELECT age FROM account1 WHERE address = 'sea'"; + String expected = "SELECT age FROM account1 WHERE address.keyword = 'sea'"; + + assertThat( + rewriteTerm(sql), + MatcherUtils.IsEqualIgnoreCaseAndWhiteSpace.equalToIgnoreCaseAndWhiteSpace(expected)); + } + + @Test + public void testSelectTheFieldWithCompatibleMappingShouldPass() { + String sql = "SELECT id FROM account* WHERE id = 10"; + String expected = sql; + + assertThat( + rewriteTerm(sql), + MatcherUtils.IsEqualIgnoreCaseAndWhiteSpace.equalToIgnoreCaseAndWhiteSpace(expected)); + } + + @Test + public void testSelectTheFieldOnlyInOneIndexShouldPass() { + String sql = "SELECT address FROM account*"; + String expected = sql; + + assertThat( + rewriteTerm(sql), + MatcherUtils.IsEqualIgnoreCaseAndWhiteSpace.equalToIgnoreCaseAndWhiteSpace(expected)); + } + + /** + * Ideally, it should fail. There are two reasons we didn't cover it now. 1. The semantic check + * already done that. 2. The {@link TermFieldRewriter} didn't touch allcolumn case. + */ + @Test + public void testSelectAllFieldWithConflictMappingShouldPass() { + String sql = "SELECT * FROM account*"; + String expected = sql; + + assertThat( + rewriteTerm(sql), + MatcherUtils.IsEqualIgnoreCaseAndWhiteSpace.equalToIgnoreCaseAndWhiteSpace(expected)); + } + + @Test + public void testSelectTheFieldWithConflictMappingShouldThrowException() { + String sql = "SELECT age FROM account* WHERE age = 10"; + exception.expect(VerificationException.class); + exception.expectMessage("Different mappings are not allowed for the same field[age]"); + rewriteTerm(sql); + } + + private String rewriteTerm(String sql) { + SQLQueryExpr sqlQueryExpr = SqlParserUtils.parse(sql); + sqlQueryExpr.accept(new TermFieldRewriter()); + return SQLUtils.toMySqlString(sqlQueryExpr) + .replaceAll("[\\n\\t]+", " ") + .replaceAll("^\\(", " ") + .replaceAll("\\)$", " ") + .trim(); + } } diff --git a/legacy/src/test/java/org/opensearch/sql/legacy/unittest/spatial/WktToGeoJsonConverterTest.java b/legacy/src/test/java/org/opensearch/sql/legacy/unittest/spatial/WktToGeoJsonConverterTest.java index 24889ff3ca..e63c60467f 100644 --- a/legacy/src/test/java/org/opensearch/sql/legacy/unittest/spatial/WktToGeoJsonConverterTest.java +++ b/legacy/src/test/java/org/opensearch/sql/legacy/unittest/spatial/WktToGeoJsonConverterTest.java @@ -3,181 +3,206 @@ * SPDX-License-Identifier: Apache-2.0 */ - package org.opensearch.sql.legacy.unittest.spatial; - import org.junit.Assert; import org.junit.Test; import org.opensearch.sql.legacy.spatial.WktToGeoJsonConverter; -/** - * Created by Eliran on 4/8/2015. - */ +/** Created by Eliran on 4/8/2015. */ public class WktToGeoJsonConverterTest { - @Test - public void convertPoint_NoRedundantSpaces_ShouldConvert(){ - String wkt = "POINT(12.3 13.3)"; - String geoJson = WktToGeoJsonConverter.toGeoJson(wkt); - String expectedGeoJson = "{\"type\":\"Point\", \"coordinates\": [12.3,13.3]}"; - Assert.assertEquals(expectedGeoJson,geoJson); - } - - @Test - public void convertPoint_WithRedundantSpaces_ShouldConvert(){ - String wkt = " POINT ( 12.3 13.3 ) "; - String geoJson = WktToGeoJsonConverter.toGeoJson(wkt); - String expectedGeoJson = "{\"type\":\"Point\", \"coordinates\": [12.3,13.3]}"; - Assert.assertEquals(expectedGeoJson,geoJson); - } - - @Test - public void convertPoint_RoundNumbers_ShouldConvert(){ - String wkt = "POINT(12 13)"; - String geoJson = WktToGeoJsonConverter.toGeoJson(wkt); - String expectedGeoJson = "{\"type\":\"Point\", \"coordinates\": [12,13]}"; - Assert.assertEquals(expectedGeoJson,geoJson); - } - - @Test - public void convertPoint_FirstIsRoundNumber_ShouldConvert(){ - String wkt = "POINT(12 13.3)"; - String geoJson = WktToGeoJsonConverter.toGeoJson(wkt); - String expectedGeoJson = "{\"type\":\"Point\", \"coordinates\": [12,13.3]}"; - Assert.assertEquals(expectedGeoJson,geoJson); - } - - @Test - public void convertPoint_SecondIsRoundNumber_ShouldConvert(){ - String wkt = "POINT(12.2 13)"; - String geoJson = WktToGeoJsonConverter.toGeoJson(wkt); - String expectedGeoJson = "{\"type\":\"Point\", \"coordinates\": [12.2,13]}"; - Assert.assertEquals(expectedGeoJson,geoJson); - } - - @Test - public void convertPoint_NegativeCoordinates_ShouldConvert(){ - String wkt = "POINT(-12.2 13)"; - String geoJson = WktToGeoJsonConverter.toGeoJson(wkt); - String expectedGeoJson = "{\"type\":\"Point\", \"coordinates\": [-12.2,13]}"; - Assert.assertEquals(expectedGeoJson,geoJson); - } - - @Test - public void convertPolygon_NoRedundantSpaces_ShouldConvert(){ - String wkt = "POLYGON ((30 10, 40 40, 20 40, 10 20, 30 10))"; - String geoJson = WktToGeoJsonConverter.toGeoJson(wkt); - String expectedGeoJson = "{\"type\":\"Polygon\", \"coordinates\": [[[30,10],[40,40],[20,40],[10,20],[30,10]]]}"; - Assert.assertEquals(expectedGeoJson,geoJson); - } - - @Test - public void convertPolygon_NegativeCoordinates_ShouldConvert(){ - String wkt = "POLYGON ((-30 10, 40 40, 20 40, 10 20, -30 10))"; - String geoJson = WktToGeoJsonConverter.toGeoJson(wkt); - String expectedGeoJson = "{\"type\":\"Polygon\", \"coordinates\": [[[-30,10],[40,40],[20,40],[10,20],[-30,10]]]}"; - Assert.assertEquals(expectedGeoJson,geoJson); - } - - @Test - public void convertPolygon_WithRedundantSpaces_ShouldConvert(){ - String wkt = " POLYGON ( (30 10, 40 40 , 20 40, 10 20, 30 10 ) ) "; - String geoJson = WktToGeoJsonConverter.toGeoJson(wkt); - String expectedGeoJson = "{\"type\":\"Polygon\", \"coordinates\": [[[30,10],[40,40],[20,40],[10,20],[30,10]]]}"; - Assert.assertEquals(expectedGeoJson,geoJson); - } - - @Test - public void convertPolygonWithHole_NoRedundantSpaces_ShouldConvert(){ - String wkt = "POLYGON ((35 10, 45 45, 15 40, 10 20, 35 10),(20 30, 35 35, 30 20, 20 30))"; - String geoJson = WktToGeoJsonConverter.toGeoJson(wkt); - String expectedGeoJson = "{\"type\":\"Polygon\", \"coordinates\": [[[35,10],[45,45],[15,40],[10,20],[35,10]],[[20,30],[35,35],[30,20],[20,30]]]}"; - Assert.assertEquals(expectedGeoJson,geoJson); - } - - @Test - public void convertPolygonWithHole_WithRedundantSpaces_ShouldConvert(){ - String wkt = "POLYGON ( (35 10, 45 45, 15 40, 10 20, 35 10 ), (20 30 , 35 35, 30 20, 20 30 ) ) "; - String geoJson = WktToGeoJsonConverter.toGeoJson(wkt); - String expectedGeoJson = "{\"type\":\"Polygon\", \"coordinates\": [[[35,10],[45,45],[15,40],[10,20],[35,10]],[[20,30],[35,35],[30,20],[20,30]]]}"; - Assert.assertEquals(expectedGeoJson,geoJson); - } - - @Test - public void convertLineString_NoRedundantSpaces_ShouldConvert(){ - String wkt = "LINESTRING (30 10, 10 30, 40 40)"; - String geoJson = WktToGeoJsonConverter.toGeoJson(wkt); - String expectedGeoJson = "{\"type\":\"LineString\", \"coordinates\": [[30,10],[10,30],[40,40]]}"; - Assert.assertEquals(expectedGeoJson,geoJson); - } - - @Test - public void convertLineString_NegativeCoordinates_ShouldConvert(){ - String wkt = "LINESTRING (-30 10, 10 30, 40 40)"; - String geoJson = WktToGeoJsonConverter.toGeoJson(wkt); - String expectedGeoJson = "{\"type\":\"LineString\", \"coordinates\": [[-30,10],[10,30],[40,40]]}"; - Assert.assertEquals(expectedGeoJson,geoJson); - } - - @Test - public void convertLineString_WithRedundantSpaces_ShouldConvert(){ - String wkt = "LINESTRING ( 30 10, 10 30 , 40 40 )"; - String geoJson = WktToGeoJsonConverter.toGeoJson(wkt); - String expectedGeoJson = "{\"type\":\"LineString\", \"coordinates\": [[30,10],[10,30],[40,40]]}"; - Assert.assertEquals(expectedGeoJson,geoJson); - } - - @Test - public void convertMultiPolygon_NoRedundantSpaces_ShouldConvert(){ - String wkt = "MULTIPOLYGON (((30 20, 45 40, 10 40, 30 20)),((15 5, 40 10, 10 20, 5 10, 15 5)))"; - String geoJson = WktToGeoJsonConverter.toGeoJson(wkt); - String expectedGeoJson = "{\"type\":\"MultiPolygon\", \"coordinates\": [[[[30,20],[45,40],[10,40],[30,20]]],[[[15,5],[40,10],[10,20],[5,10],[15,5]]]]}"; - Assert.assertEquals(expectedGeoJson,geoJson); - } - @Test - public void convertMultiPolygon_WithRedundantSpaces_ShouldConvert(){ - String wkt = "MULTIPOLYGON ( ((30 20, 45 40, 10 40, 30 20) ) , ((15 5, 40 10, 10 20, 5 10, 15 5)))"; - String geoJson = WktToGeoJsonConverter.toGeoJson(wkt); - String expectedGeoJson = "{\"type\":\"MultiPolygon\", \"coordinates\": [[[[30,20],[45,40],[10,40],[30,20]]],[[[15,5],[40,10],[10,20],[5,10],[15,5]]]]}"; - Assert.assertEquals(expectedGeoJson,geoJson); - } - @Test - public void convertMultiPolygon_OnePolygonHaveHoles_ShouldConvert(){ - String wkt = "MULTIPOLYGON (((30 20, 45 40, 10 40, 30 20),(20 30, 35 35, 30 20, 20 30)),((15 5, 40 10, 10 20, 5 10, 15 5)))"; - String geoJson = WktToGeoJsonConverter.toGeoJson(wkt); - String expectedGeoJson = "{\"type\":\"MultiPolygon\", \"coordinates\": [[[[30,20],[45,40],[10,40],[30,20]],[[20,30],[35,35],[30,20],[20,30]]],[[[15,5],[40,10],[10,20],[5,10],[15,5]]]]}"; - Assert.assertEquals(expectedGeoJson,geoJson); - } - - @Test - public void convertMultiPoint_V1_ShouldConvert(){ - String wkt = "MULTIPOINT (10 40, 40 30, 20 20, 30 10)"; - String geoJson = WktToGeoJsonConverter.toGeoJson(wkt); - String expectedGeoJson = "{\"type\":\"MultiPoint\", \"coordinates\": [[10,40],[40,30],[20,20],[30,10]]}"; - Assert.assertEquals(expectedGeoJson,geoJson); - } - - @Test - public void convertMultiPoint_V2_ShouldConvert(){ - String wkt = "MULTIPOINT ((10 40), (40 30), (20 20), (30 10))"; - String geoJson = WktToGeoJsonConverter.toGeoJson(wkt); - String expectedGeoJson = "{\"type\":\"MultiPoint\", \"coordinates\": [[10,40],[40,30],[20,20],[30,10]]}"; - Assert.assertEquals(expectedGeoJson,geoJson); - } - - @Test - public void convertMultiLineString_NoRedundantSpaces_ShouldConvert(){ - String wkt = "MULTILINESTRING ((10 10, 20 20, 10 40),(40 40, 30 30, 40 20, 30 10))"; - String geoJson = WktToGeoJsonConverter.toGeoJson(wkt); - String expectedGeoJson = "{\"type\":\"MultiLineString\", \"coordinates\": [[[10,10],[20,20],[10,40]],[[40,40],[30,30],[40,20],[30,10]]]}"; - Assert.assertEquals(expectedGeoJson,geoJson); - } - @Test - public void convertMultiLineString_WithRedundantSpaces_ShouldConvert(){ - String wkt = "MULTILINESTRING ( (10 10, 20 20, 10 40 ) , (40 40, 30 30, 40 20, 30 10))"; - String geoJson = WktToGeoJsonConverter.toGeoJson(wkt); - String expectedGeoJson = "{\"type\":\"MultiLineString\", \"coordinates\": [[[10,10],[20,20],[10,40]],[[40,40],[30,30],[40,20],[30,10]]]}"; - Assert.assertEquals(expectedGeoJson,geoJson); - } + @Test + public void convertPoint_NoRedundantSpaces_ShouldConvert() { + String wkt = "POINT(12.3 13.3)"; + String geoJson = WktToGeoJsonConverter.toGeoJson(wkt); + String expectedGeoJson = "{\"type\":\"Point\", \"coordinates\": [12.3,13.3]}"; + Assert.assertEquals(expectedGeoJson, geoJson); + } + + @Test + public void convertPoint_WithRedundantSpaces_ShouldConvert() { + String wkt = " POINT ( 12.3 13.3 ) "; + String geoJson = WktToGeoJsonConverter.toGeoJson(wkt); + String expectedGeoJson = "{\"type\":\"Point\", \"coordinates\": [12.3,13.3]}"; + Assert.assertEquals(expectedGeoJson, geoJson); + } + + @Test + public void convertPoint_RoundNumbers_ShouldConvert() { + String wkt = "POINT(12 13)"; + String geoJson = WktToGeoJsonConverter.toGeoJson(wkt); + String expectedGeoJson = "{\"type\":\"Point\", \"coordinates\": [12,13]}"; + Assert.assertEquals(expectedGeoJson, geoJson); + } + + @Test + public void convertPoint_FirstIsRoundNumber_ShouldConvert() { + String wkt = "POINT(12 13.3)"; + String geoJson = WktToGeoJsonConverter.toGeoJson(wkt); + String expectedGeoJson = "{\"type\":\"Point\", \"coordinates\": [12,13.3]}"; + Assert.assertEquals(expectedGeoJson, geoJson); + } + + @Test + public void convertPoint_SecondIsRoundNumber_ShouldConvert() { + String wkt = "POINT(12.2 13)"; + String geoJson = WktToGeoJsonConverter.toGeoJson(wkt); + String expectedGeoJson = "{\"type\":\"Point\", \"coordinates\": [12.2,13]}"; + Assert.assertEquals(expectedGeoJson, geoJson); + } + + @Test + public void convertPoint_NegativeCoordinates_ShouldConvert() { + String wkt = "POINT(-12.2 13)"; + String geoJson = WktToGeoJsonConverter.toGeoJson(wkt); + String expectedGeoJson = "{\"type\":\"Point\", \"coordinates\": [-12.2,13]}"; + Assert.assertEquals(expectedGeoJson, geoJson); + } + + @Test + public void convertPolygon_NoRedundantSpaces_ShouldConvert() { + String wkt = "POLYGON ((30 10, 40 40, 20 40, 10 20, 30 10))"; + String geoJson = WktToGeoJsonConverter.toGeoJson(wkt); + String expectedGeoJson = + "{\"type\":\"Polygon\", \"coordinates\": [[[30,10],[40,40],[20,40],[10,20],[30,10]]]}"; + Assert.assertEquals(expectedGeoJson, geoJson); + } + + @Test + public void convertPolygon_NegativeCoordinates_ShouldConvert() { + String wkt = "POLYGON ((-30 10, 40 40, 20 40, 10 20, -30 10))"; + String geoJson = WktToGeoJsonConverter.toGeoJson(wkt); + String expectedGeoJson = + "{\"type\":\"Polygon\", \"coordinates\": [[[-30,10],[40,40],[20,40],[10,20],[-30,10]]]}"; + Assert.assertEquals(expectedGeoJson, geoJson); + } + + @Test + public void convertPolygon_WithRedundantSpaces_ShouldConvert() { + String wkt = " POLYGON ( (30 10, 40 40 , 20 40, 10 20, 30 10 ) ) "; + String geoJson = WktToGeoJsonConverter.toGeoJson(wkt); + String expectedGeoJson = + "{\"type\":\"Polygon\", \"coordinates\": [[[30,10],[40,40],[20,40],[10,20],[30,10]]]}"; + Assert.assertEquals(expectedGeoJson, geoJson); + } + + @Test + public void convertPolygonWithHole_NoRedundantSpaces_ShouldConvert() { + String wkt = "POLYGON ((35 10, 45 45, 15 40, 10 20, 35 10),(20 30, 35 35, 30 20, 20 30))"; + String geoJson = WktToGeoJsonConverter.toGeoJson(wkt); + String expectedGeoJson = + "{\"type\":\"Polygon\", \"coordinates\":" + + " [[[35,10],[45,45],[15,40],[10,20],[35,10]],[[20,30],[35,35],[30,20],[20,30]]]}"; + Assert.assertEquals(expectedGeoJson, geoJson); + } + + @Test + public void convertPolygonWithHole_WithRedundantSpaces_ShouldConvert() { + String wkt = + "POLYGON ( (35 10, 45 45, 15 40, 10 20, 35 10 ), (20 30 , 35 35, 30 20, 20 30 ) ) "; + String geoJson = WktToGeoJsonConverter.toGeoJson(wkt); + String expectedGeoJson = + "{\"type\":\"Polygon\", \"coordinates\":" + + " [[[35,10],[45,45],[15,40],[10,20],[35,10]],[[20,30],[35,35],[30,20],[20,30]]]}"; + Assert.assertEquals(expectedGeoJson, geoJson); + } + + @Test + public void convertLineString_NoRedundantSpaces_ShouldConvert() { + String wkt = "LINESTRING (30 10, 10 30, 40 40)"; + String geoJson = WktToGeoJsonConverter.toGeoJson(wkt); + String expectedGeoJson = + "{\"type\":\"LineString\", \"coordinates\": [[30,10],[10,30],[40,40]]}"; + Assert.assertEquals(expectedGeoJson, geoJson); + } + + @Test + public void convertLineString_NegativeCoordinates_ShouldConvert() { + String wkt = "LINESTRING (-30 10, 10 30, 40 40)"; + String geoJson = WktToGeoJsonConverter.toGeoJson(wkt); + String expectedGeoJson = + "{\"type\":\"LineString\", \"coordinates\": [[-30,10],[10,30],[40,40]]}"; + Assert.assertEquals(expectedGeoJson, geoJson); + } + + @Test + public void convertLineString_WithRedundantSpaces_ShouldConvert() { + String wkt = "LINESTRING ( 30 10, 10 30 , 40 40 )"; + String geoJson = WktToGeoJsonConverter.toGeoJson(wkt); + String expectedGeoJson = + "{\"type\":\"LineString\", \"coordinates\": [[30,10],[10,30],[40,40]]}"; + Assert.assertEquals(expectedGeoJson, geoJson); + } + + @Test + public void convertMultiPolygon_NoRedundantSpaces_ShouldConvert() { + String wkt = "MULTIPOLYGON (((30 20, 45 40, 10 40, 30 20)),((15 5, 40 10, 10 20, 5 10, 15 5)))"; + String geoJson = WktToGeoJsonConverter.toGeoJson(wkt); + String expectedGeoJson = + "{\"type\":\"MultiPolygon\", \"coordinates\":" + + " [[[[30,20],[45,40],[10,40],[30,20]]],[[[15,5],[40,10],[10,20],[5,10],[15,5]]]]}"; + Assert.assertEquals(expectedGeoJson, geoJson); + } + + @Test + public void convertMultiPolygon_WithRedundantSpaces_ShouldConvert() { + String wkt = + "MULTIPOLYGON ( ((30 20, 45 40, 10 40, 30 20) ) , ((15 5, 40 10, 10 20, 5 10, 15 5)))"; + String geoJson = WktToGeoJsonConverter.toGeoJson(wkt); + String expectedGeoJson = + "{\"type\":\"MultiPolygon\", \"coordinates\":" + + " [[[[30,20],[45,40],[10,40],[30,20]]],[[[15,5],[40,10],[10,20],[5,10],[15,5]]]]}"; + Assert.assertEquals(expectedGeoJson, geoJson); + } + + @Test + public void convertMultiPolygon_OnePolygonHaveHoles_ShouldConvert() { + String wkt = + "MULTIPOLYGON (((30 20, 45 40, 10 40, 30 20),(20 30, 35 35, 30 20, 20 30)),((15 5, 40 10," + + " 10 20, 5 10, 15 5)))"; + String geoJson = WktToGeoJsonConverter.toGeoJson(wkt); + String expectedGeoJson = + "{\"type\":\"MultiPolygon\", \"coordinates\":" + + " [[[[30,20],[45,40],[10,40],[30,20]],[[20,30],[35,35],[30,20],[20,30]]],[[[15,5],[40,10],[10,20],[5,10],[15,5]]]]}"; + Assert.assertEquals(expectedGeoJson, geoJson); + } + + @Test + public void convertMultiPoint_V1_ShouldConvert() { + String wkt = "MULTIPOINT (10 40, 40 30, 20 20, 30 10)"; + String geoJson = WktToGeoJsonConverter.toGeoJson(wkt); + String expectedGeoJson = + "{\"type\":\"MultiPoint\", \"coordinates\": [[10,40],[40,30],[20,20],[30,10]]}"; + Assert.assertEquals(expectedGeoJson, geoJson); + } + + @Test + public void convertMultiPoint_V2_ShouldConvert() { + String wkt = "MULTIPOINT ((10 40), (40 30), (20 20), (30 10))"; + String geoJson = WktToGeoJsonConverter.toGeoJson(wkt); + String expectedGeoJson = + "{\"type\":\"MultiPoint\", \"coordinates\": [[10,40],[40,30],[20,20],[30,10]]}"; + Assert.assertEquals(expectedGeoJson, geoJson); + } + + @Test + public void convertMultiLineString_NoRedundantSpaces_ShouldConvert() { + String wkt = "MULTILINESTRING ((10 10, 20 20, 10 40),(40 40, 30 30, 40 20, 30 10))"; + String geoJson = WktToGeoJsonConverter.toGeoJson(wkt); + String expectedGeoJson = + "{\"type\":\"MultiLineString\", \"coordinates\":" + + " [[[10,10],[20,20],[10,40]],[[40,40],[30,30],[40,20],[30,10]]]}"; + Assert.assertEquals(expectedGeoJson, geoJson); + } + + @Test + public void convertMultiLineString_WithRedundantSpaces_ShouldConvert() { + String wkt = "MULTILINESTRING ( (10 10, 20 20, 10 40 ) , (40 40, 30 30, 40 20, 30 10))"; + String geoJson = WktToGeoJsonConverter.toGeoJson(wkt); + String expectedGeoJson = + "{\"type\":\"MultiLineString\", \"coordinates\":" + + " [[[10,10],[20,20],[10,40]],[[40,40],[30,30],[40,20],[30,10]]]}"; + Assert.assertEquals(expectedGeoJson, geoJson); + } } diff --git a/legacy/src/test/java/org/opensearch/sql/legacy/unittest/utils/SQLFunctionsTest.java b/legacy/src/test/java/org/opensearch/sql/legacy/unittest/utils/SQLFunctionsTest.java index 70c4a2aa11..9fc2b6012d 100644 --- a/legacy/src/test/java/org/opensearch/sql/legacy/unittest/utils/SQLFunctionsTest.java +++ b/legacy/src/test/java/org/opensearch/sql/legacy/unittest/utils/SQLFunctionsTest.java @@ -3,7 +3,6 @@ * SPDX-License-Identifier: Apache-2.0 */ - package org.opensearch.sql.legacy.unittest.utils; import static org.junit.Assert.assertEquals; @@ -35,69 +34,64 @@ public class SQLFunctionsTest { - private SQLFunctions sqlFunctions = new SQLFunctions(); - - @Rule - public ExpectedException exceptionRule = ExpectedException.none(); - - @Test - public void testAssign() throws SqlParseException { - SQLFunctions sqlFunctions = new SQLFunctions(); - - final SQLIntegerExpr sqlIntegerExpr = new SQLIntegerExpr(10); - final Tuple assign = sqlFunctions.function("assign", - ImmutableList.of(new KVValue(null, sqlIntegerExpr)), - null, - true); - - assertTrue(assign.v1().matches("assign_[0-9]+")); - assertTrue(assign.v2().matches("def assign_[0-9]+ = 10;return assign_[0-9]+;")); - } - - @Test - public void testAbsWithIntReturnType() { - final SQLIntegerExpr sqlIntegerExpr = new SQLIntegerExpr(6); - - final SQLMethodInvokeExpr invokeExpr = new SQLMethodInvokeExpr("ABS"); - invokeExpr.addParameter(sqlIntegerExpr); - List params = new ArrayList<>(); - - final MethodField field = new ScriptMethodField("ABS", params, null, null); - field.setExpression(invokeExpr); - ColumnTypeProvider columnTypeProvider = new ColumnTypeProvider(OpenSearchDataType.INTEGER); - - Schema.Type resolvedType = columnTypeProvider.get(0); - final Schema.Type returnType = sqlFunctions.getScriptFunctionReturnType(field, resolvedType); - Assert.assertEquals(returnType, Schema.Type.INTEGER); - } - - @Test - public void testCastReturnType() { - final SQLIdentifierExpr identifierExpr = new SQLIdentifierExpr("int_type"); - SQLDataType sqlDataType = new SQLDataTypeImpl("INT"); - final SQLCastExpr castExpr = new SQLCastExpr(); - castExpr.setExpr(identifierExpr); - castExpr.setDataType(sqlDataType); - - List params = new ArrayList<>(); - final MethodField field = new ScriptMethodField("CAST", params, null, null); - field.setExpression(castExpr); - ColumnTypeProvider columnTypeProvider = new ColumnTypeProvider(OpenSearchDataType.INTEGER); - - Schema.Type resolvedType = columnTypeProvider.get(0); - final Schema.Type returnType = sqlFunctions.getScriptFunctionReturnType(field, resolvedType); - Assert.assertEquals(returnType, Schema.Type.INTEGER); - } - - @Test - public void testCastIntStatementScript() throws SqlParseException { - assertEquals( - "def result = (doc['age'].value instanceof boolean) " - + "? (doc['age'].value ? 1 : 0) " - + ": Double.parseDouble(doc['age'].value.toString()).intValue()", - sqlFunctions.getCastScriptStatement( - "result", "int", Arrays.asList(new KVValue("age"))) - ); - } - + private SQLFunctions sqlFunctions = new SQLFunctions(); + + @Rule public ExpectedException exceptionRule = ExpectedException.none(); + + @Test + public void testAssign() throws SqlParseException { + SQLFunctions sqlFunctions = new SQLFunctions(); + + final SQLIntegerExpr sqlIntegerExpr = new SQLIntegerExpr(10); + final Tuple assign = + sqlFunctions.function( + "assign", ImmutableList.of(new KVValue(null, sqlIntegerExpr)), null, true); + + assertTrue(assign.v1().matches("assign_[0-9]+")); + assertTrue(assign.v2().matches("def assign_[0-9]+ = 10;return assign_[0-9]+;")); + } + + @Test + public void testAbsWithIntReturnType() { + final SQLIntegerExpr sqlIntegerExpr = new SQLIntegerExpr(6); + + final SQLMethodInvokeExpr invokeExpr = new SQLMethodInvokeExpr("ABS"); + invokeExpr.addParameter(sqlIntegerExpr); + List params = new ArrayList<>(); + + final MethodField field = new ScriptMethodField("ABS", params, null, null); + field.setExpression(invokeExpr); + ColumnTypeProvider columnTypeProvider = new ColumnTypeProvider(OpenSearchDataType.INTEGER); + + Schema.Type resolvedType = columnTypeProvider.get(0); + final Schema.Type returnType = sqlFunctions.getScriptFunctionReturnType(field, resolvedType); + Assert.assertEquals(returnType, Schema.Type.INTEGER); + } + + @Test + public void testCastReturnType() { + final SQLIdentifierExpr identifierExpr = new SQLIdentifierExpr("int_type"); + SQLDataType sqlDataType = new SQLDataTypeImpl("INT"); + final SQLCastExpr castExpr = new SQLCastExpr(); + castExpr.setExpr(identifierExpr); + castExpr.setDataType(sqlDataType); + + List params = new ArrayList<>(); + final MethodField field = new ScriptMethodField("CAST", params, null, null); + field.setExpression(castExpr); + ColumnTypeProvider columnTypeProvider = new ColumnTypeProvider(OpenSearchDataType.INTEGER); + + Schema.Type resolvedType = columnTypeProvider.get(0); + final Schema.Type returnType = sqlFunctions.getScriptFunctionReturnType(field, resolvedType); + Assert.assertEquals(returnType, Schema.Type.INTEGER); + } + + @Test + public void testCastIntStatementScript() throws SqlParseException { + assertEquals( + "def result = (doc['age'].value instanceof boolean) " + + "? (doc['age'].value ? 1 : 0) " + + ": Double.parseDouble(doc['age'].value.toString()).intValue()", + sqlFunctions.getCastScriptStatement("result", "int", Arrays.asList(new KVValue("age")))); + } } diff --git a/legacy/src/test/java/org/opensearch/sql/legacy/unittest/utils/StringUtilsTest.java b/legacy/src/test/java/org/opensearch/sql/legacy/unittest/utils/StringUtilsTest.java index d25fed6f31..b73e91981e 100644 --- a/legacy/src/test/java/org/opensearch/sql/legacy/unittest/utils/StringUtilsTest.java +++ b/legacy/src/test/java/org/opensearch/sql/legacy/unittest/utils/StringUtilsTest.java @@ -3,7 +3,6 @@ * SPDX-License-Identifier: Apache-2.0 */ - package org.opensearch.sql.legacy.unittest.utils; import static org.hamcrest.Matchers.equalTo; @@ -17,55 +16,57 @@ public class StringUtilsTest { - private Locale originalLocale; + private Locale originalLocale; - @Before - public void saveOriginalLocale() { - originalLocale = Locale.getDefault(); - } + @Before + public void saveOriginalLocale() { + originalLocale = Locale.getDefault(); + } - @After - public void restoreOriginalLocale() { - Locale.setDefault(originalLocale); - } + @After + public void restoreOriginalLocale() { + Locale.setDefault(originalLocale); + } - @Test - public void toLower() { - final String input = "SAMPLE STRING"; - final String output = StringUtils.toLower(input); + @Test + public void toLower() { + final String input = "SAMPLE STRING"; + final String output = StringUtils.toLower(input); - Assert.assertThat(output, equalTo("sample string")); + Assert.assertThat(output, equalTo("sample string")); - // See https://docs.oracle.com/javase/10/docs/api/java/lang/String.html#toLowerCase(java.util.Locale) - // for the choice of these characters and the locale. - final String upper = "\u0130 \u0049"; - Locale.setDefault(Locale.forLanguageTag("tr")); + // See + // https://docs.oracle.com/javase/10/docs/api/java/lang/String.html#toLowerCase(java.util.Locale) + // for the choice of these characters and the locale. + final String upper = "\u0130 \u0049"; + Locale.setDefault(Locale.forLanguageTag("tr")); - Assert.assertThat(upper.toUpperCase(Locale.ROOT), equalTo(StringUtils.toUpper(upper))); - } + Assert.assertThat(upper.toUpperCase(Locale.ROOT), equalTo(StringUtils.toUpper(upper))); + } - @Test - public void toUpper() { - final String input = "sample string"; - final String output = StringUtils.toUpper(input); + @Test + public void toUpper() { + final String input = "sample string"; + final String output = StringUtils.toUpper(input); - Assert.assertThat(output, equalTo("SAMPLE STRING")); + Assert.assertThat(output, equalTo("SAMPLE STRING")); - // See https://docs.oracle.com/javase/10/docs/api/java/lang/String.html#toUpperCase(java.util.Locale) - // for the choice of these characters and the locale. - final String lower = "\u0069 \u0131"; - Locale.setDefault(Locale.forLanguageTag("tr")); + // See + // https://docs.oracle.com/javase/10/docs/api/java/lang/String.html#toUpperCase(java.util.Locale) + // for the choice of these characters and the locale. + final String lower = "\u0069 \u0131"; + Locale.setDefault(Locale.forLanguageTag("tr")); - Assert.assertThat(lower.toUpperCase(Locale.ROOT), equalTo(StringUtils.toUpper(lower))); - } + Assert.assertThat(lower.toUpperCase(Locale.ROOT), equalTo(StringUtils.toUpper(lower))); + } - @Test - public void format() { - Locale.setDefault(Locale.forLanguageTag("tr")); - final String upper = "\u0130 \u0049"; - final String lower = "\u0069 \u0131"; + @Test + public void format() { + Locale.setDefault(Locale.forLanguageTag("tr")); + final String upper = "\u0130 \u0049"; + final String lower = "\u0069 \u0131"; - final String output = StringUtils.format("%s %s", upper, lower); - Assert.assertThat(output, equalTo(String.format(Locale.ROOT, "%s %s", upper, lower))); - } + final String output = StringUtils.format("%s %s", upper, lower); + Assert.assertThat(output, equalTo(String.format(Locale.ROOT, "%s %s", upper, lower))); + } } diff --git a/legacy/src/test/java/org/opensearch/sql/legacy/unittest/utils/UtilTest.java b/legacy/src/test/java/org/opensearch/sql/legacy/unittest/utils/UtilTest.java index 21731db5a5..e3c7a74a71 100644 --- a/legacy/src/test/java/org/opensearch/sql/legacy/unittest/utils/UtilTest.java +++ b/legacy/src/test/java/org/opensearch/sql/legacy/unittest/utils/UtilTest.java @@ -3,7 +3,6 @@ * SPDX-License-Identifier: Apache-2.0 */ - package org.opensearch.sql.legacy.unittest.utils; import java.util.HashMap; @@ -14,54 +13,54 @@ public class UtilTest { - @Test - public void clearEmptyPaths_EmptyMap_ShouldReturnTrue(){ - Map map = new HashMap<>(); - boolean result = Util.clearEmptyPaths(map); - // - Assert.assertTrue(result); - } + @Test + public void clearEmptyPaths_EmptyMap_ShouldReturnTrue() { + Map map = new HashMap<>(); + boolean result = Util.clearEmptyPaths(map); + // + Assert.assertTrue(result); + } - @Test - public void clearEmptyPaths_EmptyPathSize1_ShouldReturnTrueAndMapShouldBeEmpty(){ - Map map = new HashMap<>(); - map.put("a",new HashMap()); - boolean result = Util.clearEmptyPaths(map); - Assert.assertTrue(result); - Assert.assertEquals(0,map.size()); - } + @Test + public void clearEmptyPaths_EmptyPathSize1_ShouldReturnTrueAndMapShouldBeEmpty() { + Map map = new HashMap<>(); + map.put("a", new HashMap()); + boolean result = Util.clearEmptyPaths(map); + Assert.assertTrue(result); + Assert.assertEquals(0, map.size()); + } - @Test - public void clearEmptyPaths_EmptyPathSize2_ShouldReturnTrueAndMapShouldBeEmpty(){ - Map map = new HashMap<>(); - Map innerMap = new HashMap<>(); - innerMap.put("b",new HashMap()); - map.put("a",innerMap); - boolean result = Util.clearEmptyPaths(map); - Assert.assertTrue(result); - Assert.assertEquals(0,map.size()); - } + @Test + public void clearEmptyPaths_EmptyPathSize2_ShouldReturnTrueAndMapShouldBeEmpty() { + Map map = new HashMap<>(); + Map innerMap = new HashMap<>(); + innerMap.put("b", new HashMap()); + map.put("a", innerMap); + boolean result = Util.clearEmptyPaths(map); + Assert.assertTrue(result); + Assert.assertEquals(0, map.size()); + } - @Test - public void clearEmptyPaths_2PathsOneEmpty_MapShouldBeSizeOne(){ - Map map = new HashMap<>(); - map.put("a",new HashMap()); - map.put("c",1); - Util.clearEmptyPaths(map); - Assert.assertEquals(1,map.size()); - } + @Test + public void clearEmptyPaths_2PathsOneEmpty_MapShouldBeSizeOne() { + Map map = new HashMap<>(); + map.put("a", new HashMap()); + map.put("c", 1); + Util.clearEmptyPaths(map); + Assert.assertEquals(1, map.size()); + } - @Test - @SuppressWarnings("unchecked") - public void clearEmptyPaths_MapSizeTwoAndTwoOneInnerEmpty_MapShouldBeSizeTwoAndOne(){ - Map map = new HashMap<>(); - Map innerMap = new HashMap<>(); - innerMap.put("b",2); - innerMap.put("c",new HashMap()); - map.put("a",innerMap); - map.put("c",1); - Util.clearEmptyPaths(map); - Assert.assertEquals(2, map.size()); - Assert.assertEquals(1,((HashMap)map.get("a")).size()); - } + @Test + @SuppressWarnings("unchecked") + public void clearEmptyPaths_MapSizeTwoAndTwoOneInnerEmpty_MapShouldBeSizeTwoAndOne() { + Map map = new HashMap<>(); + Map innerMap = new HashMap<>(); + innerMap.put("b", 2); + innerMap.put("c", new HashMap()); + map.put("a", innerMap); + map.put("c", 1); + Util.clearEmptyPaths(map); + Assert.assertEquals(2, map.size()); + Assert.assertEquals(1, ((HashMap) map.get("a")).size()); + } } diff --git a/legacy/src/test/java/org/opensearch/sql/legacy/util/SqlExplainUtils.java b/legacy/src/test/java/org/opensearch/sql/legacy/util/SqlExplainUtils.java index 6228b971e2..3ad1cae211 100644 --- a/legacy/src/test/java/org/opensearch/sql/legacy/util/SqlExplainUtils.java +++ b/legacy/src/test/java/org/opensearch/sql/legacy/util/SqlExplainUtils.java @@ -3,7 +3,6 @@ * SPDX-License-Identifier: Apache-2.0 */ - package org.opensearch.sql.legacy.util; import com.alibaba.druid.sql.parser.ParserException; @@ -15,22 +14,20 @@ import org.opensearch.sql.legacy.query.OpenSearchActionFactory; import org.opensearch.sql.legacy.query.QueryAction; -/** - * Test utils class that explains a query - */ +/** Test utils class that explains a query */ public class SqlExplainUtils { - public static String explain(String query) { - try { - Client mockClient = Mockito.mock(Client.class); - CheckScriptContents.stubMockClient(mockClient); - QueryAction queryAction = OpenSearchActionFactory.create(mockClient, query); + public static String explain(String query) { + try { + Client mockClient = Mockito.mock(Client.class); + CheckScriptContents.stubMockClient(mockClient); + QueryAction queryAction = OpenSearchActionFactory.create(mockClient, query); - return queryAction.explain().explain(); - } catch (SqlParseException | SQLFeatureNotSupportedException | SQLFeatureDisabledException e) { - throw new ParserException("Illegal sql expr in: " + query); - } + return queryAction.explain().explain(); + } catch (SqlParseException | SQLFeatureNotSupportedException | SQLFeatureDisabledException e) { + throw new ParserException("Illegal sql expr in: " + query); } + } - private SqlExplainUtils() {} + private SqlExplainUtils() {} } diff --git a/legacy/src/test/java/org/opensearch/sql/legacy/util/SqlParserUtils.java b/legacy/src/test/java/org/opensearch/sql/legacy/util/SqlParserUtils.java index a1c023cbff..90ccc705fd 100644 --- a/legacy/src/test/java/org/opensearch/sql/legacy/util/SqlParserUtils.java +++ b/legacy/src/test/java/org/opensearch/sql/legacy/util/SqlParserUtils.java @@ -3,7 +3,6 @@ * SPDX-License-Identifier: Apache-2.0 */ - package org.opensearch.sql.legacy.util; import com.alibaba.druid.sql.ast.SQLExpr; @@ -13,24 +12,23 @@ import org.opensearch.sql.legacy.parser.ElasticSqlExprParser; import org.opensearch.sql.legacy.rewriter.parent.SQLExprParentSetter; -/** - * Test utils class include all SQLExpr related method. - */ +/** Test utils class include all SQLExpr related method. */ public class SqlParserUtils { - /** - * Parse sql with {@link ElasticSqlExprParser} - * @param sql sql - * @return {@link SQLQueryExpr} - */ - public static SQLQueryExpr parse(String sql) { - ElasticSqlExprParser parser = new ElasticSqlExprParser(sql); - SQLExpr expr = parser.expr(); - if (parser.getLexer().token() != Token.EOF) { - throw new ParserException("Illegal sql: " + sql); - } - SQLQueryExpr queryExpr = (SQLQueryExpr) expr; - queryExpr.accept(new SQLExprParentSetter()); - return (SQLQueryExpr) expr; + /** + * Parse sql with {@link ElasticSqlExprParser} + * + * @param sql sql + * @return {@link SQLQueryExpr} + */ + public static SQLQueryExpr parse(String sql) { + ElasticSqlExprParser parser = new ElasticSqlExprParser(sql); + SQLExpr expr = parser.expr(); + if (parser.getLexer().token() != Token.EOF) { + throw new ParserException("Illegal sql: " + sql); } + SQLQueryExpr queryExpr = (SQLQueryExpr) expr; + queryExpr.accept(new SQLExprParentSetter()); + return (SQLQueryExpr) expr; + } } diff --git a/legacy/src/test/java/org/opensearch/sql/legacy/util/TestUtils.java b/legacy/src/test/java/org/opensearch/sql/legacy/util/TestUtils.java index 27be512fc0..ab9a0ded14 100644 --- a/legacy/src/test/java/org/opensearch/sql/legacy/util/TestUtils.java +++ b/legacy/src/test/java/org/opensearch/sql/legacy/util/TestUtils.java @@ -3,7 +3,6 @@ * SPDX-License-Identifier: Apache-2.0 */ - package org.opensearch.sql.legacy.util; import static com.google.common.base.Strings.isNullOrEmpty; @@ -36,792 +35,809 @@ public class TestUtils { - /** - * Create test index by REST client. - * @param client client connection - * @param indexName test index name - * @param mapping test index mapping or null if no predefined mapping - */ - public static void createIndexByRestClient(RestClient client, String indexName, String mapping) { - Request request = new Request("PUT", "/" + indexName); - if (!isNullOrEmpty(mapping)) { - request.setJsonEntity(mapping); - } - performRequest(client, request); - } - - /** - * https://github.com/elastic/elasticsearch/pull/49959 - * Deprecate creation of dot-prefixed index names except for hidden and system indices. - * Create hidden index by REST client. - * @param client client connection - * @param indexName test index name - * @param mapping test index mapping or null if no predefined mapping - */ - public static void createHiddenIndexByRestClient(RestClient client, String indexName, String mapping) { - Request request = new Request("PUT", "/" + indexName); - JSONObject jsonObject = isNullOrEmpty(mapping) ? new JSONObject() : new JSONObject(mapping); - jsonObject.put("settings", new JSONObject("{\"index\":{\"hidden\":true}}")); - request.setJsonEntity(jsonObject.toString()); - - performRequest(client, request); + /** + * Create test index by REST client. + * + * @param client client connection + * @param indexName test index name + * @param mapping test index mapping or null if no predefined mapping + */ + public static void createIndexByRestClient(RestClient client, String indexName, String mapping) { + Request request = new Request("PUT", "/" + indexName); + if (!isNullOrEmpty(mapping)) { + request.setJsonEntity(mapping); } - - /** - * Check if index already exists by OpenSearch index exists API which returns: - * 200 - specified indices or aliases exist - * 404 - one or more indices specified or aliases do not exist - * @param client client connection - * @param indexName index name - * @return true for index exist - */ - public static boolean isIndexExist(RestClient client, String indexName) { - try { - Response response = client.performRequest(new Request("HEAD", "/" + indexName)); - return (response.getStatusLine().getStatusCode() == 200); - } catch (IOException e) { - throw new IllegalStateException("Failed to perform request", e); - } + performRequest(client, request); + } + + /** + * https://github.com/elastic/elasticsearch/pull/49959 Deprecate creation of dot-prefixed index + * names except for hidden and system indices. Create hidden index by REST client. + * + * @param client client connection + * @param indexName test index name + * @param mapping test index mapping or null if no predefined mapping + */ + public static void createHiddenIndexByRestClient( + RestClient client, String indexName, String mapping) { + Request request = new Request("PUT", "/" + indexName); + JSONObject jsonObject = isNullOrEmpty(mapping) ? new JSONObject() : new JSONObject(mapping); + jsonObject.put("settings", new JSONObject("{\"index\":{\"hidden\":true}}")); + request.setJsonEntity(jsonObject.toString()); + + performRequest(client, request); + } + + /** + * Check if index already exists by OpenSearch index exists API which returns: 200 - specified + * indices or aliases exist 404 - one or more indices specified or aliases do not exist + * + * @param client client connection + * @param indexName index name + * @return true for index exist + */ + public static boolean isIndexExist(RestClient client, String indexName) { + try { + Response response = client.performRequest(new Request("HEAD", "/" + indexName)); + return (response.getStatusLine().getStatusCode() == 200); + } catch (IOException e) { + throw new IllegalStateException("Failed to perform request", e); } - - /** - * Load test data set by REST client. - * @param client client connection - * @param indexName index name - * @param dataSetFilePath file path of test data set - * @throws IOException - */ - public static void loadDataByRestClient(RestClient client, String indexName, String dataSetFilePath) throws IOException { - Path path = Paths.get(getResourceFilePath(dataSetFilePath)); - Request request = new Request("POST", "/" + indexName + "/_bulk?refresh=true"); - request.setJsonEntity(new String(Files.readAllBytes(path))); - performRequest(client, request); + } + + /** + * Load test data set by REST client. + * + * @param client client connection + * @param indexName index name + * @param dataSetFilePath file path of test data set + * @throws IOException + */ + public static void loadDataByRestClient( + RestClient client, String indexName, String dataSetFilePath) throws IOException { + Path path = Paths.get(getResourceFilePath(dataSetFilePath)); + Request request = new Request("POST", "/" + indexName + "/_bulk?refresh=true"); + request.setJsonEntity(new String(Files.readAllBytes(path))); + performRequest(client, request); + } + + /** + * Perform a request by REST client. + * + * @param client client connection + * @param request request object + */ + public static Response performRequest(RestClient client, Request request) { + try { + Response response = client.performRequest(request); + int status = response.getStatusLine().getStatusCode(); + if (status >= 400) { + throw new IllegalStateException("Failed to perform request. Error code: " + status); + } + return response; + } catch (IOException e) { + throw new IllegalStateException("Failed to perform request", e); } - - /** - * Perform a request by REST client. - * @param client client connection - * @param request request object - */ - public static Response performRequest(RestClient client, Request request) { - try { - Response response = client.performRequest(request); - int status = response.getStatusLine().getStatusCode(); - if (status >= 400) { - throw new IllegalStateException("Failed to perform request. Error code: " + status); - } - return response; - } catch (IOException e) { - throw new IllegalStateException("Failed to perform request", e); + } + + public static String getAccountIndexMapping() { + return "{ \"mappings\": {" + + " \"properties\": {\n" + + " \"gender\": {\n" + + " \"type\": \"text\",\n" + + " \"fielddata\": true\n" + + " }," + + " \"address\": {\n" + + " \"type\": \"text\",\n" + + " \"fielddata\": true\n" + + " }," + + " \"firstname\": {\n" + + " \"type\": \"text\",\n" + + " \"fielddata\": true,\n" + + " \"fields\": {\n" + + " \"keyword\": {\n" + + " \"type\": \"keyword\",\n" + + " \"ignore_above\": 256\n" + + " }" + + " }" + + " }," + + " \"lastname\": {\n" + + " \"type\": \"text\",\n" + + " \"fielddata\": true,\n" + + " \"fields\": {\n" + + " \"keyword\": {\n" + + " \"type\": \"keyword\",\n" + + " \"ignore_above\": 256\n" + + " }" + + " }" + + " }," + + " \"state\": {\n" + + " \"type\": \"text\",\n" + + " \"fielddata\": true,\n" + + " \"fields\": {\n" + + " \"keyword\": {\n" + + " \"type\": \"keyword\",\n" + + " \"ignore_above\": 256\n" + + " }" + + " }" + + " }" + + " }" + + " }" + + "}"; + } + + public static String getPhraseIndexMapping() { + return "{ \"mappings\": {" + + " \"properties\": {\n" + + " \"phrase\": {\n" + + " \"type\": \"text\",\n" + + " \"store\": true\n" + + " }" + + " }" + + " }" + + "}"; + } + + public static String getDogIndexMapping() { + return "{ \"mappings\": {" + + " \"properties\": {\n" + + " \"dog_name\": {\n" + + " \"type\": \"text\",\n" + + " \"fielddata\": true\n" + + " }" + + " }" + + " }" + + "}"; + } + + public static String getDogs2IndexMapping() { + return "{ \"mappings\": {" + + " \"properties\": {\n" + + " \"dog_name\": {\n" + + " \"type\": \"text\",\n" + + " \"fielddata\": true\n" + + " },\n" + + " \"holdersName\": {\n" + + " \"type\": \"keyword\"\n" + + " }" + + " }" + + " }" + + "}"; + } + + public static String getDogs3IndexMapping() { + return "{ \"mappings\": {" + + " \"properties\": {\n" + + " \"holdersName\": {\n" + + " \"type\": \"keyword\"\n" + + " },\n" + + " \"color\": {\n" + + " \"type\": \"text\"\n" + + " }" + + " }" + + " }" + + "}"; + } + + public static String getPeople2IndexMapping() { + return "{ \"mappings\": {" + + " \"properties\": {\n" + + " \"firstname\": {\n" + + " \"type\": \"keyword\"\n" + + " }" + + " }" + + " }" + + "}"; + } + + public static String getGameOfThronesIndexMapping() { + return "{ \"mappings\": { " + + " \"properties\": {\n" + + " \"nickname\": {\n" + + " \"type\":\"text\", " + + " \"fielddata\":true" + + " },\n" + + " \"name\": {\n" + + " \"properties\": {\n" + + " \"firstname\": {\n" + + " \"type\": \"text\",\n" + + " \"fielddata\": true\n" + + " },\n" + + " \"lastname\": {\n" + + " \"type\": \"text\",\n" + + " \"fielddata\": true\n" + + " },\n" + + " \"ofHerName\": {\n" + + " \"type\": \"integer\"\n" + + " },\n" + + " \"ofHisName\": {\n" + + " \"type\": \"integer\"\n" + + " }\n" + + " }\n" + + " },\n" + + " \"house\": {\n" + + " \"type\": \"text\",\n" + + " \"fields\": {\n" + + " \"keyword\": {\n" + + " \"type\": \"keyword\"\n" + + " }\n" + + " }\n" + + " },\n" + + " \"gender\": {\n" + + " \"type\": \"text\",\n" + + " \"fields\": {\n" + + " \"keyword\": {\n" + + " \"type\": \"keyword\"\n" + + " }\n" + + " }\n" + + " }" + + "} } }"; + } + + // System + + public static String getOdbcIndexMapping() { + return "{\n" + + "\t\"mappings\" :{\n" + + "\t\t\"properties\":{\n" + + "\t\t\t\"odbc_time\":{\n" + + "\t\t\t\t\"type\":\"date\",\n" + + "\t\t\t\t\"format\": \"'{ts' ''yyyy-MM-dd HH:mm:ss.SSS'''}'\"\n" + + "\t\t\t},\n" + + "\t\t\t\"docCount\":{\n" + + "\t\t\t\t\"type\":\"text\"\n" + + "\t\t\t}\n" + + "\t\t}\n" + + "\t}\n" + + "}"; + } + + public static String getLocationIndexMapping() { + return "{\n" + + "\t\"mappings\" :{\n" + + "\t\t\"properties\":{\n" + + "\t\t\t\"place\":{\n" + + "\t\t\t\t\"type\":\"geo_shape\"\n" + + + // "\t\t\t\t\"tree\": \"quadtree\",\n" + // Field tree and precision are deprecated in + // OpenSearch + // "\t\t\t\t\"precision\": \"10km\"\n" + + "\t\t\t},\n" + + "\t\t\t\"center\":{\n" + + "\t\t\t\t\"type\":\"geo_point\"\n" + + "\t\t\t},\n" + + "\t\t\t\"description\":{\n" + + "\t\t\t\t\"type\":\"text\"\n" + + "\t\t\t}\n" + + "\t\t}\n" + + "\t}\n" + + "}"; + } + + public static String getEmployeeNestedTypeIndexMapping() { + return "{\n" + + " \"mappings\": {\n" + + " \"properties\": {\n" + + " \"comments\": {\n" + + " \"type\": \"nested\",\n" + + " \"properties\": {\n" + + " \"date\": {\n" + + " \"type\": \"date\"\n" + + " },\n" + + " \"likes\": {\n" + + " \"type\": \"long\"\n" + + " },\n" + + " \"message\": {\n" + + " \"type\": \"text\",\n" + + " \"fields\": {\n" + + " \"keyword\": {\n" + + " \"type\": \"keyword\",\n" + + " \"ignore_above\": 256\n" + + " }\n" + + " }\n" + + " }\n" + + " }\n" + + " },\n" + + " \"id\": {\n" + + " \"type\": \"long\"\n" + + " },\n" + + " \"name\": {\n" + + " \"type\": \"text\",\n" + + " \"fields\": {\n" + + " \"keyword\": {\n" + + " \"type\": \"keyword\",\n" + + " \"ignore_above\": 256\n" + + " }\n" + + " }\n" + + " },\n" + + " \"projects\": {\n" + + " \"type\": \"nested\",\n" + + " \"properties\": {\n" + + " \"address\": {\n" + + " \"type\": \"nested\",\n" + + " \"properties\": {\n" + + " \"city\": {\n" + + " \"type\": \"text\",\n" + + " \"fields\": {\n" + + " \"keyword\": {\n" + + " \"type\": \"keyword\",\n" + + " \"ignore_above\": 256\n" + + " }\n" + + " }\n" + + " },\n" + + " \"state\": {\n" + + " \"type\": \"text\",\n" + + " \"fields\": {\n" + + " \"keyword\": {\n" + + " \"type\": \"keyword\",\n" + + " \"ignore_above\": 256\n" + + " }\n" + + " }\n" + + " }\n" + + " }\n" + + " },\n" + + " \"name\": {\n" + + " \"type\": \"text\",\n" + + " \"fields\": {\n" + + " \"keyword\": {\n" + + " \"type\": \"keyword\"\n" + + " }\n" + + " },\n" + + " \"fielddata\": true\n" + + " },\n" + + " \"started_year\": {\n" + + " \"type\": \"long\"\n" + + " }\n" + + " }\n" + + " },\n" + + " \"title\": {\n" + + " \"type\": \"text\",\n" + + " \"fields\": {\n" + + " \"keyword\": {\n" + + " \"type\": \"keyword\",\n" + + " \"ignore_above\": 256\n" + + " }\n" + + " }\n" + + " }\n" + + " }\n" + + " }\n" + + "}\n"; + } + + public static String getNestedTypeIndexMapping() { + return "{ \"mappings\": {\n" + + " \"properties\": {\n" + + " \"message\": {\n" + + " \"type\": \"nested\",\n" + + " \"properties\": {\n" + + " \"info\": {\n" + + " \"type\": \"keyword\",\n" + + " \"index\": \"true\"\n" + + " },\n" + + " \"author\": {\n" + + " \"type\": \"keyword\",\n" + + " \"fields\": {\n" + + " \"keyword\": {\n" + + " \"type\": \"keyword\",\n" + + " \"ignore_above\" : 256\n" + + " }\n" + + " },\n" + + " \"index\": \"true\"\n" + + " },\n" + + " \"dayOfWeek\": {\n" + + " \"type\": \"long\"\n" + + " }\n" + + " }\n" + + " },\n" + + " \"comment\": {\n" + + " \"type\": \"nested\",\n" + + " \"properties\": {\n" + + " \"data\": {\n" + + " \"type\": \"keyword\",\n" + + " \"index\": \"true\"\n" + + " },\n" + + " \"likes\": {\n" + + " \"type\": \"long\"\n" + + " }\n" + + " }\n" + + " },\n" + + " \"myNum\": {\n" + + " \"type\": \"long\"\n" + + " },\n" + + " \"someField\": {\n" + + " \"type\": \"keyword\",\n" + + " \"index\": \"true\"\n" + + " }\n" + + " }\n" + + " }\n" + + " }}"; + } + + public static String getJoinTypeIndexMapping() { + return "{\n" + + " \"mappings\": {\n" + + " \"properties\": {\n" + + " \"join_field\": {\n" + + " \"type\": \"join\",\n" + + " \"relations\": {\n" + + " \"parentType\": \"childrenType\"\n" + + " }\n" + + " },\n" + + " \"parentTile\": {\n" + + " \"index\": \"true\",\n" + + " \"type\": \"keyword\"\n" + + " },\n" + + " \"dayOfWeek\": {\n" + + " \"type\": \"long\"\n" + + " },\n" + + " \"author\": {\n" + + " \"index\": \"true\",\n" + + " \"type\": \"keyword\"\n" + + " },\n" + + " \"info\": {\n" + + " \"index\": \"true\",\n" + + " \"type\": \"keyword\"\n" + + " }\n" + + " }\n" + + " }\n" + + "}"; + } + + public static String getBankIndexMapping() { + return "{\n" + + " \"mappings\": {\n" + + " \"properties\": {\n" + + " \"account_number\": {\n" + + " \"type\": \"long\"\n" + + " },\n" + + " \"address\": {\n" + + " \"type\": \"text\"\n" + + " },\n" + + " \"age\": {\n" + + " \"type\": \"integer\"\n" + + " },\n" + + " \"balance\": {\n" + + " \"type\": \"long\"\n" + + " },\n" + + " \"birthdate\": {\n" + + " \"type\": \"date\"\n" + + " },\n" + + " \"city\": {\n" + + " \"type\": \"keyword\"\n" + + " },\n" + + " \"email\": {\n" + + " \"type\": \"text\"\n" + + " },\n" + + " \"employer\": {\n" + + " \"type\": \"text\"\n" + + " },\n" + + " \"firstname\": {\n" + + " \"type\": \"text\"\n" + + " },\n" + + " \"gender\": {\n" + + " \"type\": \"text\",\n" + + " \"fielddata\": true\n" + + " }," + + " \"lastname\": {\n" + + " \"type\": \"keyword\"\n" + + " },\n" + + " \"male\": {\n" + + " \"type\": \"boolean\"\n" + + " },\n" + + " \"state\": {\n" + + " \"type\": \"text\",\n" + + " \"fields\": {\n" + + " \"keyword\": {\n" + + " \"type\": \"keyword\",\n" + + " \"ignore_above\": 256\n" + + " }\n" + + " }\n" + + " }\n" + + " }\n" + + " }\n" + + "}"; + } + + public static String getBankWithNullValuesIndexMapping() { + return "{\n" + + " \"mappings\": {\n" + + " \"properties\": {\n" + + " \"account_number\": {\n" + + " \"type\": \"long\"\n" + + " },\n" + + " \"address\": {\n" + + " \"type\": \"text\"\n" + + " },\n" + + " \"age\": {\n" + + " \"type\": \"integer\"\n" + + " },\n" + + " \"balance\": {\n" + + " \"type\": \"long\"\n" + + " },\n" + + " \"gender\": {\n" + + " \"type\": \"text\"\n" + + " },\n" + + " \"firstname\": {\n" + + " \"type\": \"text\"\n" + + " },\n" + + " \"lastname\": {\n" + + " \"type\": \"keyword\"\n" + + " }\n" + + " }\n" + + " }\n" + + "}"; + } + + public static String getOrderIndexMapping() { + return "{\n" + + " \"mappings\": {\n" + + " \"properties\": {\n" + + " \"id\": {\n" + + " \"type\": \"long\"\n" + + " },\n" + + " \"name\": {\n" + + " \"type\": \"text\",\n" + + " \"fields\": {\n" + + " \"keyword\": {\n" + + " \"type\": \"keyword\",\n" + + " \"ignore_above\": 256\n" + + " }\n" + + " }\n" + + " }\n" + + " }\n" + + " }\n" + + "}"; + } + + public static String getWeblogsIndexMapping() { + return "{\n" + + " \"mappings\": {\n" + + " \"properties\": {\n" + + " \"host\": {\n" + + " \"type\": \"ip\"\n" + + " },\n" + + " \"method\": {\n" + + " \"type\": \"text\"\n" + + " },\n" + + " \"url\": {\n" + + " \"type\": \"text\"\n" + + " },\n" + + " \"response\": {\n" + + " \"type\": \"text\"\n" + + " },\n" + + " \"bytes\": {\n" + + " \"type\": \"text\"\n" + + " }\n" + + " }\n" + + " }\n" + + "}"; + } + + public static String getDateIndexMapping() { + return "{ \"mappings\": {" + + " \"properties\": {\n" + + " \"date_keyword\": {\n" + + " \"type\": \"keyword\",\n" + + " \"ignore_above\": 256\n" + + " }" + + " }" + + " }" + + "}"; + } + + public static String getDateTimeIndexMapping() { + return "{" + + " \"mappings\": {" + + " \"properties\": {" + + " \"birthday\": {" + + " \"type\": \"date\"" + + " }" + + " }" + + " }" + + "}"; + } + + public static String getNestedSimpleIndexMapping() { + return "{" + + " \"mappings\": {" + + " \"properties\": {" + + " \"address\": {" + + " \"type\": \"nested\"," + + " \"properties\": {" + + " \"city\": {" + + " \"type\": \"text\"," + + " \"fields\": {" + + " \"keyword\": {" + + " \"type\": \"keyword\"," + + " \"ignore_above\": 256" + + " }" + + " }" + + " }," + + " \"state\": {" + + " \"type\": \"text\"," + + " \"fields\": {" + + " \"keyword\": {" + + " \"type\": \"keyword\"," + + " \"ignore_above\": 256" + + " }" + + " }" + + " }" + + " }" + + " }," + + " \"age\": {" + + " \"type\": \"long\"" + + " }," + + " \"id\": {" + + " \"type\": \"long\"" + + " }," + + " \"name\": {" + + " \"type\": \"text\"," + + " \"fields\": {" + + " \"keyword\": {" + + " \"type\": \"keyword\"," + + " \"ignore_above\": 256" + + " }" + + " }" + + " }" + + " }" + + " }" + + "}"; + } + + public static void loadBulk(Client client, String jsonPath, String defaultIndex) + throws Exception { + System.out.println(String.format("Loading file %s into OpenSearch cluster", jsonPath)); + String absJsonPath = getResourceFilePath(jsonPath); + + BulkRequest bulkRequest = new BulkRequest(); + try (final InputStream stream = new FileInputStream(absJsonPath); + final Reader streamReader = new InputStreamReader(stream, StandardCharsets.UTF_8); + final BufferedReader br = new BufferedReader(streamReader)) { + + while (true) { + + String actionLine = br.readLine(); + if (actionLine == null || actionLine.trim().isEmpty()) { + break; } + String sourceLine = br.readLine(); + JSONObject actionJson = new JSONObject(actionLine); + + IndexRequest indexRequest = new IndexRequest(); + indexRequest.index(defaultIndex); + if (actionJson.getJSONObject("index").has("_id")) { + String docId = actionJson.getJSONObject("index").getString("_id"); + indexRequest.id(docId); + } + if (actionJson.getJSONObject("index").has("_routing")) { + String routing = actionJson.getJSONObject("index").getString("_routing"); + indexRequest.routing(routing); + } + indexRequest.source(sourceLine, XContentType.JSON); + bulkRequest.add(indexRequest); + } } - public static String getAccountIndexMapping() { - return "{ \"mappings\": {" + - " \"properties\": {\n" + - " \"gender\": {\n" + - " \"type\": \"text\",\n" + - " \"fielddata\": true\n" + - " }," + - " \"address\": {\n" + - " \"type\": \"text\",\n" + - " \"fielddata\": true\n" + - " }," + - " \"firstname\": {\n" + - " \"type\": \"text\",\n" + - " \"fielddata\": true,\n" + - " \"fields\": {\n" + - " \"keyword\": {\n" + - " \"type\": \"keyword\",\n" + - " \"ignore_above\": 256\n" + - " }" + - " }" + - " }," + - " \"lastname\": {\n" + - " \"type\": \"text\",\n" + - " \"fielddata\": true,\n" + - " \"fields\": {\n" + - " \"keyword\": {\n" + - " \"type\": \"keyword\",\n" + - " \"ignore_above\": 256\n" + - " }" + - " }" + - " }," + - " \"state\": {\n" + - " \"type\": \"text\",\n" + - " \"fielddata\": true,\n" + - " \"fields\": {\n" + - " \"keyword\": {\n" + - " \"type\": \"keyword\",\n" + - " \"ignore_above\": 256\n" + - " }" + - " }" + - " }" + - " }"+ - " }" + - "}"; - } - - public static String getPhraseIndexMapping() { - return "{ \"mappings\": {" + - " \"properties\": {\n" + - " \"phrase\": {\n" + - " \"type\": \"text\",\n" + - " \"store\": true\n" + - " }" + - " }"+ - " }" + - "}"; - } - - public static String getDogIndexMapping() { - return "{ \"mappings\": {" + - " \"properties\": {\n" + - " \"dog_name\": {\n" + - " \"type\": \"text\",\n" + - " \"fielddata\": true\n" + - " }"+ - " }"+ - " }" + - "}"; - } - - public static String getDogs2IndexMapping() { - return "{ \"mappings\": {" + - " \"properties\": {\n" + - " \"dog_name\": {\n" + - " \"type\": \"text\",\n" + - " \"fielddata\": true\n" + - " },\n"+ - " \"holdersName\": {\n" + - " \"type\": \"keyword\"\n" + - " }"+ - " }"+ - " }" + - "}"; - } - - public static String getDogs3IndexMapping() { - return "{ \"mappings\": {" + - " \"properties\": {\n" + - " \"holdersName\": {\n" + - " \"type\": \"keyword\"\n" + - " },\n"+ - " \"color\": {\n" + - " \"type\": \"text\"\n" + - " }"+ - " }"+ - " }" + - "}"; - } - - public static String getPeople2IndexMapping() { - return "{ \"mappings\": {" + - " \"properties\": {\n" + - " \"firstname\": {\n" + - " \"type\": \"keyword\"\n" + - " }"+ - " }"+ - " }" + - "}"; - } + BulkResponse bulkResponse = client.bulk(bulkRequest).actionGet(); - public static String getGameOfThronesIndexMapping() { - return "{ \"mappings\": { " + - " \"properties\": {\n" + - " \"nickname\": {\n" + - " \"type\":\"text\", "+ - " \"fielddata\":true"+ - " },\n"+ - " \"name\": {\n" + - " \"properties\": {\n" + - " \"firstname\": {\n" + - " \"type\": \"text\",\n" + - " \"fielddata\": true\n" + - " },\n" + - " \"lastname\": {\n" + - " \"type\": \"text\",\n" + - " \"fielddata\": true\n" + - " },\n" + - " \"ofHerName\": {\n" + - " \"type\": \"integer\"\n" + - " },\n" + - " \"ofHisName\": {\n" + - " \"type\": \"integer\"\n" + - " }\n" + - " }\n" + - " },\n" + - " \"house\": {\n" + - " \"type\": \"text\",\n" + - " \"fields\": {\n" + - " \"keyword\": {\n" + - " \"type\": \"keyword\"\n" + - " }\n" + - " }\n" + - " },\n" + - " \"gender\": {\n" + - " \"type\": \"text\",\n" + - " \"fields\": {\n" + - " \"keyword\": {\n" + - " \"type\": \"keyword\"\n" + - " }\n" + - " }\n" + - " }" + - "} } }"; + if (bulkResponse.hasFailures()) { + throw new Exception( + "Failed to load test data into index " + + defaultIndex + + ", " + + bulkResponse.buildFailureMessage()); } - - // System - - public static String getOdbcIndexMapping() { - return "{\n" + - "\t\"mappings\" :{\n" + - "\t\t\"properties\":{\n" + - "\t\t\t\"odbc_time\":{\n" + - "\t\t\t\t\"type\":\"date\",\n" + - "\t\t\t\t\"format\": \"'{ts' ''yyyy-MM-dd HH:mm:ss.SSS'''}'\"\n" + - "\t\t\t},\n" + - "\t\t\t\"docCount\":{\n" + - "\t\t\t\t\"type\":\"text\"\n" + - "\t\t\t}\n" + - "\t\t}\n" + - "\t}\n" + - "}"; + System.out.println(bulkResponse.getItems().length + " documents loaded."); + // ensure the documents are searchable + client.admin().indices().prepareRefresh(defaultIndex).execute().actionGet(); + } + + public static String getResourceFilePath(String relPath) { + String projectRoot = System.getProperty("project.root", null); + if (projectRoot == null) { + return new File(relPath).getAbsolutePath(); + } else { + return new File(projectRoot + "/" + relPath).getAbsolutePath(); } + } - public static String getLocationIndexMapping() { - return "{\n" + - "\t\"mappings\" :{\n" + - "\t\t\"properties\":{\n" + - "\t\t\t\"place\":{\n" + - "\t\t\t\t\"type\":\"geo_shape\"\n" + - //"\t\t\t\t\"tree\": \"quadtree\",\n" + // Field tree and precision are deprecated in OpenSearch - //"\t\t\t\t\"precision\": \"10km\"\n" + - "\t\t\t},\n" + - "\t\t\t\"center\":{\n" + - "\t\t\t\t\"type\":\"geo_point\"\n" + - "\t\t\t},\n" + - "\t\t\t\"description\":{\n" + - "\t\t\t\t\"type\":\"text\"\n" + - "\t\t\t}\n" + - "\t\t}\n" + - "\t}\n" + - "}"; - } + public static String getResponseBody(Response response) throws IOException { - public static String getEmployeeNestedTypeIndexMapping() { - return "{\n" + - " \"mappings\": {\n" + - " \"properties\": {\n" + - " \"comments\": {\n" + - " \"type\": \"nested\",\n" + - " \"properties\": {\n" + - " \"date\": {\n" + - " \"type\": \"date\"\n" + - " },\n" + - " \"likes\": {\n" + - " \"type\": \"long\"\n" + - " },\n" + - " \"message\": {\n" + - " \"type\": \"text\",\n" + - " \"fields\": {\n" + - " \"keyword\": {\n" + - " \"type\": \"keyword\",\n" + - " \"ignore_above\": 256\n" + - " }\n" + - " }\n" + - " }\n" + - " }\n" + - " },\n" + - " \"id\": {\n" + - " \"type\": \"long\"\n" + - " },\n" + - " \"name\": {\n" + - " \"type\": \"text\",\n" + - " \"fields\": {\n" + - " \"keyword\": {\n" + - " \"type\": \"keyword\",\n" + - " \"ignore_above\": 256\n" + - " }\n" + - " }\n" + - " },\n" + - " \"projects\": {\n" + - " \"type\": \"nested\",\n" + - " \"properties\": {\n" + - " \"address\": {\n" + - " \"type\": \"nested\",\n" + - " \"properties\": {\n" + - " \"city\": {\n" + - " \"type\": \"text\",\n" + - " \"fields\": {\n" + - " \"keyword\": {\n" + - " \"type\": \"keyword\",\n" + - " \"ignore_above\": 256\n" + - " }\n" + - " }\n" + - " },\n" + - " \"state\": {\n" + - " \"type\": \"text\",\n" + - " \"fields\": {\n" + - " \"keyword\": {\n" + - " \"type\": \"keyword\",\n" + - " \"ignore_above\": 256\n" + - " }\n" + - " }\n" + - " }\n" + - " }\n" + - " },\n" + - " \"name\": {\n" + - " \"type\": \"text\",\n" + - " \"fields\": {\n" + - " \"keyword\": {\n" + - " \"type\": \"keyword\"\n" + - " }\n" + - " },\n" + - " \"fielddata\": true\n" + - " },\n" + - " \"started_year\": {\n" + - " \"type\": \"long\"\n" + - " }\n" + - " }\n" + - " },\n" + - " \"title\": {\n" + - " \"type\": \"text\",\n" + - " \"fields\": {\n" + - " \"keyword\": {\n" + - " \"type\": \"keyword\",\n" + - " \"ignore_above\": 256\n" + - " }\n" + - " }\n" + - " }\n" + - " }\n" + - " }\n" + - "}\n"; - } + return getResponseBody(response, false); + } + public static String getResponseBody(Response response, boolean retainNewLines) + throws IOException { + final StringBuilder sb = new StringBuilder(); - public static String getNestedTypeIndexMapping() { - return "{ \"mappings\": {\n" + - " \"properties\": {\n" + - " \"message\": {\n" + - " \"type\": \"nested\",\n" + - " \"properties\": {\n" + - " \"info\": {\n" + - " \"type\": \"keyword\",\n" + - " \"index\": \"true\"\n" + - " },\n" + - " \"author\": {\n" + - " \"type\": \"keyword\",\n" + - " \"fields\": {\n" + - " \"keyword\": {\n" + - " \"type\": \"keyword\",\n" + - " \"ignore_above\" : 256\n" + - " }\n" + - " },\n" + - " \"index\": \"true\"\n" + - " },\n" + - " \"dayOfWeek\": {\n" + - " \"type\": \"long\"\n" + - " }\n" + - " }\n" + - " },\n" + - " \"comment\": {\n" + - " \"type\": \"nested\",\n" + - " \"properties\": {\n" + - " \"data\": {\n" + - " \"type\": \"keyword\",\n" + - " \"index\": \"true\"\n" + - " },\n" + - " \"likes\": {\n" + - " \"type\": \"long\"\n" + - " }\n" + - " }\n" + - " },\n" + - " \"myNum\": {\n" + - " \"type\": \"long\"\n" + - " },\n" + - " \"someField\": {\n" + - " \"type\": \"keyword\",\n" + - " \"index\": \"true\"\n" + - " }\n" + - " }\n" + - " }\n" + - " }}"; - } - - public static String getJoinTypeIndexMapping() { - return "{\n" + - " \"mappings\": {\n" + - " \"properties\": {\n" + - " \"join_field\": {\n" + - " \"type\": \"join\",\n" + - " \"relations\": {\n" + - " \"parentType\": \"childrenType\"\n" + - " }\n" + - " },\n" + - " \"parentTile\": {\n" + - " \"index\": \"true\",\n" + - " \"type\": \"keyword\"\n" + - " },\n" + - " \"dayOfWeek\": {\n" + - " \"type\": \"long\"\n" + - " },\n" + - " \"author\": {\n" + - " \"index\": \"true\",\n" + - " \"type\": \"keyword\"\n" + - " },\n" + - " \"info\": {\n" + - " \"index\": \"true\",\n" + - " \"type\": \"keyword\"\n" + - " }\n" + - " }\n" + - " }\n" + - "}"; - } - - public static String getBankIndexMapping() { - return "{\n" + - " \"mappings\": {\n" + - " \"properties\": {\n" + - " \"account_number\": {\n" + - " \"type\": \"long\"\n" + - " },\n" + - " \"address\": {\n" + - " \"type\": \"text\"\n" + - " },\n" + - " \"age\": {\n" + - " \"type\": \"integer\"\n" + - " },\n" + - " \"balance\": {\n" + - " \"type\": \"long\"\n" + - " },\n" + - " \"birthdate\": {\n" + - " \"type\": \"date\"\n" + - " },\n" + - " \"city\": {\n" + - " \"type\": \"keyword\"\n" + - " },\n" + - " \"email\": {\n" + - " \"type\": \"text\"\n" + - " },\n" + - " \"employer\": {\n" + - " \"type\": \"text\"\n" + - " },\n" + - " \"firstname\": {\n" + - " \"type\": \"text\"\n" + - " },\n" + - " \"gender\": {\n" + - " \"type\": \"text\",\n" + - " \"fielddata\": true\n" + - " }," + - " \"lastname\": {\n" + - " \"type\": \"keyword\"\n" + - " },\n" + - " \"male\": {\n" + - " \"type\": \"boolean\"\n" + - " },\n" + - " \"state\": {\n" + - " \"type\": \"text\",\n" + - " \"fields\": {\n" + - " \"keyword\": {\n" + - " \"type\": \"keyword\",\n" + - " \"ignore_above\": 256\n" + - " }\n" + - " }\n" + - " }\n" + - " }\n" + - " }\n" + - "}"; - } - - public static String getBankWithNullValuesIndexMapping() { - return "{\n" + - " \"mappings\": {\n" + - " \"properties\": {\n" + - " \"account_number\": {\n" + - " \"type\": \"long\"\n" + - " },\n" + - " \"address\": {\n" + - " \"type\": \"text\"\n" + - " },\n" + - " \"age\": {\n" + - " \"type\": \"integer\"\n" + - " },\n" + - " \"balance\": {\n" + - " \"type\": \"long\"\n" + - " },\n" + - " \"gender\": {\n" + - " \"type\": \"text\"\n" + - " },\n" + - " \"firstname\": {\n" + - " \"type\": \"text\"\n" + - " },\n" + - " \"lastname\": {\n" + - " \"type\": \"keyword\"\n" + - " }\n" + - " }\n" + - " }\n" + - "}"; - } + try (final InputStream is = response.getEntity().getContent(); + final BufferedReader br = + new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8))) { - public static String getOrderIndexMapping() { - return "{\n" + - " \"mappings\": {\n" + - " \"properties\": {\n" + - " \"id\": {\n" + - " \"type\": \"long\"\n" + - " },\n" + - " \"name\": {\n" + - " \"type\": \"text\",\n" + - " \"fields\": {\n" + - " \"keyword\": {\n" + - " \"type\": \"keyword\",\n" + - " \"ignore_above\": 256\n" + - " }\n" + - " }\n" + - " }\n" + - " }\n" + - " }\n" + - "}"; + String line; + while ((line = br.readLine()) != null) { + sb.append(line); + if (retainNewLines) { + sb.append(String.format(Locale.ROOT, "%n")); + } + } } + return sb.toString(); + } - public static String getWeblogsIndexMapping() { - return "{\n" + - " \"mappings\": {\n" + - " \"properties\": {\n" + - " \"host\": {\n" + - " \"type\": \"ip\"\n" + - " },\n" + - " \"method\": {\n" + - " \"type\": \"text\"\n" + - " },\n" + - " \"url\": {\n" + - " \"type\": \"text\"\n" + - " },\n" + - " \"response\": {\n" + - " \"type\": \"text\"\n" + - " },\n" + - " \"bytes\": {\n" + - " \"type\": \"text\"\n" + - " }\n" + - " }\n" + - " }\n" + - "}"; - } + public static String fileToString( + final String filePathFromProjectRoot, final boolean removeNewLines) throws IOException { - public static String getDateIndexMapping() { - return "{ \"mappings\": {" + - " \"properties\": {\n" + - " \"date_keyword\": {\n" + - " \"type\": \"keyword\",\n" + - " \"ignore_above\": 256\n" + - " }"+ - " }"+ - " }" + - "}"; - } + final String absolutePath = getResourceFilePath(filePathFromProjectRoot); - public static String getDateTimeIndexMapping() { - return "{" + - " \"mappings\": {" + - " \"properties\": {" + - " \"birthday\": {" + - " \"type\": \"date\"" + - " }" + - " }" + - " }" + - "}"; - } + try (final InputStream stream = new FileInputStream(absolutePath); + final Reader streamReader = new InputStreamReader(stream, StandardCharsets.UTF_8); + final BufferedReader br = new BufferedReader(streamReader)) { - public static String getNestedSimpleIndexMapping() { - return "{" + - " \"mappings\": {" + - " \"properties\": {" + - " \"address\": {" + - " \"type\": \"nested\"," + - " \"properties\": {" + - " \"city\": {" + - " \"type\": \"text\"," + - " \"fields\": {" + - " \"keyword\": {" + - " \"type\": \"keyword\"," + - " \"ignore_above\": 256" + - " }" + - " }" + - " }," + - " \"state\": {" + - " \"type\": \"text\"," + - " \"fields\": {" + - " \"keyword\": {" + - " \"type\": \"keyword\"," + - " \"ignore_above\": 256" + - " }" + - " }" + - " }" + - " }" + - " }," + - " \"age\": {" + - " \"type\": \"long\"" + - " }," + - " \"id\": {" + - " \"type\": \"long\"" + - " }," + - " \"name\": {" + - " \"type\": \"text\"," + - " \"fields\": {" + - " \"keyword\": {" + - " \"type\": \"keyword\"," + - " \"ignore_above\": 256" + - " }" + - " }" + - " }" + - " }" + - " }" + - "}"; - } - public static void loadBulk(Client client, String jsonPath, String defaultIndex) throws Exception { - System.out.println(String.format("Loading file %s into OpenSearch cluster", jsonPath)); - String absJsonPath = getResourceFilePath(jsonPath); - - BulkRequest bulkRequest = new BulkRequest(); - try (final InputStream stream = new FileInputStream(absJsonPath); - final Reader streamReader = new InputStreamReader(stream, StandardCharsets.UTF_8); - final BufferedReader br = new BufferedReader(streamReader)) { - - while (true) { - - String actionLine = br.readLine(); - if (actionLine == null || actionLine.trim().isEmpty()) { - break; - } - String sourceLine = br.readLine(); - JSONObject actionJson = new JSONObject(actionLine); - - IndexRequest indexRequest = new IndexRequest(); - indexRequest.index(defaultIndex); - if (actionJson.getJSONObject("index").has("_id")) { - String docId = actionJson.getJSONObject("index").getString("_id"); - indexRequest.id(docId); - } - if (actionJson.getJSONObject("index").has("_routing")) { - String routing = actionJson.getJSONObject("index").getString("_routing"); - indexRequest.routing(routing); - } - indexRequest.source(sourceLine, XContentType.JSON); - bulkRequest.add(indexRequest); - } - } + final StringBuilder stringBuilder = new StringBuilder(); + String line = br.readLine(); - BulkResponse bulkResponse = client.bulk(bulkRequest).actionGet(); + while (line != null) { - if (bulkResponse.hasFailures()) { - throw new Exception("Failed to load test data into index " + defaultIndex + ", " + - bulkResponse.buildFailureMessage()); + stringBuilder.append(line); + if (!removeNewLines) { + stringBuilder.append(String.format(Locale.ROOT, "%n")); } - System.out.println(bulkResponse.getItems().length + " documents loaded."); - // ensure the documents are searchable - client.admin().indices().prepareRefresh(defaultIndex).execute().actionGet(); - } + line = br.readLine(); + } - public static String getResourceFilePath(String relPath) { - String projectRoot = System.getProperty("project.root", null); - if (projectRoot == null) { - return new File(relPath).getAbsolutePath(); - } else { - return new File(projectRoot + "/" + relPath).getAbsolutePath(); - } + return stringBuilder.toString(); } - - public static String getResponseBody(Response response) throws IOException { - - return getResponseBody(response, false); + } + + /** + * Builds all permutations of the given list of Strings + * + * @param items list of strings to permute + * @return list of permutations + */ + public static List> getPermutations(final List items) { + + if (items.size() > 5) { + throw new IllegalArgumentException("Inefficient test, please refactor"); } - public static String getResponseBody(Response response, boolean retainNewLines) throws IOException { - final StringBuilder sb = new StringBuilder(); + final List> result = new LinkedList<>(); - try (final InputStream is = response.getEntity().getContent(); - final BufferedReader br = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8))) { + if (items.isEmpty() || 1 == items.size()) { - String line; - while ((line = br.readLine()) != null) { - sb.append(line); - if (retainNewLines) { - sb.append(String.format(Locale.ROOT, "%n")); - } - } - } - return sb.toString(); + final List onlyElement = new ArrayList<>(); + if (1 == items.size()) { + onlyElement.add(items.get(0)); + } + result.add(onlyElement); + return result; } - public static String fileToString(final String filePathFromProjectRoot, final boolean removeNewLines) - throws IOException { - - final String absolutePath = getResourceFilePath(filePathFromProjectRoot); - - try (final InputStream stream = new FileInputStream(absolutePath); - final Reader streamReader = new InputStreamReader(stream, StandardCharsets.UTF_8); - final BufferedReader br = new BufferedReader(streamReader)) { - - final StringBuilder stringBuilder = new StringBuilder(); - String line = br.readLine(); - - while (line != null) { - - stringBuilder.append(line); - if (!removeNewLines) { - stringBuilder.append(String.format(Locale.ROOT, "%n")); - } - line = br.readLine(); - } - - return stringBuilder.toString(); - } + for (int i = 0; i < items.size(); ++i) { + + final List smallerSet = new ArrayList<>(); + + if (i != 0) { + smallerSet.addAll(items.subList(0, i)); + } + if (i != items.size() - 1) { + smallerSet.addAll(items.subList(i + 1, items.size())); + } + + final String currentItem = items.get(i); + result.addAll( + getPermutations(smallerSet).stream() + .map( + smallerSetPermutation -> { + final List permutation = new ArrayList<>(); + permutation.add(currentItem); + permutation.addAll(smallerSetPermutation); + return permutation; + }) + .collect(Collectors.toCollection(LinkedList::new))); } - /** - * Builds all permutations of the given list of Strings - * @param items - list of strings to permute - * @return list of permutations - */ - public static List> getPermutations(final List items) { - - if (items.size() > 5) { - throw new IllegalArgumentException("Inefficient test, please refactor"); - } - - final List> result = new LinkedList<>(); - - if (items.isEmpty() || 1 == items.size()) { - - final List onlyElement = new ArrayList<>(); - if (1 == items.size()) { - onlyElement.add(items.get(0)); - } - result.add(onlyElement); - return result; - } - - for (int i = 0; i < items.size(); ++i) { - - final List smallerSet = new ArrayList<>(); - - if (i != 0) { - smallerSet.addAll(items.subList(0, i)); - } - if (i != items.size() - 1) { - smallerSet.addAll(items.subList(i + 1, items.size())); - } - - final String currentItem = items.get(i); - result.addAll(getPermutations(smallerSet).stream().map(smallerSetPermutation -> { - final List permutation = new ArrayList<>(); - permutation.add(currentItem); - permutation.addAll(smallerSetPermutation); - return permutation; - }).collect(Collectors.toCollection(LinkedList::new))); - } - - return result; - } + return result; + } } diff --git a/legacy/src/test/java/org/opensearch/sql/legacy/util/TestsConstants.java b/legacy/src/test/java/org/opensearch/sql/legacy/util/TestsConstants.java index a6b2c84d55..f436cedaaa 100644 --- a/legacy/src/test/java/org/opensearch/sql/legacy/util/TestsConstants.java +++ b/legacy/src/test/java/org/opensearch/sql/legacy/util/TestsConstants.java @@ -3,48 +3,46 @@ * SPDX-License-Identifier: Apache-2.0 */ - package org.opensearch.sql.legacy.util; -/** - * Created by omershelef on 18/12/14. - */ +/** Created by omershelef on 18/12/14. */ public class TestsConstants { - public final static String PERSISTENT = "persistent"; - public final static String TRANSIENT = "transient"; - - public final static String TEST_INDEX = "opensearch-sql_test_index"; - - public final static String TEST_INDEX_ONLINE = TEST_INDEX + "_online"; - public final static String TEST_INDEX_ACCOUNT = TEST_INDEX + "_account"; - public final static String TEST_INDEX_PHRASE = TEST_INDEX + "_phrase"; - public final static String TEST_INDEX_DOG = TEST_INDEX + "_dog"; - public final static String TEST_INDEX_DOG2 = TEST_INDEX + "_dog2"; - public final static String TEST_INDEX_DOG3 = TEST_INDEX + "_dog3"; - public final static String TEST_INDEX_DOGSUBQUERY = TEST_INDEX + "_subquery"; - public final static String TEST_INDEX_PEOPLE = TEST_INDEX + "_people"; - public final static String TEST_INDEX_PEOPLE2 = TEST_INDEX + "_people2"; - public final static String TEST_INDEX_GAME_OF_THRONES = TEST_INDEX + "_game_of_thrones"; - public final static String TEST_INDEX_SYSTEM = TEST_INDEX + "_system"; - public final static String TEST_INDEX_ODBC = TEST_INDEX + "_odbc"; - public final static String TEST_INDEX_LOCATION = TEST_INDEX + "_location"; - public final static String TEST_INDEX_LOCATION2 = TEST_INDEX + "_location2"; - public final static String TEST_INDEX_NESTED_TYPE = TEST_INDEX + "_nested_type"; - public final static String TEST_INDEX_NESTED_SIMPLE = TEST_INDEX + "_nested_simple"; - public final static String TEST_INDEX_NESTED_WITH_QUOTES = TEST_INDEX + "_nested_type_with_quotes"; - public final static String TEST_INDEX_EMPLOYEE_NESTED = TEST_INDEX + "_employee_nested"; - public final static String TEST_INDEX_JOIN_TYPE = TEST_INDEX + "_join_type"; - public final static String TEST_INDEX_BANK = TEST_INDEX + "_bank"; - public final static String TEST_INDEX_BANK_TWO = TEST_INDEX_BANK + "_two"; - public final static String TEST_INDEX_BANK_WITH_NULL_VALUES = TEST_INDEX_BANK + "_with_null_values"; - public final static String TEST_INDEX_ORDER = TEST_INDEX + "_order"; - public final static String TEST_INDEX_WEBLOG = TEST_INDEX + "_weblog"; - public final static String TEST_INDEX_DATE = TEST_INDEX + "_date"; - public final static String TEST_INDEX_DATE_TIME = TEST_INDEX + "_datetime"; - - - public final static String DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"; - public final static String TS_DATE_FORMAT = "yyyy-MM-dd HH:mm:ss.SSS"; - public final static String SIMPLE_DATE_FORMAT = "yyyy-MM-dd"; + public static final String PERSISTENT = "persistent"; + public static final String TRANSIENT = "transient"; + + public static final String TEST_INDEX = "opensearch-sql_test_index"; + + public static final String TEST_INDEX_ONLINE = TEST_INDEX + "_online"; + public static final String TEST_INDEX_ACCOUNT = TEST_INDEX + "_account"; + public static final String TEST_INDEX_PHRASE = TEST_INDEX + "_phrase"; + public static final String TEST_INDEX_DOG = TEST_INDEX + "_dog"; + public static final String TEST_INDEX_DOG2 = TEST_INDEX + "_dog2"; + public static final String TEST_INDEX_DOG3 = TEST_INDEX + "_dog3"; + public static final String TEST_INDEX_DOGSUBQUERY = TEST_INDEX + "_subquery"; + public static final String TEST_INDEX_PEOPLE = TEST_INDEX + "_people"; + public static final String TEST_INDEX_PEOPLE2 = TEST_INDEX + "_people2"; + public static final String TEST_INDEX_GAME_OF_THRONES = TEST_INDEX + "_game_of_thrones"; + public static final String TEST_INDEX_SYSTEM = TEST_INDEX + "_system"; + public static final String TEST_INDEX_ODBC = TEST_INDEX + "_odbc"; + public static final String TEST_INDEX_LOCATION = TEST_INDEX + "_location"; + public static final String TEST_INDEX_LOCATION2 = TEST_INDEX + "_location2"; + public static final String TEST_INDEX_NESTED_TYPE = TEST_INDEX + "_nested_type"; + public static final String TEST_INDEX_NESTED_SIMPLE = TEST_INDEX + "_nested_simple"; + public static final String TEST_INDEX_NESTED_WITH_QUOTES = + TEST_INDEX + "_nested_type_with_quotes"; + public static final String TEST_INDEX_EMPLOYEE_NESTED = TEST_INDEX + "_employee_nested"; + public static final String TEST_INDEX_JOIN_TYPE = TEST_INDEX + "_join_type"; + public static final String TEST_INDEX_BANK = TEST_INDEX + "_bank"; + public static final String TEST_INDEX_BANK_TWO = TEST_INDEX_BANK + "_two"; + public static final String TEST_INDEX_BANK_WITH_NULL_VALUES = + TEST_INDEX_BANK + "_with_null_values"; + public static final String TEST_INDEX_ORDER = TEST_INDEX + "_order"; + public static final String TEST_INDEX_WEBLOG = TEST_INDEX + "_weblog"; + public static final String TEST_INDEX_DATE = TEST_INDEX + "_date"; + public static final String TEST_INDEX_DATE_TIME = TEST_INDEX + "_datetime"; + + public static final String DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"; + public static final String TS_DATE_FORMAT = "yyyy-MM-dd HH:mm:ss.SSS"; + public static final String SIMPLE_DATE_FORMAT = "yyyy-MM-dd"; } diff --git a/opensearch/build.gradle b/opensearch/build.gradle index a2ab670403..34b5c3f452 100644 --- a/opensearch/build.gradle +++ b/opensearch/build.gradle @@ -27,6 +27,7 @@ plugins { id "io.freefair.lombok" id 'jacoco' id 'info.solidsoft.pitest' version '1.9.0' + id 'com.diffplug.spotless' version '6.19.0' } dependencies { @@ -48,8 +49,23 @@ dependencies { testImplementation group: 'org.opensearch.test', name: 'framework', version: "${opensearch_version}" } -checkstyleTest.ignoreFailures = true -checkstyleMain.ignoreFailures = true +spotless { + java { + target fileTree('.') { + include '**/*.java' + exclude '**/build/**', '**/build-*/**' + } + importOrder() +// licenseHeader("/*\n" + +// " * Copyright OpenSearch Contributors\n" + +// " * SPDX-License-Identifier: Apache-2.0\n" + +// " */\n\n") + removeUnusedImports() + trimTrailingWhitespace() + endWithNewline() + googleJavaFormat('1.17.0').reflowLongStrings().groupArtifact('com.google.googlejavaformat:google-java-format') + } +} pitest { targetClasses = ['org.opensearch.sql.*'] diff --git a/plugin/build.gradle b/plugin/build.gradle index 8ec6844bfd..7291f78ba4 100644 --- a/plugin/build.gradle +++ b/plugin/build.gradle @@ -27,6 +27,7 @@ plugins { id "io.freefair.lombok" id 'jacoco' id 'opensearch.opensearchplugin' + id 'com.diffplug.spotless' version '6.19.0' } apply plugin: 'opensearch.pluginzip' @@ -85,9 +86,6 @@ publishing { } } -checkstyleTest.ignoreFailures = true -checkstyleMain.ignoreFailures = true - javadoc.enabled = false loggerUsageCheck.enabled = false dependencyLicenses.enabled = false @@ -119,6 +117,24 @@ compileTestJava { options.compilerArgs.addAll(["-processor", 'lombok.launch.AnnotationProcessorHider$AnnotationProcessor']) } +spotless { + java { + target fileTree('.') { + include '**/*.java' + exclude '**/build/**', '**/build-*/**' + } + importOrder() +// licenseHeader("/*\n" + +// " * Copyright OpenSearch Contributors\n" + +// " * SPDX-License-Identifier: Apache-2.0\n" + +// " */\n\n") + removeUnusedImports() + trimTrailingWhitespace() + endWithNewline() + googleJavaFormat('1.17.0').reflowLongStrings().groupArtifact('com.google.googlejavaformat:google-java-format') + } +} + dependencies { api "com.fasterxml.jackson.core:jackson-core:${versions.jackson}" api "com.fasterxml.jackson.core:jackson-databind:${versions.jackson_databind}" diff --git a/ppl/build.gradle b/ppl/build.gradle index e16b6decfc..a798b3f4b0 100644 --- a/ppl/build.gradle +++ b/ppl/build.gradle @@ -27,13 +27,9 @@ plugins { id "io.freefair.lombok" id 'jacoco' id 'antlr' + id 'com.diffplug.spotless' version '6.19.0' } -// Being ignored as a temporary measure before being removed in favour of -// spotless https://github.com/opensearch-project/sql/issues/1101 -checkstyleTest.ignoreFailures = true -checkstyleMain.ignoreFailures = true - generateGrammarSource { arguments += ['-visitor', '-package', 'org.opensearch.sql.ppl.antlr.parser'] source = sourceSets.main.antlr @@ -65,6 +61,24 @@ dependencies { testImplementation(testFixtures(project(":core"))) } +spotless { + java { + target fileTree('.') { + include '**/*.java' + exclude '**/build/**', '**/build-*/**' + } + importOrder() +// licenseHeader("/*\n" + +// " * Copyright OpenSearch Contributors\n" + +// " * SPDX-License-Identifier: Apache-2.0\n" + +// " */\n\n") + removeUnusedImports() + trimTrailingWhitespace() + endWithNewline() + googleJavaFormat('1.17.0').reflowLongStrings().groupArtifact('com.google.googlejavaformat:google-java-format') + } +} + test { testLogging { events "passed", "skipped", "failed" diff --git a/prometheus/build.gradle b/prometheus/build.gradle index 0d915a6d4a..e98dfd83e4 100644 --- a/prometheus/build.gradle +++ b/prometheus/build.gradle @@ -13,9 +13,6 @@ repositories { mavenCentral() } -checkstyleTest.ignoreFailures = true -checkstyleMain.ignoreFailures = true - dependencies { api project(':core') implementation project(':datasources') diff --git a/protocol/build.gradle b/protocol/build.gradle index dcec1c675b..212f746b1d 100644 --- a/protocol/build.gradle +++ b/protocol/build.gradle @@ -26,6 +26,7 @@ plugins { id 'java' id "io.freefair.lombok" id 'jacoco' + id 'com.diffplug.spotless' version '6.19.0' } dependencies { @@ -43,9 +44,6 @@ dependencies { testImplementation group: 'org.mockito', name: 'mockito-junit-jupiter', version: '3.12.4' } -checkstyleTest.ignoreFailures = true -checkstyleMain.ignoreFailures = true - configurations.all { resolutionStrategy.force "com.fasterxml.jackson.core:jackson-databind:${versions.jackson_databind}" } @@ -58,6 +56,8 @@ test { } } + + jacocoTestReport { reports { html.enabled true @@ -71,6 +71,24 @@ jacocoTestReport { } test.finalizedBy(project.tasks.jacocoTestReport) +spotless { + java { + target fileTree('.') { + include '**/*.java' + exclude '**/build/**', '**/build-*/**' + } + importOrder() +// licenseHeader("/*\n" + +// " * Copyright OpenSearch Contributors\n" + +// " * SPDX-License-Identifier: Apache-2.0\n" + +// " */\n\n") + removeUnusedImports() + trimTrailingWhitespace() + endWithNewline() + googleJavaFormat('1.17.0').reflowLongStrings().groupArtifact('com.google.googlejavaformat:google-java-format') + } +} + jacocoTestCoverageVerification { violationRules { rule { diff --git a/spark/build.gradle b/spark/build.gradle index 2608b88ced..89842e5ea8 100644 --- a/spark/build.gradle +++ b/spark/build.gradle @@ -13,9 +13,6 @@ repositories { mavenCentral() } -checkstyleTest.ignoreFailures = true -checkstyleMain.ignoreFailures = true - dependencies { api project(':core') implementation project(':datasources') diff --git a/sql/build.gradle b/sql/build.gradle index d85cc4ca74..2984158e57 100644 --- a/sql/build.gradle +++ b/sql/build.gradle @@ -27,6 +27,7 @@ plugins { id "io.freefair.lombok" id 'jacoco' id 'antlr' + id 'com.diffplug.spotless' version '6.19.0' } generateGrammarSource { @@ -58,10 +59,23 @@ dependencies { testImplementation(testFixtures(project(":core"))) } -// Being ignored as a temporary measure before being removed in favour of -// spotless https://github.com/opensearch-project/sql/issues/1101 -checkstyleTest.ignoreFailures = true -checkstyleMain.ignoreFailures = true +spotless { + java { + target fileTree('.') { + include '**/*.java' + exclude '**/build/**', '**/build-*/**' + } + importOrder() +// licenseHeader("/*\n" + +// " * Copyright OpenSearch Contributors\n" + +// " * SPDX-License-Identifier: Apache-2.0\n" + +// " */\n\n") + removeUnusedImports() + trimTrailingWhitespace() + endWithNewline() + googleJavaFormat('1.17.0').reflowLongStrings().groupArtifact('com.google.googlejavaformat:google-java-format') + } +} test { useJUnitPlatform()