diff --git a/.codacy.yml b/.codacy.yml index d98077f4fe..cf0315d765 100644 --- a/.codacy.yml +++ b/.codacy.yml @@ -1,3 +1,11 @@ --- +engines: + tsqllint: + exclude_paths: + # These fixtures use PostgreSQL grammar, not Transact-SQL. + - "src/test/resources/postgresql/**" + # MATCH_RECOGNIZE fixtures use Oracle and BigQuery grammar. + - "src/test/resources/net/sf/jsqlparser/statement/select/match-recognize/**" + - "src/test/resources/net/sf/jsqlparser/statement/select/match-recognize-2350.sql" exclude_paths: - "site/**" diff --git a/.github/requirements-docs.txt b/.github/requirements-docs.txt new file mode 100644 index 0000000000..9130aea185 --- /dev/null +++ b/.github/requirements-docs.txt @@ -0,0 +1,14 @@ +# Python dependencies for the Sphinx documentation build (.github/workflows/ci.yml). +# Kept in the repository so actions/setup-python can key its pip cache on it. +# +# Deliberately NOT installed from apt: sphinx-common pulls python3-pil / python3-docutils +# from the runner's stale apt index and shadows this install on PATH. + +sphinx>=7,<9 +manticore_sphinx_theme +sphinx_javadoc_xml +myst_parser +sphinx_substitution_extensions +sphinx_issues +sphinx_inline_tabs +pygments diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a3108f35b5..569d885551 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,103 +2,218 @@ name: CI Pipeline on: push: - branches: [ "**" ] # Run on every commit to any branch + branches: [ "**" ] pull_request: - branches: [ "**" ] # Run for PRs from any branch + branches: [ "**" ] workflow_dispatch: -permissions: write-all +# Least privilege by default; each job widens only what it needs. +permissions: + contents: read + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + +env: + JAVA_VERSION: '17' + JAVA_DISTRIBUTION: 'temurin' jobs: + grammar_conflicts: + name: Grammar Choice Conflicts + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + with: + fetch-depth: 0 + + - name: Set up JDK ${{ env.JAVA_VERSION }} + uses: actions/setup-java@v5 + with: + java-version: ${{ env.JAVA_VERSION }} + distribution: ${{ env.JAVA_DISTRIBUTION }} + + - name: Set up Gradle + uses: gradle/actions/setup-gradle@v4 + with: + cache-read-only: ${{ github.ref != 'refs/heads/master' }} + + # Re-runs JavaCC on the JJTree output and fails on any "Warning: Choice conflict ...". + # `check` already depends on this task, but a dedicated job gives a fast, separately + # named status check that can be made required in branch protection. + - name: Check for grammar ambiguities + run: ./gradlew checkGrammarAmbiguity --no-daemon + + - name: Upload grammar warning report + if: failure() + uses: actions/upload-artifact@v5 + with: + name: javacc-warnings + path: build/reports/grammar/ + retention-days: 7 + if-no-files-found: ignore + gradle_check: - name: Gradle Check + name: Gradle Check (${{ matrix.os }}) runs-on: ${{ matrix.os }} strategy: + fail-fast: false matrix: os: [ ubuntu-latest, windows-latest, macos-latest ] steps: - - uses: actions/checkout@main + - uses: actions/checkout@v5 with: - fetch-depth: 0 + fetch-depth: 0 # required by the git-changelog / version derivation - - name: Set up JDK 17 - uses: actions/setup-java@main + - name: Set up JDK ${{ env.JAVA_VERSION }} + uses: actions/setup-java@v5 with: - java-version: '17' - distribution: 'temurin' + java-version: ${{ env.JAVA_VERSION }} + distribution: ${{ env.JAVA_DISTRIBUTION }} - name: Set up Gradle - uses: gradle/actions/setup-gradle@main + uses: gradle/actions/setup-gradle@v4 + with: + cache-read-only: ${{ github.ref != 'refs/heads/master' }} - name: Run Gradle Check - run: ./gradlew check + run: ./gradlew check --no-daemon + + - name: Upload test reports + if: always() + uses: actions/upload-artifact@v5 + with: + name: test-reports-${{ matrix.os }} + path: | + build/reports/ + build/test-results/ + retention-days: 7 + if-no-files-found: ignore maven_verify: - name: Maven Verify - needs: gradle_check # ✅ Run only after Gradle check succeeds + name: Maven Verify (${{ matrix.os }}) + needs: gradle_check runs-on: ${{ matrix.os }} strategy: + fail-fast: false matrix: -# os: [ ubuntu-latest, windows-latest, macos-latest ] - os: [ ubuntu-latest, macos-latest ] + os: [ ubuntu-latest, windows-latest, macos-latest ] steps: - - uses: actions/checkout@main + - uses: actions/checkout@v5 with: fetch-depth: 0 - - name: Set up JDK 17 - uses: actions/setup-java@main + - name: Set up JDK ${{ env.JAVA_VERSION }} + uses: actions/setup-java@v5 with: - java-version: '17' - distribution: 'temurin' + java-version: ${{ env.JAVA_VERSION }} + distribution: ${{ env.JAVA_DISTRIBUTION }} + cache: 'maven' - name: Run Maven Verify - run: mvn --batch-mode verify + run: mvn --batch-mode --no-transfer-progress verify gradle_publish: - name: Gradle Publish - needs: [ gradle_check, maven_verify ] # ✅ Run only after both succeed - if: github.ref == 'refs/heads/master' && github.repository == 'JSQLParser/JSqlParser' # ✅ Only for master branch of main repo + name: Gradle Publish (Maven Central) + needs: [ gradle_check, maven_verify ] + if: github.ref == 'refs/heads/master' && github.repository == 'JSQLParser/JSqlParser' runs-on: ubuntu-latest + permissions: + contents: read steps: - - uses: actions/checkout@main + - uses: actions/checkout@v5 with: fetch-depth: 0 - - name: Set up JDK 17 - uses: actions/setup-java@main + - name: Set up JDK ${{ env.JAVA_VERSION }} + uses: actions/setup-java@v5 with: - java-version: '17' - distribution: 'temurin' + java-version: ${{ env.JAVA_VERSION }} + distribution: ${{ env.JAVA_DISTRIBUTION }} - - name: Build with Gradle - uses: gradle/actions/setup-gradle@main + - name: Set up Gradle + uses: gradle/actions/setup-gradle@v4 - name: Publish with Gradle - run: ./gradlew publish + # Gradle only picks up a secret as a project property when it is exported with the + # ORG_GRADLE_PROJECT_ prefix. Plain `ossrhUsername: ...` was an ordinary environment + # variable that nothing ever read. + # + # No RELEASE variable here on purpose: getVersion() then produces a -SNAPSHOT version and + # the plugin routes it to the Central Portal snapshot repository. Set RELEASE=true only + # when you actually intend to cut a release. + run: ./gradlew publishToMavenCentral --no-daemon env: - ossrhUsername: ${{ secrets.OSSRHUSERNAME }} - ossrhPassword: ${{ secrets.OSSRHPASSWORD }} + ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.mavenCentralUsername }} + ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.mavenCentralPassword }} + ORG_GRADLE_PROJECT_signingInMemoryKey: ${{ secrets.signingInMemoryKey }} + ORG_GRADLE_PROJECT_signingInMemoryKeyId: ${{ secrets.signingInMemoryKeyId }} + ORG_GRADLE_PROJECT_signingInMemoryKeyPassword: ${{ secrets.signingInMemoryKeyPassword }} + + deploy_docs: + name: Build & Deploy Sphinx Docs + needs: [ gradle_check, maven_verify ] + if: github.ref == 'refs/heads/master' && github.repository == 'JSQLParser/JSqlParser' + runs-on: ubuntu-latest + permissions: + contents: read + pages: write + id-token: write + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + steps: + - uses: actions/checkout@v5 + with: + fetch-depth: 0 # gitChangelogTask needs full history - - uses: actions/setup-python@main + - name: Set up JDK ${{ env.JAVA_VERSION }} + uses: actions/setup-java@v5 + with: + java-version: ${{ env.JAVA_VERSION }} + distribution: ${{ env.JAVA_DISTRIBUTION }} - - name: Install XSLT Processor - run: sudo apt-get install -y xsltproc sphinx-common + - name: Set up Gradle + uses: gradle/actions/setup-gradle@v4 + + # cache: 'pip' needs a dependency file to key on. setup-python only looks for + # **/requirements.txt or **/pyproject.toml by default, neither of which exists in this + # repo — hence "No file ... matched". cache-dependency-path points it at the file we keep. + - uses: actions/setup-python@v6 + with: + python-version: '3.12' + cache: 'pip' + cache-dependency-path: '.github/requirements-docs.txt' + + # Only xsltproc comes from apt. `sphinx-common` used to be installed here as well; it + # pulled in python3-sphinx / python3-docutils / python3-pil from the runner's stale apt + # index (hence the 404 on python3-pil 10.2.0-1ubuntu1.2) and shadowed the pip-installed + # Sphinx that actually knows about manticore_sphinx_theme. `apt-get update` first, because + # the pre-baked index on the runner image is regularly older than the mirror. + - name: Install XSLT processor + run: | + sudo apt-get update + sudo apt-get install -y --no-install-recommends xsltproc - name: Install Python dependencies - run: pip install manticore_sphinx_theme sphinx_javadoc_xml myst_parser sphinx_substitution_extensions sphinx_issues sphinx_inline_tabs pygments + run: | + python -m pip install --upgrade pip + python -m pip install -r .github/requirements-docs.txt - name: Build Sphinx documentation with Gradle - run: FLOATING_TOC=false ./gradlew -DFLOATING_TOC=false gitChangelogTask renderRR xslt xmldoc sphinx + env: + FLOATING_TOC: 'false' + run: ./gradlew -DFLOATING_TOC=false --no-daemon gitChangelogTask renderRR xslt xmldoc sphinx - name: Configure GitHub Pages - uses: actions/configure-pages@main + uses: actions/configure-pages@v5 - name: Upload artifact - uses: actions/upload-pages-artifact@main + uses: actions/upload-pages-artifact@v5 with: path: 'build/sphinx' - name: Deploy to GitHub Pages id: deployment - uses: actions/deploy-pages@main + uses: actions/deploy-pages@v5 \ No newline at end of file diff --git a/.gitignore b/.gitignore index 955e7bf2da..bab2fa1408 100644 --- a/.gitignore +++ b/.gitignore @@ -7,11 +7,11 @@ /src/site/sphinx/_themes # Exclude the Auto-generated Changelog -/src/site/sphinx/changelog.rst -/src/site/sphinx/syntax_stable.rst -/src/site/sphinx/syntax_snapshot.rst -/src/site/sphinx/javadoc_stable.xml -/src/site/sphinx/javadoc_snapshot.xml +src/site/sphinx/changelog.rst +src/site/sphinx/syntax_stable.rst +src/site/sphinx/syntax_snapshot.rst +src/site/sphinx/javadoc_stable.xml +src/site/sphinx/javadoc_snapshot.xml # Generated by javacc-maven-plugin /bin diff --git a/.mvn/jvm.config b/.mvn/jvm.config new file mode 100644 index 0000000000..7fecadb38e --- /dev/null +++ b/.mvn/jvm.config @@ -0,0 +1 @@ +-Dfile.encoding=UTF-8 diff --git a/CHANGELOG.md b/CHANGELOG.md index 3637519ab1..4d354ae2ac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,37 +2,909 @@ Changelog of JSqlParser. -## jsqlparser-5.4 (2025-05-25) +## jsqlparser-5.4 (2026-09-13) ### Features -- Session Statement ([7d2e6](https://github.com/JSQLParser/JSqlParser/commit/7d2e6b65324ce57) manticore-projects) -- sync with Master ([e14d7](https://github.com/JSQLParser/JSqlParser/commit/e14d7eb1c4e9963) manticore-projects) -- JavaCC 8 keyword utils ([cfe2d](https://github.com/JSQLParser/JSqlParser/commit/cfe2d8ccaf7c76d) manticore-projects) -- Complete on JavaCC-8 ([1b7ed](https://github.com/JSQLParser/JSqlParser/commit/1b7ed2d7be000ce) manticore-projects) -- Optimise performance ([e91c4](https://github.com/JSQLParser/JSqlParser/commit/e91c480b0bbe0a9) manticore-projects) -- avoid looping through the tokens every single time ([b18fb](https://github.com/JSQLParser/JSqlParser/commit/b18fbca1f48e63e) manticore-projects) -- avoid looping through the tokens every single time ([7ac6c](https://github.com/JSQLParser/JSqlParser/commit/7ac6cd0fa08d713) manticore-projects) -- add proper JMH benchmarks ([21c98](https://github.com/JSQLParser/JSqlParser/commit/21c983fc1f4f3f2) manticore-projects) -- JavaCC-8 ([9d144](https://github.com/JSQLParser/JSqlParser/commit/9d1442e9a4800e2) manticore-projects) -- remove all semantic lookaheads ([5abca](https://github.com/JSQLParser/JSqlParser/commit/5abcaeaede27cfe) manticore-projects) +- parse SQL large-object type names through shared column types (#2617) ([ed6c4](https://github.com/JSQLParser/JSqlParser/commit/ed6c499277b4343) Minjae Lee) +- preserve column default expressions in table definitions (#2616) ([2aa0d](https://github.com/JSQLParser/JSqlParser/commit/2aa0d8dab6e04ee) Minjae Lee) +- reuse table constraint grammar for PostgreSQL ALTER ADD (#2612) ([e4cf3](https://github.com/JSQLParser/JSqlParser/commit/e4cf31654c4b0e6) Minjae Lee) +- preserve ALTER COLUMN default expressions in the AST (#2611) ([08de1](https://github.com/JSQLParser/JSqlParser/commit/08de14159c1f775) Minjae Lee) +- **parser** support opt-in legacy MySQL GROUP BY ordering (#2595) ([7cc86](https://github.com/JSQLParser/JSqlParser/commit/7cc863863170c79) Minjae Lee) +- **parser** model PostgreSQL type domain and extension DDL (#2553) ([7e15a](https://github.com/JSQLParser/JSqlParser/commit/7e15aaedfb06558) Minjae Lee) +- **parser** model PostgreSQL schema DDL clauses (#2552) ([7b9d4](https://github.com/JSQLParser/JSqlParser/commit/7b9d4208d5e42ad) Minjae Lee) +- **parser** parse navigation chains after array subscripts (#2550) ([54a74](https://github.com/JSQLParser/JSqlParser/commit/54a7499579c6bbf) fudian) +- **statement** add StmtFeature classification via Statement#getFeatures() ([22023](https://github.com/JSQLParser/JSqlParser/commit/22023a48404140e) Andreas Reichel) +- **parser** enhance PostgreSQL CREATE POLICY support (#2549) ([ff2bc](https://github.com/JSQLParser/JSqlParser/commit/ff2bc8d29a00a8c) Minjae Lee) +- **parser** model MySQL CREATE TRIGGER (#2548) ([03b8f](https://github.com/JSQLParser/JSqlParser/commit/03b8fc407b32ca3) Minjae Lee) +- **parser** model MySQL event statements (#2547) ([b67a5](https://github.com/JSQLParser/JSqlParser/commit/b67a52d0f5e4531) Minjae Lee) +- **parser** model MySQL CREATE USER (#2546) ([628e3](https://github.com/JSQLParser/JSqlParser/commit/628e3c1d08fd62c) Minjae Lee) +- **parser** model MySQL schema options (#2544) ([897fb](https://github.com/JSQLParser/JSqlParser/commit/897fbd4c6549e3b) Minjae Lee) +- **parser** model ColDataType precision and scale as structured properties (#2539) ([c919a](https://github.com/JSQLParser/JSqlParser/commit/c919a0c9b316c8d) Minjae Lee) +- **parser** add column data type fragment parser (#2531) ([6413a](https://github.com/JSQLParser/JSqlParser/commit/6413a84724cc309) Minjae Lee) +- **parser** add BIGQUERY, DATABRICKS and SNOWFLAKE dialect presets (#2515) ([e7167](https://github.com/JSQLParser/JSqlParser/commit/e7167cc7dc15ce5) fudian) +- **parser** concatenate adjacent string literals behind adjacentStringLiterals (#2514) ([84d23](https://github.com/JSQLParser/JSqlParser/commit/84d23e7dfae5ac8) fudian) +- **parser** support double quoted string literals behind allowDoubleQuotedStrings (#2513) ([c86cf](https://github.com/JSQLParser/JSqlParser/commit/c86cf6ad2ce210b) fudian) +- **parser** turn on the dialect's lexer feature preset in withDialect (#2511) ([94c45](https://github.com/JSQLParser/JSqlParser/commit/94c4508bd381912) fudian) +- **parser** support MySQL # line comments behind allowHashLineComments (#2508) ([177db](https://github.com/JSQLParser/JSqlParser/commit/177dbd716f0a3c3) fudian) +- **parser** support DuckDB MAP literals and PIVOT queries (#2496) ([e1301](https://github.com/JSQLParser/JSqlParser/commit/e13019a393d9229) Liming Deng) +- **parser** support MySQL PROCEDURE ANALYSE clause (#2492) ([98bd0](https://github.com/JSQLParser/JSqlParser/commit/98bd01de38de073) Liming Deng) +- parse BigQuery JSON string literals as implicit casts (#2488) ([15e58](https://github.com/JSQLParser/JSqlParser/commit/15e58e92e1a9692) Hayssam Saleh) +- complete SQL Server OPTION query hint support (#2486) ([4c6a4](https://github.com/JSQLParser/JSqlParser/commit/4c6a4fb141db7db) fudian) +- **parser** add support for ClickHouse [LEFT] ARRAY JOIN syntax (#2482) ([5c868](https://github.com/JSQLParser/JSqlParser/commit/5c86877dfa8f3d3) Watashi00) +- support CREATE DATABASE statement (#2070) (#2471) ([7b648](https://github.com/JSQLParser/JSqlParser/commit/7b648250618299e) fudian) +- support SQL Server OPTION query hints (#161) (#2472) ([13894](https://github.com/JSQLParser/JSqlParser/commit/138947d6b1d0d28) fudian) +- add support for the XMLTABLE table function (#2458) ([d51ee](https://github.com/JSQLParser/JSqlParser/commit/d51ee1c166055d3) fudian) +- support MERGE ... WHEN NOT MATCHED BY TARGET / BY SOURCE (#2421) (#2453) ([25f30](https://github.com/JSQLParser/JSqlParser/commit/25f3040e3938fb3) fudian) +- add ForUpdateClause class with multi-table and ORDER BY support (#2426) ([2b141](https://github.com/JSQLParser/JSqlParser/commit/2b141568e8b76ed) Rogerio Robetti) +- Add bytecode size verification tests for generated parser classes ([9cfbe](https://github.com/JSQLParser/JSqlParser/commit/9cfbe681e36e638) manticore-projects) +- Optimize SimpleCharStream for SQL parsing hot path ([17448](https://github.com/JSQLParser/JSqlParser/commit/17448bb074e6371) manticore-projects) +- PRATT 7.1 ms/ops, no errors ([2d2ee](https://github.com/JSQLParser/JSqlParser/commit/2d2ee4b569e45df) manticore-projects) +- Pratt 8.5 ms/ops without failures ([36507](https://github.com/JSQLParser/JSqlParser/commit/365078db38dfa14) manticore-projects) +- Pratt 6.5 ms/ops with 14 failures ([53ca0](https://github.com/JSQLParser/JSqlParser/commit/53ca03861bb93b2) manticore-projects) +- rework tokens and preserved keywords ([08d0b](https://github.com/JSQLParser/JSqlParser/commit/08d0bcc9cb0efdf) manticore-projects) +- Allow more Function keyword arguments ([cd71a](https://github.com/JSQLParser/JSqlParser/commit/cd71aada3897745) manticore-projects) +- Fix GROUP_CONCAT SEPARATOR to accept expressions, not just string literals ([ff28f](https://github.com/JSQLParser/JSqlParser/commit/ff28f826bcf89c3) manticore-projects) +- support PostgreSQL 18 RETURNING OLD/NEW (#2398) ([f47a8](https://github.com/JSQLParser/JSqlParser/commit/f47a8b30b303e76) Liming Deng) +- support EXPLAIN for DML and simplify ExplainStatement (#2396) ([b19d5](https://github.com/JSQLParser/JSqlParser/commit/b19d556eb57f701) Liming Deng) +- add support for Oracle INSERT ALL/FIRST with WHEN branches (#2394) ([4f982](https://github.com/JSQLParser/JSqlParser/commit/4f982e74aee7f12) Liming Deng) +- add PostgreSQL Row Level Security (RLS) support (#2345) ([999cd](https://github.com/JSQLParser/JSqlParser/commit/999cdca2ac1be89) Eyal Gehasie) +- `DateUnitExpression` for parsing `HOUR`, `DAY`, `MONTH` etc. in Date Functions not as column ([4fdfa](https://github.com/JSQLParser/JSqlParser/commit/4fdfa785dcfd2d5) Andreas Reichel) +- normalised backtick quotes ([c60ff](https://github.com/JSQLParser/JSqlParser/commit/c60ff73909717be) Andreas Reichel) +- normalised backtick quotes ([619f8](https://github.com/JSQLParser/JSqlParser/commit/619f8dadb6ce942) Andreas Reichel) +- `TRY_CONVERT` and `SAFE_CONVERT` support ([9dfa0](https://github.com/JSQLParser/JSqlParser/commit/9dfa0d68af7abd3) Andreas Reichel) +- `SessionStatement` with options ([6c98f](https://github.com/JSQLParser/JSqlParser/commit/6c98f10f2d83342) Andreas Reichel) +- `SessionStatement` with options ([595d4](https://github.com/JSQLParser/JSqlParser/commit/595d4efa32f73b7) Andreas Reichel) +- `SessionStatement` with options ([55a6c](https://github.com/JSQLParser/JSqlParser/commit/55a6c465e49aeb2) Andreas Reichel) +- split catalog and schema ([4ff5c](https://github.com/JSQLParser/JSqlParser/commit/4ff5cc9830ef2e3) Andreas Reichel) +- split catalog and schema ([55201](https://github.com/JSQLParser/JSqlParser/commit/552019a56e30690) Andreas Reichel) +- `CREATE SCHEMA` with catalog ([ac46c](https://github.com/JSQLParser/JSqlParser/commit/ac46c4346eafcf8) Andreas Reichel) +- enable session with catalog ([30144](https://github.com/JSQLParser/JSqlParser/commit/30144e72ab38ccd) Andreas Reichel) +- **parser** allow COLLATE in ORDER BY clauses (issue #2245) (#2277) ([8810c](https://github.com/JSQLParser/JSqlParser/commit/8810c016c3c83ea) Sam Sovereign) +- Oracle hierarchical queries to except `Expression` in the operator ([624a7](https://github.com/JSQLParser/JSqlParser/commit/624a768b2eeae67) Andreas Reichel) +- `CORRESPONDING` modifier for `SetOperator` ([5fe93](https://github.com/JSQLParser/JSqlParser/commit/5fe938bc369a83b) Andreas Reichel) +- add support for DuckDB `CREATE TABLE` with `STRUCT(..)` columns ([0e171](https://github.com/JSQLParser/JSqlParser/commit/0e1715e9b07f293) Andreas Reichel) +- BigQuery `Historic Version` syntax ([5fa07](https://github.com/JSQLParser/JSqlParser/commit/5fa071ef240a82b) manticore-projects) +- Databricks `Temporal spec` syntax ([df5e6](https://github.com/JSQLParser/JSqlParser/commit/df5e6690ea24485) manticore-projects) +- Snowflake time travel syntax ([6f4c4](https://github.com/JSQLParser/JSqlParser/commit/6f4c4fb2dd441e4) manticore-projects) +- syntax sugar ([7c371](https://github.com/JSQLParser/JSqlParser/commit/7c371718678ee78) manticore-projects) +- more useful VisitorAdaptors ([7047c](https://github.com/JSQLParser/JSqlParser/commit/7047c187f85fcf1) manticore-projects) +- more useful VisitorAdaptors ([c6278](https://github.com/JSQLParser/JSqlParser/commit/c62781a3e16dafb) manticore-projects) +- make the various Visitor Adaptors more useful ([066e1](https://github.com/JSQLParser/JSqlParser/commit/066e1bbb7335aa2) manticore-projects) +- better defaults for `FromItemVisitorAdaptor` ([7feb3](https://github.com/JSQLParser/JSqlParser/commit/7feb3bc18a305f0) manticore-projects) +- support distinctrow keyword (#2238) ([e17cd](https://github.com/JSQLParser/JSqlParser/commit/e17cdef4275fa2b) Emily Ong) +- add Features for `dialect` and `allowedNestingDepth` ([296d0](https://github.com/JSQLParser/JSqlParser/commit/296d0321af58abc) manticore-projects) +- add fields holding the actual resolved table ([2500e](https://github.com/JSQLParser/JSqlParser/commit/2500e1ba72718a3) manticore-projects) +- `WITH ... AS NOT MATERIALIZED` ([2f6af](https://github.com/JSQLParser/JSqlParser/commit/2f6afbc3eb16d17) manticore-projects) +- SQL:2016 `BETWEEN [SYMMETRIC | ASYMMETRIC]` ([001ad](https://github.com/JSQLParser/JSqlParser/commit/001ad1c24c2a464) manticore-projects) +- Session Statement ([f0c09](https://github.com/JSQLParser/JSqlParser/commit/f0c098cec93656b) manticore-projects) +- Session Statement ([7d2e6](https://github.com/JSQLParser/JSqlParser/commit/7d2e6b65324ce57) manticore-projects) +- sync with Master ([e14d7](https://github.com/JSQLParser/JSqlParser/commit/e14d7eb1c4e9963) manticore-projects) +- JavaCC 8 keyword utils ([cfe2d](https://github.com/JSQLParser/JSqlParser/commit/cfe2d8ccaf7c76d) manticore-projects) +- Complete on JavaCC-8 ([1b7ed](https://github.com/JSQLParser/JSqlParser/commit/1b7ed2d7be000ce) manticore-projects) +- Optimise performance ([e91c4](https://github.com/JSQLParser/JSqlParser/commit/e91c480b0bbe0a9) manticore-projects) +- avoid looping through the tokens every single time ([b18fb](https://github.com/JSQLParser/JSqlParser/commit/b18fbca1f48e63e) manticore-projects) +- avoid looping through the tokens every single time ([7ac6c](https://github.com/JSQLParser/JSqlParser/commit/7ac6cd0fa08d713) manticore-projects) +- add proper JMH benchmarks ([21c98](https://github.com/JSQLParser/JSqlParser/commit/21c983fc1f4f3f2) manticore-projects) +- JavaCC-8 ([9d144](https://github.com/JSQLParser/JSqlParser/commit/9d1442e9a4800e2) manticore-projects) +- remove all semantic lookaheads ([5abca](https://github.com/JSQLParser/JSqlParser/commit/5abcaeaede27cfe) manticore-projects) ### Bug Fixes -- bring back `SYNTACTIC LOOKAHEAD` where it makes sense ([b3c5b](https://github.com/JSQLParser/JSqlParser/commit/b3c5b63344de193) manticore-projects) -- the Quotes Token manipulation ([bad81](https://github.com/JSQLParser/JSqlParser/commit/bad818e0b872c6a) manticore-projects) +- visit pipe expressions through shared deparser paths (#2618) ([537f3](https://github.com/JSQLParser/JSqlParser/commit/537f3a6dd1e35e7) Minjae Lee) +- span a binary condition's node from its left operand (#2615) ([0ecf0](https://github.com/JSQLParser/JSqlParser/commit/0ecf096d23acc1b) Knut Wannheden) +- preserve ordered table elements through legacy list edits (#2607) ([eb27c](https://github.com/JSQLParser/JSqlParser/commit/eb27c78bebccdb7) Minjae Lee) +- recognise set operation quantifiers written in any case (#2614) ([537c8](https://github.com/JSQLParser/JSqlParser/commit/537c8c1bee16907) Knut Wannheden) +- traverse and deparse structured table partitions (#2610) ([ef42d](https://github.com/JSQLParser/JSqlParser/commit/ef42d0b6ee91f65) Minjae Lee) +- share expression-aware table constraint rendering (#2609) ([9caed](https://github.com/JSQLParser/JSqlParser/commit/9caed4170713aed) Minjae Lee) +- preserve structured column options when editing specifications (#2608) ([fec4e](https://github.com/JSQLParser/JSqlParser/commit/fec4ec40e15ef53) Minjae Lee) +- keep CREATE TABLE option tokens in sync with typed options (#2606) ([d730b](https://github.com/JSQLParser/JSqlParser/commit/d730bf00fee62a0) Minjae Lee) +- **grammar** resolve JavaCC choice conflicts and gate the build against new ones ([8bb33](https://github.com/JSQLParser/JSqlParser/commit/8bb338572440cad) Andreas Reichel) +- **parser** preserve Oracle PRIOR position on all condition expressions (#2601) (#2602) ([37744](https://github.com/JSQLParser/JSqlParser/commit/3774451c3fa7f3e) fudian) +- **parser** parse Oracle outer join operator (+) after LIKE / SIMILAR TO operands (#2598) (#2600) ([35f84](https://github.com/JSQLParser/JSqlParser/commit/35f84d5a006c382) fudian) +- **parser** classify ODBC timestamp interval arguments (#2596) ([a7a5f](https://github.com/JSQLParser/JSqlParser/commit/a7a5fa5032447b2) Minjae Lee) +- **parser** resolve shadowed contextual keyword tokens (#2597) ([51e29](https://github.com/JSQLParser/JSqlParser/commit/51e29a042ce4a50) Minjae Lee) +- **parser** align empty statement input handling (#2594) ([1bef5](https://github.com/JSQLParser/JSqlParser/commit/1bef513f6f9fc7c) Minjae Lee) +- **parser** parse COMMENT ON ... IS NULL as comment removal (#2562) (#2563) ([b2115](https://github.com/JSQLParser/JSqlParser/commit/b2115ac8868dc8e) Paul_Kim) +- **parser** parse Oracle outer join operator (+) on BETWEEN operands (#672) (#2564) ([5bdd4](https://github.com/JSQLParser/JSqlParser/commit/5bdd4bf04ff884f) fudian) +- **parser** parse stacked NOT prefixes as nested NotExpression (#1170) (#2556) ([6c726](https://github.com/JSQLParser/JSqlParser/commit/6c726d8800ba1cc) fudian) +- **parser** parse INSERT ON CONFLICT after a join's trailing ON (#2551) ([ae37f](https://github.com/JSQLParser/JSqlParser/commit/ae37fd2aa06c324) fudian) +- **adapter** connect StatementVisitorAdapter sub-visitors and traverse DML CTEs ([1b57b](https://github.com/JSQLParser/JSqlParser/commit/1b57bfa41cef000) Andreas Reichel) +- **parser** support MySQL table rename variants (#2543) ([bb377](https://github.com/JSQLParser/JSqlParser/commit/bb377195dd622bd) Minjae Lee) +- **parser** model MySQL ALTER TABLE partition operations (#2540) ([80305](https://github.com/JSQLParser/JSqlParser/commit/80305c756fae362) Minjae Lee) +- **parser** model MySQL column type modifiers (#2537) ([bb2f2](https://github.com/JSQLParser/JSqlParser/commit/bb2f269e3b7489e) Minjae Lee) +- **parser** preserve unique index comments in ALTER TABLE (#2533) ([2243e](https://github.com/JSQLParser/JSqlParser/commit/2243e2ba78aa3d0) Minjae Lee) +- **parser** distinguish MySQL constraint and index names (#2534) ([bc9ca](https://github.com/JSQLParser/JSqlParser/commit/bc9ca85ecf1fd30) Minjae Lee) +- **parser** model PostgreSQL CREATE INDEX clauses (#2536) ([3ba25](https://github.com/JSQLParser/JSqlParser/commit/3ba25467ab9d14c) Minjae Lee) +- **parser** model PostgreSQL declarative partitions (#2535) ([c77bb](https://github.com/JSQLParser/JSqlParser/commit/c77bb0f0aa2c8d0) Minjae Lee) +- **parser** model ALTER COLUMN nullability actions (#2529) ([8397d](https://github.com/JSQLParser/JSqlParser/commit/8397d3a225e3fb3) Minjae Lee) +- **parser** allow VERSION in ALTER column clauses (#2530) ([a6da4](https://github.com/JSQLParser/JSqlParser/commit/a6da40051291b3c) Minjae Lee) +- memoize long-chain predicate walks behind a size threshold (#2520) ([33b03](https://github.com/JSQLParser/JSqlParser/commit/33b03e80b3bcd8e) fudian) +- prevent bare partition columns from inheriting the previous item's value (#2518) ([9a32f](https://github.com/JSQLParser/JSqlParser/commit/9a32ff568f58017) fudian) +- collect tables from all SELECT and DML trailing clauses in TablesNamesFinder (#2517) ([86418](https://github.com/JSQLParser/JSqlParser/commit/864181b74e42e8b) fudian) +- **parser** grammar ambiguities ([9c765](https://github.com/JSQLParser/JSqlParser/commit/9c76556d9b5910b) Andreas Reichel) +- **parser** parse qualified columns in GROUP BY (#2504) ([dff72](https://github.com/JSQLParser/JSqlParser/commit/dff722b593ed4e4) Chen Jianjin) +- **parser** support CAST to an array type for MySQL multi-valued indexes (#2506) ([e781b](https://github.com/JSQLParser/JSqlParser/commit/e781b3f27720347) Minjae Lee) +- **parser** support PostgreSQL # binary operator (bitwise XOR / geometric intersection) (#2507) ([d8d05](https://github.com/JSQLParser/JSqlParser/commit/d8d054aa6038a47) fudian) +- **parser** support MySQL index options and prefix-length key parts in index DDL (#2505) ([9fc38](https://github.com/JSQLParser/JSqlParser/commit/9fc38bd13bdaa7b) Minjae Lee) +- **parser** support leading CONTENT/DOCUMENT keyword in XMLPARSE (#2501) ([46584](https://github.com/JSQLParser/JSqlParser/commit/46584159b957706) Liming Deng) +- **parser** support CHARACTER SET in CAST CHAR types (#2497) ([1e4e9](https://github.com/JSQLParser/JSqlParser/commit/1e4e92bafdfd0fa) Liming Deng) +- **parser** support Oracle USING INDEX TABLESPACE in ALTER TABLE ADD CONSTRAINT (#2498) ([aa7df](https://github.com/JSQLParser/JSqlParser/commit/aa7df62608b6a88) Liming Deng) +- **parser** support IF EXISTS in ALTER TABLE MODIFY COLUMN (#2494) ([44c0f](https://github.com/JSQLParser/JSqlParser/commit/44c0fe50747fbe6) Liming Deng) +- support character set introduced string literals (#2493) ([158b5](https://github.com/JSQLParser/JSqlParser/commit/158b5800b4fd370) Liming Deng) +- support Oracle inline constraint states in CREATE TABLE (#2491) ([f5ecb](https://github.com/JSQLParser/JSqlParser/commit/f5ecba7dc51fab7) Liming Deng) +- keep all clauses when deparsing SELECT without FROM (#2483) (#2484) ([70139](https://github.com/JSQLParser/JSqlParser/commit/70139091d9d78f2) fudian) +- complete TablesNamesFinder traversal for piped queries, DML side clauses and analytic functions (#2478) (#2479) ([ad69e](https://github.com/JSQLParser/JSqlParser/commit/ad69eccc9928b92) fudian) +- reject illegal MERGE WHEN NOT MATCHED clause pairings (#2480) (#2481) ([a8cb4](https://github.com/JSQLParser/JSqlParser/commit/a8cb410ad118e78) fudian) +- treat a positional parameter '?' as a valid end of the ternary then-branch (#2477) ([f41c0](https://github.com/JSQLParser/JSqlParser/commit/f41c0b8aa29cd22) fudian) +- do not treat a named parameter ':' as the ternary else separator (#2476) ([5080d](https://github.com/JSQLParser/JSqlParser/commit/5080d19fab90463) fudian) +- allow GROUPS as identifier by making it a non-reserved keyword (#2473) (#2474) ([edfbb](https://github.com/JSQLParser/JSqlParser/commit/edfbb0ac050afa0) fudian) +- support ClickHouse ORDER BY WITH FILL and INTERPOLATE (#2467) (#2469) ([406a4](https://github.com/JSQLParser/JSqlParser/commit/406a4d41341d30f) fudian) +- support ClickHouse C-style ternary operator (? :) in expressions (#2436) (#2466) ([60b08](https://github.com/JSQLParser/JSqlParser/commit/60b0875abc1b2a5) fudian) +- allow scalar subquery as LIMIT row count (#2359) (#2464) ([2989c](https://github.com/JSQLParser/JSqlParser/commit/2989cb1d9c76774) fudian) +- support PostgreSQL composite row expansion (function()).* (#2463) ([7ced3](https://github.com/JSQLParser/JSqlParser/commit/7ced34d2bdee536) fudian) +- support SQL Server CREATE INDEX INCLUDE columns (#2462) ([b5adb](https://github.com/JSQLParser/JSqlParser/commit/b5adb60628ea504) Chen Jianjin) +- model interval qualifier as a structured property (#1728) (#2456) ([c9930](https://github.com/JSQLParser/JSqlParser/commit/c9930e36804d639) fudian) +- support PostgreSQL GROUPS window frames (#2460) ([d92f4](https://github.com/JSQLParser/JSqlParser/commit/d92f4074d3e1267) Chen Jianjin) +- **parser** keep parenthesized multi-param lambda as first function arg (#2455) ([b444b](https://github.com/JSQLParser/JSqlParser/commit/b444bc2a894dbdb) Ivan Clark) +- allow UNIQUE INDEX in CREATE TABLE (#2457) ([32a79](https://github.com/JSQLParser/JSqlParser/commit/32a79495a752f06) fudian) +- support ClickHouse tuple positional access (#2454) ([5bec7](https://github.com/JSQLParser/JSqlParser/commit/5bec712642fd267) Chen Jianjin) +- stop 0x hex literal from swallowing trailing whitespace (#2435) (#2450) ([18b12](https://github.com/JSQLParser/JSqlParser/commit/18b12f1864de80a) fudian) +- allow arbitrary column aliases in LATERAL VIEW (#2452) ([f92f5](https://github.com/JSQLParser/JSqlParser/commit/f92f5e5afd7d8b0) fudian) +- accept VISIBLE/INVISIBLE index and column attributes in CREATE TABLE and CREATE INDEX (#2449) ([aa542](https://github.com/JSQLParser/JSqlParser/commit/aa542c0822e8d00) Armand Parajon) +- support nested parametric data types as CAST target (#2451) ([3997b](https://github.com/JSQLParser/JSqlParser/commit/3997ba4347981e7) fudian) +- add 'K_TYPE' to KeywordOrIdentifier to allow 'type' as a column name (#2448) ([f0ba1](https://github.com/JSQLParser/JSqlParser/commit/f0ba1235028f37a) Minjae Lee) +- **parser** consume nested block comments in a MORE action ([83d7b](https://github.com/JSQLParser/JSqlParser/commit/83d7b5ac46b812c) Andreas Reichel) +- **test** resolve classes dir via CodeSource URI to unbreak Windows ([49622](https://github.com/JSQLParser/JSqlParser/commit/496221a653af093) Andreas Reichel) +- **grammar** use `TokenMgrException` instead of old `TokenMgrError` ([850fb](https://github.com/JSQLParser/JSqlParser/commit/850fb70d08de3c7) Andreas Reichel) +- **grammar** parse bracketed operands in function arguments and expression lists ([ca2b3](https://github.com/JSQLParser/JSqlParser/commit/ca2b3d71a870385) Andreas Reichel) +- split CCJSqlParserTokenManager static initializers to avoid 64KB method limit (#2425) ([cf5bb](https://github.com/JSQLParser/JSqlParser/commit/cf5bbc9a6216315) Hayssam Saleh) +- exponential backtracking on deeply nested bracketed expressions ([fff8a](https://github.com/JSQLParser/JSqlParser/commit/fff8a081e43d9b5) manticore-projects) +- **parser** pass modifier to ExceptOp/MinusOp constructors (#2419) (#2420) ([7fc30](https://github.com/JSQLParser/JSqlParser/commit/7fc300f7b0968ec) Alex Towell) +- **parser** parse mysql fulltext AGAINST concat expression (#2413) ([5788c](https://github.com/JSQLParser/JSqlParser/commit/5788ca067abf76a) Liming Deng) +- **parser** support KEY-prefixed expressions like KEY chain.entity (#2409) ([bfcb8](https://github.com/JSQLParser/JSqlParser/commit/bfcb8b752539ab3) Liming Deng) +- reduce timeout since we are too fast now :-D ([126c1](https://github.com/JSQLParser/JSqlParser/commit/126c1a16abaab2f) manticore-projects) +- improve performance by refactoring `Condition`, `RegularCondition`, `AndExpression` ([6c5d7](https://github.com/JSQLParser/JSqlParser/commit/6c5d7ef60351a49) manticore-projects) +- restore GENERATED ALWAYS AS IDENTITY parsing (#2397) ([bd3ce](https://github.com/JSQLParser/JSqlParser/commit/bd3ce05f356dac2) Liming Deng) +- **parser** resolve grammar LOOKAHEAD conflicts (#2399) ([59dfc](https://github.com/JSQLParser/JSqlParser/commit/59dfc3b0e7167eb) Liming Deng) +- **clickhouse** support SELECT ... SETTINGS (#2395) ([a34db](https://github.com/JSQLParser/JSqlParser/commit/a34db0ce68fe6ac) Liming Deng) +- support ClickHouse parametric aggregate calls (#2393) ([64542](https://github.com/JSQLParser/JSqlParser/commit/64542c863a1325d) Liming Deng) +- support SQL Server ORDER BY ... FOR XML PATH parsing in subqueries (#2392) ([9de70](https://github.com/JSQLParser/JSqlParser/commit/9de70747b94e575) Liming Deng) +- ALTER TABLE with a USING INDEX clause (#2384) ([c7b3b](https://github.com/JSQLParser/JSqlParser/commit/c7b3bdbd00cf96e) Liming Deng) +- remove obsolete Grammar option ([93515](https://github.com/JSQLParser/JSqlParser/commit/935151493cc9db9) manticore-projects) +- module-info.java ([484ea](https://github.com/JSQLParser/JSqlParser/commit/484eaa1c0f623cc) Andreas Reichel) +- DuckDB `STRUCT` ([297ef](https://github.com/JSQLParser/JSqlParser/commit/297ef84673521fd) Andreas Reichel) +- parsing `STRUCT` data types ([aaebe](https://github.com/JSQLParser/JSqlParser/commit/aaebe591001a164) Andreas Reichel) +- add some LOOKAHEAD ([2d739](https://github.com/JSQLParser/JSqlParser/commit/2d7393499e4d987) Andreas Reichel) +- avoid visiting twice ([49958](https://github.com/JSQLParser/JSqlParser/commit/49958b6ba96898f) Andreas Reichel) +- avoid NPE and expose `modifier` ([eeb04](https://github.com/JSQLParser/JSqlParser/commit/eeb04004da797c6) Andreas Reichel) +- return Dollar quoted text as `StringValue` ([95ebd](https://github.com/JSQLParser/JSqlParser/commit/95ebda5a6112cd7) manticore-projects) +- avoid NPE ([12600](https://github.com/JSQLParser/JSqlParser/commit/12600fb41957205) manticore-projects) +- possible NPE ([915aa](https://github.com/JSQLParser/JSqlParser/commit/915aa35d696b70b) manticore-projects) +- possible NPE ([dd05e](https://github.com/JSQLParser/JSqlParser/commit/dd05e11ae6dbe89) manticore-projects) +- `FromItemVisitorAdaptor` also needs a `SelectVisitor` ([178ca](https://github.com/JSQLParser/JSqlParser/commit/178ca057be78c00) manticore-projects) +- resolved physical tables must be clones, not references ([96321](https://github.com/JSQLParser/JSqlParser/commit/963217f48732426) manticore-projects) +- GroupByElement::getGroupByExpressionList shall not return raw ExpressionList ([f14c6](https://github.com/JSQLParser/JSqlParser/commit/f14c6115bd3e715) manticore-projects) +- increase Max. Nesting Depth to 16 (from 10) ([b8c0e](https://github.com/JSQLParser/JSqlParser/commit/b8c0ed58f3ee572) manticore-projects) +- date/time functions colliding with implicit casts ([6e91c](https://github.com/JSQLParser/JSqlParser/commit/6e91c3e1e3c94b3) manticore-projects) +- precedence of the `InExpression` ([0f9e4](https://github.com/JSQLParser/JSqlParser/commit/0f9e477944332c4) manticore-projects) +- revert to Semantic LOOKAHEAD for `SubSelect` in `PrimaryExpression` ([7f068](https://github.com/JSQLParser/JSqlParser/commit/7f068f6bd09623e) manticore-projects) +- bring back `SYNTACTIC LOOKAHEAD` where it makes sense ([b3c5b](https://github.com/JSQLParser/JSqlParser/commit/b3c5b63344de193) manticore-projects) +- the Quotes Token manipulation ([bad81](https://github.com/JSQLParser/JSqlParser/commit/bad818e0b872c6a) manticore-projects) ### Other changes +**Clarify PostgreSQL DO support and verify the reported block (#2633)** + +* Clarify PostgreSQL DO support and verify the reported block +* Keep DO round-trip assertions independent of fixture license comments + +[02b9d](https://github.com/JSQLParser/JSqlParser/commit/02b9d94072196b9) Minjae Lee *2026-09-13 12:10:51* + +**Render DML source clauses through shared visitor paths (#2630)** + + +[4b88a](https://github.com/JSQLParser/JSqlParser/commit/4b88a445419c846) Minjae Lee *2026-09-13 10:11:39* + +**Fix benchmark configuration and isolated parser class loading (#2629)** + + +[95140](https://github.com/JSQLParser/JSqlParser/commit/95140c895d836c5) Minjae Lee *2026-09-13 10:10:03* + +**Keep ALTER key accessors connected to structured index state (#2628)** + +* Keep ALTER key accessors connected to structured index state +* Clarify ALTER key element type for static analysis +* Make remaining ALTER key element types explicit +* Simplify ALTER key adapters and document replacement semantics + +[7739c](https://github.com/JSQLParser/JSqlParser/commit/7739c08181110e8) Minjae Lee *2026-09-13 10:07:36* + +**Protect shared dialect presets from external mutation (#2627)** + + +[b3b20](https://github.com/JSQLParser/JSqlParser/commit/b3b208489a19e7c) Minjae Lee *2026-09-13 10:03:57* + +**Share parser execution and preserve cancellation signals (#2626)** + + +[35105](https://github.com/JSQLParser/JSqlParser/commit/35105a0e18b2d9f) Minjae Lee *2026-09-13 09:59:42* + +**Reuse common JOIN traversal when discovering UPDATE tables (#2625)** + + +[a2baa](https://github.com/JSQLParser/JSqlParser/commit/a2baa18d7ce1589) Minjae Lee *2026-09-13 09:55:54* + +**Render DML RETURNING expressions through shared visitor handling (#2624)** + + +[38224](https://github.com/JSQLParser/JSqlParser/commit/3822466957f69f9) Minjae Lee *2026-09-13 09:54:53* + +**Validate every UPDATE assignment through shared UpdateSet traversal (#2623)** + + +[c314d](https://github.com/JSQLParser/JSqlParser/commit/c314df2b32c6fac) Minjae Lee *2026-09-13 09:53:02* + +**Structure FOR PORTION intervals in UPDATE and DELETE (#2622)** + + +[33c40](https://github.com/JSQLParser/JSqlParser/commit/33c4030433a3df2) Minjae Lee *2026-09-13 09:51:36* + +**Preserve ASOF joins through shared join rendering (#2621)** + + +[68608](https://github.com/JSQLParser/JSqlParser/commit/6860852112d6403) Minjae Lee *2026-09-13 09:49:48* + +**Support structured XMLFOREST arguments with aliases (#2619)** + + +[73bd3](https://github.com/JSQLParser/JSqlParser/commit/73bd3c498eed103) Minjae Lee *2026-09-13 09:41:15* + +**Support SQL Server SET IDENTITY_INSERT with a structured table target (#2605)** + + +[894ab](https://github.com/JSQLParser/JSqlParser/commit/894ab4d7e03717b) Minjae Lee *2026-09-12 00:35:38* + +**Support SQL Server boolean SET options with shared statement rendering (#2604)** + +* Co-authored-by: manticore-projects <andreas@manticore-projects.com> + +[f473c](https://github.com/JSQLParser/JSqlParser/commit/f473ccfd5f032c1) Minjae Lee *2026-09-12 00:34:26* + +**Support SQL Server INSERT BULK declarations (#2591)** + + +[bf2c9](https://github.com/JSQLParser/JSqlParser/commit/bf2c9ac773e8521) Minjae Lee *2026-09-11 21:41:04* + +**Support SQL Server XML methods on subquery results (#2590)** + +* Co-authored-by: manticore-projects <andreas@manticore-projects.com> + +[15156](https://github.com/JSQLParser/JSqlParser/commit/1515637c08a461a) Minjae Lee *2026-09-11 16:15:55* + +**Support PostgreSQL DO wrappers with preserved routine body literals (#2589)** + +* Co-authored-by: manticore-projects <andreas@manticore-projects.com> + +[1bc94](https://github.com/JSQLParser/JSqlParser/commit/1bc942422801523) Minjae Lee *2026-09-11 16:12:59* + +**Accept SQL Server CREATE TABLE trailing commas with shared element parsing (#2603)** + + +[179ee](https://github.com/JSQLParser/JSqlParser/commit/179ee11e17780a4) Minjae Lee *2026-09-11 16:10:39* + +**Split PostgreSQL hash operators from adjacent identifiers (#2588)** + + +[a946e](https://github.com/JSQLParser/JSqlParser/commit/a946e7715dec34d) Minjae Lee *2026-09-11 16:05:53* + +**Support Teradata UPDATE FROM before SET with shared clause rendering (#2587)** + +* Co-authored-by: mj <mj@mjui-MacBookPro.local> + +[a206b](https://github.com/JSQLParser/JSqlParser/commit/a206b861da4ff68) Minjae Lee *2026-09-11 15:20:17* + +**Structure SQL Server routine operations and table return types (#2593)** + +* Co-authored-by: mj <mj@mjui-MacBookPro.local> + +[d18de](https://github.com/JSQLParser/JSqlParser/commit/d18de825e152d05) Minjae Lee *2026-09-11 14:50:16* + +**Support CockroachDB ALTER PRIMARY KEY with structured hash options (#2585)** + +* Co-authored-by: mj <mj@mjui-MacBookPro.local> + +[7c6a9](https://github.com/JSQLParser/JSqlParser/commit/7c6a9fcbf5bd7bd) Minjae Lee *2026-09-11 14:47:57* + +**Support Doris distribution hints and share join keyword rendering (#2584)** + + +[78cff](https://github.com/JSQLParser/JSqlParser/commit/78cff6b34be54ca) Minjae Lee *2026-09-11 09:47:52* + +**Support Oracle anonymous blocks and implicit procedure calls (#2592)** + + +[17f03](https://github.com/JSQLParser/JSqlParser/commit/17f033df04e3b10) Minjae Lee *2026-09-11 09:08:42* + +**Support dialect-specific CREATE INDEX definitions (#2586)** + + +[4be19](https://github.com/JSQLParser/JSqlParser/commit/4be195b1e44d9b0) Minjae Lee *2026-09-11 09:06:37* + +**Support MySQL SELECT INTO user variables with explicit dialect (#2583)** + + +[588bf](https://github.com/JSQLParser/JSqlParser/commit/588bf070e9341e2) Minjae Lee *2026-09-11 09:05:22* + +**Support recursive CTE CYCLE clauses with shared recursive clause rendering (#2566)** + + +[eddb1](https://github.com/JSQLParser/JSqlParser/commit/eddb1fb5bb5f6ef) Minjae Lee *2026-09-11 04:37:17* + +**Support SQL Server constraint clustering with explicit dialect (#2582)** + + +[c1a61](https://github.com/JSQLParser/JSqlParser/commit/c1a611db8aa3f93) Minjae Lee *2026-09-11 04:30:55* + +**Support Informix trailing constraint names and share constraint rendering (#2570)** + +* Support Informix trailing constraint names and share constraint rendering +* fix(parser): gate Informix constraints on dialect selection + +[329ee](https://github.com/JSQLParser/JSqlParser/commit/329ee6b6465aa7e) Minjae Lee *2026-09-11 03:26:45* + +**Support PostgreSQL tagged dollar strings and preserve literal bodies (#2572)** + +* Support PostgreSQL tagged dollar strings and preserve literal bodies +* fix(parser): enable tagged dollar strings through PostgreSQL dialect + +[8ff4f](https://github.com/JSQLParser/JSqlParser/commit/8ff4f6488c7cf60) Minjae Lee *2026-09-11 03:12:26* + +**Support ksqlDB window grace periods and EMIT FINAL (#2571)** + +* Support ksqlDB window grace periods and EMIT FINAL +* Group ksqlDB window support with queries in the support table + +[e8479](https://github.com/JSQLParser/JSqlParser/commit/e8479ca3d93c6c8) Minjae Lee *2026-09-11 02:57:08* + +**Complete MySQL index options and share CREATE INDEX rendering (#2567)** + +* Complete MySQL index algorithm and lock options and share CREATE INDEX rendering +* Split shared index rendering into focused helpers +* Compare median monotonic durations in bracket growth regression checks + +[381d1](https://github.com/JSQLParser/JSqlParser/commit/381d1a9de236bd1) Minjae Lee *2026-09-11 02:48:19* + +**Support PostgreSQL conflict index elements, OVERRIDING USER and MERGE RETURNING (#2569)** + +* Support PostgreSQL conflict index elements, OVERRIDING USER and MERGE RETURNING +* Preserve bulk additions through the conflict column view + +[ba4dc](https://github.com/JSQLParser/JSqlParser/commit/ba4dce9d26016f2) Minjae Lee *2026-09-11 02:46:04* + +**Preserve EXEC bind arguments and support OUTPUT parameters (#2580)** + + +[2de24](https://github.com/JSQLParser/JSqlParser/commit/2de24179541d6c8) Minjae Lee *2026-09-11 02:44:11* + +**Support SQL Server table variables through shared table references (#2581)** + + +[7bb6e](https://github.com/JSQLParser/JSqlParser/commit/7bb6e1c4440f1fa) Minjae Lee *2026-09-11 02:41:51* + +**Fix parseStatements failure propagation and executor cleanup (#2568)** + + +[d9109](https://github.com/JSQLParser/JSqlParser/commit/d9109134703cb11) Minjae Lee *2026-09-11 02:40:33* + +**Validate CTE bodies through the existing statement dispatch (#2578)** + + +[72521](https://github.com/JSQLParser/JSqlParser/commit/7252121652154c6) Minjae Lee *2026-09-11 02:30:36* + +**Analyse analytic and conversion functions with complete window traversal (#2579)** + + +[01d7c](https://github.com/JSQLParser/JSqlParser/commit/01d7c3eea2d5e98) Minjae Lee *2026-09-11 02:27:12* + +**Fix DISTINCT traversal and share optional select-item visits (#2577)** + +* Visit optional DISTINCT ON items without changing the AST +* Verify plain DISTINCT retains unproven function analysis + +[db61f](https://github.com/JSQLParser/JSqlParser/commit/db61f4bc04a7613) Minjae Lee *2026-09-11 02:24:16* + +**Support structured PostgreSQL role, privilege and trigger DDL (#2555)** + +* Support structured PostgreSQL role privilege and trigger DDL +* Align access DDL field layout and simplify trigger rendering + +[01768](https://github.com/JSQLParser/JSqlParser/commit/01768813eb60295) Minjae Lee *2026-09-10 09:50:05* + +**Support structured PostgreSQL publication and subscription DDL (#2554)** + +* Support structured PostgreSQL logical replication DDL +* Exclude PostgreSQL fixtures from Transact-SQL linting + +[bc18f](https://github.com/JSQLParser/JSqlParser/commit/bc18fdf99b52b4b) Minjae Lee *2026-09-10 09:39:51* + +**Support SQL Server ALTER TABLE ADD DEFAULT constraints (#2561)** + + +[d45f7](https://github.com/JSQLParser/JSqlParser/commit/d45f79699d43bc0) Minjae Lee *2026-09-10 08:34:43* + +**Support Oracle XMLSERIALIZE CONTENT and DOCUMENT forms (#2560)** + + +[8ecff](https://github.com/JSQLParser/JSqlParser/commit/8ecff15fecbc8bf) Minjae Lee *2026-09-10 08:32:34* + +**Support SQL Server typed XML schema collections (#2559)** + +* Support SQL Server typed XML schema collections +* Keep XML type lookahead independent of EXPLAIN additions + +[b2c84](https://github.com/JSQLParser/JSqlParser/commit/b2c84f98e6992a6) Minjae Lee *2026-09-10 08:31:04* + +**Support Redshift materialized view BACKUP clauses (#2557)** + + +[b5595](https://github.com/JSQLParser/JSqlParser/commit/b55956b649a06f5) Minjae Lee *2026-09-10 08:30:02* + +**Support PostgreSQL parenthesized EXPLAIN options (#2558)** + + +[4c8f5](https://github.com/JSQLParser/JSqlParser/commit/4c8f593e73948df) Minjae Lee *2026-09-10 08:29:01* + +**Model MySQL DROP object lists (#2545)** + +* feat(parser): model MySQL DROP object lists +* test: keep DROP assertions Java 11 compatible + +[5c59b](https://github.com/JSQLParser/JSqlParser/commit/5c59b7bc24a1af8) Minjae Lee *2026-09-05 00:15:16* + +**Model MySQL ANALYZE TABLE statements (#2542)** + +* feat(parser): model MySQL ANALYZE TABLE +* test: keep ANALYZE assertions Java 11 compatible + +[53200](https://github.com/JSQLParser/JSqlParser/commit/5320066eb287807) Minjae Lee *2026-09-05 00:12:03* + +**Support structured MySQL table definitions (#2541)** + +* refactor(ast): model structured table definitions +* fix(parser): unify MySQL table definitions +* fix(parser): model common MySQL table options +* fix(ast): initialize check constraint kind + +[a6450](https://github.com/JSQLParser/JSqlParser/commit/a64505266ffce63) Minjae Lee *2026-09-05 00:10:27* + +**corrected missing licence headers** + + +[495da](https://github.com/JSQLParser/JSqlParser/commit/495dafec9e37e22) tw *2026-09-04 22:12:41* + +**Update ci.yml** + + +[4b89a](https://github.com/JSQLParser/JSqlParser/commit/4b89a35da568df5) manticore-projects *2026-09-04 07:24:50* + +**Create requirements-docs.txt** + + +[fd59b](https://github.com/JSQLParser/JSqlParser/commit/fd59b5267ed77f7) manticore-projects *2026-09-04 07:24:28* + +**Update ci.yml** + +* Fix workflow + +[8186f](https://github.com/JSQLParser/JSqlParser/commit/8186f38e3d8b345) manticore-projects *2026-09-04 07:10:25* + +**Support MySQL CHECK constraint enforcement (#2528)** + + +[125de](https://github.com/JSQLParser/JSqlParser/commit/125de36c05b6dd7) Minjae Lee *2026-09-03 06:40:12* + +**Support PostgreSQL CREATE INDEX header options (#2527)** + + +[43314](https://github.com/JSQLParser/JSqlParser/commit/433143e4aaacfb5) Minjae Lee *2026-09-03 06:39:21* + +**Add MySQL CREATE TABLE partition parsing (#2521)** + + +[536fe](https://github.com/JSQLParser/JSqlParser/commit/536fe3b62de6e43) Minjae Lee *2026-09-03 06:38:22* + +**anchor oracle hint pattern to avoid quadratic backtracking (#2495)** + + +[a5765](https://github.com/JSQLParser/JSqlParser/commit/a5765f3f905d41a) Javid Khan *2026-08-22 12:45:11* + +**avoid quadratic backtracking in Server name pattern (#2489)** + + +[edd5b](https://github.com/JSQLParser/JSqlParser/commit/edd5bfc1a513f01) Javid Khan *2026-08-22 02:41:56* + +**Separate JSON operators with spaces so #> deparses correctly (#2487)** + +* JsonExpression.toString glued each operator directly onto its operand, so +* a #> ... deparsed as a#>... . Because # is a legal identifier character, that +* re-parses as a# > ... -- a GreaterThan, a different tree from the original +* JsonExpression. Emit a space on each side of the operator, matching the +* JsonOperator visitor. + +[55b23](https://github.com/JSQLParser/JSqlParser/commit/55b23637604ea24) Denis Gregor *2026-08-19 03:25:49* + +**Update changelog** + + +[fa731](https://github.com/JSQLParser/JSqlParser/commit/fa731f9b1b9d5fd) Andreas Reichel *2026-08-18 01:18:54* + +**Update Gradle wrapper** + + +[37e17](https://github.com/JSQLParser/JSqlParser/commit/37e17a09a0b7cfd) Andreas Reichel *2026-08-17 01:32:56* + +**Update changelog** + + +[0944d](https://github.com/JSQLParser/JSqlParser/commit/0944d553e5b7fde) Andreas Reichel *2026-08-13 14:31:54* + +**Add support PostgreSQL ROWS FROM table functions (#2416)** + + +[14db8](https://github.com/JSQLParser/JSqlParser/commit/14db886f5a23e56) Liming Deng *2026-07-30 03:07:55* + +**use Locale.ROOT for case-insensitive keyword normalisation (#2437)** + +* Signed-off-by: dxbjavid <dxbjavid@gmail.com> + +[82b7c](https://github.com/JSQLParser/JSqlParser/commit/82b7c26334df876) Javid Khan *2026-07-30 03:06:47* + +**[Fix] #2443 - NPE In TableNamesFinder with AnalyticExpression (#2444)** + +* [Fix] #2443 - NPE In TableNamesFinder with AnalyticExpression +* If you attempt to tree walk an AnalyticExpression in TableNamesFinder with an SQL that has a window function without a range and without an offset, such as `SELECT c, SUM(COUNT(*)) OVER (ORDER BY c ASC ROWS UNBOUNDED PRECEDING) FROM tbl GROUP BY c`, an NPE is thrown. This adds defensive checks around the range and offset expressions being null. +* More aggressive NPE protection +* --------- +* Co-authored-by: David Hayes <dhayes@adobe.com> + +[56468](https://github.com/JSQLParser/JSqlParser/commit/5646842a0b1c37a) David Hayes *2026-07-25 02:34:37* + +**[feat] Support for JSON_TABLE (#2328)** + +* [feat] Initial support for JSON_TABLE +* [feat] Additional support for JSON_TABLE +* [chore] Code Review +* [chore] Started to add oracle JSON_TABLE features +* [feat] Fixed onError for Oracle, added onEmptyClause, addedParsingType, formatJson for Body +* [feat] Added EXISTS keyword +* [chore] spotless +* [chore] cleanup +* [bugfix] Fixed token limit again... +* [chore] One more test, code cleanup + +[c5e2f](https://github.com/JSQLParser/JSqlParser/commit/c5e2fdcd40e81fa) Andreas Neumann *2026-04-02 16:42:55* + +**Add support MySQL SELECT INTO OUTFILE/DUMPFILE (#2418)** + +* Add support MySQL SELECT INTO OUTFILE/DUMPFILE +* fix + +[c0e1d](https://github.com/JSQLParser/JSqlParser/commit/c0e1d0528a9b114) Liming Deng *2026-03-22 07:31:33* + +**Fix dollar-quoted CREATE FUNCTION statement splitting (#2410)** + +* Fix dollar-quoted CREATE FUNCTION statement splitting +* Fix exception + +[c5b85](https://github.com/JSQLParser/JSqlParser/commit/c5b85abffdb00ae) Liming Deng *2026-03-14 18:09:38* + +**Add support MySQL functional index key parts in ALTER/CREATE INDEX (#2405)** + + +[7b87d](https://github.com/JSQLParser/JSqlParser/commit/7b87d0816dea41e) Liming Deng *2026-03-08 15:53:07* + +**Fix nested PostgreSQL composite field access after cast (#2404)** + + +[5b5fe](https://github.com/JSQLParser/JSqlParser/commit/5b5fe6c2a7e2908) Liming Deng *2026-03-08 15:50:58* + +**Add support parse SEARCH BREADTH/DEPTH FIRST in recursive CTEs (#2402)** + +* fix(parser): parse SEARCH BREADTH/DEPTH FIRST in recursive CTEs +* fix + +[1ee36](https://github.com/JSQLParser/JSqlParser/commit/1ee36829fbce7ef) Liming Deng *2026-03-08 08:31:28* + +**Update README.md to remove General SQL Parser mention** + +* Removed a comparison of General SQL Parser from alternatives section. + +[c3b15](https://github.com/JSQLParser/JSqlParser/commit/c3b1531150b0a03) Tobias *2026-03-07 21:12:26* + +**Add support CREATE SEQUENCE AS data type (#2400)** + +* Add support CREATE SEQUENCE AS data type +* format cod + +[22da3](https://github.com/JSQLParser/JSqlParser/commit/22da32654622925) Liming Deng *2026-03-06 16:09:41* + +**Add support MySQL named UNIQUE index in ALTER TABLE (#2391)** + + +[fb36c](https://github.com/JSQLParser/JSqlParser/commit/fb36cc6a2ec46ba) Liming Deng *2026-03-01 14:58:01* + +**Add support SELECT SAMPLE 0.1/OFFSET and CREATE TABLE SAMPLE BY (#2390)** + +* fix(clickhouse): support SELECT SAMPLE 0.1/OFFSET and CREATE TABLE SAMPLE BY +* fix(clickhouse): support SELECT SAMPLE 0.1/OFFSET and CREATE TABLE SAMPLE BY + +[e1620](https://github.com/JSQLParser/JSqlParser/commit/e16202441dd91c1) Liming Deng *2026-03-01 10:31:28* + +**Fix broken maven central svg url (#2389)** + + +[ecaa2](https://github.com/JSQLParser/JSqlParser/commit/ecaa26d3e0e1bbd) Liming Deng *2026-02-28 08:53:26* + +**Add support MySQL SPATIAL KEY (#2388)** + + +[a019a](https://github.com/JSQLParser/JSqlParser/commit/a019aa01085f110) Liming Deng *2026-02-25 16:39:25* + +**Fix alter table index descending (#2387)** + + +[763e9](https://github.com/JSQLParser/JSqlParser/commit/763e92d71d728de) Liming Deng *2026-02-24 13:00:47* + +**Add support for multiple Trino JSON functions (#2382)** + +* Adding support for clauses in JSON Functions. Fixes #2368 +* Adds support for the extra clauses found in Trino's JSON functions https://trino.io/docs/current/functions/json.html#json-exists, and includes test cases for these extra functions directly taken from the documentations. +* Before: +* Result "net.sf.jsqlparser.benchmark.JSQLParserBenchmark.parseSQLStatements": +* 34.858 ±(99.9%) 1.724 ms/op [Average] +* (min, avg, max) = (32.578, 34.858, 38.383), stdev = 2.302 +* CI (99.9%): [33.133, 36.582] (assumes normal distribution) +* After: +* Result "net.sf.jsqlparser.benchmark.JSQLParserBenchmark.parseSQLStatements": +* 36.154 ±(99.9%) 1.701 ms/op [Average] +* (min, avg, max) = (33.100, 36.154, 38.353), stdev = 2.271 +* CI (99.9%): [34.453, 37.855] (assumes normal distribution) +* Co-authored-by: David Hayes <dhayes@adobe.com> + +[d33d6](https://github.com/JSQLParser/JSqlParser/commit/d33d61a882d17ef) David Hayes *2026-02-24 12:23:52* + +**Fix oracle outer join case with nvl/coalesce (#2386)** + + +[834af](https://github.com/JSQLParser/JSqlParser/commit/834afe188e7ff46) Liming Deng *2026-02-24 12:15:14* + +**Add support clickhouse GLOBAL ANY/ALL JOIN syntax variants (#2385)** + + +[865a3](https://github.com/JSQLParser/JSqlParser/commit/865a3bf00d7a58d) Liming Deng *2026-02-23 13:38:22* + +**Add support for insert as row_alias (#2383)** + +* Add support for insert as row_alias +* format code + +[4e3d7](https://github.com/JSQLParser/JSqlParser/commit/4e3d7a56fdd4460) Liming Deng *2026-02-22 16:16:52* + +**[feat] Support for the legacy Postgres named parameter (#2374)** + +* feat: add postgresql named parameters support +* modify: PostgresNamedFunctionParameterTest.java +* refactor: code formatting +* --------- +* Co-authored-by: youngjoonkim <youngjoonkim@seowoninfo.com> + +[7c52e](https://github.com/JSQLParser/JSqlParser/commit/7c52e7fe8df21a2) youngjoon *2026-02-15 09:09:33* + +**Add support for ClickHouse PREWHERE clause (#2378)** + + +[8453c](https://github.com/JSQLParser/JSqlParser/commit/8453ca0fe7ce1a0) Liming Deng *2026-02-15 09:08:55* + +**Add support for ClickHouse MATERIALIZED column in CREATE TABLE (#2377)** + +* - allow `MATERIALIZED` in column specs +* - allow `ORDER BY` in table options +* - add regression test for `ENGINE = MergeTree() ORDER BY tuple()` + +[ededd](https://github.com/JSQLParser/JSqlParser/commit/ededd864b81b33a) Liming Deng *2026-02-13 17:12:48* + +**Delete .github/dependabot.yml** + + +[38c96](https://github.com/JSQLParser/JSqlParser/commit/38c963d6464eef4) manticore-projects *2026-02-12 08:26:46* + +**Add support for JOIN FETCH (#2375)** + + +[091ef](https://github.com/JSQLParser/JSqlParser/commit/091ef964eaa0822) Liming Deng *2026-02-12 08:06:12* + +**Support PostgreSQL-specific [CREATE SEQUENCE name START n ...] (#2348)** + +* Support PostgreSQL-specific [CREATE SEQUENCE name START n ...] +* I.e. no `START WITH` +* Fixes #2347 +* Support PostgreSQL-specific [CREATE SEQUENCE … INCREMENT n ...] +* I.e. no `INCREMENT BY` +* Fixes #2347 + +[40ccf](https://github.com/JSQLParser/JSqlParser/commit/40ccf4b87171494) mjog *2025-11-21 07:21:30* + +**[feat] Add 'K_DATA' to KeywordOrIdentifier to allow usages of 'data' as an identifier (#2340)** + +* Allows for support of data as a column name when changing or dropping a column in an alter table statement + +[2d83c](https://github.com/JSQLParser/JSqlParser/commit/2d83cea9ba65a7c) Will Needham *2025-11-21 02:18:54* + +**[fix] Enable qualified table name for DROP INDEX command (#2344)** + +* Thank you for your contribution. + +[8d967](https://github.com/JSQLParser/JSqlParser/commit/8d967803c780f5f) Ton Huisman *2025-11-21 02:17:13* + +**Update README.md (#2331)** + + +[01034](https://github.com/JSQLParser/JSqlParser/commit/01034cd08c3e9d7) Hayssam Saleh *2025-10-28 11:48:22* + +**[feat/refactor] AllColumns and AllTableColumns-Support for JSON_OBJECT (#2323)** + +* [feat] JSON_OBJECT support for AllColumns and AllTableColumns +* [feat] JSON_OBJECT support for AllColumns and AllTableColumns +* [feat] JSON_OBJECT support for AllColumns and AllTableColumns +* [feat] Split the parser syntax for JsonFunction, added more Tests, added Feature to disable Commas as key value separators +* [feat] Disable Expression as JSON_OBJECT key value via feature flag +* [chore] spotlessApply +* [feat] Use append method from JsonKeyValuePair + +[e8d60](https://github.com/JSQLParser/JSqlParser/commit/e8d605854ae9148) Andreas Neumann *2025-10-12 11:06:19* + +**[feat] Support for FOR READ ONLY/FOR FETCH ONLY (#2325)** + +* [feat] Support for FOR READ ONLY/FOR FETCH ONLY +* [chore] Spotless + +[e4004](https://github.com/JSQLParser/JSqlParser/commit/e400444402c7a6e) Andreas Neumann *2025-10-10 11:01:28* + +**[feat] Support for LOCK-Statements (#2321)** + +* Add support for LOCK-Statements +* Fixed comments +* ran updateKeywords +* Added test +* Checkstyle & Spotless +* Adjusted NOWAIT and WAIT-Setter + +[6697c](https://github.com/JSQLParser/JSqlParser/commit/6697c063493cac2) Andreas Neumann *2025-10-07 23:32:53* + +**Added new features to support the complete delete using syntax of pg database, such as subqueries, etc. (#2316)** + +* Co-authored-by: zhangkenan <zhangkenan@hexadb.com> + +[15798](https://github.com/JSQLParser/JSqlParser/commit/157988d1c5f6816) ConanZhang *2025-09-24 02:06:46* + +**Fix[2312] - Fixes issue with array in function declaration (#2313)** + +* Fix[2312] - Fixes issue with array<double> in function declaration +* ```sql +* WITH +* FUNCTION takesArray(x array<double>) +* RETURNS double +* RETURN x[1] + x[2] + x[3] +* SELECT takesArray(array[1.0, 2.0, 3.0]); +* ``` +* Is unable to be parsed as we're not able to capture the array<double> correctly. This PR fixes that, as well as some missing visitor changes from my previous PR. +* Fix spotlessJavaCheck +* --------- +* Co-authored-by: David Hayes <dhayes@adobe.com> + +[528dd](https://github.com/JSQLParser/JSqlParser/commit/528dd7227401d07) David Hayes *2025-09-17 01:20:36* + +**Fix[2306] - Adds support for Trino UDF (#2307)** + +* Fix[2306] - Adds support for Trino UDF +* Trino SQL allows you to specify User Defined functions in the WITH clause ahead of a statement. This adds support for this type of statement. +* Fix[2306] - Adds support for Trino UDF +* Trino SQL allows you to specify User Defined functions in the WITH clause ahead of a statement. This adds support for this type of statement. +* --------- +* Co-authored-by: David Hayes <dhayes@adobe.com> + +[6ce95](https://github.com/JSQLParser/JSqlParser/commit/6ce95d54378a2f6) David Hayes *2025-09-13 23:29:04* + +**support opengauss "on duplicate key update nothing" grammar (#2303)** + +* support opengauss "on duplicate key nothing" grammar +* use import single classes instead of wildcard imports in Insert class. +* use import single classes instead of wildcard imports in Insert class. +* use './gradlew :spotlessApply' adjust code format. +* --------- +* Co-authored-by: zhangkenan <zhangkenan@hexadb.com> + +[468ae](https://github.com/JSQLParser/JSqlParser/commit/468aefae04b0204) ConanZhang *2025-08-28 12:15:36* + +**Fix[2290] - Fix overeager lambda function parsing (#2293)** + +* `SELECT DATE_TRUNC('week',("schema"."tbl"."column" + INTERVAL '1 day')) FROM "schema"."tbl";` +* is parsing the `("schema"."tbl"."column" + INTERVAL '1 day')` as a LambdaFunction incorrectly, and crashes out. Increasing the lookahead depth by 1 ensures it fails to match on the `->` keyword (I believe), and falls into a simple expression instead. +* JMH +* ``` +* jmh { +* includes = ['.*JSQLParserBenchmark.*'] +* warmupIterations = 2 +* fork = 5 +* iterations = 5 +* timeOnIteration = '5s' +* } +* ``` +* After: +* ``` +* 33.970 ±(99.9%) 1.773 ms/op [Average] +* (min, avg, max) = (31.405, 33.970, 37.302), stdev = 2.367 +* CI (99.9%): [32.197, 35.743] (assumes normal distribution) +* ``` +* Before: +* ``` +* 34.882 ±(99.9%) 1.923 ms/op [Average] +* (min, avg, max) = (31.191, 34.882, 37.406), stdev = 2.567 +* CI (99.9%): [32.959, 36.805] (assumes normal distribution) +* ``` +* Co-authored-by: David Hayes <dhayes@adobe.com> + +[12489](https://github.com/JSQLParser/JSqlParser/commit/12489af67cb82dc) David Hayes *2025-08-11 22:49:52* + +**Fix[2288] - Support parenthesed expressions within Between (#2289)** + +* Supports the SQL of the form: +* ```sql +* SELECT * FROM tbl WHERE +* day BETWEEN +* CAST(CAST((NOW() + INTERVAL '-30 day') AS date) AS timestamptz) +* AND +* CAST(CAST((NOW() + INTERVAL '-1 day') AS date) AS timestamptz); +* ``` +* Co-authored-by: David Hayes <dhayes@adobe.com> + +[f10b5](https://github.com/JSQLParser/JSqlParser/commit/f10b52eda68da22) David Hayes *2025-08-05 14:57:46* + +**Fix[2283] - Fix broken ParserKeywordsUtilsTest (#2286)** + +* Javacc java generator was only being included as a pom artefact in test, but the jar is required for the ParserKeywordsUtilsTest to succeed. +* Co-authored-by: David Hayes <dhayes@adobe.com> + +[2ff73](https://github.com/JSQLParser/JSqlParser/commit/2ff7342dfe41435) David Hayes *2025-08-01 09:39:30* + +**Add pluginRepositories to pom.xml (#2284)** + +* Adds pluginRepositories to the pom.xml to allow the javacc plugin to not download from insecure repositories (https vs http). +* Also adds missing copyrights which were added by maven. +* Co-authored-by: David Hayes <dhayes@adobe.com> + +[f10e5](https://github.com/JSQLParser/JSqlParser/commit/f10e5bdd4fdd31c) David Hayes *2025-07-31 13:16:04* + +**added new maven central deployer** + + +[50e75](https://github.com/JSQLParser/JSqlParser/commit/50e75d6d64958b3) tw *2025-07-10 23:38:15* + +**added new maven central deployer** + + +[afc7b](https://github.com/JSQLParser/JSqlParser/commit/afc7bdb26eb53e7) tw *2025-07-10 23:04:43* + ## jsqlparser-5.3 (2025-05-17) ### Features -- eliminate another expensive syntactic lookahead ([6049f](https://github.com/JSQLParser/JSqlParser/commit/6049fd729ec4073) manticore-projects) -- avoid looping through the tokens every single time ([ac175](https://github.com/JSQLParser/JSqlParser/commit/ac175138405726b) manticore-projects) -- adopt proper JMH based benchmark ([fe860](https://github.com/JSQLParser/JSqlParser/commit/fe860ddd18f28f8) manticore-projects) -- Optimise performance ([7d42f](https://github.com/JSQLParser/JSqlParser/commit/7d42ff614bd2cca) manticore-projects) +- eliminate another expensive syntactic lookahead ([6049f](https://github.com/JSQLParser/JSqlParser/commit/6049fd729ec4073) manticore-projects) +- avoid looping through the tokens every single time ([ac175](https://github.com/JSQLParser/JSqlParser/commit/ac175138405726b) manticore-projects) +- adopt proper JMH based benchmark ([fe860](https://github.com/JSQLParser/JSqlParser/commit/fe860ddd18f28f8) manticore-projects) +- Optimise performance ([7d42f](https://github.com/JSQLParser/JSqlParser/commit/7d42ff614bd2cca) manticore-projects) ### Other changes @@ -41,62 +913,62 @@ Changelog of JSqlParser. ### Features -- enhance ALTER TABLE support with UNIQUE KEY and INVISIBLE index (#2234) ([78318](https://github.com/JSQLParser/JSqlParser/commit/78318bb64bcfc96) Minjae Lee) -- link all Expression AST Nodes ([98e03](https://github.com/JSQLParser/JSqlParser/commit/98e03d4c5e5f659) manticore-projects) -- add support for parsing MySQL ALTER TABLE statements with new options (#2223) ([9a36a](https://github.com/JSQLParser/JSqlParser/commit/9a36a48087eb311) Minjae Lee) -- support MySQL ALTER TABLE ... PARTITION BY syntax (#2210) ([d60c7](https://github.com/JSQLParser/JSqlParser/commit/d60c762f1d71c9f) Minjae Lee) -- add support SELECT all columns from function result (#2207) ([30cf5](https://github.com/JSQLParser/JSqlParser/commit/30cf5d7b930ae0a) Lian Hu) -- `WithItem` must accept statements too for supporting `Delete`, `Insert`, `Update` with `Returning` ([585b6](https://github.com/JSQLParser/JSqlParser/commit/585b69c9149d418) manticore-projects) -- allow simple expressions for `JsonExpression` ([eef8e](https://github.com/JSQLParser/JSqlParser/commit/eef8e6e08ac39c9) Andreas Reichel) -- System.getProperty("SPLIT_NAMES_ON_DELIMITER") for allowing `.` dots in Table Names ([1d37e](https://github.com/JSQLParser/JSqlParser/commit/1d37eb0b94effdb) Andreas Reichel) -- Nested `WithItem` allows `FromQuery` ([0f749](https://github.com/JSQLParser/JSqlParser/commit/0f7493fa3d63af8) Andreas Reichel) -- add Order Suffixes to `AggregatePipeOperator` and support some weird BigQuery Syntax ([f932b](https://github.com/JSQLParser/JSqlParser/commit/f932b8f9807530c) Andreas Reichel) -- More lenient `FromQuery` allowing for `Join` and `WithItem` ([bcac0](https://github.com/JSQLParser/JSqlParser/commit/bcac023a5e43297) Andreas Reichel) -- More lenient `FromQuery` allowing for `Join` and `WithItem` ([5c2a4](https://github.com/JSQLParser/JSqlParser/commit/5c2a4537506a632) Andreas Reichel) -- `SELECT` piper operator to support `ALL | DISTINCT` ([5b209](https://github.com/JSQLParser/JSqlParser/commit/5b209f5477464ec) Andreas Reichel) -- improve TableSample Operator ([19f0a](https://github.com/JSQLParser/JSqlParser/commit/19f0aa4762667d0) Andreas Reichel) -- rewrite Piped SQL, WIP ([47583](https://github.com/JSQLParser/JSqlParser/commit/47583dea7e23992) Andreas Reichel) -- rewrite Piped SQL, WIP ([519b0](https://github.com/JSQLParser/JSqlParser/commit/519b02040af3ad6) Andreas Reichel) -- rewrite Piped SQL, WIP ([1cb26](https://github.com/JSQLParser/JSqlParser/commit/1cb26a731efae14) Andreas Reichel) -- rewrite Piped SQL, WIP ([0e488](https://github.com/JSQLParser/JSqlParser/commit/0e488b05159745b) Andreas Reichel) -- `ParenthesedSelect` can have `Alias` ([b5b24](https://github.com/JSQLParser/JSqlParser/commit/b5b242a3f6648ce) Andreas Reichel) -- `ParenthesedSelect` can have `Alias` ([a510f](https://github.com/JSQLParser/JSqlParser/commit/a510f135d50964e) Andreas Reichel) -- rework the Visitors ([8c051](https://github.com/JSQLParser/JSqlParser/commit/8c05188d74f50c0) Andreas Reichel) -- rework the Visitors ([d8162](https://github.com/JSQLParser/JSqlParser/commit/d816220f155d5a3) Andreas Reichel) -- rework the Visitors ([13879](https://github.com/JSQLParser/JSqlParser/commit/1387986effd76f3) Andreas Reichel) -- parsing Piped SQL, complete all Operators ([48cc2](https://github.com/JSQLParser/JSqlParser/commit/48cc2f748eb5685) Andreas Reichel) -- parsing Piped SQL ([f687f](https://github.com/JSQLParser/JSqlParser/commit/f687f77d38e9328) Andreas Reichel) -- Support not equals operator with a hat (#2153) ([3fee9](https://github.com/JSQLParser/JSqlParser/commit/3fee9aa9a2d621d) Stefan Steinhauser) -- Support unknown keyword (#2141) ([9ab1e](https://github.com/JSQLParser/JSqlParser/commit/9ab1ebded5fa493) Emily Ong) -- serialize `WithItem` ([df665](https://github.com/JSQLParser/JSqlParser/commit/df66569a87ce6fb) manticore-projects) -- Adding coverage for Postgres "OVERRIDING SYSTEM VALUE" (#2142) ([0ab13](https://github.com/JSQLParser/JSqlParser/commit/0ab131922d972aa) nicky6s) -- Piped SQL and FROM queries (WIP) ([01dc5](https://github.com/JSQLParser/JSqlParser/commit/01dc5f3472ce5ec) manticore-projects) -- Piped SQL and FROM queries (WIP) ([abf13](https://github.com/JSQLParser/JSqlParser/commit/abf137e04ca765d) manticore-projects) -- Piped SQL and FROM queries (WIP) ([f25e2](https://github.com/JSQLParser/JSqlParser/commit/f25e28bff4c96cb) manticore-projects) +- enhance ALTER TABLE support with UNIQUE KEY and INVISIBLE index (#2234) ([78318](https://github.com/JSQLParser/JSqlParser/commit/78318bb64bcfc96) Minjae Lee) +- link all Expression AST Nodes ([98e03](https://github.com/JSQLParser/JSqlParser/commit/98e03d4c5e5f659) manticore-projects) +- add support for parsing MySQL ALTER TABLE statements with new options (#2223) ([9a36a](https://github.com/JSQLParser/JSqlParser/commit/9a36a48087eb311) Minjae Lee) +- support MySQL ALTER TABLE ... PARTITION BY syntax (#2210) ([d60c7](https://github.com/JSQLParser/JSqlParser/commit/d60c762f1d71c9f) Minjae Lee) +- add support SELECT all columns from function result (#2207) ([30cf5](https://github.com/JSQLParser/JSqlParser/commit/30cf5d7b930ae0a) Lian Hu) +- `WithItem` must accept statements too for supporting `Delete`, `Insert`, `Update` with `Returning` ([585b6](https://github.com/JSQLParser/JSqlParser/commit/585b69c9149d418) manticore-projects) +- allow simple expressions for `JsonExpression` ([eef8e](https://github.com/JSQLParser/JSqlParser/commit/eef8e6e08ac39c9) Andreas Reichel) +- System.getProperty("SPLIT_NAMES_ON_DELIMITER") for allowing `.` dots in Table Names ([1d37e](https://github.com/JSQLParser/JSqlParser/commit/1d37eb0b94effdb) Andreas Reichel) +- Nested `WithItem` allows `FromQuery` ([0f749](https://github.com/JSQLParser/JSqlParser/commit/0f7493fa3d63af8) Andreas Reichel) +- add Order Suffixes to `AggregatePipeOperator` and support some weird BigQuery Syntax ([f932b](https://github.com/JSQLParser/JSqlParser/commit/f932b8f9807530c) Andreas Reichel) +- More lenient `FromQuery` allowing for `Join` and `WithItem` ([bcac0](https://github.com/JSQLParser/JSqlParser/commit/bcac023a5e43297) Andreas Reichel) +- More lenient `FromQuery` allowing for `Join` and `WithItem` ([5c2a4](https://github.com/JSQLParser/JSqlParser/commit/5c2a4537506a632) Andreas Reichel) +- `SELECT` piper operator to support `ALL | DISTINCT` ([5b209](https://github.com/JSQLParser/JSqlParser/commit/5b209f5477464ec) Andreas Reichel) +- improve TableSample Operator ([19f0a](https://github.com/JSQLParser/JSqlParser/commit/19f0aa4762667d0) Andreas Reichel) +- rewrite Piped SQL, WIP ([47583](https://github.com/JSQLParser/JSqlParser/commit/47583dea7e23992) Andreas Reichel) +- rewrite Piped SQL, WIP ([519b0](https://github.com/JSQLParser/JSqlParser/commit/519b02040af3ad6) Andreas Reichel) +- rewrite Piped SQL, WIP ([1cb26](https://github.com/JSQLParser/JSqlParser/commit/1cb26a731efae14) Andreas Reichel) +- rewrite Piped SQL, WIP ([0e488](https://github.com/JSQLParser/JSqlParser/commit/0e488b05159745b) Andreas Reichel) +- `ParenthesedSelect` can have `Alias` ([b5b24](https://github.com/JSQLParser/JSqlParser/commit/b5b242a3f6648ce) Andreas Reichel) +- `ParenthesedSelect` can have `Alias` ([a510f](https://github.com/JSQLParser/JSqlParser/commit/a510f135d50964e) Andreas Reichel) +- rework the Visitors ([8c051](https://github.com/JSQLParser/JSqlParser/commit/8c05188d74f50c0) Andreas Reichel) +- rework the Visitors ([d8162](https://github.com/JSQLParser/JSqlParser/commit/d816220f155d5a3) Andreas Reichel) +- rework the Visitors ([13879](https://github.com/JSQLParser/JSqlParser/commit/1387986effd76f3) Andreas Reichel) +- parsing Piped SQL, complete all Operators ([48cc2](https://github.com/JSQLParser/JSqlParser/commit/48cc2f748eb5685) Andreas Reichel) +- parsing Piped SQL ([f687f](https://github.com/JSQLParser/JSqlParser/commit/f687f77d38e9328) Andreas Reichel) +- Support not equals operator with a hat (#2153) ([3fee9](https://github.com/JSQLParser/JSqlParser/commit/3fee9aa9a2d621d) Stefan Steinhauser) +- Support unknown keyword (#2141) ([9ab1e](https://github.com/JSQLParser/JSqlParser/commit/9ab1ebded5fa493) Emily Ong) +- serialize `WithItem` ([df665](https://github.com/JSQLParser/JSqlParser/commit/df66569a87ce6fb) manticore-projects) +- Adding coverage for Postgres "OVERRIDING SYSTEM VALUE" (#2142) ([0ab13](https://github.com/JSQLParser/JSqlParser/commit/0ab131922d972aa) nicky6s) +- Piped SQL and FROM queries (WIP) ([01dc5](https://github.com/JSQLParser/JSqlParser/commit/01dc5f3472ce5ec) manticore-projects) +- Piped SQL and FROM queries (WIP) ([abf13](https://github.com/JSQLParser/JSqlParser/commit/abf137e04ca765d) manticore-projects) +- Piped SQL and FROM queries (WIP) ([f25e2](https://github.com/JSQLParser/JSqlParser/commit/f25e28bff4c96cb) manticore-projects) ### Bug Fixes -- `GRANT` to `PUBLIC` (allow keyword) ([4c4ff](https://github.com/JSQLParser/JSqlParser/commit/4c4ff286434f35e) manticore-projects) -- Oracle Hierarchical Expression shall allow XOR (not AND only) ([c9c84](https://github.com/JSQLParser/JSqlParser/commit/c9c844aa22d1bbc) manticore-projects) -- grammar ambiguities from #2217 ([b409c](https://github.com/JSQLParser/JSqlParser/commit/b409ceb5aa2c9d6) Andreas Reichel) -- SelectItem generics ([b73ce](https://github.com/JSQLParser/JSqlParser/commit/b73cee65cd74710) Andreas Reichel) -- add some public getters ([09f3d](https://github.com/JSQLParser/JSqlParser/commit/09f3dbcd1d8ad44) Andreas Reichel) -- avoid NPE on empty Window Element properties ([16685](https://github.com/JSQLParser/JSqlParser/commit/16685289d7d5879) Andreas Reichel) -- `IntervalExpression` supports complex expressions ([e30f3](https://github.com/JSQLParser/JSqlParser/commit/e30f30d27b58325) Andreas Reichel) -- avoid one JavaCC ambiguity warning ([0c092](https://github.com/JSQLParser/JSqlParser/commit/0c092afff549d52) Andreas Reichel) -- avoid NPE ([9714f](https://github.com/JSQLParser/JSqlParser/commit/9714f940d249af9) Andreas Reichel) -- Remove unneeded `LOOKAHEAD` ([fb978](https://github.com/JSQLParser/JSqlParser/commit/fb978ce11a81655) Andreas Reichel) -- `Like`, better use `SimpleExpression` only else there are precedence issues ([1d594](https://github.com/JSQLParser/JSqlParser/commit/1d59430eeeaf67e) Andreas Reichel) -- `Like` Expression accepts Complex Expressions including `BY PRIOR` ([9b92c](https://github.com/JSQLParser/JSqlParser/commit/9b92cdfcec7206c) Andreas Reichel) -- use `SelectItem` for `UnPivot` ([b1b86](https://github.com/JSQLParser/JSqlParser/commit/b1b86f549529ab6) Andreas Reichel) -- move `Pivot` and `UnPivot` before `OrderBy` ([9c37f](https://github.com/JSQLParser/JSqlParser/commit/9c37fe8eb8f3d42) Andreas Reichel) -- move `Pivot` and `UnPivot` before `OrderBy` ([2ca16](https://github.com/JSQLParser/JSqlParser/commit/2ca167a90f59c6c) Andreas Reichel) -- use `Function` and `SelectItem` ([931d3](https://github.com/JSQLParser/JSqlParser/commit/931d376ed9adc2a) Andreas Reichel) -- use `Function` and `SelectItem` ([db008](https://github.com/JSQLParser/JSqlParser/commit/db0081d18ba4b19) Andreas Reichel) -- `AllColumns` replace rewrite ([b366f](https://github.com/JSQLParser/JSqlParser/commit/b366f2b0a4e6ab6) Andreas Reichel) -- use proper tokens `..` and `...` in multi-part names for correct white space handling ([38d0e](https://github.com/JSQLParser/JSqlParser/commit/38d0e36e3335dab) Andreas Reichel) -- `AllColumns` replace takes expressions ([a2f40](https://github.com/JSQLParser/JSqlParser/commit/a2f40dd5f79fdef) Andreas Reichel) -- allowing skyline keywords as columns, tables, etc. (#2137) ([f9b1c](https://github.com/JSQLParser/JSqlParser/commit/f9b1cbd8a8e1c8d) nicky6s) +- `GRANT` to `PUBLIC` (allow keyword) ([4c4ff](https://github.com/JSQLParser/JSqlParser/commit/4c4ff286434f35e) manticore-projects) +- Oracle Hierarchical Expression shall allow XOR (not AND only) ([c9c84](https://github.com/JSQLParser/JSqlParser/commit/c9c844aa22d1bbc) manticore-projects) +- grammar ambiguities from #2217 ([b409c](https://github.com/JSQLParser/JSqlParser/commit/b409ceb5aa2c9d6) Andreas Reichel) +- SelectItem generics ([b73ce](https://github.com/JSQLParser/JSqlParser/commit/b73cee65cd74710) Andreas Reichel) +- add some public getters ([09f3d](https://github.com/JSQLParser/JSqlParser/commit/09f3dbcd1d8ad44) Andreas Reichel) +- avoid NPE on empty Window Element properties ([16685](https://github.com/JSQLParser/JSqlParser/commit/16685289d7d5879) Andreas Reichel) +- `IntervalExpression` supports complex expressions ([e30f3](https://github.com/JSQLParser/JSqlParser/commit/e30f30d27b58325) Andreas Reichel) +- avoid one JavaCC ambiguity warning ([0c092](https://github.com/JSQLParser/JSqlParser/commit/0c092afff549d52) Andreas Reichel) +- avoid NPE ([9714f](https://github.com/JSQLParser/JSqlParser/commit/9714f940d249af9) Andreas Reichel) +- Remove unneeded `LOOKAHEAD` ([fb978](https://github.com/JSQLParser/JSqlParser/commit/fb978ce11a81655) Andreas Reichel) +- `Like`, better use `SimpleExpression` only else there are precedence issues ([1d594](https://github.com/JSQLParser/JSqlParser/commit/1d59430eeeaf67e) Andreas Reichel) +- `Like` Expression accepts Complex Expressions including `BY PRIOR` ([9b92c](https://github.com/JSQLParser/JSqlParser/commit/9b92cdfcec7206c) Andreas Reichel) +- use `SelectItem` for `UnPivot` ([b1b86](https://github.com/JSQLParser/JSqlParser/commit/b1b86f549529ab6) Andreas Reichel) +- move `Pivot` and `UnPivot` before `OrderBy` ([9c37f](https://github.com/JSQLParser/JSqlParser/commit/9c37fe8eb8f3d42) Andreas Reichel) +- move `Pivot` and `UnPivot` before `OrderBy` ([2ca16](https://github.com/JSQLParser/JSqlParser/commit/2ca167a90f59c6c) Andreas Reichel) +- use `Function` and `SelectItem` ([931d3](https://github.com/JSQLParser/JSqlParser/commit/931d376ed9adc2a) Andreas Reichel) +- use `Function` and `SelectItem` ([db008](https://github.com/JSQLParser/JSqlParser/commit/db0081d18ba4b19) Andreas Reichel) +- `AllColumns` replace rewrite ([b366f](https://github.com/JSQLParser/JSqlParser/commit/b366f2b0a4e6ab6) Andreas Reichel) +- use proper tokens `..` and `...` in multi-part names for correct white space handling ([38d0e](https://github.com/JSQLParser/JSqlParser/commit/38d0e36e3335dab) Andreas Reichel) +- `AllColumns` replace takes expressions ([a2f40](https://github.com/JSQLParser/JSqlParser/commit/a2f40dd5f79fdef) Andreas Reichel) +- allowing skyline keywords as columns, tables, etc. (#2137) ([f9b1c](https://github.com/JSQLParser/JSqlParser/commit/f9b1cbd8a8e1c8d) nicky6s) ### Other changes @@ -117,74 +989,74 @@ Changelog of JSqlParser. **Add support for TableFunction [WITH OFFSET|ORDINALITY] (#2219)** -* Add support for TableFunction [WITH OFFSET|ORDINALITY] -* This PR fixes #2218, by adding a new `withClause` to a TableFunction, which allows for support of SQL-99's `UNNEST(...) WITH ORDINALITY` or GoogleSQL's `UNNEST(...) WITH OFFSET`. -* Currently, this is a feeler PR, as I'm getting a test failure, as the output of the parsing differs in my tests: -* ``` -* org.opentest4j.AssertionFailedError: Output from Deparser does not match. ==> -* Expected :select*from unnest(array[1,2,3])with offset as t(a,b) -* Actual :select*from unnest(array[1,2,3])as t(a,b) -* <Click to see difference> -* at org.junit.jupiter.api.AssertionFailureBuilder.build(AssertionFailureBuilder.java:151) -* at org.junit.jupiter.api.AssertionFailureBuilder.buildAndThrow(AssertionFailureBuilder.java:132) -* at org.junit.jupiter.api.AssertEquals.failNotEqual(AssertEquals.java:197) -* at org.junit.jupiter.api.AssertEquals.assertEquals(AssertEquals.java:182) -* at org.junit.jupiter.api.Assertions.assertEquals(Assertions.java:1156) -* at net.sf.jsqlparser@5.2-SNAPSHOT/net.sf.jsqlparser.test.TestUtils.assertStatementCanBeDeparsedAs(TestUtils.java:126) -* at net.sf.jsqlparser@5.2-SNAPSHOT/net.sf.jsqlparser.test.TestUtils.assertSqlCanBeParsedAndDeparsed(TestUtils.java:99) -* at net.sf.jsqlparser@5.2-SNAPSHOT/net.sf.jsqlparser.test.TestUtils.assertSqlCanBeParsedAndDeparsed(TestUtils.java:85) -* at net.sf.jsqlparser@5.2-SNAPSHOT/net.sf.jsqlparser.statement.select.TableFunctionTest.testTableFunctionWithSupportedWithClauses(TableFunctionTest.java:60) -* at java.base/java.lang.reflect.Method.invoke(Method.java:580) -* at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.accept(ForEachOps.java:184) -* at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:197) -* at java.base/java.util.stream.ReferencePipeline$2$1.accept(ReferencePipeline.java:179) -* at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:197) -* at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.accept(ForEachOps.java:184) -* at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:197) -* at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.accept(ForEachOps.java:184) -* at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.accept(ForEachOps.java:184) -* at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:197) -* at java.base/java.util.Spliterators$ArraySpliterator.forEachRemaining(Spliterators.java:1024) -* at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:509) -* at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:499) -* at java.base/java.util.stream.ForEachOps$ForEachOp.evaluateSequential(ForEachOps.java:151) -* at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateSequential(ForEachOps.java:174) -* at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234) -* at java.base/java.util.stream.ReferencePipeline.forEach(ReferencePipeline.java:596) -* at java.base/java.util.stream.ReferencePipeline$7$1.accept(ReferencePipeline.java:276) -* at java.base/java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1708) -* at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:509) -* at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:499) -* at java.base/java.util.stream.ForEachOps$ForEachOp.evaluateSequential(ForEachOps.java:151) -* at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateSequential(ForEachOps.java:174) -* at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234) -* at java.base/java.util.stream.ReferencePipeline.forEach(ReferencePipeline.java:596) -* at java.base/java.util.stream.ReferencePipeline$7$1.accept(ReferencePipeline.java:276) -* at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:197) -* at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:197) -* at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:197) -* at java.base/java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1708) -* at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:509) -* at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:499) -* at java.base/java.util.stream.ForEachOps$ForEachOp.evaluateSequential(ForEachOps.java:151) -* at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateSequential(ForEachOps.java:174) -* at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234) -* at java.base/java.util.stream.ReferencePipeline.forEach(ReferencePipeline.java:596) -* at java.base/java.util.stream.ReferencePipeline$7$1.accept(ReferencePipeline.java:276) -* at java.base/java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1708) -* at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:509) -* at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:499) -* at java.base/java.util.stream.ForEachOps$ForEachOp.evaluateSequential(ForEachOps.java:151) -* at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateSequential(ForEachOps.java:174) -* at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234) -* at java.base/java.util.stream.ReferencePipeline.forEach(ReferencePipeline.java:596) -* ``` -* rename clause -> withClause -* Resolving test failure, and modifying the SelectDeParser to add the withClause where present -* Addressing PR Review comments. Using existing WITH and OFFSET keywords, adding ORDINALITY keyword. -* Removing ORDINALITY from the reserved keywords list in ParserKeywordsUtils, and updating pushing the result of `gradle updateKeywords` -* --------- -* Co-authored-by: David Hayes <dhayes@adobe.com> +* Add support for TableFunction [WITH OFFSET|ORDINALITY] +* This PR fixes #2218, by adding a new `withClause` to a TableFunction, which allows for support of SQL-99's `UNNEST(...) WITH ORDINALITY` or GoogleSQL's `UNNEST(...) WITH OFFSET`. +* Currently, this is a feeler PR, as I'm getting a test failure, as the output of the parsing differs in my tests: +* ``` +* org.opentest4j.AssertionFailedError: Output from Deparser does not match. ==> +* Expected :select*from unnest(array[1,2,3])with offset as t(a,b) +* Actual :select*from unnest(array[1,2,3])as t(a,b) +* <Click to see difference> +* at org.junit.jupiter.api.AssertionFailureBuilder.build(AssertionFailureBuilder.java:151) +* at org.junit.jupiter.api.AssertionFailureBuilder.buildAndThrow(AssertionFailureBuilder.java:132) +* at org.junit.jupiter.api.AssertEquals.failNotEqual(AssertEquals.java:197) +* at org.junit.jupiter.api.AssertEquals.assertEquals(AssertEquals.java:182) +* at org.junit.jupiter.api.Assertions.assertEquals(Assertions.java:1156) +* at net.sf.jsqlparser@5.2-SNAPSHOT/net.sf.jsqlparser.test.TestUtils.assertStatementCanBeDeparsedAs(TestUtils.java:126) +* at net.sf.jsqlparser@5.2-SNAPSHOT/net.sf.jsqlparser.test.TestUtils.assertSqlCanBeParsedAndDeparsed(TestUtils.java:99) +* at net.sf.jsqlparser@5.2-SNAPSHOT/net.sf.jsqlparser.test.TestUtils.assertSqlCanBeParsedAndDeparsed(TestUtils.java:85) +* at net.sf.jsqlparser@5.2-SNAPSHOT/net.sf.jsqlparser.statement.select.TableFunctionTest.testTableFunctionWithSupportedWithClauses(TableFunctionTest.java:60) +* at java.base/java.lang.reflect.Method.invoke(Method.java:580) +* at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.accept(ForEachOps.java:184) +* at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:197) +* at java.base/java.util.stream.ReferencePipeline$2$1.accept(ReferencePipeline.java:179) +* at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:197) +* at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.accept(ForEachOps.java:184) +* at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:197) +* at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.accept(ForEachOps.java:184) +* at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.accept(ForEachOps.java:184) +* at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:197) +* at java.base/java.util.Spliterators$ArraySpliterator.forEachRemaining(Spliterators.java:1024) +* at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:509) +* at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:499) +* at java.base/java.util.stream.ForEachOps$ForEachOp.evaluateSequential(ForEachOps.java:151) +* at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateSequential(ForEachOps.java:174) +* at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234) +* at java.base/java.util.stream.ReferencePipeline.forEach(ReferencePipeline.java:596) +* at java.base/java.util.stream.ReferencePipeline$7$1.accept(ReferencePipeline.java:276) +* at java.base/java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1708) +* at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:509) +* at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:499) +* at java.base/java.util.stream.ForEachOps$ForEachOp.evaluateSequential(ForEachOps.java:151) +* at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateSequential(ForEachOps.java:174) +* at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234) +* at java.base/java.util.stream.ReferencePipeline.forEach(ReferencePipeline.java:596) +* at java.base/java.util.stream.ReferencePipeline$7$1.accept(ReferencePipeline.java:276) +* at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:197) +* at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:197) +* at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:197) +* at java.base/java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1708) +* at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:509) +* at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:499) +* at java.base/java.util.stream.ForEachOps$ForEachOp.evaluateSequential(ForEachOps.java:151) +* at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateSequential(ForEachOps.java:174) +* at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234) +* at java.base/java.util.stream.ReferencePipeline.forEach(ReferencePipeline.java:596) +* at java.base/java.util.stream.ReferencePipeline$7$1.accept(ReferencePipeline.java:276) +* at java.base/java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1708) +* at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:509) +* at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:499) +* at java.base/java.util.stream.ForEachOps$ForEachOp.evaluateSequential(ForEachOps.java:151) +* at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateSequential(ForEachOps.java:174) +* at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234) +* at java.base/java.util.stream.ReferencePipeline.forEach(ReferencePipeline.java:596) +* ``` +* rename clause -> withClause +* Resolving test failure, and modifying the SelectDeParser to add the withClause where present +* Addressing PR Review comments. Using existing WITH and OFFSET keywords, adding ORDINALITY keyword. +* Removing ORDINALITY from the reserved keywords list in ParserKeywordsUtils, and updating pushing the result of `gradle updateKeywords` +* --------- +* Co-authored-by: David Hayes <dhayes@adobe.com> [e5fa0](https://github.com/JSQLParser/JSqlParser/commit/e5fa004cdffdcc8) David Hayes *2025-04-08 09:22:59* @@ -195,11 +1067,11 @@ Changelog of JSqlParser. **Add MySQL Index Parsing Support for ALTER TABLE Statements (#2215)** -* add spatial, fulltext index -* add complex spatial index case -* fix Reflection test -* add test case about add index -* fix for passing test +* add spatial, fulltext index +* add complex spatial index case +* fix Reflection test +* add test case about add index +* fix for passing test [090bb](https://github.com/JSQLParser/JSqlParser/commit/090bb60da58c3f4) Minjae Lee *2025-04-04 12:32:44* @@ -235,16 +1107,16 @@ Changelog of JSqlParser. **Add support for parsing MySQL DISCARD and IMPORT TABLESPACE DDL statements (#2198)** -* Add support for parsing MySQL DISCARD and IMPORT TABLESPACE DDL statements -* fix code formatting -* --------- -* Co-authored-by: mj-db <mj.db@kakaocorp.com> +* Add support for parsing MySQL DISCARD and IMPORT TABLESPACE DDL statements +* fix code formatting +* --------- +* Co-authored-by: mj-db <mj.db@kakaocorp.com> [8e651](https://github.com/JSQLParser/JSqlParser/commit/8e651ff232171eb) Minjae Lee *2025-03-20 23:44:30* **IsNullExpression and IsBooleanExpression children are not visited in TablesNamesFinder (#2180)** -* IsNullExpression and IsBooleanExpression children are not visited in TablesNamesFinder +* IsNullExpression and IsBooleanExpression children are not visited in TablesNamesFinder [ad575](https://github.com/JSQLParser/JSqlParser/commit/ad5758048f06462) SuperPat *2025-03-04 14:07:39* @@ -255,16 +1127,16 @@ Changelog of JSqlParser. **added support for minus all; except all; intersect all; (#2081)** -* Co-authored-by: sunanda-vs <22m2107@iitb.ac.in> +* Co-authored-by: sunanda-vs <22m2107@iitb.ac.in> [4492e](https://github.com/JSQLParser/JSqlParser/commit/4492e74fd36f8a4) Sunanda Somwase *2025-02-07 23:42:00* **#2119 add support INSERT OVERWRITE PARTITION (#2135)** -* #2119 add support INSERT OVERWRITE PARTITION -* #2119 fix test case -* #2119 add reserved keyword -* run spotlessApply +* #2119 add support INSERT OVERWRITE PARTITION +* #2119 fix test case +* #2119 add reserved keyword +* run spotlessApply [f1dee](https://github.com/JSQLParser/JSqlParser/commit/f1dee4c43a9a300) tt20061904 *2025-01-06 06:07:23* @@ -273,48 +1145,48 @@ Changelog of JSqlParser. ### Features -- allow file names as `Table` supporting CSV or Parquet files ([b2877](https://github.com/JSQLParser/JSqlParser/commit/b287779823e338b) manticore-projects) -- add support MATCH_ANY MATCH_ALL MATCH_PHRASE MATCH_PHRASE_PREFIX MATCH_REGEXP (#2132) ([f6c3c](https://github.com/JSQLParser/JSqlParser/commit/f6c3c7dc2a1b4ff) Liming Deng) -- syntax sugar for table functions ([e8ff9](https://github.com/JSQLParser/JSqlParser/commit/e8ff9fb98d299dc) manticore-projects) -- functions with extra keywords, like BigQuery Timeseries functions ([b0b0f](https://github.com/JSQLParser/JSqlParser/commit/b0b0f4d6178918b) manticore-projects) -- add JSON to the list of data types ([92bbd](https://github.com/JSQLParser/JSqlParser/commit/92bbd90e8755d8d) Andreas Reichel) -- proper `:` JSon operator ([f8080](https://github.com/JSQLParser/JSqlParser/commit/f80800ca5294e89) Andreas Reichel) -- Snowflake `GET` operator `:` ([6eb58](https://github.com/JSQLParser/JSqlParser/commit/6eb588752f0f780) Andreas Reichel) -- `LATERAL VIEW` supports multiple alias columns ([4814c](https://github.com/JSQLParser/JSqlParser/commit/4814ccdd35b4277) Andreas Reichel) -- `ON OVERFLOW` support for the `ListAgg` function (Oracle, DB2) ([5cf28](https://github.com/JSQLParser/JSqlParser/commit/5cf281f1de4f9d3) Andreas Reichel) -- `JsonExpression` supports Expressions rather than Char/Long only ([3d488](https://github.com/JSQLParser/JSqlParser/commit/3d4883523e255e9) Andreas Reichel) -- `CosineSimilarity` Expression ([90adf](https://github.com/JSQLParser/JSqlParser/commit/90adf829153e00f) Andreas Reichel) -- add syntax sugar for accessing name parts easier ([25b08](https://github.com/JSQLParser/JSqlParser/commit/25b08b15e3cc670) manticore-projects) -- add Oracle/H2 `EXPLAIN PLAN [FOR] select` ([d837b](https://github.com/JSQLParser/JSqlParser/commit/d837b677add70ef) manticore-projects) -- add DuckDB `SUMMARIZE table | select` ([ad132](https://github.com/JSQLParser/JSqlParser/commit/ad132ee298d7bfe) manticore-projects) -- methods for returning unquoted names and identifiers ([7e7ac](https://github.com/JSQLParser/JSqlParser/commit/7e7acba1b7d8e54) manticore-projects) -- methods for returning unquoted names and identifiers ([728b2](https://github.com/JSQLParser/JSqlParser/commit/728b286f0948aba) manticore-projects) -- `CREATE SCHEMA IF NOT EXISTS ...` ([d7bf2](https://github.com/JSQLParser/JSqlParser/commit/d7bf249b94c97ff) manticore-projects) -- improve the Expression Visitor Adapter ([e8bc4](https://github.com/JSQLParser/JSqlParser/commit/e8bc4463f907b75) Andreas Reichel) -- improve Visitors ([8a662](https://github.com/JSQLParser/JSqlParser/commit/8a662af8109b650) Andreas Reichel) -- syntax sugar ([13572](https://github.com/JSQLParser/JSqlParser/commit/13572a83b703324) Andreas Reichel) -- improve String representation of `Table` and `Column` ([f3268](https://github.com/JSQLParser/JSqlParser/commit/f3268e73bdd0040) Andreas Reichel) -- improve usability of the `ExpressionVisitorAdapter` ([6a36b](https://github.com/JSQLParser/JSqlParser/commit/6a36b3fc6af5ae1) Andreas Reichel) +- allow file names as `Table` supporting CSV or Parquet files ([b2877](https://github.com/JSQLParser/JSqlParser/commit/b287779823e338b) manticore-projects) +- add support MATCH_ANY MATCH_ALL MATCH_PHRASE MATCH_PHRASE_PREFIX MATCH_REGEXP (#2132) ([f6c3c](https://github.com/JSQLParser/JSqlParser/commit/f6c3c7dc2a1b4ff) Liming Deng) +- syntax sugar for table functions ([e8ff9](https://github.com/JSQLParser/JSqlParser/commit/e8ff9fb98d299dc) manticore-projects) +- functions with extra keywords, like BigQuery Timeseries functions ([b0b0f](https://github.com/JSQLParser/JSqlParser/commit/b0b0f4d6178918b) manticore-projects) +- add JSON to the list of data types ([92bbd](https://github.com/JSQLParser/JSqlParser/commit/92bbd90e8755d8d) Andreas Reichel) +- proper `:` JSon operator ([f8080](https://github.com/JSQLParser/JSqlParser/commit/f80800ca5294e89) Andreas Reichel) +- Snowflake `GET` operator `:` ([6eb58](https://github.com/JSQLParser/JSqlParser/commit/6eb588752f0f780) Andreas Reichel) +- `LATERAL VIEW` supports multiple alias columns ([4814c](https://github.com/JSQLParser/JSqlParser/commit/4814ccdd35b4277) Andreas Reichel) +- `ON OVERFLOW` support for the `ListAgg` function (Oracle, DB2) ([5cf28](https://github.com/JSQLParser/JSqlParser/commit/5cf281f1de4f9d3) Andreas Reichel) +- `JsonExpression` supports Expressions rather than Char/Long only ([3d488](https://github.com/JSQLParser/JSqlParser/commit/3d4883523e255e9) Andreas Reichel) +- `CosineSimilarity` Expression ([90adf](https://github.com/JSQLParser/JSqlParser/commit/90adf829153e00f) Andreas Reichel) +- add syntax sugar for accessing name parts easier ([25b08](https://github.com/JSQLParser/JSqlParser/commit/25b08b15e3cc670) manticore-projects) +- add Oracle/H2 `EXPLAIN PLAN [FOR] select` ([d837b](https://github.com/JSQLParser/JSqlParser/commit/d837b677add70ef) manticore-projects) +- add DuckDB `SUMMARIZE table | select` ([ad132](https://github.com/JSQLParser/JSqlParser/commit/ad132ee298d7bfe) manticore-projects) +- methods for returning unquoted names and identifiers ([7e7ac](https://github.com/JSQLParser/JSqlParser/commit/7e7acba1b7d8e54) manticore-projects) +- methods for returning unquoted names and identifiers ([728b2](https://github.com/JSQLParser/JSqlParser/commit/728b286f0948aba) manticore-projects) +- `CREATE SCHEMA IF NOT EXISTS ...` ([d7bf2](https://github.com/JSQLParser/JSqlParser/commit/d7bf249b94c97ff) manticore-projects) +- improve the Expression Visitor Adapter ([e8bc4](https://github.com/JSQLParser/JSqlParser/commit/e8bc4463f907b75) Andreas Reichel) +- improve Visitors ([8a662](https://github.com/JSQLParser/JSqlParser/commit/8a662af8109b650) Andreas Reichel) +- syntax sugar ([13572](https://github.com/JSQLParser/JSqlParser/commit/13572a83b703324) Andreas Reichel) +- improve String representation of `Table` and `Column` ([f3268](https://github.com/JSQLParser/JSqlParser/commit/f3268e73bdd0040) Andreas Reichel) +- improve usability of the `ExpressionVisitorAdapter` ([6a36b](https://github.com/JSQLParser/JSqlParser/commit/6a36b3fc6af5ae1) Andreas Reichel) ### Bug Fixes -- rewrite the production to make it comptaible with the Maven build ([785af](https://github.com/JSQLParser/JSqlParser/commit/785af163575f189) manticore-projects) -- [FEATURE] TablesNamesFinder does not support CREATE VIEW #2123 (#2124) ([8f843](https://github.com/JSQLParser/JSqlParser/commit/8f8439e485a412e) Cedric Dandoy) -- De-parse `TableFunction` ([e122b](https://github.com/JSQLParser/JSqlParser/commit/e122bcf7d7a01cf) manticore-projects) -- Alias of the ParenthesedSelect when FromItem is a TableFunction ([46919](https://github.com/JSQLParser/JSqlParser/commit/469190d03709254) manticore-projects) -- JSon functions with Column parameters ([82251](https://github.com/JSQLParser/JSqlParser/commit/822517899546f19) Andreas Reichel) -- Issue #2109 true and false value parsed as column instead of BooleanValue (#2110) ([7acf9](https://github.com/JSQLParser/JSqlParser/commit/7acf9d548edc854) lucarota) -- `CREATE TABLE` UNIQUE vs. UNIQUE KEY ([3030c](https://github.com/JSQLParser/JSqlParser/commit/3030c35d28ad3cf) Andreas Reichel) -- clean up minor issues around #2083 ([be8ff](https://github.com/JSQLParser/JSqlParser/commit/be8ff930ebc4fa0) Andreas Reichel) -- avoid NPE ([db4b0](https://github.com/JSQLParser/JSqlParser/commit/db4b04dd0b8ce28) manticore-projects) -- fix the Grammar so it builds with Maven ([d3d3a](https://github.com/JSQLParser/JSqlParser/commit/d3d3af9b5974fb5) manticore-projects) -- `TableNameFinder` for `Alter Table` ([33ba5](https://github.com/JSQLParser/JSqlParser/commit/33ba5eb521c4dae) manticore-projects) -- remove obsolete `SimpleFunction` ([cd068](https://github.com/JSQLParser/JSqlParser/commit/cd0689e0120fc8e) manticore-projects) -- license headers ([e842e](https://github.com/JSQLParser/JSqlParser/commit/e842e18511c65f5) Andreas Reichel) -- add needed LOOKAHEAD(2) ([421e2](https://github.com/JSQLParser/JSqlParser/commit/421e2894b226018) Andreas Reichel) -- TablesNamesFinder `UpdateSets` ([11ceb](https://github.com/JSQLParser/JSqlParser/commit/11cebcfd1220944) Andreas Reichel) -- Multi-Variable `LambdaExpression` ([e0ad7](https://github.com/JSQLParser/JSqlParser/commit/e0ad7c84b688c39) Andreas Reichel) -- `FromItem` alias must not shade an actual table (found before) ([d8207](https://github.com/JSQLParser/JSqlParser/commit/d8207aaf616f10e) Andreas Reichel) +- rewrite the production to make it comptaible with the Maven build ([785af](https://github.com/JSQLParser/JSqlParser/commit/785af163575f189) manticore-projects) +- [FEATURE] TablesNamesFinder does not support CREATE VIEW #2123 (#2124) ([8f843](https://github.com/JSQLParser/JSqlParser/commit/8f8439e485a412e) Cedric Dandoy) +- De-parse `TableFunction` ([e122b](https://github.com/JSQLParser/JSqlParser/commit/e122bcf7d7a01cf) manticore-projects) +- Alias of the ParenthesedSelect when FromItem is a TableFunction ([46919](https://github.com/JSQLParser/JSqlParser/commit/469190d03709254) manticore-projects) +- JSon functions with Column parameters ([82251](https://github.com/JSQLParser/JSqlParser/commit/822517899546f19) Andreas Reichel) +- Issue #2109 true and false value parsed as column instead of BooleanValue (#2110) ([7acf9](https://github.com/JSQLParser/JSqlParser/commit/7acf9d548edc854) lucarota) +- `CREATE TABLE` UNIQUE vs. UNIQUE KEY ([3030c](https://github.com/JSQLParser/JSqlParser/commit/3030c35d28ad3cf) Andreas Reichel) +- clean up minor issues around #2083 ([be8ff](https://github.com/JSQLParser/JSqlParser/commit/be8ff930ebc4fa0) Andreas Reichel) +- avoid NPE ([db4b0](https://github.com/JSQLParser/JSqlParser/commit/db4b04dd0b8ce28) manticore-projects) +- fix the Grammar so it builds with Maven ([d3d3a](https://github.com/JSQLParser/JSqlParser/commit/d3d3af9b5974fb5) manticore-projects) +- `TableNameFinder` for `Alter Table` ([33ba5](https://github.com/JSQLParser/JSqlParser/commit/33ba5eb521c4dae) manticore-projects) +- remove obsolete `SimpleFunction` ([cd068](https://github.com/JSQLParser/JSqlParser/commit/cd0689e0120fc8e) manticore-projects) +- license headers ([e842e](https://github.com/JSQLParser/JSqlParser/commit/e842e18511c65f5) Andreas Reichel) +- add needed LOOKAHEAD(2) ([421e2](https://github.com/JSQLParser/JSqlParser/commit/421e2894b226018) Andreas Reichel) +- TablesNamesFinder `UpdateSets` ([11ceb](https://github.com/JSQLParser/JSqlParser/commit/11cebcfd1220944) Andreas Reichel) +- Multi-Variable `LambdaExpression` ([e0ad7](https://github.com/JSQLParser/JSqlParser/commit/e0ad7c84b688c39) Andreas Reichel) +- `FromItem` alias must not shade an actual table (found before) ([d8207](https://github.com/JSQLParser/JSqlParser/commit/d8207aaf616f10e) Andreas Reichel) ### Other changes @@ -345,7 +1217,7 @@ Changelog of JSqlParser. **feat mysql alter force,engine,algorithm,lock (#2121)** -* Co-authored-by: mj-db <mj.db@kakaocorp.com> +* Co-authored-by: mj-db <mj.db@kakaocorp.com> [18c1a](https://github.com/JSQLParser/JSqlParser/commit/18c1a2c63d9719b) Minjae Lee *2024-12-09 09:09:37* @@ -361,111 +1233,111 @@ Changelog of JSqlParser. **Fix issue 2089: Enhance MySQL CONVERT Statement Parsing (#2117)** -* fix convert toString -* fix mysql convert -* --------- -* Co-authored-by: mj-db <mj.db@kakaocorp.com> +* fix convert toString +* fix mysql convert +* --------- +* Co-authored-by: mj-db <mj.db@kakaocorp.com> [8b414](https://github.com/JSQLParser/JSqlParser/commit/8b4149b3b7e9a35) Minjae Lee *2024-11-26 05:55:37* **Fix issue 2106: Add parsing functionality for MySQL ALTER Table option statements (#2115)** -* add alter table option -* fix CreateParameter by adding auto_increment for passing test -* fix by updatekeywords -* test add assertSqlCanBeParsedAndDeparsed -* --------- -* Co-authored-by: mj-db <mj.db@kakaocorp.com> +* add alter table option +* fix CreateParameter by adding auto_increment for passing test +* fix by updatekeywords +* test add assertSqlCanBeParsedAndDeparsed +* --------- +* Co-authored-by: mj-db <mj.db@kakaocorp.com> [fee2f](https://github.com/JSQLParser/JSqlParser/commit/fee2f90c0283b3e) Minjae Lee *2024-11-24 00:46:00* **Fix issue 2106: Add parsing functionality for MySQL ADD PARTITION and DROP PARTITION clauses in ALTER TABLE statements(2) (#2108)** -* feat MySQL Alter add partition -* fix PartitionDefinition to Serializable -* add Engine variable to MySQL partition definition -* fix: Update parser to correctly handle ENGINE token and pass existing CREATE TABLE tests -* feat mysql alter drop partition -* refactor truncate partition -* fix codacy -* fix: add LOOKAHEADs -* Signed-off-by: Andreas Reichel <andreas@manticore-projects.com> -* doc: mention running `gradle check` -* Signed-off-by: Andreas Reichel <andreas@manticore-projects.com> -* style: add license header -* Signed-off-by: Andreas Reichel <andreas@manticore-projects.com> -* --------- -* Signed-off-by: Andreas Reichel <andreas@manticore-projects.com> -* Co-authored-by: mj-db <mj.db@kakaocorp.com> -* Co-authored-by: Andreas Reichel <andreas@manticore-projects.com> +* feat MySQL Alter add partition +* fix PartitionDefinition to Serializable +* add Engine variable to MySQL partition definition +* fix: Update parser to correctly handle ENGINE token and pass existing CREATE TABLE tests +* feat mysql alter drop partition +* refactor truncate partition +* fix codacy +* fix: add LOOKAHEADs +* Signed-off-by: Andreas Reichel <andreas@manticore-projects.com> +* doc: mention running `gradle check` +* Signed-off-by: Andreas Reichel <andreas@manticore-projects.com> +* style: add license header +* Signed-off-by: Andreas Reichel <andreas@manticore-projects.com> +* --------- +* Signed-off-by: Andreas Reichel <andreas@manticore-projects.com> +* Co-authored-by: mj-db <mj.db@kakaocorp.com> +* Co-authored-by: Andreas Reichel <andreas@manticore-projects.com> [12952](https://github.com/JSQLParser/JSqlParser/commit/12952d64f87bdef) Minjae Lee *2024-11-14 01:05:19* **Fix issue 2106: Add parsing functionality for MySQL `ADD PARTITION` and `DROP PARTITION` clauses in `ALTER TABLE` statements (#2107)** -* feat MySQL Alter add partition -* fix PartitionDefinition to Serializable -* --------- -* Co-authored-by: mj-db <mj.db@kakaocorp.com> +* feat MySQL Alter add partition +* fix PartitionDefinition to Serializable +* --------- +* Co-authored-by: mj-db <mj.db@kakaocorp.com> [7b0e4](https://github.com/JSQLParser/JSqlParser/commit/7b0e42f60a60153) Minjae Lee *2024-11-12 12:05:28* **Add parsing functionality for MySQL CONVERT TO statement (#2097)** -* Co-authored-by: mj-db <mj.db@kakaocorp.com> +* Co-authored-by: mj-db <mj.db@kakaocorp.com> [dc123](https://github.com/JSQLParser/JSqlParser/commit/dc123820f869803) Minjae Lee *2024-10-25 15:33:05* **Fix issue 2090: Correctly parse LOCK clause in ALTER TABLE statements (#2095)** -* Add support for parsing LOCK clause in ALTER TABLE statements -* fix code formatting -* --------- -* Co-authored-by: mj-db <mj.db@kakaocorp.com> +* Add support for parsing LOCK clause in ALTER TABLE statements +* fix code formatting +* --------- +* Co-authored-by: mj-db <mj.db@kakaocorp.com> [f3f6b](https://github.com/JSQLParser/JSqlParser/commit/f3f6b72731d5c92) Minjae Lee *2024-10-24 12:20:58* **Fix parsing `ALTER TABLE ... ADD COLUMNS (...)` (#2087)** -* The current behaviour fails when it encounters `ADD COLUMNS` and merges -* everything in the `()` into one string with no whitespace. -* So `ALTER TABLE catalog.table.name ADD COLUMNS (apples int)` becomes -* `ALTER TABLE catalog.table.name ADD COLUMNS (applesint)`. -* This commit adds an understanding of how `ADD COLUMNS` to the grammar. +* The current behaviour fails when it encounters `ADD COLUMNS` and merges +* everything in the `()` into one string with no whitespace. +* So `ALTER TABLE catalog.table.name ADD COLUMNS (apples int)` becomes +* `ALTER TABLE catalog.table.name ADD COLUMNS (applesint)`. +* This commit adds an understanding of how `ADD COLUMNS` to the grammar. [22909](https://github.com/JSQLParser/JSqlParser/commit/229099c74b92845) Nathan Jaremko *2024-10-24 01:11:46* **Failure to parse query with PRIOR in select list (#2083)** -* Added support for PRIOR operator to be used in select list -* Fixed copy paste issue +* Added support for PRIOR operator to be used in select list +* Fixed copy paste issue [30469](https://github.com/JSQLParser/JSqlParser/commit/30469248935ecb9) hannes92 *2024-10-02 23:57:04* **Skyline syntax (preferring clause) (#2078)** -* feat: Implement skyline syntax (preferring clause) -* fix: Fix codacy errors -* style: Execute :spotlessApply -* fix: Remove unused import -* refactor: Replace wildcard imports -* refactor: Remove redundant imports -* --------- -* Co-authored-by: Stefan Steinhauser <stefan.steinhauser@arz.at> +* feat: Implement skyline syntax (preferring clause) +* fix: Fix codacy errors +* style: Execute :spotlessApply +* fix: Remove unused import +* refactor: Replace wildcard imports +* refactor: Remove redundant imports +* --------- +* Co-authored-by: Stefan Steinhauser <stefan.steinhauser@arz.at> [d1373](https://github.com/JSQLParser/JSqlParser/commit/d1373c5f7048224) Stefan Steinhauser *2024-09-17 09:31:32* **Unparenthesized `SubSelect` as `FromItem` (#2073)** -* feat: Implement FromItem in Select -* Implement FromItem interface in Select to allow Select being an unparenthesized FromItem -* Refs: JSQLParser/JSqlParser#2071 -* feat: Implement grammar to support Select as FromItem -* Refs: JSQLParser/JSqlParser#2071 -* style: Run gradle :spotlessApply -* style: Run gradle :spotlessApply -* --------- -* Co-authored-by: Stefan Steinhauser <stefan.steinhauser@arz.at> +* feat: Implement FromItem in Select +* Implement FromItem interface in Select to allow Select being an unparenthesized FromItem +* Refs: JSQLParser/JSqlParser#2071 +* feat: Implement grammar to support Select as FromItem +* Refs: JSQLParser/JSqlParser#2071 +* style: Run gradle :spotlessApply +* style: Run gradle :spotlessApply +* --------- +* Co-authored-by: Stefan Steinhauser <stefan.steinhauser@arz.at> [60f4d](https://github.com/JSQLParser/JSqlParser/commit/60f4d74f7d8d500) Stefan Steinhauser *2024-09-10 10:49:49* @@ -481,51 +1353,51 @@ Changelog of JSqlParser. **Exasol support (#2046)** -* feat: Support REGEXP_LIKE as LikeExpression -* Implement support of REGEXP_LIKE as LikeExpression as described here: https://docs.exasol.com/db/latest/sql_references/predicates/not_regexp_like.htm -* feat: Support sub select as part of function parameters -* Allow unparenthesesed sub selects being part of multiple function parameters -* fix: Readd mistakenly removed K_TEXT_LITERAL -* revert: Revert code formatting changes -* fix: Fix choice conflicts -* refactor: Rename test methods -* refactor: Apply on changed files -* feat: Introduce allowUnparenthesizedSubSelects feature -* refactor: Apply formatApply -* test: add test for the standard grammar, expected to fail -* test: make the performance tests more robust regarding the time-outs -* style: reformat code -* Signed-off-by: Andreas Reichel <andreas@manticore-projects.com> -* fix: Revert default keyword changes -* fix: Revert default keyword changes -* --------- -* Signed-off-by: Andreas Reichel <andreas@manticore-projects.com> -* Co-authored-by: Stefan Steinhauser <stefan.steinhauser@arz.at> -* Co-authored-by: Andreas Reichel <andreas@manticore-projects.com> +* feat: Support REGEXP_LIKE as LikeExpression +* Implement support of REGEXP_LIKE as LikeExpression as described here: https://docs.exasol.com/db/latest/sql_references/predicates/not_regexp_like.htm +* feat: Support sub select as part of function parameters +* Allow unparenthesesed sub selects being part of multiple function parameters +* fix: Readd mistakenly removed K_TEXT_LITERAL +* revert: Revert code formatting changes +* fix: Fix choice conflicts +* refactor: Rename test methods +* refactor: Apply on changed files +* feat: Introduce allowUnparenthesizedSubSelects feature +* refactor: Apply formatApply +* test: add test for the standard grammar, expected to fail +* test: make the performance tests more robust regarding the time-outs +* style: reformat code +* Signed-off-by: Andreas Reichel <andreas@manticore-projects.com> +* fix: Revert default keyword changes +* fix: Revert default keyword changes +* --------- +* Signed-off-by: Andreas Reichel <andreas@manticore-projects.com> +* Co-authored-by: Stefan Steinhauser <stefan.steinhauser@arz.at> +* Co-authored-by: Andreas Reichel <andreas@manticore-projects.com> [93ca6](https://github.com/JSQLParser/JSqlParser/commit/93ca6610425fc8a) Stefan Steinhauser *2024-08-20 08:16:27* **chore removing system.out.println lines + minor clean up of unit test scripts (#2060)** -* chore removing system.out.println lines + minor clean up -* formatting fixes via spotlessApply +* chore removing system.out.println lines + minor clean up +* formatting fixes via spotlessApply [11616](https://github.com/JSQLParser/JSqlParser/commit/11616a0206ed7c5) nicky6s *2024-08-19 07:50:32* **feature/fix: parsing inserts/updates/delete within CTEs (#2055)** -* feature parsing inserts/updates/delete within CTEs -* removing System lines -* fixing codacy issues -* reducing the looping in NestedBracketsPerformanceTest to just 6 -* formatting fixes via spotlessApply +* feature parsing inserts/updates/delete within CTEs +* removing System lines +* fixing codacy issues +* reducing the looping in NestedBracketsPerformanceTest to just 6 +* formatting fixes via spotlessApply [82470](https://github.com/JSQLParser/JSqlParser/commit/82470e55aaf9157) nicky6s *2024-08-04 16:45:56* **chore adding extra details to unit test scenarios (#2051)** -* chore adding extra details to unit test scenarios -* addressing review comments +* chore adding extra details to unit test scenarios +* addressing review comments [21c60](https://github.com/JSQLParser/JSqlParser/commit/21c605e3e2f5f52) nicky6s *2024-07-31 19:19:08* @@ -536,32 +1408,32 @@ Changelog of JSqlParser. **fix truncate parsing to capture multiple tables (#2048)** -* fix truncate parsing to capture multiple tables -* followup fixes including adding a lookahead -* replacng tabs with spaces -* increasing lookahead -* fixing after getting maven working locally +* fix truncate parsing to capture multiple tables +* followup fixes including adding a lookahead +* replacng tabs with spaces +* increasing lookahead +* fixing after getting maven working locally [aca82](https://github.com/JSQLParser/JSqlParser/commit/aca82f5926b2e95) Nick Redfearn *2024-07-26 11:52:27* **Resolve parsing error for CHARACTER SET and COLLATE in MySQL ALTER TABLE (issue 2027) (#2045)** -* fix: parsing alter text character set -* style: remove unnecessary comment -* fix: TEXT as a data type token -* --------- -* Co-authored-by: mj-db <mj.db@kakaocorp.com> +* fix: parsing alter text character set +* style: remove unnecessary comment +* fix: TEXT as a data type token +* --------- +* Co-authored-by: mj-db <mj.db@kakaocorp.com> [93c82](https://github.com/JSQLParser/JSqlParser/commit/93c8210e218d7f5) Minjae Lee *2024-07-23 02:27:05* **Avoid private tokens to be white listed and allow any word character in token value (#2044)** -* fix: Avoid private tokens to be white listed -* Closes JSQLParser/JSqlParser#2040 -* feat: Allow all word characters as token value -* Closes JSQLParser/JSqlParser#2041 -* --------- -* Co-authored-by: Stefan Steinhauser <stefan.steinhauser@arz.at> +* fix: Avoid private tokens to be white listed +* Closes JSQLParser/JSqlParser#2040 +* feat: Allow all word characters as token value +* Closes JSQLParser/JSqlParser#2041 +* --------- +* Co-authored-by: Stefan Steinhauser <stefan.steinhauser@arz.at> [821e9](https://github.com/JSQLParser/JSqlParser/commit/821e92e8fb6e48e) Stefan Steinhauser *2024-07-20 10:35:47* @@ -570,97 +1442,97 @@ Changelog of JSqlParser. ### Breaking changes -- Visitors return Objects and accept parameters ([5bd28](https://github.com/JSQLParser/JSqlParser/commit/5bd28c8b309df6c) Andreas Reichel) -- Visitors return Objects ([131a9](https://github.com/JSQLParser/JSqlParser/commit/131a988ccea2d91) Andreas Reichel) -- Visitors return Objects ([c2328](https://github.com/JSQLParser/JSqlParser/commit/c2328120e7a79ff) Andreas Reichel) -- Visitors return Objects ([ec497](https://github.com/JSQLParser/JSqlParser/commit/ec49762708e920a) Andreas Reichel) -- Visitors return Objects ([681ca](https://github.com/JSQLParser/JSqlParser/commit/681cac933d83516) Andreas Reichel) +- Visitors return Objects and accept parameters ([5bd28](https://github.com/JSQLParser/JSqlParser/commit/5bd28c8b309df6c) Andreas Reichel) +- Visitors return Objects ([131a9](https://github.com/JSQLParser/JSqlParser/commit/131a988ccea2d91) Andreas Reichel) +- Visitors return Objects ([c2328](https://github.com/JSQLParser/JSqlParser/commit/c2328120e7a79ff) Andreas Reichel) +- Visitors return Objects ([ec497](https://github.com/JSQLParser/JSqlParser/commit/ec49762708e920a) Andreas Reichel) +- Visitors return Objects ([681ca](https://github.com/JSQLParser/JSqlParser/commit/681cac933d83516) Andreas Reichel) ### Features -- provide compatibility methods ([3f995](https://github.com/JSQLParser/JSqlParser/commit/3f99548b99bbfe3) Andreas Reichel) -- apply the new parametrized Visitor patterns to all entities and provide default implementations ([e1692](https://github.com/JSQLParser/JSqlParser/commit/e1692990c543ed1) Andreas Reichel) -- syntax sugar ([2fce4](https://github.com/JSQLParser/JSqlParser/commit/2fce4c009b77d85) Andreas Reichel) -- Visitors return Objects and accept parameters ([5bd28](https://github.com/JSQLParser/JSqlParser/commit/5bd28c8b309df6c) Andreas Reichel) -- Visitors return Objects ([131a9](https://github.com/JSQLParser/JSqlParser/commit/131a988ccea2d91) Andreas Reichel) -- Visitors return Objects ([c2328](https://github.com/JSQLParser/JSqlParser/commit/c2328120e7a79ff) Andreas Reichel) -- Visitors return Objects ([ec497](https://github.com/JSQLParser/JSqlParser/commit/ec49762708e920a) Andreas Reichel) -- Visitors return Objects ([681ca](https://github.com/JSQLParser/JSqlParser/commit/681cac933d83516) Andreas Reichel) -- Allow OUTER keyword as function parameter name (#2021) ([fc90c](https://github.com/JSQLParser/JSqlParser/commit/fc90c0b5e566533) Chris Crabtree) -- BigQuery `SELECT AS STRUCT ...` and `SELECT AS VALUE ...` ([5c360](https://github.com/JSQLParser/JSqlParser/commit/5c360a2fc95c261) Andreas Reichel) -- add syntax sugar ([2ace7](https://github.com/JSQLParser/JSqlParser/commit/2ace74d1047e87d) Andreas Reichel) -- `AllColumns`, DuckDB uses `EXCLUDE` instead of `EXCEPT` ([1ad42](https://github.com/JSQLParser/JSqlParser/commit/1ad4234280f7a70) Andreas Reichel) -- syntax sugar ([ae1ef](https://github.com/JSQLParser/JSqlParser/commit/ae1eff9f7434c08) Andreas Reichel) -- syntax sugar ([81846](https://github.com/JSQLParser/JSqlParser/commit/818464c93ae665a) Andreas Reichel) -- syntax sugar ([2cb3e](https://github.com/JSQLParser/JSqlParser/commit/2cb3e589b60e192) Andreas Reichel) -- syntax sugar ([b2eed](https://github.com/JSQLParser/JSqlParser/commit/b2eed1e910c97de) Andreas Reichel) -- Databricks IGNORE/RESPECT NULLS ([e9c9a](https://github.com/JSQLParser/JSqlParser/commit/e9c9a173a660bbe) Andreas Reichel) -- Databricks IGNORE/RESPECT NULLS ([544b1](https://github.com/JSQLParser/JSqlParser/commit/544b1683789f20b) Andreas Reichel) -- Capture expression name part delimiters (#2001) ([0368b](https://github.com/JSQLParser/JSqlParser/commit/0368b9ebad76742) Chris Crabtree) -- syntax sugar ([ca5c5](https://github.com/JSQLParser/JSqlParser/commit/ca5c553efde37eb) Andreas Reichel) -- translate HEX to Unicode String and ByteArray String ([df519](https://github.com/JSQLParser/JSqlParser/commit/df519333ff34740) Andreas Reichel) -- `StructType` syntax sugar ([6e9bf](https://github.com/JSQLParser/JSqlParser/commit/6e9bf42b0b2d783) Andreas Reichel) -- `Values` implement `FromItem` ([e426c](https://github.com/JSQLParser/JSqlParser/commit/e426c5a67c505b5) Andreas Reichel) -- add `ParenthesedSelect` delegate ([66d05](https://github.com/JSQLParser/JSqlParser/commit/66d05a2bb7c41f3) Andreas Reichel) -- add `ParenthesedSelect` delegate ([f1699](https://github.com/JSQLParser/JSqlParser/commit/f16999393589702) Andreas Reichel) -- Simplify traversing the AST bottom to top ([bddc4](https://github.com/JSQLParser/JSqlParser/commit/bddc41cddf5b5bf) Andreas Reichel) -- AST Node access for `FromItem` ([c1edf](https://github.com/JSQLParser/JSqlParser/commit/c1edf0f8f21bd52) Andreas Reichel) -- RedShift specific Window function IGNORE | RESPECT NULLS ([321c8](https://github.com/JSQLParser/JSqlParser/commit/321c88098a75791) Andreas Reichel) -- RedShift allows `TOP` before `DISTINCT`, see https://docs.aws.amazon.com/redshift/latest/dg/r_SELECT_list.html ([13e61](https://github.com/JSQLParser/JSqlParser/commit/13e61a726a87c2f) Andreas Reichel) -- Redshift `APPROXIMATE` Aggregate functions ([e4ece](https://github.com/JSQLParser/JSqlParser/commit/e4ece0c3ecd7ce3) Andreas Reichel) -- add `CCJSqlParserUtil.sanitizeSingleSql(String sqlStr)` to help MyBatikPlus users to clean their statements ([1606e](https://github.com/JSQLParser/JSqlParser/commit/1606e5f0492a485) Andreas Reichel) -- return any `UnsupportedStatement` content ([063d2](https://github.com/JSQLParser/JSqlParser/commit/063d2442d82f920) Andreas Reichel) -- re-enable `UnsupportedStatement` ([82b45](https://github.com/JSQLParser/JSqlParser/commit/82b459bfcd23851) Andreas Reichel) -- better statement error recovery ([b3d3a](https://github.com/JSQLParser/JSqlParser/commit/b3d3a8e492f74a8) Andreas Reichel) -- Syntax Sugar for the parser features ([1d943](https://github.com/JSQLParser/JSqlParser/commit/1d9438e7ef1a86f) Andreas Reichel) -- allow `EXTRACT` to be parsed as regular function also ([b85dc](https://github.com/JSQLParser/JSqlParser/commit/b85dc2fd0004652) Andreas Reichel) -- syntax sugar ([a3858](https://github.com/JSQLParser/JSqlParser/commit/a38581acd538d95) Andreas Reichel) -- syntax sugar ([df7c7](https://github.com/JSQLParser/JSqlParser/commit/df7c792184c61a6) Andreas Reichel) -- Syntax sugar ([67bfa](https://github.com/JSQLParser/JSqlParser/commit/67bfae673421d7c) Andreas Reichel) -- syntax sugar ([b0317](https://github.com/JSQLParser/JSqlParser/commit/b03170e180175b1) Andreas Reichel) -- syntax sugar ([57a29](https://github.com/JSQLParser/JSqlParser/commit/57a296b2c8c5bb0) Andreas Reichel) -- remove Aliases of `ParenthesedSelect`, `LateralSubSelect` and `ParenthesedFromItem` from the Table Names ([46682](https://github.com/JSQLParser/JSqlParser/commit/466826b9b115cb7) Andreas Reichel) -- better access to the `DataType` checks ([edeaf](https://github.com/JSQLParser/JSqlParser/commit/edeafc311c2ab7e) Andreas Reichel) -- Add Data Type information to task for making it easy to understand the expected return type ([31c55](https://github.com/JSQLParser/JSqlParser/commit/31c5533f49776c6) Andreas Reichel) -- Implicit Casts `SELECT DOUBLE PRECISION '1'` ([411a3](https://github.com/JSQLParser/JSqlParser/commit/411a3da9facf206) Andreas Reichel) -- Function Column Aliases without an Alias Name `func(x) (a, b, c)` ([b4ef7](https://github.com/JSQLParser/JSqlParser/commit/b4ef763614bf3a4) Andreas Reichel) -- Support BigQuery specific Aggregate clauses ([0179c](https://github.com/JSQLParser/JSqlParser/commit/0179cc0cac9ceeb) Andreas Reichel) -- syntax sugar for Binary Expressions like Conact, Addition, Multiplication ([ffdde](https://github.com/JSQLParser/JSqlParser/commit/ffddeef7199a056) Andreas Reichel) -- Hex to Long conversion ([620db](https://github.com/JSQLParser/JSqlParser/commit/620db709e48c22e) Andreas Reichel) -- syntax sugar for Expressions ([a5693](https://github.com/JSQLParser/JSqlParser/commit/a56934da1d3a7ae) Andreas Reichel) -- Salesforce SOQL `INCLUDES` and `EXCLUDES` operators (#1985) ([f3f0e](https://github.com/JSQLParser/JSqlParser/commit/f3f0e051358a493) lucarota) -- Google BigQuery `CAST` with `FORMAT` clause ([0d813](https://github.com/JSQLParser/JSqlParser/commit/0d813f03faa2b3b) Andreas Reichel) -- DuckDB Lambda Functions ([23679](https://github.com/JSQLParser/JSqlParser/commit/236793aaeabc30f) Andreas Reichel) -- DuckDB `STRUCT` with curly brackets and explicit Column Type Cast ([1cd57](https://github.com/JSQLParser/JSqlParser/commit/1cd576b32c774e8) Andreas Reichel) -- `RECURSIVE` does not need to be a reserved ([5cb4c](https://github.com/JSQLParser/JSqlParser/commit/5cb4c55067f4fe2) Andreas Reichel) -- DuckDB `STRUCT` with curly brackets ([339d6](https://github.com/JSQLParser/JSqlParser/commit/339d6baece2c199) Andreas Reichel) -- BigQuery `STRUCT` data types and literal ([4c187](https://github.com/JSQLParser/JSqlParser/commit/4c187d51055a3d8) Andreas Reichel) -- TablesNamesFinder can return also references to WITH items ([9d645](https://github.com/JSQLParser/JSqlParser/commit/9d64511239a4514) Andreas Reichel) -- allow double-quoted `DateTimeLiteral` like `DATETIME "2005-01-03 12:34:56"` ([f6790](https://github.com/JSQLParser/JSqlParser/commit/f6790913b754850) Andreas Reichel) -- support `DATETIME` literal used for Google BigQuery ([a386d](https://github.com/JSQLParser/JSqlParser/commit/a386d297c418921) Andreas Reichel) -- link `TOP` to AST node ([79c42](https://github.com/JSQLParser/JSqlParser/commit/79c42ed31eb6986) Andreas Reichel) +- provide compatibility methods ([3f995](https://github.com/JSQLParser/JSqlParser/commit/3f99548b99bbfe3) Andreas Reichel) +- apply the new parametrized Visitor patterns to all entities and provide default implementations ([e1692](https://github.com/JSQLParser/JSqlParser/commit/e1692990c543ed1) Andreas Reichel) +- syntax sugar ([2fce4](https://github.com/JSQLParser/JSqlParser/commit/2fce4c009b77d85) Andreas Reichel) +- Visitors return Objects and accept parameters ([5bd28](https://github.com/JSQLParser/JSqlParser/commit/5bd28c8b309df6c) Andreas Reichel) +- Visitors return Objects ([131a9](https://github.com/JSQLParser/JSqlParser/commit/131a988ccea2d91) Andreas Reichel) +- Visitors return Objects ([c2328](https://github.com/JSQLParser/JSqlParser/commit/c2328120e7a79ff) Andreas Reichel) +- Visitors return Objects ([ec497](https://github.com/JSQLParser/JSqlParser/commit/ec49762708e920a) Andreas Reichel) +- Visitors return Objects ([681ca](https://github.com/JSQLParser/JSqlParser/commit/681cac933d83516) Andreas Reichel) +- Allow OUTER keyword as function parameter name (#2021) ([fc90c](https://github.com/JSQLParser/JSqlParser/commit/fc90c0b5e566533) Chris Crabtree) +- BigQuery `SELECT AS STRUCT ...` and `SELECT AS VALUE ...` ([5c360](https://github.com/JSQLParser/JSqlParser/commit/5c360a2fc95c261) Andreas Reichel) +- add syntax sugar ([2ace7](https://github.com/JSQLParser/JSqlParser/commit/2ace74d1047e87d) Andreas Reichel) +- `AllColumns`, DuckDB uses `EXCLUDE` instead of `EXCEPT` ([1ad42](https://github.com/JSQLParser/JSqlParser/commit/1ad4234280f7a70) Andreas Reichel) +- syntax sugar ([ae1ef](https://github.com/JSQLParser/JSqlParser/commit/ae1eff9f7434c08) Andreas Reichel) +- syntax sugar ([81846](https://github.com/JSQLParser/JSqlParser/commit/818464c93ae665a) Andreas Reichel) +- syntax sugar ([2cb3e](https://github.com/JSQLParser/JSqlParser/commit/2cb3e589b60e192) Andreas Reichel) +- syntax sugar ([b2eed](https://github.com/JSQLParser/JSqlParser/commit/b2eed1e910c97de) Andreas Reichel) +- Databricks IGNORE/RESPECT NULLS ([e9c9a](https://github.com/JSQLParser/JSqlParser/commit/e9c9a173a660bbe) Andreas Reichel) +- Databricks IGNORE/RESPECT NULLS ([544b1](https://github.com/JSQLParser/JSqlParser/commit/544b1683789f20b) Andreas Reichel) +- Capture expression name part delimiters (#2001) ([0368b](https://github.com/JSQLParser/JSqlParser/commit/0368b9ebad76742) Chris Crabtree) +- syntax sugar ([ca5c5](https://github.com/JSQLParser/JSqlParser/commit/ca5c553efde37eb) Andreas Reichel) +- translate HEX to Unicode String and ByteArray String ([df519](https://github.com/JSQLParser/JSqlParser/commit/df519333ff34740) Andreas Reichel) +- `StructType` syntax sugar ([6e9bf](https://github.com/JSQLParser/JSqlParser/commit/6e9bf42b0b2d783) Andreas Reichel) +- `Values` implement `FromItem` ([e426c](https://github.com/JSQLParser/JSqlParser/commit/e426c5a67c505b5) Andreas Reichel) +- add `ParenthesedSelect` delegate ([66d05](https://github.com/JSQLParser/JSqlParser/commit/66d05a2bb7c41f3) Andreas Reichel) +- add `ParenthesedSelect` delegate ([f1699](https://github.com/JSQLParser/JSqlParser/commit/f16999393589702) Andreas Reichel) +- Simplify traversing the AST bottom to top ([bddc4](https://github.com/JSQLParser/JSqlParser/commit/bddc41cddf5b5bf) Andreas Reichel) +- AST Node access for `FromItem` ([c1edf](https://github.com/JSQLParser/JSqlParser/commit/c1edf0f8f21bd52) Andreas Reichel) +- RedShift specific Window function IGNORE | RESPECT NULLS ([321c8](https://github.com/JSQLParser/JSqlParser/commit/321c88098a75791) Andreas Reichel) +- RedShift allows `TOP` before `DISTINCT`, see https://docs.aws.amazon.com/redshift/latest/dg/r_SELECT_list.html ([13e61](https://github.com/JSQLParser/JSqlParser/commit/13e61a726a87c2f) Andreas Reichel) +- Redshift `APPROXIMATE` Aggregate functions ([e4ece](https://github.com/JSQLParser/JSqlParser/commit/e4ece0c3ecd7ce3) Andreas Reichel) +- add `CCJSqlParserUtil.sanitizeSingleSql(String sqlStr)` to help MyBatikPlus users to clean their statements ([1606e](https://github.com/JSQLParser/JSqlParser/commit/1606e5f0492a485) Andreas Reichel) +- return any `UnsupportedStatement` content ([063d2](https://github.com/JSQLParser/JSqlParser/commit/063d2442d82f920) Andreas Reichel) +- re-enable `UnsupportedStatement` ([82b45](https://github.com/JSQLParser/JSqlParser/commit/82b459bfcd23851) Andreas Reichel) +- better statement error recovery ([b3d3a](https://github.com/JSQLParser/JSqlParser/commit/b3d3a8e492f74a8) Andreas Reichel) +- Syntax Sugar for the parser features ([1d943](https://github.com/JSQLParser/JSqlParser/commit/1d9438e7ef1a86f) Andreas Reichel) +- allow `EXTRACT` to be parsed as regular function also ([b85dc](https://github.com/JSQLParser/JSqlParser/commit/b85dc2fd0004652) Andreas Reichel) +- syntax sugar ([a3858](https://github.com/JSQLParser/JSqlParser/commit/a38581acd538d95) Andreas Reichel) +- syntax sugar ([df7c7](https://github.com/JSQLParser/JSqlParser/commit/df7c792184c61a6) Andreas Reichel) +- Syntax sugar ([67bfa](https://github.com/JSQLParser/JSqlParser/commit/67bfae673421d7c) Andreas Reichel) +- syntax sugar ([b0317](https://github.com/JSQLParser/JSqlParser/commit/b03170e180175b1) Andreas Reichel) +- syntax sugar ([57a29](https://github.com/JSQLParser/JSqlParser/commit/57a296b2c8c5bb0) Andreas Reichel) +- remove Aliases of `ParenthesedSelect`, `LateralSubSelect` and `ParenthesedFromItem` from the Table Names ([46682](https://github.com/JSQLParser/JSqlParser/commit/466826b9b115cb7) Andreas Reichel) +- better access to the `DataType` checks ([edeaf](https://github.com/JSQLParser/JSqlParser/commit/edeafc311c2ab7e) Andreas Reichel) +- Add Data Type information to task for making it easy to understand the expected return type ([31c55](https://github.com/JSQLParser/JSqlParser/commit/31c5533f49776c6) Andreas Reichel) +- Implicit Casts `SELECT DOUBLE PRECISION '1'` ([411a3](https://github.com/JSQLParser/JSqlParser/commit/411a3da9facf206) Andreas Reichel) +- Function Column Aliases without an Alias Name `func(x) (a, b, c)` ([b4ef7](https://github.com/JSQLParser/JSqlParser/commit/b4ef763614bf3a4) Andreas Reichel) +- Support BigQuery specific Aggregate clauses ([0179c](https://github.com/JSQLParser/JSqlParser/commit/0179cc0cac9ceeb) Andreas Reichel) +- syntax sugar for Binary Expressions like Conact, Addition, Multiplication ([ffdde](https://github.com/JSQLParser/JSqlParser/commit/ffddeef7199a056) Andreas Reichel) +- Hex to Long conversion ([620db](https://github.com/JSQLParser/JSqlParser/commit/620db709e48c22e) Andreas Reichel) +- syntax sugar for Expressions ([a5693](https://github.com/JSQLParser/JSqlParser/commit/a56934da1d3a7ae) Andreas Reichel) +- Salesforce SOQL `INCLUDES` and `EXCLUDES` operators (#1985) ([f3f0e](https://github.com/JSQLParser/JSqlParser/commit/f3f0e051358a493) lucarota) +- Google BigQuery `CAST` with `FORMAT` clause ([0d813](https://github.com/JSQLParser/JSqlParser/commit/0d813f03faa2b3b) Andreas Reichel) +- DuckDB Lambda Functions ([23679](https://github.com/JSQLParser/JSqlParser/commit/236793aaeabc30f) Andreas Reichel) +- DuckDB `STRUCT` with curly brackets and explicit Column Type Cast ([1cd57](https://github.com/JSQLParser/JSqlParser/commit/1cd576b32c774e8) Andreas Reichel) +- `RECURSIVE` does not need to be a reserved ([5cb4c](https://github.com/JSQLParser/JSqlParser/commit/5cb4c55067f4fe2) Andreas Reichel) +- DuckDB `STRUCT` with curly brackets ([339d6](https://github.com/JSQLParser/JSqlParser/commit/339d6baece2c199) Andreas Reichel) +- BigQuery `STRUCT` data types and literal ([4c187](https://github.com/JSQLParser/JSqlParser/commit/4c187d51055a3d8) Andreas Reichel) +- TablesNamesFinder can return also references to WITH items ([9d645](https://github.com/JSQLParser/JSqlParser/commit/9d64511239a4514) Andreas Reichel) +- allow double-quoted `DateTimeLiteral` like `DATETIME "2005-01-03 12:34:56"` ([f6790](https://github.com/JSQLParser/JSqlParser/commit/f6790913b754850) Andreas Reichel) +- support `DATETIME` literal used for Google BigQuery ([a386d](https://github.com/JSQLParser/JSqlParser/commit/a386d297c418921) Andreas Reichel) +- link `TOP` to AST node ([79c42](https://github.com/JSQLParser/JSqlParser/commit/79c42ed31eb6986) Andreas Reichel) ### Bug Fixes -- `AllTableColumns`, DuckDB specific `EXCLUDE` ([c9ecf](https://github.com/JSQLParser/JSqlParser/commit/c9ecfc6ddbdd139) Andreas Reichel) -- `AllColumns` Replacement shall be about Columns only ([f4b40](https://github.com/JSQLParser/JSqlParser/commit/f4b40e43a4f8d3d) Andreas Reichel) -- `FromItem` with Alias without `AS` keyword ([5f580](https://github.com/JSQLParser/JSqlParser/commit/5f580af190c6fbb) Andreas Reichel) -- set `stringValue` in `DoubleValue.setValue` (#2009) ([e07f8](https://github.com/JSQLParser/JSqlParser/commit/e07f8d019ddf38d) Damian) -- try working around `UnsupportedStatement` issue ([fbe97](https://github.com/JSQLParser/JSqlParser/commit/fbe97a8deb84ae9) Andreas Reichel) -- allow `BASE64` keyword ([7daf7](https://github.com/JSQLParser/JSqlParser/commit/7daf7af36d825f7) Andreas Reichel) -- `StructType` expressions must use Visitor instead of `toString()` ([b95d8](https://github.com/JSQLParser/JSqlParser/commit/b95d8e3e4ee01b0) Andreas Reichel) -- `AnyComparisionItem` with extra brackets ([4e1a1](https://github.com/JSQLParser/JSqlParser/commit/4e1a1535f4ef706) Andreas Reichel) -- `FOR UPDATE` clause should come after the select body ([cf7fe](https://github.com/JSQLParser/JSqlParser/commit/cf7fe157de372f3) Andreas Reichel) -- initialise the `SelectDeparser` with an `ExpressionDeparser` (but not with an empty Adaptor only) ([f417c](https://github.com/JSQLParser/JSqlParser/commit/f417c8f248c7bb1) Andreas Reichel) -- `ALTER ...` shall `captureRest()` only to the next statement terminator ([15d14](https://github.com/JSQLParser/JSqlParser/commit/15d14ab0b9dcadf) Andreas Reichel) -- correct the wrong Assertion ([8461e](https://github.com/JSQLParser/JSqlParser/commit/8461e8ad1a3f5e3) Andreas Reichel) -- don't insert space after certain punctuation ([159c2](https://github.com/JSQLParser/JSqlParser/commit/159c28ee8f68cab) Andreas Reichel) -- treat Array Brackets `[..]` as syntax characters and surround by space when normalizing for comparison ([c9d1e](https://github.com/JSQLParser/JSqlParser/commit/c9d1eaefca91c6e) Andreas Reichel) -- `REGEXP` does not need to be reserved ([f6524](https://github.com/JSQLParser/JSqlParser/commit/f65240f381f9855) Andreas Reichel) -- `REGEXP` does not need to be reserved ([a9e67](https://github.com/JSQLParser/JSqlParser/commit/a9e67667b9c1590) Andreas Reichel) -- Array Arguments without `ARRAY` keyword ([0f9a8](https://github.com/JSQLParser/JSqlParser/commit/0f9a8ec02786f5d) Andreas Reichel) -- Function with Array Arguments ([f782e](https://github.com/JSQLParser/JSqlParser/commit/f782eda7afa17d3) Andreas Reichel) -- parsing `SelectItem` shall support `Xor` ([c8839](https://github.com/JSQLParser/JSqlParser/commit/c883920a1175ffc) Andreas Reichel) +- `AllTableColumns`, DuckDB specific `EXCLUDE` ([c9ecf](https://github.com/JSQLParser/JSqlParser/commit/c9ecfc6ddbdd139) Andreas Reichel) +- `AllColumns` Replacement shall be about Columns only ([f4b40](https://github.com/JSQLParser/JSqlParser/commit/f4b40e43a4f8d3d) Andreas Reichel) +- `FromItem` with Alias without `AS` keyword ([5f580](https://github.com/JSQLParser/JSqlParser/commit/5f580af190c6fbb) Andreas Reichel) +- set `stringValue` in `DoubleValue.setValue` (#2009) ([e07f8](https://github.com/JSQLParser/JSqlParser/commit/e07f8d019ddf38d) Damian) +- try working around `UnsupportedStatement` issue ([fbe97](https://github.com/JSQLParser/JSqlParser/commit/fbe97a8deb84ae9) Andreas Reichel) +- allow `BASE64` keyword ([7daf7](https://github.com/JSQLParser/JSqlParser/commit/7daf7af36d825f7) Andreas Reichel) +- `StructType` expressions must use Visitor instead of `toString()` ([b95d8](https://github.com/JSQLParser/JSqlParser/commit/b95d8e3e4ee01b0) Andreas Reichel) +- `AnyComparisionItem` with extra brackets ([4e1a1](https://github.com/JSQLParser/JSqlParser/commit/4e1a1535f4ef706) Andreas Reichel) +- `FOR UPDATE` clause should come after the select body ([cf7fe](https://github.com/JSQLParser/JSqlParser/commit/cf7fe157de372f3) Andreas Reichel) +- initialise the `SelectDeparser` with an `ExpressionDeparser` (but not with an empty Adaptor only) ([f417c](https://github.com/JSQLParser/JSqlParser/commit/f417c8f248c7bb1) Andreas Reichel) +- `ALTER ...` shall `captureRest()` only to the next statement terminator ([15d14](https://github.com/JSQLParser/JSqlParser/commit/15d14ab0b9dcadf) Andreas Reichel) +- correct the wrong Assertion ([8461e](https://github.com/JSQLParser/JSqlParser/commit/8461e8ad1a3f5e3) Andreas Reichel) +- don't insert space after certain punctuation ([159c2](https://github.com/JSQLParser/JSqlParser/commit/159c28ee8f68cab) Andreas Reichel) +- treat Array Brackets `[..]` as syntax characters and surround by space when normalizing for comparison ([c9d1e](https://github.com/JSQLParser/JSqlParser/commit/c9d1eaefca91c6e) Andreas Reichel) +- `REGEXP` does not need to be reserved ([f6524](https://github.com/JSQLParser/JSqlParser/commit/f65240f381f9855) Andreas Reichel) +- `REGEXP` does not need to be reserved ([a9e67](https://github.com/JSQLParser/JSqlParser/commit/a9e67667b9c1590) Andreas Reichel) +- Array Arguments without `ARRAY` keyword ([0f9a8](https://github.com/JSQLParser/JSqlParser/commit/0f9a8ec02786f5d) Andreas Reichel) +- Function with Array Arguments ([f782e](https://github.com/JSQLParser/JSqlParser/commit/f782eda7afa17d3) Andreas Reichel) +- parsing `SelectItem` shall support `Xor` ([c8839](https://github.com/JSQLParser/JSqlParser/commit/c883920a1175ffc) Andreas Reichel) ### Other changes @@ -686,18 +1558,18 @@ Changelog of JSqlParser. **Add missing java.sql require (#1999)** -* Add missing java.sql -* Update maven checkstyle -* Fix gradle checkstyle -* Bump surefire plugin -* Skip modules in tests +* Add missing java.sql +* Update maven checkstyle +* Fix gradle checkstyle +* Bump surefire plugin +* Skip modules in tests [df48c](https://github.com/JSQLParser/JSqlParser/commit/df48c4ba5b2b44f) Ethan McCue *2024-04-30 05:13:54* **Add module info (#1998)** -* Add module info -* Trailing newline +* Add module info +* Trailing newline [761b4](https://github.com/JSQLParser/JSqlParser/commit/761b45b2f6c4b81) Ethan McCue *2024-04-30 04:36:41* @@ -711,24 +1583,24 @@ Changelog of JSqlParser. ### Features -- add DB2 special register `CURRENT TIMEZONE` ([c412d](https://github.com/JSQLParser/JSqlParser/commit/c412d6a52f9b2ea) Andreas Reichel) -- add additional CREATE VIEW modifiers (#1964) ([67e22](https://github.com/JSQLParser/JSqlParser/commit/67e220425f24148) David Goss) -- with no log (#1953) ([d9c44](https://github.com/JSQLParser/JSqlParser/commit/d9c44499d096b1f) mjh) -- support keyword "only" for postgresql (#1952) ([f1676](https://github.com/JSQLParser/JSqlParser/commit/f1676dd992911d9) 猫屎咖啡) -- support any number/order of merge operations (#1938) ([f1c52](https://github.com/JSQLParser/JSqlParser/commit/f1c525a1eaf3087) David Goss) +- add DB2 special register `CURRENT TIMEZONE` ([c412d](https://github.com/JSQLParser/JSqlParser/commit/c412d6a52f9b2ea) Andreas Reichel) +- add additional CREATE VIEW modifiers (#1964) ([67e22](https://github.com/JSQLParser/JSqlParser/commit/67e220425f24148) David Goss) +- with no log (#1953) ([d9c44](https://github.com/JSQLParser/JSqlParser/commit/d9c44499d096b1f) mjh) +- support keyword "only" for postgresql (#1952) ([f1676](https://github.com/JSQLParser/JSqlParser/commit/f1676dd992911d9) 猫屎咖啡) +- support any number/order of merge operations (#1938) ([f1c52](https://github.com/JSQLParser/JSqlParser/commit/f1c525a1eaf3087) David Goss) ### Bug Fixes -- chained function calls of `SimpleFunction` ([98055](https://github.com/JSQLParser/JSqlParser/commit/9805581accf89d2) Andreas Reichel) -- issue #1948 `Between` with expression ([b9453](https://github.com/JSQLParser/JSqlParser/commit/b9453f228adf9ad) Andreas Reichel) -- return NULL when parsing empty Strings ([94fb8](https://github.com/JSQLParser/JSqlParser/commit/94fb87237f36cce) Andreas Reichel) -- allow Parameters like `$1`,`$2` ([17f5f](https://github.com/JSQLParser/JSqlParser/commit/17f5f2ad680dfdb) Andreas Reichel) -- allow `DATA` as `ColumnType()` keyword ([72a51](https://github.com/JSQLParser/JSqlParser/commit/72a51e58413a291) Andreas Reichel) -- make analytic expression visitor null-safe (#1944) ([768c6](https://github.com/JSQLParser/JSqlParser/commit/768c63f4660509b) David Goss) -- Fixes parsing failing for ALTER MODIFY queries not containing datatype (#1961) ([029fd](https://github.com/JSQLParser/JSqlParser/commit/029fd42e84e65ee) Tanish Grover) -- tables not find in parentheses join sql. (#1956) ([182f4](https://github.com/JSQLParser/JSqlParser/commit/182f484dc43945b) hancher) -- issue1875 (#1957) ([98aa9](https://github.com/JSQLParser/JSqlParser/commit/98aa90cb988580a) mjh) -- ExpressionVisitor.visit(AllTableColumns) method isn't being called. (#1942) ([bc166](https://github.com/JSQLParser/JSqlParser/commit/bc16618eaa8fd93) Brian S. O'Neill) +- chained function calls of `SimpleFunction` ([98055](https://github.com/JSQLParser/JSqlParser/commit/9805581accf89d2) Andreas Reichel) +- issue #1948 `Between` with expression ([b9453](https://github.com/JSQLParser/JSqlParser/commit/b9453f228adf9ad) Andreas Reichel) +- return NULL when parsing empty Strings ([94fb8](https://github.com/JSQLParser/JSqlParser/commit/94fb87237f36cce) Andreas Reichel) +- allow Parameters like `$1`,`$2` ([17f5f](https://github.com/JSQLParser/JSqlParser/commit/17f5f2ad680dfdb) Andreas Reichel) +- allow `DATA` as `ColumnType()` keyword ([72a51](https://github.com/JSQLParser/JSqlParser/commit/72a51e58413a291) Andreas Reichel) +- make analytic expression visitor null-safe (#1944) ([768c6](https://github.com/JSQLParser/JSqlParser/commit/768c63f4660509b) David Goss) +- Fixes parsing failing for ALTER MODIFY queries not containing datatype (#1961) ([029fd](https://github.com/JSQLParser/JSqlParser/commit/029fd42e84e65ee) Tanish Grover) +- tables not find in parentheses join sql. (#1956) ([182f4](https://github.com/JSQLParser/JSqlParser/commit/182f484dc43945b) hancher) +- issue1875 (#1957) ([98aa9](https://github.com/JSQLParser/JSqlParser/commit/98aa90cb988580a) mjh) +- ExpressionVisitor.visit(AllTableColumns) method isn't being called. (#1942) ([bc166](https://github.com/JSQLParser/JSqlParser/commit/bc16618eaa8fd93) Brian S. O'Neill) ### Other changes @@ -744,33 +1616,33 @@ Changelog of JSqlParser. **Update README.md** -* Fixes #1968 +* Fixes #1968 [8dcfb](https://github.com/JSQLParser/JSqlParser/commit/8dcfb4a3bf5682d) manticore-projects *2024-02-17 12:03:43* **Guard Values against null/empty values (#1965)** -* Guard Values against null/empty values -* The classes modified by this commit are `DoubleValue`, `LongValue`, and -* `TimeValue`. Both `null` and empty strings provided to their -* constructors fail, but they provide very different error messages -* (NullPointerException and StringIndexOutOfBoundsException), which is -* neither sensible nor helpful in debugging. -* This commit adds a guard to throw `IllegalArgumentException` for both -* cases in order to improve coherency and usefulness of the error -* messages. -* fix checkstyle issues +* Guard Values against null/empty values +* The classes modified by this commit are `DoubleValue`, `LongValue`, and +* `TimeValue`. Both `null` and empty strings provided to their +* constructors fail, but they provide very different error messages +* (NullPointerException and StringIndexOutOfBoundsException), which is +* neither sensible nor helpful in debugging. +* This commit adds a guard to throw `IllegalArgumentException` for both +* cases in order to improve coherency and usefulness of the error +* messages. +* fix checkstyle issues [b0032](https://github.com/JSQLParser/JSqlParser/commit/b00322efa0c77d2) Heewon Lee *2024-02-14 07:34:40* **support oracle alter table truncate partition (#1954)** -* feat: oracle alter table truncate partition -* feat: oracle alter table truncate partition -* feat: code format -* feat: code format -* --------- -* Co-authored-by: mjh <majh118@chinaunicom.cn> +* feat: oracle alter table truncate partition +* feat: oracle alter table truncate partition +* feat: code format +* feat: code format +* --------- +* Co-authored-by: mjh <majh118@chinaunicom.cn> [cc7aa](https://github.com/JSQLParser/JSqlParser/commit/cc7aa01913a7201) mjh *2024-02-04 07:19:19* @@ -804,35 +1676,35 @@ Changelog of JSqlParser. ### Features -- support mysql with rollup (#1923) ([77f6f](https://github.com/JSQLParser/JSqlParser/commit/77f6fb8c92b3378) jxnu-liguobin) -- Support `FOR SHARE` (#1922) ([815f8](https://github.com/JSQLParser/JSqlParser/commit/815f8753d552d89) jxnu-liguobin) -- [MySQL] Support `TABLE STATEMENT` (#1921) ([313a4](https://github.com/JSQLParser/JSqlParser/commit/313a4b42444b2d2) jxnu-liguobin) -- Support `RENAME INDEX` for MySQL, `RENAME CONSTRAINT` for PostgreSQL (#1920) ([989a8](https://github.com/JSQLParser/JSqlParser/commit/989a84bb215283b) jxnu-liguobin) -- Add support comment in `create view` for MySQL and MariaDb (#1913) ([4d47e](https://github.com/JSQLParser/JSqlParser/commit/4d47e0ab7bc2872) jxnu-liguobin) -- Add support for `REFRESH MATERIALIZED VIEW` (#1911) ([425c7](https://github.com/JSQLParser/JSqlParser/commit/425c72eb7d7f931) jxnu-liguobin) -- `SimpleFunction` for faster parsing of simple, but deep nested functions ([085d7](https://github.com/JSQLParser/JSqlParser/commit/085d7504235e58c) Andreas Reichel) -- add support for snowflake merge statements (#1887) ([36b80](https://github.com/JSQLParser/JSqlParser/commit/36b806dede06260) David Goss) -- `ColDataType` supports `PUBLIC` schema and all non-restricted keywords for type ([1088d](https://github.com/JSQLParser/JSqlParser/commit/1088db7aea0b2f9) Andreas Reichel) -- T-SQL Join Hints ([5f09e](https://github.com/JSQLParser/JSqlParser/commit/5f09ec4914fbdd1) Andreas Reichel) -- old TSQL Joins `*=` and `=*` ([0b50d](https://github.com/JSQLParser/JSqlParser/commit/0b50da4cca555b6) Andreas Reichel) -- MS SQL Server `Merge` `Output` clause ([7bd42](https://github.com/JSQLParser/JSqlParser/commit/7bd42edaa0d9aed) Andreas Reichel) -- MS SQL Server `UPDATE ...` Index Hint ([f919e](https://github.com/JSQLParser/JSqlParser/commit/f919e00c30ff5df) Andreas Reichel) -- Postgres `Contains` and `ContainedBy` Operators ([28a4c](https://github.com/JSQLParser/JSqlParser/commit/28a4c080b718aba) Andreas Reichel) -- Postgres `Contains` and `ContainedBy` Operators ([09d6d](https://github.com/JSQLParser/JSqlParser/commit/09d6dfe7bc7acb8) Andreas Reichel) -- Clickhouse `GLOBAL IN ...` ([ced0d](https://github.com/JSQLParser/JSqlParser/commit/ced0d0090c5c9a9) Andreas Reichel) -- `CREATE INDEX IF NOT EXISTS...` ([da13d](https://github.com/JSQLParser/JSqlParser/commit/da13d7dc1dd1608) Andreas Reichel) -- support clickhouse global keyword in IN Expression ([a9ed7](https://github.com/JSQLParser/JSqlParser/commit/a9ed79825110df7) hezw) +- support mysql with rollup (#1923) ([77f6f](https://github.com/JSQLParser/JSqlParser/commit/77f6fb8c92b3378) jxnu-liguobin) +- Support `FOR SHARE` (#1922) ([815f8](https://github.com/JSQLParser/JSqlParser/commit/815f8753d552d89) jxnu-liguobin) +- [MySQL] Support `TABLE STATEMENT` (#1921) ([313a4](https://github.com/JSQLParser/JSqlParser/commit/313a4b42444b2d2) jxnu-liguobin) +- Support `RENAME INDEX` for MySQL, `RENAME CONSTRAINT` for PostgreSQL (#1920) ([989a8](https://github.com/JSQLParser/JSqlParser/commit/989a84bb215283b) jxnu-liguobin) +- Add support comment in `create view` for MySQL and MariaDb (#1913) ([4d47e](https://github.com/JSQLParser/JSqlParser/commit/4d47e0ab7bc2872) jxnu-liguobin) +- Add support for `REFRESH MATERIALIZED VIEW` (#1911) ([425c7](https://github.com/JSQLParser/JSqlParser/commit/425c72eb7d7f931) jxnu-liguobin) +- `SimpleFunction` for faster parsing of simple, but deep nested functions ([085d7](https://github.com/JSQLParser/JSqlParser/commit/085d7504235e58c) Andreas Reichel) +- add support for snowflake merge statements (#1887) ([36b80](https://github.com/JSQLParser/JSqlParser/commit/36b806dede06260) David Goss) +- `ColDataType` supports `PUBLIC` schema and all non-restricted keywords for type ([1088d](https://github.com/JSQLParser/JSqlParser/commit/1088db7aea0b2f9) Andreas Reichel) +- T-SQL Join Hints ([5f09e](https://github.com/JSQLParser/JSqlParser/commit/5f09ec4914fbdd1) Andreas Reichel) +- old TSQL Joins `*=` and `=*` ([0b50d](https://github.com/JSQLParser/JSqlParser/commit/0b50da4cca555b6) Andreas Reichel) +- MS SQL Server `Merge` `Output` clause ([7bd42](https://github.com/JSQLParser/JSqlParser/commit/7bd42edaa0d9aed) Andreas Reichel) +- MS SQL Server `UPDATE ...` Index Hint ([f919e](https://github.com/JSQLParser/JSqlParser/commit/f919e00c30ff5df) Andreas Reichel) +- Postgres `Contains` and `ContainedBy` Operators ([28a4c](https://github.com/JSQLParser/JSqlParser/commit/28a4c080b718aba) Andreas Reichel) +- Postgres `Contains` and `ContainedBy` Operators ([09d6d](https://github.com/JSQLParser/JSqlParser/commit/09d6dfe7bc7acb8) Andreas Reichel) +- Clickhouse `GLOBAL IN ...` ([ced0d](https://github.com/JSQLParser/JSqlParser/commit/ced0d0090c5c9a9) Andreas Reichel) +- `CREATE INDEX IF NOT EXISTS...` ([da13d](https://github.com/JSQLParser/JSqlParser/commit/da13d7dc1dd1608) Andreas Reichel) +- support clickhouse global keyword in IN Expression ([a9ed7](https://github.com/JSQLParser/JSqlParser/commit/a9ed79825110df7) hezw) ### Bug Fixes -- refactor `JsonExpression`, avoiding expensive semantic lookahead and improving performance ([56515](https://github.com/JSQLParser/JSqlParser/commit/56515aba6ca893f) Andreas Reichel) -- `GO` shall terminate statement only, when appearing alone on an empty line ([14637](https://github.com/JSQLParser/JSqlParser/commit/14637ce64763b42) Andreas Reichel) -- De-Parse Oracle Hints in UPDATE, INSERT, DELETE and MERGE ([aaca0](https://github.com/JSQLParser/JSqlParser/commit/aaca05855f9a11b) Andreas Reichel) -- `UpdateSet` shall not have brackets with single element only ([15b9a](https://github.com/JSQLParser/JSqlParser/commit/15b9aef7ca05416) Andreas Reichel) -- make `GLOBAL` a restricted keyword, not usable as an Alias ([dd6cf](https://github.com/JSQLParser/JSqlParser/commit/dd6cf23150f4804) Andreas Reichel) -- Postgres `NextVal()` function ([e3afa](https://github.com/JSQLParser/JSqlParser/commit/e3afa5fbdebc715) Andreas Reichel) -- optional `Expression` in `FETCH` clause ([daee3](https://github.com/JSQLParser/JSqlParser/commit/daee30f7ae88bea) Andreas Reichel) -- allow `RAW` as `CreateParameter` ([ecd40](https://github.com/JSQLParser/JSqlParser/commit/ecd40386585a519) Andreas Reichel) +- refactor `JsonExpression`, avoiding expensive semantic lookahead and improving performance ([56515](https://github.com/JSQLParser/JSqlParser/commit/56515aba6ca893f) Andreas Reichel) +- `GO` shall terminate statement only, when appearing alone on an empty line ([14637](https://github.com/JSQLParser/JSqlParser/commit/14637ce64763b42) Andreas Reichel) +- De-Parse Oracle Hints in UPDATE, INSERT, DELETE and MERGE ([aaca0](https://github.com/JSQLParser/JSqlParser/commit/aaca05855f9a11b) Andreas Reichel) +- `UpdateSet` shall not have brackets with single element only ([15b9a](https://github.com/JSQLParser/JSqlParser/commit/15b9aef7ca05416) Andreas Reichel) +- make `GLOBAL` a restricted keyword, not usable as an Alias ([dd6cf](https://github.com/JSQLParser/JSqlParser/commit/dd6cf23150f4804) Andreas Reichel) +- Postgres `NextVal()` function ([e3afa](https://github.com/JSQLParser/JSqlParser/commit/e3afa5fbdebc715) Andreas Reichel) +- optional `Expression` in `FETCH` clause ([daee3](https://github.com/JSQLParser/JSqlParser/commit/daee30f7ae88bea) Andreas Reichel) +- allow `RAW` as `CreateParameter` ([ecd40](https://github.com/JSQLParser/JSqlParser/commit/ecd40386585a519) Andreas Reichel) ### Other changes @@ -928,14 +1800,14 @@ Changelog of JSqlParser. **Fix typo in migration.rst (#1888)** -* Found a typo in the 4.7 migration document. Trivial PR. Please merge. +* Found a typo in the 4.7 migration document. Trivial PR. Please merge. [902e4](https://github.com/JSQLParser/JSqlParser/commit/902e4c46f783985) Ed Sabol *2023-11-10 03:05:39* **Unit tests support multi-os and higher versions of jdk (#1886)** -* fix: tokenBlockPattern support \r\n or \r -* test: remove nashorn ignore annotation to support jdk11+ +* fix: tokenBlockPattern support \r\n or \r +* test: remove nashorn ignore annotation to support jdk11+ [97e92](https://github.com/JSQLParser/JSqlParser/commit/97e9229d15df7d6) human-user *2023-11-08 03:07:04* @@ -969,65 +1841,65 @@ Changelog of JSqlParser. ### Breaking changes -- add support for INTERPRET function parsing (#1816) ([180ec](https://github.com/JSQLParser/JSqlParser/commit/180ec68cc9fa7eb) Matteo Sist) -- Remove `ItemsList`, `MultiExpressionList`, `Replace` ([14170](https://github.com/JSQLParser/JSqlParser/commit/141708eabc4f2ea) Andreas Reichel) -- Consolidate the `ExpressionList`, removing many redundant List alike Classes and Productions ([288b1](https://github.com/JSQLParser/JSqlParser/commit/288b177fe9c8a4c) Andreas Reichel) -- remove `SelectExpressionItem` in favor of `SelectItem` ([b9057](https://github.com/JSQLParser/JSqlParser/commit/b9057d2b75cd1d7) Andreas Reichel) -- ClickHouse `Select...` ``FINAL` modifier ([4b7f2](https://github.com/JSQLParser/JSqlParser/commit/4b7f21c54c24d04) Andreas Reichel) +- add support for INTERPRET function parsing (#1816) ([180ec](https://github.com/JSQLParser/JSqlParser/commit/180ec68cc9fa7eb) Matteo Sist) +- Remove `ItemsList`, `MultiExpressionList`, `Replace` ([14170](https://github.com/JSQLParser/JSqlParser/commit/141708eabc4f2ea) Andreas Reichel) +- Consolidate the `ExpressionList`, removing many redundant List alike Classes and Productions ([288b1](https://github.com/JSQLParser/JSqlParser/commit/288b177fe9c8a4c) Andreas Reichel) +- remove `SelectExpressionItem` in favor of `SelectItem` ([b9057](https://github.com/JSQLParser/JSqlParser/commit/b9057d2b75cd1d7) Andreas Reichel) +- ClickHouse `Select...` ``FINAL` modifier ([4b7f2](https://github.com/JSQLParser/JSqlParser/commit/4b7f21c54c24d04) Andreas Reichel) ### Features -- H2 BYTEA Values `X'01' '02'` ([54828](https://github.com/JSQLParser/JSqlParser/commit/54828a456a7f192) Andreas Reichel) -- BigQuery Except(..) Replace(..) syntax ([4b4ae](https://github.com/JSQLParser/JSqlParser/commit/4b4ae04f44ff18b) Andreas Reichel) -- implement a few missing expressions ([04128](https://github.com/JSQLParser/JSqlParser/commit/0412897f9ea809f) Andreas Reichel) -- SQL:2016 TABLESAMPLE clause ([4d8a5](https://github.com/JSQLParser/JSqlParser/commit/4d8a512191a4a1b) Andreas Reichel) -- add a method checking balanced brackets ([52df3](https://github.com/JSQLParser/JSqlParser/commit/52df32dd8ec2c10) Andreas Reichel) -- add support for INTERPRET function parsing (#1816) ([180ec](https://github.com/JSQLParser/JSqlParser/commit/180ec68cc9fa7eb) Matteo Sist) -- MySQL `NOT RLIKE`, `NOT REGEXP` expressions ([f1325](https://github.com/JSQLParser/JSqlParser/commit/f132547f56a1edd) Andreas Reichel) -- Postgres `NOTNULL` support ([386dc](https://github.com/JSQLParser/JSqlParser/commit/386dc7a0df98f1c) manticore-projects) -- `QUALIFY` clause ([75e4d](https://github.com/JSQLParser/JSqlParser/commit/75e4d30747a7e6e) Andreas Reichel) -- T-SQL `FOR ...` clause ([8027d](https://github.com/JSQLParser/JSqlParser/commit/8027dbf2cbf9163) Andreas Reichel) -- Quoted Identifiers can contain double-quotes (PostgreSQL) ([73c55](https://github.com/JSQLParser/JSqlParser/commit/73c55fda1ac6a42) Andreas Reichel) -- functions blocks, parenthesed JSON Expressions ([5263b](https://github.com/JSQLParser/JSqlParser/commit/5263b91f3e555b7) Andreas Reichel) -- functions blocks, parenthesed JSON Expressions ([e19dc](https://github.com/JSQLParser/JSqlParser/commit/e19dc0e081f741d) Andreas Reichel) -- parse CREATE TRIGGER as UnsupportedStatement ([64b03](https://github.com/JSQLParser/JSqlParser/commit/64b0331f772278b) Andreas Reichel) -- chaining JSON Expressions ([6ef5e](https://github.com/JSQLParser/JSqlParser/commit/6ef5e0b6ee06211) Andreas Reichel) -- Write API documentation to the WebSite via XMLDoclet ([c5366](https://github.com/JSQLParser/JSqlParser/commit/c53667f8eff30e3) Andreas Reichel) -- `MEMBER OF` condition as shown at https://dev.mysql.com/doc/refman/8.0/en/json-search-functions.html#operator_member-of ([6e7a7](https://github.com/JSQLParser/JSqlParser/commit/6e7a78dfc563749) Andreas Reichel) -- access Elements of Array Columns ([09a70](https://github.com/JSQLParser/JSqlParser/commit/09a70a499121792) Andreas Reichel) -- JdbcNamedParameter allows "&" (instead of ":") ([c07a4](https://github.com/JSQLParser/JSqlParser/commit/c07a43b3c128a5d) Andreas Reichel) -- Consolidate the `ExpressionList`, removing many redundant List alike Classes and Productions ([288b1](https://github.com/JSQLParser/JSqlParser/commit/288b177fe9c8a4c) Andreas Reichel) -- ClickHouse `LIMIT ... BY ...` clause ([4d5e2](https://github.com/JSQLParser/JSqlParser/commit/4d5e26d3febe686) Andreas Reichel) -- implement SQL:2016 Convert() and Trim() ([3a27a](https://github.com/JSQLParser/JSqlParser/commit/3a27a9dd4add700) Andreas Reichel) -- Switch off contradicting `JOIN` qualifiers, when setting a qualifier ([b6ea8](https://github.com/JSQLParser/JSqlParser/commit/b6ea8b162450545) Andreas Reichel) -- Test if a JOIN is an INNER JOIN according to the SQL:2016 ([6281b](https://github.com/JSQLParser/JSqlParser/commit/6281b07a543b088) Andreas Reichel) -- ClickHouse `Select...` ``FINAL` modifier ([4b7f2](https://github.com/JSQLParser/JSqlParser/commit/4b7f21c54c24d04) Andreas Reichel) -- Multi-Part Names for Variables and Parameters ([9da7a](https://github.com/JSQLParser/JSqlParser/commit/9da7a06ebe9b036) Andreas Reichel) -- Oracle `HAVING` before `GROUP BY` ([4efb9](https://github.com/JSQLParser/JSqlParser/commit/4efb99f1510ad16) Andreas Reichel) -- Lateral View ([8a1bd](https://github.com/JSQLParser/JSqlParser/commit/8a1bdeccbadb04f) Andreas Reichel) -- FETCH uses EXPRESSION ([0979b](https://github.com/JSQLParser/JSqlParser/commit/0979b2e5ea76b8c) Andreas Reichel) -- Support more Statement Separators ([b0814](https://github.com/JSQLParser/JSqlParser/commit/b08148414bd8f30) Andreas Reichel) -- CREATE VIEW ... REFRESH AUTO... ([1c8d8](https://github.com/JSQLParser/JSqlParser/commit/1c8d8daf48ebac1) Andreas Reichel) -- Oracle Alternative Quoting ([c57c4](https://github.com/JSQLParser/JSqlParser/commit/c57c427032c91d0) Andreas Reichel) -- make important Classes Serializable ([b94b2](https://github.com/JSQLParser/JSqlParser/commit/b94b2cc6a8f8c7d) Andreas Reichel) +- H2 BYTEA Values `X'01' '02'` ([54828](https://github.com/JSQLParser/JSqlParser/commit/54828a456a7f192) Andreas Reichel) +- BigQuery Except(..) Replace(..) syntax ([4b4ae](https://github.com/JSQLParser/JSqlParser/commit/4b4ae04f44ff18b) Andreas Reichel) +- implement a few missing expressions ([04128](https://github.com/JSQLParser/JSqlParser/commit/0412897f9ea809f) Andreas Reichel) +- SQL:2016 TABLESAMPLE clause ([4d8a5](https://github.com/JSQLParser/JSqlParser/commit/4d8a512191a4a1b) Andreas Reichel) +- add a method checking balanced brackets ([52df3](https://github.com/JSQLParser/JSqlParser/commit/52df32dd8ec2c10) Andreas Reichel) +- add support for INTERPRET function parsing (#1816) ([180ec](https://github.com/JSQLParser/JSqlParser/commit/180ec68cc9fa7eb) Matteo Sist) +- MySQL `NOT RLIKE`, `NOT REGEXP` expressions ([f1325](https://github.com/JSQLParser/JSqlParser/commit/f132547f56a1edd) Andreas Reichel) +- Postgres `NOTNULL` support ([386dc](https://github.com/JSQLParser/JSqlParser/commit/386dc7a0df98f1c) manticore-projects) +- `QUALIFY` clause ([75e4d](https://github.com/JSQLParser/JSqlParser/commit/75e4d30747a7e6e) Andreas Reichel) +- T-SQL `FOR ...` clause ([8027d](https://github.com/JSQLParser/JSqlParser/commit/8027dbf2cbf9163) Andreas Reichel) +- Quoted Identifiers can contain double-quotes (PostgreSQL) ([73c55](https://github.com/JSQLParser/JSqlParser/commit/73c55fda1ac6a42) Andreas Reichel) +- functions blocks, parenthesed JSON Expressions ([5263b](https://github.com/JSQLParser/JSqlParser/commit/5263b91f3e555b7) Andreas Reichel) +- functions blocks, parenthesed JSON Expressions ([e19dc](https://github.com/JSQLParser/JSqlParser/commit/e19dc0e081f741d) Andreas Reichel) +- parse CREATE TRIGGER as UnsupportedStatement ([64b03](https://github.com/JSQLParser/JSqlParser/commit/64b0331f772278b) Andreas Reichel) +- chaining JSON Expressions ([6ef5e](https://github.com/JSQLParser/JSqlParser/commit/6ef5e0b6ee06211) Andreas Reichel) +- Write API documentation to the WebSite via XMLDoclet ([c5366](https://github.com/JSQLParser/JSqlParser/commit/c53667f8eff30e3) Andreas Reichel) +- `MEMBER OF` condition as shown at https://dev.mysql.com/doc/refman/8.0/en/json-search-functions.html#operator_member-of ([6e7a7](https://github.com/JSQLParser/JSqlParser/commit/6e7a78dfc563749) Andreas Reichel) +- access Elements of Array Columns ([09a70](https://github.com/JSQLParser/JSqlParser/commit/09a70a499121792) Andreas Reichel) +- JdbcNamedParameter allows "&" (instead of ":") ([c07a4](https://github.com/JSQLParser/JSqlParser/commit/c07a43b3c128a5d) Andreas Reichel) +- Consolidate the `ExpressionList`, removing many redundant List alike Classes and Productions ([288b1](https://github.com/JSQLParser/JSqlParser/commit/288b177fe9c8a4c) Andreas Reichel) +- ClickHouse `LIMIT ... BY ...` clause ([4d5e2](https://github.com/JSQLParser/JSqlParser/commit/4d5e26d3febe686) Andreas Reichel) +- implement SQL:2016 Convert() and Trim() ([3a27a](https://github.com/JSQLParser/JSqlParser/commit/3a27a9dd4add700) Andreas Reichel) +- Switch off contradicting `JOIN` qualifiers, when setting a qualifier ([b6ea8](https://github.com/JSQLParser/JSqlParser/commit/b6ea8b162450545) Andreas Reichel) +- Test if a JOIN is an INNER JOIN according to the SQL:2016 ([6281b](https://github.com/JSQLParser/JSqlParser/commit/6281b07a543b088) Andreas Reichel) +- ClickHouse `Select...` ``FINAL` modifier ([4b7f2](https://github.com/JSQLParser/JSqlParser/commit/4b7f21c54c24d04) Andreas Reichel) +- Multi-Part Names for Variables and Parameters ([9da7a](https://github.com/JSQLParser/JSqlParser/commit/9da7a06ebe9b036) Andreas Reichel) +- Oracle `HAVING` before `GROUP BY` ([4efb9](https://github.com/JSQLParser/JSqlParser/commit/4efb99f1510ad16) Andreas Reichel) +- Lateral View ([8a1bd](https://github.com/JSQLParser/JSqlParser/commit/8a1bdeccbadb04f) Andreas Reichel) +- FETCH uses EXPRESSION ([0979b](https://github.com/JSQLParser/JSqlParser/commit/0979b2e5ea76b8c) Andreas Reichel) +- Support more Statement Separators ([b0814](https://github.com/JSQLParser/JSqlParser/commit/b08148414bd8f30) Andreas Reichel) +- CREATE VIEW ... REFRESH AUTO... ([1c8d8](https://github.com/JSQLParser/JSqlParser/commit/1c8d8daf48ebac1) Andreas Reichel) +- Oracle Alternative Quoting ([c57c4](https://github.com/JSQLParser/JSqlParser/commit/c57c427032c91d0) Andreas Reichel) +- make important Classes Serializable ([b94b2](https://github.com/JSQLParser/JSqlParser/commit/b94b2cc6a8f8c7d) Andreas Reichel) ### Bug Fixes -- ExpressionList of Expressions in `Values` ([994e6](https://github.com/JSQLParser/JSqlParser/commit/994e6c63d065a48) Andreas Reichel) -- check for NULL before iterating ([beb68](https://github.com/JSQLParser/JSqlParser/commit/beb68d55239da97) Andreas Reichel) -- Backslash escaped single quote `'\''` ([a2975](https://github.com/JSQLParser/JSqlParser/commit/a29754341adeffc) Andreas Reichel) -- `INSERT` must use simple Column Names only ([420d7](https://github.com/JSQLParser/JSqlParser/commit/420d7d834760f14) Andreas Reichel) -- SPHINX modules and themes ([6f277](https://github.com/JSQLParser/JSqlParser/commit/6f277654b9344ec) Andreas Reichel) -- expose IntervalExpression attributes and use DeParser ([b6fab](https://github.com/JSQLParser/JSqlParser/commit/b6fab2a484e0b47) Andreas Reichel) -- throw the specific exception ([cb960](https://github.com/JSQLParser/JSqlParser/commit/cb960a35647a19a) Andreas Reichel) -- Complex Parsing Approach ([4f048](https://github.com/JSQLParser/JSqlParser/commit/4f0488ccb4611f0) Andreas Reichel) -- issue #1789 ([32ec5](https://github.com/JSQLParser/JSqlParser/commit/32ec56114c1fbc4) Andreas Reichel) -- issue #1789 ([d20c8](https://github.com/JSQLParser/JSqlParser/commit/d20c8e94de64e2a) Andreas Reichel) -- issue #1791 ([88d1b](https://github.com/JSQLParser/JSqlParser/commit/88d1b62f0038a9a) Andreas Reichel) -- Java Version 8 ([7cecd](https://github.com/JSQLParser/JSqlParser/commit/7cecd293cf4e0ea) Andreas Reichel) -- find the correct position when field belongs to an internal class ([21389](https://github.com/JSQLParser/JSqlParser/commit/21389b712995674) Andreas Reichel) -- Remove tests for `()`, since `ParenthesedExpressionList` will catch those too ([905ef](https://github.com/JSQLParser/JSqlParser/commit/905ef6512d592d6) Andreas Reichel) -- assign Enum case insensitive ([fc577](https://github.com/JSQLParser/JSqlParser/commit/fc577caa4146878) Andreas Reichel) +- ExpressionList of Expressions in `Values` ([994e6](https://github.com/JSQLParser/JSqlParser/commit/994e6c63d065a48) Andreas Reichel) +- check for NULL before iterating ([beb68](https://github.com/JSQLParser/JSqlParser/commit/beb68d55239da97) Andreas Reichel) +- Backslash escaped single quote `'\''` ([a2975](https://github.com/JSQLParser/JSqlParser/commit/a29754341adeffc) Andreas Reichel) +- `INSERT` must use simple Column Names only ([420d7](https://github.com/JSQLParser/JSqlParser/commit/420d7d834760f14) Andreas Reichel) +- SPHINX modules and themes ([6f277](https://github.com/JSQLParser/JSqlParser/commit/6f277654b9344ec) Andreas Reichel) +- expose IntervalExpression attributes and use DeParser ([b6fab](https://github.com/JSQLParser/JSqlParser/commit/b6fab2a484e0b47) Andreas Reichel) +- throw the specific exception ([cb960](https://github.com/JSQLParser/JSqlParser/commit/cb960a35647a19a) Andreas Reichel) +- Complex Parsing Approach ([4f048](https://github.com/JSQLParser/JSqlParser/commit/4f0488ccb4611f0) Andreas Reichel) +- issue #1789 ([32ec5](https://github.com/JSQLParser/JSqlParser/commit/32ec56114c1fbc4) Andreas Reichel) +- issue #1789 ([d20c8](https://github.com/JSQLParser/JSqlParser/commit/d20c8e94de64e2a) Andreas Reichel) +- issue #1791 ([88d1b](https://github.com/JSQLParser/JSqlParser/commit/88d1b62f0038a9a) Andreas Reichel) +- Java Version 8 ([7cecd](https://github.com/JSQLParser/JSqlParser/commit/7cecd293cf4e0ea) Andreas Reichel) +- find the correct position when field belongs to an internal class ([21389](https://github.com/JSQLParser/JSqlParser/commit/21389b712995674) Andreas Reichel) +- Remove tests for `()`, since `ParenthesedExpressionList` will catch those too ([905ef](https://github.com/JSQLParser/JSqlParser/commit/905ef6512d592d6) Andreas Reichel) +- assign Enum case insensitive ([fc577](https://github.com/JSQLParser/JSqlParser/commit/fc577caa4146878) Andreas Reichel) ### Other changes @@ -1038,9 +1910,9 @@ Changelog of JSqlParser. **Fixing a problem with an OP_CONCAT in WhenExpression (#1837)** -* fix: Concatenation in inner ELSE statement (Second level of Case Expression) -* fix: broken tests -* fix: Delete lookahead(3) +* fix: Concatenation in inner ELSE statement (Second level of Case Expression) +* fix: broken tests +* fix: Delete lookahead(3) [f05cb](https://github.com/JSQLParser/JSqlParser/commit/f05cb7ff4aa46c5) amigalev *2023-08-20 04:43:30* @@ -1066,7 +1938,7 @@ Changelog of JSqlParser. **Update sphinx.yml** -* fix the FURO theme +* fix the FURO theme [51cc4](https://github.com/JSQLParser/JSqlParser/commit/51cc444ff98ad1d) manticore-projects *2023-06-01 02:49:23* @@ -1087,157 +1959,157 @@ Changelog of JSqlParser. **Fix #1758: Use long for Feature.timeOut (#1759)** -* Co-authored-by: Tobias <t.warneke@gmx.net> +* Co-authored-by: Tobias <t.warneke@gmx.net> [3314e](https://github.com/JSQLParser/JSqlParser/commit/3314edf0ea17772) Tomasz Zarna *2023-04-27 20:30:31* **Ignoring unnecessarily generated jacoco report (#1762)** -* Ignoring unnecessarily generated jacoco report -* Ignoring unnecessarily generated by pmd plugin -* --------- -* Co-authored-by: other <other@ECE-A55006.austin.utexas.edu> -* Co-authored-by: Tobias <t.warneke@gmx.net> +* Ignoring unnecessarily generated jacoco report +* Ignoring unnecessarily generated by pmd plugin +* --------- +* Co-authored-by: other <other@ECE-A55006.austin.utexas.edu> +* Co-authored-by: Tobias <t.warneke@gmx.net> [1bbb1](https://github.com/JSQLParser/JSqlParser/commit/1bbb1443d84684c) optimizing-ci-builds *2023-04-27 19:50:42* **Ignoring unnecessarily generated by pmd plugin (#1763)** -* Co-authored-by: other <other@ECE-A55006.austin.utexas.edu> +* Co-authored-by: other <other@ECE-A55006.austin.utexas.edu> [52648](https://github.com/JSQLParser/JSqlParser/commit/52648277e69fa07) optimizing-ci-builds *2023-04-27 19:49:15* **Refactor Parenthesed SelectBody and FromItem (#1754)** -* Fixes #1684: Support CREATE MATERIALIZED VIEW with AUTO REFRESH -* Support parsing create view statements in Redshift with AUTO REFRESH -* option. -* Reduce cyclomatic complexity in CreateView.toString -* Extract adding the force option into a dedicated method resulting in the -* cyclomatic complexity reduction of the CreateView.toString method. -* Enhanced Keywords -* Add Keywords and document, which keywords are allowed for what purpose -* Fix incorrect tests -* Define Reserved Keywords explicitly -* Derive All Keywords from Grammar directly -* Generate production for Object Names (semi-) automatically -* Add parametrized Keyword Tests -* Fix test resources -* Adjust Gradle to JUnit 5 -* Parallel Test execution -* Gradle Caching -* Explicitly request for latest JavaCC 7.0.10 -* Do not mark SpeedTest for concurrent execution -* Remove unused imports -* Adjust Gradle to JUnit 5 -* Parallel Test execution -* Gradle Caching -* Explicitly request for latest JavaCC 7.0.10 -* Do not mark SpeedTest for concurrent execution -* Remove unused imports -* Sphinx Documentation -* Update the MANTICORE Sphinx Theme, but ignore it in GIT -* Add the content to the Sphinx sites -* Add a Gradle function to derive Stable and Snapshot version from GIT Tags -* Add a Gradle GIT change task -* Add a Gradle sphinx task -* Add a special Test case for illustrating the use of JSQLParser -* doc: request for `Conventional Commit` messages -* feat: make important Classes Serializable -* Implement Serializable for persisting via ObjectOutputStream -* chore: Make Serializable -* doc: Better integration of the RR diagrams -* - apply neutral Sphinx theme -* - insert the RR diagrams into the sphinx sources -* - better documentation on Gradle dependencies -* - link GitHub repository -* Merge -* feat: Oracle Alternative Quoting -* - add support for Oracle Alternative Quoting e.g. `q'(...)'` -* - fixes #1718 -* - add a Logo and FavIcon to the Website -* - document recent changes on Quoting/Escaping -* - add an example on building SQL from Java -* - rework the README.md, promote the Website -* - add Spotless Formatter, using Google Java Style (with Tab=4 Spaces) -* style: Appease PMD/Codacy -* doc: fix the issue template -* - fix the issue template -* - fix the -SNAPSHOT version number -* Update issue templates -* Update issue templates -* feat: Support more Statement Separators -* - `GO` -* - Slash `/` -* - Two empty lines -* feat: FETCH uses EXPRESSION -* - `FETCH` uses `EXPRESSION` instead of SimpleJDBCParameter only -* - Visit/Accept `FETCH` `EXPRESSION` instead of `append` to String -* - Visit/Accept `OFFSET` `EXPRESSION` instead of `append` to String -* - Gradle: remove obsolete/incompatible `jvmArgs` from Test() -* style: apply Spotless -* test: commit missing test -* fix: JSon Operator can use Simple Function -* Supports `Function() ->> Literal` (although `Function()` would not allow Nested Expression Parameters) -* fixes #1571 -* style: Reformat changed files and headers -* style: Remove unused variable -* feat: Add support for Hangul "\uAC00"-"\uD7A3" -* fixes #1747 -* style: expose `SetStatement` key-value list -* fixes #1746 -* style: Appease PMD/Codacy -* feat: `ConflictTarget` allows multiple `IndexColumnNames` -* fixes #1749 -* fixes #1633 -* fixes #955 -* doc: fix reference in the Java Doc -* build: better Upload Groovy Task -* feat: ParenthesedSelectBody and ParenthesedFromItem -* - First properly working version -* - Work in progress, 13 tests failing -* feat: ParenthesedSelectBody and ParenthesedFromItem -* - delete unneeded ParenthesedJoin -* - rename ParenthesisFromItem into ParenthesedFromItem -* feat: ParenthesedSelectBody and ParenthesedFromItem -* - fix `NULLS FIRST` and `NULLS LAST` -* feat: ParenthesedSelectBody and ParenthesedFromItem -* - fix Oracle Hints -* feat: ParenthesedSelectBody and ParenthesedFromItem -* - parse `SetOperation` only after a (first plain) SelectBody has found, this fixes the performance issue -* - one more special Oracle Test succeeds -* - 5 remaining test failures -* feat: ParenthesedSelectBody and ParenthesedFromItem -* - extract `OrderByElements` into `SelectBody` -* - one more special Oracle Test succeeds -* - all tests succeed -* style: Appease PMD/Codacy -* style: Appease PMD/Codacy -* feat: Refactor SelectBody implementations -* - `SelectBody` implements `FromItem` -* - get rid of `SubSelect` and `SpecialSubSelect` -* - `Merge` can use `FromItem` instead of `SubSelect` or `Table` -* - `LateralSubSelect` extends `ParenthesedSelectBody` directly -* - Simplify the `Select` statement, although it is still redundant since `SelectBody` also could implement `Statement` directly -* - `WithItem` can use `SelectBody` directly, which allows for nested `WithItems` -* BREAKING-CHANGE: Lots of redundant methods and intermediate removed -* feat: Refactor SelectBody implementations -* - `SelectBody` implements `Statement` and so makes `Select` redundant -* - get rid of `ValuesList` -* - refactor `ValuesStatement` into `Values` which just implements `SelectBody` (and becomes a `Statement` and a `FromItem`), move to `select` package -* BREAKING-CHANGE: Lots of redundant methods and intermediate removed -* style: Code cleanup -* - remove 3 unused/obsolete productions -* - appease PMD/Codacy -* feat: Merge `SelectBody` into `Select` Statement -* - former `SelectBody` implements `Statement` and so becomes `Select` -* - this reduces the AST by 1 hierarchy level -* style: Remove unused import -* test: @Disabled invalid Test -* style: Appease PMD/Codacy -* test: Add a SubSelect Parsing Test -* --------- -* Co-authored-by: zaza <tzarna@gmail.com> +* Fixes #1684: Support CREATE MATERIALIZED VIEW with AUTO REFRESH +* Support parsing create view statements in Redshift with AUTO REFRESH +* option. +* Reduce cyclomatic complexity in CreateView.toString +* Extract adding the force option into a dedicated method resulting in the +* cyclomatic complexity reduction of the CreateView.toString method. +* Enhanced Keywords +* Add Keywords and document, which keywords are allowed for what purpose +* Fix incorrect tests +* Define Reserved Keywords explicitly +* Derive All Keywords from Grammar directly +* Generate production for Object Names (semi-) automatically +* Add parametrized Keyword Tests +* Fix test resources +* Adjust Gradle to JUnit 5 +* Parallel Test execution +* Gradle Caching +* Explicitly request for latest JavaCC 7.0.10 +* Do not mark SpeedTest for concurrent execution +* Remove unused imports +* Adjust Gradle to JUnit 5 +* Parallel Test execution +* Gradle Caching +* Explicitly request for latest JavaCC 7.0.10 +* Do not mark SpeedTest for concurrent execution +* Remove unused imports +* Sphinx Documentation +* Update the MANTICORE Sphinx Theme, but ignore it in GIT +* Add the content to the Sphinx sites +* Add a Gradle function to derive Stable and Snapshot version from GIT Tags +* Add a Gradle GIT change task +* Add a Gradle sphinx task +* Add a special Test case for illustrating the use of JSQLParser +* doc: request for `Conventional Commit` messages +* feat: make important Classes Serializable +* Implement Serializable for persisting via ObjectOutputStream +* chore: Make Serializable +* doc: Better integration of the RR diagrams +* - apply neutral Sphinx theme +* - insert the RR diagrams into the sphinx sources +* - better documentation on Gradle dependencies +* - link GitHub repository +* Merge +* feat: Oracle Alternative Quoting +* - add support for Oracle Alternative Quoting e.g. `q'(...)'` +* - fixes #1718 +* - add a Logo and FavIcon to the Website +* - document recent changes on Quoting/Escaping +* - add an example on building SQL from Java +* - rework the README.md, promote the Website +* - add Spotless Formatter, using Google Java Style (with Tab=4 Spaces) +* style: Appease PMD/Codacy +* doc: fix the issue template +* - fix the issue template +* - fix the -SNAPSHOT version number +* Update issue templates +* Update issue templates +* feat: Support more Statement Separators +* - `GO` +* - Slash `/` +* - Two empty lines +* feat: FETCH uses EXPRESSION +* - `FETCH` uses `EXPRESSION` instead of SimpleJDBCParameter only +* - Visit/Accept `FETCH` `EXPRESSION` instead of `append` to String +* - Visit/Accept `OFFSET` `EXPRESSION` instead of `append` to String +* - Gradle: remove obsolete/incompatible `jvmArgs` from Test() +* style: apply Spotless +* test: commit missing test +* fix: JSon Operator can use Simple Function +* Supports `Function() ->> Literal` (although `Function()` would not allow Nested Expression Parameters) +* fixes #1571 +* style: Reformat changed files and headers +* style: Remove unused variable +* feat: Add support for Hangul "\uAC00"-"\uD7A3" +* fixes #1747 +* style: expose `SetStatement` key-value list +* fixes #1746 +* style: Appease PMD/Codacy +* feat: `ConflictTarget` allows multiple `IndexColumnNames` +* fixes #1749 +* fixes #1633 +* fixes #955 +* doc: fix reference in the Java Doc +* build: better Upload Groovy Task +* feat: ParenthesedSelectBody and ParenthesedFromItem +* - First properly working version +* - Work in progress, 13 tests failing +* feat: ParenthesedSelectBody and ParenthesedFromItem +* - delete unneeded ParenthesedJoin +* - rename ParenthesisFromItem into ParenthesedFromItem +* feat: ParenthesedSelectBody and ParenthesedFromItem +* - fix `NULLS FIRST` and `NULLS LAST` +* feat: ParenthesedSelectBody and ParenthesedFromItem +* - fix Oracle Hints +* feat: ParenthesedSelectBody and ParenthesedFromItem +* - parse `SetOperation` only after a (first plain) SelectBody has found, this fixes the performance issue +* - one more special Oracle Test succeeds +* - 5 remaining test failures +* feat: ParenthesedSelectBody and ParenthesedFromItem +* - extract `OrderByElements` into `SelectBody` +* - one more special Oracle Test succeeds +* - all tests succeed +* style: Appease PMD/Codacy +* style: Appease PMD/Codacy +* feat: Refactor SelectBody implementations +* - `SelectBody` implements `FromItem` +* - get rid of `SubSelect` and `SpecialSubSelect` +* - `Merge` can use `FromItem` instead of `SubSelect` or `Table` +* - `LateralSubSelect` extends `ParenthesedSelectBody` directly +* - Simplify the `Select` statement, although it is still redundant since `SelectBody` also could implement `Statement` directly +* - `WithItem` can use `SelectBody` directly, which allows for nested `WithItems` +* BREAKING-CHANGE: Lots of redundant methods and intermediate removed +* feat: Refactor SelectBody implementations +* - `SelectBody` implements `Statement` and so makes `Select` redundant +* - get rid of `ValuesList` +* - refactor `ValuesStatement` into `Values` which just implements `SelectBody` (and becomes a `Statement` and a `FromItem`), move to `select` package +* BREAKING-CHANGE: Lots of redundant methods and intermediate removed +* style: Code cleanup +* - remove 3 unused/obsolete productions +* - appease PMD/Codacy +* feat: Merge `SelectBody` into `Select` Statement +* - former `SelectBody` implements `Statement` and so becomes `Select` +* - this reduces the AST by 1 hierarchy level +* style: Remove unused import +* test: @Disabled invalid Test +* style: Appease PMD/Codacy +* test: Add a SubSelect Parsing Test +* --------- +* Co-authored-by: zaza <tzarna@gmail.com> [a312d](https://github.com/JSQLParser/JSqlParser/commit/a312dcdc2d618f1) manticore-projects *2023-04-27 19:38:24* @@ -1248,181 +2120,181 @@ Changelog of JSqlParser. **Assorted Fixes #7 (#1745)** -* Fixes #1684: Support CREATE MATERIALIZED VIEW with AUTO REFRESH -* Support parsing create view statements in Redshift with AUTO REFRESH -* option. -* Reduce cyclomatic complexity in CreateView.toString -* Extract adding the force option into a dedicated method resulting in the -* cyclomatic complexity reduction of the CreateView.toString method. -* Enhanced Keywords -* Add Keywords and document, which keywords are allowed for what purpose -* Fix incorrect tests -* Define Reserved Keywords explicitly -* Derive All Keywords from Grammar directly -* Generate production for Object Names (semi-) automatically -* Add parametrized Keyword Tests -* Fix test resources -* Adjust Gradle to JUnit 5 -* Parallel Test execution -* Gradle Caching -* Explicitly request for latest JavaCC 7.0.10 -* Do not mark SpeedTest for concurrent execution -* Remove unused imports -* Adjust Gradle to JUnit 5 -* Parallel Test execution -* Gradle Caching -* Explicitly request for latest JavaCC 7.0.10 -* Do not mark SpeedTest for concurrent execution -* Remove unused imports -* Sphinx Documentation -* Update the MANTICORE Sphinx Theme, but ignore it in GIT -* Add the content to the Sphinx sites -* Add a Gradle function to derive Stable and Snapshot version from GIT Tags -* Add a Gradle GIT change task -* Add a Gradle sphinx task -* Add a special Test case for illustrating the use of JSQLParser -* doc: request for `Conventional Commit` messages -* feat: make important Classes Serializable -* Implement Serializable for persisting via ObjectOutputStream -* chore: Make Serializable -* doc: Better integration of the RR diagrams -* - apply neutral Sphinx theme -* - insert the RR diagrams into the sphinx sources -* - better documentation on Gradle dependencies -* - link GitHub repository -* Merge -* feat: Oracle Alternative Quoting -* - add support for Oracle Alternative Quoting e.g. `q'(...)'` -* - fixes #1718 -* - add a Logo and FavIcon to the Website -* - document recent changes on Quoting/Escaping -* - add an example on building SQL from Java -* - rework the README.md, promote the Website -* - add Spotless Formatter, using Google Java Style (with Tab=4 Spaces) -* style: Appease PMD/Codacy -* doc: fix the issue template -* - fix the issue template -* - fix the -SNAPSHOT version number -* Update issue templates -* Update issue templates -* feat: Support more Statement Separators -* - `GO` -* - Slash `/` -* - Two empty lines -* feat: FETCH uses EXPRESSION -* - `FETCH` uses `EXPRESSION` instead of SimpleJDBCParameter only -* - Visit/Accept `FETCH` `EXPRESSION` instead of `append` to String -* - Visit/Accept `OFFSET` `EXPRESSION` instead of `append` to String -* - Gradle: remove obsolete/incompatible `jvmArgs` from Test() -* style: apply Spotless -* test: commit missing test -* fix: JSon Operator can use Simple Function -* Supports `Function() ->> Literal` (although `Function()` would not allow Nested Expression Parameters) -* fixes #1571 -* style: Reformat changed files and headers -* style: Remove unused variable -* feat: Add support for Hangul "\uAC00"-"\uD7A3" -* fixes #1747 -* style: expose `SetStatement` key-value list -* fixes #1746 -* style: Appease PMD/Codacy -* feat: `ConflictTarget` allows multiple `IndexColumnNames` -* fixes #1749 -* fixes #1633 -* fixes #955 -* doc: fix reference in the Java Doc -* build: better Upload Groovy Task -* --------- -* Co-authored-by: zaza <tzarna@gmail.com> +* Fixes #1684: Support CREATE MATERIALIZED VIEW with AUTO REFRESH +* Support parsing create view statements in Redshift with AUTO REFRESH +* option. +* Reduce cyclomatic complexity in CreateView.toString +* Extract adding the force option into a dedicated method resulting in the +* cyclomatic complexity reduction of the CreateView.toString method. +* Enhanced Keywords +* Add Keywords and document, which keywords are allowed for what purpose +* Fix incorrect tests +* Define Reserved Keywords explicitly +* Derive All Keywords from Grammar directly +* Generate production for Object Names (semi-) automatically +* Add parametrized Keyword Tests +* Fix test resources +* Adjust Gradle to JUnit 5 +* Parallel Test execution +* Gradle Caching +* Explicitly request for latest JavaCC 7.0.10 +* Do not mark SpeedTest for concurrent execution +* Remove unused imports +* Adjust Gradle to JUnit 5 +* Parallel Test execution +* Gradle Caching +* Explicitly request for latest JavaCC 7.0.10 +* Do not mark SpeedTest for concurrent execution +* Remove unused imports +* Sphinx Documentation +* Update the MANTICORE Sphinx Theme, but ignore it in GIT +* Add the content to the Sphinx sites +* Add a Gradle function to derive Stable and Snapshot version from GIT Tags +* Add a Gradle GIT change task +* Add a Gradle sphinx task +* Add a special Test case for illustrating the use of JSQLParser +* doc: request for `Conventional Commit` messages +* feat: make important Classes Serializable +* Implement Serializable for persisting via ObjectOutputStream +* chore: Make Serializable +* doc: Better integration of the RR diagrams +* - apply neutral Sphinx theme +* - insert the RR diagrams into the sphinx sources +* - better documentation on Gradle dependencies +* - link GitHub repository +* Merge +* feat: Oracle Alternative Quoting +* - add support for Oracle Alternative Quoting e.g. `q'(...)'` +* - fixes #1718 +* - add a Logo and FavIcon to the Website +* - document recent changes on Quoting/Escaping +* - add an example on building SQL from Java +* - rework the README.md, promote the Website +* - add Spotless Formatter, using Google Java Style (with Tab=4 Spaces) +* style: Appease PMD/Codacy +* doc: fix the issue template +* - fix the issue template +* - fix the -SNAPSHOT version number +* Update issue templates +* Update issue templates +* feat: Support more Statement Separators +* - `GO` +* - Slash `/` +* - Two empty lines +* feat: FETCH uses EXPRESSION +* - `FETCH` uses `EXPRESSION` instead of SimpleJDBCParameter only +* - Visit/Accept `FETCH` `EXPRESSION` instead of `append` to String +* - Visit/Accept `OFFSET` `EXPRESSION` instead of `append` to String +* - Gradle: remove obsolete/incompatible `jvmArgs` from Test() +* style: apply Spotless +* test: commit missing test +* fix: JSon Operator can use Simple Function +* Supports `Function() ->> Literal` (although `Function()` would not allow Nested Expression Parameters) +* fixes #1571 +* style: Reformat changed files and headers +* style: Remove unused variable +* feat: Add support for Hangul "\uAC00"-"\uD7A3" +* fixes #1747 +* style: expose `SetStatement` key-value list +* fixes #1746 +* style: Appease PMD/Codacy +* feat: `ConflictTarget` allows multiple `IndexColumnNames` +* fixes #1749 +* fixes #1633 +* fixes #955 +* doc: fix reference in the Java Doc +* build: better Upload Groovy Task +* --------- +* Co-authored-by: zaza <tzarna@gmail.com> [31ef1](https://github.com/JSQLParser/JSqlParser/commit/31ef1aaf23e2917) manticore-projects *2023-03-21 22:04:58* **disable xml report (#1748)** -* Co-authored-by: other <other@ECE-A55006.austin.utexas.edu> +* Co-authored-by: other <other@ECE-A55006.austin.utexas.edu> [476d9](https://github.com/JSQLParser/JSqlParser/commit/476d96965492131) optimizing-ci-builds *2023-03-21 21:58:25* **Assorted Fixes #6 (#1740)** -* Fixes #1684: Support CREATE MATERIALIZED VIEW with AUTO REFRESH -* Support parsing create view statements in Redshift with AUTO REFRESH -* option. -* Reduce cyclomatic complexity in CreateView.toString -* Extract adding the force option into a dedicated method resulting in the -* cyclomatic complexity reduction of the CreateView.toString method. -* Enhanced Keywords -* Add Keywords and document, which keywords are allowed for what purpose -* Fix incorrect tests -* Define Reserved Keywords explicitly -* Derive All Keywords from Grammar directly -* Generate production for Object Names (semi-) automatically -* Add parametrized Keyword Tests -* Fix test resources -* Adjust Gradle to JUnit 5 -* Parallel Test execution -* Gradle Caching -* Explicitly request for latest JavaCC 7.0.10 -* Do not mark SpeedTest for concurrent execution -* Remove unused imports -* Adjust Gradle to JUnit 5 -* Parallel Test execution -* Gradle Caching -* Explicitly request for latest JavaCC 7.0.10 -* Do not mark SpeedTest for concurrent execution -* Remove unused imports -* Sphinx Documentation -* Update the MANTICORE Sphinx Theme, but ignore it in GIT -* Add the content to the Sphinx sites -* Add a Gradle function to derive Stable and Snapshot version from GIT Tags -* Add a Gradle GIT change task -* Add a Gradle sphinx task -* Add a special Test case for illustrating the use of JSQLParser -* doc: request for `Conventional Commit` messages -* feat: make important Classes Serializable -* Implement Serializable for persisting via ObjectOutputStream -* chore: Make Serializable -* doc: Better integration of the RR diagrams -* - apply neutral Sphinx theme -* - insert the RR diagrams into the sphinx sources -* - better documentation on Gradle dependencies -* - link GitHub repository -* Merge -* feat: Oracle Alternative Quoting -* - add support for Oracle Alternative Quoting e.g. `q'(...)'` -* - fixes #1718 -* - add a Logo and FavIcon to the Website -* - document recent changes on Quoting/Escaping -* - add an example on building SQL from Java -* - rework the README.md, promote the Website -* - add Spotless Formatter, using Google Java Style (with Tab=4 Spaces) -* style: Appease PMD/Codacy -* doc: fix the issue template -* - fix the issue template -* - fix the -SNAPSHOT version number -* Update issue templates -* Update issue templates -* feat: Support more Statement Separators -* - `GO` -* - Slash `/` -* - Two empty lines -* feat: FETCH uses EXPRESSION -* - `FETCH` uses `EXPRESSION` instead of SimpleJDBCParameter only -* - Visit/Accept `FETCH` `EXPRESSION` instead of `append` to String -* - Visit/Accept `OFFSET` `EXPRESSION` instead of `append` to String -* - Gradle: remove obsolete/incompatible `jvmArgs` from Test() -* style: apply Spotless -* test: commit missing test -* feat: Unicode CJK Unified Ideographs (Unicode block) -* fixes #1741 -* feat: Unicode CJK Unified Ideographs (Unicode block) -* fixes #1741 -* feat: Functions with nested Attributes -* Supports `SELECT schemaName.f1(arguments).f2(arguments).f3.f4` and similar constructs -* fixes #1742 -* fixes #1050 -* --------- -* Co-authored-by: zaza <tzarna@gmail.com> +* Fixes #1684: Support CREATE MATERIALIZED VIEW with AUTO REFRESH +* Support parsing create view statements in Redshift with AUTO REFRESH +* option. +* Reduce cyclomatic complexity in CreateView.toString +* Extract adding the force option into a dedicated method resulting in the +* cyclomatic complexity reduction of the CreateView.toString method. +* Enhanced Keywords +* Add Keywords and document, which keywords are allowed for what purpose +* Fix incorrect tests +* Define Reserved Keywords explicitly +* Derive All Keywords from Grammar directly +* Generate production for Object Names (semi-) automatically +* Add parametrized Keyword Tests +* Fix test resources +* Adjust Gradle to JUnit 5 +* Parallel Test execution +* Gradle Caching +* Explicitly request for latest JavaCC 7.0.10 +* Do not mark SpeedTest for concurrent execution +* Remove unused imports +* Adjust Gradle to JUnit 5 +* Parallel Test execution +* Gradle Caching +* Explicitly request for latest JavaCC 7.0.10 +* Do not mark SpeedTest for concurrent execution +* Remove unused imports +* Sphinx Documentation +* Update the MANTICORE Sphinx Theme, but ignore it in GIT +* Add the content to the Sphinx sites +* Add a Gradle function to derive Stable and Snapshot version from GIT Tags +* Add a Gradle GIT change task +* Add a Gradle sphinx task +* Add a special Test case for illustrating the use of JSQLParser +* doc: request for `Conventional Commit` messages +* feat: make important Classes Serializable +* Implement Serializable for persisting via ObjectOutputStream +* chore: Make Serializable +* doc: Better integration of the RR diagrams +* - apply neutral Sphinx theme +* - insert the RR diagrams into the sphinx sources +* - better documentation on Gradle dependencies +* - link GitHub repository +* Merge +* feat: Oracle Alternative Quoting +* - add support for Oracle Alternative Quoting e.g. `q'(...)'` +* - fixes #1718 +* - add a Logo and FavIcon to the Website +* - document recent changes on Quoting/Escaping +* - add an example on building SQL from Java +* - rework the README.md, promote the Website +* - add Spotless Formatter, using Google Java Style (with Tab=4 Spaces) +* style: Appease PMD/Codacy +* doc: fix the issue template +* - fix the issue template +* - fix the -SNAPSHOT version number +* Update issue templates +* Update issue templates +* feat: Support more Statement Separators +* - `GO` +* - Slash `/` +* - Two empty lines +* feat: FETCH uses EXPRESSION +* - `FETCH` uses `EXPRESSION` instead of SimpleJDBCParameter only +* - Visit/Accept `FETCH` `EXPRESSION` instead of `append` to String +* - Visit/Accept `OFFSET` `EXPRESSION` instead of `append` to String +* - Gradle: remove obsolete/incompatible `jvmArgs` from Test() +* style: apply Spotless +* test: commit missing test +* feat: Unicode CJK Unified Ideographs (Unicode block) +* fixes #1741 +* feat: Unicode CJK Unified Ideographs (Unicode block) +* fixes #1741 +* feat: Functions with nested Attributes +* Supports `SELECT schemaName.f1(arguments).f2(arguments).f3.f4` and similar constructs +* fixes #1742 +* fixes #1050 +* --------- +* Co-authored-by: zaza <tzarna@gmail.com> [adeed](https://github.com/JSQLParser/JSqlParser/commit/adeed5359c65b8f) manticore-projects *2023-03-09 21:22:40* @@ -1443,34 +2315,34 @@ Changelog of JSqlParser. **Sphinx Documentation** -* Update the MANTICORE Sphinx Theme, but ignore it in GIT -* Add the content to the Sphinx sites -* Add a Gradle function to derive Stable and Snapshot version from GIT Tags -* Add a Gradle GIT change task -* Add a Gradle sphinx task -* Add a special Test case for illustrating the use of JSQLParser +* Update the MANTICORE Sphinx Theme, but ignore it in GIT +* Add the content to the Sphinx sites +* Add a Gradle function to derive Stable and Snapshot version from GIT Tags +* Add a Gradle GIT change task +* Add a Gradle sphinx task +* Add a special Test case for illustrating the use of JSQLParser [2ef66](https://github.com/JSQLParser/JSqlParser/commit/2ef6637afffa943) Andreas Reichel *2023-01-21 04:06:00* **Define Reserved Keywords explicitly** -* Derive All Keywords from Grammar directly -* Generate production for Object Names (semi-) automatically -* Add parametrized Keyword Tests +* Derive All Keywords from Grammar directly +* Generate production for Object Names (semi-) automatically +* Add parametrized Keyword Tests [f49e8](https://github.com/JSQLParser/JSqlParser/commit/f49e828fc9c5f2f) Andreas Reichel *2023-01-21 04:05:51* **Adjust Gradle to JUnit 5** -* Parallel Test execution -* Gradle Caching -* Explicitly request for latest JavaCC 7.0.10 +* Parallel Test execution +* Gradle Caching +* Explicitly request for latest JavaCC 7.0.10 [e960a](https://github.com/JSQLParser/JSqlParser/commit/e960a35e591ce07) Andreas Reichel *2023-01-21 04:05:51* **Enhanced Keywords** -* Add Keywords and document, which keywords are allowed for what purpose +* Add Keywords and document, which keywords are allowed for what purpose [b5321](https://github.com/JSQLParser/JSqlParser/commit/b5321d6e8bac588) Andreas Reichel *2023-01-21 04:05:51* @@ -1501,9 +2373,9 @@ Changelog of JSqlParser. **Adjust Gradle to JUnit 5** -* Parallel Test execution -* Gradle Caching -* Explicitly request for latest JavaCC 7.0.10 +* Parallel Test execution +* Gradle Caching +* Explicitly request for latest JavaCC 7.0.10 [2d51a](https://github.com/JSQLParser/JSqlParser/commit/2d51a82d3e9e51c) Andreas Reichel *2023-01-21 04:05:51* @@ -1514,15 +2386,15 @@ Changelog of JSqlParser. **Reduce cyclomatic complexity in CreateView.toString** -* Extract adding the force option into a dedicated method resulting in the -* cyclomatic complexity reduction of the CreateView.toString method. +* Extract adding the force option into a dedicated method resulting in the +* cyclomatic complexity reduction of the CreateView.toString method. [ea447](https://github.com/JSQLParser/JSqlParser/commit/ea4477bb775ebdb) zaza *2023-01-08 20:43:40* **Fixes #1684: Support CREATE MATERIALIZED VIEW with AUTO REFRESH** -* Support parsing create view statements in Redshift with AUTO REFRESH -* option. +* Support parsing create view statements in Redshift with AUTO REFRESH +* option. [74715](https://github.com/JSQLParser/JSqlParser/commit/747152a9fc1bfd1) zaza *2022-12-11 20:03:52* @@ -1531,7 +2403,7 @@ Changelog of JSqlParser. ### Bug Fixes -- add missing public Getter (#1632) ([d2212](https://github.com/JSQLParser/JSqlParser/commit/d2212776ac5eb83) manticore-projects) +- add missing public Getter (#1632) ([d2212](https://github.com/JSQLParser/JSqlParser/commit/d2212776ac5eb83) manticore-projects) ### Other changes @@ -1562,88 +2434,88 @@ Changelog of JSqlParser. **Oracle Alternative Quoting (#1722)** -* Fixes #1684: Support CREATE MATERIALIZED VIEW with AUTO REFRESH -* Support parsing create view statements in Redshift with AUTO REFRESH -* option. -* Reduce cyclomatic complexity in CreateView.toString -* Extract adding the force option into a dedicated method resulting in the -* cyclomatic complexity reduction of the CreateView.toString method. -* Enhanced Keywords -* Add Keywords and document, which keywords are allowed for what purpose -* Fix incorrect tests -* Define Reserved Keywords explicitly -* Derive All Keywords from Grammar directly -* Generate production for Object Names (semi-) automatically -* Add parametrized Keyword Tests -* Fix test resources -* Adjust Gradle to JUnit 5 -* Parallel Test execution -* Gradle Caching -* Explicitly request for latest JavaCC 7.0.10 -* Do not mark SpeedTest for concurrent execution -* Remove unused imports -* Adjust Gradle to JUnit 5 -* Parallel Test execution -* Gradle Caching -* Explicitly request for latest JavaCC 7.0.10 -* Do not mark SpeedTest for concurrent execution -* Remove unused imports -* Sphinx Documentation -* Update the MANTICORE Sphinx Theme, but ignore it in GIT -* Add the content to the Sphinx sites -* Add a Gradle function to derive Stable and Snapshot version from GIT Tags -* Add a Gradle GIT change task -* Add a Gradle sphinx task -* Add a special Test case for illustrating the use of JSQLParser -* doc: request for `Conventional Commit` messages -* feat: make important Classes Serializable -* Implement Serializable for persisting via ObjectOutputStream -* chore: Make Serializable -* doc: Better integration of the RR diagrams -* - apply neutral Sphinx theme -* - insert the RR diagrams into the sphinx sources -* - better documentation on Gradle dependencies -* - link GitHub repository -* Merge -* feat: Oracle Alternative Quoting -* - add support for Oracle Alternative Quoting e.g. `q'(...)'` -* - fixes #1718 -* - add a Logo and FavIcon to the Website -* - document recent changes on Quoting/Escaping -* - add an example on building SQL from Java -* - rework the README.md, promote the Website -* - add Spotless Formatter, using Google Java Style (with Tab=4 Spaces) -* style: Appease PMD/Codacy -* doc: fix the issue template -* - fix the issue template -* - fix the -SNAPSHOT version number -* Update issue templates -* Update issue templates -* feat: Support more Statement Separators -* - `GO` -* - Slash `/` -* - Two empty lines -* --------- -* Co-authored-by: zaza <tzarna@gmail.com> +* Fixes #1684: Support CREATE MATERIALIZED VIEW with AUTO REFRESH +* Support parsing create view statements in Redshift with AUTO REFRESH +* option. +* Reduce cyclomatic complexity in CreateView.toString +* Extract adding the force option into a dedicated method resulting in the +* cyclomatic complexity reduction of the CreateView.toString method. +* Enhanced Keywords +* Add Keywords and document, which keywords are allowed for what purpose +* Fix incorrect tests +* Define Reserved Keywords explicitly +* Derive All Keywords from Grammar directly +* Generate production for Object Names (semi-) automatically +* Add parametrized Keyword Tests +* Fix test resources +* Adjust Gradle to JUnit 5 +* Parallel Test execution +* Gradle Caching +* Explicitly request for latest JavaCC 7.0.10 +* Do not mark SpeedTest for concurrent execution +* Remove unused imports +* Adjust Gradle to JUnit 5 +* Parallel Test execution +* Gradle Caching +* Explicitly request for latest JavaCC 7.0.10 +* Do not mark SpeedTest for concurrent execution +* Remove unused imports +* Sphinx Documentation +* Update the MANTICORE Sphinx Theme, but ignore it in GIT +* Add the content to the Sphinx sites +* Add a Gradle function to derive Stable and Snapshot version from GIT Tags +* Add a Gradle GIT change task +* Add a Gradle sphinx task +* Add a special Test case for illustrating the use of JSQLParser +* doc: request for `Conventional Commit` messages +* feat: make important Classes Serializable +* Implement Serializable for persisting via ObjectOutputStream +* chore: Make Serializable +* doc: Better integration of the RR diagrams +* - apply neutral Sphinx theme +* - insert the RR diagrams into the sphinx sources +* - better documentation on Gradle dependencies +* - link GitHub repository +* Merge +* feat: Oracle Alternative Quoting +* - add support for Oracle Alternative Quoting e.g. `q'(...)'` +* - fixes #1718 +* - add a Logo and FavIcon to the Website +* - document recent changes on Quoting/Escaping +* - add an example on building SQL from Java +* - rework the README.md, promote the Website +* - add Spotless Formatter, using Google Java Style (with Tab=4 Spaces) +* style: Appease PMD/Codacy +* doc: fix the issue template +* - fix the issue template +* - fix the -SNAPSHOT version number +* Update issue templates +* Update issue templates +* feat: Support more Statement Separators +* - `GO` +* - Slash `/` +* - Two empty lines +* --------- +* Co-authored-by: zaza <tzarna@gmail.com> [e71e5](https://github.com/JSQLParser/JSqlParser/commit/e71e57dfe4b377c) manticore-projects *2023-02-07 20:18:52* **Issue1673 case within brackets (#1675)** -* fix: add missing public Getter -* Add public Getter for `updateSets` -* Fixes #1630 -* fix: Case within brackets -* fixes #1673 +* fix: add missing public Getter +* Add public Getter for `updateSets` +* Fixes #1630 +* fix: Case within brackets +* fixes #1673 [2ced7](https://github.com/JSQLParser/JSqlParser/commit/2ced7ded930f8b0) manticore-projects *2023-01-31 20:56:01* **Added support for SHOW INDEX from table (#1704)** -* Added support for SHOW INDEX from table -* Added * import -* fix for javadoc -* added <doclint>none</doclint> +* Added support for SHOW INDEX from table +* Added * import +* fix for javadoc +* added <doclint>none</doclint> [a2618](https://github.com/JSQLParser/JSqlParser/commit/a2618321135d517) Jayant Kumar Yadav *2023-01-31 20:54:05* @@ -1654,149 +2526,149 @@ Changelog of JSqlParser. **Sphinx Website (#1624)** -* Enhanced Keywords -* Add Keywords and document, which keywords are allowed for what purpose -* Fix incorrect tests -* Define Reserved Keywords explicitly -* Derive All Keywords from Grammar directly -* Generate production for Object Names (semi-) automatically -* Add parametrized Keyword Tests -* Fix test resources -* Adjust Gradle to JUnit 5 -* Parallel Test execution -* Gradle Caching -* Explicitly request for latest JavaCC 7.0.10 -* Do not mark SpeedTest for concurrent execution -* Remove unused imports -* Adjust Gradle to JUnit 5 -* Parallel Test execution -* Gradle Caching -* Explicitly request for latest JavaCC 7.0.10 -* Do not mark SpeedTest for concurrent execution -* Remove unused imports -* Keyword test adopt JUnit5 -* Update keywords -* CheckStyle sanitation of method names -* Merge Master -* Add Jupiter Parameters dependency again -* Automate the `updateKeywords` Step -* Update PMD and rules -* Rewrite test expected to fail -* Appease Codacy -* Remove broken rule warning about perfectly fine switch-case statements -* Force Changes -* Fix Merge Issues -* Read Tokens directly from the Grammar File without invoking JTREE -* - read Tokens per REGEX Matcher -* - move Reserved Keywords from Grammar into ParserKeywordsUtils -* - adjust the Tests -* Appease PMD/Codacy -* Extract the Keywords from the Grammar by using JTRee (instead of Regex) -* Add some tests to ensure, that all Keywords or found -* Appease Codacy/PMD -* Separate UpdateKeywords Task again -* Including it into compileJavacc won't work since it depends on compiling the ParserKeywordUtils.java -* Single file compilation did not work -* Clean-up the imports -* Add JavaCC dependency to Maven for building ParserKeywordsUtils -* Add JavaCC dependency to Maven for building ParserKeywordsUtils -* Merge Upstream -* Merge Master -* Fixes broken PR #1524 and Commit fb6e950ce0e62ebcd7a44ba9eea679da2b04b2ed -* Add AST Visualization -* Show the Statement's Java Objects in a tree hierarchy -* Sphinx Documentation -* Update the MANTICORE Sphinx Theme, but ignore it in GIT -* Add the content to the Sphinx sites -* Add a Gradle function to derive Stable and Snapshot version from GIT Tags -* Add a Gradle GIT change task -* Add a Gradle sphinx task -* Add a special Test case for illustrating the use of JSQLParser -* test: Document an additional Special Oracle test success -* doc: ignore the autogenerated changelog.rst in GIT -* build: temporarily reduce the Code Coverage requirements -* Temporarily reduce the Coverage checks regarding Minimum Coverage and Maximum Missed Lines in order to get the Keywords PR accepted. We should do a major Code cleanup afterwards. -* build: Clean-up the Gradle Build -* Prefix the Sphinx Prolog Variables with JSQLPARSER in order to allow for build the Main Website for various projects -* Remove some redundant version requests for PMD, CheckStyle and friends -* Remove JUnit-4 dependency and add HarmCrest -* Complete the PUBLISHING task -* doc: Explain the ``updateKeywords`` Gradle Task -* build: Un-escape the Unicode on the changelog file -* build: Un-escape the Unicode on the changelog file -* doc: Cleanup -* Unescape unicode characters from Git Changelog -* Remove obsolete code from Sphinx' conf.py -* doc: Properly un-escape the Git Commit message -* doc: request for `Conventional Commit` messages -* doc: correctly refer to `RelObjectNameWithoutValue()` -* build: upload the built files via Excec/SFTP -* doc: Add an example on Token White-listing -* doc: write the correct Git Repository -* doc: pronounce the OVERLAPS example more -* feat: make important Classes Serializable -* Implement Serializable for persisting via ObjectOutputStream -* doc: Add the "How to Use" java code -* chore: Make Serializable -* fix: Non-serializable field in serializable class -* build: various fixes to the Maven build file -* add the Keywords Documentation file to the task -* exclude the Sphinx files from the license header plugin -* fix the JavaDoc plugin options -* build: add the Keywords Documentation file to the task -* doc: add a page about actually Reserved Keywords -* build: avoid PMD/Codacy for Sphinx Documentation -* update Changelog -* build: Add Sphinx GitHub Action -* Add a GitHub Action, which will -* - Install Sphinx and Extensions -* - Install Gradle Wrapper -* - Run Gradle Wrapper Task `sphinx` -* - Deploy the generated static HTML site to GH Pages -* fix: fix a merge error, brackets -* fix: remove JavaCC dependency -* Parse Tokens via Regex -* Move JavaCC Token Parser into the KeywordsTest -* Make JavaCC a Test Dependency only -* doc: Fix Maven Artifact Version -* style: Avoid throwing raw exception types. -* style: Avoid throwing raw exception types. -* doc: Better integration of the RR diagrams -* - apply neutral Sphinx theme -* - insert the RR diagrams into the sphinx sources -* - better documentation on Gradle dependencies -* - link GitHub repository -* build: gradle, execute all Checks after Test -* Co-authored-by: Tobias <t.warneke@gmx.net> +* Enhanced Keywords +* Add Keywords and document, which keywords are allowed for what purpose +* Fix incorrect tests +* Define Reserved Keywords explicitly +* Derive All Keywords from Grammar directly +* Generate production for Object Names (semi-) automatically +* Add parametrized Keyword Tests +* Fix test resources +* Adjust Gradle to JUnit 5 +* Parallel Test execution +* Gradle Caching +* Explicitly request for latest JavaCC 7.0.10 +* Do not mark SpeedTest for concurrent execution +* Remove unused imports +* Adjust Gradle to JUnit 5 +* Parallel Test execution +* Gradle Caching +* Explicitly request for latest JavaCC 7.0.10 +* Do not mark SpeedTest for concurrent execution +* Remove unused imports +* Keyword test adopt JUnit5 +* Update keywords +* CheckStyle sanitation of method names +* Merge Master +* Add Jupiter Parameters dependency again +* Automate the `updateKeywords` Step +* Update PMD and rules +* Rewrite test expected to fail +* Appease Codacy +* Remove broken rule warning about perfectly fine switch-case statements +* Force Changes +* Fix Merge Issues +* Read Tokens directly from the Grammar File without invoking JTREE +* - read Tokens per REGEX Matcher +* - move Reserved Keywords from Grammar into ParserKeywordsUtils +* - adjust the Tests +* Appease PMD/Codacy +* Extract the Keywords from the Grammar by using JTRee (instead of Regex) +* Add some tests to ensure, that all Keywords or found +* Appease Codacy/PMD +* Separate UpdateKeywords Task again +* Including it into compileJavacc won't work since it depends on compiling the ParserKeywordUtils.java +* Single file compilation did not work +* Clean-up the imports +* Add JavaCC dependency to Maven for building ParserKeywordsUtils +* Add JavaCC dependency to Maven for building ParserKeywordsUtils +* Merge Upstream +* Merge Master +* Fixes broken PR #1524 and Commit fb6e950ce0e62ebcd7a44ba9eea679da2b04b2ed +* Add AST Visualization +* Show the Statement's Java Objects in a tree hierarchy +* Sphinx Documentation +* Update the MANTICORE Sphinx Theme, but ignore it in GIT +* Add the content to the Sphinx sites +* Add a Gradle function to derive Stable and Snapshot version from GIT Tags +* Add a Gradle GIT change task +* Add a Gradle sphinx task +* Add a special Test case for illustrating the use of JSQLParser +* test: Document an additional Special Oracle test success +* doc: ignore the autogenerated changelog.rst in GIT +* build: temporarily reduce the Code Coverage requirements +* Temporarily reduce the Coverage checks regarding Minimum Coverage and Maximum Missed Lines in order to get the Keywords PR accepted. We should do a major Code cleanup afterwards. +* build: Clean-up the Gradle Build +* Prefix the Sphinx Prolog Variables with JSQLPARSER in order to allow for build the Main Website for various projects +* Remove some redundant version requests for PMD, CheckStyle and friends +* Remove JUnit-4 dependency and add HarmCrest +* Complete the PUBLISHING task +* doc: Explain the ``updateKeywords`` Gradle Task +* build: Un-escape the Unicode on the changelog file +* build: Un-escape the Unicode on the changelog file +* doc: Cleanup +* Unescape unicode characters from Git Changelog +* Remove obsolete code from Sphinx' conf.py +* doc: Properly un-escape the Git Commit message +* doc: request for `Conventional Commit` messages +* doc: correctly refer to `RelObjectNameWithoutValue()` +* build: upload the built files via Excec/SFTP +* doc: Add an example on Token White-listing +* doc: write the correct Git Repository +* doc: pronounce the OVERLAPS example more +* feat: make important Classes Serializable +* Implement Serializable for persisting via ObjectOutputStream +* doc: Add the "How to Use" java code +* chore: Make Serializable +* fix: Non-serializable field in serializable class +* build: various fixes to the Maven build file +* add the Keywords Documentation file to the task +* exclude the Sphinx files from the license header plugin +* fix the JavaDoc plugin options +* build: add the Keywords Documentation file to the task +* doc: add a page about actually Reserved Keywords +* build: avoid PMD/Codacy for Sphinx Documentation +* update Changelog +* build: Add Sphinx GitHub Action +* Add a GitHub Action, which will +* - Install Sphinx and Extensions +* - Install Gradle Wrapper +* - Run Gradle Wrapper Task `sphinx` +* - Deploy the generated static HTML site to GH Pages +* fix: fix a merge error, brackets +* fix: remove JavaCC dependency +* Parse Tokens via Regex +* Move JavaCC Token Parser into the KeywordsTest +* Make JavaCC a Test Dependency only +* doc: Fix Maven Artifact Version +* style: Avoid throwing raw exception types. +* style: Avoid throwing raw exception types. +* doc: Better integration of the RR diagrams +* - apply neutral Sphinx theme +* - insert the RR diagrams into the sphinx sources +* - better documentation on Gradle dependencies +* - link GitHub repository +* build: gradle, execute all Checks after Test +* Co-authored-by: Tobias <t.warneke@gmx.net> [be8e7](https://github.com/JSQLParser/JSqlParser/commit/be8e7a8a1d77184) manticore-projects *2023-01-20 21:45:35* **Assorted Fixes #5 (#1715)** -* refactor: Merge REPLACE into UPSERT -* fixes #1706 -* feat: `DROP TEMPORARY TABLE ...` -* fixes #1712 -* build: PMD compliance -* ci: Merge master -* feat: Configurable backslash `\` escaping -* - Enables `\` as escape character in String Literals (beside SQL:2016 compliant `'`) -* - Default is OFF (since its not SQL:2016 compliant) -* - Activate per Parser Feature -* - Fixes #1638 -* - Fixes #1209 -* - Fixes #1173 -* - Fixes #1172 -* - Fixes #832 -* - Fixes #827 -* - Fixes #578 -* BREAKING-CHANGE: Backslash Escaping needs to be activated explicitly or else Backslash won't work as Escape Character. -* style: Checkstyle -* style: remove dead code -* style: PMD compliance -* style: Checkstyle, unused import -* feat: allow `S_CHAR_LITERAL` to break lines -* - fixes #875 +* refactor: Merge REPLACE into UPSERT +* fixes #1706 +* feat: `DROP TEMPORARY TABLE ...` +* fixes #1712 +* build: PMD compliance +* ci: Merge master +* feat: Configurable backslash `\` escaping +* - Enables `\` as escape character in String Literals (beside SQL:2016 compliant `'`) +* - Default is OFF (since its not SQL:2016 compliant) +* - Activate per Parser Feature +* - Fixes #1638 +* - Fixes #1209 +* - Fixes #1173 +* - Fixes #1172 +* - Fixes #832 +* - Fixes #827 +* - Fixes #578 +* BREAKING-CHANGE: Backslash Escaping needs to be activated explicitly or else Backslash won't work as Escape Character. +* style: Checkstyle +* style: remove dead code +* style: PMD compliance +* style: Checkstyle, unused import +* feat: allow `S_CHAR_LITERAL` to break lines +* - fixes #875 [a00d7](https://github.com/JSQLParser/JSqlParser/commit/a00d77a100bfab7) manticore-projects *2023-01-20 21:32:20* @@ -1812,7 +2684,7 @@ Changelog of JSqlParser. **Update README.md** -* lgtm removed +* lgtm removed [954b8](https://github.com/JSQLParser/JSqlParser/commit/954b8dd2e760a01) Tobias *2022-12-27 10:34:18* @@ -1823,69 +2695,69 @@ Changelog of JSqlParser. **Assorted Fixes #4 (#1676)** -* support clickhouse global keyword in join -* fix: add missing public Getter -* Add public Getter for `updateSets` -* Fixes #1630 -* feat: Clickhouse GLOBAL JOIN -* All credits to @julianzlzhang -* fixes #1615 -* fixes #1535 -* feat: IF/ELSE statements supports Block -* Make `If... Else...` statements work with Blocks -* Make `Statement()` production work with `Block()` -* Rewrite the `Block()` related Unit Tests -* fixes #1682 -* fix: Revert unintended changes to the Special Oracle Tests -* fix: `SET` statement supports `UserVariable` -* Make `SetStatement` parse Objects instead of Names only -* Add Grammar to accept `UserVariable` (e.g. "set @Flag = 1") -* Add Test Case for `UserVariable` -* fixes #1682 -* feat: Google Spanner Support -* Replaces PR #1415, all credit goes to @s13o -* Re-arranged some recently added Tokens in alphabetical order -* Update Keywords -* fix: fix JSonExpression, accept Expressions -* Make JSonExpression accept Expressions -* Add Testcase -* Expose Idents() and Operators() -* Fixes #1696 -* test: add Test for Issue #1237 -* Co-authored-by: Zhang Zhongliang <zhangzhongliang@xiaomi.com> +* support clickhouse global keyword in join +* fix: add missing public Getter +* Add public Getter for `updateSets` +* Fixes #1630 +* feat: Clickhouse GLOBAL JOIN +* All credits to @julianzlzhang +* fixes #1615 +* fixes #1535 +* feat: IF/ELSE statements supports Block +* Make `If... Else...` statements work with Blocks +* Make `Statement()` production work with `Block()` +* Rewrite the `Block()` related Unit Tests +* fixes #1682 +* fix: Revert unintended changes to the Special Oracle Tests +* fix: `SET` statement supports `UserVariable` +* Make `SetStatement` parse Objects instead of Names only +* Add Grammar to accept `UserVariable` (e.g. "set @Flag = 1") +* Add Test Case for `UserVariable` +* fixes #1682 +* feat: Google Spanner Support +* Replaces PR #1415, all credit goes to @s13o +* Re-arranged some recently added Tokens in alphabetical order +* Update Keywords +* fix: fix JSonExpression, accept Expressions +* Make JSonExpression accept Expressions +* Add Testcase +* Expose Idents() and Operators() +* Fixes #1696 +* test: add Test for Issue #1237 +* Co-authored-by: Zhang Zhongliang <zhangzhongliang@xiaomi.com> [8d9db](https://github.com/JSQLParser/JSqlParser/commit/8d9db7052c3aeb5) manticore-projects *2022-12-22 21:17:55* **Fixed download war script in the renderRR task (#1659)** -* Co-authored-by: Hai Chang <haichang@microsoft.com> +* Co-authored-by: Hai Chang <haichang@microsoft.com> [08a92](https://github.com/JSQLParser/JSqlParser/commit/08a92fcd7b4f7f2) haha1903 *2022-12-10 09:23:53* **Assorted fixes (#1666)** -* fix: add missing public Getter -* Add public Getter for `updateSets` -* Fixes #1630 -* feat: LISTAGG() with OVER() clause -* fixes issue #1652 -* fixes 3 more Special Oracle Tests -* fix: White-list CURRENT_DATE and CURRENT_TIMESTAMP tokens -* allows CURRENT_DATE(3) and CURRENT_TIMESTAMP(3) as regular functions -* fixes #1507 -* fixes #1607 -* feat: Deparser for Expression Lists -* Visit each Expression of a List instead ExpressionList.toString() -* fixes #1608 -* fix: Lookahead needed +* fix: add missing public Getter +* Add public Getter for `updateSets` +* Fixes #1630 +* feat: LISTAGG() with OVER() clause +* fixes issue #1652 +* fixes 3 more Special Oracle Tests +* fix: White-list CURRENT_DATE and CURRENT_TIMESTAMP tokens +* allows CURRENT_DATE(3) and CURRENT_TIMESTAMP(3) as regular functions +* fixes #1507 +* fixes #1607 +* feat: Deparser for Expression Lists +* Visit each Expression of a List instead ExpressionList.toString() +* fixes #1608 +* fix: Lookahead needed [bff26](https://github.com/JSQLParser/JSqlParser/commit/bff268a7c699947) manticore-projects *2022-11-20 10:06:01* **Fix parsing statements with multidimensional array PR2 (#1665)** -* Fix parsing statements with multidimensional array -* fix: Whitelist LOCKED keyword -* Co-authored-by: Andrei Lisouski <alisousk@akamai.com> +* Fix parsing statements with multidimensional array +* fix: Whitelist LOCKED keyword +* Co-authored-by: Andrei Lisouski <alisousk@akamai.com> [e1865](https://github.com/JSQLParser/JSqlParser/commit/e186588f044753f) manticore-projects *2022-11-20 09:59:26* @@ -1911,83 +2783,83 @@ Changelog of JSqlParser. **Enhanced Keywords (#1382)** -* Enhanced Keywords -* Add Keywords and document, which keywords are allowed for what purpose -* Fix incorrect tests -* Define Reserved Keywords explicitly -* Derive All Keywords from Grammar directly -* Generate production for Object Names (semi-) automatically -* Add parametrized Keyword Tests -* Fix test resources -* Adjust Gradle to JUnit 5 -* Parallel Test execution -* Gradle Caching -* Explicitly request for latest JavaCC 7.0.10 -* Do not mark SpeedTest for concurrent execution -* Remove unused imports -* Adjust Gradle to JUnit 5 -* Parallel Test execution -* Gradle Caching -* Explicitly request for latest JavaCC 7.0.10 -* Do not mark SpeedTest for concurrent execution -* Remove unused imports -* Keyword test adopt JUnit5 -* Update keywords -* CheckStyle sanitation of method names -* Merge Master -* Add Jupiter Parameters dependency again -* Automate the `updateKeywords` Step -* Update PMD and rules -* Rewrite test expected to fail -* Appease Codacy -* Remove broken rule warning about perfectly fine switch-case statements -* Force Changes -* Fix Merge Issues -* Read Tokens directly from the Grammar File without invoking JTREE -* - read Tokens per REGEX Matcher -* - move Reserved Keywords from Grammar into ParserKeywordsUtils -* - adjust the Tests -* Appease PMD/Codacy -* Extract the Keywords from the Grammar by using JTRee (instead of Regex) -* Add some tests to ensure, that all Keywords or found -* Appease Codacy/PMD -* Separate UpdateKeywords Task again -* Including it into compileJavacc won't work since it depends on compiling the ParserKeywordUtils.java -* Single file compilation did not work -* Clean-up the imports -* Add JavaCC dependency to Maven for building ParserKeywordsUtils -* Add JavaCC dependency to Maven for building ParserKeywordsUtils -* Merge Upstream -* Merge Master -* Fixes broken PR #1524 and Commit fb6e950ce0e62ebcd7a44ba9eea679da2b04b2ed -* Add AST Visualization -* Show the Statement's Java Objects in a tree hierarchy -* build: temporarily reduce the Code Coverage requirements -* Temporarily reduce the Coverage checks regarding Minimum Coverage and Maximum Missed Lines in order to get the Keywords PR accepted. We should do a major Code cleanup afterwards. -* build: JSQLParser is a build dependency -* chore: Update keywords -* feat: add line count to output +* Enhanced Keywords +* Add Keywords and document, which keywords are allowed for what purpose +* Fix incorrect tests +* Define Reserved Keywords explicitly +* Derive All Keywords from Grammar directly +* Generate production for Object Names (semi-) automatically +* Add parametrized Keyword Tests +* Fix test resources +* Adjust Gradle to JUnit 5 +* Parallel Test execution +* Gradle Caching +* Explicitly request for latest JavaCC 7.0.10 +* Do not mark SpeedTest for concurrent execution +* Remove unused imports +* Adjust Gradle to JUnit 5 +* Parallel Test execution +* Gradle Caching +* Explicitly request for latest JavaCC 7.0.10 +* Do not mark SpeedTest for concurrent execution +* Remove unused imports +* Keyword test adopt JUnit5 +* Update keywords +* CheckStyle sanitation of method names +* Merge Master +* Add Jupiter Parameters dependency again +* Automate the `updateKeywords` Step +* Update PMD and rules +* Rewrite test expected to fail +* Appease Codacy +* Remove broken rule warning about perfectly fine switch-case statements +* Force Changes +* Fix Merge Issues +* Read Tokens directly from the Grammar File without invoking JTREE +* - read Tokens per REGEX Matcher +* - move Reserved Keywords from Grammar into ParserKeywordsUtils +* - adjust the Tests +* Appease PMD/Codacy +* Extract the Keywords from the Grammar by using JTRee (instead of Regex) +* Add some tests to ensure, that all Keywords or found +* Appease Codacy/PMD +* Separate UpdateKeywords Task again +* Including it into compileJavacc won't work since it depends on compiling the ParserKeywordUtils.java +* Single file compilation did not work +* Clean-up the imports +* Add JavaCC dependency to Maven for building ParserKeywordsUtils +* Add JavaCC dependency to Maven for building ParserKeywordsUtils +* Merge Upstream +* Merge Master +* Fixes broken PR #1524 and Commit fb6e950ce0e62ebcd7a44ba9eea679da2b04b2ed +* Add AST Visualization +* Show the Statement's Java Objects in a tree hierarchy +* build: temporarily reduce the Code Coverage requirements +* Temporarily reduce the Coverage checks regarding Minimum Coverage and Maximum Missed Lines in order to get the Keywords PR accepted. We should do a major Code cleanup afterwards. +* build: JSQLParser is a build dependency +* chore: Update keywords +* feat: add line count to output [4863e](https://github.com/JSQLParser/JSqlParser/commit/4863eb5a8e30a5d) manticore-projects *2022-10-25 23:15:32* **#1610 Support for SKIP LOCKED tokens on SELECT statements (#1649)** -* Co-authored-by: Lucas Dillmann <lucas.dillmann@totvs.com.br> +* Co-authored-by: Lucas Dillmann <lucas.dillmann@totvs.com.br> [e6d50](https://github.com/JSQLParser/JSqlParser/commit/e6d50f756e99846) Lucas Dillmann *2022-10-25 22:59:09* **Assorted fixes (#1646)** -* fix: add missing public Getter -* Add public Getter for `updateSets` -* Fixes #1630 -* fix: Assorted Fixes -* SelectExpressionItem with Function and Complex Parameters -* Tables with Oracle DB Links -* Make Table Name Parts accessible -* Fixes #1644 -* Fixes #1643 -* fix: Revert correct test case +* fix: add missing public Getter +* Add public Getter for `updateSets` +* Fixes #1630 +* fix: Assorted Fixes +* SelectExpressionItem with Function and Complex Parameters +* Tables with Oracle DB Links +* Make Table Name Parts accessible +* Fixes #1644 +* Fixes #1643 +* fix: Revert correct test case [15ff8](https://github.com/JSQLParser/JSqlParser/commit/15ff84348228278) manticore-projects *2022-10-16 20:15:36* @@ -1998,30 +2870,30 @@ Changelog of JSqlParser. **Bump h2 from 1.4.200 to 2.1.210 (#1639)** -* Bumps [h2](https://github.com/h2database/h2database) from 1.4.200 to 2.1.210. -* - [Release notes](https://github.com/h2database/h2database/releases) -* - [Commits](https://github.com/h2database/h2database/compare/version-1.4.200...version-2.1.210) -* --- -* updated-dependencies: -* - dependency-name: com.h2database:h2 -* dependency-type: direct:development -* ... -* Signed-off-by: dependabot[bot] <support@github.com> -* Signed-off-by: dependabot[bot] <support@github.com> -* Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> +* Bumps [h2](https://github.com/h2database/h2database) from 1.4.200 to 2.1.210. +* - [Release notes](https://github.com/h2database/h2database/releases) +* - [Commits](https://github.com/h2database/h2database/compare/version-1.4.200...version-2.1.210) +* --- +* updated-dependencies: +* - dependency-name: com.h2database:h2 +* dependency-type: direct:development +* ... +* Signed-off-by: dependabot[bot] <support@github.com> +* Signed-off-by: dependabot[bot] <support@github.com> +* Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> [fc3c4](https://github.com/JSQLParser/JSqlParser/commit/fc3c4cfd6b1eda9) dependabot[bot] *2022-09-28 19:52:31* **Support BigQuery SAFE_CAST (#1622) (#1634)** -* Co-authored-by: Zhang, Dequn <deqzhang@paypal.com> +* Co-authored-by: Zhang, Dequn <deqzhang@paypal.com> [d9985](https://github.com/JSQLParser/JSqlParser/commit/d9985ae4f559cda) dequn *2022-09-20 18:22:25* **Support timestamptz dateliteral (#1621)** -* support timestamptz as datetime literal -* rename test +* support timestamptz as datetime literal +* rename test [81a64](https://github.com/JSQLParser/JSqlParser/commit/81a648eba8db92d) Todd Pollak *2022-08-31 20:31:44* @@ -2042,25 +2914,25 @@ Changelog of JSqlParser. **Fixes PR #1524 support hive alter sql (#1609)** -* Adjust Gradle to JUnit 5 -* Parallel Test execution -* Gradle Caching -* Explicitly request for latest JavaCC 7.0.10 -* Do not mark SpeedTest for concurrent execution -* Remove unused imports -* Adjust Gradle to JUnit 5 -* Parallel Test execution -* Gradle Caching -* Explicitly request for latest JavaCC 7.0.10 -* Do not mark SpeedTest for concurrent execution -* Remove unused imports -* Fixes broken PR #1524 and Commit fb6e950ce0e62ebcd7a44ba9eea679da2b04b2ed +* Adjust Gradle to JUnit 5 +* Parallel Test execution +* Gradle Caching +* Explicitly request for latest JavaCC 7.0.10 +* Do not mark SpeedTest for concurrent execution +* Remove unused imports +* Adjust Gradle to JUnit 5 +* Parallel Test execution +* Gradle Caching +* Explicitly request for latest JavaCC 7.0.10 +* Do not mark SpeedTest for concurrent execution +* Remove unused imports +* Fixes broken PR #1524 and Commit fb6e950ce0e62ebcd7a44ba9eea679da2b04b2ed [2619c](https://github.com/JSQLParser/JSqlParser/commit/2619ce0a6fd8bd5) manticore-projects *2022-08-14 16:29:18* **#1524 support hive alter sql : ALTER TABLE name ADD COLUMNS (col_spec[, col_spec ...]) (#1605)** -* Co-authored-by: zhum@aotain.com <zm7705264> +* Co-authored-by: zhum@aotain.com <zm7705264> [fb6e9](https://github.com/JSQLParser/JSqlParser/commit/fb6e950ce0e62eb) Zhumin-lv-wn *2022-08-03 20:56:44* @@ -2116,55 +2988,55 @@ Changelog of JSqlParser. **PostgreSQL INSERT ... ON CONFLICT Issue #1551 (#1552)** -* Adjust Gradle to JUnit 5 -* Parallel Test execution -* Gradle Caching -* Explicitly request for latest JavaCC 7.0.10 -* Do not mark SpeedTest for concurrent execution -* Remove unused imports -* Adjust Gradle to JUnit 5 -* Parallel Test execution -* Gradle Caching -* Explicitly request for latest JavaCC 7.0.10 -* Do not mark SpeedTest for concurrent execution -* Remove unused imports -* Adjust Gradle to JUnit 5 -* Parallel Test execution -* Gradle Caching -* Explicitly request for latest JavaCC 7.0.10 -* Do not mark SpeedTest for concurrent execution -* Remove unused imports -* Adjust Gradle to JUnit 5 -* Parallel Test execution -* Gradle Caching -* Explicitly request for latest JavaCC 7.0.10 -* Do not mark SpeedTest for concurrent execution -* Remove unused imports -* Support Postgres INSERT ... ON CONFLICT -* Fixes #1551 -* Refactor UpdateSet.toString(), which is used by Insert and Update -* Allow KEEP keyword -* Enables special Oracle Test keywordasidentifier04.sql, now 191 tests succeed -* Sanitize before push -* Tweak Grammar in order to survive the Maven Build -* Ammend the README -* Move Plugin configuration files to the CONFIG folder (hoping, that Codacy will find it there) -* Update PMD in the Maven configuration -* Update PMD in the Maven and Gradle configuration -* Appease Codacy -* Co-authored-by: Tobias <t.warneke@gmx.net> +* Adjust Gradle to JUnit 5 +* Parallel Test execution +* Gradle Caching +* Explicitly request for latest JavaCC 7.0.10 +* Do not mark SpeedTest for concurrent execution +* Remove unused imports +* Adjust Gradle to JUnit 5 +* Parallel Test execution +* Gradle Caching +* Explicitly request for latest JavaCC 7.0.10 +* Do not mark SpeedTest for concurrent execution +* Remove unused imports +* Adjust Gradle to JUnit 5 +* Parallel Test execution +* Gradle Caching +* Explicitly request for latest JavaCC 7.0.10 +* Do not mark SpeedTest for concurrent execution +* Remove unused imports +* Adjust Gradle to JUnit 5 +* Parallel Test execution +* Gradle Caching +* Explicitly request for latest JavaCC 7.0.10 +* Do not mark SpeedTest for concurrent execution +* Remove unused imports +* Support Postgres INSERT ... ON CONFLICT +* Fixes #1551 +* Refactor UpdateSet.toString(), which is used by Insert and Update +* Allow KEEP keyword +* Enables special Oracle Test keywordasidentifier04.sql, now 191 tests succeed +* Sanitize before push +* Tweak Grammar in order to survive the Maven Build +* Ammend the README +* Move Plugin configuration files to the CONFIG folder (hoping, that Codacy will find it there) +* Update PMD in the Maven configuration +* Update PMD in the Maven and Gradle configuration +* Appease Codacy +* Co-authored-by: Tobias <t.warneke@gmx.net> [5ae09](https://github.com/JSQLParser/JSqlParser/commit/5ae09ad097c7294) manticore-projects *2022-07-19 21:18:02* **Configurable Parser Timeout via Feature (#1592)** -* Configurable Parser Timeout via Feature -* Fixes #1582 -* Implement Parser Timeout Feature, e. g. `CCJSqlParserUtil.parse(sqlStr, parser -> parser.withTimeOut(60000));` -* Add a special test failing after a long time only, to test TimeOut vs. Parser Exception -* Appease Codacy -* Appease Codacy -* Co-authored-by: Tobias <t.warneke@gmx.net> +* Configurable Parser Timeout via Feature +* Fixes #1582 +* Implement Parser Timeout Feature, e. g. `CCJSqlParserUtil.parse(sqlStr, parser -> parser.withTimeOut(60000));` +* Add a special test failing after a long time only, to test TimeOut vs. Parser Exception +* Appease Codacy +* Appease Codacy +* Co-authored-by: Tobias <t.warneke@gmx.net> [74000](https://github.com/JSQLParser/JSqlParser/commit/74000130e850788) manticore-projects *2022-07-19 20:48:49* @@ -2180,10 +3052,10 @@ Changelog of JSqlParser. **extended support Postgres' `Extract( field FROM source)` where `field` is a String instead of a Keyword (#1591)** -* Fixes #1582 -* Amend the ExtractExpression -* Add Test case for issue #1582 -* Amend the README +* Fixes #1582 +* Amend the ExtractExpression +* Add Test case for issue #1582 +* Amend the README [2b3ce](https://github.com/JSQLParser/JSqlParser/commit/2b3ce25a23b264a) manticore-projects *2022-07-19 19:25:23* @@ -2214,9 +3086,9 @@ Changelog of JSqlParser. **Closes #1583:: Implement Postgresql optional TABLE in TRUNCATE (#1585)** -* Closes #1583 -* Closes #1583, removed unnecessary local variable. -* Closes #1583, proper support for deparsing. +* Closes #1583 +* Closes #1583, removed unnecessary local variable. +* Closes #1583, proper support for deparsing. [26248](https://github.com/JSQLParser/JSqlParser/commit/262482610b80d18) Rob Audenaerde *2022-07-14 18:55:44* @@ -2227,10 +3099,10 @@ Changelog of JSqlParser. **Support table option character set and index options (#1586)** -* Support table option character set and index options -* Signed-off-by: luofei <luoffei@outlook.com> -* move test -* Signed-off-by: luofei <luoffei@outlook.com> +* Support table option character set and index options +* Signed-off-by: luofei <luoffei@outlook.com> +* move test +* Signed-off-by: luofei <luoffei@outlook.com> [27cdf](https://github.com/JSQLParser/JSqlParser/commit/27cdfa9ca1237f6) luofei *2022-07-14 18:46:14* @@ -2266,8 +3138,8 @@ Changelog of JSqlParser. **Add test for LikeExpression.setEscape and LikeExpression.getStringExpression (#1568)** -* Add test for LikeExpression.setEscape and LikeExpression.getStringExpression -* like + set escape test for $ as escape character +* Add test for LikeExpression.setEscape and LikeExpression.getStringExpression +* like + set escape test for $ as escape character [bcf6f](https://github.com/JSQLParser/JSqlParser/commit/bcf6ff4157277f9) Caro *2022-07-07 19:27:43* @@ -2288,8 +3160,8 @@ Changelog of JSqlParser. **Add support for Hive dialect GROUPING SETS. (#1539)** -* Add support for Hive GROUPING SETS dialect `GROUP BY a, b, c GROUPING SETS ((a, b), (a, c))` -* Simplify HiveTest::testGroupByGroupingSets. +* Add support for Hive GROUPING SETS dialect `GROUP BY a, b, c GROUPING SETS ((a, b), (a, c))` +* Simplify HiveTest::testGroupByGroupingSets. [03c58](https://github.com/JSQLParser/JSqlParser/commit/03c58de9d341a13) chenwl *2022-07-06 19:40:41* @@ -2300,13 +3172,13 @@ Changelog of JSqlParser. **Postgres NATURAL LEFT/RIGHT joins (#1560)** -* Postgres NATURAL LEFT/RIGHT joins -* Fixes #1559 -* Make NATURAL an optional Join Keyword, which can be combined with LEFT, RIGHT, INNER -* Add tests -* Postgres NATURAL LEFT/RIGHT joins -* Amend readme -* Revert successful Oracle test +* Postgres NATURAL LEFT/RIGHT joins +* Fixes #1559 +* Make NATURAL an optional Join Keyword, which can be combined with LEFT, RIGHT, INNER +* Add tests +* Postgres NATURAL LEFT/RIGHT joins +* Amend readme +* Revert successful Oracle test [74a0f](https://github.com/JSQLParser/JSqlParser/commit/74a0f2fb22e24fe) manticore-projects *2022-06-28 20:15:34* @@ -2327,7 +3199,7 @@ Changelog of JSqlParser. **Create maven.yml** -* started maven build using github actions +* started maven build using github actions [b7e5c](https://github.com/JSQLParser/JSqlParser/commit/b7e5c151df37f5e) Tobias *2022-05-16 09:24:24* @@ -2343,40 +3215,40 @@ Changelog of JSqlParser. **INSERT with SetOperations (#1531)** -* INSERT with SetOperations -* Simplify the INSERT production -* Use SetOperations for Select and Values -* Better Bracket handling for WITH ... SELECT ... -* Fixes #1491 -* INSERT with SetOperations -* Appease Codazy/PMD -* INSERT with SetOperations -* Appease Codazy/PMD -* Update Readme -* List the changes -* Minor rephrases -* Correct the Maven Artifact Example -* Fix the two test cases (missing white space) -* Remove unused import +* INSERT with SetOperations +* Simplify the INSERT production +* Use SetOperations for Select and Values +* Better Bracket handling for WITH ... SELECT ... +* Fixes #1491 +* INSERT with SetOperations +* Appease Codazy/PMD +* INSERT with SetOperations +* Appease Codazy/PMD +* Update Readme +* List the changes +* Minor rephrases +* Correct the Maven Artifact Example +* Fix the two test cases (missing white space) +* Remove unused import [b5672](https://github.com/JSQLParser/JSqlParser/commit/b5672c54386cdf8) manticore-projects *2022-05-15 20:29:06* **#1516 rename without column keyword (#1533)** -* Adjust Gradle to JUnit 5 -* Parallel Test execution -* Gradle Caching -* Explicitly request for latest JavaCC 7.0.10 -* Do not mark SpeedTest for concurrent execution -* Remove unused imports -* Adjust Gradle to JUnit 5 -* Parallel Test execution -* Gradle Caching -* Explicitly request for latest JavaCC 7.0.10 -* Do not mark SpeedTest for concurrent execution -* Remove unused imports -* `RENAME ... TO ...` without `COLUMN` keyword -* Fixes #1516 +* Adjust Gradle to JUnit 5 +* Parallel Test execution +* Gradle Caching +* Explicitly request for latest JavaCC 7.0.10 +* Do not mark SpeedTest for concurrent execution +* Remove unused imports +* Adjust Gradle to JUnit 5 +* Parallel Test execution +* Gradle Caching +* Explicitly request for latest JavaCC 7.0.10 +* Do not mark SpeedTest for concurrent execution +* Remove unused imports +* `RENAME ... TO ...` without `COLUMN` keyword +* Fixes #1516 [e8f07](https://github.com/JSQLParser/JSqlParser/commit/e8f0750d75e74c7) manticore-projects *2022-05-11 20:44:34* @@ -2392,16 +3264,16 @@ Changelog of JSqlParser. **#1527 DELETE ... RETURNING ... (#1528)** -* #1527 DELETE ... RETURNING ... -* Fixes #1527 -* Add DELETE... RETURNING ... expression -* Simplify INSERT ... RETURNING ... expression -* Simply UPDATE ... RETURNING ... expression -* TSQL Output Clause -* According to https://docs.microsoft.com/en-us/sql/t-sql/queries/output-clause-transact-sql?view=sql-server-ver15 -* Implement Output Clause for INSERT, UPDATE and DELETE -* Add Tests according the Microsoft Documentation -* Appease Codacy/PMD +* #1527 DELETE ... RETURNING ... +* Fixes #1527 +* Add DELETE... RETURNING ... expression +* Simplify INSERT ... RETURNING ... expression +* Simply UPDATE ... RETURNING ... expression +* TSQL Output Clause +* According to https://docs.microsoft.com/en-us/sql/t-sql/queries/output-clause-transact-sql?view=sql-server-ver15 +* Implement Output Clause for INSERT, UPDATE and DELETE +* Add Tests according the Microsoft Documentation +* Appease Codacy/PMD [4d815](https://github.com/JSQLParser/JSqlParser/commit/4d8152159454069) manticore-projects *2022-05-11 20:04:23* @@ -2417,37 +3289,37 @@ Changelog of JSqlParser. **Unsupported statement (#1519)** -* Adjust Gradle to JUnit 5 -* Parallel Test execution -* Gradle Caching -* Explicitly request for latest JavaCC 7.0.10 -* Do not mark SpeedTest for concurrent execution -* Remove unused imports -* Adjust Gradle to JUnit 5 -* Parallel Test execution -* Gradle Caching -* Explicitly request for latest JavaCC 7.0.10 -* Do not mark SpeedTest for concurrent execution -* Remove unused imports -* Adjust Gradle to JUnit 5 -* Parallel Test execution -* Gradle Caching -* Explicitly request for latest JavaCC 7.0.10 -* Do not mark SpeedTest for concurrent execution -* Remove unused imports -* Adjust Gradle to JUnit 5 -* Parallel Test execution -* Gradle Caching -* Explicitly request for latest JavaCC 7.0.10 -* Do not mark SpeedTest for concurrent execution -* Remove unused imports -* Implement UnsupportedStatement -* - Add Feature allowUnsupportedStatement, default=false -* - Fully implement UnsupportedStatement for the Statement() production -* - Partially implement UnsupportedStatement for the Statements() production, works only when UnsupportedStatement comes first -* Revert unintended changes of the test resources -* Reformat BLOCK production -* Disable STATEMENTS() test, which will never fail and add comments to this regard +* Adjust Gradle to JUnit 5 +* Parallel Test execution +* Gradle Caching +* Explicitly request for latest JavaCC 7.0.10 +* Do not mark SpeedTest for concurrent execution +* Remove unused imports +* Adjust Gradle to JUnit 5 +* Parallel Test execution +* Gradle Caching +* Explicitly request for latest JavaCC 7.0.10 +* Do not mark SpeedTest for concurrent execution +* Remove unused imports +* Adjust Gradle to JUnit 5 +* Parallel Test execution +* Gradle Caching +* Explicitly request for latest JavaCC 7.0.10 +* Do not mark SpeedTest for concurrent execution +* Remove unused imports +* Adjust Gradle to JUnit 5 +* Parallel Test execution +* Gradle Caching +* Explicitly request for latest JavaCC 7.0.10 +* Do not mark SpeedTest for concurrent execution +* Remove unused imports +* Implement UnsupportedStatement +* - Add Feature allowUnsupportedStatement, default=false +* - Fully implement UnsupportedStatement for the Statement() production +* - Partially implement UnsupportedStatement for the Statements() production, works only when UnsupportedStatement comes first +* Revert unintended changes of the test resources +* Reformat BLOCK production +* Disable STATEMENTS() test, which will never fail and add comments to this regard [59bb9](https://github.com/JSQLParser/JSqlParser/commit/59bb9a4e40753cf) manticore-projects *2022-05-11 19:23:35* @@ -2458,8 +3330,8 @@ Changelog of JSqlParser. **Update bug_report.md (#1512)** -* Focus more on the particular SQL Statement and the JSQLParser Version. -* Link to the Online Formatter for testing. +* Focus more on the particular SQL Statement and the JSQLParser Version. +* Link to the Online Formatter for testing. [13441](https://github.com/JSQLParser/JSqlParser/commit/13441f47fbd8023) manticore-projects *2022-04-22 22:29:07* @@ -2470,40 +3342,40 @@ Changelog of JSqlParser. **Performance Improvements (#1439)** -* Adjust Gradle to JUnit 5 -* Parallel Test execution -* Gradle Caching -* Explicitly request for latest JavaCC 7.0.10 -* Do not mark SpeedTest for concurrent execution -* Remove unused imports -* Adjust Gradle to JUnit 5 -* Parallel Test execution -* Gradle Caching -* Explicitly request for latest JavaCC 7.0.10 -* Do not mark SpeedTest for concurrent execution -* Remove unused imports -* Performance Improvements -* Simplify the Primary Expression Production -* Try to simple parse without Complex Expressions first, before parsing complex and slow (if supported by max nesting depth) -* Add Test cases for issues #1397 and #1438 -* Update Libraries to its latest version -* Remove JUnit 4 from Gradle -* Appease PMD -* Update Gradle Plugins to its latest versions -* Let Parser timeout after 6 seconds and fail gently -* Add a special test verifying the clean up after timeout -* Revert unintended changes to the Test Resources -* Appease PMD/Codacy -* Correct the Gradle "+" dependencies -* Bump version to 4.4.-SNAPSHOT -* update build file -* revert unwarranted changes in test files -* strip the Exception Class Name from the Message -* maxDepth = 10 collides with the Parser Timeout = 6 seconds -* License Headers -* Unused imports -* Bump version to 4.5-SNAPSHOT -* Reduce test loops to fit intothe timeout +* Adjust Gradle to JUnit 5 +* Parallel Test execution +* Gradle Caching +* Explicitly request for latest JavaCC 7.0.10 +* Do not mark SpeedTest for concurrent execution +* Remove unused imports +* Adjust Gradle to JUnit 5 +* Parallel Test execution +* Gradle Caching +* Explicitly request for latest JavaCC 7.0.10 +* Do not mark SpeedTest for concurrent execution +* Remove unused imports +* Performance Improvements +* Simplify the Primary Expression Production +* Try to simple parse without Complex Expressions first, before parsing complex and slow (if supported by max nesting depth) +* Add Test cases for issues #1397 and #1438 +* Update Libraries to its latest version +* Remove JUnit 4 from Gradle +* Appease PMD +* Update Gradle Plugins to its latest versions +* Let Parser timeout after 6 seconds and fail gently +* Add a special test verifying the clean up after timeout +* Revert unintended changes to the Test Resources +* Appease PMD/Codacy +* Correct the Gradle "+" dependencies +* Bump version to 4.4.-SNAPSHOT +* update build file +* revert unwarranted changes in test files +* strip the Exception Class Name from the Message +* maxDepth = 10 collides with the Parser Timeout = 6 seconds +* License Headers +* Unused imports +* Bump version to 4.5-SNAPSHOT +* Reduce test loops to fit intothe timeout [181a2](https://github.com/JSQLParser/JSqlParser/commit/181a21ab90870e1) manticore-projects *2022-04-14 21:18:18* @@ -2519,31 +3391,31 @@ Changelog of JSqlParser. **Json function Improvements (#1506)** -* Adjust Gradle to JUnit 5 -* Parallel Test execution -* Gradle Caching -* Explicitly request for latest JavaCC 7.0.10 -* Do not mark SpeedTest for concurrent execution -* Remove unused imports -* Adjust Gradle to JUnit 5 -* Parallel Test execution -* Gradle Caching -* Explicitly request for latest JavaCC 7.0.10 -* Do not mark SpeedTest for concurrent execution -* Remove unused imports -* Improve JSON Functions -* Space around the `:` delimiter of JSON Functions -* Improve JSON Functions -* Enforce `KEY` as `S_CHAR_LITERAL` -* Allow `Column` as `VALUE` -* Temporarily disable Postgres Syntax -* Improve JSON Functions -* Bring back Postgres Syntax -* Enable MySQL Syntax JSON_OBJECT(key, value [, key, value, ...]) -* Fix some more tests, where key was not a String -* Appease Codacy -* Let JSON_OBJECT accept Expressions as value -* set Version = 4.4-SNAPSHOT +* Adjust Gradle to JUnit 5 +* Parallel Test execution +* Gradle Caching +* Explicitly request for latest JavaCC 7.0.10 +* Do not mark SpeedTest for concurrent execution +* Remove unused imports +* Adjust Gradle to JUnit 5 +* Parallel Test execution +* Gradle Caching +* Explicitly request for latest JavaCC 7.0.10 +* Do not mark SpeedTest for concurrent execution +* Remove unused imports +* Improve JSON Functions +* Space around the `:` delimiter of JSON Functions +* Improve JSON Functions +* Enforce `KEY` as `S_CHAR_LITERAL` +* Allow `Column` as `VALUE` +* Temporarily disable Postgres Syntax +* Improve JSON Functions +* Bring back Postgres Syntax +* Enable MySQL Syntax JSON_OBJECT(key, value [, key, value, ...]) +* Fix some more tests, where key was not a String +* Appease Codacy +* Let JSON_OBJECT accept Expressions as value +* set Version = 4.4-SNAPSHOT [e3f53](https://github.com/JSQLParser/JSqlParser/commit/e3f531caf7ad9ba) manticore-projects *2022-04-09 22:37:36* @@ -2564,25 +3436,25 @@ Changelog of JSqlParser. **Issue1500 - Circular References in `AllColumns` and `AllTableColumns` (#1501)** -* Adjust Gradle to JUnit 5 -* Parallel Test execution -* Gradle Caching -* Explicitly request for latest JavaCC 7.0.10 -* Do not mark SpeedTest for concurrent execution -* Remove unused imports -* Adjust Gradle to JUnit 5 -* Parallel Test execution -* Gradle Caching -* Explicitly request for latest JavaCC 7.0.10 -* Do not mark SpeedTest for concurrent execution -* Remove unused imports -* Remove circular reference revealed by issue #1500 -* Add test for Issue 1500 Circular reference for All Columns Expression -* Fix Test case -* Add Test for AllTableColumn due to similar circular reference -* Remove similar circular reference from AllTableColumn -* Update dependencies -* Adjust Jacoco Missed Lines count +* Adjust Gradle to JUnit 5 +* Parallel Test execution +* Gradle Caching +* Explicitly request for latest JavaCC 7.0.10 +* Do not mark SpeedTest for concurrent execution +* Remove unused imports +* Adjust Gradle to JUnit 5 +* Parallel Test execution +* Gradle Caching +* Explicitly request for latest JavaCC 7.0.10 +* Do not mark SpeedTest for concurrent execution +* Remove unused imports +* Remove circular reference revealed by issue #1500 +* Add test for Issue 1500 Circular reference for All Columns Expression +* Fix Test case +* Add Test for AllTableColumn due to similar circular reference +* Remove similar circular reference from AllTableColumn +* Update dependencies +* Adjust Jacoco Missed Lines count [0949d](https://github.com/JSQLParser/JSqlParser/commit/0949df9d789123c) manticore-projects *2022-04-03 18:51:35* @@ -2593,26 +3465,26 @@ Changelog of JSqlParser. **Optimize assertCanBeParsedAndDeparsed (#1389)** -* Optimize assertCanBeParsedAndDeparsed -* - Avoid redundant calls of buildSqlString() -* - Replace String.replaceAll() with Matcher.replaceAll() based on precompiled Regex Patterns -* Reset the testcase results +* Optimize assertCanBeParsedAndDeparsed +* - Avoid redundant calls of buildSqlString() +* - Replace String.replaceAll() with Matcher.replaceAll() based on precompiled Regex Patterns +* Reset the testcase results [ea316](https://github.com/JSQLParser/JSqlParser/commit/ea3164a1e418f3b) manticore-projects *2022-04-02 22:40:09* **Add geometry distance operator (#1493)** -* Add support for geometry distance operators in PostGIS. -* Fix missing imports. -* Co-authored-by: Thomas Powell <tpowell@palantir.com> +* Add support for geometry distance operators in PostGIS. +* Fix missing imports. +* Co-authored-by: Thomas Powell <tpowell@palantir.com> [98c47](https://github.com/JSQLParser/JSqlParser/commit/98c476a6c9fa1a1) Thomas Powell *2022-04-02 22:31:08* **Support WITH TIES option in TOP #1435 (#1479)** -* Support WITH TIES option in TOP -* - Add the support of WITH TIES option in SELECT TOP statement. -* add specific test +* Support WITH TIES option in TOP +* - Add the support of WITH TIES option in SELECT TOP statement. +* add specific test [1756a](https://github.com/JSQLParser/JSqlParser/commit/1756adcade48fb4) Olivier Cavadenti *2022-04-02 21:26:54* @@ -2628,10 +3500,10 @@ Changelog of JSqlParser. **Extending CaseExpression, covering #1458 (#1459)** -* Add unit tests for Case expressions. -* More tests for CaseExpression. -* Switch expression becomes an Expression instead of a Condition. -* It allows complex expressions in the switch, similarly to what is allowed in when clauses. +* Add unit tests for Case expressions. +* More tests for CaseExpression. +* Switch expression becomes an Expression instead of a Condition. +* It allows complex expressions in the switch, similarly to what is allowed in when clauses. [4df13](https://github.com/JSQLParser/JSqlParser/commit/4df1391a28a7402) Mathieu Goeminne *2022-03-15 20:07:43* @@ -2652,7 +3524,7 @@ Changelog of JSqlParser. **Add support for IS DISTINCT FROM clause (#1457)** -* Co-authored-by: Tomer Shay <tomer@Tomers-MBP.lan> +* Co-authored-by: Tomer Shay <tomer@Tomers-MBP.lan> [31ed3](https://github.com/JSQLParser/JSqlParser/commit/31ed383ff0f3903) Tomer Shay (Shimshi) *2022-01-18 07:01:14* @@ -2688,12 +3560,12 @@ Changelog of JSqlParser. **Adjust Gradle to JUnit 5 (#1428)** -* Adjust Gradle to JUnit 5 -* Parallel Test execution -* Gradle Caching -* Explicitly request for latest JavaCC 7.0.10 -* Do not mark SpeedTest for concurrent execution -* Remove unused imports +* Adjust Gradle to JUnit 5 +* Parallel Test execution +* Gradle Caching +* Explicitly request for latest JavaCC 7.0.10 +* Do not mark SpeedTest for concurrent execution +* Remove unused imports [af7bc](https://github.com/JSQLParser/JSqlParser/commit/af7bc1cc06700c3) manticore-projects *2021-11-28 21:43:10* @@ -2719,7 +3591,7 @@ Changelog of JSqlParser. **Support EMIT CHANGES for KSQL (#1426)** -* - Add the EMIT CHANGES syntax used in KSQL. +* - Add the EMIT CHANGES syntax used in KSQL. [f6c17](https://github.com/JSQLParser/JSqlParser/commit/f6c17412accdd18) Olivier Cavadenti *2021-11-21 12:20:56* @@ -2750,13 +3622,13 @@ Changelog of JSqlParser. **Support RESTART without value (#1425)** -* Since Postgre 8.4, RESTART in ALTER SEQUENCE can be set without value. +* Since Postgre 8.4, RESTART in ALTER SEQUENCE can be set without value. [98b66](https://github.com/JSQLParser/JSqlParser/commit/98b66be4b2919df) Olivier Cavadenti *2021-11-20 00:00:32* **Add support for oracle UnPivot when use multi columns at once. (#1419)** -* Co-authored-by: LeiJun <02280245@yto.net.cn> +* Co-authored-by: LeiJun <02280245@yto.net.cn> [8e8bb](https://github.com/JSQLParser/JSqlParser/commit/8e8bb708636e6c6) LeiJun *2021-11-19 23:22:29* @@ -2767,10 +3639,10 @@ Changelog of JSqlParser. **Fix issue in parsing TRY_CAST() function (#1391)** -* Fix issue in parsing TRY_CAST() function -* Fix issue in parsing TRY_CAST() function -* Add parser, deparser, validator and vistior implementation for try_cast function -* Update toString() method of TryCastExpression class +* Fix issue in parsing TRY_CAST() function +* Fix issue in parsing TRY_CAST() function +* Add parser, deparser, validator and vistior implementation for try_cast function +* Update toString() method of TryCastExpression class [bfcf0](https://github.com/JSQLParser/JSqlParser/commit/bfcf00f9dfcc0a3) Prashant Sutar *2021-11-19 22:24:49* @@ -2781,27 +3653,27 @@ Changelog of JSqlParser. **Add support for expressions (such as columns) in AT TIME ZONE expressions (#1413)** -* Co-authored-by: EverSQL <tomer@eversql.com> +* Co-authored-by: EverSQL <tomer@eversql.com> [ebe17](https://github.com/JSQLParser/JSqlParser/commit/ebe171b3b502089) Tomer Shay (Shimshi) *2021-11-19 22:04:40* **Add supported for quoted cast expressions for PostgreSQL (#1411)** -* Co-authored-by: EverSQL <tomer@eversql.com> +* Co-authored-by: EverSQL <tomer@eversql.com> [dbbce](https://github.com/JSQLParser/JSqlParser/commit/dbbcebbf0490e1c) Tomer Shay (Shimshi) *2021-11-19 21:54:37* **added USE SCHEMA and CREATE OR REPLACE support; things that are allowed in Snowflake SQL (#1409)** -* Co-authored-by: Richard Kooijman <richard.kooijman@inergy.nl> +* Co-authored-by: Richard Kooijman <richard.kooijman@inergy.nl> [f35d2](https://github.com/JSQLParser/JSqlParser/commit/f35d24cfbb88342) Richard Kooijman *2021-11-19 21:40:45* **Issue #420 Like Expression with Escape Expression (#1406)** -* Issue #420 Like Expression with Escape Expression -* Fixes issue #420 -* CheckStyle compliance +* Issue #420 Like Expression with Escape Expression +* Fixes issue #420 +* CheckStyle compliance [8eaa4](https://github.com/JSQLParser/JSqlParser/commit/8eaa4d2fc243f0a) manticore-projects *2021-11-19 21:38:54* @@ -2822,136 +3694,136 @@ Changelog of JSqlParser. **Add Delete / Update modifiers for MySQL #1254 (#1396)** -* Add Delete / Update modifiers for MySQL #1254 -* fix codacy issues + pr return -* simplify low_priority +* Add Delete / Update modifiers for MySQL #1254 +* fix codacy issues + pr return +* simplify low_priority [7be5d](https://github.com/JSQLParser/JSqlParser/commit/7be5d8e65e23f6e) Olivier Cavadenti *2021-11-19 20:31:00* **Fixes #1381 (#1383)** -* Allow Complex Expressions as SelectItem +* Allow Complex Expressions as SelectItem [cdf0f](https://github.com/JSQLParser/JSqlParser/commit/cdf0f095294b04a) manticore-projects *2021-11-19 20:27:44* **Allows CASE ... ELSE ComplexExpression (#1388)** -* Fixes #1375 -* Co-authored-by: Tobias <t.warneke@gmx.net> +* Fixes #1375 +* Co-authored-by: Tobias <t.warneke@gmx.net> [60a7d](https://github.com/JSQLParser/JSqlParser/commit/60a7d103853cb5e) manticore-projects *2021-11-02 20:48:39* **IN() with complex expressions (#1384)** -* IN() with Complex Expressions -* Fixes #905 -* Allow Complex Expressions and multiple SubSelects for the IN() Expression -* Tune the Test Coverage -* Remove unused import -* Reset TEST status +* IN() with Complex Expressions +* Fixes #905 +* Allow Complex Expressions and multiple SubSelects for the IN() Expression +* Tune the Test Coverage +* Remove unused import +* Reset TEST status [c4232](https://github.com/JSQLParser/JSqlParser/commit/c42322440d5fb36) manticore-projects *2021-11-01 21:23:48* **Fixes #1385 and PR#1380 (#1386)** -* Add another Alias() Keyword related LOOKAHEAD -* Fix a Keyword Spelling Error in the Deparser -* Remove UNPIVOT from the PARENTHESIS Deparser, as it was an ugly workaround made obsolete by PR #1380 +* Add another Alias() Keyword related LOOKAHEAD +* Fix a Keyword Spelling Error in the Deparser +* Remove UNPIVOT from the PARENTHESIS Deparser, as it was an ugly workaround made obsolete by PR #1380 [8c1eb](https://github.com/JSQLParser/JSqlParser/commit/8c1eba24be61cf0) manticore-projects *2021-10-22 20:19:15* **Fixes #1369 (#1370)** -* Issue1369 -* Add test +* Issue1369 +* Add test [2335e](https://github.com/JSQLParser/JSqlParser/commit/2335ed136e92163) Ben Grabham *2021-10-20 21:44:06* **Fixes #1371 (#1377)** -* Fixes #1371 -* Postgres specific JSON_OBJECT syntax supporting: -* SELECT json_object('{a, 1, b, 2}'); -* SELECT json_object('{{a, 1}, {b, 2}}'); -* SELECT json_object('{a, b}', '{1,2 }'); -* Improve Test Coverage +* Fixes #1371 +* Postgres specific JSON_OBJECT syntax supporting: +* SELECT json_object('{a, 1, b, 2}'); +* SELECT json_object('{{a, 1}, {b, 2}}'); +* SELECT json_object('{a, b}', '{1,2 }'); +* Improve Test Coverage [cbffe](https://github.com/JSQLParser/JSqlParser/commit/cbffe6b58cd0074) manticore-projects *2021-10-20 21:13:54* **LIMIT OFFSET with Expressions (#1378)** -* Fixes #933 +* Fixes #933 [a52db](https://github.com/JSQLParser/JSqlParser/commit/a52db54ff61be34) manticore-projects *2021-10-20 21:05:27* **Oracle Multi Column Drop (#1379)** -* Fixes #1363 +* Fixes #1363 [9ad18](https://github.com/JSQLParser/JSqlParser/commit/9ad18d29efb66a2) manticore-projects *2021-10-20 21:01:04* **Support alias for UnPivot statement (see discussion #1374) (#1380)** -* - Changed JSqlParserCC.jjt file to add the alias to the UnPivot lexical entity. -* - Added Alias to the UnPivot object. -* - Improved SelectDeParser to correctly deparse SubSelect's UnPivot component. +* - Changed JSqlParserCC.jjt file to add the alias to the UnPivot lexical entity. +* - Added Alias to the UnPivot object. +* - Improved SelectDeParser to correctly deparse SubSelect's UnPivot component. [0c0c3](https://github.com/JSQLParser/JSqlParser/commit/0c0c32e9cda0f1a) fabriziodelfranco *2021-10-20 20:18:13* **Issue1352 (#1353)** -* Fixes #1352 -* Allow SYSTEM as table- or column- name -* Fixes #1352 -* Allow SYSTEM as tablename -* Fixes #1352 -* Allow SYSTEM as tablename and columnname -* Fixes #1352 -* Allow QUERY as tablename and columnname -* Fixes #1352 -* Allow FULLTEXT as tablename and columnname -* Co-authored-by: Tobias <t.warneke@gmx.net> +* Fixes #1352 +* Allow SYSTEM as table- or column- name +* Fixes #1352 +* Allow SYSTEM as tablename +* Fixes #1352 +* Allow SYSTEM as tablename and columnname +* Fixes #1352 +* Allow QUERY as tablename and columnname +* Fixes #1352 +* Allow FULLTEXT as tablename and columnname +* Co-authored-by: Tobias <t.warneke@gmx.net> [a8afd](https://github.com/JSQLParser/JSqlParser/commit/a8afd9a4e6a8bc0) manticore-projects *2021-10-09 21:31:45* **Enhance ALTER TABLE ... DROP CONSTRAINTS ... (#1351)** -* Enhance ALTER TABLE ... DROP CONSTRAINTS ... -* Add support for DROP PRIMARY KEY, DROP UNIQUE(...) -* Add support for DROP FOREIGN KEY(...) -* Fixes #1342 -* Remove one useless PMD rule -* Add more tests -* Adjust Test Coverage +* Enhance ALTER TABLE ... DROP CONSTRAINTS ... +* Add support for DROP PRIMARY KEY, DROP UNIQUE(...) +* Add support for DROP FOREIGN KEY(...) +* Fixes #1342 +* Remove one useless PMD rule +* Add more tests +* Adjust Test Coverage [388b7](https://github.com/JSQLParser/JSqlParser/commit/388b7c3afff4f50) manticore-projects *2021-10-08 23:02:46* **Function to use AllColumns or AllTableColumns Expression (#1350)** -* Fix a trivial MERGE error from Commit 4797a8d676625fcc6cf8c9e3b403ca120b6a8141 -* Function use AllColumns or AllTableColumns -* Fixes #1346 -* Remove one useless PMD rule +* Fix a trivial MERGE error from Commit 4797a8d676625fcc6cf8c9e3b403ca120b6a8141 +* Function use AllColumns or AllTableColumns +* Fixes #1346 +* Remove one useless PMD rule [b0ada](https://github.com/JSQLParser/JSqlParser/commit/b0adaa8de1421ea) manticore-projects *2021-10-08 21:58:04* **Postgres compliant ALTER TABLE ... RENAME TO ... (#1334)** -* Fix a trivial MERGE error from Commit 4797a8d676625fcc6cf8c9e3b403ca120b6a8141 -* Fixes #1333 -* Postgres compliant ALTER TABLE ... RENAME TO ... -* Postgres compliant ALTER TABLE IF EXISTS ... RENAME TO ... -* Postgres compliant ALTER TABLE IF EXISTS ... RENAME TO ... +* Fix a trivial MERGE error from Commit 4797a8d676625fcc6cf8c9e3b403ca120b6a8141 +* Fixes #1333 +* Postgres compliant ALTER TABLE ... RENAME TO ... +* Postgres compliant ALTER TABLE IF EXISTS ... RENAME TO ... +* Postgres compliant ALTER TABLE IF EXISTS ... RENAME TO ... [f353e](https://github.com/JSQLParser/JSqlParser/commit/f353ec830deb719) manticore-projects *2021-09-18 11:35:17* **Postgres compliant ALTER TABLE ... RENAME TO ... (#1334)** -* Fix a trivial MERGE error from Commit 4797a8d676625fcc6cf8c9e3b403ca120b6a8141 -* Fixes #1333 -* Postgres compliant ALTER TABLE ... RENAME TO ... -* Postgres compliant ALTER TABLE IF EXISTS ... RENAME TO ... -* Postgres compliant ALTER TABLE IF EXISTS ... RENAME TO ... +* Fix a trivial MERGE error from Commit 4797a8d676625fcc6cf8c9e3b403ca120b6a8141 +* Fixes #1333 +* Postgres compliant ALTER TABLE ... RENAME TO ... +* Postgres compliant ALTER TABLE IF EXISTS ... RENAME TO ... +* Postgres compliant ALTER TABLE IF EXISTS ... RENAME TO ... [5e904](https://github.com/JSQLParser/JSqlParser/commit/5e9045f431d9cc4) manticore-projects *2021-09-18 11:34:50* @@ -2982,18 +3854,18 @@ Changelog of JSqlParser. **Prepare4.2 (#1329)** -* Implement caching of the Gradle and Maven files -* Provided by @YunLemon via PR #1307 -* Fix CREATE TABLE AS SELECT ... UNION SELECT ... -* Provided by @fanchuo via PR #1309 -* Fix #1316 -* Add more specific tests verifying the nature of the UpdateSets -* Allow "SELECT *" (without FROM) to parse, its a valid SELECT statement -* Add the enhancements since Release 4.1 -* Adjust the Coverage -* Improve Test Coverage -* Revert the Special Oracle Tests (accidentally set to FAILURE) -* Co-authored-by: Tobias <t.warneke@gmx.net> +* Implement caching of the Gradle and Maven files +* Provided by @YunLemon via PR #1307 +* Fix CREATE TABLE AS SELECT ... UNION SELECT ... +* Provided by @fanchuo via PR #1309 +* Fix #1316 +* Add more specific tests verifying the nature of the UpdateSets +* Allow "SELECT *" (without FROM) to parse, its a valid SELECT statement +* Add the enhancements since Release 4.1 +* Adjust the Coverage +* Improve Test Coverage +* Revert the Special Oracle Tests (accidentally set to FAILURE) +* Co-authored-by: Tobias <t.warneke@gmx.net> [4797a](https://github.com/JSQLParser/JSqlParser/commit/4797a8d676625fc) manticore-projects *2021-09-07 08:56:12* @@ -3004,38 +3876,38 @@ Changelog of JSqlParser. **Fixes #1325 (#1327)** -* Removes redundant production Identifier() and uses RelObjectnameWithoutValue() instead for MS SQL Server Hints +* Removes redundant production Identifier() and uses RelObjectnameWithoutValue() instead for MS SQL Server Hints [cf0d7](https://github.com/JSQLParser/JSqlParser/commit/cf0d74f6572d6aa) manticore-projects *2021-09-06 12:09:01* **Implement Joins with multiple trailing ON Expressions (#1303)** -* Implement Joins with multiple trailing ON Expressions -* Fixes #1302 -* Fixes SpecialOracleTest JOIN17, now 190/273 tests pass -* Fixes #1229 -* Merge MASTER -* Refactor the appendTo() method in favour of the traditional toString() -* Remove unused imports +* Implement Joins with multiple trailing ON Expressions +* Fixes #1302 +* Fixes SpecialOracleTest JOIN17, now 190/273 tests pass +* Fixes #1229 +* Merge MASTER +* Refactor the appendTo() method in favour of the traditional toString() +* Remove unused imports [d18c5](https://github.com/JSQLParser/JSqlParser/commit/d18c59bf845c57d) manticore-projects *2021-09-06 11:34:05* **Fix Gradle PMD and Checkstyle (#1318)** -* Fixes #1306 -* Nested Cases with Complex Expressions -* Reduce coverage for Java 8 -* GROUP BY with Complex Expressions -* Fixes #1308 -* Update Sets with Complex Expressions -* Fixes #1316 -* Update Sets with Complex Expressions -* Fix existing tests -* Add tests for the new functionality -* Implement PMD/Codacy recommendations -* Add Checkstyle Configuration to Gradle -* Add Checkstyle Config files -* Fix additional exceptions in Test Sources +* Fixes #1306 +* Nested Cases with Complex Expressions +* Reduce coverage for Java 8 +* GROUP BY with Complex Expressions +* Fixes #1308 +* Update Sets with Complex Expressions +* Fixes #1316 +* Update Sets with Complex Expressions +* Fix existing tests +* Add tests for the new functionality +* Implement PMD/Codacy recommendations +* Add Checkstyle Configuration to Gradle +* Add Checkstyle Config files +* Fix additional exceptions in Test Sources [2e876](https://github.com/JSQLParser/JSqlParser/commit/2e876130b46d087) manticore-projects *2021-09-01 22:01:40* @@ -3046,27 +3918,27 @@ Changelog of JSqlParser. **Fixes #1306 (#1311)** -* Fixes #1306 -* Nested Cases with Complex Expressions -* Reduce coverage for Java 8 -* GROUP BY with Complex Expressions -* Fixes #1308 +* Fixes #1306 +* Nested Cases with Complex Expressions +* Reduce coverage for Java 8 +* GROUP BY with Complex Expressions +* Fixes #1308 [8f632](https://github.com/JSQLParser/JSqlParser/commit/8f632b92e511b28) manticore-projects *2021-08-28 20:28:55* **Update sets (#1317)** -* Fixes #1306 -* Nested Cases with Complex Expressions -* Reduce coverage for Java 8 -* GROUP BY with Complex Expressions -* Fixes #1308 -* Update Sets with Complex Expressions -* Fixes #1316 -* Update Sets with Complex Expressions -* Fix existing tests -* Add tests for the new functionality -* Implement PMD/Codacy recommendations +* Fixes #1306 +* Nested Cases with Complex Expressions +* Reduce coverage for Java 8 +* GROUP BY with Complex Expressions +* Fixes #1308 +* Update Sets with Complex Expressions +* Fixes #1316 +* Update Sets with Complex Expressions +* Fix existing tests +* Add tests for the new functionality +* Implement PMD/Codacy recommendations [21e5e](https://github.com/JSQLParser/JSqlParser/commit/21e5ebac02822e2) manticore-projects *2021-08-27 21:37:05* @@ -3077,46 +3949,46 @@ Changelog of JSqlParser. **Special oracle tests (#1279)** -* Allow keywords: LINK, GROUPING() -* Deparse ParenthesisFromItem's Pivot and UnPivot correctly -* Write Test results to the SQL File -* Reduce the noise during the test -* Update/correct the list of expected passing files -* Get the benchmark from the list of expected passing files -* There are no Pivots or UnPivots yet, so we will return NULL. -* Record the expected Test Results on each SQL Source -* Fail the test when any expected success suddenly fails -* Improve Test Coverage -* Appease Codacy +* Allow keywords: LINK, GROUPING() +* Deparse ParenthesisFromItem's Pivot and UnPivot correctly +* Write Test results to the SQL File +* Reduce the noise during the test +* Update/correct the list of expected passing files +* Get the benchmark from the list of expected passing files +* There are no Pivots or UnPivots yet, so we will return NULL. +* Record the expected Test Results on each SQL Source +* Fail the test when any expected success suddenly fails +* Improve Test Coverage +* Appease Codacy [346ee](https://github.com/JSQLParser/JSqlParser/commit/346eea5fbcf2461) manticore-projects *2021-08-09 21:55:00* **Implements Hierarchical CONNECT_BY_ROOT Operator (#1282)** -* Implements Hierarchical CONNECT_BY_ROOT Operator -* Fixes Issue #1269 -* Resolves some Special Oracle Tests -* Improve Test Coverage -* Co-authored-by: Tobias <t.warneke@gmx.net> +* Implements Hierarchical CONNECT_BY_ROOT Operator +* Fixes Issue #1269 +* Resolves some Special Oracle Tests +* Improve Test Coverage +* Co-authored-by: Tobias <t.warneke@gmx.net> [b6014](https://github.com/JSQLParser/JSqlParser/commit/b60140740aab93e) manticore-projects *2021-08-09 21:53:08* **Implement Transact-SQL IF ELSE Statement Control Flows. (#1275)** -* Implement Transact-SQL IF ELSE Statement Control Flows. -* Fixes #1273 except for Blocks. -* Improce Test Coverage -* Adjust the required Test Coverage for JDK 8 +* Implement Transact-SQL IF ELSE Statement Control Flows. +* Fixes #1273 except for Blocks. +* Improce Test Coverage +* Adjust the required Test Coverage for JDK 8 [750c3](https://github.com/JSQLParser/JSqlParser/commit/750c30aafe83957) manticore-projects *2021-08-09 21:43:50* **Add some flexibility to the Alter Statement (#1293)** -* in order to allow: -* ALTER TABLE ... MOVE TABLESPACE ... -* ALTER TABLE ... COMPRESS NOLOGGING -* ALTER TABLE ... ROWFORMAT=DYNAMIC -* Fixes #1033 +* in order to allow: +* ALTER TABLE ... MOVE TABLESPACE ... +* ALTER TABLE ... COMPRESS NOLOGGING +* ALTER TABLE ... ROWFORMAT=DYNAMIC +* Fixes #1033 [a88e9](https://github.com/JSQLParser/JSqlParser/commit/a88e921970c57b8) manticore-projects *2021-08-02 20:51:19* @@ -3127,18 +3999,18 @@ Changelog of JSqlParser. **Implement Oracle Named Function Parameters Func( param1 => arg1, ...) (#1283)** -* Fixes #1270 +* Fixes #1270 [c8a5d](https://github.com/JSQLParser/JSqlParser/commit/c8a5d7c3dfc97f4) manticore-projects *2021-08-02 20:32:41* **Implement Gradle Buildsystem (#1271)** -* Gradle build -* implement SpotBugs, PMD and JaCoCo -* implement RR diagrams -* Move Special Oracle Test resources into the correct package -* Implement a basic Gradle/Maven compatibility workaround for the Special Oracle Test -* Fix the Gradle Wrapper and add the folder to git +* Gradle build +* implement SpotBugs, PMD and JaCoCo +* implement RR diagrams +* Move Special Oracle Test resources into the correct package +* Implement a basic Gradle/Maven compatibility workaround for the Special Oracle Test +* Fix the Gradle Wrapper and add the folder to git [6933d](https://github.com/JSQLParser/JSqlParser/commit/6933d86e0fa2d48) manticore-projects *2021-08-02 20:18:48* @@ -3149,17 +4021,17 @@ Changelog of JSqlParser. **Allowes JdbcParameter or JdbcNamedParameter for MySQL FullTextSearch (#1278)** -* Fixes issue #1223 +* Fixes issue #1223 [e6c91](https://github.com/JSQLParser/JSqlParser/commit/e6c91b6a813a1e0) manticore-projects *2021-07-26 21:06:38* **Fixes #1267 Cast into RowConstructor (#1274)** -* Fixes #1267 Cast into RowConstructor -* Improve Test Coverage -* Improve Test Coverage -* Improve Test Coverage -* Co-authored-by: Tobias <t.warneke@gmx.net> +* Fixes #1267 Cast into RowConstructor +* Improve Test Coverage +* Improve Test Coverage +* Improve Test Coverage +* Co-authored-by: Tobias <t.warneke@gmx.net> [c89cf](https://github.com/JSQLParser/JSqlParser/commit/c89cf21641d672b) manticore-projects *2021-07-26 21:02:19* @@ -3170,20 +4042,20 @@ Changelog of JSqlParser. **Separate MySQL Special String Functions accepting Named Argument Separation as this could collide with ComplexExpressionList when InExpression is involved (#1285)** -* Fixes #1284 -* Co-authored-by: Tobias <t.warneke@gmx.net> +* Fixes #1284 +* Co-authored-by: Tobias <t.warneke@gmx.net> [c074a](https://github.com/JSQLParser/JSqlParser/commit/c074a21ad70dd0e) manticore-projects *2021-07-26 20:34:06* **Implements Oracle RENAME oldTable TO newTable Statement (#1286)** -* Implements Oracle RENAME oldTable TO newTable Statement -* Fixes #1253 -* Implement MariaDB specific syntax -* Remove redundant License Headers -* Use LinkedHashMap to preserve the order of the Entries -* Increase Test Coverage -* Co-authored-by: Tobias <t.warneke@gmx.net> +* Implements Oracle RENAME oldTable TO newTable Statement +* Fixes #1253 +* Implement MariaDB specific syntax +* Remove redundant License Headers +* Use LinkedHashMap to preserve the order of the Entries +* Increase Test Coverage +* Co-authored-by: Tobias <t.warneke@gmx.net> [f7f7b](https://github.com/JSQLParser/JSqlParser/commit/f7f7bcd65be8f4c) manticore-projects *2021-07-26 20:26:47* @@ -3204,17 +4076,17 @@ Changelog of JSqlParser. **Json functions (#1263)** -* Implement Json Aggregate Functions JSON_OBJECTAGG() and JSON_ARRAYAGG() -* fix the returned type -* Implement JSON_OBJECT and JSON_ARRAY -* Solves #1260 and dbeaver/dbeaver/#13141 -* Better workaround for NULL, NULL NULL ON NULL -* Remove the workaround for NULL ON NULL (without expression) -* Implement "PMD.MissingBreakInSwitch" in order to appease Codacy -* Improve Test Coverage -* Improve Test Coverage -* KEYs can be SQL Value Expressions -* Add another testcase +* Implement Json Aggregate Functions JSON_OBJECTAGG() and JSON_ARRAYAGG() +* fix the returned type +* Implement JSON_OBJECT and JSON_ARRAY +* Solves #1260 and dbeaver/dbeaver/#13141 +* Better workaround for NULL, NULL NULL ON NULL +* Remove the workaround for NULL ON NULL (without expression) +* Implement "PMD.MissingBreakInSwitch" in order to appease Codacy +* Improve Test Coverage +* Improve Test Coverage +* KEYs can be SQL Value Expressions +* Add another testcase [59bf0](https://github.com/JSQLParser/JSqlParser/commit/59bf07f5425ecb4) manticore-projects *2021-07-16 21:24:44* @@ -3225,20 +4097,20 @@ Changelog of JSqlParser. **Active JJDoc and let it create the Grammar BNF documentation (#1256)** -* Clean-up the Site generation +* Clean-up the Site generation [1ecff](https://github.com/JSQLParser/JSqlParser/commit/1ecffd2c1a6e8d8) manticore-projects *2021-07-16 20:38:05* **Bump commons-io from 2.6 to 2.7 (#1265)** -* Bumps commons-io from 2.6 to 2.7. -* --- -* updated-dependencies: -* - dependency-name: commons-io:commons-io -* dependency-type: direct:development -* ... -* Signed-off-by: dependabot[bot] <support@github.com> -* Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> +* Bumps commons-io from 2.6 to 2.7. +* --- +* updated-dependencies: +* - dependency-name: commons-io:commons-io +* dependency-type: direct:development +* ... +* Signed-off-by: dependabot[bot] <support@github.com> +* Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> [09898](https://github.com/JSQLParser/JSqlParser/commit/09898107fe6d001) dependabot[bot] *2021-07-14 05:27:57* @@ -3249,17 +4121,17 @@ Changelog of JSqlParser. **Implement DB2 Special Register Date Time CURRENT DATE and CURRENT TIME (#1252)** -* Implement DB2 Special Register Date Time CURRENT DATE and CURRENT TIME -* Fixes issue #1249 -* (Although there are more Special Registers which are not supported yet.) -* Make the spaces mandatory -* Add 2 more tests +* Implement DB2 Special Register Date Time CURRENT DATE and CURRENT TIME +* Fixes issue #1249 +* (Although there are more Special Registers which are not supported yet.) +* Make the spaces mandatory +* Add 2 more tests [05157](https://github.com/JSQLParser/JSqlParser/commit/05157a841897033) manticore-projects *2021-07-13 05:32:38* **Rename the PMD ruleset configuration file hoping for automatic synchronization with Codacy (#1251)** -* Solves Issue #1220 +* Solves Issue #1220 [930b7](https://github.com/JSQLParser/JSqlParser/commit/930b7a561876b7e) manticore-projects *2021-07-13 05:28:43* @@ -3310,8 +4182,8 @@ Changelog of JSqlParser. **Savepoint rollback (#1236)** -* Implement SAVEPOINT and ROLLBACK statements, fixes issue #1235 -* Activate a test which is supported now. +* Implement SAVEPOINT and ROLLBACK statements, fixes issue #1235 +* Activate a test which is supported now. [2cae6](https://github.com/JSQLParser/JSqlParser/commit/2cae62dbf74a744) manticore-projects *2021-06-30 19:57:56* @@ -3337,14 +4209,14 @@ Changelog of JSqlParser. **RESET statement, SET PostgreSQL compatibility (#1104)** -* Support -* RESET statement (https://www.postgresql.org/docs/current/sql-reset.html) -* SET [LOCAL|SESSION] (https://www.postgresql.org/docs/current/sql-set.html) -* SET search_path=my_schema, public ( https://www.postgresql.org/docs/current/sql-set.html -* value New value of parameter. Values can be specified as string constants, identifiers, numbers, or comma-separated lists of these) -* Update ResetStatementTest.java -* remove Tim Zone token -* Co-authored-by: Tobias <t.warneke@gmx.net> +* Support +* RESET statement (https://www.postgresql.org/docs/current/sql-reset.html) +* SET [LOCAL|SESSION] (https://www.postgresql.org/docs/current/sql-set.html) +* SET search_path=my_schema, public ( https://www.postgresql.org/docs/current/sql-set.html +* value New value of parameter. Values can be specified as string constants, identifiers, numbers, or comma-separated lists of these) +* Update ResetStatementTest.java +* remove Tim Zone token +* Co-authored-by: Tobias <t.warneke@gmx.net> [13503](https://github.com/JSQLParser/JSqlParser/commit/13503edff30f06d) Роман Зотов *2021-06-26 22:44:33* @@ -3355,9 +4227,9 @@ Changelog of JSqlParser. **Implement Oracle Alter Session Statements (#1234)** -* Implement Oracle Alter Session Statements according to https://docs.oracle.com/cd/B19306_01/server.102/b14200/statements_2012.htm -* Implement PMD Rule "SwitchStmtsShouldHaveDefault" -* Reorganize Test Case imports +* Implement Oracle Alter Session Statements according to https://docs.oracle.com/cd/B19306_01/server.102/b14200/statements_2012.htm +* Implement PMD Rule "SwitchStmtsShouldHaveDefault" +* Reorganize Test Case imports [3a46a](https://github.com/JSQLParser/JSqlParser/commit/3a46a29d6936611) manticore-projects *2021-06-26 22:38:19* @@ -3368,16 +4240,16 @@ Changelog of JSqlParser. **Support DELETE FROM T1 USING T2 WHERE ... (#1228)** -* Co-authored-by: Francois Secherre <secherre.nospam@gmail.com> +* Co-authored-by: Francois Secherre <secherre.nospam@gmail.com> [96cd4](https://github.com/JSQLParser/JSqlParser/commit/96cd483ab85783d) francois-secherre *2021-06-16 05:27:14* **Row access support (#1181)** -* Row acess support -* Remove IN Left Expression List, replaced by RowConstructor Expression -* Remove IN Left Expression List, replaced by RowConstructor Expression -* Formatting +* Row acess support +* Remove IN Left Expression List, replaced by RowConstructor Expression +* Remove IN Left Expression List, replaced by RowConstructor Expression +* Formatting [27e6a](https://github.com/JSQLParser/JSqlParser/commit/27e6a9f0e07320e) Роман Зотов *2021-06-16 05:15:47* @@ -3388,67 +4260,67 @@ Changelog of JSqlParser. **Delete queries without from, with a schema identifier fails (#1224)** -* Delete queries without from, with a schema identifier fails -* Better tests -* Fix style issue -* Deparse should match for DELETE WITHOUT FROM queries -* Co-authored-by: François Sécherre <francois.secherre@ouicar.fr> +* Delete queries without from, with a schema identifier fails +* Better tests +* Fix style issue +* Deparse should match for DELETE WITHOUT FROM queries +* Co-authored-by: François Sécherre <francois.secherre@ouicar.fr> [d70e1](https://github.com/JSQLParser/JSqlParser/commit/d70e151c0f22f22) François Sécherre *2021-06-14 05:15:15* **Create temporary table t(c1, c2) as select ... (#1225)** -* Co-authored-by: Francois Secherre <secherre.nospam@gmail.com> +* Co-authored-by: Francois Secherre <secherre.nospam@gmail.com> [b62f1](https://github.com/JSQLParser/JSqlParser/commit/b62f19feb3b497c) francois-secherre *2021-06-14 05:13:13* **Nested with items (#1221)** -* Nested WithItems, fixes issue #1186 -* Remove redundant Test -* Avoid altering the nb-configuration -* Mention Nested WITH CTEs in the readme -* Eliminate dead/unused MultiExpression Code +* Nested WithItems, fixes issue #1186 +* Remove redundant Test +* Avoid altering the nb-configuration +* Mention Nested WITH CTEs in the readme +* Eliminate dead/unused MultiExpression Code [8eb3d](https://github.com/JSQLParser/JSqlParser/commit/8eb3d9a586a182e) manticore-projects *2021-06-10 05:50:08* **Implement GROUP BY () without columns (#1218)** -* Implement GROUP BY () without columns -* Migrate GroupByElement to ExpressionList -* Also solves issue #1210 automatically -* Solves issue #1168, add a test for it. +* Implement GROUP BY () without columns +* Migrate GroupByElement to ExpressionList +* Also solves issue #1210 automatically +* Solves issue #1168, add a test for it. [999db](https://github.com/JSQLParser/JSqlParser/commit/999db01658c30f9) manticore-projects *2021-06-03 05:55:35* **TSQL Compliant NEXT VALUE FOR sequence_id (but keeping the spurious NEXTVAL FOR expression) (#1216)** -* Co-authored-by: Tobias <t.warneke@gmx.net> +* Co-authored-by: Tobias <t.warneke@gmx.net> [7c212](https://github.com/JSQLParser/JSqlParser/commit/7c21242e893abcd) manticore-projects *2021-06-02 12:35:03* **Pmd clean up (#1215)** -* Add PMD Annotations in order to avoid useless exceptions for the Deparsers -* Add Eclipse Formatter configuration -* Fix typo -* Replace Comments on empty methods with Class wide PMD Annotation -* Do not enforce checkstyle formatting +* Add PMD Annotations in order to avoid useless exceptions for the Deparsers +* Add Eclipse Formatter configuration +* Fix typo +* Replace Comments on empty methods with Class wide PMD Annotation +* Do not enforce checkstyle formatting [53764](https://github.com/JSQLParser/JSqlParser/commit/537649bf28f641b) manticore-projects *2021-06-02 12:19:32* **Add support for boolean 'XOR' operator (#1193)** -* Add support for boolean 'XOR' operator -* XorExpression added to the ReflectionModelTest -* XorExpression case added to the SelectTest -* XorExpression cases added for Validation -* Additional tests added for code coverage. -* Code style fixed. -* Separate test case for XOR added. -* Imports explicitly added to avoid namespace pollution. -* Additional tests cases for precedence and associativity. -* Co-authored-by: Szabó Miklós <miklos.szabo@arh.hu> +* Add support for boolean 'XOR' operator +* XorExpression added to the ReflectionModelTest +* XorExpression case added to the SelectTest +* XorExpression cases added for Validation +* Additional tests added for code coverage. +* Code style fixed. +* Separate test case for XOR added. +* Imports explicitly added to avoid namespace pollution. +* Additional tests cases for precedence and associativity. +* Co-authored-by: Szabó Miklós <miklos.szabo@arh.hu> [c7832](https://github.com/JSQLParser/JSqlParser/commit/c7832402dded4c0) Adaptive Recognition *2021-06-02 12:10:56* @@ -3484,20 +4356,20 @@ Changelog of JSqlParser. **Allow Complex Parsing of Functions (#1200)** -* Allow Complex Parsing of Functions -* Fixes issues #1190 #1103 -* Apply Complex Parsing to PrimaryExpression() -* Fixes issue #1194 -* Increase Test Timeout to 2 seconds for slow CI Servers. -* Appease Codazy +* Allow Complex Parsing of Functions +* Fixes issues #1190 #1103 +* Apply Complex Parsing to PrimaryExpression() +* Fixes issue #1194 +* Increase Test Timeout to 2 seconds for slow CI Servers. +* Appease Codazy [3a5da](https://github.com/JSQLParser/JSqlParser/commit/3a5da445ea9cb85) manticore-projects *2021-05-26 20:35:10* **Add support for AT TIME ZONE expressions (#1196)** -* Add support for AT TIME ZONE expressions -* adding tests -* Fixing imports +* Add support for AT TIME ZONE expressions +* adding tests +* Fixing imports [a5204](https://github.com/JSQLParser/JSqlParser/commit/a5204f63ccb1ea8) Tomer Shay (Shimshi) *2021-05-25 23:03:31* @@ -3523,9 +4395,9 @@ Changelog of JSqlParser. **Fix Nested CASE WHEN performance, fixes issue #1162 (#1208)** -* Fix Nested CASE WHEN performance, fixes issue #1162 -* Apease Codazy -* Apease Codazy +* Fix Nested CASE WHEN performance, fixes issue #1162 +* Apease Codazy +* Apease Codazy [42610](https://github.com/JSQLParser/JSqlParser/commit/426102e4cf272ca) manticore-projects *2021-05-25 19:26:30* @@ -3551,70 +4423,70 @@ Changelog of JSqlParser. **supporting/fixing unique inside sql function such as count eg - SELECT count(UNIQUE col2) FROM mytable (#1184)** -* Co-authored-by: Adhikesavan <radhikesavan@paypal.com> +* Co-authored-by: Adhikesavan <radhikesavan@paypal.com> [f18e9](https://github.com/JSQLParser/JSqlParser/commit/f18e92eaf4b3bc6) RajaSudharsan Adhikesavan *2021-05-01 19:28:05* **Oracle compliant ALTER TABLE ADD/MODIFY deparser (#1163)** -* javadoc-fixes -* fix check-style error : assignment to parameter not allowed -* import for javadoc reference -* javadoc - add description to parameter "fqn" (fix warning) -* remove doclint=none, but exclude package with exclude package with -* generated sources (javacc/jjtree) from javadoc -* Implement Oracle Hints for INSERT, UPDATE, MERGE, DELETE -* Correct CreateIndex TailOptions -* Add a Test Case for CreateIndex TailOptions -* Add WHERE expression to MergeInsert -* Add test case for MergeInsert WHERE expression -* Fix Issue #1156: ALTER TABLE ADD FOREIGN KEY with schema reference -* Add a specific test case -* Fix Issue #1157: Oracle does not accept COLUMN keyword in ALTER TABLE ADD/MODIFY -* Correct the test cases accepting a non existing COLUMN keyword -* Add a specific test cases -* Fix Issue #1164 UNIQUE after PRIMARY KEY -* Add test case for UNIQUE after PRIMARY KEY -* Switch of warnings for un-fixble method namings -* Switch of warnings for un-fixble method namings -* Activate PMD and define our own ruleset -* Execute PMD before building/testing in order to fail early -* Fix 63 PMD warnings -* Activate rule "PMD.CyclomaticComplexity" in order to simulate the Codazy checks -* Apply @SuppressWarnings({"PMD.CyclomaticComplexity"}) where this rule throws an unavoidable warning (especially for toString() and deparse()) -* Activate rule , "PMD.ExcessiveMethodLength" in order to simulate the Codazy checks -* Apply @SuppressWarnings({"PMD.ExcessiveMethodLength"}) where this rule throws an unavoidable warning (especially for toString() and deparse()) -* Refactor an ENUM name -* Refactor an ENUM name and reflect this also in the JavaCC Parser definition file -* Co-authored-by: gitmotte <www@synbee.at> +* javadoc-fixes +* fix check-style error : assignment to parameter not allowed +* import for javadoc reference +* javadoc - add description to parameter "fqn" (fix warning) +* remove doclint=none, but exclude package with exclude package with +* generated sources (javacc/jjtree) from javadoc +* Implement Oracle Hints for INSERT, UPDATE, MERGE, DELETE +* Correct CreateIndex TailOptions +* Add a Test Case for CreateIndex TailOptions +* Add WHERE expression to MergeInsert +* Add test case for MergeInsert WHERE expression +* Fix Issue #1156: ALTER TABLE ADD FOREIGN KEY with schema reference +* Add a specific test case +* Fix Issue #1157: Oracle does not accept COLUMN keyword in ALTER TABLE ADD/MODIFY +* Correct the test cases accepting a non existing COLUMN keyword +* Add a specific test cases +* Fix Issue #1164 UNIQUE after PRIMARY KEY +* Add test case for UNIQUE after PRIMARY KEY +* Switch of warnings for un-fixble method namings +* Switch of warnings for un-fixble method namings +* Activate PMD and define our own ruleset +* Execute PMD before building/testing in order to fail early +* Fix 63 PMD warnings +* Activate rule "PMD.CyclomaticComplexity" in order to simulate the Codazy checks +* Apply @SuppressWarnings({"PMD.CyclomaticComplexity"}) where this rule throws an unavoidable warning (especially for toString() and deparse()) +* Activate rule , "PMD.ExcessiveMethodLength" in order to simulate the Codazy checks +* Apply @SuppressWarnings({"PMD.ExcessiveMethodLength"}) where this rule throws an unavoidable warning (especially for toString() and deparse()) +* Refactor an ENUM name +* Refactor an ENUM name and reflect this also in the JavaCC Parser definition file +* Co-authored-by: gitmotte <www@synbee.at> [83837](https://github.com/JSQLParser/JSqlParser/commit/838379f21be0d32) manticore-projects *2021-04-21 07:55:17* **Pmd (#1165)** -* Implement Oracle Hints for INSERT, UPDATE, MERGE, DELETE -* Correct CreateIndex TailOptions -* Add a Test Case for CreateIndex TailOptions -* Add WHERE expression to MergeInsert -* Add test case for MergeInsert WHERE expression -* Fix Issue #1156: ALTER TABLE ADD FOREIGN KEY with schema reference -* Add a specific test case -* Fix Issue #1157: Oracle does not accept COLUMN keyword in ALTER TABLE ADD/MODIFY -* Correct the test cases accepting a non existing COLUMN keyword -* Add a specific test cases -* Fix Issue #1164 UNIQUE after PRIMARY KEY -* Add test case for UNIQUE after PRIMARY KEY -* Switch of warnings for un-fixble method namings -* Switch of warnings for un-fixble method namings -* Activate PMD and define our own ruleset -* Execute PMD before building/testing in order to fail early -* Fix 63 PMD warnings -* Activate rule "PMD.CyclomaticComplexity" in order to simulate the Codazy checks -* Apply @SuppressWarnings({"PMD.CyclomaticComplexity"}) where this rule throws an unavoidable warning (especially for toString() and deparse()) -* Activate rule , "PMD.ExcessiveMethodLength" in order to simulate the Codazy checks -* Apply @SuppressWarnings({"PMD.ExcessiveMethodLength"}) where this rule throws an unavoidable warning (especially for toString() and deparse()) -* Refactor an ENUM name -* Refactor an ENUM name and reflect this also in the JavaCC Parser definition file +* Implement Oracle Hints for INSERT, UPDATE, MERGE, DELETE +* Correct CreateIndex TailOptions +* Add a Test Case for CreateIndex TailOptions +* Add WHERE expression to MergeInsert +* Add test case for MergeInsert WHERE expression +* Fix Issue #1156: ALTER TABLE ADD FOREIGN KEY with schema reference +* Add a specific test case +* Fix Issue #1157: Oracle does not accept COLUMN keyword in ALTER TABLE ADD/MODIFY +* Correct the test cases accepting a non existing COLUMN keyword +* Add a specific test cases +* Fix Issue #1164 UNIQUE after PRIMARY KEY +* Add test case for UNIQUE after PRIMARY KEY +* Switch of warnings for un-fixble method namings +* Switch of warnings for un-fixble method namings +* Activate PMD and define our own ruleset +* Execute PMD before building/testing in order to fail early +* Fix 63 PMD warnings +* Activate rule "PMD.CyclomaticComplexity" in order to simulate the Codazy checks +* Apply @SuppressWarnings({"PMD.CyclomaticComplexity"}) where this rule throws an unavoidable warning (especially for toString() and deparse()) +* Activate rule , "PMD.ExcessiveMethodLength" in order to simulate the Codazy checks +* Apply @SuppressWarnings({"PMD.ExcessiveMethodLength"}) where this rule throws an unavoidable warning (especially for toString() and deparse()) +* Refactor an ENUM name +* Refactor an ENUM name and reflect this also in the JavaCC Parser definition file [08cfd](https://github.com/JSQLParser/JSqlParser/commit/08cfd29459044c6) manticore-projects *2021-04-20 08:01:48* @@ -3635,13 +4507,13 @@ Changelog of JSqlParser. **Assorted fixes to the Java CC Parser definition (#1153)** -* Implement Oracle Hints for INSERT, UPDATE, MERGE, DELETE -* Correct CreateIndex TailOptions -* Add a Test Case for CreateIndex TailOptions -* Add WHERE expression to MergeInsert -* Add test case for MergeInsert WHERE expression -* Fix Issue #1156: ALTER TABLE ADD FOREIGN KEY with schema reference -* Add a specific test case +* Implement Oracle Hints for INSERT, UPDATE, MERGE, DELETE +* Correct CreateIndex TailOptions +* Add a Test Case for CreateIndex TailOptions +* Add WHERE expression to MergeInsert +* Add test case for MergeInsert WHERE expression +* Fix Issue #1156: ALTER TABLE ADD FOREIGN KEY with schema reference +* Add a specific test case [5ee6e](https://github.com/JSQLParser/JSqlParser/commit/5ee6ec9dd7a66bf) manticore-projects *2021-04-16 22:51:27* @@ -3687,8 +4559,8 @@ Changelog of JSqlParser. **Add support for union without brackets and with limit (#1132)** -* add support for union without brackets and with limit -* Fixing the last commit. +* add support for union without brackets and with limit +* Fixing the last commit. [56c2d](https://github.com/JSQLParser/JSqlParser/commit/56c2dfe332b5ee8) Tomer Shay (Shimshi) *2021-03-14 20:09:12* @@ -3719,7 +4591,7 @@ Changelog of JSqlParser. **bug fix (#769)** -* Co-authored-by: Kunal Jha <kjha@zalando-11116.corp.ad.zalando.net> +* Co-authored-by: Kunal Jha <kjha@zalando-11116.corp.ad.zalando.net> [7234d](https://github.com/JSQLParser/JSqlParser/commit/7234de1d65ccf1b) Kunal jha *2021-02-05 17:46:15* @@ -3730,12 +4602,12 @@ Changelog of JSqlParser. **Array contructor support (#1105)** -* Array contructor support array[[1, 2], [id1, id2]] -* ARRAYLITERAL->ARRAY_LITERAL -* Fix empty array -* Support ARRAY as DEFAULT value in CREATE TABLE -* https://github.com/JSQLParser/JSqlParser/issues/970#issue-594819872 -* fix empty Array +* Array contructor support array[[1, 2], [id1, id2]] +* ARRAYLITERAL->ARRAY_LITERAL +* Fix empty array +* Support ARRAY as DEFAULT value in CREATE TABLE +* https://github.com/JSQLParser/JSqlParser/issues/970#issue-594819872 +* fix empty Array [43c28](https://github.com/JSQLParser/JSqlParser/commit/43c282deaa05555) Роман Зотов *2021-02-04 19:28:49* @@ -3746,13 +4618,13 @@ Changelog of JSqlParser. **Partial support construct tuple as simple expression (#1107)** -* SELECT (1,2) +* SELECT (1,2) [2065f](https://github.com/JSQLParser/JSqlParser/commit/2065fedb5b4c318) Роман Зотов *2021-01-31 22:58:08* **support create table parameters without columns, parameter values any names (#1106)** -* CREATE TEMPORARY TABLE t1 WITH (APPENDONLY=true,ORIENTATION=column,COMPRESSTYPE=zlib,OIDS=FALSE) ON COMMIT DROP AS SELECT column FROM t2 +* CREATE TEMPORARY TABLE t1 WITH (APPENDONLY=true,ORIENTATION=column,COMPRESSTYPE=zlib,OIDS=FALSE) ON COMMIT DROP AS SELECT column FROM t2 [f2e74](https://github.com/JSQLParser/JSqlParser/commit/f2e74f15cd63d87) Роман Зотов *2021-01-31 22:53:09* @@ -3938,8 +4810,8 @@ Changelog of JSqlParser. **support IN with value (#1065)** -* Co-authored-by: Jan Monterrubio <Jan.Monterrubio@Cerner.com> -* Co-authored-by: Tobias <t.warneke@gmx.net> +* Co-authored-by: Jan Monterrubio <Jan.Monterrubio@Cerner.com> +* Co-authored-by: Tobias <t.warneke@gmx.net> [8c7ee](https://github.com/JSQLParser/JSqlParser/commit/8c7ee289e78d07d) Jan Monterrubio *2020-11-22 19:29:27* @@ -3960,11 +4832,11 @@ Changelog of JSqlParser. **Support CreateSynonym statement (#1064)** -* visual -* add synonym support -* add tests -* exclude keyword -* Co-authored-by: Jan Monterrubio <Jan.Monterrubio@Cerner.com> +* visual +* add synonym support +* add tests +* exclude keyword +* Co-authored-by: Jan Monterrubio <Jan.Monterrubio@Cerner.com> [17e26](https://github.com/JSQLParser/JSqlParser/commit/17e2633e46778c6) Jan Monterrubio *2020-11-06 21:45:14* @@ -3975,352 +4847,352 @@ Changelog of JSqlParser. **Validation visitor framework (#1045)** -* * add with prefix for fluent setters. -* https://github.com/JSQLParser/JSqlParser/issues/1004 -* add getters -* * add with prefix for fluent setters. (revert to chaining setters, do -* not break current api) -* https://github.com/JSQLParser/JSqlParser/issues/1004 -* * add with prefix for fluent setters. (revert to chaining setters, do -* not break current api) -* https://github.com/JSQLParser/JSqlParser/issues/1004 -* use new methods within testcases -* use new methods within testcases -* use new methods within testcases -* use new methods within testcases -* use new methods within testcases -* use new methods within testcases -* use new methods within testcases -* use new methods within testcases -* remove create() methods - they do not add enough value to be justified -* * use new methods within testcases -* add some constructors -* fix and add "with" / "add" methods -* * use new methods within testcases -* * use new methods within testcases -* add some constructors -* * renamed constant -* use new methods within testcases -* use new methods within testcases -* use new methods within testcases -* use new methods within testcases -* * use new methods within testcases -* add some with-methods -* add getter/setter named after the field without abbrivation -* * use new methods within testcases -* remove empty implicit constructor -* return the deparsed Statement - object -* compare object tree -* compare object tree -* * fix ObjectTreeToStringStyle -* compare object tree -* remove casts not needed -* * use new methods within testcases -* add some "set" "with" "add" methods missing -* * use new methods within testcases -* add empty constructors and override with-/add-methods returning concrete -* type -* * add ReflectionModelTest -* * use new methods within testcases -* fix checkstyle errors -* license header -* remove test-classes from ReflectionModelTest -* remove visitoradapter-classes from ReflectionModelTest -* * add SelectDeParser(StringBuilder) -* remove overriding setters/getters of buffer -* #1007 -* push to synbee-contrib -* org.synbee.commons.contrib:jsqlparser:3.2-0.0.6-SNAPSHOT -* add ValidationUtil for simple validation of one or more statements -* remove overrides of -* getCause -* printStackTrace variants -* why add an additional cause ? -* set cause.getMessage() the message within constructor -* JSQLParserException(Throwable cause), othewise cause.toString() will be -* set as default. -* add ValidationVisitor showcase -* https://github.com/JSQLParser/JSqlParser/issues/1005 -* add ValidationUtil for simple validation of one or more statements -* remove overrides of -* getCause -* printStackTrace variants -* why add an additional cause ? -* set cause.getMessage() the message within constructor -* JSQLParserException(Throwable cause), othewise cause.toString() will be -* set as default. -* visit(ShowTablesStatement) -* copyright/license -* add stubs (use deparsers as template) -* Merge branch 'master.validate' of -* https://github.com/gitmotte/JSqlParser.git into master.validate -* add ValidationVisitor showcase -* https://github.com/JSQLParser/JSqlParser/issues/1005 -* add ValidationUtil for simple validation of one or more statements -* remove overrides of -* getCause -* printStackTrace variants -* why add an additional cause ? -* set cause.getMessage() the message within constructor -* JSQLParserException(Throwable cause), othewise cause.toString() will be -* set as default. -* visit(ShowTablesStatement) -* add stubs (use deparsers as template) -* Merge branch 'master.validate' of -* https://github.com/gitmotte/JSqlParser.git into master.validate -* add tests for ValidationUtil -* + implements OrderByVisitor -* split Expressionvalidator which implements both ItemsListVisitor and -* Expressionvisitor into Expressionvalidator and ItemListValidator -* Merge branch 'github.validate' -* implement upsertvalidator -* add copyright -* validate through given ValidationCapability's -* * switch to new method forced by -* ValidationCapability.validate(ValidationContext context, -* Consumer<String> errorMessageConsumer); -* add AllowedTypesValidation -* add FeatureConfiguration -* use FeatureConfiguration within parser -* repair pom.xml -* repair pom.xml -* repair pom.xml -* repair pom.xml -* * make FeatureConfiguration not a singleton any more -* CCJSqlParser extends AbstractJSqlParser<CCJSqlParser> -* add FeaturesAllowed for testing against features allowed -* implement some Validators -* basic implementation of DatabaseMetaDataValidation / -* JdbcDatabaseMetaDataCapability -* moving classes to sub-packages -* * moving classes to sub-packages -* fixing some bugs -* repair pom.xml -* add and fix validations -* add javadoc -* * force definition of ```public String getMessage(Feature feature)``` -* in FeatureSetValidation -* allow all objects as feature-value - this may be needed by the parser, -* if a none-boolean configuration is needed -* impl. -* SelectValidator.visit(PlainSelect) -* OrderByValidator -* add Version-enums -* impl. -* InsertValidator -* multiple implementations of visit(SubSelect) -> forward to -* SelectValidator -* add some known features to SqlServerVersion -* refactoring enum-name should be upper case -* add ansi sql enum -* refactoring enum-name should be upper case -* implement limitvalidator -* + validateOffset -* + validateFetch -* + validate Pivot, UnPivot, PivotXml -* + implement DropValidator -* change testcase to image a more probably usecase -* * add javadoc and -* predefined sets for EXECUTE, ALTER, DROP -* allow to combine FeatureSets -* * implement executevalidator -* implement ExpressionValidator -* implement GrantValidator -* javadoc and complete SELECT constant -* use utility methods from AbstractValidator -* more user friendly names -* javadoc -* add subtypes for ValidationException -* ValidationParseException -* DatabaseException -* UnexpectedValidationException -* and change Set<String> errors to Set<ValidationException> for collect. -* javadoc & rename exception -* rename method -* extract parsing task into package - private class for {@link -* ValidationUtil} to parse the statements -* within it's own {@link ValidationCapability} -* add null-check for parsedStatement -* bugfix - do not collect duplicates -* implement toString() for -* ValidationError -* ValidationException -* add simple caching -* + validateOptionalFromItem(s) -* * implement GroupByValidator -* implement merge-validator -* renaming ItemListValidator -> ItemsListValidator -* + validateOptionalItemsList -* + implement ReplaceValidator -* + use validateOptionalColumns, validateOptionalExpression where possible -* * remove validateOptionalColumns -> switch to -* validateOptionalExpressions -* move validateOptionalOrderByElements to AbstractValidator -* add validateOptional in AbstractValidator -* add validateOptionalList in AbstractValidator -* + SetStatementValidator -* + ValuesStatementValidator -* + UseStatementValidator -* * implement UpdateValidator -* * implement ShowStatementValidator/ShowColumnsStatementValidator -* * implement UpdateValidator -* * add Feature.jdbcParameter, Feature.jdbcNamedParameter, to all -* featuresets -* + Version.getFeaturesClone -* add javadoc to Version-enum-constructors -* + validateOptionalFeature -* * implement DeleteValidator -* ... -* fix typo -* small optimization -* * move method getFeaturesClone to FeatureSet -* implement join - validation -* add copy(), add(Collection), remove(*) methods to FeaturesAllowed -* * add join - features to sqlserver, h2 -* implementations -* bugfix - merging the errors -* copyright -* https://github.com/JSQLParser/JSqlParser/issues/1022 -* add more fine granular control for setOperations -* fix nullpointerexception -* add more fine granular control for comments -* add Features supported -* * add javadoc -* add features to *Version-files -* extract methods isNotEmpty -* check for isNotEmpty -* * add features to *Version-files -* always parse net.sf.jsqlparser.statement.Statements and validate the -* list of included net.sf.jsqlparser.statement.Statement's -* add known mariadb features -* new names-set for FeaturesAllowed -* new names-set for FeaturesAllowed -* new names-set for FeaturesAllowed -* add ature.withItem, Feature.withItemRecursive to H2 -* Feature.setOperation, Feature.setOperationUnion, -* Feature.setOperationIntersect, Feature.setOperationExcept, -* for MariaDb -* add features to SQLServer -* Merge branch 'master.orig' into github.validate -* @Override() -> @Override -* fix typing error "joinStaight" > joinStraight -* rename Feature "insertValues" -> "values" and use "insertValues" for -* INSERT INTO ... VALUES -* add javadoc -* add Feature.selectGroupByGroupingSets to PostgresqlVersion -* implement basic OracleVersion -* add Feature.mySql* - also supported by mariadb -* add some more finegraned control over "drop" Feature. -* drop, -* dropTable, -* dropIndex, -* dropView, -* dropSchema, -* dropSequence, -* dropIfExists, -* complete FeaturesAllowed groups INSERT/UPDATE/DELETE/MERGE/DML -* add link to documentation -* fix - duplicate use of feature "function" - the use of functions in -* statements and "createFunction" as a ddl statement -* TODO this feature seams very close to a jsqlparser-user usecase -* * implement MySqlVersion -* replace feature Feature.dropIfExists by features dropTableIfExists, -* dropIndexIfExists, dropViewIfExists, dropSchemaIfExists, -* dropSequenceIfExists -* add methods FeatureSet.getNotContained FeatureSet.retainAll -* remove HSQLDBVersion - do not support this variant -* remove HSQLDBVersion - do not support this variant -* add unit-test -* + add unittests for -* UpdateValidator -* DeleteValidator -* add stubs for all other Validator-classes -* + ModifyableFeatureSet -* add some utility-methods in ValidationTestAsserts -* complete unit-tests for InsertValidator -* remote Feature.insertReturningExpressionList for Oracle - -* returning_clause requires INTO clause (only PL/SQL) -* add some more select validation tests -* add DropValidatorTests -* add DropValidatorTests -* add CreateTableValidatorTests -* add CreateTableValidatorTests -* add ExpressionValidatorTests -* add OrderByValidatorTest -* use isNotEmpty -* implement GroupByValidatorTest -* implement CreateSequenceValidatorTest -* remove @Ignore - test is ok -* implement CreateIndexValidatorTest -* implement CreateViewValidatorTest -* enable validation of Feature.commentOnView (#1024 is merged already) -* change format of #toString() for better readability -* * implement MergeValidatorTest -* implement ReplaceValidatorTest -* implement StatementValidatorTest -* rename -* ValidationUtil -> Validation -* ValidatorUtil -> ValidationUtil -* add testcases for ValidationUtil -* add DatabaseMetaDataValidationTest -* checkstyle fix -* add copyright statement -* add unit-tests for show tables, show column, show statements -* * add ExecuteValidatorTest -* as there is a difference between execute <procedure> and execute -* [immediate] <dynamic sql> with USING expr, ... remove support for -* execute on MYSQL, MARIADB, ORACLE -* * add ExecuteValidatorTest for CALL fnName (mysql, mariadb, postgres) -* add upsertvalidatortest -* add GrantValidatorTest -* add AlterSequenceValidatorTest -* add AlterSequenceValidatorTest -* add AlterViewValidatorTest -* add AlterValidatorTest -* replace != null by isNotEmpty on collections -* fix formatting -* add validate commit -* add validate block -* add DeclareStatementValidatorTest -* let NamesLookup implement UnaryOperator<String> -* let NamesLookup implement UnaryOperator<String> -* add javadoc -* add more DatabaseMetaDataValidationTest's -* extract JdbcDatabaseMetaDataCapability.splitAndValidateMinMax -* add pivot/unpivot/pivotxml validation testcases -* add testcase for Feature.tableFunction -* add test for lateral joins and subjoins -* add testValidationRowMovementOption -* add values validator test -* move tests to LimitValidatorTest -* move tests to UseStatementValidatorTest -* add tests for SET - statements -* fix checkstyle error -* new serialVersionUID -* add validation for NamedObject not existing -* need table/view reference to validate column names -* fix typo -* fix errormessage (Arrays.toString(types)) -* add trigger, alias -* return null, instead of throwing exception, if not found -* extract NamesLookup to own file (jdk-bug enum inner classes) -* fix name-check AlterOperation.ALTER -* fix error message -* remove methods not needed (they only delegate to ValidationContext) -* add tests - validate metadata -* fix compile error -* fix columnExists check - depending on the statement the prefix is an -* alias, a table/view or it has no prefix (need to lookup within all -* related tables/views) -* fix javadoc warnings +* * add with prefix for fluent setters. +* https://github.com/JSQLParser/JSqlParser/issues/1004 +* add getters +* * add with prefix for fluent setters. (revert to chaining setters, do +* not break current api) +* https://github.com/JSQLParser/JSqlParser/issues/1004 +* * add with prefix for fluent setters. (revert to chaining setters, do +* not break current api) +* https://github.com/JSQLParser/JSqlParser/issues/1004 +* use new methods within testcases +* use new methods within testcases +* use new methods within testcases +* use new methods within testcases +* use new methods within testcases +* use new methods within testcases +* use new methods within testcases +* use new methods within testcases +* remove create() methods - they do not add enough value to be justified +* * use new methods within testcases +* add some constructors +* fix and add "with" / "add" methods +* * use new methods within testcases +* * use new methods within testcases +* add some constructors +* * renamed constant +* use new methods within testcases +* use new methods within testcases +* use new methods within testcases +* use new methods within testcases +* * use new methods within testcases +* add some with-methods +* add getter/setter named after the field without abbrivation +* * use new methods within testcases +* remove empty implicit constructor +* return the deparsed Statement - object +* compare object tree +* compare object tree +* * fix ObjectTreeToStringStyle +* compare object tree +* remove casts not needed +* * use new methods within testcases +* add some "set" "with" "add" methods missing +* * use new methods within testcases +* add empty constructors and override with-/add-methods returning concrete +* type +* * add ReflectionModelTest +* * use new methods within testcases +* fix checkstyle errors +* license header +* remove test-classes from ReflectionModelTest +* remove visitoradapter-classes from ReflectionModelTest +* * add SelectDeParser(StringBuilder) +* remove overriding setters/getters of buffer +* #1007 +* push to synbee-contrib +* org.synbee.commons.contrib:jsqlparser:3.2-0.0.6-SNAPSHOT +* add ValidationUtil for simple validation of one or more statements +* remove overrides of +* getCause +* printStackTrace variants +* why add an additional cause ? +* set cause.getMessage() the message within constructor +* JSQLParserException(Throwable cause), othewise cause.toString() will be +* set as default. +* add ValidationVisitor showcase +* https://github.com/JSQLParser/JSqlParser/issues/1005 +* add ValidationUtil for simple validation of one or more statements +* remove overrides of +* getCause +* printStackTrace variants +* why add an additional cause ? +* set cause.getMessage() the message within constructor +* JSQLParserException(Throwable cause), othewise cause.toString() will be +* set as default. +* visit(ShowTablesStatement) +* copyright/license +* add stubs (use deparsers as template) +* Merge branch 'master.validate' of +* https://github.com/gitmotte/JSqlParser.git into master.validate +* add ValidationVisitor showcase +* https://github.com/JSQLParser/JSqlParser/issues/1005 +* add ValidationUtil for simple validation of one or more statements +* remove overrides of +* getCause +* printStackTrace variants +* why add an additional cause ? +* set cause.getMessage() the message within constructor +* JSQLParserException(Throwable cause), othewise cause.toString() will be +* set as default. +* visit(ShowTablesStatement) +* add stubs (use deparsers as template) +* Merge branch 'master.validate' of +* https://github.com/gitmotte/JSqlParser.git into master.validate +* add tests for ValidationUtil +* + implements OrderByVisitor +* split Expressionvalidator which implements both ItemsListVisitor and +* Expressionvisitor into Expressionvalidator and ItemListValidator +* Merge branch 'github.validate' +* implement upsertvalidator +* add copyright +* validate through given ValidationCapability's +* * switch to new method forced by +* ValidationCapability.validate(ValidationContext context, +* Consumer<String> errorMessageConsumer); +* add AllowedTypesValidation +* add FeatureConfiguration +* use FeatureConfiguration within parser +* repair pom.xml +* repair pom.xml +* repair pom.xml +* repair pom.xml +* * make FeatureConfiguration not a singleton any more +* CCJSqlParser extends AbstractJSqlParser<CCJSqlParser> +* add FeaturesAllowed for testing against features allowed +* implement some Validators +* basic implementation of DatabaseMetaDataValidation / +* JdbcDatabaseMetaDataCapability +* moving classes to sub-packages +* * moving classes to sub-packages +* fixing some bugs +* repair pom.xml +* add and fix validations +* add javadoc +* * force definition of ```public String getMessage(Feature feature)``` +* in FeatureSetValidation +* allow all objects as feature-value - this may be needed by the parser, +* if a none-boolean configuration is needed +* impl. +* SelectValidator.visit(PlainSelect) +* OrderByValidator +* add Version-enums +* impl. +* InsertValidator +* multiple implementations of visit(SubSelect) -> forward to +* SelectValidator +* add some known features to SqlServerVersion +* refactoring enum-name should be upper case +* add ansi sql enum +* refactoring enum-name should be upper case +* implement limitvalidator +* + validateOffset +* + validateFetch +* + validate Pivot, UnPivot, PivotXml +* + implement DropValidator +* change testcase to image a more probably usecase +* * add javadoc and +* predefined sets for EXECUTE, ALTER, DROP +* allow to combine FeatureSets +* * implement executevalidator +* implement ExpressionValidator +* implement GrantValidator +* javadoc and complete SELECT constant +* use utility methods from AbstractValidator +* more user friendly names +* javadoc +* add subtypes for ValidationException +* ValidationParseException +* DatabaseException +* UnexpectedValidationException +* and change Set<String> errors to Set<ValidationException> for collect. +* javadoc & rename exception +* rename method +* extract parsing task into package - private class for {@link +* ValidationUtil} to parse the statements +* within it's own {@link ValidationCapability} +* add null-check for parsedStatement +* bugfix - do not collect duplicates +* implement toString() for +* ValidationError +* ValidationException +* add simple caching +* + validateOptionalFromItem(s) +* * implement GroupByValidator +* implement merge-validator +* renaming ItemListValidator -> ItemsListValidator +* + validateOptionalItemsList +* + implement ReplaceValidator +* + use validateOptionalColumns, validateOptionalExpression where possible +* * remove validateOptionalColumns -> switch to +* validateOptionalExpressions +* move validateOptionalOrderByElements to AbstractValidator +* add validateOptional in AbstractValidator +* add validateOptionalList in AbstractValidator +* + SetStatementValidator +* + ValuesStatementValidator +* + UseStatementValidator +* * implement UpdateValidator +* * implement ShowStatementValidator/ShowColumnsStatementValidator +* * implement UpdateValidator +* * add Feature.jdbcParameter, Feature.jdbcNamedParameter, to all +* featuresets +* + Version.getFeaturesClone +* add javadoc to Version-enum-constructors +* + validateOptionalFeature +* * implement DeleteValidator +* ... +* fix typo +* small optimization +* * move method getFeaturesClone to FeatureSet +* implement join - validation +* add copy(), add(Collection), remove(*) methods to FeaturesAllowed +* * add join - features to sqlserver, h2 +* implementations +* bugfix - merging the errors +* copyright +* https://github.com/JSQLParser/JSqlParser/issues/1022 +* add more fine granular control for setOperations +* fix nullpointerexception +* add more fine granular control for comments +* add Features supported +* * add javadoc +* add features to *Version-files +* extract methods isNotEmpty +* check for isNotEmpty +* * add features to *Version-files +* always parse net.sf.jsqlparser.statement.Statements and validate the +* list of included net.sf.jsqlparser.statement.Statement's +* add known mariadb features +* new names-set for FeaturesAllowed +* new names-set for FeaturesAllowed +* new names-set for FeaturesAllowed +* add ature.withItem, Feature.withItemRecursive to H2 +* Feature.setOperation, Feature.setOperationUnion, +* Feature.setOperationIntersect, Feature.setOperationExcept, +* for MariaDb +* add features to SQLServer +* Merge branch 'master.orig' into github.validate +* @Override() -> @Override +* fix typing error "joinStaight" > joinStraight +* rename Feature "insertValues" -> "values" and use "insertValues" for +* INSERT INTO ... VALUES +* add javadoc +* add Feature.selectGroupByGroupingSets to PostgresqlVersion +* implement basic OracleVersion +* add Feature.mySql* - also supported by mariadb +* add some more finegraned control over "drop" Feature. +* drop, +* dropTable, +* dropIndex, +* dropView, +* dropSchema, +* dropSequence, +* dropIfExists, +* complete FeaturesAllowed groups INSERT/UPDATE/DELETE/MERGE/DML +* add link to documentation +* fix - duplicate use of feature "function" - the use of functions in +* statements and "createFunction" as a ddl statement +* TODO this feature seams very close to a jsqlparser-user usecase +* * implement MySqlVersion +* replace feature Feature.dropIfExists by features dropTableIfExists, +* dropIndexIfExists, dropViewIfExists, dropSchemaIfExists, +* dropSequenceIfExists +* add methods FeatureSet.getNotContained FeatureSet.retainAll +* remove HSQLDBVersion - do not support this variant +* remove HSQLDBVersion - do not support this variant +* add unit-test +* + add unittests for +* UpdateValidator +* DeleteValidator +* add stubs for all other Validator-classes +* + ModifyableFeatureSet +* add some utility-methods in ValidationTestAsserts +* complete unit-tests for InsertValidator +* remote Feature.insertReturningExpressionList for Oracle - +* returning_clause requires INTO clause (only PL/SQL) +* add some more select validation tests +* add DropValidatorTests +* add DropValidatorTests +* add CreateTableValidatorTests +* add CreateTableValidatorTests +* add ExpressionValidatorTests +* add OrderByValidatorTest +* use isNotEmpty +* implement GroupByValidatorTest +* implement CreateSequenceValidatorTest +* remove @Ignore - test is ok +* implement CreateIndexValidatorTest +* implement CreateViewValidatorTest +* enable validation of Feature.commentOnView (#1024 is merged already) +* change format of #toString() for better readability +* * implement MergeValidatorTest +* implement ReplaceValidatorTest +* implement StatementValidatorTest +* rename +* ValidationUtil -> Validation +* ValidatorUtil -> ValidationUtil +* add testcases for ValidationUtil +* add DatabaseMetaDataValidationTest +* checkstyle fix +* add copyright statement +* add unit-tests for show tables, show column, show statements +* * add ExecuteValidatorTest +* as there is a difference between execute <procedure> and execute +* [immediate] <dynamic sql> with USING expr, ... remove support for +* execute on MYSQL, MARIADB, ORACLE +* * add ExecuteValidatorTest for CALL fnName (mysql, mariadb, postgres) +* add upsertvalidatortest +* add GrantValidatorTest +* add AlterSequenceValidatorTest +* add AlterSequenceValidatorTest +* add AlterViewValidatorTest +* add AlterValidatorTest +* replace != null by isNotEmpty on collections +* fix formatting +* add validate commit +* add validate block +* add DeclareStatementValidatorTest +* let NamesLookup implement UnaryOperator<String> +* let NamesLookup implement UnaryOperator<String> +* add javadoc +* add more DatabaseMetaDataValidationTest's +* extract JdbcDatabaseMetaDataCapability.splitAndValidateMinMax +* add pivot/unpivot/pivotxml validation testcases +* add testcase for Feature.tableFunction +* add test for lateral joins and subjoins +* add testValidationRowMovementOption +* add values validator test +* move tests to LimitValidatorTest +* move tests to UseStatementValidatorTest +* add tests for SET - statements +* fix checkstyle error +* new serialVersionUID +* add validation for NamedObject not existing +* need table/view reference to validate column names +* fix typo +* fix errormessage (Arrays.toString(types)) +* add trigger, alias +* return null, instead of throwing exception, if not found +* extract NamesLookup to own file (jdk-bug enum inner classes) +* fix name-check AlterOperation.ALTER +* fix error message +* remove methods not needed (they only delegate to ValidationContext) +* add tests - validate metadata +* fix compile error +* fix columnExists check - depending on the statement the prefix is an +* alias, a table/view or it has no prefix (need to lookup within all +* related tables/views) +* fix javadoc warnings [8c735](https://github.com/JSQLParser/JSqlParser/commit/8c735be5b179e51) gitmotte *2020-11-06 21:12:25* **Support Create table LIKE (#1066)** -* fixes #413 -* add coverage -* Co-authored-by: chyun <chyun_wu@163.com> +* fixes #413 +* add coverage +* Co-authored-by: chyun <chyun_wu@163.com> [ac746](https://github.com/JSQLParser/JSqlParser/commit/ac7462286ae15b9) Chyun *2020-11-06 21:05:09* @@ -4331,12 +5203,12 @@ Changelog of JSqlParser. **Bump junit from 4.12 to 4.13.1 (#1063)** -* Bumps [junit](https://github.com/junit-team/junit4) from 4.12 to 4.13.1. -* - [Release notes](https://github.com/junit-team/junit4/releases) -* - [Changelog](https://github.com/junit-team/junit4/blob/main/doc/ReleaseNotes4.12.md) -* - [Commits](https://github.com/junit-team/junit4/compare/r4.12...r4.13.1) -* Signed-off-by: dependabot[bot] <support@github.com> -* Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> +* Bumps [junit](https://github.com/junit-team/junit4) from 4.12 to 4.13.1. +* - [Release notes](https://github.com/junit-team/junit4/releases) +* - [Changelog](https://github.com/junit-team/junit4/blob/main/doc/ReleaseNotes4.12.md) +* - [Commits](https://github.com/junit-team/junit4/compare/r4.12...r4.13.1) +* Signed-off-by: dependabot[bot] <support@github.com> +* Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> [f9a11](https://github.com/JSQLParser/JSqlParser/commit/f9a115c582dd59b) dependabot[bot] *2020-10-13 12:26:17* @@ -4352,8 +5224,8 @@ Changelog of JSqlParser. **support FILTER not only for window function (#1046)** -* support FILTER not only for window function -* Fixed imports +* support FILTER not only for window function +* Fixed imports [f32fa](https://github.com/JSQLParser/JSqlParser/commit/f32fa6137d6161b) Роман Зотов *2020-10-05 19:45:36* @@ -4374,7 +5246,7 @@ Changelog of JSqlParser. **Retain original value in TimestampValue (#1057)** -* Co-authored-by: Enrico Olivelli <enrico.olivelli@diennea.com> +* Co-authored-by: Enrico Olivelli <enrico.olivelli@diennea.com> [622f9](https://github.com/JSQLParser/JSqlParser/commit/622f9aebb3ebce7) Enrico Olivelli *2020-10-04 19:51:01* @@ -4385,8 +5257,8 @@ Changelog of JSqlParser. **Addons/fixes for Fluent API (#1049)** -* fix unittests for setter/wither methods with primitive arguments -* add missing withAscDescPresent +* fix unittests for setter/wither methods with primitive arguments +* add missing withAscDescPresent [8165e](https://github.com/JSQLParser/JSqlParser/commit/8165e29cb081080) gitmotte *2020-10-04 19:20:43* @@ -4432,8 +5304,8 @@ Changelog of JSqlParser. **bugfix #720 #991: supporting SELECT "conditions" (#1032)** -* bugfix issue #1020: JSON type in MySQL not supported in v3.2 -* bugfix issue #720 #991: supporting SELECT "CONDITIONS" +* bugfix issue #1020: JSON type in MySQL not supported in v3.2 +* bugfix issue #720 #991: supporting SELECT "CONDITIONS" [9e26b](https://github.com/JSQLParser/JSqlParser/commit/9e26b76ddbc626f) suiwenbo *2020-08-25 22:29:41* @@ -4449,101 +5321,101 @@ Changelog of JSqlParser. **Fluent builder api #1004 (#1014)** -* https://github.com/JSQLParser/JSqlParser/issues/1004 -* create(...) methods -* chaining - methods returning "this" -* overwrite chaining - methods of abstract parents/interfaces for -* returning concrete type -* add<Name> methods on collection-fields with varargs-parameter -* add public T get<Name>(Class<T>) - casting and returning an inner -* interface-type -* 1004 add chaining - methods returning "this" -* #1004 add chaining - methods returning "this" -* * add<Name> methods on collection-fields with varargs-parameter -* add<Name> methods on collection-fields with collection-parameter -* https://github.com/JSQLParser/JSqlParser/issues/1004 -* * add chaining - methods returning "this" -* add<Name> methods on collection-fields with varargs-parameter -* add<Name> methods on collection-fields with collection-parameter -* https://github.com/JSQLParser/JSqlParser/issues/1004 -* * add public T get<Name>(Class<T>) - casting and returning the concrete -* type -* https://github.com/JSQLParser/JSqlParser/issues/1004 -* * add public T get<Name>(Class<T>) - casting and returning the concrete -* type (swap Class<? extends E> for Class<E>) -* https://github.com/JSQLParser/JSqlParser/issues/1004 -* * overwrite chaining - methods of abstract parents/interfaces for -* returning concrete type -* https://github.com/JSQLParser/JSqlParser/issues/1004 -* * add with prefix for fluent setters. -* https://github.com/JSQLParser/JSqlParser/issues/1004 -* add getters -* * add with prefix for fluent setters. (revert to chaining setters, do -* not break current api) -* https://github.com/JSQLParser/JSqlParser/issues/1004 -* * add with prefix for fluent setters. (revert to chaining setters, do -* not break current api) -* https://github.com/JSQLParser/JSqlParser/issues/1004 -* use new methods within testcases -* use new methods within testcases -* use new methods within testcases -* use new methods within testcases -* use new methods within testcases -* use new methods within testcases -* use new methods within testcases -* use new methods within testcases -* remove create() methods - they do not add enough value to be justified -* * use new methods within testcases -* add some constructors -* fix and add "with" / "add" methods -* * use new methods within testcases -* * use new methods within testcases -* add some constructors -* * renamed constant -* use new methods within testcases -* use new methods within testcases -* use new methods within testcases -* use new methods within testcases -* * use new methods within testcases -* add some with-methods -* add getter/setter named after the field without abbrivation -* * use new methods within testcases -* remove empty implicit constructor -* return the deparsed Statement - object -* compare object tree -* compare object tree -* * fix ObjectTreeToStringStyle -* compare object tree -* remove casts not needed -* * use new methods within testcases -* add some "set" "with" "add" methods missing -* * use new methods within testcases -* add empty constructors and override with-/add-methods returning concrete -* type -* * add ReflectionModelTest -* * use new methods within testcases -* fix checkstyle errors -* license header -* remove test-classes from ReflectionModelTest -* remove visitoradapter-classes from ReflectionModelTest -* remove duplicate import declaration (checkstyle error) -* * fix RandomUtils to support used java.sql.* types -* fix RandomUtils to support enums -* fix RandomUtils to map objects by its interfaces and super-classes -* filter method "setASTNode" - do not test setters (cannot randomly -* create a SimpleNode) -* add javadoc, stating that this is a marker interface -* https://github.com/JSQLParser/JSqlParser/pull/1014#discussion_r454761902 -* revert formatting change -* https://github.com/JSQLParser/JSqlParser/pull/1014#discussion_r454762463 -* change to EXEC_TYPE.EXECUTE just so the assertion didn't change -* https://github.com/JSQLParser/JSqlParser/pull/1014#discussion_r454763565 -* try to revert format changes -* https://github.com/JSQLParser/JSqlParser/pull/1014#discussion_r454800430 -* try to revert format changes -* https://github.com/JSQLParser/JSqlParser/pull/1014#discussion_r454800430 -* remove brackets on @Override() -> @Override -* add with-methods to new fields +* https://github.com/JSQLParser/JSqlParser/issues/1004 +* create(...) methods +* chaining - methods returning "this" +* overwrite chaining - methods of abstract parents/interfaces for +* returning concrete type +* add<Name> methods on collection-fields with varargs-parameter +* add public T get<Name>(Class<T>) - casting and returning an inner +* interface-type +* 1004 add chaining - methods returning "this" +* #1004 add chaining - methods returning "this" +* * add<Name> methods on collection-fields with varargs-parameter +* add<Name> methods on collection-fields with collection-parameter +* https://github.com/JSQLParser/JSqlParser/issues/1004 +* * add chaining - methods returning "this" +* add<Name> methods on collection-fields with varargs-parameter +* add<Name> methods on collection-fields with collection-parameter +* https://github.com/JSQLParser/JSqlParser/issues/1004 +* * add public T get<Name>(Class<T>) - casting and returning the concrete +* type +* https://github.com/JSQLParser/JSqlParser/issues/1004 +* * add public T get<Name>(Class<T>) - casting and returning the concrete +* type (swap Class<? extends E> for Class<E>) +* https://github.com/JSQLParser/JSqlParser/issues/1004 +* * overwrite chaining - methods of abstract parents/interfaces for +* returning concrete type +* https://github.com/JSQLParser/JSqlParser/issues/1004 +* * add with prefix for fluent setters. +* https://github.com/JSQLParser/JSqlParser/issues/1004 +* add getters +* * add with prefix for fluent setters. (revert to chaining setters, do +* not break current api) +* https://github.com/JSQLParser/JSqlParser/issues/1004 +* * add with prefix for fluent setters. (revert to chaining setters, do +* not break current api) +* https://github.com/JSQLParser/JSqlParser/issues/1004 +* use new methods within testcases +* use new methods within testcases +* use new methods within testcases +* use new methods within testcases +* use new methods within testcases +* use new methods within testcases +* use new methods within testcases +* use new methods within testcases +* remove create() methods - they do not add enough value to be justified +* * use new methods within testcases +* add some constructors +* fix and add "with" / "add" methods +* * use new methods within testcases +* * use new methods within testcases +* add some constructors +* * renamed constant +* use new methods within testcases +* use new methods within testcases +* use new methods within testcases +* use new methods within testcases +* * use new methods within testcases +* add some with-methods +* add getter/setter named after the field without abbrivation +* * use new methods within testcases +* remove empty implicit constructor +* return the deparsed Statement - object +* compare object tree +* compare object tree +* * fix ObjectTreeToStringStyle +* compare object tree +* remove casts not needed +* * use new methods within testcases +* add some "set" "with" "add" methods missing +* * use new methods within testcases +* add empty constructors and override with-/add-methods returning concrete +* type +* * add ReflectionModelTest +* * use new methods within testcases +* fix checkstyle errors +* license header +* remove test-classes from ReflectionModelTest +* remove visitoradapter-classes from ReflectionModelTest +* remove duplicate import declaration (checkstyle error) +* * fix RandomUtils to support used java.sql.* types +* fix RandomUtils to support enums +* fix RandomUtils to map objects by its interfaces and super-classes +* filter method "setASTNode" - do not test setters (cannot randomly +* create a SimpleNode) +* add javadoc, stating that this is a marker interface +* https://github.com/JSQLParser/JSqlParser/pull/1014#discussion_r454761902 +* revert formatting change +* https://github.com/JSQLParser/JSqlParser/pull/1014#discussion_r454762463 +* change to EXEC_TYPE.EXECUTE just so the assertion didn't change +* https://github.com/JSQLParser/JSqlParser/pull/1014#discussion_r454763565 +* try to revert format changes +* https://github.com/JSQLParser/JSqlParser/pull/1014#discussion_r454800430 +* try to revert format changes +* https://github.com/JSQLParser/JSqlParser/pull/1014#discussion_r454800430 +* remove brackets on @Override() -> @Override +* add with-methods to new fields [6cff1](https://github.com/JSQLParser/JSqlParser/commit/6cff161dacc1e6f) gitmotte *2020-08-23 20:07:53* @@ -4559,27 +5431,27 @@ Changelog of JSqlParser. **Support Foreign Key ON UPDATE CASCADE (#1025)** -* https://github.com/JSQLParser/JSqlParser/issues/985 -* add 2 unit-tests for given statements -* https://github.com/JSQLParser/JSqlParser/issues/985 -* fix formating (line width) -* https://github.com/JSQLParser/JSqlParser/issues/985 -* * fix nullpointerexceptions -* add more unittest-assertions -* https://github.com/JSQLParser/JSqlParser/issues/985 -* change order to match the same order as in ForeignKeyIndex -* byAction should not throw an exception (is used by deprecated -* string-setters) -* add unit-tests for ReferentialAction within AlterExpression -* fix toString (added bug on refactoring) -* javadoc -* test set from get on null-values too -* refactoring: add and use ReferentialAction() to evaluate enum -* https://github.com/JSQLParser/JSqlParser/issues/985 -* refactoring: fix parser that order of referential actions does not -* matter -* https://github.com/JSQLParser/JSqlParser/issues/985 -* add empty constructor +* https://github.com/JSQLParser/JSqlParser/issues/985 +* add 2 unit-tests for given statements +* https://github.com/JSQLParser/JSqlParser/issues/985 +* fix formating (line width) +* https://github.com/JSQLParser/JSqlParser/issues/985 +* * fix nullpointerexceptions +* add more unittest-assertions +* https://github.com/JSQLParser/JSqlParser/issues/985 +* change order to match the same order as in ForeignKeyIndex +* byAction should not throw an exception (is used by deprecated +* string-setters) +* add unit-tests for ReferentialAction within AlterExpression +* fix toString (added bug on refactoring) +* javadoc +* test set from get on null-values too +* refactoring: add and use ReferentialAction() to evaluate enum +* https://github.com/JSQLParser/JSqlParser/issues/985 +* refactoring: fix parser that order of referential actions does not +* matter +* https://github.com/JSQLParser/JSqlParser/issues/985 +* add empty constructor [1e88d](https://github.com/JSQLParser/JSqlParser/commit/1e88dd57eb48ebf) gitmotte *2020-08-09 21:01:34* @@ -4605,10 +5477,10 @@ Changelog of JSqlParser. **COMMENT ON VIEW (#1024)** -* * implement COMMENT ON VIEW -* testcase "testCommentOnView" -* https://github.com/JSQLParser/JSqlParser/issues/1023 -* add more asserts +* * implement COMMENT ON VIEW +* testcase "testCommentOnView" +* https://github.com/JSQLParser/JSqlParser/issues/1023 +* add more asserts [449f5](https://github.com/JSQLParser/JSqlParser/commit/449f55219fc39eb) gitmotte *2020-08-09 20:49:05* @@ -4664,18 +5536,18 @@ Changelog of JSqlParser. **Add show tables support (#1015)** -* visual -* implement show tables -* Co-authored-by: Jan Monterrubio <Jan.Monterrubio@Cerner.com> +* visual +* implement show tables +* Co-authored-by: Jan Monterrubio <Jan.Monterrubio@Cerner.com> [c0373](https://github.com/JSQLParser/JSqlParser/commit/c03733b3cfcb758) Jan Monterrubio *2020-07-11 16:43:42* **let all deparsers extend AbstractDeParser (#1007)** -* let all deparsers extend AbstractDeParser -* * add SelectDeParser(StringBuilder) -* remove overriding setters/getters of buffer -* #1007 +* let all deparsers extend AbstractDeParser +* * add SelectDeParser(StringBuilder) +* remove overriding setters/getters of buffer +* #1007 [2b790](https://github.com/JSQLParser/JSqlParser/commit/2b7909c3be31ca8) gitmotte *2020-07-11 16:40:11* @@ -4711,13 +5583,13 @@ Changelog of JSqlParser. **Support options for Explain (#996)** -* visual -* issue-995 -* support verbose -* postgres explain -* tests -* no text -* Co-authored-by: Jan Monterrubio <Jan.Monterrubio@Cerner.com> +* visual +* issue-995 +* support verbose +* postgres explain +* tests +* no text +* Co-authored-by: Jan Monterrubio <Jan.Monterrubio@Cerner.com> [13873](https://github.com/JSQLParser/JSqlParser/commit/1387354712285e4) Jan Monterrubio *2020-06-23 21:52:42* @@ -4728,12 +5600,12 @@ Changelog of JSqlParser. **Support multiple lists for an IN clause (#997)** -* visual -* wip -* cleanup n test -* polish -* lookahead -* Co-authored-by: Jan Monterrubio <Jan.Monterrubio@Cerner.com> +* visual +* wip +* cleanup n test +* polish +* lookahead +* Co-authored-by: Jan Monterrubio <Jan.Monterrubio@Cerner.com> [5de4a](https://github.com/JSQLParser/JSqlParser/commit/5de4ae597fbeda3) Jan Monterrubio *2020-06-20 22:10:38* @@ -4749,8 +5621,8 @@ Changelog of JSqlParser. **Support ALTER SEQUENCE (#980)** -* support alter sequence -* improve coverage +* support alter sequence +* improve coverage [d34c8](https://github.com/JSQLParser/JSqlParser/commit/d34c885ba5a8c93) Jan Monterrubio *2020-05-23 10:16:07* @@ -4796,21 +5668,21 @@ Changelog of JSqlParser. **Implement row movement clause for table creation (#974)** -* visual -* implement row movement -* support row + AS -* Co-authored-by: Jan Monterrubio <Jan.Monterrubio@Cerner.com> +* visual +* implement row movement +* support row + AS +* Co-authored-by: Jan Monterrubio <Jan.Monterrubio@Cerner.com> [79b5f](https://github.com/JSQLParser/JSqlParser/commit/79b5fe9c5681961) Jan Monterrubio *2020-04-28 07:02:51* **Support CREATE SEQUENCE (#977)** -* wip -* wip, some parsing -* support sequence -* implement feature -* delete issue tests -* compile it +* wip +* wip, some parsing +* support sequence +* implement feature +* delete issue tests +* compile it [a6a3c](https://github.com/JSQLParser/JSqlParser/commit/a6a3c616b8994f1) Jan Monterrubio *2020-04-28 07:01:10* @@ -4826,7 +5698,7 @@ Changelog of JSqlParser. **implement feature (#972)** -* Co-authored-by: Jan Monterrubio <Jan.Monterrubio@Cerner.com> +* Co-authored-by: Jan Monterrubio <Jan.Monterrubio@Cerner.com> [aee39](https://github.com/JSQLParser/JSqlParser/commit/aee3947757eecf4) Jan Monterrubio *2020-04-17 21:01:22* @@ -4942,7 +5814,7 @@ Changelog of JSqlParser. **fixes #936** -* fixes #938 +* fixes #938 [39e92](https://github.com/JSQLParser/JSqlParser/commit/39e920df15fefd7) wumpz *2020-02-01 00:08:17* @@ -5048,7 +5920,7 @@ Changelog of JSqlParser. **fixes #899** -* switched to assertj from hamcrest +* switched to assertj from hamcrest [9707e](https://github.com/JSQLParser/JSqlParser/commit/9707e4f0aacff16) wumpz *2019-11-23 23:18:56* @@ -5069,8 +5941,8 @@ Changelog of JSqlParser. **Adding support for STRAIGHT_JOIN in the select clause (#861)** -* Adding support for straight_join in the select clause -* Renaming the field name to reflect that this is a MySQL hint +* Adding support for straight_join in the select clause +* Renaming the field name to reflect that this is a MySQL hint [3cdea](https://github.com/JSQLParser/JSqlParser/commit/3cdea6bd3d9ce21) Tomer Shay (Shimshi) *2019-11-09 20:30:54* @@ -5121,10 +5993,10 @@ Changelog of JSqlParser. **Added support for Oracle UNPIVOT keyword. (#882)** -* Added support for Oracle UNPIVOT keyword. -* Back to original version number. -* Updated imports. -* Added missing import. +* Added support for Oracle UNPIVOT keyword. +* Back to original version number. +* Updated imports. +* Added missing import. [bcc27](https://github.com/JSQLParser/JSqlParser/commit/bcc271870ad767f) Pascal Mulder *2019-10-26 20:35:43* @@ -5285,7 +6157,7 @@ Changelog of JSqlParser. **fixes #648** -* fixes #638 +* fixes #638 [74e02](https://github.com/JSQLParser/JSqlParser/commit/74e02267404da4e) wumpz *2019-08-13 19:19:12* @@ -5631,7 +6503,7 @@ Changelog of JSqlParser. **Support KSQL's WINDOW** -* Add support for KSQL's WINDOW (HOPPING, TUMBLING and SESSION window) +* Add support for KSQL's WINDOW (HOPPING, TUMBLING and SESSION window) [ef911](https://github.com/JSQLParser/JSqlParser/commit/ef9119806146f25) Suyash Garg *2019-06-21 12:00:06* @@ -5757,7 +6629,7 @@ Changelog of JSqlParser. **tests #775** -* removed some not flags from some classes +* removed some not flags from some classes [8dda4](https://github.com/JSQLParser/JSqlParser/commit/8dda4a60a8e558d) wumpz *2019-04-08 21:36:40* @@ -5833,7 +6705,7 @@ Changelog of JSqlParser. **update README.md (#762)** -* update latest version(1.4) +* update latest version(1.4) [13d6a](https://github.com/JSQLParser/JSqlParser/commit/13d6a9fe183a5ab) r548 *2019-03-14 12:19:58* @@ -5889,7 +6761,7 @@ Changelog of JSqlParser. **fixes #649** -* and implemented ! for not and extended not expression +* and implemented ! for not and extended not expression [10e8e](https://github.com/JSQLParser/JSqlParser/commit/10e8e2568eb7711) wumpz *2019-02-23 23:32:38* @@ -5935,39 +6807,39 @@ Changelog of JSqlParser. **Added support for DROP INDEX, ADD UNIQUE INDEX, ALGORITHM and USING (#752)** -* Merge recent changes in the master from the master (#1) -* changed license header to represent the projects dual license -* changed license header to represent the projects dual license -* changed license header to represent the projects dual license -* changed license header to represent the projects dual license -* Added support for comment(s) for column definitions in CREATE TABLE s… (#743) -* Added support for comment(s) for column definitions in CREATE TABLE statements -* Added support for comment(s) for column definitions in CREATE TABLE statements #2 -* To increase code coverage -* To increase code coverage #2 -* Added support for 'ALTER TABLE CHANGE COLUMN' (#741) -* Added support for 'ALTER TABLE CHANGE COLUMN oldName newName columnDefinition'. Please see https://dev.mysql.com/doc/refman/8.0/en/alter-table.html for reference. -* Returned import ordering to avoid conflicts -* Improved the tests somewhat -* Now also test the getOptionalSpecifier() for both cases (null and not-null) -* Expanded tests for ALTER TABLE ... CHANGE -* implemented optimize for, fixes #348 -* implemented optimize for, fixes #348 -* Support for simple informix outer joins. (#745) -* added support for simple informix outer joins -* added some test code -* added support for simple informix outer joins -* added some test code -* more testing for better code coverage -* added support for simple informix outer joins -* added some test code -* more testing for better code coverage -* fixes #747 -* fixes #733 -* fixes #707 -* Update README.md -* Update README.md -* Fix handles the following cases: 1) DROP INDEX 2) ADD UNIQUE INDEX 3) ALGORITHM 4) USING <index type> +* Merge recent changes in the master from the master (#1) +* changed license header to represent the projects dual license +* changed license header to represent the projects dual license +* changed license header to represent the projects dual license +* changed license header to represent the projects dual license +* Added support for comment(s) for column definitions in CREATE TABLE s… (#743) +* Added support for comment(s) for column definitions in CREATE TABLE statements +* Added support for comment(s) for column definitions in CREATE TABLE statements #2 +* To increase code coverage +* To increase code coverage #2 +* Added support for 'ALTER TABLE CHANGE COLUMN' (#741) +* Added support for 'ALTER TABLE CHANGE COLUMN oldName newName columnDefinition'. Please see https://dev.mysql.com/doc/refman/8.0/en/alter-table.html for reference. +* Returned import ordering to avoid conflicts +* Improved the tests somewhat +* Now also test the getOptionalSpecifier() for both cases (null and not-null) +* Expanded tests for ALTER TABLE ... CHANGE +* implemented optimize for, fixes #348 +* implemented optimize for, fixes #348 +* Support for simple informix outer joins. (#745) +* added support for simple informix outer joins +* added some test code +* added support for simple informix outer joins +* added some test code +* more testing for better code coverage +* added support for simple informix outer joins +* added some test code +* more testing for better code coverage +* fixes #747 +* fixes #733 +* fixes #707 +* Update README.md +* Update README.md +* Fix handles the following cases: 1) DROP INDEX 2) ADD UNIQUE INDEX 3) ALGORITHM 4) USING <index type> [2830c](https://github.com/JSQLParser/JSqlParser/commit/2830c17ea226635) Prateek Gupta *2019-02-19 00:44:35* @@ -6033,14 +6905,14 @@ Changelog of JSqlParser. **Support for simple informix outer joins. (#745)** -* added support for simple informix outer joins -* added some test code -* added support for simple informix outer joins -* added some test code -* more testing for better code coverage -* added support for simple informix outer joins -* added some test code -* more testing for better code coverage +* added support for simple informix outer joins +* added some test code +* added support for simple informix outer joins +* added some test code +* more testing for better code coverage +* added support for simple informix outer joins +* added some test code +* more testing for better code coverage [53e24](https://github.com/JSQLParser/JSqlParser/commit/53e247bffdae557) Kurt Schwitters *2019-02-08 05:52:51* @@ -6071,20 +6943,20 @@ Changelog of JSqlParser. **Added support for 'ALTER TABLE CHANGE COLUMN' (#741)** -* Added support for 'ALTER TABLE CHANGE COLUMN oldName newName columnDefinition'. Please see https://dev.mysql.com/doc/refman/8.0/en/alter-table.html for reference. -* Returned import ordering to avoid conflicts -* Improved the tests somewhat -* Now also test the getOptionalSpecifier() for both cases (null and not-null) -* Expanded tests for ALTER TABLE ... CHANGE +* Added support for 'ALTER TABLE CHANGE COLUMN oldName newName columnDefinition'. Please see https://dev.mysql.com/doc/refman/8.0/en/alter-table.html for reference. +* Returned import ordering to avoid conflicts +* Improved the tests somewhat +* Now also test the getOptionalSpecifier() for both cases (null and not-null) +* Expanded tests for ALTER TABLE ... CHANGE [bfb80](https://github.com/JSQLParser/JSqlParser/commit/bfb8023318c31b2) Simon *2019-02-07 14:49:28* **Added support for comment(s) for column definitions in CREATE TABLE s… (#743)** -* Added support for comment(s) for column definitions in CREATE TABLE statements -* Added support for comment(s) for column definitions in CREATE TABLE statements #2 -* To increase code coverage -* To increase code coverage #2 +* Added support for comment(s) for column definitions in CREATE TABLE statements +* Added support for comment(s) for column definitions in CREATE TABLE statements #2 +* To increase code coverage +* To increase code coverage #2 [07b86](https://github.com/JSQLParser/JSqlParser/commit/07b86761d0b4aee) Prateek Gupta *2019-02-07 07:09:26* @@ -6165,13 +7037,13 @@ Changelog of JSqlParser. **allow top keyword as column / table / alias name** -* implemented tests +* implemented tests [9e81e](https://github.com/JSQLParser/JSqlParser/commit/9e81e1592200952) wumpz *2019-01-23 23:00:12* **allow top keyword as column / table / alias name** -* implemented tests +* implemented tests [ed95e](https://github.com/JSQLParser/JSqlParser/commit/ed95e877802f46e) wumpz *2019-01-23 22:58:39* @@ -6197,7 +7069,7 @@ Changelog of JSqlParser. **corrected stackoverflow while tables extraction** -* updated readme +* updated readme [04db1](https://github.com/JSQLParser/JSqlParser/commit/04db124b85dea22) wumpz *2019-01-20 22:33:51* @@ -6208,7 +7080,7 @@ Changelog of JSqlParser. **started describe** -* some cleanup +* some cleanup [25fa3](https://github.com/JSQLParser/JSqlParser/commit/25fa31153a9a2d0) wumpz *2019-01-20 21:48:12* @@ -6224,7 +7096,7 @@ Changelog of JSqlParser. **corrected some failing tests** -* included a regression test for oracle files +* included a regression test for oracle files [aa932](https://github.com/JSQLParser/JSqlParser/commit/aa932c4a28d8021) wumpz *2018-12-30 23:06:59* @@ -6235,8 +7107,8 @@ Changelog of JSqlParser. **Support Alter Table Drop Constraint If Exists (#709)** -* Support Alter Table Drop Constraint If Exists -* #709 add constraintIfExists flag +* Support Alter Table Drop Constraint If Exists +* #709 add constraintIfExists flag [08fed](https://github.com/JSQLParser/JSqlParser/commit/08fedf752a3f99d) Robert Scholte *2018-12-13 07:14:10* @@ -6247,12 +7119,12 @@ Changelog of JSqlParser. **Support KSQL's WITHIN (#722)** -* Implements WITHIN for KSQL windowed joins -* Clean up -* Improve test -* Implements WITHIN ( before TimeUnit, after TimeUnit ) for KSQL -* Also restricts TimeUnit to units accepted by KSQL -* WITHIN should come before ON +* Implements WITHIN for KSQL windowed joins +* Clean up +* Improve test +* Implements WITHIN ( before TimeUnit, after TimeUnit ) for KSQL +* Also restricts TimeUnit to units accepted by KSQL +* WITHIN should come before ON [ae665](https://github.com/JSQLParser/JSqlParser/commit/ae665e60655f45f) Lionel Montrieux *2018-12-11 21:53:07* @@ -6393,7 +7265,7 @@ Changelog of JSqlParser. **fixes #670** -* added testcase, corrected deparser +* added testcase, corrected deparser [455c5](https://github.com/JSQLParser/JSqlParser/commit/455c5f50671eed9) wumpz *2018-09-10 15:38:54* @@ -6429,7 +7301,7 @@ Changelog of JSqlParser. **Update README.md (#667)** -* Fix a couple typos/grammar issues +* Fix a couple typos/grammar issues [a822f](https://github.com/JSQLParser/JSqlParser/commit/a822fead1b21793) Kai Presler-Marshall *2018-08-29 13:11:41* @@ -6475,10 +7347,10 @@ Changelog of JSqlParser. **Parse Cloud Spanner raw string and byte prefixes (#659)** -* #656 parse cloud spanner raw string and byte literals -* #656 fixed raw byte string prefix -* #656 fixed test case -* fixed reported codacy issue +* #656 parse cloud spanner raw string and byte literals +* #656 fixed raw byte string prefix +* #656 fixed test case +* fixed reported codacy issue [a57db](https://github.com/JSQLParser/JSqlParser/commit/a57db5d031a5229) Knut Olav Løite *2018-08-14 08:12:26* @@ -6704,13 +7576,13 @@ Changelog of JSqlParser. **- allow parenthesis around from item** -* - allow whitespace between bars from concat +* - allow whitespace between bars from concat [2cea3](https://github.com/JSQLParser/JSqlParser/commit/2cea3ee94d83447) wumpz *2018-03-29 22:25:48* **- allow parenthesis around from item** -* - allow whitespace between bars from concat +* - allow whitespace between bars from concat [dabb0](https://github.com/JSQLParser/JSqlParser/commit/dabb03f0094e4c7) wumpz *2018-03-29 22:24:33* @@ -6731,8 +7603,8 @@ Changelog of JSqlParser. **JSQLPARSER-584: adds support for MySQL (a,b,...)OP(c,d,...) expression (#585)** -* JSQLPARSER-584: adds support for MySQL (a,b,...)OP(c,d,...) expression -* JSQLPARSER-584: adds some tests and rename MySQLValueListExpression to ValueListExpression +* JSQLPARSER-584: adds support for MySQL (a,b,...)OP(c,d,...) expression +* JSQLPARSER-584: adds some tests and rename MySQLValueListExpression to ValueListExpression [2c272](https://github.com/JSQLParser/JSqlParser/commit/2c272f440995b2c) Adrien Lesur *2018-03-05 23:08:55* @@ -6778,14 +7650,14 @@ Changelog of JSqlParser. **Fix issue #563: subjoin allows only one inner join, this should be a … (#564)** -* Fix issue #563: subjoin allows only one inner join, this should be a list -* Fix failing Oracle tests because of confusion between subjoin and subselect. +* Fix issue #563: subjoin allows only one inner join, this should be a list +* Fix failing Oracle tests because of confusion between subjoin and subselect. [8456a](https://github.com/JSQLParser/JSqlParser/commit/8456acf7f228a46) Frits Jalvingh *2018-02-02 10:40:01* **fixes #320 (#576)** -* fixes #320 +* fixes #320 [e6451](https://github.com/JSQLParser/JSqlParser/commit/e645193140f4271) Taner Mansur *2018-02-01 14:55:37* @@ -6881,7 +7753,7 @@ Changelog of JSqlParser. **Added support for RLIKE expressions (#544)** -* RLIKE is a synonym of REGEXP, therefore should be treated the same. +* RLIKE is a synonym of REGEXP, therefore should be treated the same. [8a950](https://github.com/JSQLParser/JSqlParser/commit/8a950b3e09dce06) sh-tomer *2017-11-16 22:10:51* @@ -6912,12 +7784,12 @@ Changelog of JSqlParser. **Add ability to support "NOT LIKE ..." expressions (#539)** -* The parser is able to parse expressions such as "a NOT LIKE '%pattern%'", but is not able to parse expressions where the not is before the entire expression. For example: "NOT a LIKE '%pattern%'. -* When parsing the latter, the error is: -* Caused by: net.sf.jsqlparser.parser.ParseException: Encountered " "LIKE" "LIKE "" at line 1, column 32. -* Was expecting one of: ... -* The reason this is important is both because these syntaxes are both valid, and also because the deparser uses the second method. -* Therefore, if you parse a query with the first type of expression, then deparse it and parse again, you'll get the same error. +* The parser is able to parse expressions such as "a NOT LIKE '%pattern%'", but is not able to parse expressions where the not is before the entire expression. For example: "NOT a LIKE '%pattern%'. +* When parsing the latter, the error is: +* Caused by: net.sf.jsqlparser.parser.ParseException: Encountered " "LIKE" "LIKE "" at line 1, column 32. +* Was expecting one of: ... +* The reason this is important is both because these syntaxes are both valid, and also because the deparser uses the second method. +* Therefore, if you parse a query with the first type of expression, then deparse it and parse again, you'll get the same error. [daea3](https://github.com/JSQLParser/JSqlParser/commit/daea33e73aa05c6) sh-tomer *2017-10-31 20:27:50* @@ -6938,23 +7810,23 @@ Changelog of JSqlParser. **Linking structures to their AST nodes to have access to their positions (#534)** -* Linking several structures to their AST nodes to have access to their positions -* This far there were only 3 types of structures linked to their AST nodes. Now adding some more expressions and literals to their AST node to have access to their token's position in the query. -* Added missing parts in JSQqlParserCC.jjt for AST linking to work -* Added missing parts in JSQqlParserCC.jjt to make sure all relevant code is created to generate and link AST nodes to the relevant structures. +* Linking several structures to their AST nodes to have access to their positions +* This far there were only 3 types of structures linked to their AST nodes. Now adding some more expressions and literals to their AST node to have access to their token's position in the query. +* Added missing parts in JSQqlParserCC.jjt for AST linking to work +* Added missing parts in JSQqlParserCC.jjt to make sure all relevant code is created to generate and link AST nodes to the relevant structures. [514f2](https://github.com/JSQLParser/JSqlParser/commit/514f2588af97345) sh-tomer *2017-10-29 12:39:56* **add debug note (#531)** -* added a link to the visualize parsing section to have a visible debug mode (so users that create an issue can try to get us better output) +* added a link to the visualize parsing section to have a visible debug mode (so users that create an issue can try to get us better output) [b1abc](https://github.com/JSQLParser/JSqlParser/commit/b1abc6ff39e9c2a) Jan Monterrubio *2017-10-25 06:21:26* **fixes #525 (#530)** -* fixes #525 -* Simply unit test. +* fixes #525 +* Simply unit test. [1a1a1](https://github.com/JSQLParser/JSqlParser/commit/1a1a1aa53866787) Linyu Chen *2017-10-24 05:30:39* @@ -6965,10 +7837,10 @@ Changelog of JSqlParser. **Implements #509 (#504)** -* Supporting MySql hit SQL_CALC_FOUND_ROWS for selecting row count. -* Supporting MySql hit SQL_CALC_FOUND_ROWS for selecting row count. - refactoring -* Supporting MySql hit SQL_CALC_FOUND_ROWS for selecting row count. - missing copyright.ˆ -* Supporting MySql hit SQL_CALC_FOUND_ROWS for selecting row count. - Modify field type to boolean for prevent memory consumption by creating object and try assertSqlCanBeParsedAndDeparsed on unit test. +* Supporting MySql hit SQL_CALC_FOUND_ROWS for selecting row count. +* Supporting MySql hit SQL_CALC_FOUND_ROWS for selecting row count. - refactoring +* Supporting MySql hit SQL_CALC_FOUND_ROWS for selecting row count. - missing copyright.ˆ +* Supporting MySql hit SQL_CALC_FOUND_ROWS for selecting row count. - Modify field type to boolean for prevent memory consumption by creating object and try assertSqlCanBeParsedAndDeparsed on unit test. [3e163](https://github.com/JSQLParser/JSqlParser/commit/3e16345815e45b5) Yoon Kyong Sik *2017-10-20 07:27:48* @@ -6999,7 +7871,7 @@ Changelog of JSqlParser. **fixes #519** -* fixes #520 +* fixes #520 [27217](https://github.com/JSQLParser/JSqlParser/commit/272177a37b9ee81) wumpz *2017-10-06 08:24:04* @@ -7160,11 +8032,11 @@ Changelog of JSqlParser. **fix issue #424 (INSERT with SET) (#481)** -* update insert with set language -* update insert with set -* update insert with set -* update insert test -* add removed lines +* update insert with set language +* update insert with set +* update insert with set +* update insert test +* add removed lines [ca653](https://github.com/JSQLParser/JSqlParser/commit/ca6538a04dd7969) messfish *2017-07-28 06:23:33* @@ -7210,21 +8082,21 @@ Changelog of JSqlParser. **Add Upsert Grammer (#460)** -* Add files via upload -* Add files via upload -* Add files via upload -* Add files via upload -* Add files via upload -* Add test for de parser -* Add files via upload -* Add files via upload -* Add files via upload -* Add files via upload -* Add files via upload -* Add files via upload -* Add files via upload -* Add files via upload -* Add files via upload +* Add files via upload +* Add files via upload +* Add files via upload +* Add files via upload +* Add files via upload +* Add test for de parser +* Add files via upload +* Add files via upload +* Add files via upload +* Add files via upload +* Add files via upload +* Add files via upload +* Add files via upload +* Add files via upload +* Add files via upload [aaeb8](https://github.com/JSQLParser/JSqlParser/commit/aaeb8dfeb0c2a4e) messfish *2017-06-11 18:48:53* @@ -7250,25 +8122,25 @@ Changelog of JSqlParser. **Fix issue #442 (#451)** -* Fix issue #442 for delete statements -* Fix issue #442 for insert statements -* Mock only when necessary -* E.g., interfaces, behavior needs to be verified, etc. -* Prefer the first style of testing -* As discussed in issue #442 -* Fix issue #442 for replace statements -* Improve readability of issue #442 tests -* Inject SelectDeParser as well -* As discussed in issue #442. -* Fix issue #442 for select statements -* Fix issue #442 for update statements -* Fix issue #442 for execute statements -* Fix issue #442 for set statements -* Fix PR code review issue -* https://www.codacy.com/app/wumpz/JSqlParser/file/6682733346/issues/source?bid=4162857&fileBranchId=4580866#l48 -* Skip PMD check for asserts in tests using Mockito -* As agreed upon in the discussion in PR #451. -* Use correct PMD check name +* Fix issue #442 for delete statements +* Fix issue #442 for insert statements +* Mock only when necessary +* E.g., interfaces, behavior needs to be verified, etc. +* Prefer the first style of testing +* As discussed in issue #442 +* Fix issue #442 for replace statements +* Improve readability of issue #442 tests +* Inject SelectDeParser as well +* As discussed in issue #442. +* Fix issue #442 for select statements +* Fix issue #442 for update statements +* Fix issue #442 for execute statements +* Fix issue #442 for set statements +* Fix PR code review issue +* https://www.codacy.com/app/wumpz/JSqlParser/file/6682733346/issues/source?bid=4162857&fileBranchId=4580866#l48 +* Skip PMD check for asserts in tests using Mockito +* As agreed upon in the discussion in PR #451. +* Use correct PMD check name [9d680](https://github.com/JSQLParser/JSqlParser/commit/9d680a6ec4b9f07) chrycheng *2017-05-22 07:13:41* @@ -7279,8 +8151,8 @@ Changelog of JSqlParser. **Fix issue #446 (#447)** -* Test current behavior of ExecuteDeParser -* Fix issue #446 +* Test current behavior of ExecuteDeParser +* Fix issue #446 [275fb](https://github.com/JSQLParser/JSqlParser/commit/275fbbe87fa0ada) chrycheng *2017-05-16 20:46:29* @@ -7316,45 +8188,45 @@ Changelog of JSqlParser. **conversion to CNF (#434)** -* Add files via upload -* Create foo -* All the files needed for the CNF conversion -* Delete foo -* Create foo -* Test cases for the CNF conversion -* Delete foo -* Add files via upload -* Add files via upload -* change some public methods to private -* Delete CNFConverter.java -* Delete CloneHelper.java -* Delete MultiAndExpression.java -* Delete MultiOrExpression.java -* Delete MultipleExpression.java -* Create foo -* Add files via upload -* Delete CNFTest.java -* Delete StepLastHelper.java -* Create foo -* Add files via upload -* Delete foo -* Delete foo -* Add files via upload -* Add files via upload -* Delete CNFConverter.java -* Delete CloneHelper.java -* Delete MultiAndExpression.java -* Delete MultiOrExpression.java -* Delete MultipleExpression.java -* Create foo -* Add files via upload -* Delete foo -* Delete CNFTest.java -* Create foo -* Add files via upload -* Delete foo -* Add files via upload -* Add files via upload +* Add files via upload +* Create foo +* All the files needed for the CNF conversion +* Delete foo +* Create foo +* Test cases for the CNF conversion +* Delete foo +* Add files via upload +* Add files via upload +* change some public methods to private +* Delete CNFConverter.java +* Delete CloneHelper.java +* Delete MultiAndExpression.java +* Delete MultiOrExpression.java +* Delete MultipleExpression.java +* Create foo +* Add files via upload +* Delete CNFTest.java +* Delete StepLastHelper.java +* Create foo +* Add files via upload +* Delete foo +* Delete foo +* Add files via upload +* Add files via upload +* Delete CNFConverter.java +* Delete CloneHelper.java +* Delete MultiAndExpression.java +* Delete MultiOrExpression.java +* Delete MultipleExpression.java +* Create foo +* Add files via upload +* Delete foo +* Delete CNFTest.java +* Create foo +* Add files via upload +* Delete foo +* Add files via upload +* Add files via upload [afe10](https://github.com/JSQLParser/JSqlParser/commit/afe1011bdd64696) messfish *2017-05-07 20:21:00* @@ -7385,10 +8257,10 @@ Changelog of JSqlParser. **Introduce support for mysql index hints (fixing issue #374) (#429)** -* Introduce support for mysql index hints (fixing issue #374) -* Fix checkstyle errors -* -Converted indent tabs to spaces -* -Added missing {} on single-line if statement +* Introduce support for mysql index hints (fixing issue #374) +* Fix checkstyle errors +* -Converted indent tabs to spaces +* -Added missing {} on single-line if statement [6db15](https://github.com/JSQLParser/JSqlParser/commit/6db15d2c6218d06) Joey Mart *2017-04-18 06:22:25* @@ -7399,8 +8271,8 @@ Changelog of JSqlParser. **Addressing #427 (#428)** -* updating readme with Maven requirements -* removing ticks +* updating readme with Maven requirements +* removing ticks [0fddc](https://github.com/JSQLParser/JSqlParser/commit/0fddc73e2eee1b2) AnEmortalKid *2017-04-17 23:39:05* @@ -7511,8 +8383,8 @@ Changelog of JSqlParser. **Fix #407 by enhancing grammar (#410)** -* Fix #407 by enhancing grammar -* Change LF and tabs +* Fix #407 by enhancing grammar +* Change LF and tabs [5d901](https://github.com/JSQLParser/JSqlParser/commit/5d9018657df6b22) Christophe Moine *2017-03-22 07:36:14* @@ -7583,19 +8455,19 @@ Changelog of JSqlParser. **Support FOR UPDATE WAIT (#405)** -* Adding FOR UPDATE WAIT support -* removing final peppered everywhere -* updating formatting, fixing codacy test names -* updating asserts to use static import -* reverting changes -* reverting line feeds -* adding tests and deparser code back without formatting +* Adding FOR UPDATE WAIT support +* removing final peppered everywhere +* updating formatting, fixing codacy test names +* updating asserts to use static import +* reverting changes +* reverting line feeds +* adding tests and deparser code back without formatting [45b39](https://github.com/JSQLParser/JSqlParser/commit/45b392f4b93714c) AnEmortalKid *2017-03-10 22:06:53* **add support for LIMIT with only one row count JDBC parameter (#404)** -* small but powerfull change 👍 +* small but powerfull change 👍 [42318](https://github.com/JSQLParser/JSqlParser/commit/42318531bb72279) zhushaoping *2017-03-03 07:06:08* @@ -7696,22 +8568,22 @@ Changelog of JSqlParser. **Increase test coverage on AlterExpression.java** -* getOperation -* getFkColumns -* getFkSourceTable -* getFkSourceColumns -* getConstraintName -* tried getPkColumns but it does not behave as I expected. -* placed TODO in AlterTest.testAlterTablePK for this -* getIndex().getColumnNames +* getOperation +* getFkColumns +* getFkSourceTable +* getFkSourceColumns +* getConstraintName +* tried getPkColumns but it does not behave as I expected. +* placed TODO in AlterTest.testAlterTablePK for this +* getIndex().getColumnNames [5a799](https://github.com/JSQLParser/JSqlParser/commit/5a79964714b13f7) jthomas *2017-01-19 19:43:24* **Enhance AlterExpression grammar:** -* 1. optional "COLUMN" keyword in ADD alter operation -* 2. new alter operation: MODIFY -* 3. add column specs to alter table column definitions +* 1. optional "COLUMN" keyword in ADD alter operation +* 2. new alter operation: MODIFY +* 3. add column specs to alter table column definitions [24177](https://github.com/JSQLParser/JSqlParser/commit/241779b26973b47) jthomas *2017-01-19 17:32:21* @@ -7742,7 +8614,7 @@ Changelog of JSqlParser. **fixes #375** -* fixes #371 +* fixes #371 [957f3](https://github.com/JSQLParser/JSqlParser/commit/957f39c496a7fac) wumpz *2017-01-02 13:30:09* @@ -7848,7 +8720,7 @@ Changelog of JSqlParser. **#modify net.sf.jsqlparser.statement.insert.Insert Method:toString() if itemsList and useSelectBrackets together not null will error** -* #modify PlainSelect.getStringList change sql append to StringBuilder +* #modify PlainSelect.getStringList change sql append to StringBuilder [471b9](https://github.com/JSQLParser/JSqlParser/commit/471b94495623ee2) zheng.liu@baifendian.com *2016-08-15 06:22:23* @@ -7864,21 +8736,21 @@ Changelog of JSqlParser. **Update Alter and add AlterExpression class for multiple ADD/DROP expressions in a single ALTER statement** -* Update .jjt file to break out the AlterExpression into its own class and for Alter to compose multiple AlterExpressions -* Update AlterTest for new AlterExpressions and test for multiple ADD/DROP statements in a single ALTER +* Update .jjt file to break out the AlterExpression into its own class and for Alter to compose multiple AlterExpressions +* Update AlterTest for new AlterExpressions and test for multiple ADD/DROP statements in a single ALTER [e8d1c](https://github.com/JSQLParser/JSqlParser/commit/e8d1cf2cb1db16c) Rob Story *2016-08-11 19:24:41* **Support for parse delete from table using join to another table like:** -* DELETE posts -* FROM posts -* INNER JOIN projects ON projects.project_id = posts.project_id -* WHERE projects.client_id = :client_id -* This necessitated some changes to the DeleteTest class, -* specifically: -* JSqlParserCC.jjt - changes on grammar of Delete statements. -* Delete toString and DeleteDeParser. +* DELETE posts +* FROM posts +* INNER JOIN projects ON projects.project_id = posts.project_id +* WHERE projects.client_id = :client_id +* This necessitated some changes to the DeleteTest class, +* specifically: +* JSqlParserCC.jjt - changes on grammar of Delete statements. +* Delete toString and DeleteDeParser. [b6eb5](https://github.com/JSQLParser/JSqlParser/commit/b6eb57b61f7b205) Lucas Oliveira *2016-08-06 16:28:11* @@ -7979,7 +8851,7 @@ Changelog of JSqlParser. **fixes #296** -* refactored getTableList method of TableNamesFinder +* refactored getTableList method of TableNamesFinder [3ccca](https://github.com/JSQLParser/JSqlParser/commit/3ccca01bbc85778) wumpz *2016-06-28 12:16:39* @@ -8045,12 +8917,12 @@ Changelog of JSqlParser. **Implemented table check constraint for named constraints.** -* 1. Added named constraint to create table. -* 2. Added check constraint to alter table statement. -* 3. Added CheckConstraint type. -* Tests: -* 4. Added create table test. -* 5. Added alter table test. +* 1. Added named constraint to create table. +* 2. Added check constraint to alter table statement. +* 3. Added CheckConstraint type. +* Tests: +* 4. Added create table test. +* 5. Added alter table test. [401d2](https://github.com/JSQLParser/JSqlParser/commit/401d279ef7e6ef9) Megan Woods *2016-06-16 14:56:01* @@ -8066,13 +8938,13 @@ Changelog of JSqlParser. **Implemented:** -* 1. UPDATE .. RETURNING col, col as Alias -* 2. UPDATE .. RETURNING * -* Tested: -* 3. UPDATE .. ORDER BY .. LIMIT .. RETURNING -* 4. UPDATE .. RETURNING -* Item 4 represents the PostgreSQL UPDATE .. RETURNING Syntax without ORDER BY and LIMIT. -* See: https://www.postgresql.org/docs/9.5/static/sql-update.html +* 1. UPDATE .. RETURNING col, col as Alias +* 2. UPDATE .. RETURNING * +* Tested: +* 3. UPDATE .. ORDER BY .. LIMIT .. RETURNING +* 4. UPDATE .. RETURNING +* Item 4 represents the PostgreSQL UPDATE .. RETURNING Syntax without ORDER BY and LIMIT. +* See: https://www.postgresql.org/docs/9.5/static/sql-update.html [f4526](https://github.com/JSQLParser/JSqlParser/commit/f452638f0f04f99) Megan Woods *2016-06-15 09:12:14* @@ -8133,39 +9005,39 @@ Changelog of JSqlParser. **Added ability to have operators like '>=' or '<=' separated by a space.** -* This includes: -* Modifying the JJT syntax to support the 'space in the middle' versions -* of operators (any quantity of whitespace is supported). -* Modifying the various operators to inherit from a new -* 'ComparisonOperator' class, which handles the (previously NotEqualsTo- -* only) logic for capturing the form of the operator. -* Giving each of the various operators a constructor that accepts the -* syntax used. -* Modifying TestUtils to strip comments out before comparing SQL text -* (necessary because condition07.sql is now passing, and has a comment). -* Updating SpecialOracleTest to indicate 130 tests passing now -* (condition7.sql now passes). -* Adding a new test specifically for operators into SpecialOracleTest. -* NOTE: Because the "! =" form of the 'not equals' operator means something -* different in PostgresSQL (factorial of previous argument + equals), we do -* NOT include that case. +* This includes: +* Modifying the JJT syntax to support the 'space in the middle' versions +* of operators (any quantity of whitespace is supported). +* Modifying the various operators to inherit from a new +* 'ComparisonOperator' class, which handles the (previously NotEqualsTo- +* only) logic for capturing the form of the operator. +* Giving each of the various operators a constructor that accepts the +* syntax used. +* Modifying TestUtils to strip comments out before comparing SQL text +* (necessary because condition07.sql is now passing, and has a comment). +* Updating SpecialOracleTest to indicate 130 tests passing now +* (condition7.sql now passes). +* Adding a new test specifically for operators into SpecialOracleTest. +* NOTE: Because the "! =" form of the 'not equals' operator means something +* different in PostgresSQL (factorial of previous argument + equals), we do +* NOT include that case. [9886b](https://github.com/JSQLParser/JSqlParser/commit/9886b02975d1749) Dave Lindquist *2016-04-28 13:28:58* **Corrected "MERGE INTO" parsing for more complicated statements.** -* Specifically: -* Changed "Condition" to "Expression" for the "ON" clause -- this is -* needed to handle "ON" clauses that have "a = y AND b = z" or other -* more complicated expressions (basically the same as the "ON" clause -* in a SELECT query). -* Also changed the "WHERE" and "DELETE WHERE" clauses in the same -* fashion ('Condition' becomes 'Expression'), as they too support -* multiple conditions. -* Corrected the toString on the MergeUpdate clause, which was missing a -* comma between the fields. -* Added a new, more complicated MERGE INTO statement to the MergeTest -* class. +* Specifically: +* Changed "Condition" to "Expression" for the "ON" clause -- this is +* needed to handle "ON" clauses that have "a = y AND b = z" or other +* more complicated expressions (basically the same as the "ON" clause +* in a SELECT query). +* Also changed the "WHERE" and "DELETE WHERE" clauses in the same +* fashion ('Condition' becomes 'Expression'), as they too support +* multiple conditions. +* Corrected the toString on the MergeUpdate clause, which was missing a +* comma between the fields. +* Added a new, more complicated MERGE INTO statement to the MergeTest +* class. [7efd5](https://github.com/JSQLParser/JSqlParser/commit/7efd58f7704a0ff) Dave Lindquist *2016-04-28 13:19:34* @@ -8196,7 +9068,7 @@ Changelog of JSqlParser. **fixes #240** -* fixes #241 +* fixes #241 [7f8b5](https://github.com/JSQLParser/JSqlParser/commit/7f8b59b31e44521) wumpz *2016-04-05 06:25:57* @@ -8372,7 +9244,7 @@ Changelog of JSqlParser. **added TableFunction alias suppurt** -* added TableFunction unit tests +* added TableFunction unit tests [88edd](https://github.com/JSQLParser/JSqlParser/commit/88eddaf50dada22) tfedkiv *2015-12-07 15:27:02* @@ -8423,8 +9295,8 @@ Changelog of JSqlParser. **Issue 195:** -* Add support for ORDER BY and LIMIT in UPDATE and DELETE statements (as supported by MySQL). -* LimitDeparser and OrderByDeParser have been pulled out into separate classes to avoid code duplication from SelectDeParser. +* Add support for ORDER BY and LIMIT in UPDATE and DELETE statements (as supported by MySQL). +* LimitDeparser and OrderByDeParser have been pulled out into separate classes to avoid code duplication from SelectDeParser. [a3133](https://github.com/JSQLParser/JSqlParser/commit/a31333a65141e27) James Heather *2015-11-24 14:57:04* @@ -8490,7 +9362,7 @@ Changelog of JSqlParser. **Support for alter table drop column/constraint** -* Fix for Issue #184 +* Fix for Issue #184 [58dd2](https://github.com/JSQLParser/JSqlParser/commit/58dd28aaf9b9273) schweighart *2015-10-20 21:34:51* @@ -8661,13 +9533,13 @@ Changelog of JSqlParser. **Add support for variable support to "SELECT SKIP FIRST ..." construct** -* The grammar for the construct in informix [1] mentions the possibility, that <ROWCOUNT> can be either -* an integer or a host variable or local SPL variable storing the value of max. The case for plain integers and -* jdbc variables is covered by the first commit While this commit adds support for constructs using SPL -* variables. SPL variables must follow identifier rules [2][3]. -* [1] http://www-01.ibm.com/support/knowledgecenter/SSGU8G_12.1.0/com.ibm.sqls.doc/ids_sqs_0156.htm -* [2] http://www-01.ibm.com/support/knowledgecenter/SSGU8G_12.1.0/com.ibm.sqls.doc/ids_sqs_1306.htm?lang=de -* [3] http://www-01.ibm.com/support/knowledgecenter/SSGU8G_12.1.0/com.ibm.sqls.doc/ids_sqs_1660.htm%23ids_sqs_1660?lang=de +* The grammar for the construct in informix [1] mentions the possibility, that <ROWCOUNT> can be either +* an integer or a host variable or local SPL variable storing the value of max. The case for plain integers and +* jdbc variables is covered by the first commit While this commit adds support for constructs using SPL +* variables. SPL variables must follow identifier rules [2][3]. +* [1] http://www-01.ibm.com/support/knowledgecenter/SSGU8G_12.1.0/com.ibm.sqls.doc/ids_sqs_0156.htm +* [2] http://www-01.ibm.com/support/knowledgecenter/SSGU8G_12.1.0/com.ibm.sqls.doc/ids_sqs_1306.htm?lang=de +* [3] http://www-01.ibm.com/support/knowledgecenter/SSGU8G_12.1.0/com.ibm.sqls.doc/ids_sqs_1660.htm%23ids_sqs_1660?lang=de [9e77b](https://github.com/JSQLParser/JSqlParser/commit/9e77b6bd55ce242) Matthias Bläsing *2015-08-02 14:03:23* @@ -8813,13 +9685,13 @@ Changelog of JSqlParser. **fixes #143** -* some refactorings done +* some refactorings done [4d2a0](https://github.com/JSQLParser/JSqlParser/commit/4d2a0a1151faff1) wumpz *2015-06-05 20:50:28* **fixes #143** -* some refactorings done +* some refactorings done [f71c3](https://github.com/JSQLParser/JSqlParser/commit/f71c307f15c4cde) wumpz *2015-06-05 20:48:45* @@ -9245,8 +10117,8 @@ Changelog of JSqlParser. **Manage OFFSET and FETCH clauses in dedicated classes and rules in** -* jsqlparsercc.jj -* Manage also jdbc parameter in these clauses. +* jsqlparsercc.jj +* Manage also jdbc parameter in these clauses. [26524](https://github.com/JSQLParser/JSqlParser/commit/26524ac850461cb) LionelNirva *2014-10-10 14:38:20* @@ -9257,14 +10129,14 @@ Changelog of JSqlParser. **Add support for new SQL Server 2012 and Oracle 12c versions of LIMIT** -* (equivalent to MySql and PostgreSQL LIMIT ... OFFSET ... clauses) for -* parsing and deparsing. +* (equivalent to MySql and PostgreSQL LIMIT ... OFFSET ... clauses) for +* parsing and deparsing. [dc215](https://github.com/JSQLParser/JSqlParser/commit/dc215bb6b8cf70b) LionelNirva *2014-10-08 12:38:04* **Unit test for the fix Bug when Deparsing SQL Server request having TOP** -* and DISTINCT clauses. +* and DISTINCT clauses. [c2ad9](https://github.com/JSQLParser/JSqlParser/commit/c2ad9d2b2c2ee9f) LionelNirva *2014-10-07 14:31:43* @@ -9285,13 +10157,13 @@ Changelog of JSqlParser. **Fix Bug when Deparsing SQL Server request having TOP and DISTINCT** -* clauses. SQL Server requires the DISTINCT clause to be the first. +* clauses. SQL Server requires the DISTINCT clause to be the first. [7ac70](https://github.com/JSQLParser/JSqlParser/commit/7ac7002d1276f01) LionelNirva *2014-10-03 16:34:49* **Update UpdateTest.java** -* Add an SQL test for Update with Select +* Add an SQL test for Update with Select [aec82](https://github.com/JSQLParser/JSqlParser/commit/aec82516c9d0db6) CeeKayGit *2014-10-01 21:34:51* @@ -9307,31 +10179,31 @@ Changelog of JSqlParser. **Update Update.java** -* Add necessary Select import for Update with Select +* Add necessary Select import for Update with Select [8321a](https://github.com/JSQLParser/JSqlParser/commit/8321aae44a6adce) CeeKayGit *2014-10-01 20:37:42* **Update UpdateDeParser.java** -* Extend the deparser to support DB2 Updates with Select clause +* Extend the deparser to support DB2 Updates with Select clause [3a7bf](https://github.com/JSQLParser/JSqlParser/commit/3a7bf9d520ead88) CeeKayGit *2014-10-01 20:12:14* **Update JSqlParserCC.jj** -* For DB2 "$" is also a standard letter, so handle "$" as #LETTER. +* For DB2 "$" is also a standard letter, so handle "$" as #LETTER. [a6383](https://github.com/JSQLParser/JSqlParser/commit/a6383a52ccee448) CeeKayGit *2014-09-30 20:56:13* **Update Update.java** -* Add support for DB2 Updates with Select clause +* Add support for DB2 Updates with Select clause [1bdb6](https://github.com/JSQLParser/JSqlParser/commit/1bdb69b8891ee26) CeeKayGit *2014-09-30 20:52:21* **Update JSqlParserCC.jj** -* Add support for DB2 Updates with Select clause +* Add support for DB2 Updates with Select clause [17e3d](https://github.com/JSQLParser/JSqlParser/commit/17e3d539a4ce6bd) CeeKayGit *2014-09-30 20:47:54* @@ -9422,7 +10294,7 @@ Changelog of JSqlParser. **Add support for 'UNLOGGED' tables** -* Support the PostgreSQL 9.1+ ‘UNLOGGED’ table feature +* Support the PostgreSQL 9.1+ ‘UNLOGGED’ table feature [eb05c](https://github.com/JSQLParser/JSqlParser/commit/eb05ce30cb90b93) Michaël Cervera *2014-07-27 20:49:00* @@ -9623,7 +10495,7 @@ Changelog of JSqlParser. **corrected some sql test scripts to be deparseable** -* corrected pivot handling in SelectDeParser +* corrected pivot handling in SelectDeParser [8ac25](https://github.com/JSQLParser/JSqlParser/commit/8ac250d9f90144d) wumpz *2014-03-12 23:24:47* @@ -9644,8 +10516,8 @@ Changelog of JSqlParser. **!= usage corrected** -* deparser for oracle hierarchical expressions corrected -* order by asc/desc corrected +* deparser for oracle hierarchical expressions corrected +* order by asc/desc corrected [d4f74](https://github.com/JSQLParser/JSqlParser/commit/d4f744c761fffe9) wumpz *2014-03-06 22:27:11* @@ -9661,9 +10533,9 @@ Changelog of JSqlParser. **toString for windowing elements corrected** -* lax equality test implemented -* included oracle test sqls -* included @ and # for identifiers +* lax equality test implemented +* included oracle test sqls +* included @ and # for identifiers [884dc](https://github.com/JSQLParser/JSqlParser/commit/884dcafa73ecfd2) wumpz *2014-03-05 23:01:03* @@ -9844,8 +10716,8 @@ Changelog of JSqlParser. **Removed the leading and trailing whitespaces in the JavaCC parser file;** -* Organized the declared imports in order to ease further changes in the file and to remove unused ones; -* Renamed the S_INTEGER token to S_LONG to be in sync with the S_DOUBLE token; +* Organized the declared imports in order to ease further changes in the file and to remove unused ones; +* Renamed the S_INTEGER token to S_LONG to be in sync with the S_DOUBLE token; [5d151](https://github.com/JSQLParser/JSqlParser/commit/5d151c7bdbe08fd) Pap Lőrinc *2014-01-28 08:24:04* @@ -10241,7 +11113,7 @@ Changelog of JSqlParser. **create table can now have foreign key definitions** -* fixes #14 +* fixes #14 [0c41f](https://github.com/JSQLParser/JSqlParser/commit/0c41f27165a2f41) wumpz *2013-06-24 23:28:29* @@ -10307,7 +11179,7 @@ Changelog of JSqlParser. **multi value IN expression introduced (a,b,c) in ...** -* fixes #30 +* fixes #30 [3c443](https://github.com/JSQLParser/JSqlParser/commit/3c44391bf5f168f) wumpz *2013-05-26 22:19:03* @@ -10358,8 +11230,8 @@ Changelog of JSqlParser. **- solved critical grammar bug regarding concat expressions and parenthesis parsing** -* - introduced some tests to cover the above -* - corrected ExpressionDeparser to deliver same result as toString for substractions +* - introduced some tests to cover the above +* - corrected ExpressionDeparser to deliver same result as toString for substractions [51365](https://github.com/JSQLParser/JSqlParser/commit/51365239ead790b) wumpz *2013-05-16 21:53:04* @@ -10410,7 +11282,7 @@ Changelog of JSqlParser. **- insertet toString in Update** -* - modified Update deparser to deliver better results +* - modified Update deparser to deliver better results [e2e4d](https://github.com/JSQLParser/JSqlParser/commit/e2e4d80675d525d) wumpz *2013-04-21 21:18:48* @@ -10521,7 +11393,7 @@ Changelog of JSqlParser. **Added automatic license header generation. The link to the original project was moved to the poms description.** -* Format of all sources done. +* Format of all sources done. [01f7a](https://github.com/JSQLParser/JSqlParser/commit/01f7a4c92670794) wumpz *2013-03-19 21:30:50* @@ -10557,7 +11429,7 @@ Changelog of JSqlParser. **- additional test case for values** -* - additional test cases for analytical expressions +* - additional test cases for analytical expressions [15fc8](https://github.com/JSQLParser/JSqlParser/commit/15fc834bf78ae91) wumpz *2013-03-18 23:59:07* @@ -10793,8 +11665,8 @@ Changelog of JSqlParser. **- ExtractExpression integrated** -* - Tests ExtractExpression started -* - Function problem found +* - Tests ExtractExpression started +* - Function problem found [7ea63](https://github.com/JSQLParser/JSqlParser/commit/7ea63d17c1ef299) wumpz *2012-09-16 21:01:53* @@ -10855,14 +11727,14 @@ Changelog of JSqlParser. **quoted columns in create table statement included** -* CreateTableDeParser corrected (NPE with no indexes, toString delivers now same) -* CreateTableTest expanded +* CreateTableDeParser corrected (NPE with no indexes, toString delivers now same) +* CreateTableTest expanded [69287](https://github.com/JSQLParser/JSqlParser/commit/69287d7b2e8b278) wumpz *2012-05-26 19:58:14* **complex with tests included** -* exists formatting included from fork +* exists formatting included from fork [7e39e](https://github.com/JSQLParser/JSqlParser/commit/7e39e9ec4f48448) wumpz *2012-05-23 19:57:10* @@ -10978,7 +11850,7 @@ Changelog of JSqlParser. **Removal of tests that are not able to pass** -* - Functions do not accept conditions as arguments +* - Functions do not accept conditions as arguments [aa5f0](https://github.com/JSQLParser/JSqlParser/commit/aa5f044c5bebc78) Florent Bécart *2011-12-02 07:20:18* @@ -11039,7 +11911,7 @@ Changelog of JSqlParser. **Added support for select without from-list ( SELECT 1 + 2 )** -* Modified test cases for test resources in src/test/resources +* Modified test cases for test resources in src/test/resources [0e5d7](https://github.com/JSQLParser/JSqlParser/commit/0e5d732e65bf622) Christian Bockermann *2011-09-16 20:06:07* @@ -11070,14 +11942,14 @@ Changelog of JSqlParser. **Use of generics** -* - List of expressions in ItemsList -* - List of columns in Statement -* - List of joins in PlainSelect -* - List of withItems in Select -* - List of plainSelects in Union -* - List of orderByElements in Union -* - List of columns in Update -* - List of expressions in Update +* - List of expressions in ItemsList +* - List of columns in Statement +* - List of joins in PlainSelect +* - List of withItems in Select +* - List of plainSelects in Union +* - List of orderByElements in Union +* - List of columns in Update +* - List of expressions in Update [8b0b8](https://github.com/JSQLParser/JSqlParser/commit/8b0b865780ee962) Florent Bécart *2011-06-22 20:00:44* @@ -11103,18 +11975,18 @@ Changelog of JSqlParser. **Eclipse configuration files** -* Makes development environment installation easier +* Makes development environment installation easier [b93af](https://github.com/JSQLParser/JSqlParser/commit/b93af3e4f333cdc) Florent Bécart *2011-06-20 22:33:30* **Add .gitignore file** -* Auto-generated files that won't be pushed to the repo are: -* - classes: compiled main code -* - docs: project javadoc -* - *.jar: distribution jar (sources and documentation) -* - lib: library jar (.class files) -* - testclasses: compiled unit tests +* Auto-generated files that won't be pushed to the repo are: +* - classes: compiled main code +* - docs: project javadoc +* - *.jar: distribution jar (sources and documentation) +* - lib: library jar (.class files) +* - testclasses: compiled unit tests [55197](https://github.com/JSQLParser/JSqlParser/commit/5519799ce41c45b) Florent Bécart *2011-06-20 22:08:46* @@ -11135,6 +12007,8 @@ Changelog of JSqlParser. **Initial commit (base version: 0.7.0)** -* Source: http://sourceforge.net/projects/jsqlparser/files/jsqlparser/jsqlparser-0.7.0.jar/download +* Source: http://sourceforge.net/projects/jsqlparser/files/jsqlparser/jsqlparser-0.7.0.jar/download [67c91](https://github.com/JSQLParser/JSqlParser/commit/67c9150f5d93ced) Florent Bécart *2011-06-20 21:44:37* + + diff --git a/README.md b/README.md index c310ef5452..6df4adb091 100644 --- a/README.md +++ b/README.md @@ -62,7 +62,7 @@ EqualsTo equalsTo = (EqualsTo) select.getWhere(); Column a = (Column) equalsTo.getLeftExpression(); Column b = (Column) equalsTo.getRightExpression(); Assertions.assertEquals("a", a.getColumnName()); -Assertions.assertEquals("b", b.getColumnName()); + Assertions.assertEquals("b", b.getColumnName()); ``` The tree is traversable with the Visitor pattern, and the same object model works in reverse: @@ -115,9 +115,10 @@ tested, in any language — 19× ahead of `sqlglot[c]` on JSqlParser's own `SELE ```text -Benchmark (version) Mode Cnt Score Error Units -JSQLParserBenchmark.parseSQLStatements latest avgt 15 7.602 ± 0.135 ms/op -JSQLParserBenchmark.parseSQLStatements 5.3 avgt 15 84.687 ± 3.321 ms/op +Benchmark (version) Mode Cnt Score Error Units +JSQLParserBenchmark.parseSQLStatements latest avgt 15 32.525 ± 0.413 ms/op +JSQLParserBenchmark.parseSQLStatements 5.3 avgt 15 983.459 ± 8.197 ms/op +JSQLParserBenchmark.parseSQLStatements 5.1 avgt 15 319.601 ± 4.081 ms/op ``` Methodology and the full cross-parser comparison against SQLGlot, `sqlglot[c]` and @@ -138,9 +139,12 @@ and missing syntax gets added on demand — [open an issue](https://github.com/J | | Statements | |---|---| | **Queries** | `SELECT` · `WITH …` · Piped SQL | +| **ksqlDB windows** | JOIN `WITHIN`, window `GRACE PERIOD`, and `EMIT CHANGES`/`FINAL` | +| **ClickHouse column selection** | `COLUMNS('regexp')` select items with chained `APPLY`, `EXCEPT`, and `REPLACE` transformers | | **DML** | `INSERT` · `UPDATE` · `UPSERT` · `MERGE` · `DELETE` · `TRUNCATE TABLE` | | **DDL** | `CREATE …` · `ALTER …` · `DROP …` | | **PostgreSQL RLS** | `CREATE POLICY` · `ALTER TABLE … ENABLE`/`DISABLE`/`FORCE`/`NO FORCE ROW LEVEL SECURITY` | +| **Informix constraints** | `ALTER TABLE … ADD CONSTRAINT` with trailing constraint names for primary, unique, foreign and check constraints; enable with `parser.withDialect(Dialect.INFORMIX)` | | **Salesforce SOQL** | `INCLUDES` · `EXCLUDES` | Beyond statement shapes, the grammar handles nested sub-selects, bind parameters (`?`, @@ -148,6 +152,48 @@ Beyond statement shapes, the grammar handles nested sub-selects, bind parameters array-literal ambiguity. The complete reference is on the [syntax page](https://jsqlparser.github.io/JSqlParser/syntax.html). +PostgreSQL dollar-quoted strings, including `$tag$…$tag$`, retain their delimiter and +literal body in `StringValue`. Tagged quotes are disabled by default to preserve +identifier parsing. Enable them with `parser.withDialect(Dialect.POSTGRESQL)` or +`parser.withDollarQuotedStringTags(true)`. Untagged `$$…$$` literals remain enabled. + +## Statement classification + +Any parsed statement can say what it actually does — no second parse, no visitor to write: + +```java +StatementFeatures features = CCJSqlParserUtil.parse(sqlStr).getFeatures(); + +// safeguard a read-only client before anything reaches the database +if (connection.isReadOnly() && features.mayModifyData()) { + throw new SQLException("rejected: " + features.getUnresolvedReferences()); +} + +// dispatch correctly +if (features.returnsResultSet()) { statement.executeQuery(sqlStr); } +else { statement.executeUpdate(sqlStr); } +``` + +This is not `sqlStr.startsWith("SELECT")` with extra steps. `RETURNING` turns DML into a row +source, a data-modifying CTE hides a `DELETE` inside a `SELECT`, and `INSERT INTO x SELECT ..` +contains a query but returns nothing: + +| SQL | returns rows | modifies data | +|---|---|---| +| `SELECT * FROM t` | yes | no | +| `INSERT INTO x SELECT * FROM t` | no | yes | +| `DELETE FROM t RETURNING *` | yes | yes | +| `WITH c AS (DELETE FROM t RETURNING *) SELECT * FROM c` | yes | yes | +| `SELECT nextval('s')` | yes | *unproven* | + +Features are not mutually exclusive, and each is three-valued: proven, not excludable, or ruled +out. `is()` answers "did the grammar prove it", `may()` answers "could it be ruled out" — so a +guard uses `may()` and a dispatcher uses `is()`. Function volatility is not a syntactic property, +so anything the caller has not declared pure stays unproven and is listed by name. + +Legacy MySQL `GROUP BY ... ASC/DESC` is available with `Dialect.MYSQL` and the explicit +`withLegacyMySqlGroupBy(true)` option; modern/default parsing keeps it disabled. + ## Piped SQL Support is progressing for Piped SQL, which writes queries in the order they actually @@ -167,6 +213,9 @@ Background reading: the [Google research paper](https://storage.googleapis.com/g [BigQuery pipe syntax](https://cloud.google.com/bigquery/docs/reference/standard-sql/pipe-syntax) and [DuckDB FROM-first syntax](https://duckdb.org/docs/sql/query_syntax/from.html#from-first-syntax). +ODBC `{fn TIMESTAMPADD(...)}` and `{fn TIMESTAMPDIFF(...)}` expose standard +`SQL_TSI_*` interval arguments as time-unit expressions, preserving column traversal. + ## Java version | JSqlParser | Runtime | Notes | diff --git a/build.gradle b/build.gradle index dffc267ea4..b7a3112747 100644 --- a/build.gradle +++ b/build.gradle @@ -165,13 +165,13 @@ dependencies { xmlDoclet ('com.manticore-projects.tools:xml-doclet:+'){ changing = true } // enforce latest version of JavaCC - testImplementation('com.manticore-projects.jsqlformatter:javacc-core:+') - testImplementation('com.manticore-projects.jsqlformatter:javacc-java:+') + testImplementation('com.manticore-projects.jsqlformatter:javacc-core:[8.1.2.1941,)') + testImplementation('com.manticore-projects.jsqlformatter:javacc-java:[8.1.2.1941,)') jmh 'org.openjdk.jmh:jmh-core:+' jmh 'org.openjdk.jmh:jmh-generator-annprocess:+' - javacc('com.manticore-projects.jsqlformatter:javacc-core:+') - javacc('com.manticore-projects.jsqlformatter:javacc-java:+') + javacc('com.manticore-projects.jsqlformatter:javacc-core:[8.1.2.1941,)') + javacc('com.manticore-projects.jsqlformatter:javacc-java:[8.1.2.1941,)') } compileJavacc { @@ -447,7 +447,9 @@ tasks.register('renderRR') { def ebnfFile = new File(rrDir, "JSqlParserCC.ebnf") def jjFile = layout.buildDirectory.dir("generated/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jj").get().asFile.absolutePath - def convertProc = new ProcessBuilder('java', '-jar', + def convertProc = new ProcessBuilder('java', + '-Djdk.xml.maxElementDepth=0', + '-jar', new File(rrDir, "convert.war").absolutePath, jjFile) .redirectOutput(ebnfFile) @@ -504,7 +506,7 @@ Version {{name}} {{#commits}} {{#ifMatches messageTitle "^(?!Merge).*"}} * **{{{messageTitle}}}** - + {{authorName}}, {{commitDate}} {{/ifMatches}} {{/commits}} @@ -587,7 +589,9 @@ tasks.register('sphinx', Exec) { } } -publish { +// `publish` is Gradle's own task; the Central Portal upload runs under `publishToMavenCentral`. +// Both need the same prerequisites, or a CI job invoking the latter silently skips check and docs. +tasks.matching { it.name in ['publish', 'publishToMavenCentral'] }.configureEach { dependsOn(check, gitChangelogTask, renderRR, xslt, xmldoc) } @@ -634,17 +638,27 @@ mavenPublishing { } } -// Fix signing task dependencies -tasks.withType(AbstractPublishToMaven).configureEach { - dependsOn(tasks.withType(Sign)) -} - -signing { - required { !version.endsWith("SNAPSHOT") && gradle.taskGraph.hasTask("publish") } -} +// Signing and its task wiring are handled by mavenPublishing.signAllPublications() above. +// The manual `signing { required { ... hasTask("publish") } }` block that used to live here +// never matched the `publishToMavenCentral` task, so the two disagreed about whether to sign. tasks.withType(JavaCompile).configureEach { options.encoding = 'UTF-8' + + // javac's parser is recursive descent: one stack frame per level of nesting. The generated + // CCJSqlParser is nested deeply enough to exhaust the default thread stack, which surfaces as + // "An exception has occurred in the compiler ... java.lang.StackOverflowError" - a compiler + // crash, not a compile error, and not a bug worth reporting to Oracle. + // + // -Xss only takes effect at JVM launch, so the compiler has to run in its own process: + // setting forkOptions without options.fork = true silently does nothing. + options.fork = true + options.forkOptions.jvmArgs = (options.forkOptions.jvmArgs ?: []) + ['-Xss16m'] +} + +// javadoc parses the same sources in a forked process and hits the same wall +tasks.withType(Javadoc).configureEach { + options.setJFlags((options.getJFlags() ?: []) + ['-Xss16m']) } remotes { @@ -696,11 +710,139 @@ jmh { , "-XX:+DebugNonSafepoints" ] - profilers = ['async:libPath=/opt/async-profiler/lib/libasyncProfiler.so;output=tree;dir=build/reports/jmh'] + // Profiling is opt-in because the native library path depends on the host. + profilers = providers.gradleProperty('jmhProfiler').map { [it] }.getOrElse([]) includes = ['.*JSQLParserBenchmark.*'] warmupIterations = 2 fork = 3 iterations = 5 timeOnIteration = '1s' -} \ No newline at end of file +} + +// --------------------------------------------------------------------------------------------- +// Grammar ambiguity gate +// +// JavaCC reports choice conflicts as "Warning: ..." on stdout and still exits 0, so an ambiguity +// introduced by a PR is completely invisible to `./gradlew check`. The `org.javacc.javacc` plugin +// forwards that output to the Gradle logger, where it cannot be inspected either. +// +// This task therefore re-runs JavaCC on the JJTree output, into a throwaway directory, captures +// stdout+stderr and fails when any warning survives the allow list. +// --------------------------------------------------------------------------------------------- + +// Regexes for warnings that are known-benign. Add an entry ONLY together with a comment saying +// why. Never add a "Choice conflict" pattern here — that is the thing this gate exists to catch. +def grammarWarningAllowList = [ + // JJTree copies the options block into the .jj; re-passing -OUTPUT_DIRECTORY on the + // command line makes JavaCC complain that it overrides the in-file value. + ~/^Warning: .*Command line setting of .* modifies option value in file\./, + + // -OUTPUT_DIRECTORY points at a scratch directory that is wiped before every run, so + // JavaCC always announces that it is creating it. Housekeeping, not a grammar problem. + ~/^Warning: Output directory .* does not exist\. Creating the directory\./ +] + +def javaccMainClassOverride = project.findProperty('javaccMainClass') + +tasks.register('checkGrammarAmbiguity') { + group = 'verification' + description = 'Fails the build when JavaCC reports choice conflicts (grammar ambiguities).' + + dependsOn(compileJavacc) + + def jjFile = layout.buildDirectory + .file('generated/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jj') + def scratchDir = layout.buildDirectory.dir('grammar-check') + def reportFile = layout.buildDirectory.file('reports/grammar/javacc-warnings.txt') + def javaccClasspath = configurations.javacc + def allowList = grammarWarningAllowList + def mainClassOverride = javaccMainClassOverride + + inputs.file(jjFile) + inputs.files(javaccClasspath) + outputs.file(reportFile) + + doLast { + def jj = jjFile.get().asFile + if (!jj.exists()) { + throw new GradleException("Generated grammar not found: ${jj} — did compileJavacc run?") + } + + def out = scratchDir.get().asFile + out.deleteDir() + out.mkdirs() + + // JavaCC 8 splits into javacc-core / javacc-java; take Main-Class from whichever jar + // declares one, so this keeps working across upgrades of the Manticore JavaCC branch. + def mainClass = mainClassOverride ?: javaccClasspath.files.findResult { f -> + if (!f.name.endsWith('.jar')) { + return null + } + new java.util.jar.JarFile(f).withCloseable { jar -> + jar.manifest?.mainAttributes?.getValue('Main-Class') + } + } ?: 'org.javacc.parser.Main' + + def isWindows = System.getProperty('os.name').toLowerCase(Locale.ROOT).contains('win') + def javaExe = new File(new File(System.getProperty('java.home'), 'bin'), + isWindows ? 'java.exe' : 'java') + + def proc = new ProcessBuilder( + javaExe.absolutePath, + '-cp', javaccClasspath.asPath, + mainClass.toString(), + '-GRAMMAR_ENCODING=UTF-8', + "-OUTPUT_DIRECTORY=${out.absolutePath}".toString(), + jj.absolutePath) + .directory(out) + .redirectErrorStream(true) + .start() + + def output = proc.inputStream.getText('UTF-8') + def exit = proc.waitFor() + + // Warnings are multi-line: a "Warning:" header followed by indented continuation lines. + def warnings = [] + def current = null + output.eachLine { line -> + if (line.startsWith('Warning:')) { + current = new StringBuilder(line) + warnings << current + } else if (current != null && line.startsWith(' ')) { + current.append('\n').append(line) + } else { + current = null + } + } + + def offending = warnings + .collect { it.toString() } + .findAll { w -> allowList.every { !(w =~ it) } } + + def report = reportFile.get().asFile + report.parentFile.mkdirs() + report.setText(offending.isEmpty() ? 'No JavaCC grammar warnings.\n' + : offending.join('\n\n') + '\n', 'UTF-8') + + if (exit != 0) { + logger.error(output) + throw new GradleException("JavaCC exited with ${exit} while analysing the grammar.") + } + + if (!offending.isEmpty()) { + def conflicts = offending.count { it.contains('Choice conflict') } + logger.error("\n${offending.join('\n\n')}\n") + throw new GradleException( + "${offending.size()} JavaCC grammar warning(s), ${conflicts} of them choice " + + "conflicts. Resolve the ambiguity or add an explicit LOOKAHEAD to the earlier " + + "expansion. Report: ${report}") + } + + logger.lifecycle('Grammar is free of JavaCC choice conflicts.') + } +} + +tasks.named('check') { + dependsOn('checkGrammarAmbiguity') +} diff --git a/gradle.properties b/gradle.properties index 8b590d14c8..4df9d4bd05 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,6 +1,6 @@ # Specifies the JVM arguments used for the daemon process. # The setting is particularly useful for tweaking memory settings. -org.gradle.jvmargs=-Xmx8G -Xss8m -Dfile.encoding=UTF-8 -XX:+HeapDumpOnOutOfMemoryError +org.gradle.jvmargs=-Xmx8G -Xss16m -Dfile.encoding=UTF-8 -XX:+HeapDumpOnOutOfMemoryError org.gradle.caching=true diff --git a/nb-configuration.xml b/nb-configuration.xml index 2c751cae4d..04d5801d39 100644 --- a/nb-configuration.xml +++ b/nb-configuration.xml @@ -19,7 +19,6 @@ LF false true - JDK_11 false none 4 @@ -28,5 +27,6 @@ 120 true project + Temurin_11.0.32_9 diff --git a/pom.xml b/pom.xml index cd1e28b0b6..172bc3eae7 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ com.github.jsqlparser jsqlparser - 5.4-SNAPSHOT + 5.5-SNAPSHOT bundle JSQLParser library @@ -78,7 +78,7 @@ UTF-8 - 2026-01-01T00:00:00Z + 2026-09-13T21:14:58Z @@ -120,7 +120,7 @@ is expected to track the newest fork build. Pin it (e.g. 8.1.1.1939) before cutting a release so the release is reproducible. --> - [8.1.1.1939,) + [8.1.2.1941,) 5.14.4 5.23.0 3.27.7 @@ -289,7 +289,7 @@ [3.9.0,) - + @@ -426,6 +426,11 @@ jjtree-javacc + -CODE_GENERATOR:"Java" @@ -564,11 +569,13 @@ @{jacocoArgLine} is resolved late, after jacoco:prepare-agent has run. Without it the JaCoCo agent would be dropped and coverage reports empty. --> + @{jacocoArgLine} --add-opens=java.base/java.lang=ALL-UNNAMED --add-opens=java.base/java.util=ALL-UNNAMED -Xmx2G -Xms800m -Xss4m + -Dfile.encoding=UTF-8 @@ -612,8 +619,8 @@ *.md .gitignore - - + + true 4 @@ -625,13 +632,13 @@ src/main/java/**/*.java src/test/java/**/*.java - - + + config/formatter/eclipse-java-google-style.xml - + @@ -759,28 +766,28 @@ **/module-info.java,**/net/sf/jsqlparser/parser/SimpleCharStream.java - - + + - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java index ada4bfdf45..14ed418f03 100644 --- a/src/main/java/module-info.java +++ b/src/main/java/module-info.java @@ -25,6 +25,7 @@ exports net.sf.jsqlparser.statement.alter.sequence; exports net.sf.jsqlparser.statement.analyze; exports net.sf.jsqlparser.statement.comment; + exports net.sf.jsqlparser.statement.create.event; exports net.sf.jsqlparser.statement.create.function; exports net.sf.jsqlparser.statement.create.index; exports net.sf.jsqlparser.statement.create.policy; @@ -33,6 +34,8 @@ exports net.sf.jsqlparser.statement.create.sequence; exports net.sf.jsqlparser.statement.create.synonym; exports net.sf.jsqlparser.statement.create.table; + exports net.sf.jsqlparser.statement.create.trigger; + exports net.sf.jsqlparser.statement.create.user; exports net.sf.jsqlparser.statement.create.view; exports net.sf.jsqlparser.statement.delete; exports net.sf.jsqlparser.statement.drop; diff --git a/src/main/java/net/sf/jsqlparser/expression/AliasedExpression.java b/src/main/java/net/sf/jsqlparser/expression/AliasedExpression.java new file mode 100644 index 0000000000..745cdf4259 --- /dev/null +++ b/src/main/java/net/sf/jsqlparser/expression/AliasedExpression.java @@ -0,0 +1,59 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.expression; + +import java.util.function.Consumer; +import net.sf.jsqlparser.parser.ASTNodeAccessImpl; + +/** An expression with an alias, such as an XMLFOREST argument. */ +public class AliasedExpression extends ASTNodeAccessImpl implements Expression { + private Expression expression; + private Alias alias; + + public AliasedExpression(Expression expression, Alias alias) { + this.expression = expression; + this.alias = alias; + } + + public Expression getExpression() { + return expression; + } + + public void setExpression(Expression expression) { + this.expression = expression; + } + + public Alias getAlias() { + return alias; + } + + public void setAlias(Alias alias) { + this.alias = alias; + } + + @Override + public T accept(ExpressionVisitor visitor, S context) { + return visitor.visit(this, context); + } + + public StringBuilder appendTo(StringBuilder builder, Consumer expressionPrinter) { + expressionPrinter.accept(expression); + if (alias != null) { + builder.append(alias); + } + return builder; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + return appendTo(builder, builder::append).toString(); + } +} diff --git a/src/main/java/net/sf/jsqlparser/expression/AnalyticExpression.java b/src/main/java/net/sf/jsqlparser/expression/AnalyticExpression.java index d94d4ef2eb..c01df2ee33 100644 --- a/src/main/java/net/sf/jsqlparser/expression/AnalyticExpression.java +++ b/src/main/java/net/sf/jsqlparser/expression/AnalyticExpression.java @@ -58,7 +58,7 @@ public class AnalyticExpression extends ASTNodeAccessImpl implements Expression public AnalyticExpression() {} public AnalyticExpression(Function function) { - this.name = String.join(" ", function.getMultipartName()); + this.name = function.getName(); this.allColumns = function.isAllColumns(); this.distinct = function.isDistinct(); this.unique = function.isUnique(); diff --git a/src/main/java/net/sf/jsqlparser/expression/ColumnsExpression.java b/src/main/java/net/sf/jsqlparser/expression/ColumnsExpression.java new file mode 100644 index 0000000000..aa19d555f3 --- /dev/null +++ b/src/main/java/net/sf/jsqlparser/expression/ColumnsExpression.java @@ -0,0 +1,84 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.expression; + +import java.util.ArrayList; +import java.util.List; +import net.sf.jsqlparser.parser.ASTNodeAccessImpl; + +/** + * A ClickHouse {@code COLUMNS('regexp')} matcher with one or more transformers, for example + * {@code COLUMNS('^metric_') APPLY(x -> round(x, 2))}. + * + * Without a transformer, {@code COLUMNS('regexp')} keeps parsing as a regular + * {@link net.sf.jsqlparser.expression.Function}. + */ +public class ColumnsExpression extends ASTNodeAccessImpl implements Expression { + + private Expression columns; + private List transformers; + + public ColumnsExpression(Expression columns) { + this.columns = columns; + this.transformers = new ArrayList<>(); + } + + public ColumnsExpression(Expression columns, List transformers) { + this.columns = columns; + this.transformers = transformers; + } + + public Expression getColumns() { + return columns; + } + + public ColumnsExpression setColumns(Expression columns) { + this.columns = columns; + return this; + } + + public List getTransformers() { + return transformers; + } + + public ColumnsExpression setTransformers(List transformers) { + this.transformers = transformers; + return this; + } + + public List getAllExpressions() { + List expressions = new ArrayList<>(); + if (columns != null) { + expressions.add(columns); + } + for (ColumnsTransformer transformer : transformers) { + transformer.collectExpressions(expressions); + } + return expressions; + } + + public StringBuilder appendTo(StringBuilder builder) { + builder.append(columns); + for (ColumnsTransformer transformer : transformers) { + builder.append(" ").append(transformer); + } + return builder; + } + + @Override + public String toString() { + return appendTo(new StringBuilder()).toString(); + } + + @Override + public T accept(ExpressionVisitor expressionVisitor, S context) { + return expressionVisitor.visit(this, context); + } +} diff --git a/src/main/java/net/sf/jsqlparser/expression/ColumnsTransformer.java b/src/main/java/net/sf/jsqlparser/expression/ColumnsTransformer.java new file mode 100644 index 0000000000..fcf6517ce6 --- /dev/null +++ b/src/main/java/net/sf/jsqlparser/expression/ColumnsTransformer.java @@ -0,0 +1,121 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.expression; + +import java.util.List; +import net.sf.jsqlparser.expression.operators.relational.ParenthesedExpressionList; +import net.sf.jsqlparser.parser.ASTNodeAccessImpl; +import net.sf.jsqlparser.schema.Column; +import net.sf.jsqlparser.statement.select.Select; +import net.sf.jsqlparser.statement.select.SelectItem; + +/** + * A ClickHouse transformer following a {@code COLUMNS(...)} matcher, for example the + * {@code APPLY(x -> round(x, 2))} in {@code SELECT COLUMNS('^m') APPLY(x -> round(x, 2))}. + * + * ClickHouse parses its transformers in a loop, so they may repeat and combine in any order. + */ +public class ColumnsTransformer extends ASTNodeAccessImpl { + + public enum ColumnsTransformerType { + APPLY, EXCEPT, REPLACE + } + + private ColumnsTransformerType type; + private Expression applyExpression; + private ParenthesedExpressionList exceptColumns; + private List> replaceItems; + + public ColumnsTransformer(ColumnsTransformerType type) { + this.type = type; + } + + public ColumnsTransformerType getType() { + return type; + } + + public ColumnsTransformer setType(ColumnsTransformerType type) { + this.type = type; + return this; + } + + public Expression getApplyExpression() { + return applyExpression; + } + + public ColumnsTransformer setApplyExpression(Expression applyExpression) { + this.applyExpression = applyExpression; + return this; + } + + public ParenthesedExpressionList getExceptColumns() { + return exceptColumns; + } + + public ColumnsTransformer setExceptColumns(ParenthesedExpressionList exceptColumns) { + this.exceptColumns = exceptColumns; + return this; + } + + public List> getReplaceItems() { + return replaceItems; + } + + public ColumnsTransformer setReplaceItems(List> replaceItems) { + this.replaceItems = replaceItems; + return this; + } + + void collectExpressions(List expressions) { + switch (type) { + case APPLY: + if (applyExpression != null) { + expressions.add(applyExpression); + } + break; + case EXCEPT: + if (exceptColumns != null) { + expressions.addAll(exceptColumns); + } + break; + case REPLACE: + if (replaceItems != null) { + for (SelectItem item : replaceItems) { + expressions.add(item.getExpression()); + } + } + break; + default: + throw new IllegalStateException("Unhandled ColumnsTransformerType: " + type); + } + } + + public StringBuilder appendTo(StringBuilder builder) { + switch (type) { + case APPLY: + builder.append("APPLY(").append(applyExpression).append(")"); + break; + case EXCEPT: + builder.append("EXCEPT ").append(exceptColumns); + break; + case REPLACE: + builder.append("REPLACE(").append(Select.getStringList(replaceItems)).append(")"); + break; + default: + throw new IllegalStateException("Unhandled ColumnsTransformerType: " + type); + } + return builder; + } + + @Override + public String toString() { + return appendTo(new StringBuilder()).toString(); + } +} diff --git a/src/main/java/net/sf/jsqlparser/expression/DateUnitExpression.java b/src/main/java/net/sf/jsqlparser/expression/DateUnitExpression.java index 922c0c4194..61fa514ffc 100644 --- a/src/main/java/net/sf/jsqlparser/expression/DateUnitExpression.java +++ b/src/main/java/net/sf/jsqlparser/expression/DateUnitExpression.java @@ -42,7 +42,19 @@ public String toString() { } public enum DateUnit { - CENTURY, DECADE, YEAR, QUARTER, MONTH, WEEK, DAY, HOUR, MINUTE, SECOND, MILLISECOND, MICROSECOND, NANOSECOND; + CENTURY, DECADE, YEAR, QUARTER, MONTH, WEEK, DAY, HOUR, MINUTE, SECOND, MILLISECOND, MICROSECOND, NANOSECOND, SQL_TSI_FRAC_SECOND, SQL_TSI_SECOND, SQL_TSI_MINUTE, SQL_TSI_HOUR, SQL_TSI_DAY, SQL_TSI_WEEK, SQL_TSI_MONTH, SQL_TSI_QUARTER, SQL_TSI_YEAR; + + /** Returns an ODBC interval keyword, or null when the text is not one. */ + public static DateUnit fromOdbcInterval(String text) { + if (text == null || !text.toUpperCase(Locale.ROOT).startsWith("SQL_TSI_")) { + return null; + } + try { + return from(text); + } catch (IllegalArgumentException exception) { + return null; + } + } public static DateUnit from(String UnitStr) { return Enum.valueOf(DateUnit.class, UnitStr.toUpperCase(Locale.ROOT)); diff --git a/src/main/java/net/sf/jsqlparser/expression/ExpressionVisitor.java b/src/main/java/net/sf/jsqlparser/expression/ExpressionVisitor.java index 08afe5129f..91185a4625 100644 --- a/src/main/java/net/sf/jsqlparser/expression/ExpressionVisitor.java +++ b/src/main/java/net/sf/jsqlparser/expression/ExpressionVisitor.java @@ -10,6 +10,7 @@ package net.sf.jsqlparser.expression; import java.util.List; +import net.sf.jsqlparser.statement.execute.ExecuteArgument; import net.sf.jsqlparser.expression.operators.arithmetic.Addition; import net.sf.jsqlparser.expression.operators.arithmetic.BitwiseAnd; import net.sf.jsqlparser.expression.operators.arithmetic.BitwiseLeftShift; @@ -40,6 +41,7 @@ import net.sf.jsqlparser.expression.operators.relational.GreaterThanEquals; import net.sf.jsqlparser.expression.operators.relational.InExpression; import net.sf.jsqlparser.expression.operators.relational.IncludesExpression; +import net.sf.jsqlparser.expression.operators.relational.Intersects; import net.sf.jsqlparser.expression.operators.relational.IsBooleanExpression; import net.sf.jsqlparser.expression.operators.relational.IsDistinctExpression; import net.sf.jsqlparser.expression.operators.relational.IsNullExpression; @@ -70,6 +72,19 @@ import net.sf.jsqlparser.statement.update.UpdateSet; public interface ExpressionVisitor { + /** Visits the value of an aliased expression by default, preserving existing visitors. */ + default T visit(AliasedExpression expression, S context) { + return expression.getExpression().accept(this, context); + } + + default void visit(AliasedExpression expression) { + visit(expression, null); + } + + + default T visit(ExecuteArgument argument, S context) { + return argument.getExpression().accept(this, context); + } default T visitExpressions(ExpressionList expressions, S context) { if (expressions != null) { @@ -495,6 +510,14 @@ default void visit(ExtractExpression extractExpression) { this.visit(extractExpression, null); } + default T visit(RowPatternFunction function, S context) { + return function.getFunction().accept(this, context); + } + + default void visit(RowPatternFunction function) { + visit(function, null); + } + T visit(IntervalExpression intervalExpression, S context); default void visit(IntervalExpression intervalExpression) { @@ -563,6 +586,15 @@ default void visit(RowConstructor rowConstructor) { T visit(RowGetExpression rowGetExpression, S context); + default T visit(MethodCallExpression methodCall, S context) { + methodCall.getExpression().accept(this, context); + return methodCall.getMethod().accept(this, context); + } + + default void visit(MethodCallExpression methodCall) { + this.visit(methodCall, null); + } + default void visit(RowGetExpression rowGetExpression) { this.visit(rowGetExpression, null); } @@ -621,6 +653,14 @@ default void visit(ArrayConstructor arrayConstructor) { this.visit(arrayConstructor, null); } + default T visit(MapExpression mapExpression, S context) { + return null; + } + + default void visit(MapExpression mapExpression) { + this.visit(mapExpression, null); + } + T visit(VariableAssignment variableAssignment, S context); default void visit(VariableAssignment variableAssignment) { @@ -717,6 +757,12 @@ default void visit(GeometryDistance geometryDistance) { this.visit(geometryDistance, null); } + T visit(Intersects intersects, S context); + + default void visit(Intersects intersects) { + this.visit(intersects, null); + } + T visit(Select select, S context); T visit(TranscodingFunction transcodingFunction, S context); @@ -737,6 +783,12 @@ default void visit(RangeExpression rangeExpression) { this.visit(rangeExpression, null); } + T visit(TernaryExpression ternaryExpression, S context); + + default void visit(TernaryExpression ternaryExpression) { + this.visit(ternaryExpression, null); + } + T visit(TSQLLeftJoin tsqlLeftJoin, S context); default void visit(TSQLLeftJoin tsqlLeftJoin) { @@ -761,6 +813,19 @@ default void visit(LambdaExpression lambdaExpression) { this.visit(lambdaExpression, null); } + default T visit(ColumnsExpression columnsExpression, S context) { + for (Expression expression : columnsExpression.getAllExpressions()) { + if (expression != null) { + expression.accept(this, context); + } + } + return null; + } + + default void visit(ColumnsExpression columnsExpression) { + this.visit(columnsExpression, null); + } + T visit(HighExpression highExpression, S context); default void visit(HighExpression highExpression) { diff --git a/src/main/java/net/sf/jsqlparser/expression/ExpressionVisitorAdapter.java b/src/main/java/net/sf/jsqlparser/expression/ExpressionVisitorAdapter.java index 432c16354d..fed7b6f0c9 100644 --- a/src/main/java/net/sf/jsqlparser/expression/ExpressionVisitorAdapter.java +++ b/src/main/java/net/sf/jsqlparser/expression/ExpressionVisitorAdapter.java @@ -12,7 +12,8 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; -import java.util.Optional; +import java.util.List; +import java.util.Map; import net.sf.jsqlparser.expression.operators.arithmetic.Addition; import net.sf.jsqlparser.expression.operators.arithmetic.BitwiseAnd; import net.sf.jsqlparser.expression.operators.arithmetic.BitwiseLeftShift; @@ -43,6 +44,7 @@ import net.sf.jsqlparser.expression.operators.relational.GreaterThanEquals; import net.sf.jsqlparser.expression.operators.relational.InExpression; import net.sf.jsqlparser.expression.operators.relational.IncludesExpression; +import net.sf.jsqlparser.expression.operators.relational.Intersects; import net.sf.jsqlparser.expression.operators.relational.IsBooleanExpression; import net.sf.jsqlparser.expression.operators.relational.IsDistinctExpression; import net.sf.jsqlparser.expression.operators.relational.IsNullExpression; @@ -117,11 +119,9 @@ public T visit(Function function, S context) { if (function.getKeep() != null) { subExpressions.add(function.getKeep()); } - if (function.getOrderByElements() != null) { - for (OrderByElement orderByElement : function.getOrderByElements()) { - subExpressions.add(orderByElement.getExpression()); - } - } + addOrderByExpressions(subExpressions, function.getOrderByElements()); + addFunctionModifiers(subExpressions, function.getHavingClause(), + function.getKeywordArguments(), function.getLimit()); return visitExpressions(function, context, subExpressions); } @@ -417,29 +417,41 @@ public T visit(AnalyticExpression analyticExpression, S context) { if (analyticExpression.getKeep() != null) { subExpressions.add(analyticExpression.getKeep()); } - if (analyticExpression.getFuncOrderBy() != null) { - for (OrderByElement element : analyticExpression.getOrderByElements()) { - subExpressions.add(element.getExpression()); + subExpressions.add(analyticExpression.getFilterExpression()); + addOrderByExpressions(subExpressions, analyticExpression.getFuncOrderBy()); + addFunctionModifiers(subExpressions, analyticExpression.getHavingClause(), + analyticExpression.getKeywordArguments(), analyticExpression.getLimit()); + if (analyticExpression.getWindowDefinition() != null) { + subExpressions.addAll(analyticExpression.getWindowDefinition().getAllExpressions()); + } + return visitExpressions(analyticExpression, context, subExpressions); + } + + private static void addOrderByExpressions(List expressions, + List orderBy) { + if (orderBy != null) { + for (OrderByElement element : orderBy) { + expressions.add(element.getExpression()); } } - if (analyticExpression.getWindowElement() != null) { - /* - * Visit expressions from the range and offset of the window element. Do this using - * optional chains, because several things down the tree can be null e.g. the - * expression. So, null-safe versions of e.g.: - * analyticExpression.getWindowElement().getOffset().getExpression().accept(this, - * parameters); - */ - Optional.ofNullable(analyticExpression.getWindowElement().getRange()) - .map(WindowRange::getStart) - .map(WindowOffset::getExpression).ifPresent(subExpressions::add); - Optional.ofNullable(analyticExpression.getWindowElement().getRange()) - .map(WindowRange::getEnd) - .map(WindowOffset::getExpression).ifPresent(subExpressions::add); - Optional.ofNullable(analyticExpression.getWindowElement().getOffset()) - .map(WindowOffset::getExpression).ifPresent(subExpressions::add); + } + + private static void addFunctionModifiers(List expressions, + Function.HavingClause having, List arguments, + net.sf.jsqlparser.statement.select.Limit limit) { + expressions.add(having); + if (arguments != null) { + for (Function.KeywordArgument argument : arguments) { + expressions.add(argument.getExpression()); + } + } + if (limit != null) { + expressions.add(limit.getOffset()); + expressions.add(limit.getRowCount()); + if (limit.getByExpressions() != null) { + expressions.addAll(limit.getByExpressions()); + } } - return visitExpressions(analyticExpression, context, subExpressions); } @Override @@ -693,6 +705,16 @@ public T visit(ArrayConstructor arrayConstructor, S context) { return visitExpressions(arrayConstructor, context, arrayConstructor.getExpressions()); } + @Override + public T visit(MapExpression mapExpression, S context) { + ArrayList subExpressions = new ArrayList<>(); + for (Map.Entry entry : mapExpression.getEntries()) { + subExpressions.add(entry.getKey()); + subExpressions.add(entry.getValue()); + } + return visitExpressions(mapExpression, context, subExpressions); + } + @Override public T visit(VariableAssignment variableAssignment, S context) { return visitExpressions(variableAssignment, context, variableAssignment.getVariable(), @@ -701,13 +723,7 @@ public T visit(VariableAssignment variableAssignment, S context) { @Override public T visit(XMLSerializeExpr xmlSerializeExpr, S context) { - ArrayList subExpressions = new ArrayList<>(); - - subExpressions.add(xmlSerializeExpr.getExpression()); - for (OrderByElement orderByElement : xmlSerializeExpr.getOrderByElements()) { - subExpressions.add(orderByElement.getExpression()); - } - return visitExpressions(xmlSerializeExpr, context, subExpressions); + return visitExpressions(xmlSerializeExpr, context, xmlSerializeExpr.getExpressions()); } @Override @@ -793,6 +809,11 @@ public T visit(GeometryDistance geometryDistance, S context) { return visitBinaryExpression(geometryDistance, context); } + @Override + public T visit(Intersects intersects, S context) { + return visitBinaryExpression(intersects, context); + } + @Override public T visit(Select select, S context) { if (selectVisitor != null) { @@ -822,6 +843,12 @@ public T visit(RangeExpression rangeExpression, S context) { rangeExpression.getEndExpression()); } + @Override + public T visit(TernaryExpression ternaryExpression, S context) { + return visitExpressions(ternaryExpression, context, ternaryExpression.getCondition(), + ternaryExpression.getThenExpression(), ternaryExpression.getElseExpression()); + } + @Override public T visit(TSQLLeftJoin tsqlLeftJoin, S context) { return visitBinaryExpression(tsqlLeftJoin, context); @@ -848,6 +875,11 @@ public T visit(LambdaExpression lambdaExpression, S context) { return lambdaExpression.getExpression().accept(this, context); } + @Override + public T visit(ColumnsExpression columnsExpression, S context) { + return visitExpressions(columnsExpression, context, columnsExpression.getAllExpressions()); + } + @Override public T visit(HighExpression highExpression, S context) { return highExpression.getExpression().accept(this, context); diff --git a/src/main/java/net/sf/jsqlparser/expression/JsonExpression.java b/src/main/java/net/sf/jsqlparser/expression/JsonExpression.java index f258e855cb..7cd34b53d7 100644 --- a/src/main/java/net/sf/jsqlparser/expression/JsonExpression.java +++ b/src/main/java/net/sf/jsqlparser/expression/JsonExpression.java @@ -87,7 +87,10 @@ public String toString() { StringBuilder b = new StringBuilder(); b.append(expr.toString()); for (Map.Entry ident : idents) { - b.append(ident.getValue()).append(ident.getKey()); + // Separate the operator with spaces: without them `#>`/`#>>` glue onto + // the operand (e.g. `a#>'{b}'`), and since `#` is a legal identifier + // character the result re-parses as `a#` `>` `...` -- a different tree. + b.append(' ').append(ident.getValue()).append(' ').append(ident.getKey()); } return b.toString(); } diff --git a/src/main/java/net/sf/jsqlparser/expression/MapExpression.java b/src/main/java/net/sf/jsqlparser/expression/MapExpression.java new file mode 100644 index 0000000000..1e64a7db84 --- /dev/null +++ b/src/main/java/net/sf/jsqlparser/expression/MapExpression.java @@ -0,0 +1,79 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.expression; + +import java.util.AbstractMap; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import net.sf.jsqlparser.parser.ASTNodeAccessImpl; + +/** + * DuckDB's {@code MAP {key: value, ...}} literal expression. + * + * @see DuckDB MAP type + */ +public class MapExpression extends ASTNodeAccessImpl implements Expression { + + private List> entries = new ArrayList<>(); + + public MapExpression() {} + + public MapExpression(Collection> entries) { + this.entries.addAll(entries); + } + + public List> getEntries() { + return entries; + } + + public void setEntries(List> entries) { + this.entries = entries; + } + + public MapExpression withEntries(List> entries) { + setEntries(entries); + return this; + } + + public MapExpression addEntries( + Collection> entries) { + this.entries.addAll(entries); + return this; + } + + public MapExpression addEntry(Expression key, Expression value) { + entries.add(new AbstractMap.SimpleEntry<>(key, value)); + return this; + } + + public StringBuilder appendTo(StringBuilder builder) { + builder.append("MAP {"); + for (int i = 0; i < entries.size(); i++) { + if (i > 0) { + builder.append(", "); + } + Map.Entry entry = entries.get(i); + builder.append(entry.getKey()).append(": ").append(entry.getValue()); + } + return builder.append("}"); + } + + @Override + public String toString() { + return appendTo(new StringBuilder()).toString(); + } + + @Override + public T accept(ExpressionVisitor expressionVisitor, S context) { + return expressionVisitor.visit(this, context); + } +} diff --git a/src/main/java/net/sf/jsqlparser/expression/MethodCallExpression.java b/src/main/java/net/sf/jsqlparser/expression/MethodCallExpression.java new file mode 100644 index 0000000000..30fb798891 --- /dev/null +++ b/src/main/java/net/sf/jsqlparser/expression/MethodCallExpression.java @@ -0,0 +1,58 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.expression; + +import java.util.function.Consumer; +import net.sf.jsqlparser.parser.ASTNodeAccessImpl; + +/** A method applied to an expression, such as a SQL Server XML subquery result. */ +public class MethodCallExpression extends ASTNodeAccessImpl implements Expression { + private Expression expression; + private Function method; + + public MethodCallExpression(Expression expression, Function method) { + this.expression = expression; + this.method = method; + } + + public Expression getExpression() { + return expression; + } + + public void setExpression(Expression expression) { + this.expression = expression; + } + + public Function getMethod() { + return method; + } + + public void setMethod(Function method) { + this.method = method; + } + + public StringBuilder appendTo(StringBuilder builder, Consumer printer) { + printer.accept(expression); + builder.append('.'); + printer.accept(method); + return builder; + } + + @Override + public T accept(ExpressionVisitor visitor, S context) { + return visitor.visit(this, context); + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + return appendTo(builder, value -> builder.append(value)).toString(); + } +} diff --git a/src/main/java/net/sf/jsqlparser/expression/OracleHint.java b/src/main/java/net/sf/jsqlparser/expression/OracleHint.java index 4ab164f987..166e19e08d 100644 --- a/src/main/java/net/sf/jsqlparser/expression/OracleHint.java +++ b/src/main/java/net/sf/jsqlparser/expression/OracleHint.java @@ -24,7 +24,7 @@ public class OracleHint extends ASTNodeAccessImpl implements Expression { private static final Pattern SINGLE_LINE = Pattern.compile("--\\+ *([^ ].*[^ ])"); private static final Pattern MULTI_LINE = - Pattern.compile("/\\*\\+ *([^ ].*[^ ]) *\\*+/", Pattern.MULTILINE | Pattern.DOTALL); + Pattern.compile("\\A/\\*\\+ *([^ ].*[^ ]) *\\*+/", Pattern.MULTILINE | Pattern.DOTALL); private String value; private boolean singleLine = false; diff --git a/src/main/java/net/sf/jsqlparser/expression/RowPatternFunction.java b/src/main/java/net/sf/jsqlparser/expression/RowPatternFunction.java new file mode 100644 index 0000000000..a6ee2cd986 --- /dev/null +++ b/src/main/java/net/sf/jsqlparser/expression/RowPatternFunction.java @@ -0,0 +1,63 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.expression; + +import java.util.function.Consumer; +import net.sf.jsqlparser.parser.ASTNodeAccessImpl; + +/** An explicit RUNNING or FINAL evaluation mode on a row-pattern function. */ +public class RowPatternFunction extends ASTNodeAccessImpl implements Expression { + public enum EvaluationMode { + RUNNING, FINAL + } + + private EvaluationMode evaluationMode; + private Function function; + + public RowPatternFunction(EvaluationMode evaluationMode, Function function) { + this.evaluationMode = evaluationMode; + this.function = function; + } + + public EvaluationMode getEvaluationMode() { + return evaluationMode; + } + + public RowPatternFunction setEvaluationMode(EvaluationMode mode) { + evaluationMode = mode; + return this; + } + + public Function getFunction() { + return function; + } + + public RowPatternFunction setFunction(Function function) { + this.function = function; + return this; + } + + @Override + public T accept(ExpressionVisitor visitor, S context) { + return visitor.visit(this, context); + } + + public StringBuilder appendTo(StringBuilder builder, Consumer expressions) { + builder.append(evaluationMode).append(' '); + expressions.accept(function); + return builder; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + return appendTo(builder, expression -> builder.append(expression)).toString(); + } +} diff --git a/src/main/java/net/sf/jsqlparser/expression/StringValue.java b/src/main/java/net/sf/jsqlparser/expression/StringValue.java index a16536fab5..46bf58810a 100644 --- a/src/main/java/net/sf/jsqlparser/expression/StringValue.java +++ b/src/main/java/net/sf/jsqlparser/expression/StringValue.java @@ -36,10 +36,20 @@ public StringValue(String escapedValue) { && escapedValue.endsWith("'")) { value = escapedValue.substring(1, escapedValue.length() - 1); return; - } else if (escapedValue.length() >= 4 && escapedValue.startsWith("$$") - && escapedValue.endsWith("$$")) { - value = escapedValue.substring(2, escapedValue.length() - 2); - quoteStr = "$$"; + } else if (escapedValue.length() >= 2 && escapedValue.startsWith("\"") + && escapedValue.endsWith("\"")) { + // double quoted String Literals (Feature.allowDoubleQuotedStrings) + value = escapedValue.substring(1, escapedValue.length() - 1); + quoteStr = "\""; + return; + } + + String delimiter = getDollarQuoteDelimiter(escapedValue); + if (delimiter != null && escapedValue.length() >= 2 * delimiter.length() + && escapedValue.endsWith(delimiter)) { + quoteStr = delimiter; + value = escapedValue.substring(delimiter.length(), + escapedValue.length() - delimiter.length()); return; } @@ -58,6 +68,32 @@ public StringValue(String escapedValue) { value = escapedValue; } + /** + * Returns the opening PostgreSQL dollar-quote delimiter, or null if there is none. A tag + * follows unquoted identifier rules, excluding dollar signs. This method does not require the + * closing delimiter or inspect the body. + */ + public static String getDollarQuoteDelimiter(String text) { + if (text == null || text.length() < 2 || text.charAt(0) != '$') { + return null; + } + int end = text.indexOf('$', 1); + if (end < 0) { + return null; + } + for (int i = 1; i < end;) { + int character = text.codePointAt(i); + boolean valid = + i == 1 ? Character.isUnicodeIdentifierStart(character) || character == '_' + : Character.isUnicodeIdentifierPart(character); + if (!valid) { + return null; + } + i += Character.charCount(character); + } + return text.substring(0, end + 1); + } + public String getValue() { return value; } @@ -84,6 +120,9 @@ public StringValue setQuoteStr(String quoteStr) { } public String getNotExcapedValue() { + if (quoteStr != null && quoteStr.startsWith("$")) { + return value; + } StringBuilder buffer = new StringBuilder(value); int index = 0; int deletesNum = 0; diff --git a/src/main/java/net/sf/jsqlparser/expression/StructType.java b/src/main/java/net/sf/jsqlparser/expression/StructType.java index 4bd38a6930..c2b59c51a3 100644 --- a/src/main/java/net/sf/jsqlparser/expression/StructType.java +++ b/src/main/java/net/sf/jsqlparser/expression/StructType.java @@ -16,6 +16,7 @@ import java.util.ArrayList; import java.util.List; import java.util.Map; +import java.util.function.Consumer; /* * STRUCT @@ -113,73 +114,62 @@ public StructType add(Expression expression, String aliasName) { } public StringBuilder appendTo(StringBuilder builder) { - if (dialect != Dialect.DUCKDB && keyword != null) { - builder.append(keyword); - } + return appendTo(builder, expression -> builder.append(expression)); + } - if (dialect != Dialect.DUCKDB && parameters != null && !parameters.isEmpty()) { - builder.append("<"); - int i = 0; - - for (Map.Entry e : parameters) { - if (0 < i++) { - builder.append(","); - } - // optional name - if (e.getKey() != null && !e.getKey().isEmpty()) { - builder.append(e.getKey()).append(" "); - } - - // mandatory type - builder.append(e.getValue()); + /** Renders current argument expressions through the caller's visitor. */ + public StringBuilder appendTo(StringBuilder builder, Consumer expressions) { + if (dialect == Dialect.DUCKDB) { + appendArguments(builder, expressions); + appendTypeParameters(builder, true); + } else { + if (keyword != null) { + builder.append(keyword); } - - builder.append(">"); + appendTypeParameters(builder, false); + appendArguments(builder, expressions); } + return builder; + } - if (arguments != null && !arguments.isEmpty()) { - - if (dialect == Dialect.DUCKDB) { - builder.append("{ "); - int i = 0; - for (SelectItem e : arguments) { - if (0 < i++) { - builder.append(","); - } - builder.append(e.getAlias().getName()); - builder.append(":"); - builder.append(e.getExpression()); - } - builder.append(" }"); - } else { - builder.append("("); - int i = 0; - for (SelectItem e : arguments) { - if (0 < i++) { - builder.append(","); - } - e.appendTo(builder); - } - - builder.append(")"); + private void appendTypeParameters(StringBuilder builder, boolean cast) { + if (parameters == null || parameters.isEmpty()) { + return; + } + builder.append(cast ? "::STRUCT( " : "<"); + int i = 0; + for (Map.Entry parameter : parameters) { + if (i++ > 0) { + builder.append(','); + } + if (cast || parameter.getKey() != null && !parameter.getKey().isEmpty()) { + builder.append(parameter.getKey()).append(' '); } + builder.append(parameter.getValue()); } + builder.append(cast ? ')' : '>'); + } - if (dialect == Dialect.DUCKDB && parameters != null && !parameters.isEmpty()) { - builder.append("::STRUCT( "); - int i = 0; - - for (Map.Entry e : parameters) { - if (0 < i++) { - builder.append(","); - } - builder.append(e.getKey()).append(" "); - builder.append(e.getValue()); + private void appendArguments(StringBuilder builder, Consumer expressions) { + if (arguments == null || arguments.isEmpty()) { + return; + } + boolean structLiteral = dialect == Dialect.DUCKDB; + builder.append(structLiteral ? "{ " : "("); + int i = 0; + for (SelectItem argument : arguments) { + if (i++ > 0) { + builder.append(','); + } + if (structLiteral) { + builder.append(argument.getAlias().getName()).append(':'); + } + expressions.accept(argument.getExpression()); + if (!structLiteral && argument.getAlias() != null) { + builder.append(argument.getAlias()); } - builder.append(")"); } - - return builder; + builder.append(structLiteral ? " }" : ")"); } @Override diff --git a/src/main/java/net/sf/jsqlparser/expression/TernaryExpression.java b/src/main/java/net/sf/jsqlparser/expression/TernaryExpression.java new file mode 100644 index 0000000000..3d67d4ea38 --- /dev/null +++ b/src/main/java/net/sf/jsqlparser/expression/TernaryExpression.java @@ -0,0 +1,68 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2025 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.expression; + +import net.sf.jsqlparser.parser.ASTNodeAccessImpl; + +/** + * The C-style ternary conditional operator {@code condition ? thenExpression : elseExpression}, + * supported for instance by ClickHouse as an alias for {@code if(condition, then, else)}. + */ +public class TernaryExpression extends ASTNodeAccessImpl implements Expression { + private Expression condition; + private Expression thenExpression; + private Expression elseExpression; + + public TernaryExpression() {} + + public TernaryExpression(Expression condition, Expression thenExpression, + Expression elseExpression) { + this.condition = condition; + this.thenExpression = thenExpression; + this.elseExpression = elseExpression; + } + + public Expression getCondition() { + return condition; + } + + public TernaryExpression setCondition(Expression condition) { + this.condition = condition; + return this; + } + + public Expression getThenExpression() { + return thenExpression; + } + + public TernaryExpression setThenExpression(Expression thenExpression) { + this.thenExpression = thenExpression; + return this; + } + + public Expression getElseExpression() { + return elseExpression; + } + + public TernaryExpression setElseExpression(Expression elseExpression) { + this.elseExpression = elseExpression; + return this; + } + + @Override + public String toString() { + return condition + " ? " + thenExpression + " : " + elseExpression; + } + + @Override + public T accept(ExpressionVisitor expressionVisitor, S context) { + return expressionVisitor.visit(this, context); + } +} diff --git a/src/main/java/net/sf/jsqlparser/expression/WindowDefinition.java b/src/main/java/net/sf/jsqlparser/expression/WindowDefinition.java index 60760baee0..2996821cf2 100644 --- a/src/main/java/net/sf/jsqlparser/expression/WindowDefinition.java +++ b/src/main/java/net/sf/jsqlparser/expression/WindowDefinition.java @@ -10,6 +10,7 @@ package net.sf.jsqlparser.expression; import java.io.Serializable; +import java.util.ArrayList; import java.util.List; import net.sf.jsqlparser.expression.operators.relational.ExpressionList; @@ -73,6 +74,30 @@ public WindowDefinition withWindowName(String windowName) { return this; } + /** Returns the partition, order and frame expressions for both inline and named windows. */ + public List getAllExpressions() { + List expressions = new ArrayList<>(partitionBy); + if (getOrderByElements() != null) { + for (OrderByElement element : getOrderByElements()) { + expressions.add(element.getExpression()); + } + } + if (windowElement != null) { + if (windowElement.getRange() != null) { + addOffsetExpression(expressions, windowElement.getRange().getStart()); + addOffsetExpression(expressions, windowElement.getRange().getEnd()); + } + addOffsetExpression(expressions, windowElement.getOffset()); + } + return expressions; + } + + private static void addOffsetExpression(List expressions, WindowOffset offset) { + if (offset != null && offset.getExpression() != null) { + expressions.add(offset.getExpression()); + } + } + @Override public String toString() { StringBuilder b = new StringBuilder(); diff --git a/src/main/java/net/sf/jsqlparser/expression/XMLSerializeExpr.java b/src/main/java/net/sf/jsqlparser/expression/XMLSerializeExpr.java index d2534e62d2..62a4e703aa 100644 --- a/src/main/java/net/sf/jsqlparser/expression/XMLSerializeExpr.java +++ b/src/main/java/net/sf/jsqlparser/expression/XMLSerializeExpr.java @@ -9,7 +9,9 @@ */ package net.sf.jsqlparser.expression; +import java.util.ArrayList; import java.util.List; +import java.util.function.Consumer; import static java.util.stream.Collectors.joining; @@ -19,9 +21,19 @@ public class XMLSerializeExpr extends ASTNodeAccessImpl implements Expression { + public enum SerializationMode { + CONTENT, DOCUMENT + } + private Expression expression; private List orderByElements; private ColDataType dataType; + private SerializationMode serializationMode; + private StringValue encoding; + private StringValue version; + private Boolean indent; + private LongValue indentSize; + private Boolean showDefaults; @Override public T accept(ExpressionVisitor expressionVisitor, S context) { @@ -52,11 +64,141 @@ public void setDataType(ColDataType dataType) { this.dataType = dataType; } + /** Null retains the legacy XMLAGG(XMLTEXT(...)) form and its existing expression getter. */ + public SerializationMode getSerializationMode() { + return serializationMode; + } + + public void setSerializationMode(SerializationMode serializationMode) { + this.serializationMode = serializationMode; + } + + public StringValue getEncoding() { + return encoding; + } + + public void setEncoding(StringValue encoding) { + this.encoding = encoding; + } + + public StringValue getVersion() { + return version; + } + + public void setVersion(StringValue version) { + this.version = version; + } + + /** Null preserves omission, true is INDENT, and false is NO INDENT. */ + public Boolean getIndent() { + return indent; + } + + public void setIndent(Boolean indent) { + this.indent = indent; + } + + public LongValue getIndentSize() { + return indentSize; + } + + public void setIndentSize(LongValue indentSize) { + this.indentSize = indentSize; + } + + /** Null preserves omission, true is SHOW DEFAULTS, and false is HIDE DEFAULTS. */ + public Boolean getShowDefaults() { + return showDefaults; + } + + public void setShowDefaults(Boolean showDefaults) { + this.showDefaults = showDefaults; + } + + /** Shared child discovery for expression, table-name and validation visitors. */ + public List getExpressions() { + List result = new ArrayList<>(); + if (expression != null) { + result.add(expression); + } + if (orderByElements != null) { + for (OrderByElement orderBy : orderByElements) { + result.add(orderBy.getExpression()); + } + } + if (encoding != null) { + result.add(encoding); + } + if (version != null) { + result.add(version); + } + if (indentSize != null) { + result.add(indentSize); + } + return result; + } + + /** Render both forms without bypassing custom expression or ORDER BY deparsers. */ + public StringBuilder appendTo(StringBuilder sql, Consumer expressionWriter, + Consumer> orderByWriter) { + validateOptions(); + sql.append("xmlserialize("); + if (serializationMode == null) { + sql.append("xmlagg(xmltext("); + expressionWriter.accept(expression); + sql.append(")"); + if (orderByElements != null) { + orderByWriter.accept(orderByElements); + } + sql.append(") AS ").append(dataType); + } else { + sql.append(serializationMode).append(" "); + expressionWriter.accept(expression); + if (dataType != null) { + sql.append(" AS ").append(dataType); + } + if (encoding != null) { + sql.append(" ENCODING "); + expressionWriter.accept(encoding); + } + if (version != null) { + sql.append(" VERSION "); + expressionWriter.accept(version); + } + if (indent != null) { + sql.append(indent ? " INDENT" : " NO INDENT"); + if (indent && indentSize != null) { + sql.append(" SIZE = "); + expressionWriter.accept(indentSize); + } + } + if (showDefaults != null) { + sql.append(showDefaults ? " SHOW DEFAULTS" : " HIDE DEFAULTS"); + } + } + return sql.append(")"); + } + + public void validateOptions() { + if (indentSize != null && (!Boolean.TRUE.equals(indent) || indentSize.getValue() < 0)) { + throw new IllegalArgumentException( + "An indentation size requires INDENT and must be nonnegative"); + } + if (serializationMode == null && (encoding != null || version != null || indent != null + || indentSize != null || showDefaults != null)) { + throw new IllegalArgumentException("Serialization options require CONTENT or DOCUMENT"); + } + if (serializationMode != null && orderByElements != null && !orderByElements.isEmpty()) { + throw new IllegalArgumentException( + "ORDER BY belongs inside the serialized XMLAGG expression"); + } + } + @Override public String toString() { - return "xmlserialize(xmlagg(xmltext(" + expression + ")" - + (orderByElements != null ? " ORDER BY " + orderByElements.stream() - .map(OrderByElement::toString).collect(joining(", ")) : "") - + ") AS " + dataType + ")"; + StringBuilder sql = new StringBuilder(); + return appendTo(sql, sql::append, orderBy -> sql.append(" ORDER BY ") + .append(orderBy.stream().map(OrderByElement::toString).collect(joining(", ")))) + .toString(); } } diff --git a/src/main/java/net/sf/jsqlparser/expression/operators/relational/Between.java b/src/main/java/net/sf/jsqlparser/expression/operators/relational/Between.java index 8998863ef2..ff24e0eba3 100644 --- a/src/main/java/net/sf/jsqlparser/expression/operators/relational/Between.java +++ b/src/main/java/net/sf/jsqlparser/expression/operators/relational/Between.java @@ -16,7 +16,7 @@ /** * A "BETWEEN" expr1 expr2 statement */ -public class Between extends ASTNodeAccessImpl implements Expression { +public class Between extends ASTNodeAccessImpl implements Expression, SupportsOldOracleJoinSyntax { private Expression leftExpression; private boolean not = false; @@ -82,7 +82,8 @@ public T accept(ExpressionVisitor expressionVisitor, S context) { @Override public String toString() { - return leftExpression + " " + (not ? "NOT " : "") + "BETWEEN " + return (oraclePriorPosition == ORACLE_PRIOR_START ? "PRIOR " : "") + leftExpression + " " + + (not ? "NOT " : "") + "BETWEEN " + (usingSymmetric ? "SYMMETRIC " : "") + (usingAsymmetric ? "ASYMMETRIC " : "") + betweenExpressionStart + " AND " @@ -120,4 +121,39 @@ public E getBetweenExpressionStart(Class type) { public E getLeftExpression(Class type) { return type.cast(getLeftExpression()); } + + private int oraclePriorPosition = NO_ORACLE_PRIOR; + + @Override + public int getOldOracleJoinSyntax() { + return NO_ORACLE_JOIN; + } + + @Override + public void setOldOracleJoinSyntax(int oldOracleJoinSyntax) { + throw new IllegalArgumentException( + "oracle join operator (+) is not supported on this condition"); + } + + @Override + public Between withOldOracleJoinSyntax(int oldOracleJoinSyntax) { + setOldOracleJoinSyntax(oldOracleJoinSyntax); + return this; + } + + @Override + public int getOraclePriorPosition() { + return oraclePriorPosition; + } + + @Override + public void setOraclePriorPosition(int oraclePriorPosition) { + this.oraclePriorPosition = oraclePriorPosition; + } + + public Between withOraclePriorPosition(int oraclePriorPosition) { + setOraclePriorPosition(oraclePriorPosition); + return this; + } + } diff --git a/src/main/java/net/sf/jsqlparser/expression/operators/relational/InExpression.java b/src/main/java/net/sf/jsqlparser/expression/operators/relational/InExpression.java index 743d8a0aa7..c920c2b872 100644 --- a/src/main/java/net/sf/jsqlparser/expression/operators/relational/InExpression.java +++ b/src/main/java/net/sf/jsqlparser/expression/operators/relational/InExpression.java @@ -22,6 +22,8 @@ public class InExpression extends ASTNodeAccessImpl private Expression rightExpression; private int oldOracleJoinSyntax = NO_ORACLE_JOIN; + private int oraclePriorPosition = NO_ORACLE_PRIOR; + public InExpression() {} public InExpression(Expression leftExpression, Expression rightExpression) { @@ -94,6 +96,9 @@ private String getLeftExpressionString() { @Override public String toString() { StringBuilder statementBuilder = new StringBuilder(); + if (oraclePriorPosition == ORACLE_PRIOR_START) { + statementBuilder.append("PRIOR "); + } statementBuilder.append(getLeftExpressionString()); statementBuilder.append(" "); @@ -110,14 +115,12 @@ public String toString() { @Override public int getOraclePriorPosition() { - return SupportsOldOracleJoinSyntax.NO_ORACLE_PRIOR; + return oraclePriorPosition; } @Override public void setOraclePriorPosition(int priorPosition) { - if (priorPosition != SupportsOldOracleJoinSyntax.NO_ORACLE_PRIOR) { - throw new IllegalArgumentException("unexpected prior for oracle found"); - } + this.oraclePriorPosition = priorPosition; } public InExpression withRightExpression(Expression rightExpression) { diff --git a/src/main/java/net/sf/jsqlparser/expression/operators/relational/Intersects.java b/src/main/java/net/sf/jsqlparser/expression/operators/relational/Intersects.java new file mode 100644 index 0000000000..0f05edcb60 --- /dev/null +++ b/src/main/java/net/sf/jsqlparser/expression/operators/relational/Intersects.java @@ -0,0 +1,41 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.expression.operators.relational; + +import net.sf.jsqlparser.expression.BinaryExpression; +import net.sf.jsqlparser.expression.Expression; +import net.sf.jsqlparser.expression.ExpressionVisitor; + +/** + * The PostgreSQL # binary operator: the geometric intersection of lseg / line / box + * (documentation Table 9.36) and the integer bitwise exclusive OR (Table 9.4). + */ +public class Intersects extends BinaryExpression { + + @Override + public T accept(ExpressionVisitor expressionVisitor, S context) { + return expressionVisitor.visit(this, context); + } + + @Override + public String getStringExpression() { + return "#"; + } + + @Override + public Intersects withLeftExpression(Expression expression) { + return (Intersects) super.withLeftExpression(expression); + } + + @Override + public Intersects withRightExpression(Expression expression) { + return (Intersects) super.withRightExpression(expression); + } +} diff --git a/src/main/java/net/sf/jsqlparser/expression/operators/relational/IsBooleanExpression.java b/src/main/java/net/sf/jsqlparser/expression/operators/relational/IsBooleanExpression.java index 3c83365336..6cf101bd9a 100644 --- a/src/main/java/net/sf/jsqlparser/expression/operators/relational/IsBooleanExpression.java +++ b/src/main/java/net/sf/jsqlparser/expression/operators/relational/IsBooleanExpression.java @@ -13,7 +13,8 @@ import net.sf.jsqlparser.expression.ExpressionVisitor; import net.sf.jsqlparser.parser.ASTNodeAccessImpl; -public class IsBooleanExpression extends ASTNodeAccessImpl implements Expression { +public class IsBooleanExpression extends ASTNodeAccessImpl + implements Expression, SupportsOldOracleJoinSyntax { private Expression leftExpression; private boolean not = false; @@ -50,10 +51,11 @@ public T accept(ExpressionVisitor expressionVisitor, S context) { @Override public String toString() { + String prior = oraclePriorPosition == ORACLE_PRIOR_START ? "PRIOR " : ""; if (isTrue()) { - return leftExpression + " IS" + (not ? " NOT" : "") + " TRUE"; + return prior + leftExpression + " IS" + (not ? " NOT" : "") + " TRUE"; } else { - return leftExpression + " IS" + (not ? " NOT" : "") + " FALSE"; + return prior + leftExpression + " IS" + (not ? " NOT" : "") + " FALSE"; } } @@ -75,4 +77,39 @@ public IsBooleanExpression withNot(boolean not) { public E getLeftExpression(Class type) { return type.cast(getLeftExpression()); } + + private int oraclePriorPosition = NO_ORACLE_PRIOR; + + @Override + public int getOldOracleJoinSyntax() { + return NO_ORACLE_JOIN; + } + + @Override + public void setOldOracleJoinSyntax(int oldOracleJoinSyntax) { + throw new IllegalArgumentException( + "oracle join operator (+) is not supported on this condition"); + } + + @Override + public IsBooleanExpression withOldOracleJoinSyntax(int oldOracleJoinSyntax) { + setOldOracleJoinSyntax(oldOracleJoinSyntax); + return this; + } + + @Override + public int getOraclePriorPosition() { + return oraclePriorPosition; + } + + @Override + public void setOraclePriorPosition(int oraclePriorPosition) { + this.oraclePriorPosition = oraclePriorPosition; + } + + public IsBooleanExpression withOraclePriorPosition(int oraclePriorPosition) { + setOraclePriorPosition(oraclePriorPosition); + return this; + } + } diff --git a/src/main/java/net/sf/jsqlparser/expression/operators/relational/IsDistinctExpression.java b/src/main/java/net/sf/jsqlparser/expression/operators/relational/IsDistinctExpression.java index 60add6b6ed..12bc3027cd 100644 --- a/src/main/java/net/sf/jsqlparser/expression/operators/relational/IsDistinctExpression.java +++ b/src/main/java/net/sf/jsqlparser/expression/operators/relational/IsDistinctExpression.java @@ -12,7 +12,7 @@ import net.sf.jsqlparser.expression.BinaryExpression; import net.sf.jsqlparser.expression.ExpressionVisitor; -public class IsDistinctExpression extends BinaryExpression { +public class IsDistinctExpression extends BinaryExpression implements SupportsOldOracleJoinSyntax { private boolean not = false; @@ -36,7 +36,43 @@ public String getStringExpression() { @Override public String toString() { - String retval = getLeftExpression() + getStringExpression() + getRightExpression(); + String retval = (oraclePriorPosition == ORACLE_PRIOR_START ? "PRIOR " : "") + + getLeftExpression() + getStringExpression() + getRightExpression(); return retval; } + + private int oraclePriorPosition = NO_ORACLE_PRIOR; + + @Override + public int getOldOracleJoinSyntax() { + return NO_ORACLE_JOIN; + } + + @Override + public void setOldOracleJoinSyntax(int oldOracleJoinSyntax) { + throw new IllegalArgumentException( + "oracle join operator (+) is not supported on this condition"); + } + + @Override + public IsDistinctExpression withOldOracleJoinSyntax(int oldOracleJoinSyntax) { + setOldOracleJoinSyntax(oldOracleJoinSyntax); + return this; + } + + @Override + public int getOraclePriorPosition() { + return oraclePriorPosition; + } + + @Override + public void setOraclePriorPosition(int oraclePriorPosition) { + this.oraclePriorPosition = oraclePriorPosition; + } + + public IsDistinctExpression withOraclePriorPosition(int oraclePriorPosition) { + setOraclePriorPosition(oraclePriorPosition); + return this; + } + } diff --git a/src/main/java/net/sf/jsqlparser/expression/operators/relational/IsNullExpression.java b/src/main/java/net/sf/jsqlparser/expression/operators/relational/IsNullExpression.java index 393f9f1944..f044c436ae 100644 --- a/src/main/java/net/sf/jsqlparser/expression/operators/relational/IsNullExpression.java +++ b/src/main/java/net/sf/jsqlparser/expression/operators/relational/IsNullExpression.java @@ -14,7 +14,8 @@ import net.sf.jsqlparser.parser.ASTNodeAccessImpl; import net.sf.jsqlparser.schema.Column; -public class IsNullExpression extends ASTNodeAccessImpl implements Expression { +public class IsNullExpression extends ASTNodeAccessImpl + implements Expression, SupportsOldOracleJoinSyntax { private Expression leftExpression; private boolean not = false; @@ -72,12 +73,13 @@ public T accept(ExpressionVisitor expressionVisitor, S context) { @Override public String toString() { + String prior = oraclePriorPosition == ORACLE_PRIOR_START ? "PRIOR " : ""; if (useNotNull) { - return leftExpression + " NOTNULL"; + return prior + leftExpression + " NOTNULL"; } else if (useIsNull) { - return leftExpression + (not ? " NOT" : "") + " ISNULL"; + return prior + leftExpression + (not ? " NOT" : "") + " ISNULL"; } else { - return leftExpression + " IS " + (not ? "NOT " : "") + "NULL"; + return prior + leftExpression + " IS " + (not ? "NOT " : "") + "NULL"; } } @@ -99,4 +101,39 @@ public IsNullExpression withNot(boolean not) { public E getLeftExpression(Class type) { return type.cast(getLeftExpression()); } + + private int oraclePriorPosition = NO_ORACLE_PRIOR; + + @Override + public int getOldOracleJoinSyntax() { + return NO_ORACLE_JOIN; + } + + @Override + public void setOldOracleJoinSyntax(int oldOracleJoinSyntax) { + throw new IllegalArgumentException( + "oracle join operator (+) is not supported on this condition"); + } + + @Override + public IsNullExpression withOldOracleJoinSyntax(int oldOracleJoinSyntax) { + setOldOracleJoinSyntax(oldOracleJoinSyntax); + return this; + } + + @Override + public int getOraclePriorPosition() { + return oraclePriorPosition; + } + + @Override + public void setOraclePriorPosition(int oraclePriorPosition) { + this.oraclePriorPosition = oraclePriorPosition; + } + + public IsNullExpression withOraclePriorPosition(int oraclePriorPosition) { + setOraclePriorPosition(oraclePriorPosition); + return this; + } + } diff --git a/src/main/java/net/sf/jsqlparser/expression/operators/relational/IsUnknownExpression.java b/src/main/java/net/sf/jsqlparser/expression/operators/relational/IsUnknownExpression.java index 506d6e2466..06abbbcb55 100644 --- a/src/main/java/net/sf/jsqlparser/expression/operators/relational/IsUnknownExpression.java +++ b/src/main/java/net/sf/jsqlparser/expression/operators/relational/IsUnknownExpression.java @@ -13,7 +13,8 @@ import net.sf.jsqlparser.expression.ExpressionVisitor; import net.sf.jsqlparser.parser.ASTNodeAccessImpl; -public class IsUnknownExpression extends ASTNodeAccessImpl implements Expression { +public class IsUnknownExpression extends ASTNodeAccessImpl + implements Expression, SupportsOldOracleJoinSyntax { private Expression leftExpression; private boolean isNot = false; @@ -41,7 +42,8 @@ public T accept(ExpressionVisitor expressionVisitor, S context) { @Override public String toString() { - return leftExpression + " IS" + (isNot ? " NOT" : "") + " UNKNOWN"; + return (oraclePriorPosition == ORACLE_PRIOR_START ? "PRIOR " : "") + leftExpression + " IS" + + (isNot ? " NOT" : "") + " UNKNOWN"; } public IsUnknownExpression withLeftExpression(Expression leftExpression) { @@ -57,4 +59,39 @@ public IsUnknownExpression withNot(boolean isNot) { public E getLeftExpression(Class type) { return type.cast(getLeftExpression()); } + + private int oraclePriorPosition = NO_ORACLE_PRIOR; + + @Override + public int getOldOracleJoinSyntax() { + return NO_ORACLE_JOIN; + } + + @Override + public void setOldOracleJoinSyntax(int oldOracleJoinSyntax) { + throw new IllegalArgumentException( + "oracle join operator (+) is not supported on this condition"); + } + + @Override + public IsUnknownExpression withOldOracleJoinSyntax(int oldOracleJoinSyntax) { + setOldOracleJoinSyntax(oldOracleJoinSyntax); + return this; + } + + @Override + public int getOraclePriorPosition() { + return oraclePriorPosition; + } + + @Override + public void setOraclePriorPosition(int oraclePriorPosition) { + this.oraclePriorPosition = oraclePriorPosition; + } + + public IsUnknownExpression withOraclePriorPosition(int oraclePriorPosition) { + setOraclePriorPosition(oraclePriorPosition); + return this; + } + } diff --git a/src/main/java/net/sf/jsqlparser/expression/operators/relational/LikeExpression.java b/src/main/java/net/sf/jsqlparser/expression/operators/relational/LikeExpression.java index 770db3de39..92563d7d85 100644 --- a/src/main/java/net/sf/jsqlparser/expression/operators/relational/LikeExpression.java +++ b/src/main/java/net/sf/jsqlparser/expression/operators/relational/LikeExpression.java @@ -14,7 +14,7 @@ import net.sf.jsqlparser.expression.Expression; import net.sf.jsqlparser.expression.ExpressionVisitor; -public class LikeExpression extends BinaryExpression { +public class LikeExpression extends BinaryExpression implements SupportsOldOracleJoinSyntax { private boolean not = false; private boolean useBinary = false; private Expression escapeExpression = null; @@ -50,7 +50,8 @@ public String getStringExpression() { @Override public String toString() { - String retval = getLeftExpression() + " " + (not ? "NOT " : "") + String retval = (oraclePriorPosition == ORACLE_PRIOR_START ? "PRIOR " : "") + + getLeftExpression() + " " + (not ? "NOT " : "") + (likeKeyWord == KeyWord.SIMILAR_TO ? "SIMILAR TO" : likeKeyWord) + " " + (useBinary ? "BINARY " : "") + getRightExpression(); if (escapeExpression != null) { @@ -118,11 +119,46 @@ public LikeExpression withRightExpression(Expression arg0) { } public enum KeyWord { - LIKE, ILIKE, RLIKE, REGEXP_LIKE, REGEXP, SIMILAR_TO, MATCH_ANY, MATCH_ALL, MATCH_PHRASE, MATCH_PHRASE_PREFIX, MATCH_REGEXP; + LIKE, ILIKE, RLIKE, REGEXP_LIKE, REGEXP, GLOB, SIMILAR_TO, MATCH_ANY, MATCH_ALL, MATCH_PHRASE, MATCH_PHRASE_PREFIX, MATCH_REGEXP; public static KeyWord from(String keyword) { return Enum.valueOf(KeyWord.class, keyword.toUpperCase(Locale.ROOT).replaceAll("\\s+", "_")); } } + + private int oraclePriorPosition = NO_ORACLE_PRIOR; + + @Override + public int getOldOracleJoinSyntax() { + return NO_ORACLE_JOIN; + } + + @Override + public void setOldOracleJoinSyntax(int oldOracleJoinSyntax) { + throw new IllegalArgumentException( + "oracle join operator (+) is not supported on this condition"); + } + + @Override + public LikeExpression withOldOracleJoinSyntax(int oldOracleJoinSyntax) { + setOldOracleJoinSyntax(oldOracleJoinSyntax); + return this; + } + + @Override + public int getOraclePriorPosition() { + return oraclePriorPosition; + } + + @Override + public void setOraclePriorPosition(int oraclePriorPosition) { + this.oraclePriorPosition = oraclePriorPosition; + } + + public LikeExpression withOraclePriorPosition(int oraclePriorPosition) { + setOraclePriorPosition(oraclePriorPosition); + return this; + } + } diff --git a/src/main/java/net/sf/jsqlparser/expression/operators/relational/MemberOfExpression.java b/src/main/java/net/sf/jsqlparser/expression/operators/relational/MemberOfExpression.java index d602e62788..87a54dd072 100644 --- a/src/main/java/net/sf/jsqlparser/expression/operators/relational/MemberOfExpression.java +++ b/src/main/java/net/sf/jsqlparser/expression/operators/relational/MemberOfExpression.java @@ -13,7 +13,8 @@ import net.sf.jsqlparser.expression.ExpressionVisitor; import net.sf.jsqlparser.parser.ASTNodeAccessImpl; -public class MemberOfExpression extends ASTNodeAccessImpl implements Expression { +public class MemberOfExpression extends ASTNodeAccessImpl + implements Expression, SupportsOldOracleJoinSyntax { Expression leftExpression; Expression rightExpression; @@ -53,11 +54,47 @@ public MemberOfExpression setNot(boolean not) { @Override public String toString() { - return leftExpression + " MEMBER OF " + rightExpression; + return (oraclePriorPosition == ORACLE_PRIOR_START ? "PRIOR " : "") + leftExpression + + " MEMBER OF " + rightExpression; } @Override public T accept(ExpressionVisitor expressionVisitor, S context) { return expressionVisitor.visit(this, context); } + + private int oraclePriorPosition = NO_ORACLE_PRIOR; + + @Override + public int getOldOracleJoinSyntax() { + return NO_ORACLE_JOIN; + } + + @Override + public void setOldOracleJoinSyntax(int oldOracleJoinSyntax) { + throw new IllegalArgumentException( + "oracle join operator (+) is not supported on this condition"); + } + + @Override + public MemberOfExpression withOldOracleJoinSyntax(int oldOracleJoinSyntax) { + setOldOracleJoinSyntax(oldOracleJoinSyntax); + return this; + } + + @Override + public int getOraclePriorPosition() { + return oraclePriorPosition; + } + + @Override + public void setOraclePriorPosition(int oraclePriorPosition) { + this.oraclePriorPosition = oraclePriorPosition; + } + + public MemberOfExpression withOraclePriorPosition(int oraclePriorPosition) { + setOraclePriorPosition(oraclePriorPosition); + return this; + } + } diff --git a/src/main/java/net/sf/jsqlparser/expression/operators/relational/SimilarToExpression.java b/src/main/java/net/sf/jsqlparser/expression/operators/relational/SimilarToExpression.java index 0818d75c0a..fbb50f25d5 100644 --- a/src/main/java/net/sf/jsqlparser/expression/operators/relational/SimilarToExpression.java +++ b/src/main/java/net/sf/jsqlparser/expression/operators/relational/SimilarToExpression.java @@ -13,7 +13,7 @@ import net.sf.jsqlparser.expression.Expression; import net.sf.jsqlparser.expression.ExpressionVisitor; -public class SimilarToExpression extends BinaryExpression { +public class SimilarToExpression extends BinaryExpression implements SupportsOldOracleJoinSyntax { private boolean not = false; private String escape = null; @@ -38,7 +38,8 @@ public String getStringExpression() { @Override public String toString() { - String retval = getLeftExpression() + " " + (not ? "NOT " : "") + getStringExpression() + String retval = (oraclePriorPosition == ORACLE_PRIOR_START ? "PRIOR " : "") + + getLeftExpression() + " " + (not ? "NOT " : "") + getStringExpression() + " " + getRightExpression(); if (escape != null) { retval += " ESCAPE " + "'" + escape + "'"; @@ -74,4 +75,39 @@ public SimilarToExpression withLeftExpression(Expression arg0) { public SimilarToExpression withRightExpression(Expression arg0) { return (SimilarToExpression) super.withRightExpression(arg0); } + + private int oraclePriorPosition = NO_ORACLE_PRIOR; + + @Override + public int getOldOracleJoinSyntax() { + return NO_ORACLE_JOIN; + } + + @Override + public void setOldOracleJoinSyntax(int oldOracleJoinSyntax) { + throw new IllegalArgumentException( + "oracle join operator (+) is not supported on this condition"); + } + + @Override + public SimilarToExpression withOldOracleJoinSyntax(int oldOracleJoinSyntax) { + setOldOracleJoinSyntax(oldOracleJoinSyntax); + return this; + } + + @Override + public int getOraclePriorPosition() { + return oraclePriorPosition; + } + + @Override + public void setOraclePriorPosition(int oraclePriorPosition) { + this.oraclePriorPosition = oraclePriorPosition; + } + + public SimilarToExpression withOraclePriorPosition(int oraclePriorPosition) { + setOraclePriorPosition(oraclePriorPosition); + return this; + } + } diff --git a/src/main/java/net/sf/jsqlparser/parser/AbstractJSqlParser.java b/src/main/java/net/sf/jsqlparser/parser/AbstractJSqlParser.java index 45580cb5eb..a394558459 100644 --- a/src/main/java/net/sf/jsqlparser/parser/AbstractJSqlParser.java +++ b/src/main/java/net/sf/jsqlparser/parser/AbstractJSqlParser.java @@ -13,7 +13,11 @@ import net.sf.jsqlparser.parser.feature.FeatureConfiguration; import java.util.ArrayList; +import java.util.Collections; +import java.util.Arrays; +import java.util.EnumSet; import java.util.List; +import java.util.Set; public abstract class AbstractJSqlParser

{ @@ -21,8 +25,51 @@ public abstract class AbstractJSqlParser

{ protected boolean errorRecovery = false; protected List parseErrors = new ArrayList<>(); + public enum AdjacentStringLiterals { + OFF, NEWLINE, WHITESPACE + } + public enum Dialect { - ORACLE, EXASOL + ANSI_SQL(AdjacentStringLiterals.NEWLINE), ORACLE, MYSQL(AdjacentStringLiterals.OFF, + Feature.allowBackslashEscapeCharacter, + Feature.allowHashLineComments, + Feature.allowDoubleQuotedStrings), MARIADB(AdjacentStringLiterals.OFF, + Feature.allowBackslashEscapeCharacter, + Feature.allowHashLineComments, + Feature.allowDoubleQuotedStrings), SQLSERVER(AdjacentStringLiterals.OFF, + Feature.allowSquareBracketQuotation), POSTGRESQL( + AdjacentStringLiterals.NEWLINE, + Feature.allowDollarQuotedStringTags), H2, EXASOL, BIGQUERY( + AdjacentStringLiterals.WHITESPACE, + Feature.allowDoubleQuotedStrings, + Feature.allowHashLineComments, + Feature.allowBackslashEscapeCharacter), DATABRICKS( + AdjacentStringLiterals.WHITESPACE, + Feature.allowDoubleQuotedStrings, + Feature.allowBackslashEscapeCharacter), SNOWFLAKE( + Feature.allowBackslashEscapeCharacter), INFORMIX, SPANNER, DORIS, COCKROACHDB, TERADATA; + + private final Set lexerFeatures; + private final AdjacentStringLiterals adjacentStringLiterals; + + Dialect(AdjacentStringLiterals adjacentStringLiterals, Feature... lexerFeatures) { + this.adjacentStringLiterals = adjacentStringLiterals; + this.lexerFeatures = lexerFeatures.length == 0 ? EnumSet.noneOf(Feature.class) + : EnumSet.copyOf(Arrays.asList(lexerFeatures)); + } + + Dialect(Feature... lexerFeatures) { + this(AdjacentStringLiterals.OFF, lexerFeatures); + } + + /** Returns the immutable lexer defaults shared by this dialect preset. */ + public Set getLexerFeatures() { + return Collections.unmodifiableSet(lexerFeatures); + } + + public AdjacentStringLiterals getAdjacentStringLiterals() { + return adjacentStringLiterals; + } } public P withSquareBracketQuotation() { @@ -57,12 +104,43 @@ public P withUnsupportedStatements(boolean allowUnsupportedStatements) { return withFeature(Feature.allowUnsupportedStatements, allowUnsupportedStatements); } + /** Enables GROUP BY ASC/DESC for MySQL versions before 8.0.13. Requires MYSQL dialect. */ + public P withLegacyMySqlGroupBy(boolean enabled) { + return withFeature(Feature.allowLegacyMySqlGroupBy, enabled); + } + public P withTimeOut(long timeOutMillSeconds) { return withFeature(Feature.timeOut, timeOutMillSeconds); } + /** + * Applies this dialect's enabled lexer defaults to the current configuration. Existing options + * remain set; apply explicit overrides after this call, or use a new parser for a fresh preset. + */ public P withDialect(Dialect dialect) { - return withFeature(Feature.dialect, dialect.name()); + withFeature(Feature.dialect, dialect.name()); + if (dialect.getAdjacentStringLiterals() != AdjacentStringLiterals.OFF) { + withAdjacentStringLiterals(dialect.getAdjacentStringLiterals()); + } + for (Feature lexerFeature : dialect.getLexerFeatures()) { + withFeature(lexerFeature, true); + } + return me(); + } + + public P withAdjacentStringLiterals() { + return withAdjacentStringLiterals(AdjacentStringLiterals.NEWLINE); + } + + public P withAdjacentStringLiterals(boolean adjacentStringLiterals) { + return withAdjacentStringLiterals( + adjacentStringLiterals ? AdjacentStringLiterals.NEWLINE + : AdjacentStringLiterals.OFF); + } + + public P withAdjacentStringLiterals(AdjacentStringLiterals adjacentStringLiterals) { + getConfiguration().setValue(Feature.adjacentStringLiterals, adjacentStringLiterals); + return me(); } public P withAllowedNestingDepth(int allowedNestingDepth) { @@ -77,6 +155,30 @@ public P withBackslashEscapeCharacter(boolean allowBackslashEscapeCharacter) { return withFeature(Feature.allowBackslashEscapeCharacter, allowBackslashEscapeCharacter); } + /** + * Controls tagged dollar quotes; disabled by default, enabled by the PostgreSQL dialect preset. + * False preserves dollar-containing identifier spellings. + */ + public P withDollarQuotedStringTags(boolean allowDollarQuotedStringTags) { + return withFeature(Feature.allowDollarQuotedStringTags, allowDollarQuotedStringTags); + } + + public P withDoubleQuotedStrings() { + return withFeature(Feature.allowDoubleQuotedStrings, true); + } + + public P withDoubleQuotedStrings(boolean allowDoubleQuotedStrings) { + return withFeature(Feature.allowDoubleQuotedStrings, allowDoubleQuotedStrings); + } + + public P withHashLineComments() { + return withFeature(Feature.allowHashLineComments, true); + } + + public P withHashLineComments(boolean allowHashLineComments) { + return withFeature(Feature.allowHashLineComments, allowHashLineComments); + } + public P withUnparenthesizedSubSelects() { return withFeature(Feature.allowUnparenthesizedSubSelects, true); } diff --git a/src/main/java/net/sf/jsqlparser/parser/CCJSqlParserUtil.java b/src/main/java/net/sf/jsqlparser/parser/CCJSqlParserUtil.java index 31e0709758..f187b7da1f 100644 --- a/src/main/java/net/sf/jsqlparser/parser/CCJSqlParserUtil.java +++ b/src/main/java/net/sf/jsqlparser/parser/CCJSqlParserUtil.java @@ -14,6 +14,8 @@ import java.io.Reader; import java.util.Stack; import java.util.concurrent.Callable; +import java.util.concurrent.CancellationException; +import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; @@ -30,6 +32,7 @@ import net.sf.jsqlparser.parser.feature.Feature; import net.sf.jsqlparser.statement.Statement; import net.sf.jsqlparser.statement.Statements; +import net.sf.jsqlparser.statement.create.table.ColDataType; /** * Toolfunctions to start and use JSqlParser. @@ -59,6 +62,13 @@ public static Statement parse(Reader statementReader) throws JSQLParserException return statement; } + /** + * Parses a single SQL statement. + * + * @param sql the SQL statement to parse + * @return the parsed statement + * @throws JSQLParserException if the input is null, empty, or cannot be parsed + */ public static Statement parse(String sql) throws JSQLParserException { return parse(sql, null); } @@ -72,17 +82,15 @@ public static Statement parse(String sql) throws JSQLParserException { * CCJSqlParserUtil.parse("select * from [mytable]", parser -> parser.withSquareBracketQuotation(true)); * } * - * @param sql - * @param consumer - * @return - * @throws JSQLParserException + * @param sql the SQL statement to parse + * @param consumer parser configuration callback, or {@code null} + * @return the parsed statement + * @throws JSQLParserException if the input is null, empty, or cannot be parsed */ public static Statement parse(String sql, Consumer consumer) throws JSQLParserException { - if (sql == null || sql.isEmpty()) { - return null; - } + requireStatementInput(sql); ExecutorService executorService = Executors.newSingleThreadExecutor(); Statement statement; @@ -96,12 +104,19 @@ public static Statement parse(String sql, Consumer consumer) return statement; } + /** + * Parses a single SQL statement using the caller's executor, which is left open. + * + * @param sql the SQL statement to parse + * @param executorService executor to use for parsing + * @param consumer parser configuration callback, or {@code null} + * @return the parsed statement + * @throws JSQLParserException if the input is null, empty, or cannot be parsed + */ public static Statement parse(String sql, ExecutorService executorService, Consumer consumer) throws JSQLParserException { - if (sql == null || sql.isEmpty()) { - return null; - } + requireStatementInput(sql); Statement statement; // first, try to parse fast and simple @@ -116,6 +131,9 @@ public static Statement parse(String sql, ExecutorService executorService, LOGGER.info("Trying SIMPLE parsing " + (allowComplex ? "first" : "only")); statement = parseStatement(parser.withAllowComplexParsing(false), executorService); } catch (JSQLParserException ex) { + if (wasExecutionStopped(ex)) { + throw ex; + } LOGGER.info("Nesting Depth" + getNestingDepth(sql)); if (allowComplex && (allowedNestingDepth < 0 || getNestingDepth(sql) <= allowedNestingDepth)) { @@ -133,6 +151,12 @@ public static Statement parse(String sql, ExecutorService executorService, return statement; } + private static void requireStatementInput(String sql) throws JSQLParserException { + if (sql == null || sql.isEmpty()) { + throw new JSQLParserException("SQL statement must not be null or empty."); + } + } + public static CCJSqlParser newParser(String sql) { if (sql == null || sql.isEmpty()) { return null; @@ -199,6 +223,61 @@ public static Expression parseExpression(String expression, boolean allowPartial }); } + /** + * Parses a column data type fragment. The complete input must represent the data type; trailing + * tokens are rejected. + * + * @param columnDataType the column data type fragment to parse + * @return the parsed column data type, or {@code null} for a null or empty input + * @throws JSQLParserException when the input cannot be parsed completely + * @see #parseColDataType(String, Consumer) + */ + public static ColDataType parseColDataType(String columnDataType) throws JSQLParserException { + return parseColDataType(columnDataType, null); + } + + /** + * Parses a column data type fragment while allowing the parser to be configured. The complete + * input must represent the data type; trailing tokens are rejected. + * + * @param columnDataType the column data type fragment to parse + * @param consumer parser configuration callback, or {@code null} + * @return the parsed column data type, or {@code null} for a null or empty input + * @throws JSQLParserException when the input cannot be parsed completely + */ + public static ColDataType parseColDataType(String columnDataType, + Consumer consumer) throws JSQLParserException { + if (columnDataType == null || columnDataType.isEmpty()) { + return null; + } + + try { + return parseColDataType(columnDataType, false, consumer); + } catch (JSQLParserException ex) { + return parseColDataType(columnDataType, true, consumer); + } + } + + private static ColDataType parseColDataType(String columnDataType, boolean allowComplexParsing, + Consumer consumer) throws JSQLParserException { + CCJSqlParser parser = newParser(columnDataType) + .withAllowComplexParsing(allowComplexParsing); + if (consumer != null) { + consumer.accept(parser); + } + + try { + ColDataType result = parser.ColDataType(); + if (parser.getNextToken().kind != CCJSqlParserTokenManager.EOF) { + throw new JSQLParserException( + "could only parse partial column data type " + result); + } + return result; + } catch (ParseException ex) { + throw new JSQLParserException(columnDataType, ex); + } + } + @SuppressWarnings("PMD.CyclomaticComplexity") public static Expression parseExpression(String expressionStr, boolean allowPartialParse, Consumer consumer) throws JSQLParserException { @@ -333,65 +412,49 @@ public static Expression parseCondExpression(String conditionalExpressionStr, public static Statement parseStatement(CCJSqlParser parser, ExecutorService executorService) throws JSQLParserException { - Statement statement; - Future future = executorService.submit(new Callable() { - @Override - public Statement call() throws ParseException { - return parser.Statement(); - } - }); - try { - statement = future.get(parser.getAsLong(Feature.timeOut), - TimeUnit.MILLISECONDS); - } catch (TimeoutException ex) { - parser.interrupted = true; - future.cancel(true); - throw new JSQLParserException("Time out occurred.", ex); - } catch (Exception ex) { - throw new JSQLParserException(ex); - } - return statement; + return executeParser(parser, executorService, parser::Statement); } /** * Parse a statement list. * - * @return the statements parsed + * @return the statements parsed, or a new empty list for null or empty input */ public static Statements parseStatements(String sqls) throws JSQLParserException { - if (sqls == null || sqls.isEmpty()) { - return null; - } - return parseStatements(sqls, null); } + /** + * Parses a statement list with optional parser configuration. + * + * @return the statements parsed, or a new empty list for null or empty input + */ public static Statements parseStatements(String sqls, Consumer consumer) throws JSQLParserException { if (sqls == null || sqls.isEmpty()) { - return null; + return new Statements(); } ExecutorService executorService = Executors.newSingleThreadExecutor(); - final Statements statements = parseStatements(sqls, executorService, consumer); - executorService.shutdown(); - - return statements; + try { + return parseStatements(sqls, executorService, consumer); + } finally { + executorService.shutdown(); + } } /** - * Parse a statement list. + * Parses a statement list using the caller's executor, which is left open. * - * @return the statements parsed + * @return the statements parsed, or a new empty list for null or empty input */ public static Statements parseStatements(String sqls, ExecutorService executorService, Consumer consumer) throws JSQLParserException { if (sqls == null || sqls.isEmpty()) { - return null; + return new Statements(); } - Statements statements = null; CCJSqlParser parser = newParser(sqls); if (consumer != null) { consumer.accept(parser); @@ -401,8 +464,11 @@ public static Statements parseStatements(String sqls, ExecutorService executorSe // first, try to parse fast and simple try { - statements = parseStatements(parser.withAllowComplexParsing(false), executorService); + return parseStatements(parser.withAllowComplexParsing(false), executorService); } catch (JSQLParserException ex) { + if (wasExecutionStopped(ex)) { + throw ex; + } // when fast simple parsing fails, try complex parsing but only if it has a chance to // succeed if (allowComplex @@ -412,10 +478,10 @@ public static Statements parseStatements(String sqls, ExecutorService executorSe if (consumer != null) { consumer.accept(parser); } - statements = parseStatements(parser.withAllowComplexParsing(true), executorService); + return parseStatements(parser.withAllowComplexParsing(true), executorService); } + throw ex; } - return statements; } /** @@ -427,24 +493,35 @@ public static Statements parseStatements(String sqls, ExecutorService executorSe */ public static Statements parseStatements(CCJSqlParser parser, ExecutorService executorService) throws JSQLParserException { - Statements statements = null; - Future future = executorService.submit(new Callable() { - @Override - public Statements call() throws ParseException { - return parser.Statements(); - } - }); + return executeParser(parser, executorService, parser::Statements); + } + + private static T executeParser(CCJSqlParser parser, ExecutorService executorService, + Callable operation) throws JSQLParserException { + Future future = executorService.submit(operation); try { - statements = future.get(parser.getAsLong(Feature.timeOut), - TimeUnit.MILLISECONDS); + return future.get(parser.getAsLong(Feature.timeOut), TimeUnit.MILLISECONDS); + } catch (InterruptedException ex) { + parser.interrupted = true; + future.cancel(true); + Thread.currentThread().interrupt(); + throw new JSQLParserException(ex); } catch (TimeoutException ex) { parser.interrupted = true; future.cancel(true); throw new JSQLParserException("Time out occurred.", ex); - } catch (Exception ex) { + } catch (CancellationException ex) { + parser.interrupted = true; + throw new JSQLParserException(ex); + } catch (ExecutionException ex) { throw new JSQLParserException(ex); } - return statements; + } + + private static boolean wasExecutionStopped(JSQLParserException exception) { + Throwable cause = exception.getCause(); + return cause instanceof InterruptedException || cause instanceof TimeoutException + || cause instanceof CancellationException; } public static void streamStatements(StatementListener listener, InputStream is, String encoding) diff --git a/src/main/java/net/sf/jsqlparser/parser/RowPatternTokenContext.java b/src/main/java/net/sf/jsqlparser/parser/RowPatternTokenContext.java new file mode 100644 index 0000000000..f10b7c12b4 --- /dev/null +++ b/src/main/java/net/sf/jsqlparser/parser/RowPatternTokenContext.java @@ -0,0 +1,83 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.parser; + +import java.util.ArrayDeque; +import java.util.Deque; + +/** + * Recognizes the BigQuery PATTERN lexical region even during JavaCC lookahead. The required initial + * ORDER BY (or optional PARTITION BY) distinguishes a clause from an ordinary function named + * MATCH_RECOGNIZE. Quoted tokens never change this state. + */ +final class RowPatternTokenContext implements CCJSqlParserConstants { + private final Deque frames = new ArrayDeque<>(); + private int depth; + private boolean pendingClause; + + private static final class Frame { + private final int depth; + private int phase; + private int patternDepth = -1; + private boolean pendingPattern; + private boolean patternComplete; + + private Frame(int depth) { + this.depth = depth; + } + } + + boolean isIdle() { + return frames.isEmpty() && !pendingClause; + } + + boolean isInPattern() { + return !frames.isEmpty() && frames.peek().patternDepth >= 0; + } + + void accept(Token token) { + if (token.kind == OPENING_BRACKET) { + depth++; + if (pendingClause) { + frames.push(new Frame(depth)); + pendingClause = false; + return; + } + if (!frames.isEmpty() && frames.peek().pendingPattern) { + frames.peek().patternDepth = depth; + frames.peek().pendingPattern = false; + } + } else if (token.kind == CLOSING_BRACKET) { + if (!frames.isEmpty()) { + Frame frame = frames.peek(); + if (frame.patternDepth == depth) { + frame.patternDepth = -1; + frame.patternComplete = true; + } + if (frame.depth == depth) { + frames.pop(); + } + } + depth--; + } else if (!frames.isEmpty() && depth == frames.peek().depth) { + Frame frame = frames.peek(); + if (frame.phase == 0) { + frame.phase = token.kind == K_ORDER || token.kind == K_PARTITION ? 1 : -1; + } else if (frame.phase == 1) { + frame.phase = token.kind == K_BY ? 2 : -1; + } else if (frame.phase == 2 && !frame.patternComplete) { + frame.pendingPattern = token.kind == S_IDENTIFIER + && "PATTERN".equalsIgnoreCase(token.image); + } + } + pendingClause = token.kind == S_IDENTIFIER + && "MATCH_RECOGNIZE".equalsIgnoreCase(token.image); + } +} diff --git a/src/main/java/net/sf/jsqlparser/parser/SimpleCharStream.java b/src/main/java/net/sf/jsqlparser/parser/SimpleCharStream.java index 0e633ebbb5..ca81a8dd11 100644 --- a/src/main/java/net/sf/jsqlparser/parser/SimpleCharStream.java +++ b/src/main/java/net/sf/jsqlparser/parser/SimpleCharStream.java @@ -7,9 +7,18 @@ * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 * #L% */ -/* Generated By:JavaCC: Do not edit this line. SimpleCharStream.java Version 8.0.0 */ +/* Generated By:JavaCC: Do not edit this line by hand. SimpleCharStream.java Version 8.0.0 */ +/* + * Warning: this file is maintained by hand on top of the JavaCC template. Among other manual + * adjustments it carries the in-buffer '#' rewrite in BeginToken(), which + * Feature.allowHashLineComments depends on. Never overwrite it with a freshly generated + * SimpleCharStream, that would silently drop the rewrite and break allowHashLineComments. + */ package net.sf.jsqlparser.parser; +import net.sf.jsqlparser.parser.feature.Feature; +import net.sf.jsqlparser.parser.feature.FeatureConfiguration; + /** * An implementation of interface CharStream, where the stream is assumed to contain only ASCII * characters (without unicode processing). @@ -42,6 +51,17 @@ public class SimpleCharStream { int available; int tokenBegin; + // MySQL `#` line comments (#2499): under the flag a token-start `#` is + // rewritten in the buffer to HASH_SUBSTITUTION, a character no other + // lexical rule starts with, so the dedicated HASH_LINE_COMMENT production + // wins the match for every `#` form (`# c`, `#c`, `#>`, ...). Rewriting + // the buffer itself (instead of synthesizing reads) keeps the matcher's + // backup / re-read arithmetic intact; the production's action restores + // the `#` in the token image. Wired (with the token manager's + // configuration) before parsing, null keeps this inert. + static final char HASH_SUBSTITUTION = '\u0001'; + FeatureConfiguration featureConfiguration; + /** * Constructor. */ @@ -159,6 +179,11 @@ public final char BeginToken() throws java.io.IOException { absoluteTokenBegin = totalCharsRead; + if (c == '#' && featureConfiguration != null + && featureConfiguration.getAsBoolean(Feature.allowHashLineComments)) { + buffer[bufpos] = HASH_SUBSTITUTION; + c = HASH_SUBSTITUTION; + } return c; } @@ -325,6 +350,23 @@ public void ReInit(Provider dstream) { * Get token literal value. */ public String GetImage() { + // restore the `#` rewritten to HASH_SUBSTITUTION: only the comment + // token's own window starts with the sentinel (token-start rewrites + // only), so this is exact and covers every consumer of GetImage. + // The wiring check keeps parses that never opted in on the original + // cost (the stream is only wired through the feature consumers / + // withConfiguration) + if (featureConfiguration == null) { + return doGetImage(); + } + String image = doGetImage(); + if (!image.isEmpty() && image.charAt(0) == HASH_SUBSTITUTION) { + image = "#" + image.substring(1); + } + return image; + } + + private String doGetImage() { if (bufpos >= tokenBegin) { return new String(buffer, tokenBegin, bufpos - tokenBegin + 1); } else { diff --git a/src/main/java/net/sf/jsqlparser/parser/feature/Feature.java b/src/main/java/net/sf/jsqlparser/parser/feature/Feature.java index d786f5170a..2328c8e269 100644 --- a/src/main/java/net/sf/jsqlparser/parser/feature/Feature.java +++ b/src/main/java/net/sf/jsqlparser/parser/feature/Feature.java @@ -15,6 +15,7 @@ import net.sf.jsqlparser.expression.OracleHierarchicalExpression; import net.sf.jsqlparser.expression.OracleHint; import net.sf.jsqlparser.expression.operators.relational.SupportsOldOracleJoinSyntax; +import net.sf.jsqlparser.parser.AbstractJSqlParser; import net.sf.jsqlparser.statement.Block; import net.sf.jsqlparser.statement.Commit; import net.sf.jsqlparser.statement.CreateFunctionalStatement; @@ -30,6 +31,7 @@ import net.sf.jsqlparser.statement.alter.sequence.AlterSequence; import net.sf.jsqlparser.statement.analyze.Analyze; import net.sf.jsqlparser.statement.comment.Comment; +import net.sf.jsqlparser.statement.create.database.CreateDatabase; import net.sf.jsqlparser.statement.create.function.CreateFunction; import net.sf.jsqlparser.statement.create.index.CreateIndex; import net.sf.jsqlparser.statement.create.procedure.CreateProcedure; @@ -69,10 +71,16 @@ public enum Feature { * "SELECT" */ select, + /** Structured FROM row pattern recognition. */ + matchRecognize, + /** BigQuery longest-match option. */ + matchRecognizeOptions, /** * "GROUP BY" */ selectGroupBy, + /** Explicit ASC/DESC on GROUP BY items in legacy MySQL. */ + selectGroupByOrdering, /** * "GROUPING SETS" */ @@ -178,6 +186,10 @@ public enum Feature { * "SEMI" join */ joinSemi, + /** + * @see net.sf.jsqlparser.statement.select.Join#isAnti() + */ + joinAnti, /** * "INNER" join */ @@ -191,6 +203,9 @@ public enum Feature { */ joinApply, + /** Nearest-match ASOF join. */ + joinAsOf, + joinWindow, joinUsingColumns, /** @@ -333,6 +348,9 @@ public enum Feature { * @see Update */ update, + + /** Application-time FOR PORTION OF in UPDATE and DELETE. */ + forPortion, /** * "UPDATE table1 SET ... FROM table2 */ @@ -451,7 +469,9 @@ public enum Feature { * * @see Execute */ - execute, executeExec, executeCall, executeExecute, + oracleBlock, + + execute, executeExec, executeCall, executeExecute, doStatement, insertBulk, /** * SQL "EXECUTE" statement is allowed @@ -475,6 +495,12 @@ public enum Feature { */ drop, dropTable, dropIndex, dropView, dropSchema, dropSequence, dropTableIfExists, dropIndexIfExists, dropViewIfExists, dropSchemaIfExists, dropSequenceIfExists, + /** + * SQL "CREATE DATABASE" statement is allowed + * + * @see CreateDatabase + */ + createDatabase, /** * SQL "CREATE SCHEMA" statement is allowed * @@ -551,6 +577,12 @@ public enum Feature { * @see CreateSequence */ createSequence, + /** Publication and subscription definitions. */ + createPublication, alterPublication, createSubscription, alterSubscription, + /** + * Structured type, domain and extension statements. + */ + createType, alterType, createDomain, alterDomain, createExtension, alterExtension, /** * SQL "CREATE SYNONYM" statement is allowed * @@ -586,6 +618,9 @@ public enum Feature { */ commentOnView, + /** Additional structured catalog targets of COMMENT ON. */ + commentOnIndex, commentOnSchema, commentOnSequence, commentOnDomain, commentOnType, commentOnMaterializedView, commentOnFunction, commentOnConstraint, + /** * SQL "DESCRIBE" statement is allowed * @@ -626,10 +661,12 @@ public enum Feature { * @see UseStatement */ use, + /** SQL Server session permission to insert explicit identity values into a table. */ + setIdentityInsert, /** * @see Grant */ - grant, + grant, revoke, createRole, alterRole, alterDefaultPrivileges, /** * @see Function */ @@ -646,6 +683,8 @@ public enum Feature { * @see CreateFunctionalStatement */ functionalStatement, + + alterFunction, alterProcedure, createOrAlterRoutine, /** * SQL block starting with "BEGIN" and ends with "END" statement is allowed * @@ -656,10 +695,15 @@ public enum Feature { * @see DeclareStatement */ declare, + + /** SQL Server local table variables in queries and DML targets. */ + tableVariable, /** * @see SetStatement */ set, + /** SQL Server SET option [, option] ON | OFF. */ + sqlServerSetOptions, /** * @see ResetStatement */ @@ -767,6 +811,9 @@ public enum Feature { */ allowPostgresSpecificSyntax(false), + /** Enables legacy GROUP BY ordering with the MYSQL dialect; disabled by default. */ + allowLegacyMySqlGroupBy(false), + // PERFORMANCE /** @@ -788,6 +835,32 @@ public enum Feature { */ allowBackslashEscapeCharacter(false), + /** + * allows double quoted String Literals (BigQuery, Spark/Databricks, MySQL default sql_mode); + * disabled by default, where double quotes stay quoted identifiers (ANSI SQL) + */ + allowDoubleQuotedStrings(false), + + /** + * Recognizes PostgreSQL $tag$...$tag$ literals; disabled by default to preserve unquoted + * identifiers, enabled by the PostgreSQL dialect preset. Untagged $$ literals are unaffected. + */ + allowDollarQuotedStringTags(false), + + /** + * concatenates adjacent String Literals: NEWLINE when separated by whitespace with at least one + * newline (the SQL standard and PostgreSQL), WHITESPACE across any whitespace (GoogleSQL, + * Spark/Databricks); OFF by default, where the second literal stays an alias (MySQL, SQL + * Server) or fails (everywhere else) + */ + adjacentStringLiterals(AbstractJSqlParser.AdjacentStringLiterals.OFF), + + /** + * allows MySQL `#` line comments; disabled by default, where a lone `#` stays the binary + * operator (#2507: PostgreSQL bitwise XOR / geometric intersection) + */ + allowHashLineComments(false), + /** * allows sub selects without parentheses, e.g. `select * from dual where 1 = select 1` */ diff --git a/src/main/java/net/sf/jsqlparser/schema/Sequence.java b/src/main/java/net/sf/jsqlparser/schema/Sequence.java index 5d7b960cdc..efff62a970 100644 --- a/src/main/java/net/sf/jsqlparser/schema/Sequence.java +++ b/src/main/java/net/sf/jsqlparser/schema/Sequence.java @@ -30,6 +30,15 @@ public class Sequence extends ASTNodeAccessImpl implements MultiPartName { private List parameters; private String dataType; + private SequenceOwnership ownership; + + public SequenceOwnership getOwnership() { + return ownership; + } + + public void setOwnership(SequenceOwnership ownership) { + this.ownership = ownership; + } public Sequence() {} @@ -151,6 +160,9 @@ public String toString() { sql.append(" ").append(parameter.formatParameter()); } } + if (ownership != null) { + sql.append(' ').append(ownership); + } return sql.toString(); } @@ -175,7 +187,7 @@ public Sequence addParameters(Collection parameters) { * The available parameters to a sequence */ public enum ParameterType { - INCREMENT_BY, INCREMENT, START_WITH, START, RESTART_WITH, MAXVALUE, NOMAXVALUE, MINVALUE, NOMINVALUE, CYCLE, NOCYCLE, CACHE, NOCACHE, ORDER, NOORDER, KEEP, NOKEEP, SESSION, GLOBAL; + INCREMENT_BY, INCREMENT, START_WITH, START, RESTART_WITH, MAXVALUE, NOMAXVALUE, MINVALUE, NOMINVALUE, CYCLE, NOCYCLE, CACHE, NOCACHE, ORDER, NOORDER, KEEP, NOKEEP, SESSION, GLOBAL, NO_MINVALUE, NO_MAXVALUE, NO_CYCLE; public static ParameterType from(String type) { return Enum.valueOf(ParameterType.class, type.toUpperCase(Locale.ROOT)); @@ -185,7 +197,7 @@ public static ParameterType from(String type) { /** * Represents a parameter when declaring a sequence */ - public static class Parameter { + public static class Parameter implements java.io.Serializable { private final ParameterType option; private Long value; @@ -198,6 +210,15 @@ public Long getValue() { return value; } + public ParameterType getOption() { + return option; + } + + @Override + public String toString() { + return formatParameter(); + } + public void setValue(Long value) { this.value = value; } @@ -222,6 +243,10 @@ public String formatParameter() { case MINVALUE: case CACHE: return prefix(option.name()); + case NO_MINVALUE: + case NO_MAXVALUE: + case NO_CYCLE: + return option.name().replace('_', ' '); default: // fallthrough just return option name return option.name(); diff --git a/src/main/java/net/sf/jsqlparser/schema/SequenceOwnership.java b/src/main/java/net/sf/jsqlparser/schema/SequenceOwnership.java new file mode 100644 index 0000000000..8f3f95322e --- /dev/null +++ b/src/main/java/net/sf/jsqlparser/schema/SequenceOwnership.java @@ -0,0 +1,43 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.schema; + +import java.io.Serializable; +import java.util.Objects; + +/** Distinguishes explicit OWNED BY NONE from an owning column and from an omitted clause. */ +public class SequenceOwnership implements Serializable { + private final Column column; + + private SequenceOwnership(Column column) { + this.column = column; + } + + public static SequenceOwnership none() { + return new SequenceOwnership(null); + } + + public static SequenceOwnership ownedBy(Column column) { + return new SequenceOwnership(Objects.requireNonNull(column, "column")); + } + + public Column getColumn() { + return column; + } + + public boolean isNone() { + return column == null; + } + + @Override + public String toString() { + return "OWNED BY " + (column == null ? "NONE" : column); + } +} diff --git a/src/main/java/net/sf/jsqlparser/schema/Server.java b/src/main/java/net/sf/jsqlparser/schema/Server.java index 9ac9bd2d2b..7e4eaa51ca 100644 --- a/src/main/java/net/sf/jsqlparser/schema/Server.java +++ b/src/main/java/net/sf/jsqlparser/schema/Server.java @@ -14,7 +14,7 @@ public final class Server implements MultiPartName { public static final Pattern SERVER_PATTERN = - Pattern.compile("\\[([^\\]]+?)(?:\\\\([^\\]]+))?\\]"); + Pattern.compile("\\[([^\\]\\\\]+)(?:\\\\([^\\]]+))?\\]"); private String serverName; diff --git a/src/main/java/net/sf/jsqlparser/schema/Table.java b/src/main/java/net/sf/jsqlparser/schema/Table.java index 1de14a8a5e..b79abbde7a 100644 --- a/src/main/java/net/sf/jsqlparser/schema/Table.java +++ b/src/main/java/net/sf/jsqlparser/schema/Table.java @@ -44,6 +44,8 @@ public class Table extends ASTNodeAccessImpl private List partDelimiters = new ArrayList<>(); + private boolean tableVariable; + // holds the various `time travel` syntax for BigQuery, RedShift, Snowflake or RedShift private String timeTravelStr = null; @@ -67,6 +69,20 @@ public class Table extends ASTNodeAccessImpl public Table() {} + /** Whether this reference names a local table variable rather than a catalog table. */ + public boolean isTableVariable() { + return tableVariable; + } + + public void setTableVariable(boolean tableVariable) { + this.tableVariable = tableVariable; + } + + public Table withTableVariable(boolean tableVariable) { + setTableVariable(tableVariable); + return this; + } + /** * Instantiates a new Table. * @@ -527,6 +543,9 @@ public Table setResolvedTable(Table resolvedTable) { * @return the provided table */ public Table setUnsetCatalogAndSchema(String currentCatalogName, String currentSchemaName) { + if (tableVariable) { + return this; + } String databaseName = getDatabaseName(); if (databaseName == null || databaseName.isEmpty()) { setDatabaseName(currentCatalogName); @@ -561,6 +580,7 @@ public static Table[] setUnsetCatalogAndSchema(String currentCatalogName, @Override public Table clone() { Table clone = new Table(this.getFullyQualifiedName()); + clone.setTableVariable(tableVariable); clone.setResolvedTable(this.resolvedTable != null ? this.resolvedTable.clone() : null); return clone; } diff --git a/src/main/java/net/sf/jsqlparser/statement/AssertStatement.java b/src/main/java/net/sf/jsqlparser/statement/AssertStatement.java new file mode 100644 index 0000000000..0879faf685 --- /dev/null +++ b/src/main/java/net/sf/jsqlparser/statement/AssertStatement.java @@ -0,0 +1,81 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.statement; + +import net.sf.jsqlparser.expression.Expression; +import net.sf.jsqlparser.expression.StringValue; + +/** + * BigQuery's {@code ASSERT expression [AS description]}, which fails the script when the expression + * does not evaluate to {@code TRUE}. + * + * @see Debugging + * statements + */ +public class AssertStatement implements Statement { + private Expression expression; + private StringValue description; + + public AssertStatement() {} + + public AssertStatement(Expression expression) { + this.expression = expression; + } + + public AssertStatement(Expression expression, StringValue description) { + this.expression = expression; + this.description = description; + } + + public Expression getExpression() { + return expression; + } + + public void setExpression(Expression expression) { + this.expression = expression; + } + + public AssertStatement withExpression(Expression expression) { + setExpression(expression); + return this; + } + + public StringValue getDescription() { + return description; + } + + public void setDescription(StringValue description) { + this.description = description; + } + + public AssertStatement withDescription(StringValue description) { + setDescription(description); + return this; + } + + public StringBuilder appendTo(StringBuilder builder) { + builder.append("ASSERT ").append(expression); + if (description != null) { + builder.append(" AS ").append(description); + } + return builder; + } + + @Override + public String toString() { + return appendTo(new StringBuilder()).toString(); + } + + @Override + public T accept(StatementVisitor statementVisitor, S context) { + return statementVisitor.visit(this, context); + } +} diff --git a/src/main/java/net/sf/jsqlparser/statement/AttachStatement.java b/src/main/java/net/sf/jsqlparser/statement/AttachStatement.java new file mode 100644 index 0000000000..7ad71dd546 --- /dev/null +++ b/src/main/java/net/sf/jsqlparser/statement/AttachStatement.java @@ -0,0 +1,121 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.statement; + +import net.sf.jsqlparser.statement.select.PlainSelect; + +import java.util.List; + +/** + * DuckDB's {@code ATTACH [DATABASE] [IF NOT EXISTS] path [AS alias] [(options)]}, which adds + * another database file or remote database to the session. + * + * @see ATTACH + */ +public class AttachStatement implements Statement { + private String databasePath; + private String alias; + private boolean usingDatabaseKeyword; + private boolean ifNotExists; + private List options; + + public String getDatabasePath() { + return databasePath; + } + + public void setDatabasePath(String databasePath) { + this.databasePath = databasePath; + } + + public AttachStatement withDatabasePath(String databasePath) { + setDatabasePath(databasePath); + return this; + } + + public String getAlias() { + return alias; + } + + public void setAlias(String alias) { + this.alias = alias; + } + + public AttachStatement withAlias(String alias) { + setAlias(alias); + return this; + } + + public boolean isUsingDatabaseKeyword() { + return usingDatabaseKeyword; + } + + public void setUsingDatabaseKeyword(boolean usingDatabaseKeyword) { + this.usingDatabaseKeyword = usingDatabaseKeyword; + } + + public AttachStatement withUsingDatabaseKeyword(boolean usingDatabaseKeyword) { + setUsingDatabaseKeyword(usingDatabaseKeyword); + return this; + } + + public boolean isIfNotExists() { + return ifNotExists; + } + + public void setIfNotExists(boolean ifNotExists) { + this.ifNotExists = ifNotExists; + } + + public AttachStatement withIfNotExists(boolean ifNotExists) { + setIfNotExists(ifNotExists); + return this; + } + + public List getOptions() { + return options; + } + + public void setOptions(List options) { + this.options = options; + } + + public AttachStatement withOptions(List options) { + setOptions(options); + return this; + } + + public StringBuilder appendTo(StringBuilder builder) { + builder.append("ATTACH "); + if (usingDatabaseKeyword) { + builder.append("DATABASE "); + } + if (ifNotExists) { + builder.append("IF NOT EXISTS "); + } + builder.append(databasePath); + if (alias != null) { + builder.append(" AS ").append(alias); + } + if (options != null && !options.isEmpty()) { + builder.append(" ").append(PlainSelect.getStringList(options, true, true)); + } + return builder; + } + + @Override + public String toString() { + return appendTo(new StringBuilder()).toString(); + } + + @Override + public T accept(StatementVisitor statementVisitor, S context) { + return statementVisitor.visit(this, context); + } +} diff --git a/src/main/java/net/sf/jsqlparser/statement/ConnectStatement.java b/src/main/java/net/sf/jsqlparser/statement/ConnectStatement.java new file mode 100644 index 0000000000..a5ae350c64 --- /dev/null +++ b/src/main/java/net/sf/jsqlparser/statement/ConnectStatement.java @@ -0,0 +1,69 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.statement; + +/** + * DuckDB's {@code CONNECT target [AS alias]}, which routes the session's queries to a remote + * database. + */ +public class ConnectStatement implements Statement { + private String target; + private String alias; + + public ConnectStatement() {} + + public ConnectStatement(String target) { + this.target = target; + } + + public String getTarget() { + return target; + } + + public void setTarget(String target) { + this.target = target; + } + + public ConnectStatement withTarget(String target) { + setTarget(target); + return this; + } + + public String getAlias() { + return alias; + } + + public void setAlias(String alias) { + this.alias = alias; + } + + public ConnectStatement withAlias(String alias) { + setAlias(alias); + return this; + } + + public StringBuilder appendTo(StringBuilder builder) { + builder.append("CONNECT ").append(target); + if (alias != null) { + builder.append(" AS ").append(alias); + } + return builder; + } + + @Override + public String toString() { + return appendTo(new StringBuilder()).toString(); + } + + @Override + public T accept(StatementVisitor statementVisitor, S context) { + return statementVisitor.visit(this, context); + } +} diff --git a/src/main/java/net/sf/jsqlparser/statement/CopyStatement.java b/src/main/java/net/sf/jsqlparser/statement/CopyStatement.java new file mode 100644 index 0000000000..0dbc2892b4 --- /dev/null +++ b/src/main/java/net/sf/jsqlparser/statement/CopyStatement.java @@ -0,0 +1,139 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.statement; + +import net.sf.jsqlparser.expression.operators.relational.ExpressionList; +import net.sf.jsqlparser.schema.Column; +import net.sf.jsqlparser.schema.Table; +import net.sf.jsqlparser.statement.select.PlainSelect; +import net.sf.jsqlparser.statement.select.Select; + +import java.util.List; + +/** + * {@code COPY table [(columns)] FROM path [(options)]} and + * {@code COPY {table | (query)} TO path [(options)]}, DuckDB's bulk import and export statement. + * + * @see COPY + */ +public class CopyStatement implements Statement { + private Table table; + private ExpressionList columns; + private Select select; + private boolean from; + private String path; + private List options; + + public Table getTable() { + return table; + } + + public void setTable(Table table) { + this.table = table; + } + + public CopyStatement withTable(Table table) { + setTable(table); + return this; + } + + public ExpressionList getColumns() { + return columns; + } + + public void setColumns(ExpressionList columns) { + this.columns = columns; + } + + public CopyStatement withColumns(ExpressionList columns) { + setColumns(columns); + return this; + } + + public Select getSelect() { + return select; + } + + public void setSelect(Select select) { + this.select = select; + } + + public CopyStatement withSelect(Select select) { + setSelect(select); + return this; + } + + /** {@code true} for COPY ... FROM (import), {@code false} for COPY ... TO (export). */ + public boolean isFrom() { + return from; + } + + public void setFrom(boolean from) { + this.from = from; + } + + public CopyStatement withFrom(boolean from) { + setFrom(from); + return this; + } + + public String getPath() { + return path; + } + + public void setPath(String path) { + this.path = path; + } + + public CopyStatement withPath(String path) { + setPath(path); + return this; + } + + public List getOptions() { + return options; + } + + public void setOptions(List options) { + this.options = options; + } + + public CopyStatement withOptions(List options) { + setOptions(options); + return this; + } + + public StringBuilder appendTo(StringBuilder builder) { + builder.append("COPY "); + if (select != null) { + builder.append("(").append(select).append(")"); + } else { + builder.append(table); + if (columns != null && !columns.isEmpty()) { + builder.append(" (").append(columns).append(")"); + } + } + builder.append(from ? " FROM " : " TO ").append(path); + if (options != null && !options.isEmpty()) { + builder.append(" ").append(PlainSelect.getStringList(options, true, true)); + } + return builder; + } + + @Override + public String toString() { + return appendTo(new StringBuilder()).toString(); + } + + @Override + public T accept(StatementVisitor statementVisitor, S context) { + return statementVisitor.visit(this, context); + } +} diff --git a/src/main/java/net/sf/jsqlparser/statement/CreateFunctionalStatement.java b/src/main/java/net/sf/jsqlparser/statement/CreateFunctionalStatement.java index 536a2b68f2..0845742e9b 100644 --- a/src/main/java/net/sf/jsqlparser/statement/CreateFunctionalStatement.java +++ b/src/main/java/net/sf/jsqlparser/statement/CreateFunctionalStatement.java @@ -14,6 +14,9 @@ import java.util.Collections; import java.util.List; import java.util.Optional; +import java.util.function.Consumer; +import net.sf.jsqlparser.statement.create.function.FunctionReturnType; +import net.sf.jsqlparser.statement.create.table.TableElement; /** * A base for the declaration of function like statements @@ -23,6 +26,39 @@ public abstract class CreateFunctionalStatement implements Statement { private String kind; private boolean orReplace = false; + public enum Operation { + CREATE, ALTER, CREATE_OR_ALTER + } + + private Operation operation = Operation.CREATE; + private FunctionReturnType returnType; + private List routineBodyParts; + + public Operation getOperation() { + return operation; + } + + public void setOperation(Operation operation) { + this.operation = operation; + } + + public FunctionReturnType getReturnType() { + return returnType; + } + + public void setReturnType(FunctionReturnType returnType) { + this.returnType = returnType; + } + + public List getRoutineBodyParts() { + return routineBodyParts; + } + + public void setRoutineBodyParts(List parts) { + routineBodyParts = parts; + } + + private List functionDeclarationParts; protected CreateFunctionalStatement(String kind) { @@ -41,7 +77,9 @@ protected CreateFunctionalStatement(boolean orReplace, String kind, } /** - * @return the declaration parts after {@code CREATE FUNCTION|PROCEDURE} + * @return the declaration parts after {@code CREATE FUNCTION|PROCEDURE}. For a SQL Server + * function with a structured {@link #getReturnType()}, these are the name and parameter + * tokens before RETURNS; {@link #getRoutineBodyParts()} holds the remaining tokens. */ public List getFunctionDeclarationParts() { return functionDeclarationParts; @@ -66,22 +104,43 @@ public void setOrReplace(boolean orReplace) { * @return a whitespace appended String with the declaration parts with some minimal formatting. */ public String formatDeclaration() { - StringBuilder declaration = new StringBuilder(); - int currIndex = 0; - while (currIndex < functionDeclarationParts.size()) { - String token = functionDeclarationParts.get(currIndex); - declaration.append(token); - // if the next token is a ; don't put a space - if (currIndex + 1 < functionDeclarationParts.size()) { - // peek ahead just to format nicely - String nextToken = functionDeclarationParts.get(currIndex + 1); - if (!nextToken.equals(";")) { - declaration.append(" "); - } + StringBuilder builder = new StringBuilder(); + return appendDeclarationTo(builder, builder::append).toString(); + } + + private StringBuilder appendDeclarationTo(StringBuilder builder, + Consumer printer) { + appendTokens(builder, functionDeclarationParts); + if (returnType != null) { + builder.append(' '); + returnType.appendTo(builder, printer); + if (routineBodyParts != null && !routineBodyParts.isEmpty()) { + builder.append(' '); + appendTokens(builder, routineBodyParts); } - currIndex++; } - return declaration.toString(); + return builder; + } + + private static void appendTokens(StringBuilder builder, List tokens) { + if (tokens == null) { + return; + } + for (int i = 0; i < tokens.size(); i++) { + if (i > 0 && !";".equals(tokens.get(i))) { + builder.append(' '); + } + builder.append(tokens.get(i)); + } + } + + public StringBuilder appendTo(StringBuilder builder, Consumer printer) { + builder.append(operation.name().replace('_', ' ')).append(' '); + if (orReplace && operation == Operation.CREATE) { + builder.append("OR REPLACE "); + } + builder.append(kind).append(' '); + return appendDeclarationTo(builder, printer); } @Override @@ -91,9 +150,8 @@ public T accept(StatementVisitor statementVisitor, S context) { @Override public String toString() { - return "CREATE " - + (orReplace ? "OR REPLACE " : "") - + kind + " " + formatDeclaration(); + StringBuilder builder = new StringBuilder(); + return appendTo(builder, builder::append).toString(); } public CreateFunctionalStatement withFunctionDeclarationParts( diff --git a/src/main/java/net/sf/jsqlparser/statement/DeallocateStatement.java b/src/main/java/net/sf/jsqlparser/statement/DeallocateStatement.java new file mode 100644 index 0000000000..25d375e38b --- /dev/null +++ b/src/main/java/net/sf/jsqlparser/statement/DeallocateStatement.java @@ -0,0 +1,69 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.statement; + +/** + * {@code DEALLOCATE [PREPARE] name}, which drops a statement prepared by {@link PrepareStatement}. + */ +public class DeallocateStatement implements Statement { + private String name; + private boolean usingPrepareKeyword; + + public DeallocateStatement() {} + + public DeallocateStatement(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public DeallocateStatement withName(String name) { + setName(name); + return this; + } + + public boolean isUsingPrepareKeyword() { + return usingPrepareKeyword; + } + + public void setUsingPrepareKeyword(boolean usingPrepareKeyword) { + this.usingPrepareKeyword = usingPrepareKeyword; + } + + public DeallocateStatement withUsingPrepareKeyword(boolean usingPrepareKeyword) { + setUsingPrepareKeyword(usingPrepareKeyword); + return this; + } + + public StringBuilder appendTo(StringBuilder builder) { + builder.append("DEALLOCATE "); + if (usingPrepareKeyword) { + builder.append("PREPARE "); + } + builder.append(name); + return builder; + } + + @Override + public String toString() { + return appendTo(new StringBuilder()).toString(); + } + + @Override + public T accept(StatementVisitor statementVisitor, S context) { + return statementVisitor.visit(this, context); + } +} diff --git a/src/main/java/net/sf/jsqlparser/statement/DescribeStatement.java b/src/main/java/net/sf/jsqlparser/statement/DescribeStatement.java index 10a92764cc..054b648436 100644 --- a/src/main/java/net/sf/jsqlparser/statement/DescribeStatement.java +++ b/src/main/java/net/sf/jsqlparser/statement/DescribeStatement.java @@ -10,10 +10,12 @@ package net.sf.jsqlparser.statement; import net.sf.jsqlparser.schema.Table; +import net.sf.jsqlparser.statement.select.Select; public class DescribeStatement implements Statement { private Table table; + private Select select; private String describeType; public DescribeStatement() { @@ -32,9 +34,24 @@ public void setTable(Table table) { this.table = table; } + /** DuckDB also describes a query: {@code DESCRIBE SELECT ...}. */ + public Select getSelect() { + return select; + } + + public void setSelect(Select select) { + this.select = select; + } + + public DescribeStatement withSelect(Select select) { + setSelect(select); + return this; + } + @Override public String toString() { - return this.describeType + " " + table.getFullyQualifiedName(); + return this.describeType + " " + + (select != null ? select.toString() : table.getFullyQualifiedName()); } @Override diff --git a/src/main/java/net/sf/jsqlparser/statement/DetachStatement.java b/src/main/java/net/sf/jsqlparser/statement/DetachStatement.java new file mode 100644 index 0000000000..80fe5780a9 --- /dev/null +++ b/src/main/java/net/sf/jsqlparser/statement/DetachStatement.java @@ -0,0 +1,88 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.statement; + +/** + * DuckDB's {@code DETACH [DATABASE] [IF EXISTS] name}, the counterpart of {@link AttachStatement}. + * + * @see DETACH + */ +public class DetachStatement implements Statement { + private String databaseName; + private boolean usingDatabaseKeyword; + private boolean ifExists; + + public DetachStatement() {} + + public DetachStatement(String databaseName) { + this.databaseName = databaseName; + } + + public String getDatabaseName() { + return databaseName; + } + + public void setDatabaseName(String databaseName) { + this.databaseName = databaseName; + } + + public DetachStatement withDatabaseName(String databaseName) { + setDatabaseName(databaseName); + return this; + } + + public boolean isUsingDatabaseKeyword() { + return usingDatabaseKeyword; + } + + public void setUsingDatabaseKeyword(boolean usingDatabaseKeyword) { + this.usingDatabaseKeyword = usingDatabaseKeyword; + } + + public DetachStatement withUsingDatabaseKeyword(boolean usingDatabaseKeyword) { + setUsingDatabaseKeyword(usingDatabaseKeyword); + return this; + } + + public boolean isIfExists() { + return ifExists; + } + + public void setIfExists(boolean ifExists) { + this.ifExists = ifExists; + } + + public DetachStatement withIfExists(boolean ifExists) { + setIfExists(ifExists); + return this; + } + + public StringBuilder appendTo(StringBuilder builder) { + builder.append("DETACH "); + if (usingDatabaseKeyword) { + builder.append("DATABASE "); + } + if (ifExists) { + builder.append("IF EXISTS "); + } + builder.append(databaseName); + return builder; + } + + @Override + public String toString() { + return appendTo(new StringBuilder()).toString(); + } + + @Override + public T accept(StatementVisitor statementVisitor, S context) { + return statementVisitor.visit(this, context); + } +} diff --git a/src/main/java/net/sf/jsqlparser/statement/DisconnectStatement.java b/src/main/java/net/sf/jsqlparser/statement/DisconnectStatement.java new file mode 100644 index 0000000000..fdbe6f4e71 --- /dev/null +++ b/src/main/java/net/sf/jsqlparser/statement/DisconnectStatement.java @@ -0,0 +1,54 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.statement; + +/** + * DuckDB's {@code DISCONNECT [name]}, the counterpart of {@link ConnectStatement}. + */ +public class DisconnectStatement implements Statement { + private String name; + + public DisconnectStatement() {} + + public DisconnectStatement(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public DisconnectStatement withName(String name) { + setName(name); + return this; + } + + public StringBuilder appendTo(StringBuilder builder) { + builder.append("DISCONNECT"); + if (name != null) { + builder.append(" ").append(name); + } + return builder; + } + + @Override + public String toString() { + return appendTo(new StringBuilder()).toString(); + } + + @Override + public T accept(StatementVisitor statementVisitor, S context) { + return statementVisitor.visit(this, context); + } +} diff --git a/src/main/java/net/sf/jsqlparser/statement/DoStatement.java b/src/main/java/net/sf/jsqlparser/statement/DoStatement.java new file mode 100644 index 0000000000..c595ea1a32 --- /dev/null +++ b/src/main/java/net/sf/jsqlparser/statement/DoStatement.java @@ -0,0 +1,78 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.statement; + +import java.util.function.Consumer; +import net.sf.jsqlparser.expression.StringValue; + +/** A PostgreSQL anonymous routine. The language-specific body remains a string literal. */ +public class DoStatement implements Statement { + private StringValue code; + private String language; + private boolean languageBeforeCode; + + public StringValue getCode() { + return code; + } + + public void setCode(StringValue code) { + this.code = code; + } + + public DoStatement withCode(StringValue code) { + setCode(code); + return this; + } + + /** Returns the explicit language, or null when omitted. */ + public String getLanguage() { + return language; + } + + public void setLanguage(String language) { + this.language = language; + } + + public DoStatement withLanguage(String language) { + setLanguage(language); + return this; + } + + public boolean isLanguageBeforeCode() { + return languageBeforeCode; + } + + public void setLanguageBeforeCode(boolean languageBeforeCode) { + this.languageBeforeCode = languageBeforeCode; + } + + public StringBuilder appendTo(StringBuilder builder, Consumer codePrinter) { + builder.append("DO "); + if (languageBeforeCode && language != null) { + builder.append("LANGUAGE ").append(language).append(' '); + } + codePrinter.accept(code); + if (!languageBeforeCode && language != null) { + builder.append(" LANGUAGE ").append(language); + } + return builder; + } + + @Override + public T accept(StatementVisitor visitor, S context) { + return visitor.visit(this, context); + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + return appendTo(builder, value -> builder.append(value)).toString(); + } +} diff --git a/src/main/java/net/sf/jsqlparser/statement/ExplainStatement.java b/src/main/java/net/sf/jsqlparser/statement/ExplainStatement.java index fbc5e85037..5442ce4c5e 100644 --- a/src/main/java/net/sf/jsqlparser/statement/ExplainStatement.java +++ b/src/main/java/net/sf/jsqlparser/statement/ExplainStatement.java @@ -10,10 +10,10 @@ package net.sf.jsqlparser.statement; import java.io.Serializable; +import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; import java.util.Locale; -import java.util.stream.Collectors; import net.sf.jsqlparser.schema.Table; /** @@ -22,7 +22,8 @@ public class ExplainStatement implements Statement { private String keyword; private Statement statement; - private LinkedHashMap options; + private List

tableRenderer) { + builder.append("SET IDENTITY_INSERT "); + tableRenderer.accept(table); + return builder.append(on ? " ON" : " OFF"); + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + return appendTo(builder, builder::append).toString(); + } + + @Override + public T accept(StatementVisitor statementVisitor, S context) { + return statementVisitor.visit(this, context); + } +} diff --git a/src/main/java/net/sf/jsqlparser/statement/SetStatement.java b/src/main/java/net/sf/jsqlparser/statement/SetStatement.java index 16878f7739..6edca1a1e7 100644 --- a/src/main/java/net/sf/jsqlparser/statement/SetStatement.java +++ b/src/main/java/net/sf/jsqlparser/statement/SetStatement.java @@ -11,18 +11,84 @@ import net.sf.jsqlparser.expression.Expression; import net.sf.jsqlparser.expression.operators.relational.ExpressionList; -import net.sf.jsqlparser.statement.select.PlainSelect; import java.io.Serializable; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.List; +import java.util.Objects; +import java.util.function.Consumer; public final class SetStatement implements Statement { private final List values = new ArrayList<>(); private String effectParameter; + private OnOffOptions onOffOptions; + + /** SQL Server options that share the SET option [, option] ON | OFF syntax. */ + public enum OnOffOption { + QUOTED_IDENTIFIER, CONCAT_NULL_YIELDS_NULL, CURSOR_CLOSE_ON_COMMIT, ARITHABORT, ARITHIGNORE, FMTONLY, NOCOUNT, NOEXEC, NUMERIC_ROUNDABORT, PARSEONLY, ANSI_DEFAULTS, ANSI_NULL_DFLT_OFF, ANSI_NULL_DFLT_ON, ANSI_NULLS, ANSI_PADDING, ANSI_WARNINGS, FORCEPLAN, SHOWPLAN_ALL, SHOWPLAN_TEXT, SHOWPLAN_XML, IMPLICIT_TRANSACTIONS, REMOTE_PROC_TRANSACTIONS, XACT_ABORT; + + public static OnOffOption fromName(String name) { + for (OnOffOption option : values()) { + if (option.name().equalsIgnoreCase(name)) { + return option; + } + } + return null; + } + } + + /** A group of options sharing one ON/OFF value; separate from assignment expressions. */ + public static final class OnOffOptions implements Serializable { + private final List options; + private boolean on; + + public OnOffOptions(Collection options, boolean on) { + this.options = new ArrayList<>(options); + if (this.options.isEmpty() || this.options.contains(null)) { + throw new IllegalArgumentException("At least one non-null SET option is required"); + } + this.on = on; + } + + public List getOptions() { + return options; + } + + public boolean isOn() { + return on; + } + + public void setOn(boolean on) { + this.on = on; + } + + private StringBuilder appendTo(StringBuilder builder) { + if (options.isEmpty() || options.contains(null)) { + throw new IllegalStateException("Invalid SQL Server SET option group"); + } + for (int i = 0; i < options.size(); i++) { + if (i > 0) { + builder.append(", "); + } + builder.append(options.get(i)); + } + return builder.append(on ? " ON" : " OFF"); + } + } + + public OnOffOptions getOnOffOptions() { + return onOffOptions; + } + + /** Selects SQL Server option syntax, clearing any existing assignments and scope. */ + public void setOnOffOptions(OnOffOptions onOffOptions) { + Objects.requireNonNull(onOffOptions, "onOffOptions"); + clear(); + this.onOffOptions = onOffOptions; + } public SetStatement() { // empty constructor @@ -33,6 +99,7 @@ public SetStatement(Object name, ExpressionList value) { } public void add(Object name, ExpressionList value, boolean useEqual) { + onOffOptions = null; values.add(new NameExpr(name, value, useEqual)); } @@ -103,28 +170,45 @@ public void setExpressions(int idx, ExpressionList expressions) { values.get(idx).expressions = expressions; } - private String toString(NameExpr ne) { - return ne.name + (ne.useEqual ? " = " : " ") - + PlainSelect.getStringList(ne.expressions, true, false); + /** Shares statement punctuation with deparsers while allowing expression visitors. */ + public StringBuilder appendTo(StringBuilder builder, Consumer expressionRenderer) { + builder.append("SET "); + if (onOffOptions != null) { + if (!values.isEmpty() || effectParameter != null) { + throw new IllegalStateException( + "SET options cannot be combined with assignments or scope"); + } + return onOffOptions.appendTo(builder); + } + if (effectParameter != null) { + builder.append(effectParameter).append(" "); + } + for (int i = 0; i < values.size(); i++) { + if (i > 0) { + builder.append(", "); + } + appendAssignment(builder, values.get(i), expressionRenderer); + } + return builder; } - @Override - public String toString() { - StringBuilder b = new StringBuilder("SET "); - if (effectParameter != null) { - b.append(effectParameter).append(" "); - } - boolean addComma = false; - for (NameExpr ne : values) { - if (addComma) { - b.append(", "); - } else { - addComma = true; + private static void appendAssignment(StringBuilder builder, NameExpr value, + Consumer expressionRenderer) { + builder.append(value.name).append(value.useEqual ? " = " : " "); + if (value.expressions != null) { + for (int i = 0; i < value.expressions.size(); i++) { + if (i > 0) { + builder.append(", "); + } + expressionRenderer.accept((Expression) value.expressions.get(i)); } - b.append(toString(ne)); } + } - return b.toString(); + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + return appendTo(builder, builder::append).toString(); } public List getKeyValuePairs() { @@ -132,6 +216,7 @@ public List getKeyValuePairs() { } public void addKeyValuePairs(Collection keyValuePairs) { + onOffOptions = null; values.addAll(keyValuePairs); } @@ -140,6 +225,7 @@ public void addKeyValuePairs(NameExpr... keyValuePairs) { } public void clear() { + onOffOptions = null; values.clear(); effectParameter = null; } diff --git a/src/main/java/net/sf/jsqlparser/statement/Statement.java b/src/main/java/net/sf/jsqlparser/statement/Statement.java index 380092ab68..57e6e67def 100644 --- a/src/main/java/net/sf/jsqlparser/statement/Statement.java +++ b/src/main/java/net/sf/jsqlparser/statement/Statement.java @@ -11,10 +11,26 @@ import net.sf.jsqlparser.Model; +import java.util.function.Predicate; + public interface Statement extends Model { T accept(StatementVisitor statementVisitor, S context); default void accept(StatementVisitor statementVisitor) { accept(statementVisitor, null); } + + /** + * Static feature analysis of this statement tree. Not cached: the AST is mutable and publicly + * constructible, and a stale bitmask is worse than a recomputation (micro seconds against a + * millisecond-scale parse). + */ + default StatementFeatures getFeatures() { + return StatementFeatureVisitor.analyse(this); + } + + /** Same, with a caller-supplied allow-list of provably side-effect-free functions. */ + default StatementFeatures getFeatures(Predicate pureFunctions) { + return StatementFeatureVisitor.analyse(this, pureFunctions); + } } diff --git a/src/main/java/net/sf/jsqlparser/statement/StatementFeatureVisitor.java b/src/main/java/net/sf/jsqlparser/statement/StatementFeatureVisitor.java new file mode 100644 index 0000000000..e057d2db60 --- /dev/null +++ b/src/main/java/net/sf/jsqlparser/statement/StatementFeatureVisitor.java @@ -0,0 +1,1145 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.statement; + +import net.sf.jsqlparser.statement.oracle.OracleBlock; +import net.sf.jsqlparser.statement.oracle.OracleAssignment; +import net.sf.jsqlparser.statement.oracle.OracleNullStatement; + +import net.sf.jsqlparser.statement.role.CreateRole; +import net.sf.jsqlparser.statement.role.AlterRole; +import net.sf.jsqlparser.statement.grant.Revoke; +import net.sf.jsqlparser.statement.grant.AlterDefaultPrivileges; +import net.sf.jsqlparser.statement.create.trigger.CreateTrigger; +import net.sf.jsqlparser.statement.create.type.CreateType; +import net.sf.jsqlparser.statement.alter.AlterType; +import net.sf.jsqlparser.statement.create.domain.CreateDomain; +import net.sf.jsqlparser.statement.alter.AlterDomain; +import net.sf.jsqlparser.statement.create.extension.CreateExtension; +import net.sf.jsqlparser.statement.alter.AlterExtension; +import net.sf.jsqlparser.statement.create.publication.CreatePublication; +import net.sf.jsqlparser.statement.alter.AlterPublication; +import net.sf.jsqlparser.statement.create.subscription.CreateSubscription; +import net.sf.jsqlparser.statement.create.subscription.SubscriptionOption; +import net.sf.jsqlparser.statement.alter.AlterSubscription; + +import net.sf.jsqlparser.JSQLParserException; +import net.sf.jsqlparser.expression.AnalyticExpression; +import net.sf.jsqlparser.expression.TranscodingFunction; +import net.sf.jsqlparser.expression.Expression; +import net.sf.jsqlparser.expression.ExpressionVisitor; +import net.sf.jsqlparser.expression.ExpressionVisitorAdapter; +import net.sf.jsqlparser.expression.Function; +import net.sf.jsqlparser.parser.CCJSqlParserUtil; +import net.sf.jsqlparser.statement.alter.Alter; +import net.sf.jsqlparser.statement.alter.AlterSession; +import net.sf.jsqlparser.statement.alter.AlterSystemStatement; +import net.sf.jsqlparser.statement.alter.RenameTableStatement; +import net.sf.jsqlparser.statement.alter.sequence.AlterSequence; +import net.sf.jsqlparser.statement.analyze.Analyze; +import net.sf.jsqlparser.statement.comment.Comment; +import net.sf.jsqlparser.statement.create.database.CreateDatabase; +import net.sf.jsqlparser.statement.create.index.CreateIndex; +import net.sf.jsqlparser.statement.create.policy.CreatePolicy; +import net.sf.jsqlparser.statement.create.schema.CreateSchema; +import net.sf.jsqlparser.statement.create.sequence.CreateSequence; +import net.sf.jsqlparser.statement.create.synonym.CreateSynonym; +import net.sf.jsqlparser.statement.create.table.CreateTable; +import net.sf.jsqlparser.statement.create.view.AlterView; +import net.sf.jsqlparser.statement.create.view.CreateView; +import net.sf.jsqlparser.statement.delete.Delete; +import net.sf.jsqlparser.statement.drop.Drop; +import net.sf.jsqlparser.statement.execute.Execute; +import net.sf.jsqlparser.statement.export.Export; +import net.sf.jsqlparser.statement.grant.Grant; +import net.sf.jsqlparser.statement.imprt.Import; +import net.sf.jsqlparser.statement.insert.Insert; +import net.sf.jsqlparser.statement.insert.InsertBulk; +import net.sf.jsqlparser.statement.lock.LockStatement; +import net.sf.jsqlparser.statement.merge.Merge; +import net.sf.jsqlparser.statement.refresh.RefreshMaterializedViewStatement; +import net.sf.jsqlparser.statement.select.FromItemVisitor; +import net.sf.jsqlparser.statement.select.FromItemVisitorAdapter; +import net.sf.jsqlparser.statement.select.PivotVisitor; +import net.sf.jsqlparser.statement.select.PivotVisitorAdapter; +import net.sf.jsqlparser.statement.select.PlainSelect; +import net.sf.jsqlparser.statement.select.MySqlSelectIntoClause; +import net.sf.jsqlparser.statement.select.Select; +import net.sf.jsqlparser.statement.select.SelectItem; +import net.sf.jsqlparser.statement.select.SelectItemVisitor; +import net.sf.jsqlparser.statement.select.SelectItemVisitorAdapter; +import net.sf.jsqlparser.statement.select.SelectVisitor; +import net.sf.jsqlparser.statement.select.SelectVisitorAdapter; +import net.sf.jsqlparser.statement.select.TableFunction; +import net.sf.jsqlparser.statement.select.TableStatement; +import net.sf.jsqlparser.statement.select.WithItem; +import net.sf.jsqlparser.statement.show.ShowIndexStatement; +import net.sf.jsqlparser.statement.show.ShowTablesStatement; +import net.sf.jsqlparser.statement.truncate.Truncate; +import net.sf.jsqlparser.statement.update.Update; +import net.sf.jsqlparser.statement.upsert.Upsert; + +import java.util.ArrayList; +import java.util.EnumSet; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Locale; +import java.util.Set; +import java.util.function.Predicate; +import net.sf.jsqlparser.statement.create.macro.CreateMacro; +import net.sf.jsqlparser.statement.create.extension.CreateExtensionRepository; +import net.sf.jsqlparser.statement.export.ExportDataStatement; +import net.sf.jsqlparser.statement.load.LoadDataStatement; + +/** + * Derives a {@link StatementFeatures} verdict from a statement tree. + * + *

+ * Built on the existing adapter chain rather than on a hand-written traversal: only the nodes that + * contribute a feature are overridden, everything else is inherited from + * {@link StatementVisitorAdapter} / {@link SelectVisitorAdapter} / {@link ExpressionVisitorAdapter} + * / {@link FromItemVisitorAdapter}. + * + *

Why a top-level flag and not a flag union

+ * + * {@code RETURNS_RESULT_SET} is a property of the statement's own result position, not of whatever + * happens to appear somewhere in the tree: + * + *
+ *   SELECT * FROM t                                        RETURNS_RESULT_SET, READS_DATA
+ *   INSERT INTO x SELECT * FROM t                          READS_DATA, MODIFIES_DATA  (no result set)
+ *   DELETE FROM t RETURNING *                              RETURNS_RESULT_SET, MODIFIES_DATA
+ *   WITH c AS (DELETE FROM t RETURNING *) SELECT * FROM c  RETURNS_RESULT_SET, MODIFIES_DATA
+ *   INSERT INTO x WITH c AS (DELETE .. RETURNING *) SELECT MODIFIES_DATA            (no result set)
+ * 
+ * + * The first statement visit claims the top level; every nested statement - reached through a + * data-modifying CTE, a subquery or an IF block - finds it already claimed and can therefore + * contribute {@code MODIFIES_DATA} but never {@code RETURNS_RESULT_SET}. + * + *

Two upstream defects this class works around

+ * + *
    + *
  1. {@code SelectVisitorAdapter#visit(WithItem)} calls {@code withItem.getSelect()}, an unchecked + * cast to {@code ParenthesedSelect}. A data-modifying CTE reached through the select path + * - i.e. from a subquery, via {@code ExpressionVisitorAdapter#visit(Select)} - throws + * ClassCastException. {@link FeatureSelectVisitor} routes through {@code getParenthesedStatement()} + * instead. Worth fixing in the adapter itself.
  2. + *
  3. {@code StatementVisitorAdapter#visit(Delete)} never calls {@code visitReturningClause}, + * unlike its Insert and Update counterparts, so expressions in {@code DELETE .. RETURNING f(x)} are + * not traversed. Walked explicitly here.
  4. + *
+ */ +@SuppressWarnings({"PMD.CyclomaticComplexity", "PMD.TooManyMethods", "PMD.ExcessiveImports"}) +public class StatementFeatureVisitor extends StatementVisitorAdapter { + + private final Analysis analysis; + + // ---- construction ----------------------------------------------------------------------- + + private StatementFeatureVisitor(Analysis analysis) { + super(wire(analysis)); + this.analysis = analysis; + analysis.statements = this; + } + + /** + * Builds the mutually recursive visitor chain. The cycle (expression -> select -> from + * item -> expression) is closed after construction through the adapters' setters, because + * neither adapter can be handed a reference to something not yet constructed. + */ + private static SelectVisitorAdapter wire(Analysis analysis) { + FeatureExpressionVisitor expressions = new FeatureExpressionVisitor(analysis); + FeatureFromItemVisitor fromItems = new FeatureFromItemVisitor(analysis); + + FeatureSelectVisitor selects = new FeatureSelectVisitor(analysis, expressions, + new PivotVisitorAdapter<>(expressions), + new SelectItemVisitorAdapter<>(expressions), fromItems); + + expressions.setSelectVisitor(selects); + fromItems.setSelectVisitor((SelectVisitor) selects).setExpressionVisitor(expressions); + + analysis.expressions = expressions; + analysis.selects = selects; + return selects; + } + + public static StatementFeatures analyse(Statement statement) { + return analyse(statement, name -> false); + } + + /** + * @param pureFunctions names (lower case) the caller can prove side-effect free, e.g. from + * {@code pg_proc.provolatile} or a static allow-list. Anything not proven pure leaves + * {@code MODIFIES_DATA} in the possible set. + */ + public static StatementFeatures analyse(Statement statement, Predicate pureFunctions) { + Analysis analysis = new Analysis(pureFunctions); + StatementFeatureVisitor visitor = new StatementFeatureVisitor(analysis); + statement.accept(visitor, null); + analysis.failLoudIfSilent(statement.getClass().getSimpleName()); + return new StatementFeatures(analysis.certain, analysis.uncertain, analysis.unresolved); + } + + public static StatementFeatures analyse(String sql) throws JSQLParserException { + return analyse(CCJSqlParserUtil.parse(sql)); + } + + /** + * Aggregate verdict over a whole script. + * + *

+ * {@link Statements} is an {@code ArrayList}, not a {@link Statement}, so it cannot + * go through {@link #analyse(Statement)} and has no {@code getFeatures()} default method - + * hence this overload. + * + *

+ * Read the result as a union, and only for guard purposes. Each statement in the script + * claims its own result position, so {@code UPDATE ..; SELECT ..;} yields both + * {@code MODIFIES_DATA} and {@code RETURNS_RESULT_SET} - which is the right answer for "may + * this script write?" and a meaningless one for "executeQuery or executeUpdate?", since the + * union says nothing about which statement returns rows. Dispatchers want + * {@link #analyseEach(Statements)}. + */ + public static StatementFeatures analyse(Statements statements) { + return analyse(statements, name -> false); + } + + public static StatementFeatures analyse(Statements statements, + Predicate pureFunctions) { + Analysis analysis = new Analysis(pureFunctions); + StatementFeatureVisitor visitor = new StatementFeatureVisitor(analysis); + statements.accept(visitor, null); + analysis.failLoudIfSilent(Statements.class.getSimpleName()); + return new StatementFeatures(analysis.certain, analysis.uncertain, analysis.unresolved); + } + + /** + * One verdict per statement, in script order. This is what a dispatcher needs: it answers "does + * statement n return rows?" instead of "does anything in here return rows?". Each + * statement is analysed independently, so no feature leaks between them. + */ + public static List analyseEach(Statements statements) { + return analyseEach(statements, name -> false); + } + + public static List analyseEach(Statements statements, + Predicate pureFunctions) { + List perStatement = new ArrayList<>(statements.size()); + for (Statement statement : statements) { + perStatement.add(analyse(statement, pureFunctions)); + } + return perStatement; + } + + // ---- shared state ----------------------------------------------------------------------- + + /** + * The {@code S context} argument cannot carry this state: + * {@code StatementVisitorAdapter#visit(Insert)} passes a hardcoded {@code null} context down to + * the select visitor, so anything threaded through the context is lost at exactly the node that + * matters most. + */ + static final class Analysis { + final EnumSet certain = EnumSet.noneOf(StmtFeature.class); + final EnumSet uncertain = EnumSet.noneOf(StmtFeature.class); + final Set unresolved = new LinkedHashSet<>(); + final Predicate pureFunctions; + + StatementVisitor statements; + ExpressionVisitor expressions; + SelectVisitor selects; + + private boolean topLevelClaimed; + private int readsSuppressed; + + Analysis(Predicate pureFunctions) { + this.pureFunctions = pureFunctions; + } + + /** True exactly once per top-level statement; false for everything nested. */ + boolean claimTopLevel() { + if (topLevelClaimed) { + return false; + } + topLevelClaimed = true; + return true; + } + + void releaseTopLevel() { + topLevelClaimed = false; + } + + void certain(StmtFeature... features) { + for (StmtFeature feature : features) { + if (feature == StmtFeature.READS_DATA && readsSuppressed > 0) { + continue; + } + certain.add(feature); + } + } + + void possible(StmtFeature... features) { + for (StmtFeature feature : features) { + uncertain.add(feature); + } + } + + void unresolved(String reference) { + unresolved.add(reference.toLowerCase(Locale.ROOT)); + } + + /** A view definition references tables without reading them. */ + void suppressReads(Runnable body) { + readsSuppressed++; + try { + body.run(); + } finally { + readsSuppressed--; + } + } + + void opaque(String reference) { + certain.add(StmtFeature.OPAQUE); + uncertain.addAll(EnumSet.of(StmtFeature.READS_DATA, StmtFeature.RETURNS_RESULT_SET, + StmtFeature.MODIFIES_DATA, StmtFeature.MODIFIES_SCHEMA)); + unresolved(reference); + } + + /** + * A statement that contributed nothing was not classified - almost certainly a statement + * type added to the grammar after this visitor was written, whose inherited adapter method + * silently returns null. Degrade loudly, never to "harmless". This is what replaces a + * hand-maintained type table; the price is that a genuinely inert statement is also + * reported opaque, which is the right direction to be wrong in. + */ + void failLoudIfSilent(String label) { + if (certain.isEmpty()) { + opaque(label); + } + } + } + + // ---- result-producing clauses ----------------------------------------------------------- + + /** + * RETURNING always ships rows to the client. So does OUTPUT - unless the rows are redirected, + * which the grammar allows in two ways: + * + *

+     *   OUTPUT DELETED.*                    -> rows go to the client
+     *   OUTPUT DELETED.id INTO audit        -> rows go to a table       (getOutputTable())
+     *   OUTPUT DELETED.id INTO @MyTableVar  -> rows go to a table variable (getTableVariable())
+     * 
+ * + * Both redirections have to be checked. {@code OutputClause()} in the grammar is + * {@code ( UserVariable() | Table() )}, so the table-variable form leaves + * {@code getOutputTable()} null - testing only that one would report + * {@code OUTPUT .. INTO @MyTableVar} as returning a result set, which it does not. + */ + private void resultClauses(boolean topLevel, ReturningClause returning, OutputClause output) { + if (!topLevel) { + return; + } + if (returning != null && !returning.isEmpty()) { + analysis.certain(StmtFeature.RETURNS_RESULT_SET); + } + if (output != null && output.getSelectItemList() != null + && output.getOutputTable() == null && output.getTableVariable() == null) { + analysis.certain(StmtFeature.RETURNS_RESULT_SET); + } + if (output != null && (output.getOutputTable() != null + || output.getTableVariable() != null)) { + // OUTPUT .. INTO
writes rows somewhere else + analysis.certain(StmtFeature.MODIFIES_DATA); + } + } + + /** Works around defect (2): the adapter does not walk DELETE's RETURNING expressions. */ + private void walkReturning(ReturningClause returning) { + if (returning == null) { + return; + } + for (SelectItem item : returning) { + Expression expression = item.getExpression(); + if (expression != null) { + expression.accept(analysis.expressions, null); + } + } + } + + // ---- DML -------------------------------------------------------------------------------- + + @Override + public Void visit(Select select, S context) { + if (analysis.claimTopLevel()) { + analysis.certain(StmtFeature.RETURNS_RESULT_SET); + } + // READS_DATA is decided in FeatureSelectVisitor - "SELECT 1" reads nothing. + return super.visit(select, context); + } + + @Override + public Void visit(Insert insert, S context) { + boolean topLevel = analysis.claimTopLevel(); + analysis.certain(StmtFeature.MODIFIES_DATA); + resultClauses(topLevel, insert.getReturningClause(), insert.getOutputClause()); + if (insert.getSelect() != null) { + analysis.certain(StmtFeature.READS_DATA); + } + return super.visit(insert, context); + } + + @Override + public Void visit(InsertBulk statement, S context) { + analysis.claimTopLevel(); + analysis.certain(StmtFeature.MODIFIES_DATA); + return super.visit(statement, context); + } + + @Override + public Void visit(Update update, S context) { + boolean topLevel = analysis.claimTopLevel(); + analysis.certain(StmtFeature.MODIFIES_DATA); + resultClauses(topLevel, update.getReturningClause(), update.getOutputClause()); + return super.visit(update, context); + } + + @Override + public Void visit(Delete delete, S context) { + boolean topLevel = analysis.claimTopLevel(); + analysis.certain(StmtFeature.MODIFIES_DATA); + resultClauses(topLevel, delete.getReturningClause(), delete.getOutputClause()); + super.visit(delete, context); + walkReturning(delete.getReturningClause()); + return null; + } + + @Override + public Void visit(Merge merge, S context) { + boolean topLevel = analysis.claimTopLevel(); + analysis.certain(StmtFeature.MODIFIES_DATA, StmtFeature.READS_DATA); + resultClauses(topLevel, null, merge.getOutputClause()); + return super.visit(merge, context); + } + + @Override + public Void visit(Upsert upsert, S context) { + analysis.claimTopLevel(); + analysis.certain(StmtFeature.MODIFIES_DATA); + if (upsert.getSelect() != null) { + analysis.certain(StmtFeature.READS_DATA); + upsert.getSelect().accept(analysis.selects, context); + } + return null; + } + + @Override + public Void visit(Import imprt, S context) { + analysis.claimTopLevel(); + analysis.certain(StmtFeature.MODIFIES_DATA); + return null; + } + + @Override + public Void visit(Export export, S context) { + analysis.claimTopLevel(); + analysis.certain(StmtFeature.READS_DATA); + if (export.getSelect() != null) { + export.getSelect().accept(analysis.selects, context); + } + return null; + } + + // ---- DDL -------------------------------------------------------------------------------- + + @Override + public Void visit(CreateTable createTable, S context) { + analysis.claimTopLevel(); + analysis.certain(StmtFeature.MODIFIES_SCHEMA); + if (createTable.getSelect() != null) { + // CTAS reads; it does not return a result set - the top level is already claimed + analysis.certain(StmtFeature.READS_DATA); + } + return super.visit(createTable, context); + } + + @Override + public Void visit(CreateView createView, S context) { + analysis.claimTopLevel(); + analysis.certain(StmtFeature.MODIFIES_SCHEMA); + if (createView.isMaterialized() && !Boolean.FALSE.equals(createView.getWithData())) { + if (createView.getSelect() != null) { + createView.getSelect().accept(analysis.selects, context); + } + } else { + analysis.suppressReads(() -> { + if (createView.getSelect() != null) { + createView.getSelect().accept(analysis.selects, null); + } + }); + } + return null; + } + + @Override + public Void visit(AlterView alterView, S context) { + analysis.claimTopLevel(); + analysis.certain(StmtFeature.MODIFIES_SCHEMA); + analysis.suppressReads(() -> { + if (alterView.getSelect() != null) { + alterView.getSelect().accept(analysis.selects, null); + } + }); + return null; + } + + @Override + public Void visit(RefreshMaterializedViewStatement materializedView, S context) { + analysis.claimTopLevel(); + analysis.certain(StmtFeature.MODIFIES_SCHEMA, StmtFeature.MODIFIES_DATA, + StmtFeature.READS_DATA); + return null; + } + + @Override + public Void visit(Truncate truncate, S context) { + analysis.claimTopLevel(); + // DDL in most engines (implicit commit, no row triggers) but it destroys rows: + // a read-only guard must see MODIFIES_DATA here + analysis.certain(StmtFeature.MODIFIES_SCHEMA, StmtFeature.MODIFIES_DATA); + return null; + } + + @Override + public Void visit(Drop drop, S context) { + analysis.claimTopLevel(); + analysis.certain(StmtFeature.MODIFIES_SCHEMA, StmtFeature.MODIFIES_DATA); + return null; + } + + @Override + public Void visit(PragmaStatement pragmaStatement, S context) { + analysis.claimTopLevel(); + analysis.certain(StmtFeature.MODIFIES_SESSION); + return null; + } + + @Override + public Void visit(AssertStatement assertStatement, S context) { + analysis.claimTopLevel(); + analysis.certain(StmtFeature.READS_DATA); + return null; + } + + @Override + public Void visit(ExtensionStatement extensionStatement, S context) { + analysis.claimTopLevel(); + analysis.certain(StmtFeature.MODIFIES_SESSION); + return null; + } + + @Override + public Void visit(AttachStatement attachStatement, S context) { + analysis.claimTopLevel(); + analysis.certain(StmtFeature.MODIFIES_SESSION); + return null; + } + + @Override + public Void visit(DetachStatement detachStatement, S context) { + analysis.claimTopLevel(); + analysis.certain(StmtFeature.MODIFIES_SESSION); + return null; + } + + @Override + public Void visit(ConnectStatement connectStatement, S context) { + analysis.claimTopLevel(); + analysis.certain(StmtFeature.MODIFIES_SESSION); + return null; + } + + @Override + public Void visit(DisconnectStatement disconnectStatement, S context) { + analysis.claimTopLevel(); + analysis.certain(StmtFeature.MODIFIES_SESSION); + return null; + } + + @Override + public Void visit(PrepareStatement prepareStatement, S context) { + analysis.claimTopLevel(); + analysis.certain(StmtFeature.MODIFIES_SESSION); + return null; + } + + @Override + public Void visit(DeallocateStatement deallocateStatement, S context) { + analysis.claimTopLevel(); + analysis.certain(StmtFeature.MODIFIES_SESSION); + return null; + } + + @Override + public Void visit(CopyStatement copyStatement, S context) { + analysis.claimTopLevel(); + analysis.certain(StmtFeature.READS_DATA, StmtFeature.MODIFIES_DATA); + return null; + } + + @Override + public Void visit(ExportDataStatement exportDataStatement, S context) { + analysis.claimTopLevel(); + analysis.certain(StmtFeature.READS_DATA, StmtFeature.MODIFIES_DATA); + return null; + } + + @Override + public Void visit(CreateMacro createMacro, S context) { + analysis.claimTopLevel(); + analysis.certain(StmtFeature.MODIFIES_SCHEMA); + return null; + } + + @Override + public Void visit(CreateExtensionRepository createExtensionRepository, S context) { + analysis.claimTopLevel(); + analysis.certain(StmtFeature.MODIFIES_SCHEMA); + return null; + } + + @Override + public Void visit(LoadDataStatement loadDataStatement, S context) { + analysis.claimTopLevel(); + analysis.certain(StmtFeature.MODIFIES_DATA); + return null; + } + + @Override + public Void visit(PurgeStatement purgeStatement, S context) { + analysis.claimTopLevel(); + analysis.certain(StmtFeature.MODIFIES_SCHEMA, StmtFeature.MODIFIES_DATA); + return null; + } + + @Override + public Void visit(Alter alter, S context) { + return schemaOnly(); + } + + @Override + public Void visit(CreateIndex createIndex, S context) { + return schemaOnly(); + } + + @Override + public Void visit(CreateSchema createSchema, S context) { + return schemaOnly(); + } + + @Override + public Void visit(CreateDatabase createDatabase, S context) { + return schemaOnly(); + } + + @Override + public Void visit(CreateSequence createSequence, S context) { + return schemaOnly(); + } + + @Override + public Void visit(AlterSequence alterSequence, S context) { + return schemaOnly(); + } + + @Override + public Void visit(CreateSynonym createSynonym, S context) { + return schemaOnly(); + } + + @Override + public Void visit(CreatePolicy createPolicy, S context) { + return schemaOnly(); + } + + @Override + public Void visit(RenameTableStatement renameTableStatement, S context) { + return schemaOnly(); + } + + @Override + public Void visit(Comment comment, S context) { + return schemaOnly(); + } + + @Override + public Void visit(Grant grant, S context) { + return schemaOnly(); + } + + @Override + public Void visit(Analyze analyze, S context) { + analysis.claimTopLevel(); + analysis.certain(StmtFeature.MODIFIES_SCHEMA, StmtFeature.READS_DATA); // rewrites + // statistics + return null; + } + + private Void schemaOnly() { + analysis.claimTopLevel(); + analysis.certain(StmtFeature.MODIFIES_SCHEMA); + return null; + } + + // ---- opaque ----------------------------------------------------------------------------- + + @Override + public Void visit(Execute execute, S context) { + analysis.claimTopLevel(); + analysis.opaque(String.valueOf(execute.getName())); + return null; + } + + @Override + public Void visit(DoStatement statement, S context) { + analysis.claimTopLevel(); + analysis.opaque("DO"); + return null; + } + + @Override + public Void visit(CreateFunctionalStatement createFunctionalStatement, S context) { + analysis.claimTopLevel(); + analysis.certain(StmtFeature.MODIFIES_SCHEMA); + // the body is an opaque token list - nothing about it is knowable here + analysis.possible(StmtFeature.MODIFIES_DATA, StmtFeature.READS_DATA); + analysis.unresolved(createFunctionalStatement.getClass().getSimpleName()); + return null; + } + + @Override + public Void visit(UnsupportedStatement unsupportedStatement, S context) { + analysis.claimTopLevel(); + analysis.opaque("unsupported"); + return null; + } + + @Override + public Void visit(ExplainStatement explainStatement, S context) { + if (analysis.claimTopLevel()) { + analysis.certain(StmtFeature.RETURNS_RESULT_SET); + } + // EXPLAIN ANALYZE executes the statement. The option set is dialect-specific, so the + // side effects of the explained statement stay in the possible set. + analysis.possible(StmtFeature.MODIFIES_DATA, StmtFeature.READS_DATA); + analysis.unresolved("explain"); + if (explainStatement.getStatement() != null) { + explainStatement.getStatement().accept(this, context); + } + return null; + } + + // ---- control flow ----------------------------------------------------------------------- + + @Override + public Void visit(Statements statements, S context) { + for (Statement statement : statements) { + analysis.releaseTopLevel(); // each statement in a script has its own result position + statement.accept(this, context); + } + analysis.releaseTopLevel(); + return null; + } + + @Override + public Void visit(Block block, S context) { + analysis.claimTopLevel(); + // Statements is itself a List; getStatements() is deprecated + Statements inner = block.getStatements(); + if (inner != null) { + for (Statement statement : inner) { + statement.accept(this, context); + } + } + return null; + } + + @Override + public Void visit(IfElseStatement ifElseStatement, S context) { + analysis.claimTopLevel(); + // both branches contribute: this is what the statement *may* do, not what it will do + return super.visit(ifElseStatement, context); + } + + // ---- session / transaction -------------------------------------------------------------- + + @Override + public Void visit(Commit commit, S context) { + return transactionOnly(); + } + + @Override + public Void visit(RollbackStatement rollbackStatement, S context) { + return transactionOnly(); + } + + @Override + public Void visit(SavepointStatement savepointStatement, S context) { + return transactionOnly(); + } + + @Override + public Void visit(LockStatement lock, S context) { + return transactionOnly(); + } + + private Void transactionOnly() { + analysis.claimTopLevel(); + analysis.certain(StmtFeature.MODIFIES_TRANSACTION); + return null; + } + + @Override + public Void visit(SetStatement set, S context) { + return sessionOnly(); + } + + @Override + public Void visit(ResetStatement reset, S context) { + return sessionOnly(); + } + + @Override + public Void visit(SetIdentityInsertStatement statement, S context) { + return sessionOnly(); + } + + @Override + public Void visit(UseStatement use, S context) { + return sessionOnly(); + } + + @Override + public Void visit(SessionStatement sessionStatement, S context) { + return sessionOnly(); + } + + @Override + public Void visit(AlterSession alterSession, S context) { + return sessionOnly(); + } + + @Override + public Void visit(DeclareStatement declareStatement, S context) { + return sessionOnly(); + } + + @Override + public Void visit(AlterSystemStatement alterSystemStatement, S context) { + analysis.claimTopLevel(); + analysis.certain(StmtFeature.MODIFIES_SESSION); + analysis.possible(StmtFeature.MODIFIES_SCHEMA); + return null; + } + + private Void sessionOnly() { + analysis.claimTopLevel(); + analysis.certain(StmtFeature.MODIFIES_SESSION); + return null; + } + + // ---- metadata readers ------------------------------------------------------------------- + + @Override + public Void visit(ShowStatement showStatement, S context) { + return metadataOnly(); + } + + @Override + public Void visit(ShowColumnsStatement showColumnsStatement, S context) { + return metadataOnly(); + } + + @Override + public Void visit(ShowIndexStatement showIndexStatement, S context) { + return metadataOnly(); + } + + @Override + public Void visit(ShowTablesStatement showTables, S context) { + return metadataOnly(); + } + + @Override + public Void visit(DescribeStatement describe, S context) { + return metadataOnly(); + } + + /** Reads the catalogue, not the data. */ + private Void metadataOnly() { + if (analysis.claimTopLevel()) { + analysis.certain(StmtFeature.RETURNS_RESULT_SET); + } else { + analysis.certain(StmtFeature.OPAQUE); // keeps failLoudIfSilent honest when nested + } + return null; + } + + // ---- nested visitors -------------------------------------------------------------------- + + static final class FeatureSelectVisitor extends SelectVisitorAdapter { + private final Analysis analysis; + + FeatureSelectVisitor(Analysis analysis, ExpressionVisitor expressionVisitor, + PivotVisitor pivotVisitor, SelectItemVisitor selectItemVisitor, + FromItemVisitor fromItemVisitor) { + super(expressionVisitor, pivotVisitor, selectItemVisitor, fromItemVisitor); + this.analysis = analysis; + } + + @Override + public Void visit(PlainSelect plainSelect, S context) { + if (plainSelect.getFromItem() != null + || plainSelect.getJoins() != null && !plainSelect.getJoins().isEmpty()) { + analysis.certain(StmtFeature.READS_DATA); + } + + // SELECT .. INTO
materialises rows instead of returning them, so the + // RETURNS_RESULT_SET claimed by the statement visit has to be withdrawn + boolean into = plainSelect.getIntoTables() != null + && !plainSelect.getIntoTables().isEmpty() + || plainSelect.getIntoTempTable() != null; + if (into) { + analysis.certain(StmtFeature.MODIFIES_DATA, StmtFeature.MODIFIES_SCHEMA); + analysis.certain.remove(StmtFeature.RETURNS_RESULT_SET); + } + + MySqlSelectIntoClause mySqlInto = plainSelect.getMySqlSelectIntoClause(); + if (mySqlInto != null && mySqlInto.getType() == MySqlSelectIntoClause.Type.VARIABLES) { + analysis.certain(StmtFeature.MODIFIES_SESSION); + analysis.certain.remove(StmtFeature.RETURNS_RESULT_SET); + } + + if (plainSelect.getForMode() != null) { + // FOR UPDATE / FOR SHARE take row locks + analysis.certain(StmtFeature.MODIFIES_TRANSACTION); + } + + return super.visit(plainSelect, context); + } + + @Override + public Void visit(TableStatement tableStatement, S context) { + analysis.certain(StmtFeature.READS_DATA); + return super.visit(tableStatement, context); + } + + /** + * Replaces the inherited implementation, which casts the CTE body to ParenthesedSelect and + * therefore throws on {@code WITH c AS (DELETE .. RETURNING *)}. Routing to the statement + * visitor is also what makes a data-modifying CTE contribute MODIFIES_DATA. + */ + @Override + public Void visit(WithItem withItem, S context) { + ParenthesedStatement body = withItem.getParenthesedStatement(); + if (body instanceof Statement) { + ((Statement) body).accept(analysis.statements, context); + } + if (withItem.getExpression() != null) { + withItem.getExpression().accept(new FeatureExpressionVisitor(analysis), context); + } + return null; + } + } + + static final class FeatureExpressionVisitor extends ExpressionVisitorAdapter { + private final Analysis analysis; + + FeatureExpressionVisitor(Analysis analysis) { + this.analysis = analysis; + } + + /** Records unproven functions consistently across their different expression models. */ + private void analyseFunction(String functionName) { + String name = functionName == null ? "?" : functionName.toLowerCase(Locale.ROOT); + if (!analysis.pureFunctions.test(name)) { + analysis.possible(StmtFeature.MODIFIES_DATA, StmtFeature.MODIFIES_SCHEMA); + analysis.unresolved(name); + } + } + + @Override + public Void visit(Function function, S context) { + analyseFunction(function.getName()); + return super.visit(function, context); + } + + @Override + public Void visit(AnalyticExpression expression, S context) { + analyseFunction(expression.getName()); + return super.visit(expression, context); + } + + @Override + public Void visit(TranscodingFunction expression, S context) { + analyseFunction(expression.getKeyword()); + return super.visit(expression, context); + } + } + + static final class FeatureFromItemVisitor extends FromItemVisitorAdapter { + private final Analysis analysis; + + FeatureFromItemVisitor(Analysis analysis) { + this.analysis = analysis; + } + + /** The inherited implementation is empty, so table functions escape the purity check. */ + @Override + public Void visit(TableFunction tableFunction, S context) { + if (tableFunction.getFunction() != null) { + tableFunction.getFunction().accept(analysis.expressions, context); + } + return super.visit(tableFunction, context); + } + } + + @Override + public Void visit(CreateRole statement, S context) { + return schemaOnly(); + } + + @Override + public Void visit(AlterRole statement, S context) { + return schemaOnly(); + } + + @Override + public Void visit(Revoke statement, S context) { + return schemaOnly(); + } + + @Override + public Void visit(AlterDefaultPrivileges statement, S context) { + return schemaOnly(); + } + + @Override + public Void visit(CreateTrigger statement, S context) { + return schemaOnly(); + } + + @Override + public Void visit(CreateType statement, S context) { + analysis.claimTopLevel(); + analysis.certain(StmtFeature.MODIFIES_SCHEMA); + return null; + } + + @Override + public Void visit(AlterType statement, S context) { + analysis.claimTopLevel(); + analysis.certain(StmtFeature.MODIFIES_SCHEMA); + return null; + } + + @Override + public Void visit(CreateDomain statement, S context) { + analysis.claimTopLevel(); + analysis.certain(StmtFeature.MODIFIES_SCHEMA); + return null; + } + + @Override + public Void visit(AlterDomain statement, S context) { + analysis.claimTopLevel(); + analysis.certain(StmtFeature.MODIFIES_SCHEMA); + return null; + } + + @Override + public Void visit(CreateExtension statement, S context) { + analysis.claimTopLevel(); + analysis.certain(StmtFeature.MODIFIES_SCHEMA); + return null; + } + + @Override + public Void visit(AlterExtension statement, S context) { + analysis.claimTopLevel(); + analysis.certain(StmtFeature.MODIFIES_SCHEMA); + return null; + } + + @Override + public Void visit(CreatePublication statement, S context) { + analysis.claimTopLevel(); + analysis.certain(StmtFeature.MODIFIES_SCHEMA); + + return null; + } + + @Override + public Void visit(AlterPublication statement, S context) { + analysis.claimTopLevel(); + analysis.certain(StmtFeature.MODIFIES_SCHEMA); + + return null; + } + + @Override + public Void visit(CreateSubscription statement, S context) { + analysis.claimTopLevel(); + analysis.certain(StmtFeature.MODIFIES_SCHEMA); + if (statement.getOptions().stream() + .noneMatch(option -> (option.getKind() == SubscriptionOption.Kind.CONNECT + || option.getKind() == SubscriptionOption.Kind.ENABLED) + && Boolean.FALSE.equals(option.getBooleanValue()))) { + // A subscription may start asynchronous replication; the remote contents are unknown. + analysis.possible(StmtFeature.MODIFIES_DATA); + } + return null; + } + + @Override + public Void visit(AlterSubscription statement, S context) { + analysis.claimTopLevel(); + analysis.certain(StmtFeature.MODIFIES_SCHEMA); + if (statement.getAction() == AlterSubscription.Action.ENABLE + || statement.getAction() == AlterSubscription.Action.REFRESH_PUBLICATION + || statement.getAction() == AlterSubscription.Action.SET_PUBLICATION + || statement.getAction() == AlterSubscription.Action.ADD_PUBLICATION + || statement.getAction() == AlterSubscription.Action.DROP_PUBLICATION) { + analysis.possible(StmtFeature.MODIFIES_DATA); + } + return null; + } + + @Override + public Void visit(OracleBlock block, S context) { + analysis.claimTopLevel(); + return super.visit(block, context); + } + + @Override + public Void visit(OracleAssignment assignment, S context) { + analysis.claimTopLevel(); + // Assignment to a local variable is not a database write. + return super.visit(assignment, context); + } + + @Override + public Void visit(OracleNullStatement statement, S context) { + analysis.claimTopLevel(); + return null; + } + +} diff --git a/src/main/java/net/sf/jsqlparser/statement/StatementFeatures.java b/src/main/java/net/sf/jsqlparser/statement/StatementFeatures.java new file mode 100644 index 0000000000..d921039150 --- /dev/null +++ b/src/main/java/net/sf/jsqlparser/statement/StatementFeatures.java @@ -0,0 +1,116 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +/*- + * Copyright (C) 2004 - 2026 JSQLParser + * Licensed under the Apache License, Version 2.0. + */ +package net.sf.jsqlparser.statement; + +import java.util.Collections; +import java.util.EnumSet; +import java.util.LinkedHashSet; +import java.util.Set; + +/** + * Result of a static feature analysis of a {@link Statement}. + * + *

+ * Every flag is three-valued: certain (the grammar proves it), possible (could + * not be excluded), or absent (the grammar proves it cannot happen). Callers pick their own risk + * direction: + * + *

    + *
  • a dispatcher (executeQuery vs. executeUpdate) uses {@link #is(StmtFeature)}
  • + *
  • a read-only guard uses {@link #may(StmtFeature)}
  • + *
+ * + *

+ * This is a syntactic claim, never a semantic guarantee. Function volatility lives in the + * catalogue (e.g. {@code pg_proc.provolatile}), not in the parse tree. + */ +public final class StatementFeatures { + + private final Set certain; + private final Set uncertain; + private final Set unresolved; + + public StatementFeatures(EnumSet certain, EnumSet uncertain, + Set unresolved) { + // a feature proven certain is never also reported as merely possible + EnumSet u = EnumSet.copyOf(uncertain); + u.removeAll(certain); + this.certain = Collections.unmodifiableSet(EnumSet.copyOf(certain)); + this.uncertain = Collections.unmodifiableSet(u); + this.unresolved = Collections.unmodifiableSet(new LinkedHashSet<>(unresolved)); + } + + /** Proven by the grammar. */ + public boolean is(StmtFeature feature) { + return certain.contains(feature); + } + + /** Proven, or not excludable. This is what a guard must call. */ + public boolean may(StmtFeature feature) { + return certain.contains(feature) || uncertain.contains(feature); + } + + public Set getCertain() { + return certain; + } + + /** Strictly the "could not be excluded" set; disjoint from {@link #getCertain()}. */ + public Set getUncertain() { + return uncertain; + } + + /** + * Why the analysis is uncertain: unresolved function names, dynamic SQL markers, called + * procedure names. Resolve these against your own catalogue or allow-list. + */ + public Set getUnresolvedReferences() { + return unresolved; + } + + public boolean isOpaque() { + return may(StmtFeature.OPAQUE); + } + + // ---- convenience shorthands ------------------------------------------------------------- + // Deliberately named for what they mean, not for the DDL/DML/Query folklore, which cannot + // express "INSERT ... RETURNING *" (both row-returning and mutating) at all. + + public boolean returnsResultSet() { + return is(StmtFeature.RETURNS_RESULT_SET); + } + + public boolean modifiesData() { + return is(StmtFeature.MODIFIES_DATA); + } + + public boolean mayModifyData() { + return may(StmtFeature.MODIFIES_DATA); + } + + public boolean modifiesSchema() { + return is(StmtFeature.MODIFIES_SCHEMA); + } + + @Override + public String toString() { + StringBuilder b = new StringBuilder("certain=").append(certain); + if (!uncertain.isEmpty()) { + b.append(" possible=").append(uncertain); + } + if (!unresolved.isEmpty()) { + b.append(" unresolved=").append(unresolved); + } + return b.toString(); + } +} diff --git a/src/main/java/net/sf/jsqlparser/statement/StatementVisitor.java b/src/main/java/net/sf/jsqlparser/statement/StatementVisitor.java index 9ebab53a8b..93cb54c75e 100644 --- a/src/main/java/net/sf/jsqlparser/statement/StatementVisitor.java +++ b/src/main/java/net/sf/jsqlparser/statement/StatementVisitor.java @@ -9,6 +9,25 @@ */ package net.sf.jsqlparser.statement; +import net.sf.jsqlparser.statement.oracle.OracleBlock; +import net.sf.jsqlparser.statement.oracle.OracleAssignment; +import net.sf.jsqlparser.statement.oracle.OracleNullStatement; + +import net.sf.jsqlparser.statement.role.CreateRole; +import net.sf.jsqlparser.statement.role.AlterRole; +import net.sf.jsqlparser.statement.grant.Revoke; +import net.sf.jsqlparser.statement.grant.AlterDefaultPrivileges; +import net.sf.jsqlparser.statement.create.type.CreateType; +import net.sf.jsqlparser.statement.alter.AlterType; +import net.sf.jsqlparser.statement.create.domain.CreateDomain; +import net.sf.jsqlparser.statement.alter.AlterDomain; +import net.sf.jsqlparser.statement.create.extension.CreateExtension; +import net.sf.jsqlparser.statement.alter.AlterExtension; +import net.sf.jsqlparser.statement.create.publication.CreatePublication; +import net.sf.jsqlparser.statement.alter.AlterPublication; +import net.sf.jsqlparser.statement.create.subscription.CreateSubscription; +import net.sf.jsqlparser.statement.alter.AlterSubscription; + import net.sf.jsqlparser.statement.alter.Alter; import net.sf.jsqlparser.statement.alter.AlterSession; import net.sf.jsqlparser.statement.alter.AlterSystemStatement; @@ -16,12 +35,17 @@ import net.sf.jsqlparser.statement.alter.sequence.AlterSequence; import net.sf.jsqlparser.statement.analyze.Analyze; import net.sf.jsqlparser.statement.comment.Comment; +import net.sf.jsqlparser.statement.create.database.CreateDatabase; +import net.sf.jsqlparser.statement.create.event.AlterEvent; +import net.sf.jsqlparser.statement.create.event.CreateEvent; import net.sf.jsqlparser.statement.create.index.CreateIndex; import net.sf.jsqlparser.statement.create.policy.CreatePolicy; import net.sf.jsqlparser.statement.create.schema.CreateSchema; import net.sf.jsqlparser.statement.create.sequence.CreateSequence; import net.sf.jsqlparser.statement.create.synonym.CreateSynonym; import net.sf.jsqlparser.statement.create.table.CreateTable; +import net.sf.jsqlparser.statement.create.trigger.CreateTrigger; +import net.sf.jsqlparser.statement.create.user.CreateUser; import net.sf.jsqlparser.statement.create.view.AlterView; import net.sf.jsqlparser.statement.create.view.CreateView; import net.sf.jsqlparser.statement.delete.Delete; @@ -32,6 +56,7 @@ import net.sf.jsqlparser.statement.grant.Grant; import net.sf.jsqlparser.statement.imprt.Import; import net.sf.jsqlparser.statement.insert.Insert; +import net.sf.jsqlparser.statement.insert.InsertBulk; import net.sf.jsqlparser.statement.insert.ParenthesedInsert; import net.sf.jsqlparser.statement.lock.LockStatement; import net.sf.jsqlparser.statement.merge.Merge; @@ -43,9 +68,21 @@ import net.sf.jsqlparser.statement.update.ParenthesedUpdate; import net.sf.jsqlparser.statement.update.Update; import net.sf.jsqlparser.statement.upsert.Upsert; +import net.sf.jsqlparser.statement.create.macro.CreateMacro; +import net.sf.jsqlparser.statement.create.extension.CreateExtensionRepository; +import net.sf.jsqlparser.statement.export.ExportDataStatement; +import net.sf.jsqlparser.statement.load.LoadDataStatement; public interface StatementVisitor { + default T visit(InsertBulk statement, S context) { + return null; + } + + default void visit(InsertBulk statement) { + this.visit(statement, null); + } + T visit(Analyze analyze, S context); default void visit(Analyze analyze) { @@ -118,12 +155,36 @@ default void visit(CreateSchema createSchema) { this.visit(createSchema, null); } + T visit(CreateDatabase createDatabase, S context); + + default void visit(CreateDatabase createDatabase) { + this.visit(createDatabase, null); + } + + T visit(CreateUser createUser, S context); + + default void visit(CreateUser createUser) { + this.visit(createUser, null); + } + + T visit(CreateEvent createEvent, S context); + + default void visit(CreateEvent createEvent) { + this.visit(createEvent, null); + } + T visit(CreateTable createTable, S context); default void visit(CreateTable createTable) { this.visit(createTable, null); } + T visit(CreateTrigger createTrigger, S context); + + default void visit(CreateTrigger createTrigger) { + this.visit(createTrigger, null); + } + T visit(CreateView createView, S context); default void visit(CreateView createView) { @@ -148,6 +209,12 @@ default void visit(Alter alter) { this.visit(alter, null); } + T visit(AlterEvent alterEvent, S context); + + default void visit(AlterEvent alterEvent) { + this.visit(alterEvent, null); + } + T visit(Statements statements, S context); default void visit(Statements statements) { @@ -156,6 +223,14 @@ default void visit(Statements statements) { T visit(Execute execute, S context); + default T visit(DoStatement statement, S context) { + return null; + } + + default void visit(DoStatement statement) { + this.visit(statement, null); + } + default void visit(Execute execute) { this.visit(execute, null); } @@ -208,6 +283,14 @@ default void visit(Upsert upsert) { this.visit(upsert, null); } + default T visit(SetIdentityInsertStatement statement, S context) { + return null; + } + + default void visit(SetIdentityInsertStatement statement) { + visit(statement, null); + } + T visit(UseStatement use, S context); default void visit(UseStatement use) { @@ -292,6 +375,90 @@ default void visit(RenameTableStatement renameTableStatement) { this.visit(renameTableStatement, null); } + T visit(PragmaStatement pragmaStatement, S context); + + default void visit(PragmaStatement pragmaStatement) { + this.visit(pragmaStatement, null); + } + + T visit(ExtensionStatement extensionStatement, S context); + + default void visit(ExtensionStatement extensionStatement) { + this.visit(extensionStatement, null); + } + + T visit(AttachStatement attachStatement, S context); + + default void visit(AttachStatement attachStatement) { + this.visit(attachStatement, null); + } + + T visit(DetachStatement detachStatement, S context); + + default void visit(DetachStatement detachStatement) { + this.visit(detachStatement, null); + } + + T visit(ConnectStatement connectStatement, S context); + + default void visit(ConnectStatement connectStatement) { + this.visit(connectStatement, null); + } + + T visit(DisconnectStatement disconnectStatement, S context); + + default void visit(DisconnectStatement disconnectStatement) { + this.visit(disconnectStatement, null); + } + + T visit(PrepareStatement prepareStatement, S context); + + default void visit(PrepareStatement prepareStatement) { + this.visit(prepareStatement, null); + } + + T visit(DeallocateStatement deallocateStatement, S context); + + default void visit(DeallocateStatement deallocateStatement) { + this.visit(deallocateStatement, null); + } + + T visit(CopyStatement copyStatement, S context); + + default void visit(CopyStatement copyStatement) { + this.visit(copyStatement, null); + } + + T visit(CreateMacro createMacro, S context); + + default void visit(CreateMacro createMacro) { + this.visit(createMacro, null); + } + + T visit(CreateExtensionRepository createExtensionRepository, S context); + + default void visit(CreateExtensionRepository createExtensionRepository) { + this.visit(createExtensionRepository, null); + } + + T visit(AssertStatement assertStatement, S context); + + default void visit(AssertStatement assertStatement) { + this.visit(assertStatement, null); + } + + T visit(ExportDataStatement exportDataStatement, S context); + + default void visit(ExportDataStatement exportDataStatement) { + this.visit(exportDataStatement, null); + } + + T visit(LoadDataStatement loadDataStatement, S context); + + default void visit(LoadDataStatement loadDataStatement) { + this.visit(loadDataStatement, null); + } + T visit(PurgeStatement purgeStatement, S context); default void visit(PurgeStatement purgeStatement) { @@ -358,4 +525,141 @@ default void visit(CreatePolicy createPolicy) { this.visit(createPolicy, null); } + + default T visit(CreateRole statement, S context) { + return null; + } + + default void visit(CreateRole statement) { + visit(statement, null); + } + + default T visit(AlterRole statement, S context) { + return null; + } + + default void visit(AlterRole statement) { + visit(statement, null); + } + + default T visit(Revoke statement, S context) { + return null; + } + + default void visit(Revoke statement) { + visit(statement, null); + } + + default T visit(AlterDefaultPrivileges statement, S context) { + return null; + } + + default void visit(AlterDefaultPrivileges statement) { + visit(statement, null); + } + + default T visit(CreateType statement, S context) { + return null; + } + + default void visit(CreateType statement) { + visit(statement, null); + } + + default T visit(AlterType statement, S context) { + return null; + } + + default void visit(AlterType statement) { + visit(statement, null); + } + + default T visit(CreateDomain statement, S context) { + return null; + } + + default void visit(CreateDomain statement) { + visit(statement, null); + } + + default T visit(AlterDomain statement, S context) { + return null; + } + + default void visit(AlterDomain statement) { + visit(statement, null); + } + + default T visit(CreateExtension statement, S context) { + return null; + } + + default void visit(CreateExtension statement) { + visit(statement, null); + } + + default T visit(AlterExtension statement, S context) { + return null; + } + + default void visit(AlterExtension statement) { + visit(statement, null); + } + + default T visit(CreatePublication statement, S context) { + return null; + } + + default void visit(CreatePublication statement) { + visit(statement, null); + } + + default T visit(AlterPublication statement, S context) { + return null; + } + + default void visit(AlterPublication statement) { + visit(statement, null); + } + + default T visit(CreateSubscription statement, S context) { + return null; + } + + default void visit(CreateSubscription statement) { + visit(statement, null); + } + + default T visit(AlterSubscription statement, S context) { + return null; + } + + default void visit(AlterSubscription statement) { + visit(statement, null); + } + + default T visit(OracleBlock block, S context) { + return visit((Block) block, context); + } + + default void visit(OracleBlock block) { + visit(block, null); + } + + default T visit(OracleAssignment assignment, S context) { + return null; + } + + default void visit(OracleAssignment assignment) { + visit(assignment, null); + } + + default T visit(OracleNullStatement statement, S context) { + return null; + } + + default void visit(OracleNullStatement statement) { + visit(statement, null); + } + } diff --git a/src/main/java/net/sf/jsqlparser/statement/StatementVisitorAdapter.java b/src/main/java/net/sf/jsqlparser/statement/StatementVisitorAdapter.java index 3b12c01c0b..68453814a3 100644 --- a/src/main/java/net/sf/jsqlparser/statement/StatementVisitorAdapter.java +++ b/src/main/java/net/sf/jsqlparser/statement/StatementVisitorAdapter.java @@ -9,23 +9,49 @@ */ package net.sf.jsqlparser.statement; +import net.sf.jsqlparser.statement.oracle.OracleBlock; +import net.sf.jsqlparser.statement.oracle.OracleAssignment; + +import net.sf.jsqlparser.statement.role.CreateRole; +import net.sf.jsqlparser.statement.role.AlterRole; +import net.sf.jsqlparser.statement.role.RoleOption; +import net.sf.jsqlparser.statement.grant.Revoke; +import net.sf.jsqlparser.statement.grant.AlterDefaultPrivileges; +import net.sf.jsqlparser.statement.create.type.CreateType; +import net.sf.jsqlparser.statement.alter.AlterType; +import net.sf.jsqlparser.statement.create.domain.CreateDomain; +import net.sf.jsqlparser.statement.alter.AlterDomain; +import net.sf.jsqlparser.statement.create.extension.CreateExtension; +import net.sf.jsqlparser.statement.alter.AlterExtension; +import net.sf.jsqlparser.statement.create.publication.CreatePublication; +import net.sf.jsqlparser.statement.alter.AlterPublication; +import net.sf.jsqlparser.statement.create.subscription.CreateSubscription; +import net.sf.jsqlparser.statement.alter.AlterSubscription; + import net.sf.jsqlparser.expression.ExpressionVisitor; import net.sf.jsqlparser.expression.ExpressionVisitorAdapter; import net.sf.jsqlparser.schema.Column; +import net.sf.jsqlparser.schema.Table; import net.sf.jsqlparser.schema.Partition; import net.sf.jsqlparser.statement.alter.Alter; +import net.sf.jsqlparser.statement.alter.AlterExpression; import net.sf.jsqlparser.statement.alter.AlterSession; import net.sf.jsqlparser.statement.alter.AlterSystemStatement; import net.sf.jsqlparser.statement.alter.RenameTableStatement; import net.sf.jsqlparser.statement.alter.sequence.AlterSequence; import net.sf.jsqlparser.statement.analyze.Analyze; import net.sf.jsqlparser.statement.comment.Comment; +import net.sf.jsqlparser.statement.create.database.CreateDatabase; +import net.sf.jsqlparser.statement.create.event.AlterEvent; +import net.sf.jsqlparser.statement.create.event.CreateEvent; import net.sf.jsqlparser.statement.create.index.CreateIndex; import net.sf.jsqlparser.statement.create.policy.CreatePolicy; import net.sf.jsqlparser.statement.create.schema.CreateSchema; import net.sf.jsqlparser.statement.create.sequence.CreateSequence; import net.sf.jsqlparser.statement.create.synonym.CreateSynonym; import net.sf.jsqlparser.statement.create.table.CreateTable; +import net.sf.jsqlparser.statement.create.trigger.CreateTrigger; +import net.sf.jsqlparser.statement.create.user.CreateUser; import net.sf.jsqlparser.statement.create.view.AlterView; import net.sf.jsqlparser.statement.create.view.CreateView; import net.sf.jsqlparser.statement.delete.Delete; @@ -36,6 +62,7 @@ import net.sf.jsqlparser.statement.grant.Grant; import net.sf.jsqlparser.statement.imprt.Import; import net.sf.jsqlparser.statement.insert.Insert; +import net.sf.jsqlparser.statement.insert.InsertBulk; import net.sf.jsqlparser.statement.insert.InsertConflictAction; import net.sf.jsqlparser.statement.insert.ParenthesedInsert; import net.sf.jsqlparser.statement.lock.LockStatement; @@ -61,6 +88,11 @@ import net.sf.jsqlparser.statement.upsert.Upsert; import java.util.List; +import net.sf.jsqlparser.util.TableDefinitionTraversal; +import net.sf.jsqlparser.statement.create.macro.CreateMacro; +import net.sf.jsqlparser.statement.create.extension.CreateExtensionRepository; +import net.sf.jsqlparser.statement.export.ExportDataStatement; +import net.sf.jsqlparser.statement.load.LoadDataStatement; @SuppressWarnings({"PMD.UncommentedEmptyMethodBody"}) public class StatementVisitorAdapter implements StatementVisitor { @@ -71,14 +103,46 @@ public class StatementVisitorAdapter implements StatementVisitor { private final SelectVisitor selectVisitor; private final MergeOperationVisitor mergeOperationVisitor; + /** + * Builds a fully connected visitor graph. + * + *

+ * The sub-visitors are mutually recursive, so none of them can be handed its collaborators at + * construction time; the cycle has to be closed afterwards through the setters. The previous + * version skipped that step and the graph was not actually connected: + * + *

    + *
  • {@code new ExpressionVisitorAdapter<>()} leaves its {@code selectVisitor} null, and + * {@code ExpressionVisitorAdapter#visit(Select)} is a no-op when it is null - so every + * sub-select in an expression ({@code WHERE id IN (SELECT ..)}, scalar subqueries, EXISTS) was + * silently dropped;
  • + *
  • {@code new FromItemVisitorAdapter<>()} builds its own throwaway SelectVisitorAdapter and + * ExpressionVisitorAdapter, so subqueries in FROM were traversed by a detached chain that no + * subclass of this adapter could observe.
  • + *
+ * + *

+ * Both meant a subclass overriding a visit method never saw the nested nodes. The three setter + * calls below fix that; {@code setStatementVisitor} additionally lets the select visitor hand a + * data-modifying CTE body back here. + */ public StatementVisitorAdapter() { - this.expressionVisitor = new ExpressionVisitorAdapter<>(); - this.pivotVisitor = new PivotVisitorAdapter<>(this.expressionVisitor); - this.selectItemVisitor = new SelectItemVisitorAdapter<>(this.expressionVisitor); - this.fromItemVisitor = new FromItemVisitorAdapter<>(); + ExpressionVisitorAdapter expressions = new ExpressionVisitorAdapter<>(); + FromItemVisitorAdapter fromItems = new FromItemVisitorAdapter<>(); + + this.expressionVisitor = expressions; + this.pivotVisitor = new PivotVisitorAdapter<>(expressions); + this.selectItemVisitor = new SelectItemVisitorAdapter<>(expressions); + this.fromItemVisitor = fromItems; + + SelectVisitorAdapter selects = new SelectVisitorAdapter<>(expressions, this.pivotVisitor, + this.selectItemVisitor, fromItems); - this.selectVisitor = new SelectVisitorAdapter<>(this.expressionVisitor, this.pivotVisitor, - this.selectItemVisitor, this.fromItemVisitor); + expressions.setSelectVisitor(selects); + fromItems.setSelectVisitor((SelectVisitor) selects).setExpressionVisitor(expressions); + selects.setStatementVisitor(this); + + this.selectVisitor = selects; this.mergeOperationVisitor = new MergeOperationVisitorAdapter<>(); } @@ -92,10 +156,12 @@ public StatementVisitorAdapter(ExpressionVisitor expressionVisitor, this.fromItemVisitor = fromItemVisitor; this.selectVisitor = selectVisitor; this.mergeOperationVisitor = mergeOperationVisitor; + adoptAsStatementVisitor(selectVisitor); } public StatementVisitorAdapter(SelectVisitorAdapter selectVisitor) { this.selectVisitor = selectVisitor; + adoptAsStatementVisitor(selectVisitor); this.expressionVisitor = selectVisitor.getExpressionVisitor(); this.pivotVisitor = selectVisitor.getPivotVisitor(); this.selectItemVisitor = selectVisitor.getSelectItemVisitor(); @@ -103,9 +169,27 @@ public StatementVisitorAdapter(SelectVisitorAdapter selectVisitor) { this.mergeOperationVisitor = new MergeOperationVisitorAdapter<>(); } + /** + * Lets the select visitor hand a data-modifying CTE body back to this statement visitor. + * Without it, {@code SelectVisitorAdapter#visit(WithItem)} has no way to traverse + * {@code WITH c AS (DELETE FROM t RETURNING *) ..} and silently skips it. + * + *

+ * An explicitly configured statement visitor is never overwritten. + */ + private void adoptAsStatementVisitor(SelectVisitor visitor) { + if (visitor instanceof SelectVisitorAdapter) { + SelectVisitorAdapter adapter = (SelectVisitorAdapter) visitor; + if (adapter.getStatementVisitor() == null) { + adapter.setStatementVisitor(this); + } + } + } + @Override public T visit(Comment comment, S context) { - + comment.visitRelations(table -> table.accept(fromItemVisitor, context)); + expressionVisitor.visitExpression(comment.getComment(), context); return null; } @@ -136,6 +220,10 @@ public T visit(Delete delete, S context) { expressionVisitor.visitLimit(delete.getLimit(), context); + // was missing, unlike visit(Insert) and visit(Update): + // DELETE FROM t RETURNING f(x) left f(x) untraversed + visitReturningClause(delete.getReturningClause(), context); + return null; } @@ -217,6 +305,9 @@ public T visit(Insert insert, S context) { expressionVisitor.visitUpdateSets(conflictAction.getUpdateSets(), context); } + if (insert.getConflictTarget() != null) { + insert.getConflictTarget().accept(expressionVisitor, context); + } visitReturningClause(insert.getReturningClause(), context); return null; } @@ -229,6 +320,13 @@ private T visitReturningClause(ReturningClause returningClause, S context) { return null; } + @Override + public T visit(InsertBulk statement, S context) { + fromItemVisitor.visitFromItem(statement.getTable(), context); + statement.visitExpressions(expression -> expression.accept(expressionVisitor, context)); + return null; + } + @Override public T visit(ParenthesedInsert insert, S context) { insert.getInsert().accept(this, context); @@ -237,10 +335,7 @@ public T visit(ParenthesedInsert insert, S context) { @Override public T visit(Drop drop, S context) { - if (drop.getType().equalsIgnoreCase("table")) { - fromItemVisitor.visitFromItem(drop.getName(), context); - } - // @todo: handle schemas + drop.visitTables(table -> fromItemVisitor.visitFromItem(table, context)); return null; } @@ -252,7 +347,9 @@ public T visit(Truncate truncate, S context) { @Override public T visit(CreateIndex createIndex, S context) { - + TableDefinitionTraversal.visit(createIndex, + expression -> expression.accept(expressionVisitor, context), + table -> table.accept(fromItemVisitor, context)); return null; } @@ -261,19 +358,64 @@ public T visit(CreateSchema createSchema, S context) { return null; } + @Override + public T visit(CreateDatabase createDatabase, S context) { + return null; + } + + @Override + public T visit(CreateUser createUser, S context) { + return null; + } + + @Override + public T visit(CreateEvent createEvent, S context) { + return null; + } + @Override public T visit(CreateTable createTable, S context) { + TableDefinitionTraversal.visit(createTable, + expression -> expression.accept(expressionVisitor, context), + table -> table.accept(fromItemVisitor, context)); + if (createTable.getSelect() != null) { + createTable.getSelect().accept(selectVisitor, context); + } return createTable.getTable().accept(fromItemVisitor, context); } + @Override + public T visit(CreateTrigger createTrigger, S context) { + createTrigger.visit(t -> t.accept(fromItemVisitor, context), + e -> e.accept(expressionVisitor, context)); + if (createTrigger.getBody() != null) { + createTrigger.getBody().accept(this, context); + } + return null; + } + @Override public T visit(CreateView createView, S context) { + createView.getView().accept(fromItemVisitor, context); + if (createView.getSelect() != null) { + createView.getSelect().accept(selectVisitor, context); + } return null; } @Override public T visit(Alter alter, S context) { + alter.getTable().accept(fromItemVisitor, context); + for (AlterExpression action : alter.getAlterExpressions()) { + TableDefinitionTraversal.visit(action, + expression -> expression.accept(expressionVisitor, context), + table -> table.accept(fromItemVisitor, context)); + } + return null; + } + @Override + public T visit(AlterEvent alterEvent, S context) { return null; } @@ -287,7 +429,13 @@ public T visit(Statements statements, S context) { @Override public T visit(Execute execute, S context) { + expressionVisitor.visitExpression(execute.getExprList(), context); + return null; + } + @Override + public T visit(DoStatement statement, S context) { + expressionVisitor.visitExpression(statement.getCode(), context); return null; } @@ -299,6 +447,9 @@ public T visit(LockStatement lock, S context) { @Override public T visit(CreatePolicy createPolicy, S context) { + fromItemVisitor.visitFromItem(createPolicy.getTable(), context); + expressionVisitor.visitExpression(createPolicy.getUsingExpression(), context); + expressionVisitor.visitExpression(createPolicy.getWithCheckExpression(), context); return null; } @@ -323,6 +474,7 @@ public T visit(Merge merge, S context) { expressionVisitor.visitExpression(merge.getOnCondition(), context); mergeOperationVisitor.visit(merge.getOperations(), context); selectVisitor.visitOutputClause(merge.getOutputClause(), context); + visitReturningClause(merge.getReturningClause(), context); return null; } @@ -336,6 +488,11 @@ public T visit(Upsert upsert, S context) { return null; } + @Override + public T visit(SetIdentityInsertStatement statement, S context) { + return statement.getTable().accept(fromItemVisitor, context); + } + @Override public T visit(UseStatement use, S context) { return null; @@ -383,21 +540,40 @@ public T visit(DeclareStatement declareStatement, S context) { @Override public T visit(Grant grant, S context) { + grant.getClause().visit(t -> t.accept(fromItemVisitor, context), + e -> e.accept(expressionVisitor, context)); return null; } @Override public T visit(CreateSequence createSequence, S context) { + if (createSequence.getSequence().getOwnership() != null + && createSequence.getSequence().getOwnership().getColumn() != null) { + createSequence.getSequence().getOwnership().getColumn().accept(expressionVisitor, + context); + } return null; } @Override public T visit(AlterSequence alterSequence, S context) { + if (alterSequence.getSequence().getOwnership() != null + && alterSequence.getSequence().getOwnership().getColumn() != null) { + alterSequence.getSequence().getOwnership().getColumn().accept(expressionVisitor, + context); + } return null; } @Override public T visit(CreateFunctionalStatement createFunctionalStatement, S context) { + if (createFunctionalStatement.getReturnType() != null + && createFunctionalStatement.getReturnType().getTableElements() != null) { + createFunctionalStatement.getReturnType().getTableElements() + .forEach(element -> TableDefinitionTraversal.visit(element, + expression -> expression.accept(expressionVisitor, context), + table -> table.accept(fromItemVisitor, context))); + } return null; } @@ -444,6 +620,76 @@ public T visit(RenameTableStatement renameTableStatement, S context) { return null; } + @Override + public T visit(PragmaStatement pragmaStatement, S context) { + return null; + } + + @Override + public T visit(AssertStatement assertStatement, S context) { + return null; + } + + @Override + public T visit(ExtensionStatement extensionStatement, S context) { + return null; + } + + @Override + public T visit(ExportDataStatement exportDataStatement, S context) { + return null; + } + + @Override + public T visit(AttachStatement attachStatement, S context) { + return null; + } + + @Override + public T visit(DetachStatement detachStatement, S context) { + return null; + } + + @Override + public T visit(ConnectStatement connectStatement, S context) { + return null; + } + + @Override + public T visit(DisconnectStatement disconnectStatement, S context) { + return null; + } + + @Override + public T visit(PrepareStatement prepareStatement, S context) { + return null; + } + + @Override + public T visit(DeallocateStatement deallocateStatement, S context) { + return null; + } + + @Override + public T visit(CopyStatement copyStatement, S context) { + return null; + } + + @Override + public T visit(CreateMacro createMacro, S context) { + return null; + } + + @Override + public T visit(CreateExtensionRepository createExtensionRepository, S context) { + return null; + } + + @Override + public T visit(LoadDataStatement loadDataStatement, S context) { + return null; + } + @Override public T visit(PurgeStatement purgeStatement, S context) { return null; @@ -474,4 +720,164 @@ public T visit(Import imprt, S context) { public T visit(Export export, S context) { return null; } + + @Override + public T visit(CreateRole statement, S context) { + for (RoleOption option : statement.getOptions()) { + if (option.getValue() != null) { + option.getValue().accept(expressionVisitor, context); + } + } + return null; + } + + @Override + public T visit(AlterRole statement, S context) { + for (RoleOption option : statement.getOptions()) { + if (option.getValue() != null) { + option.getValue().accept(expressionVisitor, context); + } + } + if (statement.getValues() != null) { + statement.getValues().forEach(e -> e.accept(expressionVisitor, context)); + } + return null; + } + + @Override + public T visit(Revoke statement, S context) { + statement.getClause().visit(t -> t.accept(fromItemVisitor, context), + e -> e.accept(expressionVisitor, context)); + return null; + } + + @Override + public T visit(AlterDefaultPrivileges statement, S context) { + if (statement.getGrant() != null) { + statement.getGrant().accept(this, context); + } + if (statement.getRevoke() != null) { + statement.getRevoke().accept(this, context); + } + return null; + } + + @Override + public T visit(CreateType statement, S context) { + + return null; + } + + @Override + public T visit(AlterType statement, S context) { + + return null; + } + + @Override + public T visit(CreateDomain statement, S context) { + if (statement.getDefaultExpression() != null) { + statement.getDefaultExpression().accept(expressionVisitor, context); + } + statement.getConstraints().forEach(constraint -> { + if (constraint.getExpression() != null) { + constraint.getExpression().accept(expressionVisitor, context); + } + }); + return null; + } + + @Override + public T visit(AlterDomain statement, S context) { + if (statement.getDefaultExpression() != null) { + statement.getDefaultExpression().accept(expressionVisitor, context); + } + if (statement.getConstraint() != null + && statement.getConstraint().getExpression() != null) { + statement.getConstraint().getExpression().accept(expressionVisitor, context); + } + return null; + } + + @Override + public T visit(CreateExtension statement, S context) { + + return null; + } + + @Override + public T visit(AlterExtension statement, S context) { + if (statement.getMember() != null && statement.getMember().isTable()) { + new Table(statement.getMember().getName()) + .accept(fromItemVisitor, context); + } + return null; + } + + @Override + public T visit(CreatePublication statement, S context) { + statement.getTargets().forEach(target -> target.visit( + table -> table.accept(fromItemVisitor, context), + expression -> expression.accept(expressionVisitor, context))); + statement.getOptions().forEach(option -> { + if (option.getValue() != null) { + option.getValue().accept(expressionVisitor, context); + } + }); + return null; + } + + @Override + public T visit(AlterPublication statement, S context) { + statement.getTargets().forEach(target -> target.visit( + table -> table.accept(fromItemVisitor, context), + expression -> expression.accept(expressionVisitor, context))); + statement.getOptions().forEach(option -> { + if (option.getValue() != null) { + option.getValue().accept(expressionVisitor, context); + } + }); + return null; + } + + @Override + public T visit(CreateSubscription statement, S context) { + if (statement.getConnection() != null) { + statement.getConnection().accept(expressionVisitor, context); + } + statement.getOptions().forEach(option -> { + if (option.getValue() != null) { + option.getValue().accept(expressionVisitor, context); + } + }); + return null; + } + + @Override + public T visit(AlterSubscription statement, S context) { + if (statement.getConnection() != null) { + statement.getConnection().accept(expressionVisitor, context); + } + statement.getOptions().forEach(option -> { + if (option.getValue() != null) { + option.getValue().accept(expressionVisitor, context); + } + }); + return null; + } + + @Override + public T visit(OracleBlock block, S context) { + block.visitChildren(expression -> expression.accept(expressionVisitor, context), + statement -> statement.accept(this, context)); + return null; + } + + @Override + public T visit(OracleAssignment assignment, S context) { + assignment.getTarget().accept(expressionVisitor, context); + assignment.getValue().accept(expressionVisitor, context); + return null; + } + } diff --git a/src/main/java/net/sf/jsqlparser/statement/StmtFeature.java b/src/main/java/net/sf/jsqlparser/statement/StmtFeature.java new file mode 100644 index 0000000000..00a413851f --- /dev/null +++ b/src/main/java/net/sf/jsqlparser/statement/StmtFeature.java @@ -0,0 +1,50 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.statement; + +/** + * What a statement does, as far as the grammar can prove it. + * + *

+ * Named {@code StmtFeature} rather than {@code Feature} because + * {@code net.sf.jsqlparser.parser.feature.Feature} already occupies that simple name and the two + * are unrelated: that one describes parser capabilities, this one describes statement effects. + * + *

+ * These are not mutually exclusive. {@code INSERT .. RETURNING *} carries {@link #MODIFIES_DATA} + * and {@link #RETURNS_RESULT_SET}; {@code CREATE TABLE .. AS SELECT} carries + * {@link #MODIFIES_SCHEMA} and {@link #READS_DATA}. Any attempt to collapse them into a single + * Query/DDL/DML verdict loses information the caller needs. + */ +public enum StmtFeature { + /** Reads persistent data - a table or view in a query position. */ + READS_DATA, + + /** + * Returns a result set to the client. A property of the statement's own result + * position, never of a nested one: {@code INSERT INTO x SELECT ..} does not have it. + */ + RETURNS_RESULT_SET, + + /** Inserts, updates, deletes, merges or otherwise destroys rows. */ + MODIFIES_DATA, + + /** Creates, alters, drops, truncates, renames, comments, grants. */ + MODIFIES_SCHEMA, + + /** SET, RESET, USE, DECLARE, ALTER SESSION. */ + MODIFIES_SESSION, + + /** COMMIT, ROLLBACK, SAVEPOINT, LOCK, SELECT .. FOR UPDATE. */ + MODIFIES_TRANSACTION, + + /** Nothing further is knowable statically: CALL, EXECUTE, dynamic SQL, procedure bodies. */ + OPAQUE +} diff --git a/src/main/java/net/sf/jsqlparser/statement/alter/AlterDomain.java b/src/main/java/net/sf/jsqlparser/statement/alter/AlterDomain.java new file mode 100644 index 0000000000..3531ba8146 --- /dev/null +++ b/src/main/java/net/sf/jsqlparser/statement/alter/AlterDomain.java @@ -0,0 +1,170 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.statement.alter; + +import java.util.function.Consumer; +import net.sf.jsqlparser.expression.Expression; +import net.sf.jsqlparser.statement.create.domain.DomainConstraint; +import net.sf.jsqlparser.statement.Statement; +import net.sf.jsqlparser.statement.StatementVisitor; + +public class AlterDomain implements Statement { + private String name; + private Action action; + private Expression defaultExpression; + private DomainConstraint constraint; + private String constraintName; + private String newName; + private boolean notValid; + private boolean ifExists; + private Behavior behavior; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Action getAction() { + return action; + } + + public void setAction(Action action) { + this.action = action; + } + + public Expression getDefaultExpression() { + return defaultExpression; + } + + public void setDefaultExpression(Expression defaultExpression) { + this.defaultExpression = defaultExpression; + } + + public DomainConstraint getConstraint() { + return constraint; + } + + public void setConstraint(DomainConstraint constraint) { + this.constraint = constraint; + } + + public String getConstraintName() { + return constraintName; + } + + public void setConstraintName(String constraintName) { + this.constraintName = constraintName; + } + + public String getNewName() { + return newName; + } + + public void setNewName(String newName) { + this.newName = newName; + } + + public boolean isNotValid() { + return notValid; + } + + public void setNotValid(boolean notValid) { + this.notValid = notValid; + } + + public boolean isIfExists() { + return ifExists; + } + + public void setIfExists(boolean ifExists) { + this.ifExists = ifExists; + } + + public Behavior getBehavior() { + return behavior; + } + + public void setBehavior(Behavior behavior) { + this.behavior = behavior; + } + + @Override + public T accept(StatementVisitor visitor, S context) { + return visitor.visit(this, context); + } + + public enum Action { + SET_DEFAULT, DROP_DEFAULT, SET_NOT_NULL, DROP_NOT_NULL, ADD_CONSTRAINT, DROP_CONSTRAINT, RENAME_CONSTRAINT, VALIDATE_CONSTRAINT, OWNER, RENAME, SET_SCHEMA + } + public enum Behavior { + CASCADE, RESTRICT + } + + public void appendTo(StringBuilder sql, Consumer expressions) { + sql.append("ALTER DOMAIN ").append(name).append(' '); + switch (action) { + case SET_DEFAULT: + sql.append("SET DEFAULT "); + expressions.accept(defaultExpression); + break; + case DROP_DEFAULT: + sql.append("DROP DEFAULT"); + break; + case SET_NOT_NULL: + sql.append("SET NOT NULL"); + break; + case DROP_NOT_NULL: + sql.append("DROP NOT NULL"); + break; + case ADD_CONSTRAINT: + sql.append("ADD "); + constraint.appendTo(sql, expressions); + if (notValid) { + sql.append(" NOT VALID"); + } + break; + case DROP_CONSTRAINT: + sql.append("DROP CONSTRAINT ").append(ifExists ? "IF EXISTS " : "") + .append(constraintName); + if (behavior != null) { + sql.append(' ').append(behavior); + } + break; + case RENAME_CONSTRAINT: + sql.append("RENAME CONSTRAINT ").append(constraintName).append(" TO ") + .append(newName); + break; + case VALIDATE_CONSTRAINT: + sql.append("VALIDATE CONSTRAINT ").append(constraintName); + break; + case OWNER: + sql.append("OWNER TO ").append(newName); + break; + case RENAME: + sql.append("RENAME TO ").append(newName); + break; + case SET_SCHEMA: + sql.append("SET SCHEMA ").append(newName); + break; + default: + throw new IllegalStateException("Unknown domain alteration: " + action); + } + } + + @Override + public String toString() { + StringBuilder sql = new StringBuilder(); + appendTo(sql, sql::append); + return sql.toString(); + } +} diff --git a/src/main/java/net/sf/jsqlparser/statement/alter/AlterExpression.java b/src/main/java/net/sf/jsqlparser/statement/alter/AlterExpression.java index 43615abdb6..29c9b1ced5 100644 --- a/src/main/java/net/sf/jsqlparser/statement/alter/AlterExpression.java +++ b/src/main/java/net/sf/jsqlparser/statement/alter/AlterExpression.java @@ -18,6 +18,7 @@ import java.util.List; import java.util.Optional; import java.util.Set; +import java.util.function.Consumer; import java.util.stream.Collectors; import net.sf.jsqlparser.expression.Expression; import net.sf.jsqlparser.statement.ReferentialAction; @@ -32,15 +33,21 @@ @SuppressWarnings({"PMD.CyclomaticComplexity"}) public class AlterExpression implements Serializable { + public enum TableRenameKeyword { + NONE, TO, AS + } + private final Set referentialActions = new LinkedHashSet<>(2); private AlterOperation operation; private String optionalSpecifier; private String newTableName; + private TableRenameKeyword tableRenameKeyword = TableRenameKeyword.TO; private String columnName; // private ColDataType dataType; private String columnOldName; private List colDataTypeList; private List columnDropNotNullList; + private List columnSetNotNullList; private List columnDropDefaultList; private List columnSetDefaultList; private List columnSetVisibilityList; @@ -382,6 +389,13 @@ public void addColDropNotNull(ColumnDropNotNull columnDropNotNull) { columnDropNotNullList.add(columnDropNotNull); } + public void addColSetNotNull(ColumnSetNotNull columnSetNotNull) { + if (columnSetNotNullList == null) { + columnSetNotNullList = new ArrayList<>(); + } + columnSetNotNullList.add(columnSetNotNull); + } + public List getColumnDropDefaultList() { return columnDropDefaultList; } @@ -441,6 +455,14 @@ public void setNewTableName(String newTableName) { this.newTableName = newTableName; } + public TableRenameKeyword getTableRenameKeyword() { + return tableRenameKeyword; + } + + public void setTableRenameKeyword(TableRenameKeyword tableRenameKeyword) { + this.tableRenameKeyword = tableRenameKeyword; + } + public String getColumnName() { return columnName; } @@ -483,20 +505,57 @@ public void setUsingIfExists(boolean usingIfExists) { this.usingIfExists = usingIfExists; } + /** + * Returns a snapshot of the rendered key elements for a structured PRIMARY_KEY, or the legacy + * list otherwise. Changing the snapshot does not change the index; use + * {@link #setPkColumns(List)} to replace its keys, or {@link Index#getColumns()} for structured + * edits. + */ public List getPkColumns() { - return pkColumns; + return hasKeyIndex(Index.Kind.PRIMARY_KEY) ? index.getColumnsNames() : pkColumns; } + /** + * Replaces a structured PRIMARY_KEY's elements using {@link Index#setColumnsNames(List)}. The + * strings are not parsed and existing element attributes are not retained, even when the + * rendered SQL is unchanged. Use {@link Index#setColumns(List)} to preserve structured + * elements. Without a matching index, stores the legacy list. + */ public void setPkColumns(List pkColumns) { - this.pkColumns = pkColumns; + if (hasKeyIndex(Index.Kind.PRIMARY_KEY)) { + index.setColumnsNames(pkColumns); + this.pkColumns = null; + } else { + this.pkColumns = pkColumns; + } } + /** + * Returns a snapshot of the rendered key elements for a structured UNIQUE, or the legacy list + * otherwise. Changing the snapshot does not change the index; use {@link #setUkColumns(List)} + * to replace its keys, or {@link Index#getColumns()} for structured edits. + */ public List getUkColumns() { - return ukColumns; + return hasKeyIndex(Index.Kind.UNIQUE) ? index.getColumnsNames() : ukColumns; } + /** + * Replaces a structured UNIQUE's elements using {@link Index#setColumnsNames(List)}. The + * strings are not parsed and existing element attributes are not retained, even when the + * rendered SQL is unchanged. Use {@link Index#setColumns(List)} to preserve structured + * elements. Without a matching index, stores the legacy list. + */ public void setUkColumns(List ukColumns) { - this.ukColumns = ukColumns; + if (hasKeyIndex(Index.Kind.UNIQUE)) { + index.setColumnsNames(ukColumns); + this.ukColumns = null; + } else { + this.ukColumns = ukColumns; + } + } + + private boolean hasKeyIndex(Index.Kind kind) { + return index != null && index.getKind() == kind; } public String getUkName() { @@ -527,6 +586,10 @@ public List getColumnDropNotNullList() { return columnDropNotNullList; } + public List getColumnSetNotNullList() { + return columnSetNotNullList; + } + public void addParameters(String... params) { if (parameters == null) { parameters = new ArrayList<>(); @@ -818,6 +881,8 @@ protected boolean isPartitionOperation() { switch (operation) { case DISCARD_PARTITION: case IMPORT_PARTITION: + case ATTACH_PARTITION: + case DETACH_PARTITION: case TRUNCATE_PARTITION: case COALESCE_PARTITION: case REORGANIZE_PARTITION: @@ -856,6 +921,16 @@ protected void toStringConstraintAlter(StringBuilder b) { } protected void toStringAlterColumn(StringBuilder b) { + appendAlterColumn(b, b::append); + } + + /** Renders ALTER COLUMN default/visibility actions with their common tail. */ + public void appendColumnActionTo(StringBuilder b, Consumer expressionPrinter) { + appendAlterColumn(b, expressionPrinter); + appendCommonTail(b); + } + + private void appendAlterColumn(StringBuilder b, Consumer expressionPrinter) { b.append("ALTER "); if (hasColumn) { b.append("COLUMN "); @@ -863,7 +938,12 @@ protected void toStringAlterColumn(StringBuilder b) { if (columnDropDefaultList != null && !columnDropDefaultList.isEmpty()) { b.append(PlainSelect.getStringList(columnDropDefaultList)); } else if (columnSetDefaultList != null && !columnSetDefaultList.isEmpty()) { - b.append(PlainSelect.getStringList(columnSetDefaultList)); + for (int i = 0; i < columnSetDefaultList.size(); i++) { + if (i > 0) { + b.append(", "); + } + columnSetDefaultList.get(i).appendTo(b, expressionPrinter); + } } else { b.append(PlainSelect.getStringList(columnSetVisibilityList)); } @@ -1072,9 +1152,19 @@ protected void toStringPartition(StringBuilder b) { * Handles the general case for ADD, MODIFY, CHANGE, DROP (column), COMMENT, row-level security, * and all field-based dispatch (columns, constraints, FK, UK, PK, index). */ + protected void toStringGeneral(StringBuilder b) { + toStringGeneral(b, b::append); + } + + /** Appends a column-definition action, including its common tail. */ + public void appendColumnDefinitionsTo(StringBuilder b, Consumer expressionPrinter) { + toStringGeneral(b, column -> column.appendTo(b, expressionPrinter)); + appendCommonTail(b); + } + @SuppressWarnings({"PMD.CyclomaticComplexity", "PMD.NPathComplexity", "PMD.ExcessiveMethodLength"}) - protected void toStringGeneral(StringBuilder b) { + private void toStringGeneral(StringBuilder b, Consumer columnPrinter) { if (operation == AlterOperation.COMMENT_WITH_EQUAL_SIGN) { b.append("COMMENT =").append(" "); } else if (operation == AlterOperation.ENABLE_ROW_LEVEL_SECURITY) { @@ -1118,21 +1208,30 @@ protected void toStringGeneral(StringBuilder b) { } else if (hasColumns) { b.append("COLUMNS "); } - if (useIfNotExists - && operation == AlterOperation.ADD) { + if (usingIfExists) { + b.append("IF EXISTS "); + } else if (useIfNotExists) { b.append("IF NOT EXISTS "); } } if (useBrackets && colDataTypeList.size() == 1) { b.append(" ( "); } - b.append(PlainSelect.getStringList(colDataTypeList)); + for (int i = 0; i < colDataTypeList.size(); i++) { + if (i > 0) { + b.append(", "); + } + columnPrinter.accept(colDataTypeList.get(i)); + } if (useBrackets && colDataTypeList.size() == 1) { b.append(" ) "); } if (colDataTypeList.size() > 1) { b.append(")"); } + } else if (getColumnSetNotNullList() != null) { + b.append("COLUMN "); + b.append(PlainSelect.getStringList(columnSetNotNullList)); } else if (getColumnDropNotNullList() != null) { b.append("COLUMN "); b.append(PlainSelect.getStringList(columnDropNotNullList)); @@ -1145,6 +1244,10 @@ protected void toStringGeneral(StringBuilder b) { b.append("IF EXISTS "); } b.append(constraintName); + } else if (index != null) { + // The structured index is canonical. Legacy PK/UK/FK fields may also be populated for + // source compatibility, but cannot represent names, expressions, or index options. + b.append(index); } else if (pkColumns != null) { b.append("PRIMARY KEY (").append(PlainSelect.getStringList(pkColumns)).append(')'); } else if (ukColumns != null) { @@ -1177,8 +1280,6 @@ protected void toStringGeneral(StringBuilder b) { .append(PlainSelect.getStringList(fkSourceColumns)) .append(")"); referentialActions.forEach(b::append); - } else if (index != null) { - b.append(index); } if (getConstraints() != null && !getConstraints().isEmpty()) { @@ -1199,6 +1300,16 @@ public AlterExpression withOptionalSpecifier(String optionalSpecifier) { return this; } + public AlterExpression withNewTableName(String newTableName) { + setNewTableName(newTableName); + return this; + } + + public AlterExpression withTableRenameKeyword(TableRenameKeyword tableRenameKeyword) { + setTableRenameKeyword(tableRenameKeyword); + return this; + } + public AlterExpression withColumnName(String columnName) { this.setColumnName(columnName); return this; @@ -1298,24 +1409,28 @@ public AlterExpression withColumnOldName(String columnOldName) { return this; } + /** Adds strings using the replacement semantics of {@link #setPkColumns(List)}. */ public AlterExpression addPkColumns(String... pkColumns) { List collection = Optional.ofNullable(getPkColumns()).orElseGet(ArrayList::new); Collections.addAll(collection, pkColumns); return this.withPkColumns(collection); } + /** Adds strings using the replacement semantics of {@link #setPkColumns(List)}. */ public AlterExpression addPkColumns(Collection pkColumns) { List collection = Optional.ofNullable(getPkColumns()).orElseGet(ArrayList::new); collection.addAll(pkColumns); return this.withPkColumns(collection); } + /** Adds strings using the replacement semantics of {@link #setUkColumns(List)}. */ public AlterExpression addUkColumns(String... ukColumns) { List collection = Optional.ofNullable(getUkColumns()).orElseGet(ArrayList::new); Collections.addAll(collection, ukColumns); return this.withUkColumns(collection); } + /** Adds strings using the replacement semantics of {@link #setUkColumns(List)}. */ public AlterExpression addUkColumns(Collection ukColumns) { List collection = Optional.ofNullable(getUkColumns()).orElseGet(ArrayList::new); collection.addAll(ukColumns); @@ -1393,6 +1508,29 @@ public void setHasEqualForCollate(boolean hasEqualForCollate) { public static final class ColumnDataType extends ColumnDefinition { private final boolean withType; + private Expression usingExpression; + private List identityAlterations; + + public boolean isWithType() { + return withType; + } + + public Expression getUsingExpression() { + return usingExpression; + } + + public void setUsingExpression(Expression usingExpression) { + this.usingExpression = usingExpression; + } + + public List getIdentityAlterations() { + return identityAlterations; + } + + public void setIdentityAlterations(List identityAlterations) { + this.identityAlterations = + identityAlterations == null ? null : new ArrayList<>(identityAlterations); + } public ColumnDataType(boolean withType) { super(); @@ -1408,8 +1546,25 @@ public ColumnDataType( @Override public String toString() { - return getColumnName() + (withType ? " TYPE " : getColDataType() == null ? "" : " ") - + toStringDataTypeAndSpec(); + StringBuilder builder = new StringBuilder(); + appendTo(builder, builder::append); + return builder.toString(); + } + + @Override + public void appendTo(StringBuilder builder, Consumer expressionPrinter) { + builder.append(getColumnName()); + if (identityAlterations != null) { + builder.append(' ') + .append(PlainSelect.getStringList(identityAlterations, false, false)); + return; + } + builder.append(withType ? " TYPE " : getColDataType() == null ? "" : " "); + appendDataTypeAndSpecTo(builder, expressionPrinter); + if (usingExpression != null) { + builder.append(" USING "); + expressionPrinter.accept(usingExpression); + } } @Override @@ -1466,6 +1621,24 @@ public String toString() { } } + public static final class ColumnSetNotNull implements Serializable { + + private final String columnName; + + public ColumnSetNotNull(String columnName) { + this.columnName = columnName; + } + + public String getColumnName() { + return columnName; + } + + @Override + public String toString() { + return columnName + " SET NOT NULL"; + } + } + public static final class ColumnDropDefault implements Serializable { private final String columnName; @@ -1486,7 +1659,8 @@ public String toString() { public static final class ColumnSetDefault implements Serializable { private final String columnName; - private final String defaultValue; + private String defaultValue; + private Expression defaultExpression; public ColumnSetDefault(String columnName, String defaultValue) { this.columnName = columnName; @@ -1497,13 +1671,40 @@ public String getColumnName() { return columnName; } + /** Constructs a structured default without overloading the legacy nullable String API. */ + public static ColumnSetDefault fromExpression(String columnName, Expression expression) { + ColumnSetDefault result = new ColumnSetDefault(columnName, null); + result.setDefaultExpression(expression); + return result; + } + + public Expression getDefaultExpression() { + return defaultExpression; + } + + public void setDefaultExpression(Expression defaultExpression) { + this.defaultExpression = defaultExpression; + this.defaultValue = null; + } + public String getDefaultValue() { - return defaultValue; + return defaultExpression == null ? defaultValue : defaultExpression.toString(); + } + + public void appendTo(StringBuilder sql, Consumer expressionPrinter) { + sql.append(columnName).append(" SET DEFAULT "); + if (defaultExpression == null) { + sql.append(defaultValue); + } else { + expressionPrinter.accept(defaultExpression); + } } @Override public String toString() { - return columnName + " SET DEFAULT " + defaultValue; + StringBuilder sql = new StringBuilder(); + appendTo(sql, sql::append); + return sql.toString(); } } diff --git a/src/main/java/net/sf/jsqlparser/statement/alter/AlterExpressionPartition.java b/src/main/java/net/sf/jsqlparser/statement/alter/AlterExpressionPartition.java index dd9e37b6fe..56e21e3e07 100644 --- a/src/main/java/net/sf/jsqlparser/statement/alter/AlterExpressionPartition.java +++ b/src/main/java/net/sf/jsqlparser/statement/alter/AlterExpressionPartition.java @@ -9,15 +9,344 @@ */ package net.sf.jsqlparser.statement.alter; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import java.util.function.Consumer; +import net.sf.jsqlparser.expression.Expression; +import net.sf.jsqlparser.schema.Table; +import net.sf.jsqlparser.statement.create.table.PartitionBound; +import net.sf.jsqlparser.statement.create.table.PartitionDefinition; +import net.sf.jsqlparser.statement.create.table.TablePartitioning; +import net.sf.jsqlparser.statement.select.PlainSelect; + /** - * Internal subclass for partition maintenance operations within ALTER TABLE. Handles TRUNCATE, - * COALESCE, REORGANIZE, EXCHANGE, ANALYZE, CHECK, OPTIMIZE, REBUILD, REPAIR PARTITION, PARTITION - * BY, and REMOVE PARTITIONING. + * Structured model for partition operations within {@code ALTER TABLE}. + * + *

+ * MySQL partition names and definitions remain available through the inherited + * {@link #getPartitions()} and {@link #getPartitionDefinitions()} methods. This subtype adds + * structured access to the complete {@code PARTITION BY} clause, the table used by + * {@code EXCHANGE PARTITION}, and its validation mode. It also models PostgreSQL + * {@code ATTACH/DETACH PARTITION} operations. */ public class AlterExpressionPartition extends AlterExpression { + public enum DetachMode { + CONCURRENTLY, FINALIZE + } + + public enum ExchangeValidationMode { + WITH, WITHOUT + } + + private Table partitionTable; + private PartitionBound partitionBound; + private DetachMode detachMode; + private TablePartitioning partitioning; + private Integer coalescePartitionCount; + private Table exchangeTable; + private ExchangeValidationMode exchangeValidationMode; + + public Table getPartitionTable() { + return partitionTable; + } + + public void setPartitionTable(Table partitionTable) { + this.partitionTable = partitionTable; + } + + public PartitionBound getPartitionBound() { + return partitionBound; + } + + public void setPartitionBound(PartitionBound partitionBound) { + this.partitionBound = partitionBound; + } + + public DetachMode getDetachMode() { + return detachMode; + } + + public void setDetachMode(DetachMode detachMode) { + this.detachMode = detachMode; + } + + /** + * Returns the full MySQL partitioning clause for a {@link AlterOperation#PARTITION_BY} + * operation. + */ + public TablePartitioning getPartitioning() { + return partitioning; + } + + public void setPartitioning(TablePartitioning partitioning) { + this.partitioning = partitioning; + if (partitioning != null) { + setPartitionType(partitioning.getType() != null + ? partitioning.getType().toString() + : null); + setPartitionExpression(partitioning.getExpression() != null + ? partitioning.getExpression() + : partitioning.getExpressionList()); + if (partitioning.getColumns() != null) { + List partitionColumns = new ArrayList<>(); + partitioning.getColumns().forEach( + column -> partitionColumns.add(column.getFullyQualifiedName())); + setPartitionColumns(partitionColumns); + } else { + setPartitionColumns(null); + } + setPartitionDefinitions(partitioning.getPartitionDefinitions()); + } + } + + /** + * Convenience alias that names the inherited MySQL partition-name list explicitly. + */ + public List getPartitionNames() { + return getPartitions(); + } + + public void setPartitionNames(List partitionNames) { + setPartitions(partitionNames); + } + + /** + * Returns whether the operation targets every partition rather than named partitions. + */ + public boolean isAllPartitions() { + List partitionNames = getPartitionNames(); + return partitionNames != null && partitionNames.size() == 1 + && "ALL".equalsIgnoreCase(partitionNames.get(0)); + } + + public void setAllPartitions(boolean allPartitions) { + if (allPartitions) { + setPartitionNames(Collections.singletonList("ALL")); + } else if (isAllPartitions()) { + setPartitionNames(null); + } + } + + /** + * Returns the number of partitions added by a MySQL {@code COALESCE PARTITION} operation. + * Unlike the legacy primitive accessor, this is {@code null} when no count was supplied. + */ + public Integer getCoalescePartitionCount() { + return coalescePartitionCount; + } + + public void setCoalescePartitionCount(Integer coalescePartitionCount) { + this.coalescePartitionCount = coalescePartitionCount; + if (coalescePartitionCount != null) { + super.setCoalescePartitionNumber(coalescePartitionCount); + } + } + + @Override + public void setCoalescePartitionNumber(int coalescePartitionNumber) { + super.setCoalescePartitionNumber(coalescePartitionNumber); + this.coalescePartitionCount = coalescePartitionNumber; + } + + public Table getExchangeTable() { + return exchangeTable; + } + + public void setExchangeTable(Table exchangeTable) { + this.exchangeTable = exchangeTable; + super.setExchangePartitionTableName( + exchangeTable != null ? exchangeTable.getFullyQualifiedName() : null); + } + + @Override + public void setExchangePartitionTableName(String exchangePartitionTableName) { + super.setExchangePartitionTableName(exchangePartitionTableName); + this.exchangeTable = exchangePartitionTableName != null + ? new Table(exchangePartitionTableName) + : null; + } + + public ExchangeValidationMode getExchangeValidationMode() { + return exchangeValidationMode; + } + + public void setExchangeValidationMode(ExchangeValidationMode exchangeValidationMode) { + this.exchangeValidationMode = exchangeValidationMode; + super.setExchangePartitionWithValidation( + exchangeValidationMode == ExchangeValidationMode.WITH); + super.setExchangePartitionWithoutValidation( + exchangeValidationMode == ExchangeValidationMode.WITHOUT); + } + + @Override + public void setExchangePartitionWithValidation(boolean exchangePartitionWithValidation) { + super.setExchangePartitionWithValidation(exchangePartitionWithValidation); + if (exchangePartitionWithValidation) { + exchangeValidationMode = ExchangeValidationMode.WITH; + super.setExchangePartitionWithoutValidation(false); + } else if (exchangeValidationMode == ExchangeValidationMode.WITH) { + exchangeValidationMode = null; + } + } + + @Override + public void setExchangePartitionWithoutValidation(boolean exchangePartitionWithoutValidation) { + super.setExchangePartitionWithoutValidation(exchangePartitionWithoutValidation); + if (exchangePartitionWithoutValidation) { + exchangeValidationMode = ExchangeValidationMode.WITHOUT; + super.setExchangePartitionWithValidation(false); + } else if (exchangeValidationMode == ExchangeValidationMode.WITHOUT) { + exchangeValidationMode = null; + } + } + + public AlterExpressionPartition withPartitionTable(Table partitionTable) { + setPartitionTable(partitionTable); + return this; + } + + public AlterExpressionPartition withPartitionBound(PartitionBound partitionBound) { + setPartitionBound(partitionBound); + return this; + } + + public AlterExpressionPartition withDetachMode(DetachMode detachMode) { + setDetachMode(detachMode); + return this; + } + + @Override + public AlterExpressionPartition withOperation(AlterOperation operation) { + setOperation(operation); + return this; + } + + public AlterExpressionPartition withPartitioning(TablePartitioning partitioning) { + setPartitioning(partitioning); + return this; + } + + public AlterExpressionPartition withPartitionNames(List partitionNames) { + setPartitionNames(partitionNames); + return this; + } + + public AlterExpressionPartition withAllPartitions(boolean allPartitions) { + setAllPartitions(allPartitions); + return this; + } + + public AlterExpressionPartition withCoalescePartitionCount(Integer coalescePartitionCount) { + setCoalescePartitionCount(coalescePartitionCount); + return this; + } + + public AlterExpressionPartition withExchangeTable(Table exchangeTable) { + setExchangeTable(exchangeTable); + return this; + } + + public AlterExpressionPartition withExchangeValidationMode( + ExchangeValidationMode exchangeValidationMode) { + setExchangeValidationMode(exchangeValidationMode); + return this; + } + + public AlterExpressionPartition withPartitionDefinitions( + List partitionDefinitions) { + setPartitionDefinitions(partitionDefinitions); + return this; + } + + public AlterExpressionPartition addPartitionNames(String... partitionNames) { + List collection = + Optional.ofNullable(getPartitionNames()).orElseGet(ArrayList::new); + Collections.addAll(collection, partitionNames); + return withPartitionNames(collection); + } + + public AlterExpressionPartition addPartitionNames(Collection partitionNames) { + List collection = + Optional.ofNullable(getPartitionNames()).orElseGet(ArrayList::new); + collection.addAll(partitionNames); + return withPartitionNames(collection); + } + + public AlterExpressionPartition addPartitionDefinitions( + PartitionDefinition... partitionDefinitions) { + List collection = Optional.ofNullable(getPartitionDefinitions()) + .orElseGet(ArrayList::new); + Collections.addAll(collection, partitionDefinitions); + return withPartitionDefinitions(collection); + } + + public AlterExpressionPartition addPartitionDefinitions( + Collection partitionDefinitions) { + List collection = Optional.ofNullable(getPartitionDefinitions()) + .orElseGet(ArrayList::new); + collection.addAll(partitionDefinitions); + return withPartitionDefinitions(collection); + } + @Override protected void appendBody(StringBuilder b) { - toStringPartition(b); + appendBody(b, b::append); + } + + public void appendTo(StringBuilder b, Consumer expressionPrinter) { + appendBody(b, expressionPrinter); + appendCommonTail(b); + } + + private void appendBody(StringBuilder b, Consumer expressionPrinter) { + switch (getOperation()) { + case ADD_PARTITION: + b.append("ADD PARTITION ") + .append(PlainSelect.getStringList(getPartitionDefinitions(), true, true)); + break; + case DROP_PARTITION: + b.append("DROP PARTITION ") + .append(PlainSelect.getStringList(getPartitionNames())); + break; + case ATTACH_PARTITION: + b.append("ATTACH PARTITION ").append(partitionTable).append(' '); + if (partitionBound == null) { + b.append("null"); + } else { + partitionBound.appendTo(b, expressionPrinter); + } + break; + case DETACH_PARTITION: + b.append("DETACH PARTITION ").append(partitionTable); + if (detachMode != null) { + b.append(" ").append(detachMode); + } + break; + case PARTITION_BY: + if (partitioning != null) { + partitioning.appendTo(b, expressionPrinter); + } else { + toStringPartition(b); + } + break; + case EXCHANGE_PARTITION: + if (exchangeTable != null) { + b.append("EXCHANGE PARTITION ").append(getPartitionNames().get(0)) + .append(" WITH TABLE ").append(exchangeTable); + if (exchangeValidationMode != null) { + b.append(" ").append(exchangeValidationMode).append(" VALIDATION"); + } + } else { + toStringPartition(b); + } + break; + default: + toStringPartition(b); + break; + } } } diff --git a/src/main/java/net/sf/jsqlparser/statement/alter/AlterExpressionPrimaryKey.java b/src/main/java/net/sf/jsqlparser/statement/alter/AlterExpressionPrimaryKey.java new file mode 100644 index 0000000000..7121fd3cd3 --- /dev/null +++ b/src/main/java/net/sf/jsqlparser/statement/alter/AlterExpressionPrimaryKey.java @@ -0,0 +1,99 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.statement.alter; + +import java.util.Iterator; +import java.util.function.Consumer; +import net.sf.jsqlparser.expression.Expression; +import net.sf.jsqlparser.statement.create.table.Index; + +/** + * CockroachDB's ALTER PRIMARY KEY USING COLUMNS operation. Key elements and storage options are + * available through {@link #getIndex()}; hash sharding and the legacy WITH BUCKET_COUNT expression + * are represented separately. + */ +public class AlterExpressionPrimaryKey extends AlterExpression { + private boolean usingHash; + private Expression bucketCount; + + public AlterExpressionPrimaryKey() { + setOperation(AlterOperation.ALTER_PRIMARY_KEY); + setIndex(new Index().withType("PRIMARY KEY")); + } + + public boolean isUsingHash() { + return usingHash; + } + + public void setUsingHash(boolean usingHash) { + this.usingHash = usingHash; + } + + public Expression getBucketCount() { + return bucketCount; + } + + public void setBucketCount(Expression bucketCount) { + this.bucketCount = bucketCount; + } + + @Override + protected void appendBody(StringBuilder builder) { + appendDefinition(builder, expression -> builder.append(expression)); + } + + /** Shares statement rendering while preserving expression visitor customization. */ + public StringBuilder appendTo(StringBuilder builder, Consumer expressionPrinter) { + appendDefinition(builder, expressionPrinter); + appendCommonTail(builder); + return builder; + } + + private void appendDefinition(StringBuilder builder, Consumer expressionPrinter) { + builder.append("ALTER PRIMARY KEY USING COLUMNS ("); + if (getIndex().getColumns() != null) { + for (Iterator columns = getIndex().getColumns().iterator(); columns + .hasNext();) { + columns.next().appendTo(builder, expressionPrinter); + if (columns.hasNext()) { + builder.append(", "); + } + } + } + builder.append(')'); + appendSharding(builder, expressionPrinter); + appendStorageOptions(builder, expressionPrinter); + } + + private void appendSharding(StringBuilder builder, Consumer expressionPrinter) { + if (usingHash) { + builder.append(" USING HASH"); + if (bucketCount != null) { + builder.append(" WITH BUCKET_COUNT = "); + expressionPrinter.accept(bucketCount); + } + } + } + + private void appendStorageOptions(StringBuilder builder, + Consumer expressionPrinter) { + if (getIndex().getStorageParameters() != null) { + builder.append(" WITH ("); + for (Iterator options = + getIndex().getStorageParameters().iterator(); options.hasNext();) { + options.next().appendTo(builder, expressionPrinter); + if (options.hasNext()) { + builder.append(", "); + } + } + builder.append(')'); + } + } +} diff --git a/src/main/java/net/sf/jsqlparser/statement/alter/AlterExpressionRename.java b/src/main/java/net/sf/jsqlparser/statement/alter/AlterExpressionRename.java index 703e20047a..3fa50fe737 100644 --- a/src/main/java/net/sf/jsqlparser/statement/alter/AlterExpressionRename.java +++ b/src/main/java/net/sf/jsqlparser/statement/alter/AlterExpressionRename.java @@ -26,7 +26,11 @@ protected void appendBody(StringBuilder b) { b.append(getColumnOldName()).append(" TO ").append(getColumnName()); break; case RENAME_TABLE: - b.append("RENAME TO ").append(getNewTableName()); + b.append("RENAME"); + if (getTableRenameKeyword() != TableRenameKeyword.NONE) { + b.append(" ").append(getTableRenameKeyword()); + } + b.append(" ").append(getNewTableName()); break; case RENAME_INDEX: case RENAME_KEY: diff --git a/src/main/java/net/sf/jsqlparser/statement/alter/AlterExtension.java b/src/main/java/net/sf/jsqlparser/statement/alter/AlterExtension.java new file mode 100644 index 0000000000..0affa5f43e --- /dev/null +++ b/src/main/java/net/sf/jsqlparser/statement/alter/AlterExtension.java @@ -0,0 +1,95 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.statement.alter; + +import net.sf.jsqlparser.expression.Expression; +import net.sf.jsqlparser.statement.create.extension.ExtensionObject; +import net.sf.jsqlparser.statement.Statement; +import net.sf.jsqlparser.statement.StatementVisitor; + +public class AlterExtension implements Statement { + private String name; + private Action action; + private Expression version; + private String schema; + private ExtensionObject member; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Action getAction() { + return action; + } + + public void setAction(Action action) { + this.action = action; + } + + public Expression getVersion() { + return version; + } + + public void setVersion(Expression version) { + this.version = version; + } + + public String getSchema() { + return schema; + } + + public void setSchema(String schema) { + this.schema = schema; + } + + public ExtensionObject getMember() { + return member; + } + + public void setMember(ExtensionObject member) { + this.member = member; + } + + @Override + public T accept(StatementVisitor visitor, S context) { + return visitor.visit(this, context); + } + + public enum Action { + UPDATE, SET_SCHEMA, ADD, DROP + } + + @Override + public String toString() { + StringBuilder sql = new StringBuilder("ALTER EXTENSION ").append(name).append(' '); + switch (action) { + case UPDATE: + sql.append("UPDATE"); + if (version != null) { + sql.append(" TO ").append(version); + } + break; + case SET_SCHEMA: + sql.append("SET SCHEMA ").append(schema); + break; + case ADD: + case DROP: + sql.append(action).append(' ').append(member); + break; + default: + throw new IllegalStateException("Unknown extension alteration: " + action); + } + return sql.toString(); + } +} diff --git a/src/main/java/net/sf/jsqlparser/statement/alter/AlterOperation.java b/src/main/java/net/sf/jsqlparser/statement/alter/AlterOperation.java index a33a98536b..52bdc23ce7 100644 --- a/src/main/java/net/sf/jsqlparser/statement/alter/AlterOperation.java +++ b/src/main/java/net/sf/jsqlparser/statement/alter/AlterOperation.java @@ -12,7 +12,7 @@ import java.util.Locale; public enum AlterOperation { - ADD, ALTER, DROP, DROP_PRIMARY_KEY, DROP_UNIQUE, DROP_FOREIGN_KEY, MODIFY, CHANGE, CONVERT, COLLATE, ALGORITHM, RENAME, RENAME_TABLE, RENAME_INDEX, RENAME_KEY, RENAME_CONSTRAINT, COMMENT, COMMENT_WITH_EQUAL_SIGN, UNSPECIFIC, ADD_PARTITION, DROP_PARTITION, DISCARD_PARTITION, IMPORT_PARTITION, TRUNCATE_PARTITION, COALESCE_PARTITION, REORGANIZE_PARTITION, EXCHANGE_PARTITION, ANALYZE_PARTITION, CHECK_PARTITION, OPTIMIZE_PARTITION, REBUILD_PARTITION, REPAIR_PARTITION, REMOVE_PARTITIONING, PARTITION_BY, SET_TABLE_OPTION, ENGINE, FORCE, KEY_BLOCK_SIZE, LOCK, DISCARD_TABLESPACE, IMPORT_TABLESPACE, DISABLE_KEYS, ENABLE_KEYS, ENABLE_ROW_LEVEL_SECURITY, DISABLE_ROW_LEVEL_SECURITY, FORCE_ROW_LEVEL_SECURITY, NO_FORCE_ROW_LEVEL_SECURITY; + ADD, ALTER, DROP, DROP_PRIMARY_KEY, DROP_UNIQUE, DROP_FOREIGN_KEY, MODIFY, CHANGE, CONVERT, COLLATE, ALGORITHM, RENAME, RENAME_TABLE, RENAME_INDEX, RENAME_KEY, RENAME_CONSTRAINT, COMMENT, COMMENT_WITH_EQUAL_SIGN, UNSPECIFIC, ADD_PARTITION, DROP_PARTITION, ATTACH_PARTITION, DETACH_PARTITION, DISCARD_PARTITION, IMPORT_PARTITION, TRUNCATE_PARTITION, COALESCE_PARTITION, REORGANIZE_PARTITION, EXCHANGE_PARTITION, ANALYZE_PARTITION, CHECK_PARTITION, OPTIMIZE_PARTITION, REBUILD_PARTITION, REPAIR_PARTITION, REMOVE_PARTITIONING, PARTITION_BY, SET_TABLE_OPTION, ENGINE, FORCE, KEY_BLOCK_SIZE, LOCK, DISCARD_TABLESPACE, IMPORT_TABLESPACE, DISABLE_KEYS, ENABLE_KEYS, ENABLE_ROW_LEVEL_SECURITY, DISABLE_ROW_LEVEL_SECURITY, FORCE_ROW_LEVEL_SECURITY, NO_FORCE_ROW_LEVEL_SECURITY, ALTER_PRIMARY_KEY; public static AlterOperation from(String operation) { return Enum.valueOf(AlterOperation.class, operation.toUpperCase(Locale.ROOT)); diff --git a/src/main/java/net/sf/jsqlparser/statement/alter/AlterPublication.java b/src/main/java/net/sf/jsqlparser/statement/alter/AlterPublication.java new file mode 100644 index 0000000000..6216403954 --- /dev/null +++ b/src/main/java/net/sf/jsqlparser/statement/alter/AlterPublication.java @@ -0,0 +1,118 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.statement.alter; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; +import net.sf.jsqlparser.expression.Expression; +import net.sf.jsqlparser.statement.create.publication.PublicationTarget; +import net.sf.jsqlparser.statement.create.publication.PublicationOption; +import net.sf.jsqlparser.statement.Statement; +import net.sf.jsqlparser.statement.StatementVisitor; + +public class AlterPublication implements Statement { + private String name; + private Action action; + private List targets = new ArrayList<>(); + private List options = new ArrayList<>(); + private String newName; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Action getAction() { + return action; + } + + public void setAction(Action action) { + this.action = action; + } + + public List getTargets() { + return targets; + } + + public void setTargets(List targets) { + this.targets = targets; + } + + public List getOptions() { + return options; + } + + public void setOptions(List options) { + this.options = options; + } + + public String getNewName() { + return newName; + } + + public void setNewName(String newName) { + this.newName = newName; + } + + @Override + public T accept(StatementVisitor visitor, S context) { + return visitor.visit(this, context); + } + + public enum Action { + ADD, SET, DROP, SET_OPTIONS, OWNER, RENAME + } + + public void appendTo(StringBuilder sql, Consumer expressions) { + sql.append("ALTER PUBLICATION ").append(name).append(' '); + switch (action) { + case OWNER: + sql.append("OWNER TO ").append(newName); + break; + case RENAME: + sql.append("RENAME TO ").append(newName); + break; + case SET_OPTIONS: + sql.append("SET ("); + for (int i = 0; i < options.size(); i++) { + if (i > 0) { + sql.append(", "); + } + options.get(i).appendTo(sql, expressions); + } + sql.append(')'); + break; + case ADD: + case SET: + case DROP: + sql.append(action).append(' '); + for (int i = 0; i < targets.size(); i++) { + if (i > 0) { + sql.append(", "); + } + targets.get(i).appendTo(sql, expressions); + } + break; + default: + throw new IllegalStateException("Unknown publication alteration: " + action); + } + } + + @Override + public String toString() { + StringBuilder sql = new StringBuilder(); + appendTo(sql, sql::append); + return sql.toString(); + } +} diff --git a/src/main/java/net/sf/jsqlparser/statement/alter/AlterSubscription.java b/src/main/java/net/sf/jsqlparser/statement/alter/AlterSubscription.java new file mode 100644 index 0000000000..486ebb2d33 --- /dev/null +++ b/src/main/java/net/sf/jsqlparser/statement/alter/AlterSubscription.java @@ -0,0 +1,127 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.statement.alter; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; +import net.sf.jsqlparser.expression.Expression; +import net.sf.jsqlparser.expression.StringValue; +import net.sf.jsqlparser.statement.create.subscription.SubscriptionOption; +import net.sf.jsqlparser.statement.Statement; +import net.sf.jsqlparser.statement.StatementVisitor; + +public class AlterSubscription implements Statement { + private String name; + private Action action; + private StringValue connection; + private List publications = new ArrayList<>(); + private List options = new ArrayList<>(); + private String newName; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Action getAction() { + return action; + } + + public void setAction(Action action) { + this.action = action; + } + + public StringValue getConnection() { + return connection; + } + + public void setConnection(StringValue connection) { + this.connection = connection; + } + + public List getPublications() { + return publications; + } + + public void setPublications(List publications) { + this.publications = publications; + } + + public List getOptions() { + return options; + } + + public void setOptions(List options) { + this.options = options; + } + + public String getNewName() { + return newName; + } + + public void setNewName(String newName) { + this.newName = newName; + } + + @Override + public T accept(StatementVisitor visitor, S context) { + return visitor.visit(this, context); + } + + public enum Action { + CONNECTION, SET_PUBLICATION, ADD_PUBLICATION, DROP_PUBLICATION, REFRESH_PUBLICATION, ENABLE, DISABLE, SET_OPTIONS, SKIP, OWNER, RENAME + } + + public void appendTo(StringBuilder sql, Consumer expressions) { + sql.append("ALTER SUBSCRIPTION ").append(name).append(' '); + switch (action) { + case CONNECTION: + sql.append("CONNECTION "); + expressions.accept(connection); + break; + case OWNER: + sql.append("OWNER TO ").append(newName); + break; + case RENAME: + sql.append("RENAME TO ").append(newName); + break; + case SET_OPTIONS: + sql.append("SET"); + break; + default: + sql.append(action.name().replace('_', ' ')); + if (!publications.isEmpty()) { + sql.append(' ').append(String.join(", ", publications)); + } + break; + } + if (!options.isEmpty()) { + sql.append(action == Action.SET_OPTIONS || action == Action.SKIP ? " (" : " WITH ("); + for (int i = 0; i < options.size(); i++) { + if (i > 0) { + sql.append(", "); + } + options.get(i).appendTo(sql, expressions); + } + sql.append(')'); + } + } + + @Override + public String toString() { + StringBuilder sql = new StringBuilder(); + appendTo(sql, sql::append); + return sql.toString(); + } +} diff --git a/src/main/java/net/sf/jsqlparser/statement/alter/AlterType.java b/src/main/java/net/sf/jsqlparser/statement/alter/AlterType.java new file mode 100644 index 0000000000..f49936d41e --- /dev/null +++ b/src/main/java/net/sf/jsqlparser/statement/alter/AlterType.java @@ -0,0 +1,247 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.statement.alter; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; +import net.sf.jsqlparser.expression.StringValue; +import net.sf.jsqlparser.statement.create.type.TypeAttribute; +import net.sf.jsqlparser.statement.Statement; +import net.sf.jsqlparser.statement.StatementVisitor; + +public class AlterType implements Statement { + private String name; + private Action action; + private String newName; + private String attributeName; + private StringValue value; + private StringValue newValue; + private StringValue neighborValue; + private Position position; + private boolean ifNotExists; + private Behavior behavior; + private List attributeChanges = new ArrayList<>(); + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Action getAction() { + return action; + } + + public void setAction(Action action) { + this.action = action; + } + + public String getNewName() { + return newName; + } + + public void setNewName(String newName) { + this.newName = newName; + } + + public String getAttributeName() { + return attributeName; + } + + public void setAttributeName(String attributeName) { + this.attributeName = attributeName; + } + + public StringValue getValue() { + return value; + } + + public void setValue(StringValue value) { + this.value = value; + } + + public StringValue getNewValue() { + return newValue; + } + + public void setNewValue(StringValue newValue) { + this.newValue = newValue; + } + + public StringValue getNeighborValue() { + return neighborValue; + } + + public void setNeighborValue(StringValue neighborValue) { + this.neighborValue = neighborValue; + } + + public Position getPosition() { + return position; + } + + public void setPosition(Position position) { + this.position = position; + } + + public boolean isIfNotExists() { + return ifNotExists; + } + + public void setIfNotExists(boolean ifNotExists) { + this.ifNotExists = ifNotExists; + } + + public Behavior getBehavior() { + return behavior; + } + + public void setBehavior(Behavior behavior) { + this.behavior = behavior; + } + + public List getAttributeChanges() { + return attributeChanges; + } + + public void setAttributeChanges(List attributeChanges) { + this.attributeChanges = attributeChanges; + } + + @Override + public T accept(StatementVisitor visitor, S context) { + return visitor.visit(this, context); + } + + public enum Action { + OWNER, RENAME, SET_SCHEMA, RENAME_ATTRIBUTE, ADD_VALUE, RENAME_VALUE, ATTRIBUTES + } + public enum Position { + BEFORE, AFTER + } + public enum Behavior { + CASCADE, RESTRICT + } + public static class AttributeChange implements java.io.Serializable { + public enum Kind { + ADD, DROP, ALTER + } + + private Kind kind; + private TypeAttribute attribute; + private boolean ifExists; + private boolean useSetData; + private Behavior behavior; + + public Kind getKind() { + return kind; + } + + public void setKind(Kind kind) { + this.kind = kind; + } + + public TypeAttribute getAttribute() { + return attribute; + } + + public void setAttribute(TypeAttribute attribute) { + this.attribute = attribute; + } + + public boolean isIfExists() { + return ifExists; + } + + public void setIfExists(boolean ifExists) { + this.ifExists = ifExists; + } + + public boolean isUseSetData() { + return useSetData; + } + + public void setUseSetData(boolean useSetData) { + this.useSetData = useSetData; + } + + public Behavior getBehavior() { + return behavior; + } + + public void setBehavior(Behavior behavior) { + this.behavior = behavior; + } + + @Override + public String toString() { + StringBuilder sql = new StringBuilder().append(kind).append(" ATTRIBUTE "); + if (ifExists) { + sql.append("IF EXISTS "); + } + sql.append(attribute.getName()); + if (kind == Kind.ALTER) { + sql.append(useSetData ? " SET DATA TYPE" : " TYPE"); + } + if (kind != Kind.DROP) { + sql.append(' ').append(attribute.getDataType()); + if (attribute.getCollation() != null) { + sql.append(" COLLATE ").append(attribute.getCollation()); + } + } + if (behavior != null) { + sql.append(' ').append(behavior); + } + return sql.toString(); + } + } + + @Override + public String toString() { + StringBuilder sql = new StringBuilder("ALTER TYPE ").append(name).append(' '); + switch (action) { + case OWNER: + sql.append("OWNER TO ").append(newName); + break; + case RENAME: + sql.append("RENAME TO ").append(newName); + break; + case SET_SCHEMA: + sql.append("SET SCHEMA ").append(newName); + break; + case RENAME_ATTRIBUTE: + sql.append("RENAME ATTRIBUTE ").append(attributeName).append(" TO ") + .append(newName); + if (behavior != null) { + sql.append(' ').append(behavior); + } + break; + case ADD_VALUE: + sql.append("ADD VALUE ").append(ifNotExists ? "IF NOT EXISTS " : "").append(value); + if (position != null) { + sql.append(' ').append(position).append(' ').append(neighborValue); + } + break; + case RENAME_VALUE: + sql.append("RENAME VALUE ").append(value).append(" TO ").append(newValue); + break; + case ATTRIBUTES: + sql.append(attributeChanges.stream().map(Object::toString) + .collect(Collectors.joining(", "))); + break; + default: + throw new IllegalStateException("Unknown type alteration: " + action); + } + return sql.toString(); + } +} diff --git a/src/main/java/net/sf/jsqlparser/statement/alter/IdentityAlteration.java b/src/main/java/net/sf/jsqlparser/statement/alter/IdentityAlteration.java new file mode 100644 index 0000000000..fdc8949e65 --- /dev/null +++ b/src/main/java/net/sf/jsqlparser/statement/alter/IdentityAlteration.java @@ -0,0 +1,100 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.statement.alter; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; +import net.sf.jsqlparser.schema.Sequence; +import net.sf.jsqlparser.statement.create.table.IdentityDefinition; +import net.sf.jsqlparser.statement.select.PlainSelect; + +/** An action on an identity column, as opposed to a change of its SQL data type. */ +public class IdentityAlteration implements Serializable { + public enum Kind { + ADD, SET_GENERATED, SET_PARAMETER, RESTART, DROP + } + + private final Kind kind; + private IdentityDefinition identityDefinition; + private IdentityDefinition.GenerationMode generationMode; + private List parameters; + private Long restartWith; + private boolean ifExists; + + public IdentityAlteration(Kind kind) { + this.kind = kind; + } + + public Kind getKind() { + return kind; + } + + public IdentityDefinition getIdentityDefinition() { + return identityDefinition; + } + + public void setIdentityDefinition(IdentityDefinition identityDefinition) { + this.identityDefinition = identityDefinition; + } + + public IdentityDefinition.GenerationMode getGenerationMode() { + return generationMode; + } + + public void setGenerationMode(IdentityDefinition.GenerationMode generationMode) { + this.generationMode = generationMode; + } + + public List getParameters() { + return parameters; + } + + public void setParameters(List parameters) { + this.parameters = parameters == null ? null : new ArrayList<>(parameters); + } + + public Long getRestartWith() { + return restartWith; + } + + public void setRestartWith(Long restartWith) { + this.restartWith = restartWith; + } + + public boolean isIfExists() { + return ifExists; + } + + public void setIfExists(boolean ifExists) { + this.ifExists = ifExists; + } + + @Override + public String toString() { + switch (kind) { + case ADD: + return "ADD " + identityDefinition; + case SET_GENERATED: + return "SET GENERATED " + + (generationMode == IdentityDefinition.GenerationMode.ALWAYS + ? "ALWAYS" + : "BY DEFAULT"); + case SET_PARAMETER: + return "SET " + PlainSelect.getStringList(parameters, false, false); + case RESTART: + return "RESTART" + (restartWith == null ? "" : " WITH " + restartWith); + case DROP: + return "DROP IDENTITY" + (ifExists ? " IF EXISTS" : ""); + default: + throw new IllegalStateException("Unknown identity action: " + kind); + } + } +} diff --git a/src/main/java/net/sf/jsqlparser/statement/analyze/Analyze.java b/src/main/java/net/sf/jsqlparser/statement/analyze/Analyze.java index ab25cf3b40..558de06ad6 100644 --- a/src/main/java/net/sf/jsqlparser/statement/analyze/Analyze.java +++ b/src/main/java/net/sf/jsqlparser/statement/analyze/Analyze.java @@ -9,13 +9,32 @@ */ package net.sf.jsqlparser.statement.analyze; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; +import net.sf.jsqlparser.schema.Column; import net.sf.jsqlparser.schema.Table; import net.sf.jsqlparser.statement.Statement; import net.sf.jsqlparser.statement.StatementVisitor; public class Analyze implements Statement { - private Table table; + public enum Modifier { + NO_WRITE_TO_BINLOG, LOCAL + } + + public enum HistogramAction { + UPDATE, DROP + } + + private final List

tables = new ArrayList<>(); + private boolean tableKeyword; + private Modifier modifier; + private HistogramAction histogramAction; + private final List histogramColumns = new ArrayList<>(); + private Integer histogramBucketCount; @Override public T accept(StatementVisitor statementVisitor, S context) { @@ -23,20 +42,134 @@ public T accept(StatementVisitor statementVisitor, S context) { } public Table getTable() { - return table; + return tables.isEmpty() ? null : tables.get(0); } public void setTable(Table table) { - this.table = table; + tables.clear(); + if (table != null) { + tables.add(table); + } + } + + public List
getTables() { + return Collections.unmodifiableList(tables); + } + + public void setTables(Collection tables) { + this.tables.clear(); + if (tables != null) { + this.tables.addAll(tables); + } + } + + public boolean isTableKeyword() { + return tableKeyword; + } + + public void setTableKeyword(boolean tableKeyword) { + this.tableKeyword = tableKeyword; + } + + public Modifier getModifier() { + return modifier; + } + + public void setModifier(Modifier modifier) { + this.modifier = modifier; + } + + public HistogramAction getHistogramAction() { + return histogramAction; + } + + public void setHistogramAction(HistogramAction histogramAction) { + this.histogramAction = histogramAction; + } + + public List getHistogramColumns() { + return Collections.unmodifiableList(histogramColumns); + } + + public void setHistogramColumns(Collection columns) { + histogramColumns.clear(); + if (columns != null) { + histogramColumns.addAll(columns); + } + } + + public Integer getHistogramBucketCount() { + return histogramBucketCount; + } + + public void setHistogramBucketCount(Integer histogramBucketCount) { + this.histogramBucketCount = histogramBucketCount; } @Override public String toString() { - return "ANALYZE " + table.toString(); + StringBuilder builder = new StringBuilder("ANALYZE"); + if (modifier != null) { + builder.append(" ").append(modifier); + } + if (tableKeyword) { + builder.append(" TABLE"); + } + builder.append(" ").append(tables.stream().map(Table::toString) + .collect(Collectors.joining(", "))); + if (histogramAction != null) { + builder.append(" ").append(histogramAction).append(" HISTOGRAM ON ") + .append(histogramColumns.stream().map(Column::toString) + .collect(Collectors.joining(", "))); + if (histogramBucketCount != null) { + builder.append(" WITH ").append(histogramBucketCount).append(" BUCKETS"); + } + } + return builder.toString(); } public Analyze withTable(Table table) { this.setTable(table); return this; } + + public Analyze withTables(Collection tables) { + setTables(tables); + return this; + } + + public Analyze addTables(Table... tables) { + Collections.addAll(this.tables, tables); + return this; + } + + public Analyze withTableKeyword(boolean tableKeyword) { + setTableKeyword(tableKeyword); + return this; + } + + public Analyze withModifier(Modifier modifier) { + setModifier(modifier); + return this; + } + + public Analyze withHistogramAction(HistogramAction histogramAction) { + setHistogramAction(histogramAction); + return this; + } + + public Analyze withHistogramColumns(Collection columns) { + setHistogramColumns(columns); + return this; + } + + public Analyze addHistogramColumns(Column... columns) { + Collections.addAll(histogramColumns, columns); + return this; + } + + public Analyze withHistogramBucketCount(Integer histogramBucketCount) { + setHistogramBucketCount(histogramBucketCount); + return this; + } } diff --git a/src/main/java/net/sf/jsqlparser/statement/comment/Comment.java b/src/main/java/net/sf/jsqlparser/statement/comment/Comment.java index d213aac6b8..1a5310be0c 100755 --- a/src/main/java/net/sf/jsqlparser/statement/comment/Comment.java +++ b/src/main/java/net/sf/jsqlparser/statement/comment/Comment.java @@ -9,6 +9,7 @@ */ package net.sf.jsqlparser.statement.comment; +import java.util.function.Consumer; import net.sf.jsqlparser.expression.StringValue; import net.sf.jsqlparser.schema.Column; import net.sf.jsqlparser.schema.Table; @@ -20,6 +21,7 @@ public class Comment implements Statement { private Table table; private Column column; private Table view; + private CommentTarget target; private StringValue comment; @Override @@ -33,6 +35,9 @@ public Table getTable() { public void setTable(Table table) { this.table = table; + if (table != null) { + target = null; + } } public Column getColumn() { @@ -41,6 +46,9 @@ public Column getColumn() { public void setColumn(Column column) { this.column = column; + if (column != null) { + target = null; + } } public Table getView() { @@ -49,6 +57,39 @@ public Table getView() { public void setView(Table view) { this.view = view; + if (view != null) { + target = null; + } + } + + /** Additional catalog targets; the existing table, column and view accessors remain intact. */ + public CommentTarget getTarget() { + return target; + } + + public void setTarget(CommentTarget target) { + this.target = target; + if (target != null) { + table = null; + column = null; + view = null; + } + } + + public Comment withTarget(CommentTarget target) { + setTarget(target); + return this; + } + + /** Visits the relation explicitly named by this comment, without resolving catalog objects. */ + public void visitRelations(Consumer
visitor) { + Table relation = table != null ? table + : column != null ? column.getTable() + : view != null ? view + : target != null ? target.getReferencedRelation() : null; + if (relation != null) { + visitor.accept(relation); + } } public StringValue getComment() { @@ -61,16 +102,37 @@ public void setComment(StringValue comment) { @Override public String toString() { - String sql = "COMMENT ON "; + StringBuilder builder = new StringBuilder(); + return appendTo(builder, builder::append, builder::append, builder::append).toString(); + } + + public StringBuilder appendTo(StringBuilder builder, Consumer
relationWriter, + Consumer columnWriter, Consumer commentWriter) { + builder.append("COMMENT ON "); if (table != null) { - sql += "TABLE " + table + " "; + builder.append("TABLE "); + relationWriter.accept(table); + builder.append(' '); } else if (column != null) { - sql += "COLUMN " + column + " "; + builder.append("COLUMN "); + columnWriter.accept(column); + builder.append(' '); } else if (view != null) { - sql += "VIEW " + view + " "; + builder.append("VIEW "); + relationWriter.accept(view); + builder.append(' '); + } else if (target != null) { + target.appendTo(builder, relationWriter); + builder.append(' '); + } + // a null comment stands for PostgreSQL's COMMENT ON ... IS NULL, which removes the comment + builder.append("IS "); + if (comment == null) { + builder.append("NULL"); + } else { + commentWriter.accept(comment); } - sql += "IS " + comment; - return sql; + return builder; } public Comment withTable(Table table) { diff --git a/src/main/java/net/sf/jsqlparser/statement/comment/CommentTarget.java b/src/main/java/net/sf/jsqlparser/statement/comment/CommentTarget.java new file mode 100644 index 0000000000..7269d29a60 --- /dev/null +++ b/src/main/java/net/sf/jsqlparser/statement/comment/CommentTarget.java @@ -0,0 +1,104 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.statement.comment; + +import java.io.Serializable; +import java.util.function.Consumer; +import net.sf.jsqlparser.schema.Table; +import net.sf.jsqlparser.statement.RoutineReference; + +/** A catalog object addressed by COMMENT, rather than a function invocation or a query. */ +public class CommentTarget implements Serializable { + public enum Kind { + INDEX, SCHEMA, SEQUENCE, DOMAIN, TYPE, MATERIALIZED_VIEW, FUNCTION, CONSTRAINT + } + + private Kind kind; + private Table name; + private RoutineReference routine; + private Table relation; + private boolean onDomain; + + public Kind getKind() { + return kind; + } + + public void setKind(Kind kind) { + this.kind = kind; + } + + /** The object's identifier; using Table preserves the individual name components. */ + public Table getName() { + return name; + } + + public void setName(Table name) { + this.name = name; + } + + public RoutineReference getRoutine() { + return routine; + } + + public void setRoutine(RoutineReference routine) { + this.routine = routine; + } + + /** The table or domain owning a constraint, distinguished by {@link #isOnDomain()}. */ + public Table getRelation() { + return relation; + } + + public void setRelation(Table relation) { + this.relation = relation; + } + + public boolean isOnDomain() { + return onDomain; + } + + public void setOnDomain(boolean onDomain) { + this.onDomain = onDomain; + } + + /** Returns only an explicitly named table/view, never an index, type, function or domain. */ + public Table getReferencedRelation() { + if (kind == Kind.MATERIALIZED_VIEW) { + return name; + } + return kind == Kind.CONSTRAINT && !onDomain ? relation : null; + } + + public StringBuilder appendTo(StringBuilder builder, Consumer
relationWriter) { + builder.append(kind.name().replace('_', ' ')).append(' '); + if (kind == Kind.FUNCTION) { + builder.append(routine); + } else if (kind == Kind.MATERIALIZED_VIEW) { + relationWriter.accept(name); + } else { + builder.append(name); + if (kind == Kind.CONSTRAINT) { + builder.append(" ON "); + if (onDomain) { + builder.append("DOMAIN ").append(relation); + } else { + relationWriter.accept(relation); + } + } + } + return builder; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + return appendTo(builder, builder::append).toString(); + } +} diff --git a/src/main/java/net/sf/jsqlparser/statement/create/database/CreateDatabase.java b/src/main/java/net/sf/jsqlparser/statement/create/database/CreateDatabase.java new file mode 100644 index 0000000000..e1fb9ea059 --- /dev/null +++ b/src/main/java/net/sf/jsqlparser/statement/create/database/CreateDatabase.java @@ -0,0 +1,146 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.statement.create.database; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +import net.sf.jsqlparser.statement.Statement; +import net.sf.jsqlparser.statement.StatementVisitor; + +/** + * A {@code CREATE DATABASE} statement. Modelled after MySQL, where the statement takes an optional + * {@code IF NOT EXISTS} and vendor-specific options, which are captured verbatim. + */ +public class CreateDatabase implements Statement { + + private String databaseName; + private boolean hasIfNotExists = false; + private List databaseOptions = null; + private List options; + + @Override + public T accept(StatementVisitor statementVisitor, S context) { + return statementVisitor.visit(this, context); + } + + /** + * The name of the database. + * + * @return the database name + */ + public String getDatabaseName() { + return databaseName; + } + + public CreateDatabase setDatabaseName(String databaseName) { + this.databaseName = databaseName; + return this; + } + + public boolean hasIfNotExists() { + return hasIfNotExists; + } + + public CreateDatabase setIfNotExists(boolean hasIfNotExists) { + this.hasIfNotExists = hasIfNotExists; + return this; + } + + /** + * The vendor-specific options of the database, captured as raw tokens. + * + * @return the list of option tokens, {@code null} when no options were given + */ + public List getDatabaseOptions() { + return databaseOptions; + } + + public CreateDatabase setDatabaseOptions(List databaseOptions) { + this.databaseOptions = databaseOptions; + this.options = null; + return this; + } + + public List getOptions() { + return options; + } + + public CreateDatabase setOptions(List options) { + this.options = options; + this.databaseOptions = options == null ? null + : options.stream().flatMap(option -> option.getTokens().stream()) + .collect(Collectors.toList()); + return this; + } + + public Optional getOption(DatabaseOption.Kind kind) { + return Optional.ofNullable(options).orElseGet(Collections::emptyList).stream() + .filter(option -> option.getKind() == kind).findFirst(); + } + + public CreateDatabase withDatabaseName(String databaseName) { + return this.setDatabaseName(databaseName); + } + + public CreateDatabase withIfNotExists(boolean hasIfNotExists) { + return this.setIfNotExists(hasIfNotExists); + } + + public CreateDatabase withDatabaseOptions(List databaseOptions) { + return this.setDatabaseOptions(databaseOptions); + } + + public CreateDatabase withOptions(List options) { + return setOptions(options); + } + + public CreateDatabase addOptions(DatabaseOption... options) { + List collection = + Optional.ofNullable(getOptions()).orElseGet(ArrayList::new); + Collections.addAll(collection, options); + return withOptions(collection); + } + + public CreateDatabase addDatabaseOptions(String... databaseOptions) { + List collection = + Optional.ofNullable(getDatabaseOptions()).orElseGet(ArrayList::new); + Collections.addAll(collection, databaseOptions); + return this.withDatabaseOptions(collection); + } + + public CreateDatabase addDatabaseOptions(Collection databaseOptions) { + List collection = + Optional.ofNullable(getDatabaseOptions()).orElseGet(ArrayList::new); + collection.addAll(databaseOptions); + return this.withDatabaseOptions(collection); + } + + @Override + public String toString() { + String sql = "CREATE DATABASE"; + if (hasIfNotExists) { + sql += " IF NOT EXISTS"; + } + if (databaseName != null) { + sql += " " + databaseName; + } + if (options != null && !options.isEmpty()) { + sql += " " + options.stream().map(Object::toString).collect(Collectors.joining(" ")); + } else if (databaseOptions != null && !databaseOptions.isEmpty()) { + sql += " " + String.join(" ", databaseOptions); + } + return sql; + } +} diff --git a/src/main/java/net/sf/jsqlparser/statement/create/database/DatabaseOption.java b/src/main/java/net/sf/jsqlparser/statement/create/database/DatabaseOption.java new file mode 100644 index 0000000000..75a9cabae7 --- /dev/null +++ b/src/main/java/net/sf/jsqlparser/statement/create/database/DatabaseOption.java @@ -0,0 +1,142 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.statement.create.database; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** A structured option shared by MySQL {@code CREATE DATABASE} and {@code CREATE SCHEMA}. */ +public class DatabaseOption implements Serializable { + + public enum Kind { + CHARACTER_SET, COLLATE, ENCRYPTION, OTHER + } + + private Kind kind = Kind.OTHER; + private String name; + private String value; + private boolean useDefault; + private boolean useEquals; + private List rawTokens; + + public DatabaseOption() {} + + public DatabaseOption(Kind kind, String name, String value, boolean useDefault, + boolean useEquals) { + this.kind = kind; + this.name = name; + this.value = value; + this.useDefault = useDefault; + this.useEquals = useEquals; + } + + public static DatabaseOption raw(List tokens) { + DatabaseOption option = new DatabaseOption(); + option.setRawTokens(tokens); + return option; + } + + public Kind getKind() { + return kind; + } + + public void setKind(Kind kind) { + this.kind = kind; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + public boolean isUseDefault() { + return useDefault; + } + + public void setUseDefault(boolean useDefault) { + this.useDefault = useDefault; + } + + public boolean isUseEquals() { + return useEquals; + } + + public void setUseEquals(boolean useEquals) { + this.useEquals = useEquals; + } + + public List getRawTokens() { + return rawTokens; + } + + public void setRawTokens(List rawTokens) { + this.rawTokens = rawTokens; + } + + /** Returns this option in the legacy flat-token representation. */ + public List getTokens() { + if (rawTokens != null) { + return Collections.unmodifiableList(rawTokens); + } + List tokens = new ArrayList<>(); + if (useDefault) { + tokens.add("DEFAULT"); + } + Collections.addAll(tokens, name.split(" ")); + if (useEquals) { + tokens.add("="); + } + tokens.add(value); + return tokens; + } + + @Override + public String toString() { + return String.join(" ", getTokens()); + } + + public DatabaseOption withKind(Kind kind) { + setKind(kind); + return this; + } + + public DatabaseOption withName(String name) { + setName(name); + return this; + } + + public DatabaseOption withValue(String value) { + setValue(value); + return this; + } + + public DatabaseOption withUseDefault(boolean useDefault) { + setUseDefault(useDefault); + return this; + } + + public DatabaseOption withUseEquals(boolean useEquals) { + setUseEquals(useEquals); + return this; + } +} diff --git a/src/main/java/net/sf/jsqlparser/statement/create/domain/CreateDomain.java b/src/main/java/net/sf/jsqlparser/statement/create/domain/CreateDomain.java new file mode 100644 index 0000000000..61da30eb1e --- /dev/null +++ b/src/main/java/net/sf/jsqlparser/statement/create/domain/CreateDomain.java @@ -0,0 +1,102 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.statement.create.domain; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; +import net.sf.jsqlparser.expression.Expression; +import net.sf.jsqlparser.statement.create.table.ColDataType; +import net.sf.jsqlparser.statement.Statement; +import net.sf.jsqlparser.statement.StatementVisitor; + +public class CreateDomain implements Statement { + private String name; + private boolean useAs; + private ColDataType dataType; + private String collation; + private Expression defaultExpression; + private List constraints = new ArrayList<>(); + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public boolean isUseAs() { + return useAs; + } + + public void setUseAs(boolean useAs) { + this.useAs = useAs; + } + + public ColDataType getDataType() { + return dataType; + } + + public void setDataType(ColDataType dataType) { + this.dataType = dataType; + } + + public String getCollation() { + return collation; + } + + public void setCollation(String collation) { + this.collation = collation; + } + + public Expression getDefaultExpression() { + return defaultExpression; + } + + public void setDefaultExpression(Expression defaultExpression) { + this.defaultExpression = defaultExpression; + } + + public List getConstraints() { + return constraints; + } + + public void setConstraints(List constraints) { + this.constraints = constraints; + } + + @Override + public T accept(StatementVisitor visitor, S context) { + return visitor.visit(this, context); + } + + public void appendTo(StringBuilder sql, Consumer expressions) { + sql.append("CREATE DOMAIN ").append(name).append(useAs ? " AS " : " ").append(dataType); + if (collation != null) { + sql.append(" COLLATE ").append(collation); + } + if (defaultExpression != null) { + sql.append(" DEFAULT "); + expressions.accept(defaultExpression); + } + for (DomainConstraint constraint : constraints) { + sql.append(' '); + constraint.appendTo(sql, expressions); + } + } + + @Override + public String toString() { + StringBuilder sql = new StringBuilder(); + appendTo(sql, sql::append); + return sql.toString(); + } +} diff --git a/src/main/java/net/sf/jsqlparser/statement/create/domain/DomainConstraint.java b/src/main/java/net/sf/jsqlparser/statement/create/domain/DomainConstraint.java new file mode 100644 index 0000000000..7b42733d72 --- /dev/null +++ b/src/main/java/net/sf/jsqlparser/statement/create/domain/DomainConstraint.java @@ -0,0 +1,68 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.statement.create.domain; + +import java.util.function.Consumer; +import net.sf.jsqlparser.expression.Expression; +import java.io.Serializable; + +public class DomainConstraint implements Serializable { + private String name; + private Kind kind; + private Expression expression; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Kind getKind() { + return kind; + } + + public void setKind(Kind kind) { + this.kind = kind; + } + + public Expression getExpression() { + return expression; + } + + public void setExpression(Expression expression) { + this.expression = expression; + } + + public enum Kind { + NULL, NOT_NULL, CHECK + } + + public void appendTo(StringBuilder sql, Consumer expressions) { + if (name != null) { + sql.append("CONSTRAINT ").append(name).append(' '); + } + if (kind == Kind.CHECK) { + sql.append("CHECK ("); + expressions.accept(expression); + sql.append(')'); + } else { + sql.append(kind == Kind.NULL ? "NULL" : "NOT NULL"); + } + } + + @Override + public String toString() { + StringBuilder sql = new StringBuilder(); + appendTo(sql, sql::append); + return sql.toString(); + } +} diff --git a/src/main/java/net/sf/jsqlparser/statement/create/event/AlterEvent.java b/src/main/java/net/sf/jsqlparser/statement/create/event/AlterEvent.java new file mode 100644 index 0000000000..c018be76bc --- /dev/null +++ b/src/main/java/net/sf/jsqlparser/statement/create/event/AlterEvent.java @@ -0,0 +1,51 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.statement.create.event; + +import net.sf.jsqlparser.schema.Table; +import net.sf.jsqlparser.statement.StatementVisitor; + +/** MySQL {@code ALTER EVENT} statement. */ +public class AlterEvent extends EventStatement { + + private Table renameTo; + + public Table getRenameTo() { + return renameTo; + } + + public void setRenameTo(Table renameTo) { + this.renameTo = renameTo; + } + + @Override + public T accept(StatementVisitor statementVisitor, S context) { + return statementVisitor.visit(this, context); + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder("ALTER EVENT ").append(getEvent()); + appendScheduleAndCompletion(builder); + if (renameTo != null) { + builder.append(" RENAME TO ").append(renameTo); + } + appendStatusAndComment(builder); + if (getBody() != null) { + builder.append(" DO ").append(getBody()); + } + return builder.toString(); + } + + public AlterEvent withRenameTo(Table renameTo) { + setRenameTo(renameTo); + return this; + } +} diff --git a/src/main/java/net/sf/jsqlparser/statement/create/event/CreateEvent.java b/src/main/java/net/sf/jsqlparser/statement/create/event/CreateEvent.java new file mode 100644 index 0000000000..2cefc5d9e4 --- /dev/null +++ b/src/main/java/net/sf/jsqlparser/statement/create/event/CreateEvent.java @@ -0,0 +1,49 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.statement.create.event; + +import net.sf.jsqlparser.statement.StatementVisitor; + +/** MySQL {@code CREATE EVENT} statement. */ +public class CreateEvent extends EventStatement { + + private boolean ifNotExists; + + public boolean isIfNotExists() { + return ifNotExists; + } + + public void setIfNotExists(boolean ifNotExists) { + this.ifNotExists = ifNotExists; + } + + @Override + public T accept(StatementVisitor statementVisitor, S context) { + return statementVisitor.visit(this, context); + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder("CREATE EVENT "); + if (ifNotExists) { + builder.append("IF NOT EXISTS "); + } + builder.append(getEvent()); + appendScheduleAndCompletion(builder); + appendStatusAndComment(builder); + builder.append(" DO ").append(getBody()); + return builder.toString(); + } + + public CreateEvent withIfNotExists(boolean ifNotExists) { + setIfNotExists(ifNotExists); + return this; + } +} diff --git a/src/main/java/net/sf/jsqlparser/statement/create/event/EventSchedule.java b/src/main/java/net/sf/jsqlparser/statement/create/event/EventSchedule.java new file mode 100644 index 0000000000..b3dba66f19 --- /dev/null +++ b/src/main/java/net/sf/jsqlparser/statement/create/event/EventSchedule.java @@ -0,0 +1,123 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.statement.create.event; + +import java.io.Serializable; +import net.sf.jsqlparser.expression.Expression; + +/** Structured {@code ON SCHEDULE} clause of a MySQL event. */ +public class EventSchedule implements Serializable { + + public enum Type { + AT, EVERY + } + + private Type type; + private Expression executeAt; + private Expression interval; + private String intervalUnit; + private Expression starts; + private Expression ends; + + public Type getType() { + return type; + } + + public void setType(Type type) { + this.type = type; + } + + public Expression getExecuteAt() { + return executeAt; + } + + public void setExecuteAt(Expression executeAt) { + this.executeAt = executeAt; + } + + public Expression getInterval() { + return interval; + } + + public void setInterval(Expression interval) { + this.interval = interval; + } + + public String getIntervalUnit() { + return intervalUnit; + } + + public void setIntervalUnit(String intervalUnit) { + this.intervalUnit = intervalUnit; + } + + public Expression getStarts() { + return starts; + } + + public void setStarts(Expression starts) { + this.starts = starts; + } + + public Expression getEnds() { + return ends; + } + + public void setEnds(Expression ends) { + this.ends = ends; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + if (type == Type.AT) { + builder.append("AT ").append(executeAt); + } else if (type == Type.EVERY) { + builder.append("EVERY ").append(interval).append(" ").append(intervalUnit); + if (starts != null) { + builder.append(" STARTS ").append(starts); + } + if (ends != null) { + builder.append(" ENDS ").append(ends); + } + } + return builder.toString(); + } + + public EventSchedule withType(Type type) { + setType(type); + return this; + } + + public EventSchedule withExecuteAt(Expression executeAt) { + setExecuteAt(executeAt); + return this; + } + + public EventSchedule withInterval(Expression interval) { + setInterval(interval); + return this; + } + + public EventSchedule withIntervalUnit(String intervalUnit) { + setIntervalUnit(intervalUnit); + return this; + } + + public EventSchedule withStarts(Expression starts) { + setStarts(starts); + return this; + } + + public EventSchedule withEnds(Expression ends) { + setEnds(ends); + return this; + } +} diff --git a/src/main/java/net/sf/jsqlparser/statement/create/event/EventStatement.java b/src/main/java/net/sf/jsqlparser/statement/create/event/EventStatement.java new file mode 100644 index 0000000000..bd66e95f22 --- /dev/null +++ b/src/main/java/net/sf/jsqlparser/statement/create/event/EventStatement.java @@ -0,0 +1,99 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.statement.create.event; + +import net.sf.jsqlparser.expression.StringValue; +import net.sf.jsqlparser.schema.Table; +import net.sf.jsqlparser.statement.Statement; + +/** Common structured properties of MySQL {@code CREATE EVENT} and {@code ALTER EVENT}. */ +public abstract class EventStatement implements Statement { + + private Table event; + private EventSchedule schedule; + private Boolean onCompletionPreserve; + private EventStatus status; + private StringValue comment; + private Statement body; + + public Table getEvent() { + return event; + } + + public void setEvent(Table event) { + this.event = event; + } + + public EventSchedule getSchedule() { + return schedule; + } + + public void setSchedule(EventSchedule schedule) { + this.schedule = schedule; + } + + /** + * Returns {@code true} for {@code ON COMPLETION PRESERVE}, {@code false} for + * {@code ON COMPLETION NOT PRESERVE}, or {@code null} when omitted. + */ + public Boolean getOnCompletionPreserve() { + return onCompletionPreserve; + } + + public void setOnCompletionPreserve(Boolean onCompletionPreserve) { + this.onCompletionPreserve = onCompletionPreserve; + } + + public EventStatus getStatus() { + return status; + } + + public void setStatus(EventStatus status) { + this.status = status; + } + + public StringValue getComment() { + return comment; + } + + public void setComment(StringValue comment) { + this.comment = comment; + } + + public Statement getBody() { + return body; + } + + public void setBody(Statement body) { + this.body = body; + } + + protected void appendScheduleAndCompletion(StringBuilder builder) { + if (schedule != null) { + builder.append(" ON SCHEDULE ").append(schedule); + } + if (onCompletionPreserve != null) { + builder.append(" ON COMPLETION "); + if (!onCompletionPreserve) { + builder.append("NOT "); + } + builder.append("PRESERVE"); + } + } + + protected void appendStatusAndComment(StringBuilder builder) { + if (status != null) { + builder.append(" ").append(status); + } + if (comment != null) { + builder.append(" COMMENT ").append(comment); + } + } +} diff --git a/src/main/java/net/sf/jsqlparser/statement/create/event/EventStatus.java b/src/main/java/net/sf/jsqlparser/statement/create/event/EventStatus.java new file mode 100644 index 0000000000..8418267e24 --- /dev/null +++ b/src/main/java/net/sf/jsqlparser/statement/create/event/EventStatus.java @@ -0,0 +1,25 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.statement.create.event; + +public enum EventStatus { + ENABLE("ENABLE"), DISABLE("DISABLE"), DISABLE_ON_SLAVE("DISABLE ON SLAVE"); + + private final String sql; + + EventStatus(String sql) { + this.sql = sql; + } + + @Override + public String toString() { + return sql; + } +} diff --git a/src/main/java/net/sf/jsqlparser/statement/create/extension/CreateExtension.java b/src/main/java/net/sf/jsqlparser/statement/create/extension/CreateExtension.java new file mode 100644 index 0000000000..bbac3276ed --- /dev/null +++ b/src/main/java/net/sf/jsqlparser/statement/create/extension/CreateExtension.java @@ -0,0 +1,104 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.statement.create.extension; + +import java.util.ArrayList; +import java.util.List; +import net.sf.jsqlparser.expression.Expression; +import net.sf.jsqlparser.statement.Statement; +import net.sf.jsqlparser.statement.StatementVisitor; + +public class CreateExtension implements Statement { + private String name; + private boolean ifNotExists; + private boolean useWith; + private List
tables, Consumer expressions) { + tables.accept(table); + if (columns != null) { + columns.forEach(expressions); + } + if (where != null) { + expressions.accept(where); + } + } + + @Override + public String toString() { + StringBuilder sql = new StringBuilder(); + appendTo(sql, sql::append); + return sql.toString(); + } +} diff --git a/src/main/java/net/sf/jsqlparser/statement/create/publication/PublicationTarget.java b/src/main/java/net/sf/jsqlparser/statement/create/publication/PublicationTarget.java new file mode 100644 index 0000000000..97205da8c7 --- /dev/null +++ b/src/main/java/net/sf/jsqlparser/statement/create/publication/PublicationTarget.java @@ -0,0 +1,78 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.statement.create.publication; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; +import net.sf.jsqlparser.expression.Expression; +import net.sf.jsqlparser.schema.Table; +import java.io.Serializable; + +public class PublicationTarget implements Serializable { + private Kind kind; + private List tables = new ArrayList<>(); + private List schemas = new ArrayList<>(); + + public Kind getKind() { + return kind; + } + + public void setKind(Kind kind) { + this.kind = kind; + } + + public List getTables() { + return tables; + } + + public void setTables(List tables) { + this.tables = tables; + } + + public List getSchemas() { + return schemas; + } + + public void setSchemas(List schemas) { + this.schemas = schemas; + } + + public enum Kind { + TABLE, TABLES_IN_SCHEMA + } + + public void appendTo(StringBuilder sql, Consumer expressions) { + if (kind == Kind.TABLES_IN_SCHEMA) { + sql.append("TABLES IN SCHEMA ").append(String.join(", ", schemas)); + } else { + sql.append("TABLE "); + for (int i = 0; i < tables.size(); i++) { + if (i > 0) { + sql.append(", "); + } + tables.get(i).appendTo(sql, expressions); + } + } + } + + public void visit(Consumer
tableVisitor, Consumer expressions) { + if (kind == Kind.TABLE) { + tables.forEach(table -> table.visit(tableVisitor, expressions)); + } + } + + @Override + public String toString() { + StringBuilder sql = new StringBuilder(); + appendTo(sql, sql::append); + return sql.toString(); + } +} diff --git a/src/main/java/net/sf/jsqlparser/statement/create/schema/CreateSchema.java b/src/main/java/net/sf/jsqlparser/statement/create/schema/CreateSchema.java index e972c9c30a..f8888132f3 100644 --- a/src/main/java/net/sf/jsqlparser/statement/create/schema/CreateSchema.java +++ b/src/main/java/net/sf/jsqlparser/statement/create/schema/CreateSchema.java @@ -14,9 +14,11 @@ import java.util.Collections; import java.util.List; import java.util.Optional; +import java.util.stream.Collectors; import net.sf.jsqlparser.statement.Statement; import net.sf.jsqlparser.statement.StatementVisitor; +import net.sf.jsqlparser.statement.create.database.DatabaseOption; public class CreateSchema implements Statement { @@ -25,6 +27,7 @@ public class CreateSchema implements Statement { private String schemaName; private List schemaPath; private List statements = new ArrayList<>(); + private List databaseOptions; private boolean hasIfNotExists = false; @Override @@ -114,6 +117,19 @@ public List getStatements() { return statements; } + public List getDatabaseOptions() { + return databaseOptions; + } + + public void setDatabaseOptions(List databaseOptions) { + this.databaseOptions = databaseOptions; + } + + public Optional getDatabaseOption(DatabaseOption.Kind kind) { + return Optional.ofNullable(databaseOptions).orElseGet(Collections::emptyList).stream() + .filter(option -> option.getKind() == kind).findFirst(); + } + public boolean hasIfNotExists() { return hasIfNotExists; } @@ -131,11 +147,15 @@ public String toString() { if (schemaName != null) { sql += " "; - if (catalogName!=null) { + if (catalogName != null) { sql += catalogName + "."; } sql += schemaName; } + if (databaseOptions != null && !databaseOptions.isEmpty()) { + sql += " " + databaseOptions.stream().map(Object::toString) + .collect(Collectors.joining(" ")); + } if (authorization != null) { sql += " AUTHORIZATION " + authorization; } @@ -157,6 +177,18 @@ public CreateSchema withSchemaPath(List schemaPath) { return this; } + public CreateSchema withDatabaseOptions(List databaseOptions) { + setDatabaseOptions(databaseOptions); + return this; + } + + public CreateSchema addDatabaseOptions(DatabaseOption... databaseOptions) { + List collection = + Optional.ofNullable(getDatabaseOptions()).orElseGet(ArrayList::new); + Collections.addAll(collection, databaseOptions); + return withDatabaseOptions(collection); + } + public CreateSchema addSchemaPath(String... schemaPath) { List collection = Optional.ofNullable(getSchemaPath()).orElseGet(ArrayList::new); Collections.addAll(collection, schemaPath); diff --git a/src/main/java/net/sf/jsqlparser/statement/create/subscription/CreateSubscription.java b/src/main/java/net/sf/jsqlparser/statement/create/subscription/CreateSubscription.java new file mode 100644 index 0000000000..75d193e0e4 --- /dev/null +++ b/src/main/java/net/sf/jsqlparser/statement/create/subscription/CreateSubscription.java @@ -0,0 +1,85 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.statement.create.subscription; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; +import net.sf.jsqlparser.expression.Expression; +import net.sf.jsqlparser.expression.StringValue; +import net.sf.jsqlparser.statement.Statement; +import net.sf.jsqlparser.statement.StatementVisitor; + +public class CreateSubscription implements Statement { + private String name; + private StringValue connection; + private List publications = new ArrayList<>(); + private List options = new ArrayList<>(); + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public StringValue getConnection() { + return connection; + } + + public void setConnection(StringValue connection) { + this.connection = connection; + } + + public List getPublications() { + return publications; + } + + public void setPublications(List publications) { + this.publications = publications; + } + + public List getOptions() { + return options; + } + + public void setOptions(List options) { + this.options = options; + } + + @Override + public T accept(StatementVisitor visitor, S context) { + return visitor.visit(this, context); + } + + public void appendTo(StringBuilder sql, Consumer expressions) { + sql.append("CREATE SUBSCRIPTION ").append(name).append(" CONNECTION "); + expressions.accept(connection); + sql.append(" PUBLICATION ").append(String.join(", ", publications)); + if (!options.isEmpty()) { + sql.append(" WITH ("); + for (int i = 0; i < options.size(); i++) { + if (i > 0) { + sql.append(", "); + } + options.get(i).appendTo(sql, expressions); + } + sql.append(')'); + } + } + + @Override + public String toString() { + StringBuilder sql = new StringBuilder(); + appendTo(sql, sql::append); + return sql.toString(); + } +} diff --git a/src/main/java/net/sf/jsqlparser/statement/create/subscription/SubscriptionOption.java b/src/main/java/net/sf/jsqlparser/statement/create/subscription/SubscriptionOption.java new file mode 100644 index 0000000000..62cad87089 --- /dev/null +++ b/src/main/java/net/sf/jsqlparser/statement/create/subscription/SubscriptionOption.java @@ -0,0 +1,70 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.statement.create.subscription; + +import net.sf.jsqlparser.expression.Expression; +import net.sf.jsqlparser.expression.StringValue; +import net.sf.jsqlparser.statement.replication.ReplicationOption; + +public class SubscriptionOption extends ReplicationOption { + public enum Kind { + CONNECT, CREATE_SLOT, ENABLED, SLOT_NAME, BINARY, COPY_DATA, STREAMING, SYNCHRONOUS_COMMIT, TWO_PHASE, DISABLE_ON_ERROR, PASSWORD_REQUIRED, RUN_AS_OWNER, ORIGIN, FAILOVER, REFRESH, LSN + } + public enum Streaming { + ON, OFF, PARALLEL + } + public enum Origin { + ANY, NONE + } + public enum SynchronousCommit { + OFF, LOCAL, REMOTE_WRITE, ON, REMOTE_APPLY + } + + public SubscriptionOption(String name, Kind kind, Expression value) { + super(name, kind, value); + } + + public Boolean getBooleanValue() { + switch (getKind()) { + case CONNECT: + case CREATE_SLOT: + case ENABLED: + case BINARY: + case COPY_DATA: + case TWO_PHASE: + case DISABLE_ON_ERROR: + case PASSWORD_REQUIRED: + case RUN_AS_OWNER: + case FAILOVER: + case REFRESH: + return booleanValue(); + default: + return null; + } + } + + public Streaming getStreaming() { + return getKind() == Kind.STREAMING ? enumValue(Streaming.class) : null; + } + + public Origin getOrigin() { + return getKind() == Kind.ORIGIN ? enumValue(Origin.class) : null; + } + + public SynchronousCommit getSynchronousCommit() { + return getKind() == Kind.SYNCHRONOUS_COMMIT ? enumValue(SynchronousCommit.class) : null; + } + + /** Quoted 'NONE' remains a literal slot name, not the NONE keyword. */ + public boolean isSlotNameNone() { + return getKind() == Kind.SLOT_NAME && !(getValue() instanceof StringValue) + && "NONE".equalsIgnoreCase(getValueText()); + } +} diff --git a/src/main/java/net/sf/jsqlparser/statement/create/table/CheckConstraint.java b/src/main/java/net/sf/jsqlparser/statement/create/table/CheckConstraint.java index 254fb3d01e..a3047fb422 100644 --- a/src/main/java/net/sf/jsqlparser/statement/create/table/CheckConstraint.java +++ b/src/main/java/net/sf/jsqlparser/statement/create/table/CheckConstraint.java @@ -11,6 +11,7 @@ import java.util.Collection; import java.util.List; +import java.util.function.Consumer; import net.sf.jsqlparser.expression.Expression; import net.sf.jsqlparser.schema.Table; @@ -21,6 +22,12 @@ public class CheckConstraint extends NamedConstraint { private Expression expression; + private Boolean enforced; + + public CheckConstraint() { + setKind(Kind.CHECK); + } + public Table getTable() { return table; } @@ -37,14 +44,29 @@ public void setExpression(Expression expression) { this.expression = expression; } + public Boolean getEnforced() { + return enforced; + } + + public void setEnforced(Boolean enforced) { + this.enforced = enforced; + } + @Override - public String toString() { - StringBuilder b = new StringBuilder(); - if (getName() != null) { - b.append("CONSTRAINT ").append(getName()).append(" "); + public void appendTo(StringBuilder b, Consumer expressionPrinter) { + appendConstraintPrefixTo(b); + b.append("CHECK ("); + if (expression == null) { + b.append("null"); + } else { + expressionPrinter.accept(expression); } - b.append("CHECK (").append(expression).append(")"); - return b.toString(); + b.append(')'); + if (enforced != null) { + b.append(enforced ? " ENFORCED" : " NOT ENFORCED"); + } + appendConstraintSuffixTo(b); + appendConstraintAttributesTo(b); } public CheckConstraint withTable(Table table) { @@ -57,6 +79,11 @@ public CheckConstraint withExpression(Expression expression) { return this; } + public CheckConstraint withEnforced(Boolean enforced) { + setEnforced(enforced); + return this; + } + public E getExpression(Class type) { return type.cast(getExpression()); } @@ -71,6 +98,11 @@ public CheckConstraint withUsing(String using) { return (CheckConstraint) super.withUsing(using); } + @Override + public CheckConstraint withUseConstraintKeyword(boolean useConstraintKeyword) { + return (CheckConstraint) super.withUseConstraintKeyword(useConstraintKeyword); + } + @Override public CheckConstraint withName(List name) { return (CheckConstraint) super.withName(name); diff --git a/src/main/java/net/sf/jsqlparser/statement/create/table/ColDataType.java b/src/main/java/net/sf/jsqlparser/statement/create/table/ColDataType.java index a3ddcdb9fc..77a95f4cc1 100644 --- a/src/main/java/net/sf/jsqlparser/statement/create/table/ColDataType.java +++ b/src/main/java/net/sf/jsqlparser/statement/create/table/ColDataType.java @@ -24,11 +24,30 @@ public class ColDataType implements Serializable { + public enum Signedness { + SIGNED, UNSIGNED + } + + public enum TypeModifier { + SIGNED, UNSIGNED, ZEROFILL + } + + public enum NationalCharacterType { + CHAR, VARCHAR + } + private String dataType; private List argumentsStringList; private String characterSet; private IntervalQualifier intervalQualifier; private List arrayData = new ArrayList(); + private Signedness signedness; + private boolean zerofill; + private Integer precision; + private Integer scale; + private List typeModifiers; + private NationalCharacterType nationalCharacterType; + private XmlTypeModifier xmlTypeModifier; public ColDataType() { // empty constructor @@ -38,8 +57,10 @@ public ColDataType(String dataType, int precision, int scale) { this.dataType = dataType; if (precision >= 0) { + this.precision = precision; this.dataType += " (" + (precision == Integer.MAX_VALUE ? "MAX" : precision); if (scale >= 0) { + this.scale = scale; this.dataType += ", " + scale; } this.dataType += ")"; @@ -94,6 +115,113 @@ public void setArrayData(List arrayData) { this.arrayData = arrayData; } + public Signedness getSignedness() { + return signedness; + } + + public void setSignedness(Signedness signedness) { + this.signedness = signedness; + } + + public boolean isZerofill() { + return zerofill; + } + + public void setZerofill(boolean zerofill) { + this.zerofill = zerofill; + } + + /** Returns MySQL numeric modifiers in their original order, including repetitions. */ + public List getTypeModifiers() { + return typeModifiers; + } + + public void setTypeModifiers(List typeModifiers) { + this.typeModifiers = typeModifiers; + signedness = null; + zerofill = false; + if (typeModifiers != null) { + for (TypeModifier modifier : typeModifiers) { + updateEffectiveModifier(modifier); + } + } + } + + public void addTypeModifier(TypeModifier typeModifier) { + if (typeModifiers == null) { + typeModifiers = new ArrayList<>(); + } + typeModifiers.add(typeModifier); + updateEffectiveModifier(typeModifier); + } + + private void updateEffectiveModifier(TypeModifier modifier) { + switch (modifier) { + case SIGNED: + signedness = Signedness.SIGNED; + break; + case UNSIGNED: + signedness = Signedness.UNSIGNED; + break; + case ZEROFILL: + zerofill = true; + break; + default: + break; + } + } + + public NationalCharacterType getNationalCharacterType() { + return nationalCharacterType; + } + + public void setNationalCharacterType(NationalCharacterType nationalCharacterType) { + this.nationalCharacterType = nationalCharacterType; + } + + public boolean isNational() { + return nationalCharacterType != null; + } + + public XmlTypeModifier getXmlTypeModifier() { + return xmlTypeModifier; + } + + public void setXmlTypeModifier(XmlTypeModifier xmlTypeModifier) { + this.xmlTypeModifier = xmlTypeModifier; + } + + public ColDataType withXmlTypeModifier(XmlTypeModifier xmlTypeModifier) { + setXmlTypeModifier(xmlTypeModifier); + return this; + } + + /** + * The first numeric type parameter, e.g. {@code 255} for {@code VARCHAR(255)} or {@code 10} for + * {@code DECIMAL(10, 2)}. {@code MAX} is reported as {@link Integer#MAX_VALUE}. Returns + * {@code null} when the type carries no numeric parameters, e.g. {@code INT} or + * {@code ENUM('a', 'b')}. + */ + public Integer getPrecision() { + return precision; + } + + public void setPrecision(Integer precision) { + this.precision = precision; + } + + /** + * The second numeric type parameter, e.g. {@code 2} for {@code DECIMAL(10, 2)}. Returns + * {@code null} when absent. + */ + public Integer getScale() { + return scale; + } + + public void setScale(Integer scale) { + this.scale = scale; + } + @Override public String toString() { StringBuilder arraySpec = new StringBuilder(); @@ -106,9 +234,14 @@ public String toString() { } return dataType + (intervalQualifier != null ? " " + intervalQualifier.toString() : "") + + (xmlTypeModifier != null ? " " + xmlTypeModifier : "") + (argumentsStringList != null ? " " + PlainSelect.getStringList(argumentsStringList, true, true) : "") + + (typeModifiers != null && !typeModifiers.isEmpty() + ? " " + PlainSelect.getStringList(typeModifiers, false, false) + : (signedness != null ? " " + signedness : "") + + (zerofill ? " ZEROFILL" : "")) + arraySpec.toString() + (characterSet != null ? " CHARACTER SET " + characterSet : ""); } @@ -138,6 +271,36 @@ public ColDataType withArrayData(List arrayData) { return this; } + public ColDataType withSignedness(Signedness signedness) { + setSignedness(signedness); + return this; + } + + public ColDataType withZerofill(boolean zerofill) { + setZerofill(zerofill); + return this; + } + + public ColDataType withTypeModifiers(List typeModifiers) { + setTypeModifiers(typeModifiers); + return this; + } + + public ColDataType withNationalCharacterType(NationalCharacterType nationalCharacterType) { + setNationalCharacterType(nationalCharacterType); + return this; + } + + public ColDataType withPrecision(Integer precision) { + this.setPrecision(precision); + return this; + } + + public ColDataType withScale(Integer scale) { + this.setScale(scale); + return this; + } + public ColDataType addArgumentsStringList(String... argumentsStringList) { List collection = Optional.ofNullable(getArgumentsStringList()).orElseGet(ArrayList::new); @@ -178,16 +341,29 @@ public final boolean equals(Object o) { && Objects.equals(argumentsStringList, that.argumentsStringList) && Objects.equals(characterSet, that.characterSet) && Objects.equals(intervalQualifier, that.intervalQualifier) - && Objects.equals(arrayData, that.arrayData); + && Objects.equals(arrayData, that.arrayData) + && signedness == that.signedness + && zerofill == that.zerofill + && Objects.equals(typeModifiers, that.typeModifiers) + && Objects.equals(xmlTypeModifier, that.xmlTypeModifier) + && nationalCharacterType == that.nationalCharacterType; } @Override public int hashCode() { - int result = dataType.hashCode(); + // Use the same per-code-point case folding as String.equalsIgnoreCase, including Unicode. + int result = dataType.codePoints() + .map(c -> Character.toLowerCase(Character.toUpperCase(c))) + .reduce(0, (hash, c) -> 31 * hash + c); result = 31 * result + Objects.hashCode(argumentsStringList); result = 31 * result + Objects.hashCode(characterSet); result = 31 * result + Objects.hashCode(intervalQualifier); result = 31 * result + Objects.hashCode(arrayData); + result = 31 * result + Objects.hashCode(signedness); + result = 31 * result + Boolean.hashCode(zerofill); + result = 31 * result + Objects.hashCode(typeModifiers); + result = 31 * result + Objects.hashCode(nationalCharacterType); + result = 31 * result + Objects.hashCode(xmlTypeModifier); return result; } } diff --git a/src/main/java/net/sf/jsqlparser/statement/create/table/ColumnDefinition.java b/src/main/java/net/sf/jsqlparser/statement/create/table/ColumnDefinition.java index a7f47104c3..ccede4eb84 100644 --- a/src/main/java/net/sf/jsqlparser/statement/create/table/ColumnDefinition.java +++ b/src/main/java/net/sf/jsqlparser/statement/create/table/ColumnDefinition.java @@ -11,22 +11,35 @@ import net.sf.jsqlparser.statement.imprt.ImportColumn; import net.sf.jsqlparser.statement.select.PlainSelect; +import net.sf.jsqlparser.expression.Expression; import java.io.Serializable; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Optional; +import java.util.function.Consumer; /** * Globally used definition class for columns. */ -public class ColumnDefinition implements ImportColumn, Serializable { +public class ColumnDefinition implements ImportColumn, TableElement, Serializable { private String columnName; private ColDataType colDataType; private List columnSpecs; + private List columnOptions; + private boolean withOptions; + + public boolean isWithOptions() { + return withOptions; + } + + public void setWithOptions(boolean withOptions) { + this.withOptions = withOptions; + } public ColumnDefinition() {} @@ -40,12 +53,70 @@ public ColumnDefinition(String columnName, ColDataType colDataType, List this.columnSpecs = columnSpecs; } + /** + * Returns raw specifications, or a token snapshot when structured options are present. Use the + * option API or {@link #addColumnSpecs(Collection)} to append without discarding structured + * expressions, references and constraints. DEFAULT values use the expression's SQL rendering. + */ public List getColumnSpecs() { + if (columnOptions != null) { + List tokens = new ArrayList<>(); + for (ColumnOption option : columnOptions) { + tokens.addAll(option.getTokens()); + } + return tokens; + } return columnSpecs; } public void setColumnSpecs(List list) { columnSpecs = list; + columnOptions = null; + } + + /** + * Returns column options in source order, including defaults, references, generated columns, + * nullability, collation, comments, visibility and MySQL attributes. Unrecognized options + * remain raw. Structured option keywords use canonical capitalization when rendered. + */ + public List getColumnOptions() { + return columnOptions; + } + + public void setColumnOptions(List columnOptions) { + this.columnOptions = columnOptions; + this.columnSpecs = null; + } + + public boolean isSerialDefaultValue() { + return columnOptions != null && columnOptions.stream() + .anyMatch(option -> option.getKind() == ColumnOption.Kind.SERIAL_DEFAULT_VALUE); + } + + public ForeignKeyReference getForeignKeyReference() { + if (columnOptions == null) { + return null; + } + return columnOptions.stream() + .filter(option -> option.getKind() == ColumnOption.Kind.REFERENCE) + .map(ColumnOption::getForeignKeyReference) + .findFirst() + .orElse(null); + } + + public ColumnDefinition withColumnOptions(List columnOptions) { + setColumnOptions(columnOptions); + return this; + } + + public ColumnDefinition addColumnOptions(ColumnOption... columnOptions) { + List collection = + Optional.ofNullable(getColumnOptions()).orElseGet(ArrayList::new); + if (this.columnOptions == null && columnSpecs != null && !columnSpecs.isEmpty()) { + collection.add(ColumnOption.raw(new ArrayList<>(columnSpecs))); + } + Collections.addAll(collection, columnOptions); + return withColumnOptions(collection); } public ColDataType getColDataType() { @@ -66,14 +137,42 @@ public void setColumnName(String string) { @Override public String toString() { - return columnName + " " + toStringDataTypeAndSpec(); + StringBuilder builder = new StringBuilder(); + appendTo(builder, builder::append); + return builder.toString().trim(); + } + + /** Appends a column definition using the supplied printer for structured expressions. */ + public void appendTo(StringBuilder builder, Consumer expressionPrinter) { + builder.append(columnName); + if (colDataType != null || withOptions) { + builder.append(' '); + } + appendDataTypeAndSpecTo(builder, expressionPrinter); } public String toStringDataTypeAndSpec() { - return (colDataType == null ? "" : colDataType) - + (columnSpecs != null && !columnSpecs.isEmpty() - ? " " + PlainSelect.getStringList(columnSpecs, false, false) - : ""); + StringBuilder builder = new StringBuilder(); + appendDataTypeAndSpecTo(builder, builder::append); + return builder.toString(); + } + + protected void appendDataTypeAndSpecTo(StringBuilder builder, + Consumer expressionPrinter) { + if (colDataType != null) { + builder.append(colDataType); + } + if (withOptions) { + builder.append("WITH OPTIONS"); + } + if (columnOptions != null) { + for (ColumnOption option : columnOptions) { + builder.append(' '); + option.appendTo(builder, expressionPrinter); + } + } else if (columnSpecs != null && !columnSpecs.isEmpty()) { + builder.append(' ').append(PlainSelect.getStringList(columnSpecs, false, false)); + } } public ColumnDefinition withColumnName(String columnName) { @@ -92,12 +191,16 @@ public ColumnDefinition withColumnSpecs(List columnSpecs) { } public ColumnDefinition addColumnSpecs(String... columnSpecs) { - List collection = Optional.ofNullable(getColumnSpecs()).orElseGet(ArrayList::new); - Collections.addAll(collection, columnSpecs); - return this.withColumnSpecs(collection); + return addColumnSpecs(Arrays.asList(columnSpecs)); } public ColumnDefinition addColumnSpecs(Collection columnSpecs) { + if (columnOptions != null) { + if (!columnSpecs.isEmpty()) { + columnOptions.add(ColumnOption.raw(new ArrayList<>(columnSpecs))); + } + return this; + } List collection = Optional.ofNullable(getColumnSpecs()).orElseGet(ArrayList::new); collection.addAll(columnSpecs); return this.withColumnSpecs(collection); diff --git a/src/main/java/net/sf/jsqlparser/statement/create/table/ColumnOption.java b/src/main/java/net/sf/jsqlparser/statement/create/table/ColumnOption.java new file mode 100644 index 0000000000..23f2ef91f3 --- /dev/null +++ b/src/main/java/net/sf/jsqlparser/statement/create/table/ColumnOption.java @@ -0,0 +1,300 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.statement.create.table; + +import java.io.Serializable; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.function.Consumer; +import net.sf.jsqlparser.expression.Expression; +import net.sf.jsqlparser.expression.StringValue; +import net.sf.jsqlparser.statement.select.PlainSelect; + +/** A structured option following a column data type. */ +public class ColumnOption implements Serializable { + + public enum Kind { + SERIAL_DEFAULT_VALUE, REFERENCE, IDENTITY, CONSTRAINT, DEFAULT, NULLABILITY, COLLATE, COMMENT, ON_UPDATE, GENERATED, AUTO_INCREMENT, VISIBILITY, OTHER + } + + private Kind kind = Kind.OTHER; + private List tokens; + private ForeignKeyReference foreignKeyReference; + private IdentityDefinition identityDefinition; + private Index constraint; + private Expression defaultExpression; + private Boolean nullable; + private Boolean visible; + private String collation; + private StringValue comment; + private Expression onUpdateExpression; + private GeneratedColumnDefinition generatedDefinition; + + public static ColumnOption nullability(boolean nullable) { + ColumnOption option = new ColumnOption(); + option.kind = Kind.NULLABILITY; + option.nullable = nullable; + return option; + } + + public Boolean getNullable() { + return nullable; + } + + public void setNullable(boolean nullable) { + this.nullable = nullable; + } + + public static ColumnOption visibility(boolean visible) { + ColumnOption option = new ColumnOption(); + option.kind = Kind.VISIBILITY; + option.visible = visible; + return option; + } + + public Boolean getVisible() { + return visible; + } + + public void setVisible(boolean visible) { + this.visible = visible; + } + + public static ColumnOption autoIncrement() { + ColumnOption option = new ColumnOption(); + option.kind = Kind.AUTO_INCREMENT; + return option; + } + + public static ColumnOption collate(String collation) { + ColumnOption option = new ColumnOption(); + option.kind = Kind.COLLATE; + option.setCollation(collation); + return option; + } + + public String getCollation() { + return collation; + } + + public void setCollation(String collation) { + this.collation = Objects.requireNonNull(collation, "collation"); + } + + public static ColumnOption comment(StringValue comment) { + ColumnOption option = new ColumnOption(); + option.kind = Kind.COMMENT; + option.setComment(comment); + return option; + } + + public StringValue getComment() { + return comment; + } + + public void setComment(StringValue comment) { + this.comment = Objects.requireNonNull(comment, "comment"); + } + + public static ColumnOption onUpdate(Expression expression) { + ColumnOption option = new ColumnOption(); + option.kind = Kind.ON_UPDATE; + option.setOnUpdateExpression(expression); + return option; + } + + public Expression getOnUpdateExpression() { + return onUpdateExpression; + } + + public void setOnUpdateExpression(Expression expression) { + onUpdateExpression = Objects.requireNonNull(expression, "expression"); + } + + public static ColumnOption generated(GeneratedColumnDefinition definition) { + ColumnOption option = new ColumnOption(); + option.kind = Kind.GENERATED; + option.setGeneratedDefinition(definition); + return option; + } + + public GeneratedColumnDefinition getGeneratedDefinition() { + return generatedDefinition; + } + + public void setGeneratedDefinition(GeneratedColumnDefinition definition) { + generatedDefinition = Objects.requireNonNull(definition, "definition"); + } + + /** Visits expressions of the selected option kind, including comment literals. */ + public void visitExpressions(Consumer visitor) { + if (kind == Kind.DEFAULT) { + visitor.accept(defaultExpression); + } else if (kind == Kind.COMMENT) { + visitor.accept(comment); + } else if (kind == Kind.ON_UPDATE) { + visitor.accept(onUpdateExpression); + } else if (kind == Kind.GENERATED) { + visitor.accept(generatedDefinition.getExpression()); + } + } + + + /** Creates a DEFAULT option. Use a NullValue expression for SQL NULL. */ + public static ColumnOption defaultValue(Expression expression) { + ColumnOption option = new ColumnOption(); + option.kind = Kind.DEFAULT; + option.setDefaultExpression(expression); + return option; + } + + public Expression getDefaultExpression() { + return defaultExpression; + } + + /** Replaces the expression of a DEFAULT option created by {@link #defaultValue(Expression)}. */ + public void setDefaultExpression(Expression expression) { + defaultExpression = Objects.requireNonNull(expression, "defaultExpression"); + } + + public static ColumnOption identity(IdentityDefinition definition) { + ColumnOption option = new ColumnOption(); + option.kind = Kind.IDENTITY; + option.identityDefinition = definition; + return option; + } + + public static ColumnOption constraint(Index constraint) { + ColumnOption option = new ColumnOption(); + option.kind = Kind.CONSTRAINT; + option.constraint = constraint; + return option; + } + + public IdentityDefinition getIdentityDefinition() { + return identityDefinition; + } + + public Index getConstraint() { + return constraint; + } + + public static ColumnOption raw(List tokens) { + ColumnOption option = new ColumnOption(); + option.tokens = tokens; + return option; + } + + public static ColumnOption raw(String... tokens) { + return raw(Arrays.asList(tokens)); + } + + public static ColumnOption serialDefaultValue() { + ColumnOption option = raw("SERIAL", "DEFAULT", "VALUE"); + option.kind = Kind.SERIAL_DEFAULT_VALUE; + return option; + } + + public static ColumnOption reference(ForeignKeyReference reference) { + ColumnOption option = new ColumnOption(); + option.kind = Kind.REFERENCE; + option.foreignKeyReference = reference; + return option; + } + + public Kind getKind() { + return kind; + } + + public List getTokens() { + switch (kind) { + case DEFAULT: + return Arrays.asList("DEFAULT", String.valueOf(defaultExpression)); + case NULLABILITY: + return nullable ? Collections.singletonList("NULL") : Arrays.asList("NOT", "NULL"); + case COLLATE: + return Arrays.asList("COLLATE", collation); + case COMMENT: + return Arrays.asList("COMMENT", comment.toString()); + case ON_UPDATE: + return Arrays.asList("ON", "UPDATE", onUpdateExpression.toString()); + case GENERATED: + return generatedDefinition.getTokens(); + case CONSTRAINT: + if ("PRIMARY KEY".equals(constraint.toString())) { + return Arrays.asList("PRIMARY", "KEY"); + } + return Collections.singletonList(toString()); + case OTHER: + case SERIAL_DEFAULT_VALUE: + return tokens; + default: + return Collections.singletonList(toString()); + } + } + + public ForeignKeyReference getForeignKeyReference() { + return foreignKeyReference; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + appendTo(builder, builder::append); + return builder.toString(); + } + + /** Appends the option using the supplied printer for structured expressions. */ + public void appendTo(StringBuilder builder, Consumer expressionPrinter) { + switch (kind) { + case DEFAULT: + builder.append("DEFAULT "); + expressionPrinter.accept(defaultExpression); + break; + case NULLABILITY: + builder.append(nullable ? "NULL" : "NOT NULL"); + break; + case VISIBILITY: + builder.append(visible ? "VISIBLE" : "INVISIBLE"); + break; + case AUTO_INCREMENT: + builder.append("AUTO_INCREMENT"); + break; + case COLLATE: + builder.append("COLLATE ").append(collation); + break; + case COMMENT: + builder.append("COMMENT "); + expressionPrinter.accept(comment); + break; + case ON_UPDATE: + builder.append("ON UPDATE "); + expressionPrinter.accept(onUpdateExpression); + break; + case GENERATED: + generatedDefinition.appendTo(builder, expressionPrinter); + break; + case REFERENCE: + builder.append(foreignKeyReference); + break; + case IDENTITY: + builder.append(identityDefinition); + break; + case CONSTRAINT: + constraint.appendTo(builder, expressionPrinter); + break; + default: + builder.append(PlainSelect.getStringList(tokens, false, false)); + break; + } + } +} diff --git a/src/main/java/net/sf/jsqlparser/statement/create/table/ConstraintAttributes.java b/src/main/java/net/sf/jsqlparser/statement/create/table/ConstraintAttributes.java new file mode 100644 index 0000000000..ca063d6033 --- /dev/null +++ b/src/main/java/net/sf/jsqlparser/statement/create/table/ConstraintAttributes.java @@ -0,0 +1,66 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.statement.create.table; + +import java.io.Serializable; + +/** Shared PostgreSQL constraint attributes; null values preserve omitted clauses. */ +public class ConstraintAttributes implements Serializable { + public enum Initially { + IMMEDIATE, DEFERRED + } + + private Boolean deferrable; + private Initially initially; + private boolean notValid; + + public Boolean getDeferrable() { + return deferrable; + } + + public void setDeferrable(Boolean deferrable) { + this.deferrable = deferrable; + } + + public Initially getInitially() { + return initially; + } + + public void setInitially(Initially initially) { + this.initially = initially; + } + + public boolean isNotValid() { + return notValid; + } + + public void setNotValid(boolean notValid) { + this.notValid = notValid; + } + + public void appendTo(StringBuilder sql) { + if (deferrable != null) { + sql.append(deferrable ? " DEFERRABLE" : " NOT DEFERRABLE"); + } + if (initially != null) { + sql.append(" INITIALLY ").append(initially); + } + if (notValid) { + sql.append(" NOT VALID"); + } + } + + @Override + public String toString() { + StringBuilder sql = new StringBuilder(); + appendTo(sql); + return sql.toString().trim(); + } +} diff --git a/src/main/java/net/sf/jsqlparser/statement/create/table/CreateTable.java b/src/main/java/net/sf/jsqlparser/statement/create/table/CreateTable.java index d2be0e0264..d248f7879d 100644 --- a/src/main/java/net/sf/jsqlparser/statement/create/table/CreateTable.java +++ b/src/main/java/net/sf/jsqlparser/statement/create/table/CreateTable.java @@ -18,6 +18,7 @@ import net.sf.jsqlparser.expression.SpannerInterleaveIn; import net.sf.jsqlparser.schema.Table; import net.sf.jsqlparser.statement.Statement; +import net.sf.jsqlparser.statement.LikeClause; import net.sf.jsqlparser.statement.StatementVisitor; import net.sf.jsqlparser.statement.select.PlainSelect; import net.sf.jsqlparser.statement.select.Select; @@ -28,14 +29,21 @@ public class CreateTable implements Statement { private boolean unlogged = false; private List createOptionsStrings; private List tableOptionsStrings; + private List tableOptions; private List columnDefinitions; private List columns; private List indexes; + private List tableElements; private Select select; private Table likeTable; + private Table cloneTable; + private ColDataType ofType; private boolean selectParenthesis; private boolean ifNotExists = false; private boolean orReplace = false; + private TablePartitioning partitioning; + private Table partitionOf; + private PartitionBound partitionBound; private RowMovement rowMovement; @@ -63,14 +71,19 @@ public void setUnlogged(boolean unlogged) { } /** - * @return a list of {@link ColumnDefinition}s of this table. + * @return a list of {@link ColumnDefinition}s of this table. When ordered table elements are + * present, this is a mutable view of the column definitions in that list. */ public List getColumnDefinitions() { return columnDefinitions; } public void setColumnDefinitions(List list) { - columnDefinitions = list; + if (tableElements == null) { + columnDefinitions = list; + } else { + TableElementList.replace(tableElements, ColumnDefinition.class, list); + } } public List getColumns() { @@ -83,14 +96,41 @@ public void setColumns(List columns) { /** * @return a list of options (as simple strings) of this table definition, as ("TYPE", "=", - * "MYISAM") + * "MYISAM"). For typed options, this is a snapshot of their current tokens; use + * {@link #getTableOptions()} to edit the structured options or + * {@link #setTableOptionsStrings(List)} to replace them with raw options. */ public List getTableOptionsStrings() { - return tableOptionsStrings; + if (tableOptions == null) { + return tableOptionsStrings; + } + List tokens = new ArrayList<>(); + for (TableOption option : tableOptions) { + tokens.addAll(option.getTokens()); + } + return tokens; } public void setTableOptionsStrings(List tableOptionsStrings) { this.tableOptionsStrings = tableOptionsStrings; + tableOptions = null; + } + + /** Returns typed table options in source order. */ + public List getTableOptions() { + return tableOptions; + } + + public void setTableOptions(List tableOptions) { + this.tableOptions = tableOptions; + tableOptionsStrings = null; + } + + /** Returns the first option of the requested kind, if present. */ + public Optional getTableOption(TableOption.Kind kind) { + return Optional.ofNullable(tableOptions).orElseGet(Collections::emptyList).stream() + .filter(option -> option.getKind() == kind) + .findFirst(); } public List getCreateOptionsStrings() { @@ -104,14 +144,51 @@ public void setCreateOptionsStrings(List createOptionsStrings) { /** * @return a list of {@link Index}es (for example "PRIMARY KEY") of this table.
* Indexes created with column definitions (as in mycol INT PRIMARY KEY) are not - * inserted into this list. + * inserted into this list. When ordered table elements are present, this is a mutable + * view of their indexes. */ public List getIndexes() { return indexes; } public void setIndexes(List list) { - indexes = list; + if (tableElements == null) { + indexes = list; + } else { + TableElementList.replace(tableElements, Index.class, list); + } + } + + /** + * Returns columns, constraints, and indexes in the order in which they were declared. + */ + public List getTableElements() { + return tableElements; + } + + public void setTableElements(List tableElements) { + this.tableElements = tableElements; + if (tableElements == null) { + columnDefinitions = null; + indexes = null; + return; + } + columnDefinitions = new TableElementList<>(tableElements, ColumnDefinition.class); + indexes = new TableElementList<>(tableElements, Index.class); + } + + /** Returns table elements of a requested AST type while preserving their declaration order. */ + public List getTableElements(Class type) { + if (tableElements == null) { + return Collections.emptyList(); + } + List result = new ArrayList<>(); + for (TableElement element : tableElements) { + if (type.isInstance(element)) { + result.add(type.cast(element)); + } + } + return result; } public Select getSelect() { @@ -124,14 +201,58 @@ public void setSelect(Select select, boolean parenthesis) { } public Table getLikeTable() { + if (likeTable != null) { + return likeTable; + } + List clauses = getTableElements(LikeClause.class); + return clauses.isEmpty() ? null : clauses.get(0).getTable(); + } + + /** Returns the legacy trailing LIKE source, excluding LIKE clauses inside the definition. */ + public Table getTrailingLikeTable() { return likeTable; } + public ColDataType getOfType() { + return ofType; + } + + public void setOfType(ColDataType ofType) { + this.ofType = ofType; + } + public void setLikeTable(Table likeTable, boolean parenthesis) { + List clauses = getTableElements(LikeClause.class); + if (this.likeTable == null && !clauses.isEmpty()) { + if (likeTable != null) { + clauses.get(0).setTable(likeTable); + } else { + List elements = new ArrayList<>(tableElements); + elements.remove(clauses.get(0)); + setTableElements(elements); + } + return; + } this.likeTable = likeTable; this.selectParenthesis = parenthesis; } + /** + * BigQuery's {@code CREATE [SNAPSHOT] TABLE target CLONE source [FOR SYSTEM_TIME AS OF ...]}. + */ + public Table getCloneTable() { + return cloneTable; + } + + public void setCloneTable(Table cloneTable) { + this.cloneTable = cloneTable; + } + + public CreateTable withCloneTable(Table cloneTable) { + setCloneTable(cloneTable); + return this; + } + public boolean isIfNotExists() { return ifNotExists; } @@ -148,6 +269,30 @@ public void setOrReplace(boolean orReplace) { this.orReplace = orReplace; } + public TablePartitioning getPartitioning() { + return partitioning; + } + + public void setPartitioning(TablePartitioning partitioning) { + this.partitioning = partitioning; + } + + public Table getPartitionOf() { + return partitionOf; + } + + public void setPartitionOf(Table partitionOf) { + this.partitionOf = partitionOf; + } + + public PartitionBound getPartitionBound() { + return partitionBound; + } + + public void setPartitionBound(PartitionBound partitionBound) { + this.partitionBound = partitionBound; + } + public boolean isSelectParenthesis() { return selectParenthesis; } @@ -170,6 +315,9 @@ public String toString() { StringBuilder b = new StringBuilder(); appendCreateClause(b); appendColumnDefinitions(b); + if (partitionBound != null) { + b.append(" ").append(partitionBound); + } appendTableOptions(b); appendTableProperties(b); return b.toString(); @@ -193,6 +341,15 @@ private void appendCreateClause(StringBuilder b) { b.append("IF NOT EXISTS "); } b.append(table); + if (ofType != null) { + b.append(" OF ").append(ofType); + } + if (partitionOf != null) { + b.append(" PARTITION OF ").append(partitionOf); + } + if (cloneTable != null) { + b.append(" CLONE ").append(cloneTable); + } } private void appendColumnDefinitions(StringBuilder b) { @@ -200,7 +357,11 @@ private void appendColumnDefinitions(StringBuilder b) { b.append(" "); b.append(PlainSelect.getStringList(columns, true, true)); } - if (columnDefinitions != null && !columnDefinitions.isEmpty()) { + if (tableElements != null) { + b.append(" ("); + b.append(PlainSelect.getStringList(tableElements, true, false)); + b.append(")"); + } else if (columnDefinitions != null && !columnDefinitions.isEmpty()) { b.append(" ("); b.append(PlainSelect.getStringList(columnDefinitions, true, false)); if (indexes != null && !indexes.isEmpty()) { @@ -212,10 +373,15 @@ private void appendColumnDefinitions(StringBuilder b) { } private void appendTableOptions(StringBuilder b) { - String options = PlainSelect.getStringList(tableOptionsStrings, false, false); + String options = tableOptions != null + ? PlainSelect.getStringList(tableOptions, false, false) + : PlainSelect.getStringList(tableOptionsStrings, false, false); if (options != null && options.length() > 0) { b.append(" ").append(options); } + if (partitioning != null) { + b.append(" ").append(partitioning); + } } private void appendTableProperties(StringBuilder b) { @@ -272,6 +438,21 @@ public CreateTable withIfNotExists(boolean ifNotExists) { return this; } + public CreateTable withPartitioning(TablePartitioning partitioning) { + this.setPartitioning(partitioning); + return this; + } + + public CreateTable withPartitionOf(Table partitionOf) { + setPartitionOf(partitionOf); + return this; + } + + public CreateTable withPartitionBound(PartitionBound partitionBound) { + setPartitionBound(partitionBound); + return this; + } + public CreateTable withRowMovement(RowMovement rowMovement) { this.setRowMovement(rowMovement); return this; @@ -282,6 +463,11 @@ public CreateTable withTableOptionsStrings(List tableOptionsStrings) { return this; } + public CreateTable withTableOptions(List tableOptions) { + this.setTableOptions(tableOptions); + return this; + } + public CreateTable withColumnDefinitions(List columnDefinitions) { this.setColumnDefinitions(columnDefinitions); return this; @@ -297,6 +483,11 @@ public CreateTable withIndexes(List indexes) { return this; } + public CreateTable withTableElements(List tableElements) { + this.setTableElements(tableElements); + return this; + } + public CreateTable addCreateOptionsStrings(String... createOptionsStrings) { List collection = Optional.ofNullable(getCreateOptionsStrings()).orElseGet(ArrayList::new); diff --git a/src/main/java/net/sf/jsqlparser/statement/create/table/DefaultConstraint.java b/src/main/java/net/sf/jsqlparser/statement/create/table/DefaultConstraint.java new file mode 100644 index 0000000000..fd6daa82a8 --- /dev/null +++ b/src/main/java/net/sf/jsqlparser/statement/create/table/DefaultConstraint.java @@ -0,0 +1,95 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.statement.create.table; + +import java.util.function.Consumer; +import net.sf.jsqlparser.expression.Expression; +import net.sf.jsqlparser.schema.Column; + +/** A SQL Server table-level DEFAULT expression FOR a column, with an optional constraint name. */ +public class DefaultConstraint extends NamedConstraint { + private Expression expression; + private Column column; + private boolean withValues; + + public DefaultConstraint() { + setType("DEFAULT"); + setKind(Kind.DEFAULT); + } + + public Expression getExpression() { + return expression; + } + + public void setExpression(Expression expression) { + this.expression = expression; + } + + public Column getColumn() { + return column; + } + + public void setColumn(Column column) { + this.column = column; + } + + public boolean isWithValues() { + return withValues; + } + + public void setWithValues(boolean withValues) { + this.withValues = withValues; + } + + public DefaultConstraint withExpression(Expression expression) { + setExpression(expression); + return this; + } + + public DefaultConstraint withColumn(Column column) { + setColumn(column); + return this; + } + + public DefaultConstraint withWithValues(boolean withValues) { + setWithValues(withValues); + return this; + } + + @Override + public DefaultConstraint withName(String name) { + setName(name); + return this; + } + + /** Shares rendering with deparsers while allowing both the value and column to be visited. */ + public void appendTo(StringBuilder builder, Consumer expressionWriter) { + if (expression == null || column == null) { + throw new IllegalStateException("DEFAULT requires an expression and a target column"); + } + if (getName() != null) { + builder.append("CONSTRAINT ").append(getName()).append(' '); + } + builder.append("DEFAULT "); + expressionWriter.accept(expression); + builder.append(" FOR "); + expressionWriter.accept(column); + if (withValues) { + builder.append(" WITH VALUES"); + } + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + appendTo(builder, builder::append); + return builder.toString(); + } +} diff --git a/src/main/java/net/sf/jsqlparser/statement/create/table/ExcludeConstraint.java b/src/main/java/net/sf/jsqlparser/statement/create/table/ExcludeConstraint.java index 4cdc37bc4c..f8ed3a836c 100644 --- a/src/main/java/net/sf/jsqlparser/statement/create/table/ExcludeConstraint.java +++ b/src/main/java/net/sf/jsqlparser/statement/create/table/ExcludeConstraint.java @@ -11,6 +11,7 @@ import java.util.Collection; import java.util.List; +import java.util.function.Consumer; import net.sf.jsqlparser.expression.Expression; @@ -18,6 +19,11 @@ public class ExcludeConstraint extends Index { private Expression expression; + public ExcludeConstraint() { + setKind(Kind.EXCLUDE); + setType("EXCLUDE"); + } + public Expression getExpression() { return expression; } @@ -27,12 +33,25 @@ public void setExpression(Expression expression) { } @Override - public String toString() { - StringBuilder exclusionStatement = new StringBuilder("EXCLUDE WHERE "); - exclusionStatement.append("("); - exclusionStatement.append(expression); - exclusionStatement.append(")"); - return exclusionStatement.toString(); + public void appendTo(StringBuilder exclusionStatement, Consumer expressionPrinter) { + if (getName() != null) { + exclusionStatement.append("CONSTRAINT ").append(getName()).append(' '); + } + exclusionStatement.append("EXCLUDE"); + if (getUsing() != null) { + exclusionStatement.append(" USING ").append(getUsing()); + } + if (getColumns() != null) { + exclusionStatement.append(' '); + appendColumnsTo(exclusionStatement, expressionPrinter); + } + appendConstraintOptionsTo(exclusionStatement, expressionPrinter); + if (expression != null) { + exclusionStatement.append(" WHERE ("); + expressionPrinter.accept(expression); + exclusionStatement.append(')'); + } + appendConstraintAttributesTo(exclusionStatement); } public ExcludeConstraint withExpression(Expression expression) { diff --git a/src/main/java/net/sf/jsqlparser/statement/create/table/ForeignKeyIndex.java b/src/main/java/net/sf/jsqlparser/statement/create/table/ForeignKeyIndex.java index c877a1ad9e..869bf79693 100644 --- a/src/main/java/net/sf/jsqlparser/statement/create/table/ForeignKeyIndex.java +++ b/src/main/java/net/sf/jsqlparser/statement/create/table/ForeignKeyIndex.java @@ -20,6 +20,8 @@ import java.util.Collections; import java.util.LinkedHashSet; import java.util.List; +import java.util.function.Consumer; +import net.sf.jsqlparser.expression.Expression; import java.util.Optional; import java.util.Set; @@ -28,21 +30,58 @@ public class ForeignKeyIndex extends NamedConstraint { private Table table; private List referencedColumnNames; private Set referentialActions = new LinkedHashSet<>(2); + private ForeignKeyReference reference; + + public ForeignKeyReference getReference() { + if (reference == null) { + reference = new ForeignKeyReference(); + reference.setTable(table); + reference.setReferencedColumnNames(referencedColumnNames); + for (ReferentialAction action : referentialActions) { + reference.setReferentialAction(action.getType(), action.getAction()); + } + } + return reference; + } + + public void setReference(ForeignKeyReference reference) { + this.reference = reference; + if (reference != null) { + table = reference.getTable(); + referencedColumnNames = reference.getReferencedColumnNames(); + referentialActions.clear(); + referentialActions.addAll(reference.getReferentialActions()); + } + } + + public ForeignKeyReference.MatchType getMatchType() { + return reference != null ? reference.getMatchType() : null; + } + + public void setMatchType(ForeignKeyReference.MatchType matchType) { + getReference().setMatchType(matchType); + } public Table getTable() { - return table; + return reference != null ? reference.getTable() : table; } public void setTable(Table table) { this.table = table; + if (reference != null) { + reference.setTable(table); + } } public List getReferencedColumnNames() { - return referencedColumnNames; + return reference != null ? reference.getReferencedColumnNames() : referencedColumnNames; } public void setReferencedColumnNames(List referencedColumnNames) { this.referencedColumnNames = referencedColumnNames; + if (reference != null) { + reference.setReferencedColumnNames(referencedColumnNames); + } } /** @@ -70,6 +109,9 @@ public void removeReferentialAction(Type type) { * @return */ public ReferentialAction getReferentialAction(Type type) { + if (reference != null) { + return reference.getReferentialAction(type); + } return referentialActions.stream().filter(ra -> type.equals(ra.getType())).findFirst() .orElse(null); } @@ -77,6 +119,10 @@ public ReferentialAction getReferentialAction(Type type) { private void setReferentialAction(Type type, Action action, boolean set) { ReferentialAction found = getReferentialAction(type); if (set) { + if (reference != null) { + reference.setReferentialAction(type, action); + return; + } if (found == null) { referentialActions.add(new ReferentialAction(type, action)); } else { @@ -84,6 +130,9 @@ private void setReferentialAction(Type type, Action action, boolean set) { } } else if (found != null) { referentialActions.remove(found); + if (reference != null) { + reference.removeReferentialAction(type); + } } } @@ -118,11 +167,18 @@ public void setOnUpdateReferenceOption(String onUpdateReferenceOption) { } @Override - public String toString() { - StringBuilder b = new StringBuilder(super.toString()).append(" REFERENCES ").append(table) - .append(PlainSelect.getStringList(getReferencedColumnNames(), true, true)); - referentialActions.forEach(b::append); - return b.toString(); + public void appendTo(StringBuilder b, Consumer expressionPrinter) { + super.appendTo(b, expressionPrinter); + b.append(' '); + if (reference != null) { + b.append(reference); + } else { + b.append("REFERENCES ").append(table) + .append(PlainSelect.getStringList(getReferencedColumnNames(), true, true)); + referentialActions.forEach(b::append); + } + appendConstraintSuffixTo(b); + appendConstraintAttributesTo(b); } public ForeignKeyIndex withTable(Table table) { @@ -135,6 +191,16 @@ public ForeignKeyIndex withReferencedColumnNames(List referencedColumnNa return this; } + public ForeignKeyIndex withReference(ForeignKeyReference reference) { + setReference(reference); + return this; + } + + public ForeignKeyIndex withMatchType(ForeignKeyReference.MatchType matchType) { + setMatchType(matchType); + return this; + } + public ForeignKeyIndex withOnDeleteReferenceOption(String onDeleteReferenceOption) { this.setOnDeleteReferenceOption(onDeleteReferenceOption); return this; @@ -169,6 +235,16 @@ public ForeignKeyIndex withUsing(String using) { return (ForeignKeyIndex) super.withUsing(using); } + @Override + public ForeignKeyIndex withIndexName(String indexName) { + return (ForeignKeyIndex) super.withIndexName(indexName); + } + + @Override + public ForeignKeyIndex withUseConstraintKeyword(boolean useConstraintKeyword) { + return (ForeignKeyIndex) super.withUseConstraintKeyword(useConstraintKeyword); + } + @Override public ForeignKeyIndex withName(List name) { return (ForeignKeyIndex) super.withName(name); diff --git a/src/main/java/net/sf/jsqlparser/statement/create/table/ForeignKeyReference.java b/src/main/java/net/sf/jsqlparser/statement/create/table/ForeignKeyReference.java new file mode 100644 index 0000000000..607a9d0df7 --- /dev/null +++ b/src/main/java/net/sf/jsqlparser/statement/create/table/ForeignKeyReference.java @@ -0,0 +1,135 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.statement.create.table; + +import java.io.Serializable; +import java.util.LinkedHashSet; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import net.sf.jsqlparser.schema.Table; +import net.sf.jsqlparser.statement.ReferentialAction; +import net.sf.jsqlparser.statement.ReferentialAction.Action; +import net.sf.jsqlparser.statement.ReferentialAction.Type; +import net.sf.jsqlparser.statement.select.PlainSelect; + +/** The target and actions of a column- or table-level foreign-key reference. */ +public class ForeignKeyReference implements Serializable { + + public enum MatchType { + FULL, PARTIAL, SIMPLE + } + + private Table table; + private List referencedColumnNames; + private MatchType matchType; + private final Set referentialActions = new LinkedHashSet<>(2); + + public Table getTable() { + return table; + } + + public void setTable(Table table) { + this.table = table; + } + + public List getReferencedColumnNames() { + return referencedColumnNames; + } + + public void setReferencedColumnNames(List referencedColumnNames) { + this.referencedColumnNames = referencedColumnNames; + } + + public MatchType getMatchType() { + return matchType; + } + + public void setMatchType(MatchType matchType) { + this.matchType = matchType; + } + + public Set getReferentialActions() { + return referentialActions; + } + + public ReferentialAction getReferentialAction(Type type) { + return referentialActions.stream().filter(action -> type.equals(action.getType())) + .findFirst() + .orElse(null); + } + + public void setReferentialAction(Type type, Action action) { + ReferentialAction current = getReferentialAction(type); + if (current == null) { + referentialActions.add(new ReferentialAction(type, action)); + } else { + current.setAction(action); + } + } + + public void removeReferentialAction(Type type) { + ReferentialAction current = getReferentialAction(type); + if (current != null) { + referentialActions.remove(current); + } + } + + public ForeignKeyReference withTable(Table table) { + setTable(table); + return this; + } + + public ForeignKeyReference withReferencedColumnNames(List referencedColumnNames) { + setReferencedColumnNames(referencedColumnNames); + return this; + } + + public ForeignKeyReference withMatchType(MatchType matchType) { + setMatchType(matchType); + return this; + } + + public ForeignKeyReference withReferentialAction(Type type, Action action) { + setReferentialAction(type, action); + return this; + } + + public ForeignKeyReference addReferencedColumnNames(String... referencedColumnNames) { + List collection = Optional.ofNullable(getReferencedColumnNames()) + .orElseGet(ArrayList::new); + Collections.addAll(collection, referencedColumnNames); + return withReferencedColumnNames(collection); + } + + public ForeignKeyReference addReferencedColumnNames( + Collection referencedColumnNames) { + List collection = Optional.ofNullable(getReferencedColumnNames()) + .orElseGet(ArrayList::new); + collection.addAll(referencedColumnNames); + return withReferencedColumnNames(collection); + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder("REFERENCES ").append(table); + if (referencedColumnNames != null) { + builder.append(PlainSelect.getStringList(referencedColumnNames, true, true)); + } + if (matchType != null) { + builder.append(" MATCH ").append(matchType); + } + referentialActions.forEach(builder::append); + return builder.toString(); + } +} diff --git a/src/main/java/net/sf/jsqlparser/statement/create/table/GeneratedColumnDefinition.java b/src/main/java/net/sf/jsqlparser/statement/create/table/GeneratedColumnDefinition.java new file mode 100644 index 0000000000..cfc6bce5ad --- /dev/null +++ b/src/main/java/net/sf/jsqlparser/statement/create/table/GeneratedColumnDefinition.java @@ -0,0 +1,90 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.statement.create.table; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.function.Consumer; +import net.sf.jsqlparser.expression.Expression; + +/** A parenthesized generated column expression, distinct from an identity declaration. */ +public class GeneratedColumnDefinition implements Serializable { + public enum Storage { + STORED, VIRTUAL + } + + private Expression expression; + private boolean generatedAlways; + private Storage storage; + + public GeneratedColumnDefinition(Expression expression) { + setExpression(expression); + } + + public Expression getExpression() { + return expression; + } + + public void setExpression(Expression expression) { + this.expression = Objects.requireNonNull(expression, "expression"); + } + + public boolean isGeneratedAlways() { + return generatedAlways; + } + + public void setGeneratedAlways(boolean generatedAlways) { + this.generatedAlways = generatedAlways; + } + + /** Returns null when the SQL does not specify STORED or VIRTUAL. */ + public Storage getStorage() { + return storage; + } + + public void setStorage(Storage storage) { + this.storage = storage; + } + + public List getTokens() { + List tokens = new ArrayList<>(); + if (generatedAlways) { + tokens.add("GENERATED"); + tokens.add("ALWAYS"); + } + tokens.add("AS"); + tokens.add("(" + expression + ")"); + if (storage != null) { + tokens.add(storage.name()); + } + return tokens; + } + + public StringBuilder appendTo(StringBuilder builder, Consumer expressionPrinter) { + if (generatedAlways) { + builder.append("GENERATED ALWAYS "); + } + builder.append("AS ("); + expressionPrinter.accept(expression); + builder.append(')'); + if (storage != null) { + builder.append(' ').append(storage); + } + return builder; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + return appendTo(builder, builder::append).toString(); + } +} diff --git a/src/main/java/net/sf/jsqlparser/statement/create/table/IdentityDefinition.java b/src/main/java/net/sf/jsqlparser/statement/create/table/IdentityDefinition.java new file mode 100644 index 0000000000..02ed06a4f0 --- /dev/null +++ b/src/main/java/net/sf/jsqlparser/statement/create/table/IdentityDefinition.java @@ -0,0 +1,59 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.statement.create.table; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; +import net.sf.jsqlparser.schema.Sequence; +import net.sf.jsqlparser.statement.select.PlainSelect; + +/** The generation mode and sequence parameters of a GENERATED ... AS IDENTITY clause. */ +public class IdentityDefinition implements Serializable { + public enum GenerationMode { + ALWAYS, BY_DEFAULT + } + + private GenerationMode generationMode; + private List parameters; + + public IdentityDefinition(GenerationMode generationMode) { + this.generationMode = generationMode; + } + + public GenerationMode getGenerationMode() { + return generationMode; + } + + public void setGenerationMode(GenerationMode generationMode) { + this.generationMode = generationMode; + } + + /** Returns null when no parenthesized sequence options were specified. */ + public List getParameters() { + return parameters; + } + + public void setParameters(List parameters) { + this.parameters = parameters == null ? null : new ArrayList<>(parameters); + } + + @Override + public String toString() { + StringBuilder sql = new StringBuilder("GENERATED "); + sql.append(generationMode == GenerationMode.ALWAYS ? "ALWAYS" : "BY DEFAULT") + .append(" AS IDENTITY"); + if (parameters != null) { + sql.append(" (").append(PlainSelect.getStringList(parameters, false, false)) + .append(')'); + } + return sql.toString(); + } +} diff --git a/src/main/java/net/sf/jsqlparser/statement/create/table/Index.java b/src/main/java/net/sf/jsqlparser/statement/create/table/Index.java index 114b30d147..bb60df7a02 100644 --- a/src/main/java/net/sf/jsqlparser/statement/create/table/Index.java +++ b/src/main/java/net/sf/jsqlparser/statement/create/table/Index.java @@ -17,10 +17,19 @@ import java.util.Collections; import java.util.List; import java.util.Optional; +import java.util.function.Consumer; import net.sf.jsqlparser.expression.Expression; import net.sf.jsqlparser.statement.select.PlainSelect; -public class Index implements Serializable { +public class Index implements TableElement, Serializable { + + public enum Kind { + PRIMARY_KEY, UNIQUE, INDEX, FULLTEXT, SPATIAL, FOREIGN_KEY, CHECK, EXCLUDE, DEFAULT, OTHER + } + + public enum Clustering { + CLUSTERED, NONCLUSTERED + } private final List name = new ArrayList<>(); private String type; @@ -29,13 +38,118 @@ public class Index implements Serializable { private List idxSpec; private String commentText; private String indexKeyword; + private Kind kind = Kind.OTHER; + private Clustering clustering; + private Boolean nullsDistinct; + private List includeColumns; + private List
tables, Consumer expressions) { + if (table != null) { + tables.accept(table); + } + if (referencedTable != null) { + tables.accept(referencedTable); + } + for (TriggerEvent triggerEvent : events) { + if (triggerEvent.getColumns() != null) { + triggerEvent.getColumns().forEach(expressions); + } + } + if (whenExpression != null) { + expressions.accept(whenExpression); + } + if (routine != null) { + expressions.accept(routine); + } + } + + public void appendTo(StringBuilder sql, Consumer visitor) { + // MySQL's shorter form has neither a routine nor any of the standard clauses + if (routine == null && transitionRelations.isEmpty() && !constraint + && orientation != Orientation.STATEMENT) { + sql.append(mysqlSql()); + return; + } + appendPostgreSqlHeader(sql, visitor); + appendPostgreSqlTarget(sql); + if (whenExpression != null) { + sql.append(" WHEN ("); + visitor.accept(whenExpression); + sql.append(')'); + } + if (routine != null) { + sql.append(" EXECUTE ").append(executeKeyword).append(' '); + visitor.accept(routine); + } else { + // DuckDB 2.0 runs a statement rather than a function + sql.append(' ').append(body); + } + } + + private void appendPostgreSqlHeader(StringBuilder sql, Consumer visitor) { + sql.append("CREATE "); + if (orReplace) { + sql.append("OR REPLACE "); + } + if (constraint) { + sql.append("CONSTRAINT "); + } + sql.append("TRIGGER ").append(trigger).append(' ').append(timing.name().replace('_', ' ')) + .append(' '); + for (int i = 0; i < events.size(); i++) { + if (i > 0) { + sql.append(" OR "); + } + events.get(i).appendTo(sql, visitor); + } + } + + private void appendPostgreSqlTarget(StringBuilder sql) { + sql.append(" ON ").append(table); + if (referencedTable != null) { + sql.append(" FROM ").append(referencedTable); + } + constraintAttributes.appendTo(sql); + if (!transitionRelations.isEmpty()) { + sql.append(" REFERENCING"); + for (TransitionRelation relation : transitionRelations) { + sql.append(' ').append(relation); + } + } + if (orientation != null) { + sql.append(" FOR ").append(useEach ? "EACH " : "").append(orientation); + } + } + + @Override + public String toString() { + StringBuilder sql = new StringBuilder(); + appendTo(sql, sql::append); + return sql.toString(); + } + + private String mysqlSql() { + StringBuilder builder = new StringBuilder("CREATE "); + if (definer != null) { + builder.append("DEFINER = ").append(definer).append(" "); + } + builder.append("TRIGGER ").append(trigger).append(" ").append(timing).append(" ") + .append(getEvent()).append(" ON ").append(table).append(" FOR EACH ROW "); + if (order != null) { + builder.append(order).append(" ").append(otherTrigger).append(" "); + } + return builder.append(body).toString(); + } +} diff --git a/src/main/java/net/sf/jsqlparser/statement/create/trigger/TransitionRelation.java b/src/main/java/net/sf/jsqlparser/statement/create/trigger/TransitionRelation.java new file mode 100644 index 0000000000..6f5eb77c5e --- /dev/null +++ b/src/main/java/net/sf/jsqlparser/statement/create/trigger/TransitionRelation.java @@ -0,0 +1,51 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.statement.create.trigger; + +import java.io.Serializable; + +public class TransitionRelation implements Serializable { + public enum Image { + OLD, NEW + } + + private Image image; + private String name; + private boolean useAs; + + public Image getImage() { + return image; + } + + public void setImage(Image image) { + this.image = image; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public boolean isUseAs() { + return useAs; + } + + public void setUseAs(boolean useAs) { + this.useAs = useAs; + } + + @Override + public String toString() { + return image + " TABLE " + (useAs ? "AS " : "") + name; + } +} diff --git a/src/main/java/net/sf/jsqlparser/statement/create/trigger/TriggerDefiner.java b/src/main/java/net/sf/jsqlparser/statement/create/trigger/TriggerDefiner.java new file mode 100644 index 0000000000..a4ba3e14ec --- /dev/null +++ b/src/main/java/net/sf/jsqlparser/statement/create/trigger/TriggerDefiner.java @@ -0,0 +1,55 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.statement.create.trigger; + +import java.io.Serializable; +import net.sf.jsqlparser.expression.StringValue; + +/** Structured account used by a MySQL trigger {@code DEFINER} clause. */ +public class TriggerDefiner implements Serializable { + + private StringValue user; + private StringValue host; + + public StringValue getUser() { + return user; + } + + public void setUser(StringValue user) { + this.user = user; + } + + public StringValue getHost() { + return host; + } + + public void setHost(StringValue host) { + this.host = host; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder().append(user); + if (host != null) { + builder.append("@").append(host); + } + return builder.toString(); + } + + public TriggerDefiner withUser(StringValue user) { + setUser(user); + return this; + } + + public TriggerDefiner withHost(StringValue host) { + setHost(host); + return this; + } +} diff --git a/src/main/java/net/sf/jsqlparser/statement/create/trigger/TriggerEvent.java b/src/main/java/net/sf/jsqlparser/statement/create/trigger/TriggerEvent.java new file mode 100644 index 0000000000..da7c35bff9 --- /dev/null +++ b/src/main/java/net/sf/jsqlparser/statement/create/trigger/TriggerEvent.java @@ -0,0 +1,61 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.statement.create.trigger; + +import java.io.Serializable; +import java.util.function.Consumer; +import net.sf.jsqlparser.expression.Expression; +import net.sf.jsqlparser.schema.Column; +import net.sf.jsqlparser.expression.operators.relational.ExpressionList; + +public class TriggerEvent implements Serializable { + private CreateTrigger.Event event; + private ExpressionList columns; + + public CreateTrigger.Event getEvent() { + return event; + } + + public void setEvent(CreateTrigger.Event event) { + this.event = event; + } + + public ExpressionList getColumns() { + return columns; + } + + public void setColumns(ExpressionList columns) { + this.columns = columns; + } + + public TriggerEvent(CreateTrigger.Event event) { + this.event = event; + } + + public void appendTo(StringBuilder sql, Consumer visitor) { + sql.append(event); + if (columns != null) { + sql.append(" OF "); + for (int i = 0; i < columns.size(); i++) { + if (i > 0) { + sql.append(", "); + } + visitor.accept(columns.get(i)); + } + } + } + + @Override + public String toString() { + StringBuilder sql = new StringBuilder(); + appendTo(sql, sql::append); + return sql.toString(); + } +} diff --git a/src/main/java/net/sf/jsqlparser/statement/create/type/CompositeTypeDefinition.java b/src/main/java/net/sf/jsqlparser/statement/create/type/CompositeTypeDefinition.java new file mode 100644 index 0000000000..ae785207ef --- /dev/null +++ b/src/main/java/net/sf/jsqlparser/statement/create/type/CompositeTypeDefinition.java @@ -0,0 +1,32 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.statement.create.type; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +public class CompositeTypeDefinition implements TypeDefinition { + private List attributes = new ArrayList<>(); + + public List getAttributes() { + return attributes; + } + + public void setAttributes(List attributes) { + this.attributes = attributes; + } + + @Override + public String toString() { + return "(" + attributes.stream().map(Object::toString).collect(Collectors.joining(", ")) + + ")"; + } +} diff --git a/src/main/java/net/sf/jsqlparser/statement/create/type/CreateType.java b/src/main/java/net/sf/jsqlparser/statement/create/type/CreateType.java new file mode 100644 index 0000000000..891c5bdb0c --- /dev/null +++ b/src/main/java/net/sf/jsqlparser/statement/create/type/CreateType.java @@ -0,0 +1,45 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.statement.create.type; + + +import net.sf.jsqlparser.statement.Statement; +import net.sf.jsqlparser.statement.StatementVisitor; + +public class CreateType implements Statement { + private String name; + private TypeDefinition definition; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public TypeDefinition getDefinition() { + return definition; + } + + public void setDefinition(TypeDefinition definition) { + this.definition = definition; + } + + @Override + public T accept(StatementVisitor visitor, S context) { + return visitor.visit(this, context); + } + + @Override + public String toString() { + return "CREATE TYPE " + name + (definition == null ? "" : " AS " + definition); + } +} diff --git a/src/main/java/net/sf/jsqlparser/statement/create/type/EnumTypeDefinition.java b/src/main/java/net/sf/jsqlparser/statement/create/type/EnumTypeDefinition.java new file mode 100644 index 0000000000..2cb3f062e1 --- /dev/null +++ b/src/main/java/net/sf/jsqlparser/statement/create/type/EnumTypeDefinition.java @@ -0,0 +1,33 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.statement.create.type; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; +import net.sf.jsqlparser.expression.StringValue; + +public class EnumTypeDefinition implements TypeDefinition { + private List labels = new ArrayList<>(); + + public List getLabels() { + return labels; + } + + public void setLabels(List labels) { + this.labels = labels; + } + + @Override + public String toString() { + return "ENUM (" + labels.stream().map(Object::toString).collect(Collectors.joining(", ")) + + ")"; + } +} diff --git a/src/main/java/net/sf/jsqlparser/statement/create/type/RangeTypeDefinition.java b/src/main/java/net/sf/jsqlparser/statement/create/type/RangeTypeDefinition.java new file mode 100644 index 0000000000..56ab0cdf1d --- /dev/null +++ b/src/main/java/net/sf/jsqlparser/statement/create/type/RangeTypeDefinition.java @@ -0,0 +1,76 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.statement.create.type; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; +import net.sf.jsqlparser.statement.create.table.ColDataType; + +public class RangeTypeDefinition implements TypeDefinition { + private List
tables; private List usingFromItemList; @@ -45,6 +48,7 @@ public class Delete implements Statement { private Expression where; private PreferringClause preferringClause; private Limit limit; + private OptionClause option; private List orderByElements; private boolean hasFrom = true; private DeleteModifierPriority modifierPriority; @@ -111,6 +115,19 @@ public T accept(StatementVisitor statementVisitor, S context) { return statementVisitor.visit(this, context); } + public ForPortionClause getForPortionClause() { + return forPortionClause; + } + + public void setForPortionClause(ForPortionClause forPortionClause) { + this.forPortionClause = forPortionClause; + } + + public Delete withForPortionClause(ForPortionClause forPortionClause) { + setForPortionClause(forPortionClause); + return this; + } + public Table getTable() { return table; } @@ -147,6 +164,19 @@ public Limit getLimit() { return limit; } + public OptionClause getOption() { + return option; + } + + public Delete setOption(OptionClause option) { + this.option = option; + return this; + } + + public Delete withOption(OptionClause option) { + return setOption(option); + } + public void setLimit(Limit limit) { this.limit = limit; } @@ -246,6 +276,9 @@ public String toString() { b.append(" FROM"); } b.append(" ").append(table); + if (forPortionClause != null) { + b.append(' ').append(forPortionClause); + } if (usingFromItemList != null && !usingFromItemList.isEmpty()) { b.append(" USING "); @@ -280,6 +313,10 @@ public String toString() { b.append(limit); } + if (option != null) { + b.append(option); + } + if (returningClause != null) { returningClause.appendTo(b); } diff --git a/src/main/java/net/sf/jsqlparser/statement/drop/Drop.java b/src/main/java/net/sf/jsqlparser/statement/drop/Drop.java index a7ac362535..7b7e592321 100644 --- a/src/main/java/net/sf/jsqlparser/statement/drop/Drop.java +++ b/src/main/java/net/sf/jsqlparser/statement/drop/Drop.java @@ -14,8 +14,9 @@ import java.util.Collections; import java.util.HashMap; import java.util.List; +import java.util.Locale; import java.util.Map; -import java.util.Optional; +import java.util.function.Consumer; import net.sf.jsqlparser.schema.Table; import net.sf.jsqlparser.statement.Statement; @@ -24,9 +25,16 @@ public class Drop implements Statement { + public enum ObjectType { + DATABASE, EVENT, FUNCTION, INDEX, PROCEDURE, SCHEMA, SEQUENCE, SERVER, TABLE, TABLESPACE, TRIGGER, VIEW, OTHER + } + private String type; - private Table name; + private ObjectType objectType = ObjectType.OTHER; + private final List
names = new ArrayList<>(); private List parameters; + private Table table; + private int tablePosition; private Map> typeToParameters = new HashMap<>(); private boolean ifExists = false; private boolean materialized = false; @@ -46,19 +54,76 @@ public T accept(StatementVisitor statementVisitor, S context) { } public Table getName() { - return name; + return names.isEmpty() ? null : names.get(0); + } + + public void setName(Table name) { + names.clear(); + if (name != null) { + names.add(name); + } + } + + public List
getNames() { + return Collections.unmodifiableList(names); } - public void setName(Table string) { - name = string; + public void setNames(Collection names) { + this.names.clear(); + if (names != null) { + this.names.addAll(names); + } } + /** Returns legacy tokens, including ON and its owner. Structured owners produce a snapshot. */ public List getParameters() { - return parameters; + if (table == null) { + return parameters; + } + List tokens = parameters == null ? new ArrayList<>() : new ArrayList<>(parameters); + tokens.add(tablePosition, "ON"); + tokens.add(tablePosition + 1, table.toString()); + return tokens; } + /** The table explicitly named by DROP INDEX ... ON, or null when SQL does not name one. */ + public Table getTable() { + return table; + } + + public void setTable(Table table) { + setTable(table, this.table == null ? 0 : tablePosition); + } + + /** Places ON among the remaining parameter tokens, preserving their source order. */ + public void setTable(Table table, int parameterPosition) { + int size = parameters == null ? 0 : parameters.size(); + if (parameterPosition < 0 || parameterPosition > size) { + throw new IllegalArgumentException("Invalid ON table position: " + parameterPosition); + } + this.table = table; + tablePosition = table == null ? 0 : parameterPosition; + } + + public Drop withTable(Table table) { + setTable(table); + return this; + } + + /** Visits table/view targets and explicit index owners without resolving catalog objects. */ + public void visitTables(Consumer
visitor) { + if (objectType == ObjectType.TABLE || objectType == ObjectType.VIEW) { + names.forEach(visitor); + } else if (objectType == ObjectType.INDEX && table != null) { + visitor.accept(table); + } + } + + /** Replaces all legacy parameters and clears the structured ON table. */ public void setParameters(List list) { parameters = list; + table = null; + tablePosition = 0; } public String getType() { @@ -67,6 +132,22 @@ public String getType() { public void setType(String string) { type = string; + try { + objectType = ObjectType.valueOf(string.toUpperCase(Locale.ROOT)); + } catch (IllegalArgumentException | NullPointerException ignored) { + objectType = ObjectType.OTHER; + } + } + + public ObjectType getObjectType() { + return objectType; + } + + public void setObjectType(ObjectType objectType) { + this.objectType = objectType == null ? ObjectType.OTHER : objectType; + if (this.objectType != ObjectType.OTHER) { + this.type = this.objectType.name(); + } } public boolean isIfExists() { @@ -108,21 +189,51 @@ public void setTypeToParameters(Map> typeToParameters) { @Override public String toString() { - String sql = "DROP " - + (isUsingTemporary ? "TEMPORARY " : "") - + (materialized ? "MATERIALIZED " : "") - + type + " " - + (ifExists ? "IF EXISTS " : "") + name.toString(); - - if (type.equals("FUNCTION")) { - sql += formatFuncParams(getParamsByType("FUNCTION")); + StringBuilder builder = new StringBuilder(); + return appendTo(builder, builder::append).toString(); + } + + public StringBuilder appendTo(StringBuilder builder, Consumer
tablePrinter) { + builder.append("DROP "); + if (isUsingTemporary) { + builder.append("TEMPORARY "); + } + if (materialized) { + builder.append("MATERIALIZED "); + } + builder.append(type).append(ifExists ? " IF EXISTS " : " "); + appendNames(builder, tablePrinter); + if ("FUNCTION".equals(type)) { + builder.append(formatFuncParams(getParamsByType("FUNCTION"))); } + appendParameters(builder, tablePrinter); + return builder; + } - if (parameters != null && !parameters.isEmpty()) { - sql += " " + PlainSelect.getStringList(parameters, false, false); + private void appendNames(StringBuilder builder, Consumer
tablePrinter) { + for (int i = 0; i < names.size(); i++) { + if (i > 0) { + builder.append(", "); + } + if (objectType == ObjectType.TABLE || objectType == ObjectType.VIEW) { + tablePrinter.accept(names.get(i)); + } else { + builder.append(names.get(i)); + } } + } - return sql; + private void appendParameters(StringBuilder builder, Consumer
tablePrinter) { + int size = parameters == null ? 0 : parameters.size(); + for (int i = 0; i <= size; i++) { + if (table != null && i == tablePosition) { + builder.append(" ON "); + tablePrinter.accept(table); + } + if (i < size) { + builder.append(' ').append(parameters.get(i)); + } + } } public List getParamsByType(String type) { @@ -149,20 +260,36 @@ public Drop withName(Table name) { return this; } + public Drop withNames(Collection names) { + setNames(names); + return this; + } + + public Drop addNames(Table... names) { + Collections.addAll(this.names, names); + return this; + } + + public Drop withObjectType(ObjectType objectType) { + setObjectType(objectType); + return this; + } + public Drop withParameters(List parameters) { this.setParameters(parameters); return this; } public Drop addParameters(String... parameters) { - List collection = Optional.ofNullable(getParameters()).orElseGet(ArrayList::new); - Collections.addAll(collection, parameters); - return this.withParameters(collection); + return addParameters(java.util.Arrays.asList(parameters)); } + /** Appends trailing tokens without discarding a structured ON table. */ public Drop addParameters(Collection parameters) { - List collection = Optional.ofNullable(getParameters()).orElseGet(ArrayList::new); - collection.addAll(parameters); - return this.withParameters(collection); + if (this.parameters == null) { + this.parameters = new ArrayList<>(); + } + this.parameters.addAll(parameters); + return this; } } diff --git a/src/main/java/net/sf/jsqlparser/statement/execute/Execute.java b/src/main/java/net/sf/jsqlparser/statement/execute/Execute.java index f29ecb2169..cb2d8439f4 100644 --- a/src/main/java/net/sf/jsqlparser/statement/execute/Execute.java +++ b/src/main/java/net/sf/jsqlparser/statement/execute/Execute.java @@ -10,11 +10,13 @@ package net.sf.jsqlparser.statement.execute; import java.util.Locale; +import java.util.StringJoiner; +import java.util.function.Consumer; +import net.sf.jsqlparser.expression.Expression; import net.sf.jsqlparser.expression.operators.relational.ExpressionList; import net.sf.jsqlparser.expression.operators.relational.ParenthesedExpressionList; import net.sf.jsqlparser.statement.Statement; import net.sf.jsqlparser.statement.StatementVisitor; -import net.sf.jsqlparser.statement.select.PlainSelect; import java.util.List; @@ -33,13 +35,11 @@ public void setName(String name) { } public void setName(List names) { + StringJoiner qualifiedName = new StringJoiner("."); for (String item : names) { - if (this.name != null) { - this.name = this.name + "." + item; - } else { - this.name = item; - } + qualifiedName.add(item == null ? "" : item); } + this.name = qualifiedName.toString(); } public ExpressionList getExprList() { @@ -70,11 +70,32 @@ public T accept(StatementVisitor statementVisitor, S context) { @Override public String toString() { - return execType.name() + " " + name - + (exprList != null - ? " " + PlainSelect.getStringList(exprList, true, - exprList instanceof ParenthesedExpressionList) - : ""); + StringBuilder builder = new StringBuilder(); + return appendTo(builder, builder::append).toString(); + } + + public StringBuilder appendTo(StringBuilder builder, Consumer expressionPrinter) { + if (execType != ExecType.IMPLICIT) { + builder.append(execType.name()).append(' '); + } + builder.append(name); + if (exprList != null) { + builder.append(' '); + boolean brackets = exprList instanceof ParenthesedExpressionList; + if (brackets) { + builder.append('('); + } + for (int i = 0; i < exprList.size(); i++) { + if (i > 0) { + builder.append(", "); + } + expressionPrinter.accept((Expression) exprList.get(i)); + } + if (brackets) { + builder.append(')'); + } + } + return builder; } public Execute withExecType(ExecType execType) { @@ -93,7 +114,7 @@ public Execute withExprList(ExpressionList exprList) { } public enum ExecType { - EXECUTE, EXEC, CALL; + EXECUTE, EXEC, CALL, IMPLICIT; public static ExecType from(String type) { return Enum.valueOf(ExecType.class, type.toUpperCase(Locale.ROOT)); diff --git a/src/main/java/net/sf/jsqlparser/statement/execute/ExecuteArgument.java b/src/main/java/net/sf/jsqlparser/statement/execute/ExecuteArgument.java new file mode 100644 index 0000000000..9ed550a6e4 --- /dev/null +++ b/src/main/java/net/sf/jsqlparser/statement/execute/ExecuteArgument.java @@ -0,0 +1,62 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.statement.execute; + +import java.util.Objects; +import java.util.function.Consumer; +import net.sf.jsqlparser.expression.Expression; +import net.sf.jsqlparser.expression.ExpressionVisitor; +import net.sf.jsqlparser.parser.ASTNodeAccessImpl; + +/** An EXEC argument with a call-specific OUTPUT modifier. */ +public class ExecuteArgument extends ASTNodeAccessImpl implements Expression { + private Expression expression; + private boolean output; + + public ExecuteArgument(Expression expression, boolean output) { + setExpression(expression); + this.output = output; + } + + public Expression getExpression() { + return expression; + } + + public void setExpression(Expression expression) { + this.expression = Objects.requireNonNull(expression, "expression"); + } + + public boolean isOutput() { + return output; + } + + public void setOutput(boolean output) { + this.output = output; + } + + @Override + public T accept(ExpressionVisitor visitor, S context) { + return visitor.visit(this, context); + } + + public StringBuilder appendTo(StringBuilder builder, Consumer expressionPrinter) { + expressionPrinter.accept(expression); + if (output) { + builder.append(" OUTPUT"); + } + return builder; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + return appendTo(builder, builder::append).toString(); + } +} diff --git a/src/main/java/net/sf/jsqlparser/statement/export/ExportDataStatement.java b/src/main/java/net/sf/jsqlparser/statement/export/ExportDataStatement.java new file mode 100644 index 0000000000..e9571a397a --- /dev/null +++ b/src/main/java/net/sf/jsqlparser/statement/export/ExportDataStatement.java @@ -0,0 +1,90 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.statement.export; + +import net.sf.jsqlparser.expression.Expression; +import net.sf.jsqlparser.expression.operators.relational.ExpressionList; +import net.sf.jsqlparser.statement.Statement; +import net.sf.jsqlparser.statement.StatementVisitor; +import net.sf.jsqlparser.statement.select.Select; + +/** + * BigQuery's {@code EXPORT DATA [WITH CONNECTION connection] OPTIONS (option_list) AS query}. + * + * @see Export + * statements + */ +public class ExportDataStatement implements Statement { + private String connectionName; + private ExpressionList options; + private Select select; + + public String getConnectionName() { + return connectionName; + } + + public void setConnectionName(String connectionName) { + this.connectionName = connectionName; + } + + public ExportDataStatement withConnectionName(String connectionName) { + setConnectionName(connectionName); + return this; + } + + public ExpressionList getOptions() { + return options; + } + + public void setOptions(ExpressionList options) { + this.options = options; + } + + public ExportDataStatement withOptions(ExpressionList options) { + setOptions(options); + return this; + } + + public Select getSelect() { + return select; + } + + public void setSelect(Select select) { + this.select = select; + } + + public ExportDataStatement withSelect(Select select) { + setSelect(select); + return this; + } + + public StringBuilder appendTo(StringBuilder builder) { + builder.append("EXPORT DATA"); + if (connectionName != null) { + builder.append(" WITH CONNECTION ").append(connectionName); + } + if (options != null) { + builder.append(" OPTIONS (").append(options).append(")"); + } + builder.append(" AS ").append(select); + return builder; + } + + @Override + public String toString() { + return appendTo(new StringBuilder()).toString(); + } + + @Override + public T accept(StatementVisitor statementVisitor, S context) { + return statementVisitor.visit(this, context); + } +} diff --git a/src/main/java/net/sf/jsqlparser/statement/grant/AlterDefaultPrivileges.java b/src/main/java/net/sf/jsqlparser/statement/grant/AlterDefaultPrivileges.java new file mode 100644 index 0000000000..42003ceee8 --- /dev/null +++ b/src/main/java/net/sf/jsqlparser/statement/grant/AlterDefaultPrivileges.java @@ -0,0 +1,99 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.statement.grant; + +import java.util.List; +import java.util.function.Consumer; +import net.sf.jsqlparser.expression.Expression; +import net.sf.jsqlparser.statement.Statement; +import net.sf.jsqlparser.statement.StatementVisitor; + +public class AlterDefaultPrivileges implements Statement { + public enum RoleKeyword { + ROLE, USER + } + + private RoleKeyword roleKeyword; + private List roles; + private List schemas; + private Grant grant; + private Revoke revoke; + + public RoleKeyword getRoleKeyword() { + return roleKeyword; + } + + public void setRoleKeyword(RoleKeyword roleKeyword) { + this.roleKeyword = roleKeyword; + } + + public List getRoles() { + return roles; + } + + public void setRoles(List roles) { + this.roles = roles; + } + + public List getSchemas() { + return schemas; + } + + public void setSchemas(List schemas) { + this.schemas = schemas; + } + + public Grant getGrant() { + return grant; + } + + public void setGrant(Grant grant) { + this.grant = grant; + } + + public Revoke getRevoke() { + return revoke; + } + + public void setRevoke(Revoke revoke) { + this.revoke = revoke; + } + + public void appendTo(StringBuilder sql, Consumer visitor) { + sql.append("ALTER DEFAULT PRIVILEGES"); + if (roles != null) { + sql.append(" FOR ").append(roleKeyword).append(' ').append(String.join(", ", roles)); + } + if (schemas != null) { + sql.append(" IN SCHEMA ").append(String.join(", ", schemas)); + } + sql.append(' '); + if ((grant == null) == (revoke == null)) { + throw new IllegalStateException("Exactly one GRANT or REVOKE is required"); + } + if (grant != null) { + grant.appendTo(sql, visitor); + } else { + revoke.appendTo(sql, visitor); + } + } + + @Override + public T accept(StatementVisitor visitor, S context) { + return visitor.visit(this, context); + } + + @Override + public String toString() { + StringBuilder sql = new StringBuilder(); + appendTo(sql, sql::append); + return sql.toString(); + } +} diff --git a/src/main/java/net/sf/jsqlparser/statement/grant/Grant.java b/src/main/java/net/sf/jsqlparser/statement/grant/Grant.java index 191e66c0c4..a9c43dc267 100644 --- a/src/main/java/net/sf/jsqlparser/statement/grant/Grant.java +++ b/src/main/java/net/sf/jsqlparser/statement/grant/Grant.java @@ -2,106 +2,183 @@ * #%L * JSQLParser library * %% - * Copyright (C) 2004 - 2020 JSQLParser + * Copyright (C) 2004 - 2026 JSQLParser * %% * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 * #L% */ package net.sf.jsqlparser.statement.grant; +import java.util.AbstractList; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Optional; - -import static java.util.stream.Collectors.joining; - +import java.util.function.Consumer; +import java.util.stream.Collectors; +import net.sf.jsqlparser.expression.Expression; import net.sf.jsqlparser.statement.Statement; import net.sf.jsqlparser.statement.StatementVisitor; public class Grant implements Statement { + private PrivilegeClause clause = new PrivilegeClause(); + private GrantOption option; - private String role; - private List privileges; - private final List objectName = new ArrayList<>(); - private List users; + public PrivilegeClause getClause() { + return clause; + } - @Override - public T accept(StatementVisitor statementVisitor, S context) { - return statementVisitor.visit(this, context); + public void setClause(PrivilegeClause clause) { + this.clause = clause; + } + + public GrantOption getOption() { + return option; + } + + public void setOption(GrantOption option) { + this.option = option; + } + + public List getPrivilegeItems() { + return clause.getPrivileges(); + } + + public void setPrivilegeItems(List privileges) { + clause.setPrivileges(privileges); + } + + public PrivilegeTarget getTarget() { + return clause.getTarget(); + } + + public void setTarget(PrivilegeTarget target) { + clause.setTarget(target); + } + + public List getRoles() { + return clause.getRoles(); + } + + public void setRoles(List roles) { + clause.setRoles(roles); } public String getRole() { - return role; + return clause.getRoles().isEmpty() ? null : clause.getRoles().get(0); } public void setRole(String role) { - this.role = role; + clause.setRoles(new ArrayList<>()); + if (role != null) { + clause.getRoles().add(role); + clause.setPrivileges(null); + } } + /** Mutable legacy string view over the typed privileges. */ public List getPrivileges() { - return privileges; + if (clause.getPrivileges() == null) { + return null; + } + return new AbstractList() { + @Override + public int size() { + return clause.getPrivileges().size(); + } + + @Override + public String get(int index) { + return clause.getPrivileges().get(index).toString(); + } + + @Override + public String set(int index, String value) { + return clause.getPrivileges().set(index, new Privilege(value)).toString(); + } + + @Override + public void add(int index, String value) { + clause.getPrivileges().add(index, new Privilege(value)); + } + + @Override + public String remove(int index) { + return clause.getPrivileges().remove(index).toString(); + } + }; } public void setPrivileges(List privileges) { - this.privileges = privileges; + List items = null; + if (privileges != null) { + items = new ArrayList<>(); + for (String privilege : privileges) { + items.add(new Privilege(privilege)); + } + } + clause.setPrivileges(items); } public String getObjectName() { - return objectName.isEmpty() ? null - : objectName.stream() - .map(part -> part == null ? "" : part) - .collect(joining(".")); + if (clause.getTarget().getNames().isEmpty() + || clause.getTarget().getNames().get(0).isEmpty()) { + return null; + } + List parts = clause.getTarget().getNames().get(0); + return parts.stream().map(part -> part == null ? "" : part) + .collect(Collectors.joining(".")); } public void setObjectName(String objectName) { - this.objectName.clear(); - this.objectName.add(objectName); + setObjectName(Collections.singletonList(objectName)); } public void setObjectName(List objectName) { - this.objectName.clear(); - this.objectName.addAll(objectName); + List parts = new ArrayList<>(objectName); + getObjectNameParts().clear(); + getObjectNameParts().addAll(parts); } + /** Mutable parts of the first named target, retained for compatibility. */ public List getObjectNameParts() { - return objectName; + if (clause.getTarget().getNames().isEmpty()) { + clause.getTarget().getNames().add(new ArrayList<>()); + } + return clause.getTarget().getNames().get(0); } public List getUsers() { - return users; + return clause.getGrantees(); } public void setUsers(List users) { - this.users = users; + clause.setGrantees(users); } - @Override - public String toString() { - StringBuilder buffer = new StringBuilder(); - - buffer.append("GRANT "); - if (role != null) { - buffer.append(role); - } else { - for (int i = 0; i < getPrivileges().size(); i++) { - if (i != 0) { - buffer.append(", "); - } - buffer.append(privileges.get(i)); - } - buffer.append(" ON "); - buffer.append(getObjectName()); + public void appendTo(StringBuilder sql, Consumer visitor) { + sql.append("GRANT "); + clause.appendTo(sql, false, visitor); + if (option != null) { + sql.append(" WITH ").append(option); } - buffer.append(" TO "); - for (int i = 0; i < getUsers().size(); i++) { - if (i != 0) { - buffer.append(", "); - } - buffer.append(users.get(i)); + if (clause.getGrantedBy() != null) { + sql.append(" GRANTED BY ").append(clause.getGrantedBy()); } - return buffer.toString(); + } + + @Override + public T accept(StatementVisitor visitor, S context) { + return visitor.visit(this, context); + } + + @Override + public String toString() { + StringBuilder sql = new StringBuilder(); + appendTo(sql, sql::append); + return sql.toString(); } public Grant withRole(String role) { @@ -130,15 +207,18 @@ public Grant withUsers(List users) { } public Grant addPrivileges(String... privileges) { - List collection = Optional.ofNullable(getPrivileges()).orElseGet(ArrayList::new); - Collections.addAll(collection, privileges); - return this.withPrivileges(collection); + return addPrivileges(Arrays.asList(privileges)); } public Grant addPrivileges(Collection privileges) { - List collection = Optional.ofNullable(getPrivileges()).orElseGet(ArrayList::new); - collection.addAll(privileges); - return this.withPrivileges(collection); + List additions = new ArrayList<>(privileges); + if (clause.getPrivileges() == null) { + clause.setPrivileges(new ArrayList<>()); + } + for (String privilege : additions) { + clause.getPrivileges().add(new Privilege(privilege)); + } + return this; } public Grant addUsers(String... users) { diff --git a/src/main/java/net/sf/jsqlparser/statement/grant/GrantOption.java b/src/main/java/net/sf/jsqlparser/statement/grant/GrantOption.java new file mode 100644 index 0000000000..712524378a --- /dev/null +++ b/src/main/java/net/sf/jsqlparser/statement/grant/GrantOption.java @@ -0,0 +1,49 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.statement.grant; + +import java.io.Serializable; + +public class GrantOption implements Serializable { + public enum Kind { + GRANT, ADMIN, INHERIT, SET + } + public enum Value { + OPTION, TRUE, FALSE + } + + private Kind kind; + private Value value = Value.OPTION; + + public Kind getKind() { + return kind; + } + + public void setKind(Kind kind) { + this.kind = kind; + } + + public Value getValue() { + return value; + } + + public void setValue(Value value) { + this.value = value; + } + + public GrantOption(Kind kind) { + this.kind = kind; + } + + @Override + public String toString() { + return kind + " " + value; + } +} diff --git a/src/main/java/net/sf/jsqlparser/statement/grant/Privilege.java b/src/main/java/net/sf/jsqlparser/statement/grant/Privilege.java new file mode 100644 index 0000000000..b1f3b461aa --- /dev/null +++ b/src/main/java/net/sf/jsqlparser/statement/grant/Privilege.java @@ -0,0 +1,88 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.statement.grant; + +import java.io.Serializable; +import java.util.function.Consumer; +import net.sf.jsqlparser.expression.Expression; +import net.sf.jsqlparser.expression.operators.relational.ExpressionList; +import net.sf.jsqlparser.schema.Column; + +public class Privilege implements Serializable { + public enum Kind { + ALL, SELECT, INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES, TRIGGER, MAINTAIN, USAGE, CREATE, CONNECT, TEMPORARY, TEMP, EXECUTE, SET, ALTER_SYSTEM, ALTER, DROP + } + + private String legacyName; + private Kind kind; + private boolean usePrivileges; + private ExpressionList columns; + + public Kind getKind() { + return kind; + } + + public void setKind(Kind kind) { + this.kind = kind; + } + + public boolean isUsePrivileges() { + return usePrivileges; + } + + public void setUsePrivileges(boolean usePrivileges) { + this.usePrivileges = usePrivileges; + } + + public ExpressionList getColumns() { + return columns; + } + + public void setColumns(ExpressionList columns) { + this.columns = columns; + } + + public Privilege(Kind kind) { + this.kind = kind; + } + + /** Preserves values supplied through the pre-existing string-based Grant API. */ + public Privilege(String name) { + try { + this.kind = Kind.valueOf(name); + } catch (IllegalArgumentException ex) { + this.legacyName = name; + } + } + + public void appendTo(StringBuilder sql, Consumer visitor) { + sql.append(kind == null ? legacyName : kind.name().replace('_', ' ')); + if (usePrivileges) { + sql.append(" PRIVILEGES"); + } + if (columns != null) { + sql.append(" ("); + for (int i = 0; i < columns.size(); i++) { + if (i > 0) { + sql.append(", "); + } + visitor.accept(columns.get(i)); + } + sql.append(')'); + } + } + + @Override + public String toString() { + StringBuilder sql = new StringBuilder(); + appendTo(sql, sql::append); + return sql.toString(); + } +} diff --git a/src/main/java/net/sf/jsqlparser/statement/grant/PrivilegeClause.java b/src/main/java/net/sf/jsqlparser/statement/grant/PrivilegeClause.java new file mode 100644 index 0000000000..4f517d1229 --- /dev/null +++ b/src/main/java/net/sf/jsqlparser/statement/grant/PrivilegeClause.java @@ -0,0 +1,92 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.statement.grant; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; +import net.sf.jsqlparser.expression.Expression; +import net.sf.jsqlparser.schema.Table; + +/** Shared, editable GRANT/REVOKE payload. Null privileges denote role membership. */ +public class PrivilegeClause implements Serializable { + private List privileges; + private List grantees; + private String grantedBy; + private List roles = new ArrayList<>(); + private PrivilegeTarget target = new PrivilegeTarget(); + + public List getPrivileges() { + return privileges; + } + + public void setPrivileges(List privileges) { + this.privileges = privileges; + } + + public List getRoles() { + return roles; + } + + public void setRoles(List roles) { + this.roles = roles; + } + + public PrivilegeTarget getTarget() { + return target; + } + + public void setTarget(PrivilegeTarget target) { + this.target = target; + } + + public List getGrantees() { + return grantees; + } + + public void setGrantees(List grantees) { + this.grantees = grantees; + } + + public String getGrantedBy() { + return grantedBy; + } + + public void setGrantedBy(String grantedBy) { + this.grantedBy = grantedBy; + } + + public void appendTo(StringBuilder sql, boolean revoke, Consumer visitor) { + if (privileges == null) { + sql.append(String.join(", ", roles)); + } else { + for (int i = 0; i < privileges.size(); i++) { + if (i > 0) { + sql.append(", "); + } + privileges.get(i).appendTo(sql, visitor); + } + sql.append(" ON ").append(target); + } + sql.append(revoke ? " FROM " : " TO ").append(String.join(", ", grantees)); + } + + public void visit(Consumer
tables, Consumer expressions) { + if (privileges != null) { + target.visitTables(tables); + for (Privilege privilege : privileges) { + if (privilege.getColumns() != null) { + privilege.getColumns().forEach(expressions); + } + } + } + } +} diff --git a/src/main/java/net/sf/jsqlparser/statement/grant/PrivilegeTarget.java b/src/main/java/net/sf/jsqlparser/statement/grant/PrivilegeTarget.java new file mode 100644 index 0000000000..a92ac85391 --- /dev/null +++ b/src/main/java/net/sf/jsqlparser/statement/grant/PrivilegeTarget.java @@ -0,0 +1,106 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.statement.grant; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; +import java.util.stream.Collectors; +import net.sf.jsqlparser.schema.Table; +import net.sf.jsqlparser.statement.RoutineReference; + +/** Targets have distinct kinds; a routine signature is not a function invocation. */ +public class PrivilegeTarget implements Serializable { + public enum Kind { + TABLE, SEQUENCE, DATABASE, DOMAIN, FOREIGN_DATA_WRAPPER, FOREIGN_SERVER, FUNCTION, PROCEDURE, ROUTINE, LANGUAGE, LARGE_OBJECT, PARAMETER, SCHEMA, TABLESPACE, TYPE + } + + private Kind kind = Kind.TABLE; + private boolean explicitKind; + private boolean allInSchema; + private boolean defaultPrivileges; + private List> names = new ArrayList<>(); + private List routines = new ArrayList<>(); + + public Kind getKind() { + return kind; + } + + public void setKind(Kind kind) { + this.kind = kind; + } + + public boolean isExplicitKind() { + return explicitKind; + } + + public void setExplicitKind(boolean explicitKind) { + this.explicitKind = explicitKind; + } + + public boolean isAllInSchema() { + return allInSchema; + } + + public void setAllInSchema(boolean allInSchema) { + this.allInSchema = allInSchema; + } + + public boolean isDefaultPrivileges() { + return defaultPrivileges; + } + + public void setDefaultPrivileges(boolean defaultPrivileges) { + this.defaultPrivileges = defaultPrivileges; + } + + public List> getNames() { + return names; + } + + public void setNames(List> names) { + this.names = names; + } + + public List getRoutines() { + return routines; + } + + public void setRoutines(List routines) { + this.routines = routines; + } + + public void visitTables(Consumer
visitor) { + if (kind == Kind.TABLE && !allInSchema && !defaultPrivileges) { + for (List name : names) { + if (!name.isEmpty()) { + visitor.accept(new Table(name)); + } + } + } + } + + @Override + public String toString() { + String type = kind.name().replace('_', ' '); + String targets = routines.isEmpty() + ? names.stream().map(n -> n.stream().map(part -> part == null ? "" : part) + .collect(Collectors.joining("."))).collect(Collectors.joining(", ")) + : routines.stream().map(Object::toString).collect(Collectors.joining(", ")); + if (defaultPrivileges) { + return type + "S"; + } + if (allInSchema) { + return "ALL " + type + "S IN SCHEMA " + targets; + } + return (explicitKind ? type + " " : "") + targets; + } +} diff --git a/src/main/java/net/sf/jsqlparser/statement/grant/Revoke.java b/src/main/java/net/sf/jsqlparser/statement/grant/Revoke.java new file mode 100644 index 0000000000..157eccc8a0 --- /dev/null +++ b/src/main/java/net/sf/jsqlparser/statement/grant/Revoke.java @@ -0,0 +1,75 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.statement.grant; + +import java.util.function.Consumer; +import net.sf.jsqlparser.expression.Expression; +import net.sf.jsqlparser.statement.Statement; +import net.sf.jsqlparser.statement.StatementVisitor; + +public class Revoke implements Statement { + public enum Behavior { + CASCADE, RESTRICT + } + + private PrivilegeClause clause = new PrivilegeClause(); + private GrantOption.Kind optionFor; + private Behavior behavior; + + public PrivilegeClause getClause() { + return clause; + } + + public void setClause(PrivilegeClause clause) { + this.clause = clause; + } + + public GrantOption.Kind getOptionFor() { + return optionFor; + } + + public void setOptionFor(GrantOption.Kind optionFor) { + this.optionFor = optionFor; + } + + public Behavior getBehavior() { + return behavior; + } + + public void setBehavior(Behavior behavior) { + this.behavior = behavior; + } + + public void appendTo(StringBuilder sql, Consumer visitor) { + sql.append("REVOKE "); + if (optionFor != null) { + sql.append(optionFor).append(" OPTION FOR "); + } + clause.appendTo(sql, true, visitor); + if (clause.getGrantedBy() != null) { + sql.append(" GRANTED BY ").append(clause.getGrantedBy()); + } + if (behavior != null) { + sql.append(' ').append(behavior); + } + } + + @Override + public T accept(StatementVisitor visitor, S context) { + return visitor.visit(this, context); + } + + @Override + public String toString() { + StringBuilder sql = new StringBuilder(); + appendTo(sql, sql::append); + return sql.toString(); + } +} diff --git a/src/main/java/net/sf/jsqlparser/statement/insert/Insert.java b/src/main/java/net/sf/jsqlparser/statement/insert/Insert.java index 4920aec622..3541c1022f 100644 --- a/src/main/java/net/sf/jsqlparser/statement/insert/Insert.java +++ b/src/main/java/net/sf/jsqlparser/statement/insert/Insert.java @@ -40,7 +40,12 @@ public class Insert implements Statement { private List partitions; private Select select; private boolean onlyDefaultValues = false; - private boolean overriding = false; + + public enum OverridingMode { + NONE, SYSTEM, USER + } + + private OverridingMode overridingMode = OverridingMode.NONE; private List duplicateUpdateSets = null; private InsertModifierPriority modifierPriority = null; private boolean modifierIgnore = false; @@ -54,6 +59,7 @@ public class Insert implements Statement { private InsertConflictAction conflictAction; private InsertDuplicateAction duplicateAction; private Alias rowAlias; + private ColumnMatching columnMatching; private boolean oracleMultiInsert = false; private boolean oracleMultiInsertFirst = false; private List oracleMultiInsertBranches; @@ -226,12 +232,57 @@ public void setWithItemsList(List> withItemsList) { this.withItemsList = withItemsList; } + /** + * DuckDB matches the source columns of an INSERT either positionally or by name: + * {@code INSERT INTO tbl BY NAME SELECT ...}. + */ + public enum ColumnMatching { + BY_NAME("BY NAME"), BY_POSITION("BY POSITION"); + + private final String keywords; + + ColumnMatching(String keywords) { + this.keywords = keywords; + } + + @Override + public String toString() { + return keywords; + } + } + + public ColumnMatching getColumnMatching() { + return columnMatching; + } + + public void setColumnMatching(ColumnMatching columnMatching) { + this.columnMatching = columnMatching; + } + + public Insert withColumnMatching(ColumnMatching columnMatching) { + setColumnMatching(columnMatching); + return this; + } + + public OverridingMode getOverridingMode() { + return overridingMode; + } + + public void setOverridingMode(OverridingMode overridingMode) { + this.overridingMode = java.util.Objects.requireNonNull(overridingMode); + } + + public Insert withOverridingMode(OverridingMode overridingMode) { + setOverridingMode(overridingMode); + return this; + } + public boolean isOverriding() { - return overriding; + return overridingMode != OverridingMode.NONE; } public void setOverriding(boolean overriding) { - this.overriding = overriding; + this.overridingMode = overriding ? OverridingMode.SYSTEM : OverridingMode.NONE; } public Insert withOverriding(boolean overriding) { @@ -388,8 +439,12 @@ private void appendInsertTargetAndValues(StringBuilder sql) { sql.append(") "); } - if (overriding) { - sql.append("OVERRIDING SYSTEM VALUE "); + if (columnMatching != null) { + sql.append(columnMatching).append(" "); + } + + if (isOverriding()) { + sql.append("OVERRIDING ").append(overridingMode).append(" VALUE "); } if (partitions != null) { diff --git a/src/main/java/net/sf/jsqlparser/statement/insert/InsertBulk.java b/src/main/java/net/sf/jsqlparser/statement/insert/InsertBulk.java new file mode 100644 index 0000000000..8464a4fa99 --- /dev/null +++ b/src/main/java/net/sf/jsqlparser/statement/insert/InsertBulk.java @@ -0,0 +1,162 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.statement.insert; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; +import net.sf.jsqlparser.expression.Expression; +import net.sf.jsqlparser.schema.Table; +import net.sf.jsqlparser.statement.Statement; +import net.sf.jsqlparser.statement.StatementVisitor; +import net.sf.jsqlparser.statement.create.table.ColumnDefinition; +import net.sf.jsqlparser.statement.select.OrderByElement; + +/** SQL Server's SQL declaration preceding a separate bulk-load data stream. */ +public class InsertBulk implements Statement { + private Table table; + private List columns = new ArrayList<>(); + private List
iter = intoTables.iterator(); iter.hasNext();) { @@ -578,9 +685,11 @@ public StringBuilder appendSelectBodyTo(StringBuilder builder) { .getPosition() == MySqlSelectIntoClause.Position.BEFORE_FROM) { builder.append(" ").append(mySqlSelectIntoClause); } + } + private void appendFromClauseTo(StringBuilder builder) { if (fromItem != null) { - builder.append(" FROM "); + builder.append(fromFirst ? "FROM " : " FROM "); if (isUsingOnly) { builder.append("ONLY "); } @@ -607,53 +716,10 @@ public StringBuilder appendSelectBodyTo(StringBuilder builder) { if (ksqlWindow != null) { builder.append(" WINDOW ").append(ksqlWindow); } - if (preWhere != null) { - builder.append(" PREWHERE ").append(preWhere); - } - if (where != null) { - builder.append(" WHERE ").append(where); - } - if (oracleHierarchical != null) { - builder.append(oracleHierarchical); - } - if (preferringClause != null) { - builder.append(" ").append(preferringClause); - } - if (groupBy != null) { - builder.append(" ").append(groupBy); - } - if (having != null) { - builder.append(" HAVING ").append(having); - } - if (qualify != null) { - builder.append(" QUALIFY ").append(qualify); - } - if (windowDefinitions != null) { - builder.append(" WINDOW "); - builder.append(windowDefinitions.stream().map(WindowDefinition::toString) - .collect(joining(", "))); - } - if (emitChanges) { - builder.append(" EMIT CHANGES"); - } - } else { - // without from - if (preWhere != null) { - builder.append(" PREWHERE ").append(preWhere); - } - if (where != null) { - builder.append(" WHERE ").append(where); - } } - if (intoTempTable != null) { - builder.append(" INTO TEMP ").append(intoTempTable); - } - if (useWithNoLog) { - builder.append(" WITH NO LOG"); - } - return builder; } + @Override @SuppressWarnings({"PMD.CyclomaticComplexity", "PMD.ExcessiveMethodLength", "PMD.NPathComplexity"}) diff --git a/src/main/java/net/sf/jsqlparser/statement/select/RowPattern.java b/src/main/java/net/sf/jsqlparser/statement/select/RowPattern.java new file mode 100644 index 0000000000..9d922ff5b3 --- /dev/null +++ b/src/main/java/net/sf/jsqlparser/statement/select/RowPattern.java @@ -0,0 +1,392 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.statement.select; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; +import net.sf.jsqlparser.expression.Expression; +import net.sf.jsqlparser.parser.ASTNodeAccessImpl; + +/** A row pattern, with explicit grouping and ordered alternatives preserved. */ +public abstract class RowPattern extends ASTNodeAccessImpl { + public abstract T accept(RowPatternVisitor visitor, S context); + + public abstract StringBuilder appendTo(StringBuilder builder, Consumer expressions); + + /** Visits only SQL expressions embedded in the pattern, such as quantifier bounds. */ + public void forEachExpression(Consumer expressions) {} + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + return appendTo(builder, expression -> builder.append(expression)).toString(); + } + + public static class Variable extends RowPattern { + private String name; + + public Variable(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public Variable setName(String name) { + this.name = name; + return this; + } + + @Override + public T accept(RowPatternVisitor visitor, S context) { + return visitor.visit(this, context); + } + + @Override + public StringBuilder appendTo(StringBuilder builder, Consumer expressions) { + return builder.append(name); + } + } + + public static class Empty extends RowPattern { + @Override + public T accept(RowPatternVisitor visitor, S context) { + return visitor.visit(this, context); + } + + @Override + public StringBuilder appendTo(StringBuilder builder, Consumer expressions) { + return builder; + } + } + + public static class Anchor extends RowPattern { + public enum Type { + START, END + } + + private Type type; + + public Anchor(Type type) { + this.type = type; + } + + public Type getType() { + return type; + } + + public Anchor setType(Type type) { + this.type = type; + return this; + } + + @Override + public T accept(RowPatternVisitor visitor, S context) { + return visitor.visit(this, context); + } + + @Override + public StringBuilder appendTo(StringBuilder builder, Consumer expressions) { + return builder.append(type == Type.START ? '^' : '$'); + } + } + + public static class Group extends RowPattern { + private RowPattern pattern; + + public Group(RowPattern pattern) { + this.pattern = pattern; + } + + public RowPattern getPattern() { + return pattern; + } + + public Group setPattern(RowPattern pattern) { + this.pattern = pattern; + return this; + } + + @Override + public T accept(RowPatternVisitor visitor, S context) { + return visitor.visit(this, context); + } + + @Override + public void forEachExpression(Consumer expressions) { + pattern.forEachExpression(expressions); + } + + @Override + public StringBuilder appendTo(StringBuilder builder, Consumer expressions) { + builder.append('('); + pattern.appendTo(builder, expressions); + return builder.append(')'); + } + } + + public static class Operation extends RowPattern { + public enum Type { + SEQUENCE, ALTERNATION + } + + private Type type; + private List patterns; + + public Operation(Type type, List patterns) { + this.type = type; + this.patterns = new ArrayList<>(patterns); + } + + public Type getType() { + return type; + } + + public Operation setType(Type type) { + this.type = type; + return this; + } + + public List getPatterns() { + return patterns; + } + + public Operation setPatterns(List patterns) { + this.patterns = patterns; + return this; + } + + @Override + public T accept(RowPatternVisitor visitor, S context) { + return visitor.visit(this, context); + } + + @Override + public void forEachExpression(Consumer expressions) { + patterns.forEach(pattern -> pattern.forEachExpression(expressions)); + } + + @Override + public StringBuilder appendTo(StringBuilder builder, Consumer expressions) { + for (int i = 0; i < patterns.size(); i++) { + if (i > 0) { + builder.append(type == Type.SEQUENCE ? " " : " | "); + } + RowPattern child = patterns.get(i); + // Explicit parentheses keep the tree unambiguous across dialects, including + // Snowflake's documented alternative-before-concatenation precedence. + boolean brackets = type == Type.SEQUENCE && child instanceof Empty + || child instanceof Operation && ((Operation) child).type != type; + if (brackets) { + builder.append('('); + } + child.appendTo(builder, expressions); + if (brackets) { + builder.append(')'); + } + } + return builder; + } + } + + /** A permutation stays compact; it is never expanded into factorially many alternatives. */ + public static class Permute extends RowPattern { + private List patterns; + + public Permute(List patterns) { + this.patterns = new ArrayList<>(patterns); + } + + public List getPatterns() { + return patterns; + } + + public Permute setPatterns(List patterns) { + this.patterns = patterns; + return this; + } + + @Override + public T accept(RowPatternVisitor visitor, S context) { + return visitor.visit(this, context); + } + + @Override + public void forEachExpression(Consumer expressions) { + patterns.forEach(pattern -> pattern.forEachExpression(expressions)); + } + + @Override + public StringBuilder appendTo(StringBuilder builder, Consumer expressions) { + builder.append("PERMUTE("); + for (int i = 0; i < patterns.size(); i++) { + if (i > 0) { + builder.append(", "); + } + patterns.get(i).appendTo(builder, expressions); + } + return builder.append(')'); + } + } + + public static class Exclusion extends RowPattern { + private RowPattern pattern; + + public Exclusion(RowPattern pattern) { + this.pattern = pattern; + } + + public RowPattern getPattern() { + return pattern; + } + + public Exclusion setPattern(RowPattern pattern) { + this.pattern = pattern; + return this; + } + + @Override + public T accept(RowPatternVisitor visitor, S context) { + return visitor.visit(this, context); + } + + @Override + public void forEachExpression(Consumer expressions) { + pattern.forEachExpression(expressions); + } + + @Override + public StringBuilder appendTo(StringBuilder builder, Consumer expressions) { + builder.append("{- "); + pattern.appendTo(builder, expressions); + return builder.append(" -}"); + } + } + + public static class Quantified extends RowPattern { + public enum Type { + ZERO_OR_MORE, ONE_OR_MORE, OPTIONAL, EXACT, RANGE + } + + private RowPattern pattern; + private Type type; + private Expression lowerBound; + private Expression upperBound; + private boolean reluctant; + + public Quantified(RowPattern pattern, Type type) { + this.pattern = pattern; + this.type = type; + } + + public RowPattern getPattern() { + return pattern; + } + + public Quantified setPattern(RowPattern pattern) { + this.pattern = pattern; + return this; + } + + public Type getType() { + return type; + } + + public Quantified setType(Type type) { + this.type = type; + return this; + } + + public Expression getLowerBound() { + return lowerBound; + } + + public Quantified setLowerBound(Expression bound) { + lowerBound = bound; + return this; + } + + public Expression getUpperBound() { + return upperBound; + } + + public Quantified setUpperBound(Expression bound) { + upperBound = bound; + return this; + } + + public boolean isReluctant() { + return reluctant; + } + + public Quantified setReluctant(boolean reluctant) { + this.reluctant = reluctant; + return this; + } + + @Override + public T accept(RowPatternVisitor visitor, S context) { + return visitor.visit(this, context); + } + + @Override + public void forEachExpression(Consumer expressions) { + pattern.forEachExpression(expressions); + if (lowerBound != null) { + expressions.accept(lowerBound); + } + if (upperBound != null) { + expressions.accept(upperBound); + } + } + + @Override + public StringBuilder appendTo(StringBuilder builder, Consumer expressions) { + boolean brackets = !(pattern instanceof Variable || pattern instanceof Group + || pattern instanceof Permute || pattern instanceof Exclusion); + if (brackets) { + builder.append('('); + } + pattern.appendTo(builder, expressions); + if (brackets) { + builder.append(')'); + } + switch (type) { + case ZERO_OR_MORE: + builder.append('*'); + break; + case ONE_OR_MORE: + builder.append('+'); + break; + case OPTIONAL: + builder.append('?'); + break; + default: + builder.append('{'); + if (lowerBound != null) { + expressions.accept(lowerBound); + } + if (type == Type.RANGE) { + builder.append(','); + if (upperBound != null) { + expressions.accept(upperBound); + } + } + builder.append('}'); + } + if (reluctant) { + builder.append('?'); + } + return builder; + } + } +} diff --git a/src/main/java/net/sf/jsqlparser/statement/select/RowPatternVisitor.java b/src/main/java/net/sf/jsqlparser/statement/select/RowPatternVisitor.java new file mode 100644 index 0000000000..e61bf4ad92 --- /dev/null +++ b/src/main/java/net/sf/jsqlparser/statement/select/RowPatternVisitor.java @@ -0,0 +1,29 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.statement.select; + +/** Visitor for the structural operators of a row pattern. */ +public interface RowPatternVisitor { + T visit(RowPattern.Variable pattern, S context); + + T visit(RowPattern.Empty pattern, S context); + + T visit(RowPattern.Anchor pattern, S context); + + T visit(RowPattern.Group pattern, S context); + + T visit(RowPattern.Operation pattern, S context); + + T visit(RowPattern.Quantified pattern, S context); + + T visit(RowPattern.Permute pattern, S context); + + T visit(RowPattern.Exclusion pattern, S context); +} diff --git a/src/main/java/net/sf/jsqlparser/statement/select/RowPatternVisitorAdapter.java b/src/main/java/net/sf/jsqlparser/statement/select/RowPatternVisitorAdapter.java new file mode 100644 index 0000000000..65241f984f --- /dev/null +++ b/src/main/java/net/sf/jsqlparser/statement/select/RowPatternVisitorAdapter.java @@ -0,0 +1,80 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.statement.select; + +import net.sf.jsqlparser.expression.ExpressionVisitor; + +/** Traverses pattern children and, when supplied, SQL expressions in quantifier bounds. */ +public class RowPatternVisitorAdapter implements RowPatternVisitor { + private final ExpressionVisitor expressionVisitor; + + public RowPatternVisitorAdapter() { + this(null); + } + + public RowPatternVisitorAdapter(ExpressionVisitor expressionVisitor) { + this.expressionVisitor = expressionVisitor; + } + + @Override + public T visit(RowPattern.Variable pattern, S context) { + return null; + } + + @Override + public T visit(RowPattern.Empty pattern, S context) { + return null; + } + + @Override + public T visit(RowPattern.Anchor pattern, S context) { + return null; + } + + @Override + public T visit(RowPattern.Group pattern, S context) { + return pattern.getPattern().accept(this, context); + } + + @Override + public T visit(RowPattern.Operation pattern, S context) { + for (RowPattern child : pattern.getPatterns()) { + child.accept(this, context); + } + return null; + } + + @Override + public T visit(RowPattern.Quantified pattern, S context) { + pattern.getPattern().accept(this, context); + if (expressionVisitor != null) { + if (pattern.getLowerBound() != null) { + pattern.getLowerBound().accept(expressionVisitor, context); + } + if (pattern.getUpperBound() != null) { + pattern.getUpperBound().accept(expressionVisitor, context); + } + } + return null; + } + + @Override + public T visit(RowPattern.Permute pattern, S context) { + for (RowPattern child : pattern.getPatterns()) { + child.accept(this, context); + } + return null; + } + + @Override + public T visit(RowPattern.Exclusion pattern, S context) { + return pattern.getPattern().accept(this, context); + } +} diff --git a/src/main/java/net/sf/jsqlparser/statement/select/SampleClause.java b/src/main/java/net/sf/jsqlparser/statement/select/SampleClause.java index e6bc8f21d9..a3c0203dfa 100644 --- a/src/main/java/net/sf/jsqlparser/statement/select/SampleClause.java +++ b/src/main/java/net/sf/jsqlparser/statement/select/SampleClause.java @@ -17,6 +17,7 @@ public class SampleClause { private Number percentageArgument; private String percentageUnit; private boolean argumentInBrackets = true; + private boolean methodInBrackets = false; // ClickHouse specific private Number offsetArgument; private Number repeatArgument; @@ -130,10 +131,23 @@ public SampleClause setMethod(String method) { return this; } + /** + * DuckDB writes the sampling method behind the sample size: + * {@code USING SAMPLE 10 PERCENT (bernoulli)}. + */ + public boolean isMethodInBrackets() { + return methodInBrackets; + } + + public SampleClause setMethodInBrackets(boolean methodInBrackets) { + this.methodInBrackets = methodInBrackets; + return this; + } + public StringBuilder appendTo(StringBuilder builder) { builder.append(" ").append(keyword); - if (method != null) { + if (method != null && !methodInBrackets) { builder.append(" ").append(method); } @@ -144,11 +158,20 @@ public StringBuilder appendTo(StringBuilder builder) { } else { builder.append(" ").append(percentageArgument); if (percentageUnit != null) { - builder.append(" ").append(percentageUnit); + // "10%" has no blank, "10 PERCENT" and "10 ROWS" do + builder.append("%".equals(percentageUnit) ? "" : " ").append(percentageUnit); } } } + if (methodInBrackets) { + builder.append(" (").append(method); + if (seedArgument != null) { + builder.append(", ").append(seedArgument); + } + builder.append(")"); + } + if (offsetArgument != null) { builder.append(" OFFSET ").append(offsetArgument); } @@ -157,7 +180,7 @@ public StringBuilder appendTo(StringBuilder builder) { builder.append(" REPEATABLE (").append(repeatArgument).append(")"); } - if (seedArgument != null) { + if (seedArgument != null && !methodInBrackets) { builder.append(" SEED (").append(seedArgument).append(")"); } @@ -189,7 +212,7 @@ public String toString() { } public enum SampleMethod { - BERNOULLI, SYSTEM, BLOCK; + BERNOULLI, SYSTEM, BLOCK, RESERVOIR; public static SampleMethod from(String sampleMethod) { return Enum.valueOf(SampleMethod.class, diff --git a/src/main/java/net/sf/jsqlparser/statement/select/Select.java b/src/main/java/net/sf/jsqlparser/statement/select/Select.java index 737608020f..d3750c98ef 100644 --- a/src/main/java/net/sf/jsqlparser/statement/select/Select.java +++ b/src/main/java/net/sf/jsqlparser/statement/select/Select.java @@ -31,11 +31,14 @@ public abstract class Select extends ASTNodeAccessImpl implements Statement, Exp Offset offset; Fetch fetch; WithIsolation isolation; + MySqlProcedureAnalyse mySqlProcedureAnalyse; boolean oracleSiblings = false; ForClause forClause = null; + OptionClause option = null; List orderByElements; + List interpolate; ForMode forMode = null; private boolean skipLocked; private Wait wait; @@ -54,6 +57,16 @@ public static String orderByToString(boolean oracleSiblings, return getFormattedList(orderByElements, oracleSiblings ? "ORDER SIBLINGS BY" : "ORDER BY"); } + public static String interpolateToString(List interpolate) { + if (interpolate == null) { + return ""; + } + if (interpolate.isEmpty()) { + return " INTERPOLATE"; + } + return getFormattedList(interpolate, "INTERPOLATE", true, true); + } + public static String getFormattedList(List list, String expression) { return getFormattedList(list, expression, true, false); } @@ -184,6 +197,19 @@ public Select setForClause(ForClause forClause) { return this; } + public OptionClause getOption() { + return option; + } + + public Select setOption(OptionClause option) { + this.option = option; + return this; + } + + public Select withOption(OptionClause option) { + return setOption(option); + } + public List getOrderByElements() { return orderByElements; } @@ -192,6 +218,19 @@ public void setOrderByElements(List orderByElements) { this.orderByElements = orderByElements; } + public List getInterpolate() { + return interpolate; + } + + public void setInterpolate(List interpolate) { + this.interpolate = interpolate; + } + + public Select withInterpolate(List interpolate) { + setInterpolate(interpolate); + return this; + } + public Select withOrderByElements(List orderByElements) { this.setOrderByElements(orderByElements); return this; @@ -284,6 +323,19 @@ public Select withIsolation(WithIsolation isolation) { return this; } + public MySqlProcedureAnalyse getMySqlProcedureAnalyse() { + return mySqlProcedureAnalyse; + } + + public void setMySqlProcedureAnalyse(MySqlProcedureAnalyse mySqlProcedureAnalyse) { + this.mySqlProcedureAnalyse = mySqlProcedureAnalyse; + } + + public Select withMySqlProcedureAnalyse(MySqlProcedureAnalyse mySqlProcedureAnalyse) { + setMySqlProcedureAnalyse(mySqlProcedureAnalyse); + return this; + } + public ForMode getForMode() { return this.forMode; } @@ -463,12 +515,17 @@ public StringBuilder appendTo(StringBuilder builder) { if (!forUpdateBeforeOrderBy) { builder.append(orderByToString(oracleSiblings, orderByElements)); + builder.append(interpolateToString(interpolate)); } if (forClause != null) { forClause.appendTo(builder); } + if (option != null) { + builder.append(option); + } + if (limitBy != null) { builder.append(limitBy); } @@ -478,6 +535,9 @@ public StringBuilder appendTo(StringBuilder builder) { if (offset != null) { builder.append(offset); } + if (mySqlProcedureAnalyse != null) { + mySqlProcedureAnalyse.appendTo(builder); + } if (fetch != null) { builder.append(fetch); } @@ -512,6 +572,7 @@ public StringBuilder appendTo(StringBuilder builder) { if (forUpdateBeforeOrderBy) { builder.append(orderByToString(oracleSiblings, orderByElements)); + builder.append(interpolateToString(interpolate)); } return builder; diff --git a/src/main/java/net/sf/jsqlparser/statement/select/SelectVisitor.java b/src/main/java/net/sf/jsqlparser/statement/select/SelectVisitor.java index db5ce31752..5591f4bf83 100644 --- a/src/main/java/net/sf/jsqlparser/statement/select/SelectVisitor.java +++ b/src/main/java/net/sf/jsqlparser/statement/select/SelectVisitor.java @@ -9,11 +9,10 @@ */ package net.sf.jsqlparser.statement.select; +import java.util.List; import net.sf.jsqlparser.statement.OutputClause; import net.sf.jsqlparser.statement.piped.FromQuery; -import java.util.List; - public interface SelectVisitor { default T visitWithItems(List> withItemsList, S context) { if (withItemsList != null) { @@ -40,6 +39,22 @@ default void visit(PlainSelect plainSelect) { this.visit(plainSelect, null); } + default T visit(PivotQuery pivotQuery, S context) { + return null; + } + + default void visit(PivotQuery pivotQuery) { + this.visit(pivotQuery, null); + } + + default T visit(UnPivotQuery unPivotQuery, S context) { + return null; + } + + default void visit(UnPivotQuery unPivotQuery) { + this.visit(unPivotQuery, null); + } + T visit(FromQuery fromQuery, S context); T visit(SetOperationList setOpList, S context); diff --git a/src/main/java/net/sf/jsqlparser/statement/select/SelectVisitorAdapter.java b/src/main/java/net/sf/jsqlparser/statement/select/SelectVisitorAdapter.java index 9b0eb33a65..45a2db3dd5 100644 --- a/src/main/java/net/sf/jsqlparser/statement/select/SelectVisitorAdapter.java +++ b/src/main/java/net/sf/jsqlparser/statement/select/SelectVisitorAdapter.java @@ -9,9 +9,16 @@ */ package net.sf.jsqlparser.statement.select; +import java.util.List; import net.sf.jsqlparser.expression.ExpressionVisitor; import net.sf.jsqlparser.expression.ExpressionVisitorAdapter; +import net.sf.jsqlparser.expression.Function; +import net.sf.jsqlparser.expression.Expression; +import net.sf.jsqlparser.expression.WindowDefinition; import net.sf.jsqlparser.statement.OutputClause; +import net.sf.jsqlparser.statement.ParenthesedStatement; +import net.sf.jsqlparser.statement.Statement; +import net.sf.jsqlparser.statement.StatementVisitor; import net.sf.jsqlparser.statement.piped.FromQuery; @SuppressWarnings({"PMD.UncommentedEmptyMethodBody"}) @@ -21,6 +28,14 @@ public class SelectVisitorAdapter implements SelectVisitor { private final SelectItemVisitor selectItemVisitor; private final FromItemVisitor fromItemVisitor; + /** + * Optional, and only needed for data-modifying CTEs - see {@link #visit(WithItem, Object)}. A + * plain SelectVisitor cannot accept an INSERT/UPDATE/DELETE body; without this the CTE is + * skipped rather than traversed. {@link net.sf.jsqlparser.statement.StatementVisitorAdapter} + * sets it automatically. + */ + private StatementVisitor statementVisitor; + public SelectVisitorAdapter() { this.expressionVisitor = new ExpressionVisitorAdapter<>(this); this.pivotVisitor = new PivotVisitorAdapter<>(this.expressionVisitor); @@ -55,11 +70,7 @@ public SelectVisitorAdapter(ExpressionVisitor expressionVisitor) { @Override public T visitOutputClause(OutputClause outputClause, S context) { if (outputClause != null) { - if (outputClause.getSelectItemList() != null) { - for (SelectItem selectItem : outputClause.getSelectItemList()) { - selectItem.accept(selectItemVisitor, context); - } - } + visitSelectItems(outputClause.getSelectItemList(), context); if (outputClause.getTableVariable() != null) { outputClause.getTableVariable().accept(expressionVisitor, context); } @@ -74,6 +85,14 @@ public T visitOutputClause(OutputClause outputClause, S context) { return null; } + private void visitSelectItems(List> items, S context) { + if (items != null) { + for (SelectItem item : items) { + item.accept(selectItemVisitor, context); + } + } + } + public ExpressionVisitor getExpressionVisitor() { return expressionVisitor; } @@ -90,6 +109,15 @@ public FromItemVisitor getFromItemVisitor() { return fromItemVisitor; } + public StatementVisitor getStatementVisitor() { + return statementVisitor; + } + + public SelectVisitorAdapter setStatementVisitor(StatementVisitor statementVisitor) { + this.statementVisitor = statementVisitor; + return this; + } + @Override public T visit(ParenthesedSelect select, S context) { visitWithItems(select.withItemsList, context); @@ -125,21 +153,18 @@ public T visit(PlainSelect plainSelect, S context) { visitWithItems(plainSelect.withItemsList, context); if (plainSelect.getDistinct() != null) { - for (SelectItem selectItem : plainSelect.getDistinct().getOnSelectItems()) { - selectItem.accept(selectItemVisitor, context); - } + visitSelectItems(plainSelect.getDistinct().getOnSelectItems(), context); } if (plainSelect.getTop() != null) { plainSelect.getTop().getExpression().accept(expressionVisitor, context); } - for (SelectItem selectItem : plainSelect.getSelectItems()) { - selectItem.accept(selectItemVisitor, context); - } + visitSelectItems(plainSelect.getSelectItems(), context); if (plainSelect.getMySqlSelectIntoClause() != null) { MySqlSelectIntoClause mySqlSelectIntoClause = plainSelect.getMySqlSelectIntoClause(); + expressionVisitor.visitExpressions(mySqlSelectIntoClause.getVariables(), context); expressionVisitor.visitExpression(mySqlSelectIntoClause.getFileName(), context); expressionVisitor.visitExpression(mySqlSelectIntoClause.getFieldsTerminatedBy(), context); @@ -176,9 +201,13 @@ public T visit(PlainSelect plainSelect, S context) { expressionVisitor.visitExpression(plainSelect.getHaving(), context); expressionVisitor.visitExpression(plainSelect.getQualify(), context); - // if (plainSelect.getWindowDefinitions() != null) { - // //@todo: implement - // } + if (plainSelect.getWindowDefinitions() != null) { + for (WindowDefinition window : plainSelect.getWindowDefinitions()) { + for (Expression expression : window.getAllExpressions()) { + expressionVisitor.visitExpression(expression, context); + } + } + } Pivot pivot = plainSelect.getPivot(); if (pivot != null) { @@ -200,6 +229,12 @@ public T visit(PlainSelect plainSelect, S context) { if (plainSelect.getOffset() != null) { expressionVisitor.visitExpression(plainSelect.getOffset().getOffset(), context); } + if (plainSelect.getMySqlProcedureAnalyse() != null) { + expressionVisitor.visitExpression( + plainSelect.getMySqlProcedureAnalyse().getMaxElements(), context); + expressionVisitor.visitExpression( + plainSelect.getMySqlProcedureAnalyse().getMaxMemory(), context); + } if (plainSelect.getFetch() != null) { expressionVisitor.visitExpression(plainSelect.getFetch().getExpression(), context); } @@ -211,6 +246,42 @@ public T visit(PlainSelect plainSelect, S context) { return null; } + @Override + public T visit(PivotQuery pivotQuery, S context) { + visitWithItems(pivotQuery.getWithItemsList(), context); + + fromItemVisitor.visitFromItem(pivotQuery.getFromItem(), context); + expressionVisitor.visitExpressions(pivotQuery.getOnExpressions(), context); + + if (pivotQuery.getUsingItems() != null) { + for (SelectItem item : pivotQuery.getUsingItems()) { + item.accept(selectItemVisitor, context); + } + } + + expressionVisitor.visitExpressions(pivotQuery.getGroupByExpressions(), context); + expressionVisitor.visitOrderBy(pivotQuery.getOrderByElements(), context); + expressionVisitor.visitLimit(pivotQuery.getLimit(), context); + + if (pivotQuery.getOffset() != null) { + expressionVisitor.visitExpression(pivotQuery.getOffset().getOffset(), context); + } + if (pivotQuery.getFetch() != null) { + expressionVisitor.visitExpression(pivotQuery.getFetch().getExpression(), context); + } + return null; + } + + @Override + public T visit(UnPivotQuery unPivotQuery, S context) { + visitWithItems(unPivotQuery.getWithItemsList(), context); + + fromItemVisitor.visitFromItem(unPivotQuery.getFromItem(), context); + expressionVisitor.visitExpressions(unPivotQuery.getOnExpressions(), context); + expressionVisitor.visitExpressions(unPivotQuery.getValueColumns(), context); + return null; + } + @Override public T visit(FromQuery fromQuery, S context) { return null; @@ -224,9 +295,49 @@ public T visit(SetOperationList setOpList, S context) { return null; } + /** + * A CTE body is a {@link ParenthesedStatement}, which is not necessarily a SELECT: PostgreSQL + * and others allow {@code WITH c AS (DELETE FROM t RETURNING *) SELECT * FROM c}. + * + *

+ * The previous implementation called {@code withItem.getSelect()}, an unchecked cast to + * {@link ParenthesedSelect}, and threw {@link ClassCastException} on every data-modifying CTE + * reached through the select path - which {@code ExpressionVisitorAdapter#visit(Select)} does + * for any sub-select. {@code WithItem#accept(StatementVisitor)} has always dispatched correctly + * through {@code getParenthesedStatement()}; only this path did not. + */ @Override public T visit(WithItem withItem, S context) { - return withItem.getSelect().accept(this, context); + if (withItem.getCycleClause() != null) { + withItem.getCycleClause().accept(expressionVisitor, context); + } + ParenthesedStatement body = withItem.getParenthesedStatement(); + + // ParenthesedSelect is a Select and stays on the select path + if (body instanceof Select) { + return ((Select) body).accept(this, context); + } + + // ParenthesedInsert / ParenthesedUpdate / ParenthesedDelete need a StatementVisitor + if (statementVisitor != null && body instanceof Statement) { + return ((Statement) body).accept(statementVisitor, context); + } + + // a ClickHouse expression alias (WITH AS ): a + // parenthesized subquery stays on the select path, any other expression + // goes to the expression visitor + Expression expression = withItem.getExpression(); + if (expression != null) { + if (expression instanceof Select) { + return ((Select) expression).accept(this, context); + } + if (expressionVisitor != null) { + return expression.accept(expressionVisitor, context); + } + } + + // no statement visitor available: skip the body rather than fail + return null; } @Override diff --git a/src/main/java/net/sf/jsqlparser/statement/select/SetOperation.java b/src/main/java/net/sf/jsqlparser/statement/select/SetOperation.java index 4438d75376..b08471bd82 100644 --- a/src/main/java/net/sf/jsqlparser/statement/select/SetOperation.java +++ b/src/main/java/net/sf/jsqlparser/statement/select/SetOperation.java @@ -20,19 +20,35 @@ public String getModifier() { } public boolean isAll() { - return modifier != null && modifier.contains("ALL"); + return leadsWith("ALL"); } public void setAll(boolean all) { - this.modifier = "ALL"; + this.modifier = all ? "ALL" : ""; } public boolean isDistinct() { - return modifier != null && modifier.contains("DISTINCT"); + return leadsWith("DISTINCT"); } public void setDistinct(boolean distinct) { - this.modifier = "DISTINCT"; + this.modifier = distinct ? "DISTINCT" : ""; + } + + /** + * Whether the modifier opens with {@code keyword}, which the grammar records verbatim from the + * source and so may be written in any case. Anchored at the start and required to end on a word + * boundary, so a {@code CORRESPONDING BY NAME MATCHING(all)} column list cannot be mistaken for + * the ALL quantifier. + */ + private boolean leadsWith(String keyword) { + if (modifier == null) { + return false; + } + String trimmed = modifier.trim(); + return trimmed.regionMatches(true, 0, keyword, 0, keyword.length()) + && (trimmed.length() == keyword.length() + || !Character.isLetterOrDigit(trimmed.charAt(keyword.length()))); } private final SetOperationType type; diff --git a/src/main/java/net/sf/jsqlparser/statement/select/TableFunction.java b/src/main/java/net/sf/jsqlparser/statement/select/TableFunction.java index 51e4057ec8..359ab7e770 100644 --- a/src/main/java/net/sf/jsqlparser/statement/select/TableFunction.java +++ b/src/main/java/net/sf/jsqlparser/statement/select/TableFunction.java @@ -24,6 +24,8 @@ public class TableFunction extends Function implements FromItem { private Function function; private ParenthesedExpressionList rowsFromFunctions; private String withClause = null; + private boolean withOffset = false; + private Alias offsetAlias = null; public TableFunction(Function function) { this.function = function; @@ -121,6 +123,37 @@ public TableFunction setPrefix(String prefix) { return this; } + /** + * BigQuery's {@code UNNEST(array) [AS alias] WITH OFFSET [AS offsetAlias]}, where the + * {@code WITH OFFSET} clause follows the table alias and carries an alias of its own. + */ + public boolean isWithOffset() { + return withOffset; + } + + public void setWithOffset(boolean withOffset) { + this.withOffset = withOffset; + } + + public TableFunction withWithOffset(boolean withOffset) { + setWithOffset(withOffset); + return this; + } + + public Alias getOffsetAlias() { + return offsetAlias; + } + + public void setOffsetAlias(Alias offsetAlias) { + this.offsetAlias = offsetAlias; + this.withOffset = true; + } + + public TableFunction withOffsetAlias(Alias offsetAlias) { + setOffsetAlias(offsetAlias); + return this; + } + public String getWithClause() { return withClause; } @@ -211,6 +244,13 @@ public StringBuilder appendTo(StringBuilder builder) { if (alias != null) { builder.append(alias); } + + if (withOffset) { + builder.append(" WITH OFFSET"); + if (offsetAlias != null) { + builder.append(offsetAlias); + } + } return builder; } diff --git a/src/main/java/net/sf/jsqlparser/statement/select/UnPivotQuery.java b/src/main/java/net/sf/jsqlparser/statement/select/UnPivotQuery.java new file mode 100644 index 0000000000..799e6b6123 --- /dev/null +++ b/src/main/java/net/sf/jsqlparser/statement/select/UnPivotQuery.java @@ -0,0 +1,115 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.statement.select; + +import net.sf.jsqlparser.expression.Expression; +import net.sf.jsqlparser.expression.operators.relational.ExpressionList; +import net.sf.jsqlparser.schema.Column; + +/** + * DuckDB's simplified UNPIVOT statement: + * {@code UNPIVOT tbl ON columns [INTO NAME name VALUE values]}, the counterpart of + * {@link PivotQuery}. + * + * @see UNPIVOT + */ +public class UnPivotQuery extends Select { + private FromItem fromItem; + private ExpressionList onExpressions; + private String nameColumn; + private ExpressionList valueColumns; + + public FromItem getFromItem() { + return fromItem; + } + + public void setFromItem(FromItem fromItem) { + this.fromItem = fromItem; + } + + public UnPivotQuery withFromItem(FromItem fromItem) { + setFromItem(fromItem); + return this; + } + + public ExpressionList getOnExpressions() { + return onExpressions; + } + + public void setOnExpressions(ExpressionList onExpressions) { + this.onExpressions = onExpressions; + } + + public UnPivotQuery withOnExpressions(ExpressionList onExpressions) { + setOnExpressions(onExpressions); + return this; + } + + public String getNameColumn() { + return nameColumn; + } + + public void setNameColumn(String nameColumn) { + this.nameColumn = nameColumn; + } + + public UnPivotQuery withNameColumn(String nameColumn) { + setNameColumn(nameColumn); + return this; + } + + public ExpressionList getValueColumns() { + return valueColumns; + } + + public void setValueColumns(ExpressionList valueColumns) { + this.valueColumns = valueColumns; + } + + public UnPivotQuery withValueColumns(ExpressionList valueColumns) { + setValueColumns(valueColumns); + return this; + } + + @Override + public StringBuilder appendSelectBodyTo(StringBuilder builder) { + builder.append("UNPIVOT ").append(fromItem); + if (onExpressions != null) { + builder.append(" ON ").append(onExpressions); + } + if (nameColumn != null) { + builder.append(" INTO NAME ").append(nameColumn); + if (valueColumns != null) { + builder.append(" VALUE ").append(valueColumns); + } + } + return builder; + } + + @Override + public T accept(SelectVisitor selectVisitor, S context) { + return selectVisitor.visit(this, context); + } + + @Override + public T accept(FromItemVisitor fromItemVisitor, S context) { + return fromItemVisitor.visit(this, context); + } + + @Override + public SampleClause getSampleClause() { + return null; + } + + @Override + public FromItem setSampleClause(SampleClause sampleClause) { + return null; + } +} diff --git a/src/main/java/net/sf/jsqlparser/statement/select/WithCycleClause.java b/src/main/java/net/sf/jsqlparser/statement/select/WithCycleClause.java new file mode 100644 index 0000000000..5a0cc5155b --- /dev/null +++ b/src/main/java/net/sf/jsqlparser/statement/select/WithCycleClause.java @@ -0,0 +1,129 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.statement.select; + +import java.io.Serializable; +import java.util.function.Consumer; +import net.sf.jsqlparser.expression.Expression; +import net.sf.jsqlparser.expression.ExpressionVisitor; +import net.sf.jsqlparser.expression.operators.relational.ExpressionList; +import net.sf.jsqlparser.schema.Column; + +/** Cycle detection metadata for a recursive common table expression. */ +public class WithCycleClause implements Serializable { + private ExpressionList cycleColumns; + private String markColumnName; + private Expression markValue; + private Expression markDefault; + private String pathColumnName; + + public ExpressionList getCycleColumns() { + return cycleColumns; + } + + public void setCycleColumns(ExpressionList cycleColumns) { + this.cycleColumns = cycleColumns; + } + + public WithCycleClause withCycleColumns(ExpressionList cycleColumns) { + setCycleColumns(cycleColumns); + return this; + } + + public String getMarkColumnName() { + return markColumnName; + } + + public void setMarkColumnName(String markColumnName) { + this.markColumnName = markColumnName; + } + + public WithCycleClause withMarkColumnName(String markColumnName) { + setMarkColumnName(markColumnName); + return this; + } + + public Expression getMarkValue() { + return markValue; + } + + public void setMarkValue(Expression markValue) { + this.markValue = markValue; + } + + public WithCycleClause withMarkValue(Expression markValue) { + setMarkValue(markValue); + return this; + } + + public Expression getMarkDefault() { + return markDefault; + } + + public void setMarkDefault(Expression markDefault) { + this.markDefault = markDefault; + } + + public WithCycleClause withMarkDefault(Expression markDefault) { + setMarkDefault(markDefault); + return this; + } + + public String getPathColumnName() { + return pathColumnName; + } + + public void setPathColumnName(String pathColumnName) { + this.pathColumnName = pathColumnName; + } + + public WithCycleClause withPathColumnName(String pathColumnName) { + setPathColumnName(pathColumnName); + return this; + } + + public void accept(ExpressionVisitor visitor, S context) { + if (cycleColumns != null) { + cycleColumns.forEach(column -> column.accept(visitor, context)); + } + if (markValue != null) { + markValue.accept(visitor, context); + } + if (markDefault != null) { + markDefault.accept(visitor, context); + } + } + + public StringBuilder appendTo(StringBuilder builder, Consumer expressionPrinter) { + builder.append("CYCLE "); + if (cycleColumns != null) { + for (int i = 0; i < cycleColumns.size(); i++) { + if (i > 0) { + builder.append(", "); + } + expressionPrinter.accept(cycleColumns.get(i)); + } + } + builder.append(" SET ").append(markColumnName); + if (markValue != null) { + builder.append(" TO "); + expressionPrinter.accept(markValue); + builder.append(" DEFAULT "); + expressionPrinter.accept(markDefault); + } + return builder.append(" USING ").append(pathColumnName); + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + return appendTo(builder, expression -> builder.append(expression)).toString(); + } +} diff --git a/src/main/java/net/sf/jsqlparser/statement/select/WithFill.java b/src/main/java/net/sf/jsqlparser/statement/select/WithFill.java new file mode 100644 index 0000000000..9da5672512 --- /dev/null +++ b/src/main/java/net/sf/jsqlparser/statement/select/WithFill.java @@ -0,0 +1,102 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2019 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.statement.select; + +import java.io.Serializable; + +import net.sf.jsqlparser.expression.Expression; + +/** + * Models the ClickHouse {@code WITH FILL} modifier of an {@link OrderByElement}, which fills gaps + * in the sorted sequence of the order by expression (see + * ClickHouse: + * ORDER BY). + *

+ * The modifier takes optional {@code FROM}, {@code TO}, {@code STEP} and {@code STALENESS} bounds, + * where the step may be an {@code INTERVAL} literal. Its presence on an {@link OrderByElement} + * marks the element as filled. + */ +public class WithFill implements Serializable { + + private Expression from; + private Expression to; + private Expression step; + private Expression staleness; + + public Expression getFrom() { + return from; + } + + public void setFrom(Expression from) { + this.from = from; + } + + public WithFill withFrom(Expression from) { + setFrom(from); + return this; + } + + public Expression getTo() { + return to; + } + + public void setTo(Expression to) { + this.to = to; + } + + public WithFill withTo(Expression to) { + setTo(to); + return this; + } + + public Expression getStep() { + return step; + } + + public void setStep(Expression step) { + this.step = step; + } + + public WithFill withStep(Expression step) { + setStep(step); + return this; + } + + public Expression getStaleness() { + return staleness; + } + + public void setStaleness(Expression staleness) { + this.staleness = staleness; + } + + public WithFill withStaleness(Expression staleness) { + setStaleness(staleness); + return this; + } + + @Override + public String toString() { + StringBuilder b = new StringBuilder("WITH FILL"); + if (from != null) { + b.append(" FROM ").append(from); + } + if (to != null) { + b.append(" TO ").append(to); + } + if (step != null) { + b.append(" STEP ").append(step); + } + if (staleness != null) { + b.append(" STALENESS ").append(staleness); + } + return b.toString(); + } +} diff --git a/src/main/java/net/sf/jsqlparser/statement/select/WithItem.java b/src/main/java/net/sf/jsqlparser/statement/select/WithItem.java index 01605c8a91..90cd9c0c5c 100644 --- a/src/main/java/net/sf/jsqlparser/statement/select/WithItem.java +++ b/src/main/java/net/sf/jsqlparser/statement/select/WithItem.java @@ -9,6 +9,10 @@ */ package net.sf.jsqlparser.statement.select; +import java.util.function.Consumer; +import net.sf.jsqlparser.expression.Expression; +import net.sf.jsqlparser.expression.operators.relational.ExpressionList; + import java.io.Serializable; import java.util.ArrayList; import java.util.Collection; @@ -27,10 +31,13 @@ public class WithItem implements Serializable { private Alias alias; private List> withItemList; private WithFunctionDeclaration withFunctionDeclaration; + private Expression expression; private WithSearchClause searchClause; + private WithCycleClause cycleClause; private boolean recursive = false; private boolean usingNot = false; private boolean materialized = false; + private ExpressionList usingKeyExpressions; public WithItem(K statement, Alias alias) { this.statement = statement; @@ -41,6 +48,25 @@ public WithItem() { this(null, (Alias) null); } + /** + * The aliased expression of a WITH item like ClickHouse's + * {@code WITH AS }, where the item does not hold a statement. + * + * @return the expression of this WITH item, or null for statement-based (CTE) items + */ + public Expression getExpression() { + return expression; + } + + public void setExpression(Expression expression) { + this.expression = expression; + } + + public WithItem withExpression(Expression expression) { + this.setExpression(expression); + return this; + } + public K getParenthesedStatement() { return statement; } @@ -149,11 +175,60 @@ public WithItem withSearchClause(WithSearchClause searchClause) { return this; } + public WithCycleClause getCycleClause() { + return cycleClause; + } + + public void setCycleClause(WithCycleClause cycleClause) { + this.cycleClause = cycleClause; + } + + public WithItem withCycleClause(WithCycleClause cycleClause) { + setCycleClause(cycleClause); + return this; + } + + /** + * DuckDB 2.0 aggregates a recursive CTE by key: + * {@code WITH RECURSIVE tbl (a, b) USING KEY (a, avg(b)) AS (...)}. + */ + public ExpressionList getUsingKeyExpressions() { + return usingKeyExpressions; + } + + public void setUsingKeyExpressions(ExpressionList usingKeyExpressions) { + this.usingKeyExpressions = usingKeyExpressions; + } + + public WithItem withUsingKeyExpressions(ExpressionList usingKeyExpressions) { + setUsingKeyExpressions(usingKeyExpressions); + return this; + } + + public StringBuilder appendRecursiveClausesTo(StringBuilder builder, + Consumer expressionPrinter) { + if (searchClause != null) { + builder.append(" "); + searchClause.appendTo(builder, expressionPrinter); + } + if (cycleClause != null) { + builder.append(" "); + cycleClause.appendTo(builder, expressionPrinter); + } + return builder; + } + @Override public String toString() { StringBuilder builder = new StringBuilder(); if (withFunctionDeclaration != null) { builder.append(withFunctionDeclaration); + } else if (expression != null) { + builder.append(expression); + if (alias != null) { + builder.append(" AS ").append(alias.getName()); + } + appendRecursiveClausesTo(builder, expr -> builder.append(expr)); } else { builder.append(recursive ? "RECURSIVE " : ""); if (alias != null) { @@ -167,6 +242,9 @@ public String toString() { } builder.append(")"); } + if (usingKeyExpressions != null) { + builder.append(" USING KEY (").append(usingKeyExpressions).append(")"); + } builder.append(" AS "); if (materialized) { builder.append(usingNot @@ -174,9 +252,7 @@ public String toString() { : "MATERIALIZED "); } builder.append(statement); - if (searchClause != null) { - builder.append(" ").append(searchClause); - } + appendRecursiveClausesTo(builder, expression -> builder.append(expression)); } return builder.toString(); } diff --git a/src/main/java/net/sf/jsqlparser/statement/select/WithSearchClause.java b/src/main/java/net/sf/jsqlparser/statement/select/WithSearchClause.java index 84de6e2233..75f7a1949e 100644 --- a/src/main/java/net/sf/jsqlparser/statement/select/WithSearchClause.java +++ b/src/main/java/net/sf/jsqlparser/statement/select/WithSearchClause.java @@ -9,6 +9,9 @@ */ package net.sf.jsqlparser.statement.select; +import java.util.function.Consumer; +import net.sf.jsqlparser.expression.Expression; + import java.io.Serializable; import java.util.Collection; @@ -86,15 +89,23 @@ public WithSearchClause withSequenceColumnName(String sequenceColumnName) { return this; } + public StringBuilder appendTo(StringBuilder builder, + Consumer expressionPrinter) { + builder.append("SEARCH ").append(searchOrder).append(" FIRST BY "); + if (searchColumns != null) { + for (int i = 0; i < searchColumns.size(); i++) { + if (i > 0) { + builder.append(", "); + } + expressionPrinter.accept(searchColumns.get(i)); + } + } + return builder.append(" SET ").append(sequenceColumnName); + } + @Override public String toString() { - return new StringBuilder() - .append("SEARCH ") - .append(searchOrder) - .append(" FIRST BY ") - .append(Select.getStringList(searchColumns)) - .append(" SET ") - .append(sequenceColumnName) - .toString(); + StringBuilder builder = new StringBuilder(); + return appendTo(builder, expression -> builder.append(expression)).toString(); } } diff --git a/src/main/java/net/sf/jsqlparser/statement/update/Update.java b/src/main/java/net/sf/jsqlparser/statement/update/Update.java index b09854fe69..8e17df515a 100644 --- a/src/main/java/net/sf/jsqlparser/statement/update/Update.java +++ b/src/main/java/net/sf/jsqlparser/statement/update/Update.java @@ -14,6 +14,7 @@ import net.sf.jsqlparser.expression.PreferringClause; import net.sf.jsqlparser.schema.Column; import net.sf.jsqlparser.schema.Table; +import net.sf.jsqlparser.statement.ForPortionClause; import net.sf.jsqlparser.statement.OutputClause; import net.sf.jsqlparser.statement.ReturningClause; import net.sf.jsqlparser.statement.Statement; @@ -21,6 +22,7 @@ import net.sf.jsqlparser.statement.select.FromItem; import net.sf.jsqlparser.statement.select.Join; import net.sf.jsqlparser.statement.select.Limit; +import net.sf.jsqlparser.statement.select.OptionClause; import net.sf.jsqlparser.statement.select.OrderByElement; import net.sf.jsqlparser.statement.select.PlainSelect; import net.sf.jsqlparser.statement.select.Select; @@ -39,15 +41,18 @@ public class Update implements Statement { private List> withItemsList; private Table table; + private ForPortionClause forPortionClause; private Expression where; private PreferringClause preferringClause; private List updateSets; private FromItem fromItem; + private boolean fromBeforeSet; private List joins; private List startJoins; private OracleHint oracleHint = null; private List orderByElements; private Limit limit; + private OptionClause option; private ReturningClause returningClause; private UpdateModifierPriority modifierPriority; private boolean modifierIgnore; @@ -111,6 +116,19 @@ public Update addWithItemsList(Collection> withItemsList) return this.withWithItemsList(collection); } + public ForPortionClause getForPortionClause() { + return forPortionClause; + } + + public void setForPortionClause(ForPortionClause forPortionClause) { + this.forPortionClause = forPortionClause; + } + + public Update withForPortionClause(ForPortionClause forPortionClause) { + setForPortionClause(forPortionClause); + return this; + } + public Table getTable() { return table; } @@ -188,6 +206,50 @@ public void setFromItem(FromItem fromItem) { this.fromItem = fromItem; } + /** Whether the FROM clause precedes SET, as in Teradata joined updates. */ + public boolean isFromBeforeSet() { + return fromBeforeSet; + } + + public void setFromBeforeSet(boolean fromBeforeSet) { + this.fromBeforeSet = fromBeforeSet; + } + + public Update withFromBeforeSet(boolean fromBeforeSet) { + setFromBeforeSet(fromBeforeSet); + return this; + } + + /** Whether the Teradata target names an alias declared in this UPDATE's FROM clause. */ + public boolean isTargetTableAlias() { + if (!fromBeforeSet || table == null) { + return false; + } + if (matchesTargetAlias(fromItem)) { + return true; + } + return joins != null + && joins.stream().anyMatch(join -> matchesTargetAlias(join.getRightItem())); + } + + private boolean matchesTargetAlias(FromItem source) { + return source != null && source.getAlias() != null + && table.getFullyQualifiedName().equalsIgnoreCase(source.getAlias().getName()); + } + + /** Shared FROM rendering for both supported clause positions. */ + public StringBuilder appendFromTo(StringBuilder builder) { + if (fromItem != null) { + builder.append(" FROM ").append(fromItem); + if (joins != null) { + for (Join join : joins) { + builder.append(join.isSimple() ? ", " : " ").append(join); + } + } + } + return builder; + } + public List getJoins() { return joins; } @@ -255,6 +317,19 @@ public Limit getLimit() { return limit; } + public OptionClause getOption() { + return option; + } + + public Update setOption(OptionClause option) { + this.option = option; + return this; + } + + public Update withOption(OptionClause option) { + return setOption(option); + } + public void setLimit(Limit limit) { this.limit = limit; } @@ -312,6 +387,9 @@ public String toString() { b.append("IGNORE "); } b.append(table); + if (forPortionClause != null) { + b.append(' ').append(forPortionClause); + } if (startJoins != null) { for (Join join : startJoins) { if (join.isSimple()) { @@ -322,6 +400,9 @@ public String toString() { } } + if (fromBeforeSet) { + appendFromTo(b); + } b.append(" SET "); UpdateSet.appendUpdateSetsTo(b, updateSets); @@ -329,17 +410,8 @@ public String toString() { outputClause.appendTo(b); } - if (fromItem != null) { - b.append(" FROM ").append(fromItem); - if (joins != null) { - for (Join join : joins) { - if (join.isSimple()) { - b.append(", ").append(join); - } else { - b.append(" ").append(join); - } - } - } + if (!fromBeforeSet) { + appendFromTo(b); } if (where != null) { @@ -356,6 +428,10 @@ public String toString() { b.append(limit); } + if (option != null) { + b.append(option); + } + if (returningClause != null) { returningClause.appendTo(b); } diff --git a/src/main/java/net/sf/jsqlparser/util/TableDefinitionTraversal.java b/src/main/java/net/sf/jsqlparser/util/TableDefinitionTraversal.java new file mode 100644 index 0000000000..f6883922ec --- /dev/null +++ b/src/main/java/net/sf/jsqlparser/util/TableDefinitionTraversal.java @@ -0,0 +1,185 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.util; + +import java.util.List; +import java.util.function.Consumer; +import net.sf.jsqlparser.expression.Expression; +import net.sf.jsqlparser.schema.Table; +import net.sf.jsqlparser.statement.LikeClause; +import net.sf.jsqlparser.statement.alter.AlterExpression; +import net.sf.jsqlparser.statement.alter.AlterExpressionPartition; +import net.sf.jsqlparser.statement.alter.AlterExpressionPrimaryKey; +import net.sf.jsqlparser.statement.create.index.CreateIndex; +import net.sf.jsqlparser.statement.create.table.CheckConstraint; +import net.sf.jsqlparser.statement.create.table.ColumnDefinition; +import net.sf.jsqlparser.statement.create.table.ColumnOption; +import net.sf.jsqlparser.statement.create.table.CreateTable; +import net.sf.jsqlparser.statement.create.table.DefaultConstraint; +import net.sf.jsqlparser.statement.create.table.ExcludeConstraint; +import net.sf.jsqlparser.statement.create.table.ForeignKeyIndex; +import net.sf.jsqlparser.statement.create.table.Index; +import net.sf.jsqlparser.statement.create.table.TableElement; +import net.sf.jsqlparser.statement.create.table.TablePartitioning; +import net.sf.jsqlparser.statement.create.table.PartitionBound; + +/** Traverses structured table definitions without interpreting legacy raw column options. */ +public final class TableDefinitionTraversal { + private TableDefinitionTraversal() {} + + public static void visit(CreateIndex createIndex, Consumer expressions, + Consumer

tables) { + accept(createIndex.getTable(), tables); + if (createIndex.getIndex() != null) { + visit(createIndex.getIndex(), expressions, tables); + } + visitOptions(createIndex.getStorageParameters(), expressions); + accept(createIndex.getWhere(), expressions); + } + + /** Visits the structured definitions and expressions belonging to a single ALTER action. */ + public static void visit(AlterExpression action, Consumer expressions, + Consumer
tables) { + if (action.getColumnSetDefaultList() != null) { + action.getColumnSetDefaultList() + .forEach(column -> accept(column.getDefaultExpression(), expressions)); + } + if (action.getColDataTypeList() != null) { + action.getColDataTypeList().forEach(column -> visit(column, expressions, tables)); + } + if (action.getIndex() != null) { + visit(action.getIndex(), expressions, tables); + } + if (action instanceof AlterExpressionPartition) { + AlterExpressionPartition partition = (AlterExpressionPartition) action; + switch (partition.getOperation()) { + case ATTACH_PARTITION: + accept(partition.getPartitionTable(), tables); + visit(partition.getPartitionBound(), expressions); + break; + case DETACH_PARTITION: + accept(partition.getPartitionTable(), tables); + break; + case EXCHANGE_PARTITION: + accept(partition.getExchangeTable(), tables); + break; + case PARTITION_BY: + visit(partition.getPartitioning(), expressions); + break; + default: + break; + } + } + if (action instanceof AlterExpressionPrimaryKey) { + AlterExpressionPrimaryKey primaryKey = (AlterExpressionPrimaryKey) action; + if (primaryKey.isUsingHash()) { + accept(primaryKey.getBucketCount(), expressions); + } + } + } + + public static void visit(CreateTable table, Consumer expressions, + Consumer
tables) { + if (table.getTableElements() != null) { + table.getTableElements().forEach(element -> visit(element, expressions, tables)); + } else { + if (table.getColumnDefinitions() != null) { + table.getColumnDefinitions().forEach(column -> visit(column, expressions, tables)); + } + if (table.getIndexes() != null) { + table.getIndexes().forEach(index -> visit(index, expressions, tables)); + } + } + accept(table.getTrailingLikeTable(), tables); + accept(table.getPartitionOf(), tables); + visit(table.getPartitioning(), expressions); + visit(table.getPartitionBound(), expressions); + } + + /** Visits the active partition key and any subpartition key. Raw bounds remain opaque. */ + public static void visit(TablePartitioning partitioning, Consumer expressions) { + if (partitioning == null) { + return; + } + if (partitioning.getExpression() != null) { + accept(partitioning.getExpression(), expressions); + } else if (partitioning.getExpressionList() != null) { + accept(partitioning.getExpressionList(), expressions); + } else { + accept(partitioning.getColumns(), expressions); + } + visit(partitioning.getSubPartitioning(), expressions); + } + + /** Visits expressions belonging to the selected PostgreSQL bound type. */ + public static void visit(PartitionBound bound, Consumer expressions) { + if (bound != null) { + bound.visitExpressions(expressions); + } + } + + public static void visit(TableElement element, Consumer expressions, + Consumer
tables) { + if (element instanceof LikeClause) { + accept(((LikeClause) element).getTable(), tables); + } else if (element instanceof ColumnDefinition) { + ColumnDefinition column = (ColumnDefinition) element; + if (column.getColumnOptions() != null) { + for (ColumnOption option : column.getColumnOptions()) { + option.visitExpressions(expressions); + if (option.getForeignKeyReference() != null) { + accept(option.getForeignKeyReference().getTable(), tables); + } + if (option.getConstraint() != null) { + visit(option.getConstraint(), expressions, tables); + } + } + } + if (column instanceof AlterExpression.ColumnDataType) { + accept(((AlterExpression.ColumnDataType) column).getUsingExpression(), expressions); + } + } else if (element instanceof Index) { + Index index = (Index) element; + if (index.getColumns() != null) { + for (Index.ColumnParams column : index.getColumns()) { + accept(column.getExpression(), expressions); + visitOptions(column.getOperatorClassParameters(), expressions); + } + } + visitOptions(index.getStorageParameters(), expressions); + if (index instanceof CheckConstraint) { + accept(((CheckConstraint) index).getExpression(), expressions); + } + if (index instanceof DefaultConstraint) { + DefaultConstraint constraint = (DefaultConstraint) index; + accept(constraint.getExpression(), expressions); + accept(constraint.getColumn(), expressions); + } + if (index instanceof ExcludeConstraint) { + accept(((ExcludeConstraint) index).getExpression(), expressions); + } + if (index instanceof ForeignKeyIndex) { + accept(((ForeignKeyIndex) index).getTable(), tables); + } + } + } + + private static void visitOptions(List options, Consumer expressions) { + if (options != null) { + options.forEach(option -> accept(option.getValue(), expressions)); + } + } + + private static void accept(T value, Consumer consumer) { + if (value != null) { + consumer.accept(value); + } + } +} diff --git a/src/main/java/net/sf/jsqlparser/util/TablesNamesFinder.java b/src/main/java/net/sf/jsqlparser/util/TablesNamesFinder.java index 6eea66a574..58ed96435c 100644 --- a/src/main/java/net/sf/jsqlparser/util/TablesNamesFinder.java +++ b/src/main/java/net/sf/jsqlparser/util/TablesNamesFinder.java @@ -9,6 +9,30 @@ */ package net.sf.jsqlparser.util; +import net.sf.jsqlparser.statement.select.MatchRecognize; +import net.sf.jsqlparser.expression.RowPatternFunction; + +import net.sf.jsqlparser.expression.AliasedExpression; + +import net.sf.jsqlparser.statement.oracle.OracleBlock; +import net.sf.jsqlparser.statement.oracle.OracleAssignment; +import net.sf.jsqlparser.statement.oracle.OracleNullStatement; + +import net.sf.jsqlparser.statement.role.CreateRole; +import net.sf.jsqlparser.statement.role.AlterRole; +import net.sf.jsqlparser.statement.grant.Revoke; +import net.sf.jsqlparser.statement.grant.AlterDefaultPrivileges; +import net.sf.jsqlparser.statement.create.type.CreateType; +import net.sf.jsqlparser.statement.alter.AlterType; +import net.sf.jsqlparser.statement.create.domain.CreateDomain; +import net.sf.jsqlparser.statement.alter.AlterDomain; +import net.sf.jsqlparser.statement.create.extension.CreateExtension; +import net.sf.jsqlparser.statement.alter.AlterExtension; +import net.sf.jsqlparser.statement.create.publication.CreatePublication; +import net.sf.jsqlparser.statement.alter.AlterPublication; +import net.sf.jsqlparser.statement.create.subscription.CreateSubscription; +import net.sf.jsqlparser.statement.alter.AlterSubscription; + import java.util.ArrayList; import java.util.HashSet; import java.util.List; @@ -46,6 +70,7 @@ import net.sf.jsqlparser.expression.operators.relational.GreaterThanEquals; import net.sf.jsqlparser.expression.operators.relational.InExpression; import net.sf.jsqlparser.expression.operators.relational.IncludesExpression; +import net.sf.jsqlparser.expression.operators.relational.Intersects; import net.sf.jsqlparser.expression.operators.relational.IsBooleanExpression; import net.sf.jsqlparser.expression.operators.relational.IsDistinctExpression; import net.sf.jsqlparser.expression.operators.relational.IsNullExpression; @@ -71,11 +96,14 @@ import net.sf.jsqlparser.statement.CreateFunctionalStatement; import net.sf.jsqlparser.statement.DeclareStatement; import net.sf.jsqlparser.statement.DescribeStatement; +import net.sf.jsqlparser.statement.DoStatement; import net.sf.jsqlparser.statement.ExplainStatement; import net.sf.jsqlparser.statement.IfElseStatement; +import net.sf.jsqlparser.statement.OutputClause; import net.sf.jsqlparser.statement.PurgeObjectType; import net.sf.jsqlparser.statement.PurgeStatement; import net.sf.jsqlparser.statement.ResetStatement; +import net.sf.jsqlparser.statement.ReturningClause; import net.sf.jsqlparser.statement.RollbackStatement; import net.sf.jsqlparser.statement.SavepointStatement; import net.sf.jsqlparser.statement.SessionStatement; @@ -87,6 +115,7 @@ import net.sf.jsqlparser.statement.Statements; import net.sf.jsqlparser.statement.UnsupportedStatement; import net.sf.jsqlparser.statement.UseStatement; +import net.sf.jsqlparser.statement.SetIdentityInsertStatement; import net.sf.jsqlparser.statement.alter.Alter; import net.sf.jsqlparser.statement.alter.AlterSession; import net.sf.jsqlparser.statement.alter.AlterSystemStatement; @@ -94,28 +123,61 @@ import net.sf.jsqlparser.statement.alter.sequence.AlterSequence; import net.sf.jsqlparser.statement.analyze.Analyze; import net.sf.jsqlparser.statement.comment.Comment; +import net.sf.jsqlparser.statement.create.database.CreateDatabase; +import net.sf.jsqlparser.statement.create.event.AlterEvent; +import net.sf.jsqlparser.statement.create.event.CreateEvent; import net.sf.jsqlparser.statement.create.index.CreateIndex; import net.sf.jsqlparser.statement.create.policy.CreatePolicy; import net.sf.jsqlparser.statement.create.schema.CreateSchema; import net.sf.jsqlparser.statement.create.sequence.CreateSequence; import net.sf.jsqlparser.statement.create.synonym.CreateSynonym; import net.sf.jsqlparser.statement.create.table.CreateTable; +import net.sf.jsqlparser.statement.create.trigger.CreateTrigger; +import net.sf.jsqlparser.statement.create.user.CreateUser; import net.sf.jsqlparser.statement.create.view.AlterView; import net.sf.jsqlparser.statement.create.view.CreateView; import net.sf.jsqlparser.statement.delete.Delete; import net.sf.jsqlparser.statement.delete.ParenthesedDelete; import net.sf.jsqlparser.statement.drop.Drop; import net.sf.jsqlparser.statement.execute.Execute; +import net.sf.jsqlparser.statement.execute.ExecuteArgument; import net.sf.jsqlparser.statement.export.Export; import net.sf.jsqlparser.statement.grant.Grant; import net.sf.jsqlparser.statement.imprt.Import; import net.sf.jsqlparser.statement.insert.Insert; +import net.sf.jsqlparser.statement.insert.InsertBulk; +import net.sf.jsqlparser.statement.insert.InsertConflictAction; +import net.sf.jsqlparser.statement.insert.InsertDuplicateAction; import net.sf.jsqlparser.statement.insert.OracleMultiInsertBranch; import net.sf.jsqlparser.statement.insert.OracleMultiInsertClause; import net.sf.jsqlparser.statement.insert.ParenthesedInsert; import net.sf.jsqlparser.statement.lock.LockStatement; import net.sf.jsqlparser.statement.merge.Merge; +import net.sf.jsqlparser.statement.merge.MergeDelete; +import net.sf.jsqlparser.statement.merge.MergeInsert; +import net.sf.jsqlparser.statement.merge.MergeOperation; +import net.sf.jsqlparser.statement.merge.MergeOperationVisitor; +import net.sf.jsqlparser.statement.merge.MergeUpdate; +import net.sf.jsqlparser.statement.piped.AggregatePipeOperator; +import net.sf.jsqlparser.statement.piped.AsPipeOperator; +import net.sf.jsqlparser.statement.piped.CallPipeOperator; +import net.sf.jsqlparser.statement.piped.DropPipeOperator; +import net.sf.jsqlparser.statement.piped.ExtendPipeOperator; import net.sf.jsqlparser.statement.piped.FromQuery; +import net.sf.jsqlparser.statement.piped.JoinPipeOperator; +import net.sf.jsqlparser.statement.piped.LimitPipeOperator; +import net.sf.jsqlparser.statement.piped.OrderByPipeOperator; +import net.sf.jsqlparser.statement.piped.PipeOperator; +import net.sf.jsqlparser.statement.piped.PipeOperatorVisitor; +import net.sf.jsqlparser.statement.piped.PivotPipeOperator; +import net.sf.jsqlparser.statement.piped.RenamePipeOperator; +import net.sf.jsqlparser.statement.piped.SelectPipeOperator; +import net.sf.jsqlparser.statement.piped.SetOperationPipeOperator; +import net.sf.jsqlparser.statement.piped.SetPipeOperator; +import net.sf.jsqlparser.statement.piped.TableSamplePipeOperator; +import net.sf.jsqlparser.statement.piped.UnPivotPipeOperator; +import net.sf.jsqlparser.statement.piped.WherePipeOperator; +import net.sf.jsqlparser.statement.piped.WindowPipeOperator; import net.sf.jsqlparser.statement.refresh.RefreshMaterializedViewStatement; import net.sf.jsqlparser.statement.select.AllColumns; import net.sf.jsqlparser.statement.select.AllTableColumns; @@ -124,9 +186,15 @@ import net.sf.jsqlparser.statement.select.FunctionAllColumns; import net.sf.jsqlparser.statement.select.Join; import net.sf.jsqlparser.statement.select.LateralSubSelect; +import net.sf.jsqlparser.statement.select.LateralView; import net.sf.jsqlparser.statement.select.OrderByElement; import net.sf.jsqlparser.statement.select.ParenthesedFromItem; import net.sf.jsqlparser.statement.select.ParenthesedSelect; +import net.sf.jsqlparser.statement.select.Pivot; +import net.sf.jsqlparser.statement.select.PivotQuery; +import net.sf.jsqlparser.statement.select.UnPivotQuery; +import net.sf.jsqlparser.statement.select.PivotVisitor; +import net.sf.jsqlparser.statement.select.PivotXml; import net.sf.jsqlparser.statement.select.PlainSelect; import net.sf.jsqlparser.statement.select.Select; import net.sf.jsqlparser.statement.select.SelectItem; @@ -134,6 +202,7 @@ import net.sf.jsqlparser.statement.select.SelectVisitor; import net.sf.jsqlparser.statement.select.SetOperationList; import net.sf.jsqlparser.statement.select.TableFunction; +import net.sf.jsqlparser.statement.select.UnPivot; import net.sf.jsqlparser.statement.select.TableStatement; import net.sf.jsqlparser.statement.select.Values; import net.sf.jsqlparser.statement.select.WithItem; @@ -144,6 +213,20 @@ import net.sf.jsqlparser.statement.update.Update; import net.sf.jsqlparser.statement.update.UpdateSet; import net.sf.jsqlparser.statement.upsert.Upsert; +import net.sf.jsqlparser.statement.PragmaStatement; +import net.sf.jsqlparser.statement.ExtensionStatement; +import net.sf.jsqlparser.statement.AttachStatement; +import net.sf.jsqlparser.statement.DetachStatement; +import net.sf.jsqlparser.statement.ConnectStatement; +import net.sf.jsqlparser.statement.DisconnectStatement; +import net.sf.jsqlparser.statement.PrepareStatement; +import net.sf.jsqlparser.statement.DeallocateStatement; +import net.sf.jsqlparser.statement.CopyStatement; +import net.sf.jsqlparser.statement.create.macro.CreateMacro; +import net.sf.jsqlparser.statement.create.extension.CreateExtensionRepository; +import net.sf.jsqlparser.statement.AssertStatement; +import net.sf.jsqlparser.statement.export.ExportDataStatement; +import net.sf.jsqlparser.statement.load.LoadDataStatement; /** @@ -155,10 +238,12 @@ @SuppressWarnings({"PMD.CyclomaticComplexity", "PMD.UncommentedEmptyMethodBody"}) public class TablesNamesFinder implements SelectVisitor, FromItemVisitor, ExpressionVisitor, - SelectItemVisitor, StatementVisitor { + SelectItemVisitor, StatementVisitor, MergeOperationVisitor, + PipeOperatorVisitor, PivotVisitor { private Set tables; private boolean allowColumnProcessing = false; + private Set rowPatternVariables = java.util.Collections.emptySet(); private List otherItemNames; @@ -249,6 +334,14 @@ public Void visit(RangeExpression rangeExpression, S context) { return null; } + @Override + public Void visit(TernaryExpression ternaryExpression, S context) { + ternaryExpression.getCondition().accept(this, context); + ternaryExpression.getThenExpression().accept(this, context); + ternaryExpression.getElseExpression().accept(this, context); + return null; + } + /** * Main entry for this Tool class. A list of found tables is returned. */ @@ -268,8 +361,14 @@ public Void visit(WithItem withItem, S context) { if (withItem.getAlias() != null) { otherItemNames.add(withItem.getAlias().getName()); } - if (withItem.getSelect() != null) { - withItem.getSelect().accept((SelectVisitor) this, context); + if (withItem.getCycleClause() != null) { + withItem.getCycleClause().accept(this, context); + } + // dispatch any ParenthesedStatement payload (Select, Delete, Update, Insert) + withItem.accept((StatementVisitor) this, context); + // an expression alias (WITH expr AS name) may read tables on its own + if (withItem.getExpression() != null) { + withItem.getExpression().accept(this, context); } return null; } @@ -291,6 +390,20 @@ public Void visit(ParenthesedSelect select, S context) { } } select.getSelect().accept((SelectVisitor) this, context); + visitOrderBy(select.getOrderByElements(), context); + if (select.getPivot() != null) { + select.getPivot().accept(this, context); + } + if (select.getUnPivot() != null) { + select.getUnPivot().accept(this, context); + } + visitLimit(select.getLimit(), context); + if (select.getOffset() != null) { + select.getOffset().getOffset().accept(this, context); + } + if (select.getFetch() != null) { + select.getFetch().getExpression().accept(this, context); + } return null; } @@ -301,38 +414,110 @@ public void visit(ParenthesedSelect parenthesedSelect) { @Override public Void visit(PlainSelect plainSelect, S context) { - List> withItemsList = plainSelect.getWithItemsList(); - if (withItemsList != null && !withItemsList.isEmpty()) { - for (WithItem withItem : withItemsList) { - withItem.accept((SelectVisitor) this, context); - } + Set previous = rowPatternVariables; + try { + rowPatternVariables = java.util.Collections.emptySet(); + return visitPlainSelectBody(plainSelect, context); + } finally { + rowPatternVariables = previous; } - if (plainSelect.getSelectItems() != null) { - for (SelectItem item : plainSelect.getSelectItems()) { - item.accept(this, context); - } + } + + private Void visitPlainSelectBody(PlainSelect plainSelect, S context) { + visitWithItems(plainSelect.getWithItemsList(), context); + if (plainSelect.getDistinct() != null) { + visitSelectItems(plainSelect.getDistinct().getOnSelectItems(), context); } + visitTables(plainSelect.getIntoTables(), context); - if (plainSelect.getFromItem() != null) { - plainSelect.getFromItem().accept(this, context); + visitSelectItems(plainSelect.getSelectItems(), context); + visitFromItem(plainSelect.getFromItem(), context); + + if (plainSelect.getLateralViews() != null) { + for (LateralView lateralView : plainSelect.getLateralViews()) { + lateralView.getGeneratorFunction().accept(this, context); + } } visitJoins(plainSelect.getJoins(), context); - if (plainSelect.getPreWhere() != null) { - plainSelect.getPreWhere().accept(this, context); + visitExpression(plainSelect.getPreWhere(), context); + visitExpression(plainSelect.getWhere(), context); + + visitPreferringClause(plainSelect.getPreferringClause(), context); + visit(plainSelect.getGroupBy(), context); + + visitExpression(plainSelect.getHaving(), context); + + visitExpression(plainSelect.getQualify(), context); + + visitExpression(plainSelect.getOracleHierarchical(), context); + + if (plainSelect.getWindowDefinitions() != null) { + for (WindowDefinition windowDefinition : plainSelect.getWindowDefinitions()) { + visitExpressions(windowDefinition.getPartitionExpressionList(), context); + visitOrderBy(windowDefinition.getOrderByElements(), context); + } + } + + if (plainSelect.getPivot() != null) { + plainSelect.getPivot().accept(this, context); } - if (plainSelect.getWhere() != null) { - plainSelect.getWhere().accept(this, context); + if (plainSelect.getUnPivot() != null) { + plainSelect.getUnPivot().accept(this, context); } - if (plainSelect.getHaving() != null) { - plainSelect.getHaving().accept(this, context); + visitOrderBy(plainSelect.getOrderByElements(), context); + visitLimit(plainSelect.getLimit(), context); + visitLimit(plainSelect.getLimitBy(), context); + visitSelectPagination(plainSelect, context); + visitUpdateSets(plainSelect.getSettings(), context); + visitFromItem(plainSelect.getIntoTempTable(), context); + return null; + } + + @Override + public Void visit(PivotQuery pivotQuery, S context) { + visitWithItems(pivotQuery.getWithItemsList(), context); + visitFromItem(pivotQuery.getFromItem(), context); + visitExpressions(pivotQuery.getOnExpressions(), context); + visitSelectItems(pivotQuery.getUsingItems(), context); + visitExpressions(pivotQuery.getGroupByExpressions(), context); + visitOrderBy(pivotQuery.getOrderByElements(), context); + visitLimit(pivotQuery.getLimit(), context); + + visitSelectPagination(pivotQuery, context); + return null; + } + + @Override + public Void visit(UnPivotQuery unPivotQuery, S context) { + visitWithItems(unPivotQuery.getWithItemsList(), context); + visitFromItem(unPivotQuery.getFromItem(), context); + visitExpressions(unPivotQuery.getOnExpressions(), context); + visitExpressions(unPivotQuery.getValueColumns(), context); + return null; + } + + @Override + public void visit(UnPivotQuery unPivotQuery) { + SelectVisitor.super.visit(unPivotQuery); + } + + private void visitSelectItems(List> selectItems, S context) { + if (selectItems != null) { + for (SelectItem item : selectItems) { + item.accept(this, context); + } } + } - if (plainSelect.getOracleHierarchical() != null) { - plainSelect.getOracleHierarchical().accept(this, context); + private void visitSelectPagination(Select select, S context) { + if (select.getOffset() != null) { + select.getOffset().getOffset().accept(this, context); + } + if (select.getFetch() != null && select.getFetch().getExpression() != null) { + select.getFetch().getExpression().accept(this, context); } - return null; } @Override @@ -340,6 +525,11 @@ public void visit(PlainSelect plainSelect) { SelectVisitor.super.visit(plainSelect); } + @Override + public void visit(PivotQuery pivotQuery) { + SelectVisitor.super.visit(pivotQuery); + } + /** * Override to adapt the tableName generation (e.g. with / without schema). * @@ -350,12 +540,47 @@ protected String extractTableName(Table table) { return table.getFullyQualifiedName(); } + @Override + public Void visit(RowPatternFunction function, S context) { + return function.getFunction().accept(this, context); + } + + @Override + public Void visit(MatchRecognize matchRecognize, S context) { + matchRecognize.getInput().accept(this, context); + if (matchRecognize.getPivot() != null) { + matchRecognize.getPivot().accept(this, context); + } + if (matchRecognize.getUnPivot() != null) { + matchRecognize.getUnPivot().accept(this, context); + } + matchRecognize.getPartitionBy().forEach(expression -> expression.accept(this, context)); + matchRecognize.getOrderByElements() + .forEach(order -> order.getExpression().accept(this, context)); + Set previous = rowPatternVariables; + try { + rowPatternVariables = matchRecognize.getPatternVariableNames(); + matchRecognize.forEachPatternExpression(expression -> expression.accept(this, context)); + } finally { + rowPatternVariables = previous; + } + return null; + } + @Override public Void visit(Table table, S context) { String tableWholeName = extractTableName(table); - if (!otherItemNames.contains(tableWholeName)) { + if (table.isTableVariable()) { + otherItemNames.add(tableWholeName); + } else if (!otherItemNames.contains(tableWholeName)) { tables.add(tableWholeName); } + if (table.getPivot() != null) { + table.getPivot().accept(this, context); + } + if (table.getUnPivot() != null) { + table.getUnPivot().accept(this, context); + } return null; } @@ -394,7 +619,9 @@ public Void visit(OverlapsCondition overlapsCondition, S context) { @Override public Void visit(Column tableColumn, S context) { if (allowColumnProcessing && tableColumn.getTable() != null - && tableColumn.getTable().getName() != null) { + && tableColumn.getTable().getName() != null + && !rowPatternVariables.contains( + MatchRecognize.normalizeVariableName(tableColumn.getTable().getName()))) { visit(tableColumn.getTable(), context); } return null; @@ -759,7 +986,15 @@ public Void visit(AnalyticExpression analytic, S context) { if (analytic.getKeep() != null) { analytic.getKeep().accept(this, context); } + if (analytic.getFilterExpression() != null) { + analytic.getFilterExpression().accept(this, context); + } if (analytic.getFuncOrderBy() != null) { + for (OrderByElement element : analytic.getFuncOrderBy()) { + element.getExpression().accept(this, context); + } + } + if (analytic.getOrderByElements() != null) { for (OrderByElement element : analytic.getOrderByElements()) { element.getExpression().accept(this, context); } @@ -800,6 +1035,14 @@ public Void visit(SetOperationList list, S context) { for (Select selectBody : list.getSelects()) { selectBody.accept((SelectVisitor) this, context); } + visitOrderBy(list.getOrderByElements(), context); + visitLimit(list.getLimit(), context); + if (list.getOffset() != null) { + list.getOffset().getOffset().accept(this, context); + } + if (list.getFetch() != null) { + list.getFetch().getExpression().accept(this, context); + } return null; } @@ -843,9 +1086,173 @@ public void visit(TableStatement tableStatement) { @Override public Void visit(FromQuery fromQuery, S context) { + List> withItemsList = fromQuery.getWithItemsList(); + if (withItemsList != null && !withItemsList.isEmpty()) { + for (WithItem withItem : withItemsList) { + withItem.accept((SelectVisitor) this, context); + } + } + if (fromQuery.getFromItem() != null) { + fromQuery.getFromItem().accept(this, context); + } + for (PipeOperator pipeOperator : fromQuery.getPipeOperators()) { + pipeOperator.accept(this, null); + } + return null; + } + + @Override + public Void visit(Pivot pivot, S context) { + visitSelectItems(pivot.getFunctionItems(), context); + visitExpressions(pivot.getForColumns(), context); + visitSelectItems(pivot.getSingleInItems(), context); + visitSelectItems(pivot.getMultiInItems(), context); + return null; + } + + @Override + public Void visit(PivotXml pivotXml, S context) { + visit((Pivot) pivotXml, context); + if (pivotXml.getInSelect() != null) { + pivotXml.getInSelect().accept((SelectVisitor) this, context); + } + return null; + } + + @Override + public Void visit(UnPivot unpivot, S context) { + for (Column column : unpivot.getUnPivotClause()) { + column.accept(this, context); + } + for (Column column : unpivot.getUnPivotForClause()) { + column.accept(this, context); + } + visitSelectItems(unpivot.getUnPivotInClause(), context); + return null; + } + + @Override + public Void visit(AggregatePipeOperator aggregate, Void context) { + for (SelectItem selectItem : aggregate.getSelectItems()) { + selectItem.accept(this, context); + } + for (SelectItem groupItem : aggregate.getGroupItems()) { + groupItem.accept(this, context); + } + return null; + } + + @Override + public Void visit(AsPipeOperator as, Void context) { + if (as.getAlias() != null) { + otherItemNames.add(as.getAlias().getName()); + } + return null; + } + + @Override + public Void visit(CallPipeOperator call, Void context) { + visit(call.getTableFunction(), context); + return null; + } + + @Override + public Void visit(DropPipeOperator drop, Void context) { + drop.getColumns().accept(this, context); + return null; + } + + @Override + public Void visit(ExtendPipeOperator extend, Void context) { + return visit((SelectPipeOperator) extend, context); + } + + @Override + public Void visit(JoinPipeOperator joinPipeOperator, Void context) { + visitJoins(List.of(joinPipeOperator.getJoin()), context); + return null; + } + + @Override + public Void visit(LimitPipeOperator limit, Void context) { + limit.getLimitExpression().accept(this, context); + if (limit.getOffsetExpression() != null) { + limit.getOffsetExpression().accept(this, context); + } + return null; + } + + @Override + public Void visit(OrderByPipeOperator orderBy, Void context) { + for (OrderByElement element : orderBy.getOrderByElements()) { + element.getExpression().accept(this, context); + } + return null; + } + + @Override + public Void visit(PivotPipeOperator pivot, Void context) { + pivot.getAggregateExpression().accept(this, context); + for (SelectItem pivotColumn : pivot.getPivotColumns()) { + pivotColumn.accept(this, context); + } + return null; + } + + @Override + public Void visit(RenamePipeOperator rename, Void context) { + return visit((SelectPipeOperator) rename, context); + } + + @Override + public Void visit(SelectPipeOperator select, Void context) { + for (SelectItem selectItem : select.getSelectItems()) { + selectItem.accept(this, context); + } + return null; + } + + @Override + public Void visit(SetPipeOperator set, Void context) { + for (UpdateSet updateSet : set.getUpdateSets()) { + updateSet.getColumns().accept(this, context); + updateSet.getValues().accept(this, context); + } + return null; + } + + @Override + public Void visit(TableSamplePipeOperator tableSample, Void context) { + return null; + } + + @Override + public Void visit(SetOperationPipeOperator setOperation, Void context) { + for (ParenthesedSelect select : setOperation.getSelects()) { + select.accept((SelectVisitor) this, context); + } + return null; + } + + @Override + public Void visit(UnPivotPipeOperator unPivot, Void context) { + for (SelectItem pivotColumn : unPivot.getPivotColumns()) { + pivotColumn.accept(this, context); + } + return null; + } + + @Override + public Void visit(WherePipeOperator where, Void context) { + where.getExpression().accept(this, context); return null; } + @Override + public Void visit(WindowPipeOperator window, Void context) { + return visit((SelectPipeOperator) window, context); + } + @Override public Void visit(DateUnitExpression dateUnitExpression, S context) { return null; @@ -863,6 +1270,7 @@ protected void init(boolean allowColumnProcessing) { otherItemNames = new ArrayList(); tables = new HashSet<>(); this.allowColumnProcessing = allowColumnProcessing; + this.rowPatternVariables = java.util.Collections.emptySet(); } @Override @@ -911,6 +1319,12 @@ public Void visit(JsonOperator jsonExpr, S context) { return null; } + @Override + public Void visit(Intersects intersects, S context) { + visitBinaryExpression(intersects); + return null; + } + @Override public Void visit(AllColumns allColumns, S context) { @@ -925,7 +1339,7 @@ public Void visit(AllTableColumns allTableColumns, S context) { @Override public Void visit(FunctionAllColumns functionAllColumns, S context) { - + functionAllColumns.getFunction().accept(this, context); return null; } @@ -979,6 +1393,11 @@ public Void visit(MySQLGroupConcat groupConcat, S context) { @Override public Void visit(Delete delete, S context) { + if (delete.getWithItemsList() != null) { + for (WithItem withItem : delete.getWithItemsList()) { + withItem.accept((SelectVisitor) this, context); + } + } visit(delete.getTable(), context); if (delete.getUsingFromItemList() != null) { @@ -989,9 +1408,16 @@ public Void visit(Delete delete, S context) { visitJoins(delete.getJoins(), context); + if (delete.getForPortionClause() != null) { + delete.getForPortionClause().accept(this, context); + } if (delete.getWhere() != null) { delete.getWhere().accept(this, context); } + visitOrderBy(delete.getOrderByElements(), context); + visitLimit(delete.getLimit(), context); + visitOutputClause(delete.getOutputClause(), context); + visitReturningClause(delete.getReturningClause(), context); return null; } @@ -1018,14 +1444,12 @@ public Void visit(Update update, S context) { } } - visit(update.getTable(), context); - - if (update.getStartJoins() != null) { - for (Join join : update.getStartJoins()) { - join.getRightItem().accept(this, context); - } + if (!update.isTargetTableAlias()) { + visit(update.getTable(), context); } + visitJoins(update.getStartJoins(), context); + if (update.getUpdateSets() != null) { for (UpdateSet updateSet : update.getUpdateSets()) { updateSet.getColumns().accept(this, context); @@ -1037,18 +1461,18 @@ public Void visit(Update update, S context) { update.getFromItem().accept(this, context); } - if (update.getJoins() != null) { - for (Join join : update.getJoins()) { - join.getRightItem().accept(this, context); - for (Expression expression : join.getOnExpressions()) { - expression.accept(this, context); - } - } - } + visitJoins(update.getJoins(), context); + if (update.getForPortionClause() != null) { + update.getForPortionClause().accept(this, context); + } if (update.getWhere() != null) { update.getWhere().accept(this, context); } + visitOrderBy(update.getOrderByElements(), context); + visitLimit(update.getLimit(), context); + visitOutputClause(update.getOutputClause(), context); + visitReturningClause(update.getReturningClause(), context); return null; } @@ -1087,12 +1511,58 @@ public Void visit(Insert insert, S context) { withItem.accept((SelectVisitor) this, context); } } + if (insert.getSetUpdateSets() != null) { + visitUpdateSets(insert.getSetUpdateSets(), context); + } + if (insert.getDuplicateAction() != null) { + visitInsertAction(insert.getDuplicateAction(), context); + } + if (insert.getConflictAction() != null) { + visitInsertAction(insert.getConflictAction(), context); + } + if (insert.getConflictTarget() != null) { + insert.getConflictTarget().accept(this, context); + } + visitOutputClause(insert.getOutputClause(), context); + visitReturningClause(insert.getReturningClause(), context); if (insert.getSelect() != null) { visit(insert.getSelect(), context); } return null; } + private void visitInsertAction(InsertDuplicateAction action, S context) { + visitUpdateSets(action.getUpdateSets(), context); + if (action.getWhereExpression() != null) { + action.getWhereExpression().accept(this, context); + } + } + + private void visitInsertAction(InsertConflictAction action, S context) { + visitUpdateSets(action.getUpdateSets(), context); + if (action.getWhereExpression() != null) { + action.getWhereExpression().accept(this, context); + } + } + + @Override + public Void visitOutputClause(OutputClause outputClause, S context) { + if (outputClause != null && outputClause.getSelectItemList() != null) { + for (SelectItem selectItem : outputClause.getSelectItemList()) { + selectItem.accept(this, context); + } + } + return null; + } + + private void visitReturningClause(ReturningClause returningClause, S context) { + if (returningClause != null) { + for (SelectItem selectItem : returningClause) { + selectItem.accept(this, context); + } + } + } + @Override public Void visit(ParenthesedInsert insert, S context) { return visit(insert.getInsert(), context); @@ -1105,7 +1575,7 @@ public void visit(Insert insert) { @Override public Void visit(Analyze analyze, S context) { - visit(analyze.getTable(), context); + analyze.getTables().forEach(table -> visit(table, context)); return null; } @@ -1116,7 +1586,7 @@ public void visit(Analyze analyze) { @Override public Void visit(Drop drop, S context) { - visit(drop.getName(), context); + drop.visitTables(table -> visit(table, context)); return null; } @@ -1138,7 +1608,22 @@ public void visit(Truncate truncate) { @Override public Void visit(CreateIndex createIndex, S context) { - throwUnsupported(createIndex); + TableDefinitionTraversal.visit(createIndex, + expression -> expression.accept(this, context), + table -> visit(table, context)); + return null; + } + + @Override + public Void visit(DoStatement statement, S context) { + throwUnsupported(statement); + return null; + } + + @Override + public Void visit(InsertBulk statement, S context) { + visit(statement.getTable(), context); + statement.visitExpressions(value -> value.accept(this, context)); return null; } @@ -1158,9 +1643,45 @@ public void visit(CreateSchema createSchema) { StatementVisitor.super.visit(createSchema); } + @Override + public Void visit(CreateDatabase createDatabase, S context) { + throwUnsupported(createDatabase); + return null; + } + + @Override + public void visit(CreateDatabase createDatabase) { + StatementVisitor.super.visit(createDatabase); + } + + @Override + public Void visit(CreateUser createUser, S context) { + return null; + } + + @Override + public void visit(CreateUser createUser) { + StatementVisitor.super.visit(createUser); + } + + @Override + public Void visit(CreateEvent createEvent, S context) { + if (createEvent.getBody() != null) { + createEvent.getBody().accept(this, context); + } + return null; + } + + @Override + public void visit(CreateEvent createEvent) { + StatementVisitor.super.visit(createEvent); + } + @Override public Void visit(CreateTable create, S context) { visit(create.getTable(), null); + TableDefinitionTraversal.visit(create, + expression -> expression.accept(this, context), table -> visit(table, context)); if (create.getSelect() != null) { create.getSelect().accept((SelectVisitor) this, context); } @@ -1172,6 +1693,20 @@ public void visit(CreateTable createTable) { StatementVisitor.super.visit(createTable); } + @Override + public Void visit(CreateTrigger createTrigger, S context) { + createTrigger.visit(t -> t.accept(this, context), e -> e.accept(this, context)); + if (createTrigger.getBody() != null) { + createTrigger.getBody().accept(this, context); + } + return null; + } + + @Override + public void visit(CreateTrigger createTrigger) { + StatementVisitor.super.visit(createTrigger); + } + @Override public Void visit(CreateView create, S context) { visit(create.getView(), null); @@ -1188,12 +1723,31 @@ public void visit(CreateView createView) { @Override public Void visit(Alter alter, S context) { + for (net.sf.jsqlparser.statement.alter.AlterExpression action : alter + .getAlterExpressions()) { + TableDefinitionTraversal.visit(action, + expression -> expression.accept(this, context), + table -> visit(table, context)); + } return alter.getTable().accept(this, context); } @Override public void visit(Alter alter) { - alter.getTable().accept(this, null); + StatementVisitor.super.visit(alter); + } + + @Override + public Void visit(AlterEvent alterEvent, S context) { + if (alterEvent.getBody() != null) { + alterEvent.getBody().accept(this, context); + } + return null; + } + + @Override + public void visit(AlterEvent alterEvent) { + StatementVisitor.super.visit(alterEvent); } @Override @@ -1215,6 +1769,11 @@ public Void visit(Execute execute, S context) { return null; } + @Override + public Void visit(ExecuteArgument argument, S context) { + return argument.getExpression().accept(this, context); + } + @Override public void visit(Execute execute) { StatementVisitor.super.visit(execute); @@ -1222,7 +1781,9 @@ public void visit(Execute execute) { @Override public Void visit(SetStatement setStatement, S context) { - throwUnsupported(setStatement); + if (setStatement.getOnOffOptions() == null) { + throwUnsupported(setStatement); + } return null; } @@ -1278,6 +1839,11 @@ public Void visit(RowGetExpression rowGetExpression, S context) { return null; } + @Override + public Void visit(MethodCallExpression methodCall, S context) { + return ExpressionVisitor.super.visit(methodCall, context); + } + @Override public Void visit(HexValue hexValue, S context) { return null; @@ -1295,6 +1861,54 @@ public Void visit(Merge merge, S context) { if (merge.getFromItem() != null) { merge.getFromItem().accept(this, context); } + + if (merge.getOnCondition() != null) { + merge.getOnCondition().accept(this, context); + } + + if (merge.getOperations() != null) { + for (MergeOperation operation : merge.getOperations()) { + operation.accept(this, context); + } + } + visitReturningClause(merge.getReturningClause(), context); + return null; + } + + @Override + public Void visit(MergeDelete mergeDelete, S context) { + if (mergeDelete.getAndPredicate() != null) { + mergeDelete.getAndPredicate().accept(this, context); + } + return null; + } + + @Override + public Void visit(MergeInsert mergeInsert, S context) { + if (mergeInsert.getAndPredicate() != null) { + mergeInsert.getAndPredicate().accept(this, context); + } + if (mergeInsert.getValues() != null) { + mergeInsert.getValues().accept(this, context); + } + if (mergeInsert.getWhereCondition() != null) { + mergeInsert.getWhereCondition().accept(this, context); + } + return null; + } + + @Override + public Void visit(MergeUpdate mergeUpdate, S context) { + if (mergeUpdate.getAndPredicate() != null) { + mergeUpdate.getAndPredicate().accept(this, context); + } + visitUpdateSets(mergeUpdate.getUpdateSets(), context); + if (mergeUpdate.getWhereCondition() != null) { + mergeUpdate.getWhereCondition().accept(this, context); + } + if (mergeUpdate.getDeleteWhereCondition() != null) { + mergeUpdate.getDeleteWhereCondition().accept(this, context); + } return null; } @@ -1382,6 +1996,11 @@ public void visit(Upsert upsert) { StatementVisitor.super.visit(upsert); } + @Override + public Void visit(SetIdentityInsertStatement statement, S context) { + return statement.getTable().accept(this, context); + } + @Override public Void visit(UseStatement use, S context) { return null; @@ -1437,15 +2056,7 @@ public void visit(Block block) { @Override public Void visit(Comment comment, S context) { - if (comment.getTable() != null) { - visit(comment.getTable(), context); - } - if (comment.getColumn() != null) { - Table table = comment.getColumn().getTable(); - if (table != null) { - visit(table, context); - } - } + comment.visitRelations(table -> visit(table, context)); return null; } @@ -1457,6 +2068,14 @@ public void visit(Comment comment) { @Override public Void visit(Values values, S context) { values.getExpressions().accept(this, context); + visitOrderBy(values.getOrderByElements(), context); + visitLimit(values.getLimit(), context); + if (values.getOffset() != null) { + values.getOffset().getOffset().accept(this, context); + } + if (values.getFetch() != null) { + values.getFetch().getExpression().accept(this, context); + } return null; } @@ -1467,7 +2086,11 @@ public void visit(Values values) { @Override public Void visit(DescribeStatement describe, S context) { - describe.getTable().accept(this, context); + if (describe.getSelect() != null) { + describe.getSelect().accept((SelectVisitor) this, context); + } else { + describe.getTable().accept(this, context); + } return null; } @@ -1528,6 +2151,7 @@ public void visit(DeclareStatement declareStatement) { @Override public Void visit(Grant grant, S context) { + grant.getClause().visit(t -> t.accept(this, context), e -> e.accept(this, context)); return null; } @@ -1559,9 +2183,21 @@ public Void visit(ArrayConstructor array, S context) { return null; } + @Override + public Void visit(MapExpression mapExpression, S context) { + for (Map.Entry entry : mapExpression.getEntries()) { + entry.getKey().accept(this, context); + entry.getValue().accept(this, context); + } + return null; + } + @Override public Void visit(CreateSequence createSequence, S context) { - throwUnsupported(createSequence); + if (createSequence.getSequence().getOwnership() != null + && !createSequence.getSequence().getOwnership().isNone()) { + visit(createSequence.getSequence().getOwnership().getColumn().getTable(), context); + } return null; } @@ -1572,7 +2208,10 @@ public void visit(CreateSequence createSequence) { @Override public Void visit(AlterSequence alterSequence, S context) { - throwUnsupported(alterSequence); + if (alterSequence.getSequence().getOwnership() != null + && !alterSequence.getSequence().getOwnership().isNone()) { + visit(alterSequence.getSequence().getOwnership().getColumn().getTable(), context); + } return null; } @@ -1631,6 +2270,16 @@ public Void visit(LambdaExpression lambdaExpression, S context) { return null; } + @Override + public Void visit(ColumnsExpression columnsExpression, S context) { + for (Expression expression : columnsExpression.getAllExpressions()) { + if (expression != null) { + expression.accept(this, context); + } + } + return null; + } + @Override public Void visit(HighExpression highExpression, S context) { highExpression.getExpression().accept(this, context); @@ -1677,7 +2326,9 @@ public Void visit(VariableAssignment variableAssignment, S context) { @Override public Void visit(XMLSerializeExpr xmlSerializeExpr, S context) { - + for (Expression expression : xmlSerializeExpr.getExpressions()) { + expression.accept(this, context); + } return null; } @@ -1837,6 +2488,11 @@ public void visit(IfElseStatement ifElseStatement) { StatementVisitor.super.visit(ifElseStatement); } + @Override + public Void visit(AliasedExpression expression, S context) { + return expression.getExpression().accept(this, context); + } + @Override public Void visit(OracleNamedFunctionParameter oracleNamedFunctionParameter, S context) { oracleNamedFunctionParameter.getExpression().accept(this, context); @@ -1864,6 +2520,166 @@ public void visit(RenameTableStatement renameTableStatement) { StatementVisitor.super.visit(renameTableStatement); } + @Override + public Void visit(PragmaStatement pragmaStatement, S context) { + // no tables involved in this statement + return null; + } + + @Override + public Void visit(AssertStatement assertStatement, S context) { + assertStatement.getExpression().accept(this, context); + return null; + } + + @Override + public void visit(PragmaStatement pragmaStatement) { + StatementVisitor.super.visit(pragmaStatement); + } + + @Override + public Void visit(ExtensionStatement extensionStatement, S context) { + // no tables involved in this statement + return null; + } + + @Override + public void visit(AssertStatement assertStatement) { + StatementVisitor.super.visit(assertStatement); + } + + @Override + public Void visit(ExportDataStatement exportDataStatement, S context) { + exportDataStatement.getSelect().accept((SelectVisitor) this, context); + return null; + } + + @Override + public void visit(ExtensionStatement extensionStatement) { + StatementVisitor.super.visit(extensionStatement); + } + + @Override + public Void visit(AttachStatement attachStatement, S context) { + // no tables involved in this statement + return null; + } + + @Override + public void visit(ExportDataStatement exportDataStatement) { + StatementVisitor.super.visit(exportDataStatement); + } + + @Override + public Void visit(LoadDataStatement loadDataStatement, S context) { + loadDataStatement.getTable().accept(this, context); + return null; + } + + @Override + public void visit(AttachStatement attachStatement) { + StatementVisitor.super.visit(attachStatement); + } + + @Override + public Void visit(DetachStatement detachStatement, S context) { + // no tables involved in this statement + return null; + } + + @Override + public void visit(DetachStatement detachStatement) { + StatementVisitor.super.visit(detachStatement); + } + + @Override + public Void visit(ConnectStatement connectStatement, S context) { + // no tables involved in this statement + return null; + } + + @Override + public void visit(ConnectStatement connectStatement) { + StatementVisitor.super.visit(connectStatement); + } + + @Override + public Void visit(DisconnectStatement disconnectStatement, S context) { + // no tables involved in this statement + return null; + } + + @Override + public void visit(DisconnectStatement disconnectStatement) { + StatementVisitor.super.visit(disconnectStatement); + } + + @Override + public Void visit(PrepareStatement prepareStatement, S context) { + prepareStatement.getStatement().accept(this, context); + return null; + } + + @Override + public void visit(PrepareStatement prepareStatement) { + StatementVisitor.super.visit(prepareStatement); + } + + @Override + public Void visit(DeallocateStatement deallocateStatement, S context) { + // no tables involved in this statement + return null; + } + + @Override + public void visit(DeallocateStatement deallocateStatement) { + StatementVisitor.super.visit(deallocateStatement); + } + + @Override + public Void visit(CopyStatement copyStatement, S context) { + if (copyStatement.getSelect() != null) { + copyStatement.getSelect().accept((SelectVisitor) this, context); + } else if (copyStatement.getTable() != null) { + copyStatement.getTable().accept(this, context); + } + return null; + } + + @Override + public void visit(CopyStatement copyStatement) { + StatementVisitor.super.visit(copyStatement); + } + + @Override + public Void visit(CreateMacro createMacro, S context) { + if (createMacro.getSelect() != null) { + createMacro.getSelect().accept((SelectVisitor) this, context); + } + return null; + } + + @Override + public void visit(CreateMacro createMacro) { + StatementVisitor.super.visit(createMacro); + } + + @Override + public Void visit(CreateExtensionRepository createExtensionRepository, S context) { + // no tables involved in this statement + return null; + } + + @Override + public void visit(CreateExtensionRepository createExtensionRepository) { + StatementVisitor.super.visit(createExtensionRepository); + } + + @Override + public void visit(LoadDataStatement loadDataStatement) { + StatementVisitor.super.visit(loadDataStatement); + } + @Override public Void visit(PurgeStatement purgeStatement, S context) { if (purgeStatement.getPurgeObjectType() == PurgeObjectType.TABLE) { @@ -1961,4 +2777,123 @@ public Void visit(CreatePolicy createPolicy, S context) { public void visit(CreatePolicy createPolicy) { StatementVisitor.super.visit(createPolicy); } + + @Override + public Void visit(CreateRole statement, S context) { + return null; + } + + @Override + public Void visit(AlterRole statement, S context) { + return null; + } + + @Override + public Void visit(Revoke statement, S context) { + statement.getClause().visit(t -> t.accept(this, context), e -> e.accept(this, context)); + return null; + } + + @Override + public Void visit(AlterDefaultPrivileges statement, S context) { + return null; + } + + @Override + public Void visit(CreateType statement, S context) { + + return null; + } + + @Override + public Void visit(AlterType statement, S context) { + + return null; + } + + @Override + public Void visit(CreateDomain statement, S context) { + if (statement.getDefaultExpression() != null) { + statement.getDefaultExpression().accept(this, context); + } + statement.getConstraints().forEach(constraint -> { + if (constraint.getExpression() != null) { + constraint.getExpression().accept(this, context); + } + }); + return null; + } + + @Override + public Void visit(AlterDomain statement, S context) { + if (statement.getDefaultExpression() != null) { + statement.getDefaultExpression().accept(this, context); + } + if (statement.getConstraint() != null + && statement.getConstraint().getExpression() != null) { + statement.getConstraint().getExpression().accept(this, context); + } + return null; + } + + @Override + public Void visit(CreateExtension statement, S context) { + + return null; + } + + @Override + public Void visit(AlterExtension statement, S context) { + if (statement.getMember() != null && statement.getMember().isTable()) { + visit(new Table(statement.getMember().getName()), context); + } + return null; + } + + @Override + public Void visit(CreatePublication statement, S context) { + statement.getTargets().forEach(target -> target.visit( + table -> table.accept(this, context), + expression -> expression.accept(this, context))); + return null; + } + + @Override + public Void visit(AlterPublication statement, S context) { + statement.getTargets().forEach(target -> target.visit( + table -> table.accept(this, context), + expression -> expression.accept(this, context))); + return null; + } + + @Override + public Void visit(CreateSubscription statement, S context) { + + return null; + } + + @Override + public Void visit(AlterSubscription statement, S context) { + + return null; + } + + @Override + public Void visit(OracleBlock block, S context) { + block.visitChildren(expression -> expression.accept(this, context), + statement -> statement.accept(this, context)); + return null; + } + + @Override + public Void visit(OracleAssignment assignment, S context) { + assignment.getValue().accept(this, context); + return null; + } + + @Override + public Void visit(OracleNullStatement statement, S context) { + return null; + } + } diff --git a/src/main/java/net/sf/jsqlparser/util/deparser/AbstractDeParser.java b/src/main/java/net/sf/jsqlparser/util/deparser/AbstractDeParser.java index 6458f79c9c..7217c6e1f2 100644 --- a/src/main/java/net/sf/jsqlparser/util/deparser/AbstractDeParser.java +++ b/src/main/java/net/sf/jsqlparser/util/deparser/AbstractDeParser.java @@ -9,8 +9,10 @@ */ package net.sf.jsqlparser.util.deparser; +import net.sf.jsqlparser.statement.ReturningClause; import net.sf.jsqlparser.expression.ExpressionVisitor; import net.sf.jsqlparser.statement.update.UpdateSet; +import net.sf.jsqlparser.statement.ForPortionClause; import java.util.List; @@ -26,6 +28,14 @@ protected AbstractDeParser(StringBuilder builder) { this.builder = builder; } + protected void deparseReturningClause(ReturningClause clause, + ExpressionVisitor visitor) { + if (clause != null) { + SelectDeParser selectItems = new SelectDeParser(visitor, builder); + clause.appendTo(builder, item -> item.accept(selectItems, null)); + } + } + public static void deparseUpdateSets(List updateSets, StringBuilder buffer, ExpressionVisitor visitor) { ExpressionListDeParser expressionListDeParser = @@ -43,6 +53,14 @@ public static void deparseUpdateSets(List updateSets, StringBuilder b } } + protected void deparseForPortionClause(ForPortionClause clause, + ExpressionVisitor visitor) { + if (clause != null) { + builder.append(' '); + clause.appendTo(builder, expression -> expression.accept(visitor, null)); + } + } + public StringBuilder getBuilder() { return builder; } diff --git a/src/main/java/net/sf/jsqlparser/util/deparser/AlterDeParser.java b/src/main/java/net/sf/jsqlparser/util/deparser/AlterDeParser.java index bfc00e7ef3..e162f0ec8a 100644 --- a/src/main/java/net/sf/jsqlparser/util/deparser/AlterDeParser.java +++ b/src/main/java/net/sf/jsqlparser/util/deparser/AlterDeParser.java @@ -10,16 +10,93 @@ package net.sf.jsqlparser.util.deparser; import net.sf.jsqlparser.statement.alter.Alter; +import net.sf.jsqlparser.expression.ExpressionVisitor; +import net.sf.jsqlparser.statement.alter.AlterExpression; +import net.sf.jsqlparser.statement.alter.AlterExpressionPartition; +import net.sf.jsqlparser.statement.alter.AlterExpressionPrimaryKey; +import net.sf.jsqlparser.statement.alter.AlterOperation; +import net.sf.jsqlparser.statement.select.PlainSelect; +import java.util.Iterator; public class AlterDeParser extends AbstractDeParser { + private final ExpressionVisitor expressionVisitor; public AlterDeParser(StringBuilder buffer) { + this(buffer, new StatementDeParser(buffer).getExpressionDeParser()); + } + + public AlterDeParser(StringBuilder buffer, ExpressionVisitor expressionVisitor) { super(buffer); + this.expressionVisitor = expressionVisitor; } @Override public void deParse(Alter alter) { - builder.append(alter.toString()); + builder.append("ALTER TABLE "); + if (alter.isUseOnly()) { + builder.append("ONLY "); + } + if (alter.isUseTableIfExists()) { + builder.append("IF EXISTS "); + } + builder.append(alter.getTable().getFullyQualifiedName()).append(' '); + for (Iterator iterator = alter.getAlterExpressions().iterator(); iterator + .hasNext();) { + deParseAction(iterator.next()); + if (iterator.hasNext()) { + builder.append(", "); + } + } + } + + private void deParseAction(AlterExpression action) { + if (action instanceof AlterExpressionPartition) { + ((AlterExpressionPartition) action).appendTo(builder, + expression -> expression.accept(expressionVisitor, null)); + return; + } + if (action instanceof AlterExpressionPrimaryKey) { + ((AlterExpressionPrimaryKey) action).appendTo(builder, + expression -> expression.accept(expressionVisitor, null)); + return; + } + if (action.getOperation() == AlterOperation.ADD && action.getIndex() != null + && action.getConstraintType() == null) { + builder.append(action.getOperation()).append(' '); + new TableElementDeParser(builder, expressionVisitor).deParse(action.getIndex()); + if (action.getConstraints() != null && !action.getConstraints().isEmpty()) { + builder.append(' ') + .append(PlainSelect.getStringList(action.getConstraints(), false, false)); + } + if (action.getUseEqual()) { + builder.append('='); + } + deParseTail(action); + return; + } + if (action.getOperation() == net.sf.jsqlparser.statement.alter.AlterOperation.ALTER + && action.getColumnSetDefaultList() != null + && !action.getColumnSetDefaultList().isEmpty()) { + action.appendColumnActionTo(builder, + expression -> expression.accept(expressionVisitor, null)); + return; + } + if (action.getColDataTypeList() != null) { + action.appendColumnDefinitionsTo(builder, + expression -> expression.accept(expressionVisitor, null)); + } else { + builder.append(action); + } + } + + private void deParseTail(AlterExpression action) { + if (action.getParameters() != null && !action.getParameters().isEmpty()) { + builder.append(' ') + .append(PlainSelect.getStringList(action.getParameters(), false, false)); + } + if (action.getIndex() != null && action.getIndex().getCommentText() != null) { + builder.append(" COMMENT ").append(action.getIndex().getCommentText()); + } } } diff --git a/src/main/java/net/sf/jsqlparser/util/deparser/CreateIndexDeParser.java b/src/main/java/net/sf/jsqlparser/util/deparser/CreateIndexDeParser.java index cc5d71ba18..94ac42e7ef 100644 --- a/src/main/java/net/sf/jsqlparser/util/deparser/CreateIndexDeParser.java +++ b/src/main/java/net/sf/jsqlparser/util/deparser/CreateIndexDeParser.java @@ -9,61 +9,28 @@ */ package net.sf.jsqlparser.util.deparser; -import static java.util.stream.Collectors.joining; - +import net.sf.jsqlparser.expression.ExpressionVisitor; import net.sf.jsqlparser.statement.create.index.CreateIndex; -import net.sf.jsqlparser.statement.create.table.Index; public class CreateIndexDeParser extends AbstractDeParser { + private final ExpressionVisitor expressionVisitor; public CreateIndexDeParser(StringBuilder buffer) { + this(buffer, null); + } + + public CreateIndexDeParser(StringBuilder buffer, + ExpressionVisitor expressionVisitor) { super(buffer); + this.expressionVisitor = expressionVisitor; } @Override public void deParse(CreateIndex createIndex) { - Index index = createIndex.getIndex(); - - builder.append("CREATE "); - - if (index.getType() != null) { - builder.append(index.getType()); - builder.append(" "); - } - - builder.append("INDEX "); - if (createIndex.isUsingIfNotExists()) { - builder.append("IF NOT EXISTS "); - } - builder.append(index.getName()); - - String using = index.getUsing(); - if (using != null && createIndex.isIndexTypeBeforeOn()) { - builder.append(" USING "); - builder.append(using); - } - - builder.append(" ON "); - builder.append(createIndex.getTable().getFullyQualifiedName()); - - if (using != null && !createIndex.isIndexTypeBeforeOn()) { - builder.append(" USING "); - builder.append(using); - } - - if (index.getColumnsNames() != null) { - builder.append(" ("); - builder.append(index.getColumnWithParams().stream() - .map(Index.ColumnParams::toString) - .collect(joining(", "))); - builder.append(")"); - } - - if (createIndex.getTailParameters() != null) { - for (String param : createIndex.getTailParameters()) { - builder.append(" ").append(param); - } + if (expressionVisitor == null) { + createIndex.appendTo(builder); + } else { + createIndex.appendTo(builder, expression -> expression.accept(expressionVisitor, null)); } } - } diff --git a/src/main/java/net/sf/jsqlparser/util/deparser/CreatePolicyDeParser.java b/src/main/java/net/sf/jsqlparser/util/deparser/CreatePolicyDeParser.java new file mode 100644 index 0000000000..a470990bfa --- /dev/null +++ b/src/main/java/net/sf/jsqlparser/util/deparser/CreatePolicyDeParser.java @@ -0,0 +1,61 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.util.deparser; + +import net.sf.jsqlparser.expression.ExpressionVisitor; +import net.sf.jsqlparser.statement.create.policy.CreatePolicy; + +public class CreatePolicyDeParser extends AbstractDeParser { + + private final ExpressionVisitor expressionVisitor; + + public CreatePolicyDeParser(StringBuilder builder) { + super(builder); + ExpressionDeParser expressionDeParser = new ExpressionDeParser(); + expressionDeParser.setBuilder(builder); + this.expressionVisitor = expressionDeParser; + } + + public CreatePolicyDeParser(ExpressionVisitor expressionVisitor, + StringBuilder builder) { + super(builder); + this.expressionVisitor = expressionVisitor; + } + + @Override + public void deParse(CreatePolicy createPolicy) { + builder.append("CREATE POLICY ").append(createPolicy.getPolicyName()); + builder.append(" ON ").append(createPolicy.getTable()); + + if (createPolicy.getPolicyMode() != null) { + builder.append(" AS ").append(createPolicy.getPolicyMode()); + } + + if (createPolicy.getPolicyCommand() != null) { + builder.append(" FOR ").append(createPolicy.getPolicyCommand()); + } + + if (createPolicy.getRoles() != null && !createPolicy.getRoles().isEmpty()) { + builder.append(" TO ").append(String.join(", ", createPolicy.getRoles())); + } + + if (createPolicy.getUsingExpression() != null) { + builder.append(" USING ("); + createPolicy.getUsingExpression().accept(expressionVisitor, null); + builder.append(")"); + } + + if (createPolicy.getWithCheckExpression() != null) { + builder.append(" WITH CHECK ("); + createPolicy.getWithCheckExpression().accept(expressionVisitor, null); + builder.append(")"); + } + } +} diff --git a/src/main/java/net/sf/jsqlparser/util/deparser/CreateTableDeParser.java b/src/main/java/net/sf/jsqlparser/util/deparser/CreateTableDeParser.java index 1d9fc85f35..faed455c30 100644 --- a/src/main/java/net/sf/jsqlparser/util/deparser/CreateTableDeParser.java +++ b/src/main/java/net/sf/jsqlparser/util/deparser/CreateTableDeParser.java @@ -15,6 +15,7 @@ import net.sf.jsqlparser.statement.create.table.ColumnDefinition; import net.sf.jsqlparser.statement.create.table.CreateTable; import net.sf.jsqlparser.statement.create.table.Index; +import net.sf.jsqlparser.statement.create.table.TableElement; import net.sf.jsqlparser.statement.select.PlainSelect; import net.sf.jsqlparser.statement.select.Select; @@ -23,7 +24,7 @@ public class CreateTableDeParser extends AbstractDeParser { private StatementDeParser statementDeParser; public CreateTableDeParser(StringBuilder buffer) { - super(buffer); + this(new StatementDeParser(buffer), buffer); } public CreateTableDeParser(StatementDeParser statementDeParser, StringBuilder buffer) { @@ -34,6 +35,8 @@ public CreateTableDeParser(StatementDeParser statementDeParser, StringBuilder bu @Override @SuppressWarnings({"PMD.CyclomaticComplexity", "PMD.NPathComplexity"}) public void deParse(CreateTable createTable) { + TableElementDeParser elements = + new TableElementDeParser(builder, statementDeParser.getExpressionDeParser()); builder.append("CREATE "); if (createTable.isOrReplace()) { builder.append("OR REPLACE "); @@ -52,6 +55,16 @@ public void deParse(CreateTable createTable) { builder.append("IF NOT EXISTS "); } builder.append(createTable.getTable().getFullyQualifiedName()); + if (createTable.getOfType() != null) { + builder.append(" OF ").append(createTable.getOfType()); + } + if (createTable.getPartitionOf() != null) { + builder.append(" PARTITION OF ") + .append(createTable.getPartitionOf().getFullyQualifiedName()); + } + if (createTable.getCloneTable() != null) { + builder.append(" CLONE ").append(createTable.getCloneTable()); + } if (createTable.getColumns() != null && !createTable.getColumns().isEmpty()) { builder.append(" ("); @@ -62,20 +75,22 @@ public void deParse(CreateTable createTable) { } builder.append(")"); } - if (createTable.getColumnDefinitions() != null) { + if (createTable.getTableElements() != null) { + builder.append(" ("); + for (Iterator iter = createTable.getTableElements().iterator(); iter + .hasNext();) { + elements.deParse(iter.next()); + if (iter.hasNext()) { + builder.append(", "); + } + } + builder.append(")"); + } else if (createTable.getColumnDefinitions() != null) { builder.append(" ("); for (Iterator iter = createTable.getColumnDefinitions().iterator(); iter.hasNext();) { ColumnDefinition columnDefinition = iter.next(); - builder.append(columnDefinition.getColumnName()); - builder.append(" "); - builder.append(columnDefinition.getColDataType().toString()); - if (columnDefinition.getColumnSpecs() != null) { - for (String s : columnDefinition.getColumnSpecs()) { - builder.append(" "); - builder.append(s); - } - } + elements.deParse(columnDefinition); if (iter.hasNext()) { builder.append(", "); @@ -85,17 +100,30 @@ public void deParse(CreateTable createTable) { if (createTable.getIndexes() != null) { for (Index index : createTable.getIndexes()) { builder.append(", "); - builder.append(index.toString()); + elements.deParse(index); } } builder.append(")"); } + if (createTable.getPartitionBound() != null) { + builder.append(' '); + createTable.getPartitionBound().appendTo(builder, + expression -> expression.accept(statementDeParser.getExpressionDeParser(), + null)); + } + params = PlainSelect.getStringList(createTable.getTableOptionsStrings(), false, false); if (!"".equals(params)) { builder.append(' ').append(params); } + if (createTable.getPartitioning() != null) { + builder.append(' '); + createTable.getPartitioning().appendTo(builder, + expression -> expression.accept(statementDeParser.getExpressionDeParser(), + null)); + } if (createTable.getRowMovement() != null) { builder.append(' ').append(createTable.getRowMovement().getMode().toString()) @@ -112,12 +140,12 @@ public void deParse(CreateTable createTable) { builder.append(")"); } } - if (createTable.getLikeTable() != null) { + if (createTable.getTrailingLikeTable() != null) { builder.append(" LIKE "); if (createTable.isSelectParenthesis()) { builder.append("("); } - Table table = createTable.getLikeTable(); + Table table = createTable.getTrailingLikeTable(); builder.append(table.getFullyQualifiedName()); if (createTable.isSelectParenthesis()) { builder.append(")"); diff --git a/src/main/java/net/sf/jsqlparser/util/deparser/CreateViewDeParser.java b/src/main/java/net/sf/jsqlparser/util/deparser/CreateViewDeParser.java index 196b4d4d9d..432ef52b91 100644 --- a/src/main/java/net/sf/jsqlparser/util/deparser/CreateViewDeParser.java +++ b/src/main/java/net/sf/jsqlparser/util/deparser/CreateViewDeParser.java @@ -9,7 +9,6 @@ */ package net.sf.jsqlparser.util.deparser; -import net.sf.jsqlparser.statement.create.view.AutoRefreshOption; import net.sf.jsqlparser.statement.create.view.CreateView; import net.sf.jsqlparser.statement.create.view.TemporaryOption; import net.sf.jsqlparser.statement.select.PlainSelect; @@ -37,6 +36,7 @@ public CreateViewDeParser(StringBuilder buffer, SelectVisitor sel @Override @SuppressWarnings({"PMD.CyclomaticComplexity", "PMD.NPathComplexity"}) public void deParse(CreateView createView) { + createView.validateOptions(); builder.append("CREATE "); if (createView.isOrReplace()) { builder.append("OR REPLACE "); @@ -59,16 +59,21 @@ public void deParse(CreateView createView) { if (createView.getTemporary() != TemporaryOption.NONE) { builder.append(createView.getTemporary().name()).append(" "); } + if (createView.isRecursive()) { + builder.append("RECURSIVE "); + } if (createView.isMaterialized()) { builder.append("MATERIALIZED "); } - builder.append("VIEW ").append(createView.getView().getFullyQualifiedName()); - if (createView.isIfNotExists()) { - builder.append(" IF NOT EXISTS"); + builder.append("VIEW "); + if (createView.isIfNotExists() && !createView.isIfNotExistsAfterViewName()) { + builder.append("IF NOT EXISTS "); } - if (createView.getAutoRefresh() != AutoRefreshOption.NONE) { - builder.append(" AUTO REFRESH ").append(createView.getAutoRefresh().name()); + builder.append(createView.getView().getFullyQualifiedName()); + if (createView.isIfNotExists() && createView.isIfNotExistsAfterViewName()) { + builder.append(" IF NOT EXISTS"); } + createView.appendMaterializationOptionsTo(builder); if (createView.getColumnNames() != null) { builder.append("("); builder.append(createView.getColumnNames()); @@ -78,6 +83,7 @@ public void deParse(CreateView createView) { builder.append( PlainSelect.getStringList(createView.getViewCommentOptions(), false, false)); } + createView.appendParametersTo(builder); builder.append(" AS "); Select select = createView.getSelect(); @@ -85,6 +91,7 @@ public void deParse(CreateView createView) { if (createView.isWithReadOnly()) { builder.append(" WITH READ ONLY"); } + createView.appendOptionsAfterQueryTo(builder); } } diff --git a/src/main/java/net/sf/jsqlparser/util/deparser/DeleteDeParser.java b/src/main/java/net/sf/jsqlparser/util/deparser/DeleteDeParser.java index 2ab59ea148..23b3f8f601 100644 --- a/src/main/java/net/sf/jsqlparser/util/deparser/DeleteDeParser.java +++ b/src/main/java/net/sf/jsqlparser/util/deparser/DeleteDeParser.java @@ -13,10 +13,6 @@ import net.sf.jsqlparser.expression.ExpressionVisitorAdapter; import net.sf.jsqlparser.schema.Table; import net.sf.jsqlparser.statement.delete.Delete; -import net.sf.jsqlparser.statement.select.Join; -import net.sf.jsqlparser.statement.select.WithItem; - -import java.util.Iterator; import static java.util.stream.Collectors.joining; @@ -25,31 +21,30 @@ public class DeleteDeParser extends AbstractDeParser { private ExpressionVisitor expressionVisitor = new ExpressionVisitorAdapter(); + private SelectDeParser selectDeParser; + public DeleteDeParser() { super(new StringBuilder()); } public DeleteDeParser(ExpressionVisitor expressionVisitor, StringBuilder buffer) { + this(expressionVisitor, null, buffer); + } + + public DeleteDeParser(ExpressionVisitor expressionVisitor, + SelectDeParser selectDeParser, StringBuilder buffer) { super(buffer); this.expressionVisitor = expressionVisitor; + this.selectDeParser = selectDeParser; } @Override @SuppressWarnings({"PMD.CyclomaticComplexity", "PMD.NPathComplexity"}) public void deParse(Delete delete) { - if (delete.getWithItemsList() != null && !delete.getWithItemsList().isEmpty()) { - builder.append("WITH "); - for (Iterator> iter = delete.getWithItemsList().iterator(); iter - .hasNext();) { - WithItem withItem = iter.next(); - builder.append(withItem); - if (iter.hasNext()) { - builder.append(","); - } - builder.append(" "); - } - } + DmlDeParserSupport sources = + new DmlDeParserSupport(expressionVisitor, selectDeParser, builder); + sources.deparseWithItems(delete.getWithItemsList(), null); builder.append("DELETE"); if (delete.getOracleHint() != null) { builder.append(delete.getOracleHint()).append(" "); @@ -77,21 +72,10 @@ public void deParse(Delete delete) { builder.append(" FROM"); } builder.append(" ").append(delete.getTable().toString()); + deparseForPortionClause(delete.getForPortionClause(), expressionVisitor); - if (delete.getUsingFromItemList() != null && !delete.getUsingFromItemList().isEmpty()) { - builder.append(" USING").append( - delete.getUsingFromItemList().stream().map(Object::toString) - .collect(joining(", ", " ", ""))); - } - if (delete.getJoins() != null) { - for (Join join : delete.getJoins()) { - if (join.isSimple()) { - builder.append(", ").append(join); - } else { - builder.append(" ").append(join); - } - } - } + sources.deparseUsing(delete.getUsingFromItemList()); + sources.deparseJoins(delete.getJoins()); deparseWhereClause(delete); @@ -105,10 +89,12 @@ public void deParse(Delete delete) { new LimitDeparser(expressionVisitor, builder).deParse(delete.getLimit()); } - if (delete.getReturningClause() != null) { - delete.getReturningClause().appendTo(builder); + if (delete.getOption() != null) { + builder.append(delete.getOption()); } + deparseReturningClause(delete.getReturningClause(), expressionVisitor); + } protected void deparseWhereClause(Delete delete) { diff --git a/src/main/java/net/sf/jsqlparser/util/deparser/DmlDeParserSupport.java b/src/main/java/net/sf/jsqlparser/util/deparser/DmlDeParserSupport.java new file mode 100644 index 0000000000..13138db4d4 --- /dev/null +++ b/src/main/java/net/sf/jsqlparser/util/deparser/DmlDeParserSupport.java @@ -0,0 +1,144 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2019 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.util.deparser; + +import java.util.List; +import net.sf.jsqlparser.expression.ExpressionVisitor; +import net.sf.jsqlparser.statement.StatementVisitorAdapter; +import net.sf.jsqlparser.statement.delete.ParenthesedDelete; +import net.sf.jsqlparser.statement.insert.ParenthesedInsert; +import net.sf.jsqlparser.statement.update.ParenthesedUpdate; +import net.sf.jsqlparser.statement.select.*; + +/** Shared source rendering for DML and the statements nested in WITH items. */ +final class DmlDeParserSupport { + private final ExpressionVisitor expressions; + private final SelectDeParser selects; + private final StringBuilder builder; + + DmlDeParserSupport(ExpressionVisitor expressions, SelectDeParser selects, + StringBuilder builder) { + this.expressions = expressions; + this.builder = builder; + SelectDeParser selected = selects; + if (selected == null && expressions instanceof ExpressionDeParser) { + SelectVisitor visitor = + ((ExpressionDeParser) expressions).getSelectVisitor(); + if (visitor instanceof SelectDeParser) { + selected = (SelectDeParser) visitor; + } + } + this.selects = selected == null ? new SelectDeParser(expressions, builder) : selected; + this.selects.setBuilder(builder); + this.selects.setExpressionVisitor(expressions); + } + + void deparseWithItems(List> items, S context) { + if (items != null && !items.isEmpty()) { + builder.append("WITH "); + for (int i = 0; i < items.size(); i++) { + if (i > 0) { + builder.append(", "); + } + selects.visit(items.get(i), context); + } + builder.append(' '); + } + } + + void deparseJoins(List joins) { + if (joins != null) { + for (Join join : joins) { + selects.deparseJoin(join); + } + } + } + + void deparseFrom(FromItem item, List joins) { + if (item != null) { + builder.append(" FROM "); + item.accept(selects, null); + deparseJoins(joins); + } + } + + void deparseUsing(List items) { + if (items != null && !items.isEmpty()) { + builder.append(" USING "); + for (int i = 0; i < items.size(); i++) { + if (i > 0) { + builder.append(", "); + } + items.get(i).accept(selects, null); + } + } + } + + StringBuilder deparseWithItem(WithItem item, S context) { + if (item.getWithFunctionDeclaration() != null) { + return builder.append(item.getWithFunctionDeclaration()); + } + if (item.getExpression() != null) { + item.getExpression().accept(expressions, context); + if (item.getAlias() != null) { + builder.append(" AS ").append(item.getAlias().getName()); + } + return item.appendRecursiveClausesTo(builder, + expression -> expression.accept(expressions, context)); + } + if (item.isRecursive()) { + builder.append("RECURSIVE "); + } + builder.append(item.getAlias().getName()); + if (item.getWithItemList() != null) { + builder.append(' ') + .append(PlainSelect.getStringList(item.getWithItemList(), true, true)); + } + if (item.getUsingKeyExpressions() != null) { + builder.append(" USING KEY (").append(item.getUsingKeyExpressions()).append(")"); + } + builder.append(" AS "); + if (item.isMaterialized()) { + builder.append(item.isUsingNot() ? "NOT MATERIALIZED " : "MATERIALIZED "); + } + item.getParenthesedStatement().accept(new StatementVisitorAdapter() { + @Override + public StringBuilder visit(Select select, T nestedContext) { + return select.accept((SelectVisitor) selects, nestedContext); + } + + @Override + public StringBuilder visit(ParenthesedInsert insert, T nestedContext) { + deparseWithItems(insert.getWithItemsList(), nestedContext); + builder.append('('); + new InsertDeParser(expressions, selects, builder).deParse(insert.getInsert()); + return builder.append(')'); + } + + @Override + public StringBuilder visit(ParenthesedUpdate update, T nestedContext) { + deparseWithItems(update.getWithItemsList(), nestedContext); + builder.append('('); + new UpdateDeParser(expressions, selects, builder).deParse(update.getUpdate()); + return builder.append(')'); + } + + @Override + public StringBuilder visit(ParenthesedDelete delete, T nestedContext) { + deparseWithItems(delete.getWithItemsList(), nestedContext); + builder.append('('); + new DeleteDeParser(expressions, selects, builder).deParse(delete.getDelete()); + return builder.append(')'); + } + }, context); + return item.appendRecursiveClausesTo(builder, + expression -> expression.accept(expressions, context)); + } +} diff --git a/src/main/java/net/sf/jsqlparser/util/deparser/DropDeParser.java b/src/main/java/net/sf/jsqlparser/util/deparser/DropDeParser.java index 64c39b5051..569abd5cd8 100644 --- a/src/main/java/net/sf/jsqlparser/util/deparser/DropDeParser.java +++ b/src/main/java/net/sf/jsqlparser/util/deparser/DropDeParser.java @@ -9,39 +9,24 @@ */ package net.sf.jsqlparser.util.deparser; +import java.util.function.Consumer; +import net.sf.jsqlparser.schema.Table; import net.sf.jsqlparser.statement.drop.Drop; -import net.sf.jsqlparser.statement.select.PlainSelect; public class DropDeParser extends AbstractDeParser { + private final Consumer
tablePrinter; public DropDeParser(StringBuilder buffer) { + this(buffer, buffer::append); + } + + public DropDeParser(StringBuilder buffer, Consumer
tablePrinter) { super(buffer); + this.tablePrinter = tablePrinter; } @Override public void deParse(Drop drop) { - builder.append("DROP "); - if (drop.isUsingTemporary()) { - builder.append("TEMPORARY "); - } - if (drop.isMaterialized()) { - builder.append("MATERIALIZED "); - } - builder.append(drop.getType()); - if (drop.isIfExists()) { - builder.append(" IF EXISTS"); - } - - builder.append(" ").append(drop.getName()); - - if (drop.getType().equals("FUNCTION")) { - builder.append(Drop.formatFuncParams(drop.getParamsByType("FUNCTION"))); - } - - if (drop.getParameters() != null && !drop.getParameters().isEmpty()) { - builder.append(" ") - .append(PlainSelect.getStringList(drop.getParameters(), false, false)); - } + drop.appendTo(builder, tablePrinter); } - } diff --git a/src/main/java/net/sf/jsqlparser/util/deparser/ExecuteDeParser.java b/src/main/java/net/sf/jsqlparser/util/deparser/ExecuteDeParser.java index 4034bd152d..a4292b3802 100644 --- a/src/main/java/net/sf/jsqlparser/util/deparser/ExecuteDeParser.java +++ b/src/main/java/net/sf/jsqlparser/util/deparser/ExecuteDeParser.java @@ -9,9 +9,7 @@ */ package net.sf.jsqlparser.util.deparser; -import java.util.List; -import net.sf.jsqlparser.expression.Expression; import net.sf.jsqlparser.expression.ExpressionVisitor; import net.sf.jsqlparser.statement.execute.Execute; @@ -27,24 +25,7 @@ public ExecuteDeParser(ExpressionVisitor expressionVisitor, @Override public void deParse(Execute execute) { - builder.append(execute.getExecType().name()).append(" ").append(execute.getName()); - if (execute.isParenthesis()) { - builder.append(" ("); - } else if (execute.getExprList() != null) { - builder.append(" "); - } - if (execute.getExprList() != null) { - List expressions = execute.getExprList().getExpressions(); - for (int i = 0; i < expressions.size(); i++) { - if (i > 0) { - builder.append(", "); - } - expressions.get(i).accept(expressionVisitor, null); - } - } - if (execute.isParenthesis()) { - builder.append(")"); - } + execute.appendTo(builder, expression -> expression.accept(expressionVisitor, null)); } public ExpressionVisitor getExpressionVisitor() { diff --git a/src/main/java/net/sf/jsqlparser/util/deparser/ExpressionDeParser.java b/src/main/java/net/sf/jsqlparser/util/deparser/ExpressionDeParser.java index e50ccad6a6..6ffd4cbc29 100644 --- a/src/main/java/net/sf/jsqlparser/util/deparser/ExpressionDeParser.java +++ b/src/main/java/net/sf/jsqlparser/util/deparser/ExpressionDeParser.java @@ -9,6 +9,8 @@ */ package net.sf.jsqlparser.util.deparser; +import net.sf.jsqlparser.expression.AliasedExpression; + import static java.util.stream.Collectors.joining; import java.util.Iterator; @@ -25,6 +27,8 @@ import net.sf.jsqlparser.expression.CaseExpression; import net.sf.jsqlparser.expression.CastExpression; import net.sf.jsqlparser.expression.CollateExpression; +import net.sf.jsqlparser.expression.ColumnsExpression; +import net.sf.jsqlparser.expression.ColumnsTransformer; import net.sf.jsqlparser.expression.ConnectByPriorOperator; import net.sf.jsqlparser.expression.ConnectByRootOperator; import net.sf.jsqlparser.expression.DateTimeLiteralExpression; @@ -38,6 +42,7 @@ import net.sf.jsqlparser.expression.HexValue; import net.sf.jsqlparser.expression.HighExpression; import net.sf.jsqlparser.expression.IntervalExpression; +import net.sf.jsqlparser.expression.RowPatternFunction; import net.sf.jsqlparser.expression.Inverse; import net.sf.jsqlparser.expression.JdbcNamedParameter; import net.sf.jsqlparser.expression.JdbcParameter; @@ -45,12 +50,12 @@ import net.sf.jsqlparser.expression.JsonExpression; import net.sf.jsqlparser.expression.JsonFunction; import net.sf.jsqlparser.expression.JsonTableFunction; -import net.sf.jsqlparser.expression.XmlTableFunction; import net.sf.jsqlparser.expression.KeepExpression; import net.sf.jsqlparser.expression.KeyExpression; import net.sf.jsqlparser.expression.LambdaExpression; import net.sf.jsqlparser.expression.LongValue; import net.sf.jsqlparser.expression.LowExpression; +import net.sf.jsqlparser.expression.MapExpression; import net.sf.jsqlparser.expression.MySQLGroupConcat; import net.sf.jsqlparser.expression.NextValExpression; import net.sf.jsqlparser.expression.NotExpression; @@ -64,9 +69,11 @@ import net.sf.jsqlparser.expression.RangeExpression; import net.sf.jsqlparser.expression.RowConstructor; import net.sf.jsqlparser.expression.RowGetExpression; +import net.sf.jsqlparser.expression.MethodCallExpression; import net.sf.jsqlparser.expression.SignedExpression; import net.sf.jsqlparser.expression.StringValue; import net.sf.jsqlparser.expression.StructType; +import net.sf.jsqlparser.expression.TernaryExpression; import net.sf.jsqlparser.expression.TimeKeyExpression; import net.sf.jsqlparser.expression.TimeValue; import net.sf.jsqlparser.expression.TimestampValue; @@ -78,6 +85,7 @@ import net.sf.jsqlparser.expression.WhenClause; import net.sf.jsqlparser.expression.WindowElement; import net.sf.jsqlparser.expression.XMLSerializeExpr; +import net.sf.jsqlparser.expression.XmlTableFunction; import net.sf.jsqlparser.expression.operators.arithmetic.Addition; import net.sf.jsqlparser.expression.operators.arithmetic.BitwiseAnd; import net.sf.jsqlparser.expression.operators.arithmetic.BitwiseLeftShift; @@ -108,6 +116,7 @@ import net.sf.jsqlparser.expression.operators.relational.GreaterThanEquals; import net.sf.jsqlparser.expression.operators.relational.InExpression; import net.sf.jsqlparser.expression.operators.relational.IncludesExpression; +import net.sf.jsqlparser.expression.operators.relational.Intersects; import net.sf.jsqlparser.expression.operators.relational.IsBooleanExpression; import net.sf.jsqlparser.expression.operators.relational.IsDistinctExpression; import net.sf.jsqlparser.expression.operators.relational.IsNullExpression; @@ -165,6 +174,11 @@ public ExpressionDeParser(SelectVisitor selectVisitor, StringBuil this.orderByDeParser = orderByDeParser; } + @Override + public StringBuilder visit(AliasedExpression expression, S context) { + return expression.appendTo(builder, value -> value.accept(this, context)); + } + @Override public StringBuilder visit(Addition addition, S context) { deparse(addition, " + ", null); @@ -180,6 +194,9 @@ public StringBuilder visit(AndExpression andExpression, S context) { @Override public StringBuilder visit(Between between, S context) { + if (between.getOraclePriorPosition() == SupportsOldOracleJoinSyntax.ORACLE_PRIOR_START) { + builder.append("PRIOR "); + } between.getLeftExpression().accept(this, context); if (between.isNot()) { builder.append(" NOT"); @@ -265,11 +282,17 @@ public StringBuilder deparse( // if (expression.isNot()) { // buffer.append(NOT); // } + if (expression.getOraclePriorPosition() == SupportsOldOracleJoinSyntax.ORACLE_PRIOR_START) { + builder.append("PRIOR "); + } expression.getLeftExpression().accept(this, context); if (expression.getOldOracleJoinSyntax() == EqualsTo.ORACLE_JOIN_RIGHT) { builder.append("(+)"); } builder.append(operator); + if (expression.getOraclePriorPosition() == SupportsOldOracleJoinSyntax.ORACLE_PRIOR_END) { + builder.append("PRIOR "); + } expression.getRightExpression().accept(this, context); if (expression.getOldOracleJoinSyntax() == EqualsTo.ORACLE_JOIN_LEFT) { builder.append("(+)"); @@ -342,6 +365,10 @@ public void visit(BitwiseLeftShift expr) { @Override public StringBuilder visit(InExpression inExpression, S context) { + if (inExpression + .getOraclePriorPosition() == SupportsOldOracleJoinSyntax.ORACLE_PRIOR_START) { + builder.append("PRIOR "); + } inExpression.getLeftExpression().accept(this, context); if (inExpression .getOldOracleJoinSyntax() == SupportsOldOracleJoinSyntax.ORACLE_JOIN_RIGHT) { @@ -404,6 +431,10 @@ public StringBuilder visit(SignedExpression signedExpression, S context) { @Override public StringBuilder visit(IsNullExpression isNullExpression, S context) { + if (isNullExpression + .getOraclePriorPosition() == SupportsOldOracleJoinSyntax.ORACLE_PRIOR_START) { + builder.append("PRIOR "); + } isNullExpression.getLeftExpression().accept(this, context); if (isNullExpression.isUseNotNull()) { builder.append(" NOTNULL"); @@ -425,6 +456,10 @@ public StringBuilder visit(IsNullExpression isNullExpression, S context) { @Override public StringBuilder visit(IsBooleanExpression isBooleanExpression, S context) { + if (isBooleanExpression + .getOraclePriorPosition() == SupportsOldOracleJoinSyntax.ORACLE_PRIOR_START) { + builder.append("PRIOR "); + } isBooleanExpression.getLeftExpression().accept(this, context); if (isBooleanExpression.isTrue()) { if (isBooleanExpression.isNot()) { @@ -444,6 +479,10 @@ public StringBuilder visit(IsBooleanExpression isBooleanExpression, S contex @Override public StringBuilder visit(IsUnknownExpression isUnknownExpression, S context) { + if (isUnknownExpression + .getOraclePriorPosition() == SupportsOldOracleJoinSyntax.ORACLE_PRIOR_START) { + builder.append("PRIOR "); + } isUnknownExpression.getLeftExpression().accept(this, context); if (isUnknownExpression.isNot()) { builder.append(" IS NOT UNKNOWN"); @@ -469,6 +508,10 @@ public StringBuilder visit(LikeExpression likeExpression, S context) { ? " SIMILAR TO" : likeExpression.getLikeKeyWord().toString(); + if (likeExpression + .getOraclePriorPosition() == SupportsOldOracleJoinSyntax.ORACLE_PRIOR_START) { + builder.append("PRIOR "); + } likeExpression.getLeftExpression().accept(this, context); builder.append(" "); if (likeExpression.isNot()) { @@ -501,6 +544,10 @@ public StringBuilder visit(ExistsExpression existsExpression, S context) { @Override public StringBuilder visit(MemberOfExpression memberOfExpression, S context) { + if (memberOfExpression + .getOraclePriorPosition() == SupportsOldOracleJoinSyntax.ORACLE_PRIOR_START) { + builder.append("PRIOR "); + } memberOfExpression.getLeftExpression().accept(this, context); if (memberOfExpression.isNot()) { builder.append(" NOT MEMBER OF "); @@ -818,6 +865,16 @@ public StringBuilder visit(RangeExpression rangeExpression, S context) { return builder; } + @Override + public StringBuilder visit(TernaryExpression ternaryExpression, S context) { + ternaryExpression.getCondition().accept(this, context); + builder.append(" ? "); + ternaryExpression.getThenExpression().accept(this, context); + builder.append(" : "); + ternaryExpression.getElseExpression().accept(this, context); + return builder; + } + @Override public StringBuilder visit(Column tableColumn, S context) { final Table table = tableColumn.getTable(); @@ -829,7 +886,9 @@ public StringBuilder visit(Column tableColumn, S context) { tableName = table.getFullyQualifiedName(); } } - if (tableName != null && !tableName.isEmpty()) { + if (tableColumn.getReturningQualifier() != null) { + builder.append(tableColumn.getReturningQualifier()).append("."); + } else if (tableName != null && !tableName.isEmpty()) { builder.append(tableName).append(tableColumn.getTableDelimiter()); } @@ -1045,6 +1104,10 @@ public void visit(RangeExpression rangeExpression) { visit(rangeExpression, null); } + public void visit(TernaryExpression ternaryExpression) { + visit(ternaryExpression, null); + } + public void visit(Column tableColumn) { visit(tableColumn, null); } @@ -1402,6 +1465,12 @@ public StringBuilder visit(UserVariable var, S context) { return builder; } + @Override + public StringBuilder visit( + net.sf.jsqlparser.statement.execute.ExecuteArgument argument, S context) { + return argument.appendTo(builder, expression -> expression.accept(this, context)); + } + @Override public StringBuilder visit(NumericBind bind, S context) { builder.append(bind.toString()); @@ -1446,6 +1515,11 @@ public StringBuilder visit(RowGetExpression rowGetExpression, S context) { return null; } + @Override + public StringBuilder visit(MethodCallExpression methodCall, S context) { + return methodCall.appendTo(builder, expression -> expression.accept(this, context)); + } + @Override public StringBuilder visit(OracleHint hint, S context) { builder.append(hint.toString()); @@ -1480,7 +1554,12 @@ public StringBuilder visit(CollateExpression col, S context) { @Override public StringBuilder visit(SimilarToExpression expr, S context) { - deparse(expr, (expr.isNot() ? " NOT" : "") + " SIMILAR TO ", null); + if (expr.getOraclePriorPosition() == SupportsOldOracleJoinSyntax.ORACLE_PRIOR_START) { + builder.append("PRIOR "); + } + expr.getLeftExpression().accept(this, context); + builder.append(expr.isNot() ? " NOT SIMILAR TO " : " SIMILAR TO "); + expr.getRightExpression().accept(this, context); return builder; } @@ -1605,6 +1684,22 @@ public StringBuilder visit(ArrayConstructor arrayConstructor, S context) { return builder; } + @Override + public StringBuilder visit(MapExpression mapExpression, S context) { + builder.append("MAP {"); + for (int i = 0; i < mapExpression.getEntries().size(); i++) { + if (i > 0) { + builder.append(", "); + } + Map.Entry entry = mapExpression.getEntries().get(i); + entry.getKey().accept(this, context); + builder.append(": "); + entry.getValue().accept(this, context); + } + builder.append("}"); + return builder; + } + @Override void deParse(Expression statement) { statement.accept(this, null); @@ -1620,21 +1715,8 @@ public StringBuilder visit(VariableAssignment var, S context) { @Override public StringBuilder visit(XMLSerializeExpr expr, S context) { - // xmlserialize(xmlagg(xmltext(COMMENT_LINE) ORDER BY COMMENT_SEQUENCE) as varchar(1024)) - builder.append("xmlserialize(xmlagg(xmltext("); - expr.getExpression().accept(this, context); - builder.append(")"); - if (expr.getOrderByElements() != null) { - builder.append(" ORDER BY "); - for (Iterator i = expr.getOrderByElements().iterator(); i.hasNext();) { - builder.append(i.next().toString()); - if (i.hasNext()) { - builder.append(", "); - } - } - } - builder.append(") AS ").append(expr.getDataType()).append(")"); - return builder; + return expr.appendTo(builder, expression -> expression.accept(this, context), + orderBy -> new OrderByDeParser(this, builder).deParse(false, orderBy, context)); } @Override @@ -1728,6 +1810,10 @@ public StringBuilder visit(AllValue allValue, S context) { @Override public StringBuilder visit(IsDistinctExpression isDistinctExpression, S context) { + if (isDistinctExpression + .getOraclePriorPosition() == SupportsOldOracleJoinSyntax.ORACLE_PRIOR_START) { + builder.append("PRIOR "); + } builder.append(isDistinctExpression.getLeftExpression()) .append(isDistinctExpression.getStringExpression()) .append(isDistinctExpression.getRightExpression()); @@ -1741,6 +1827,12 @@ public StringBuilder visit(GeometryDistance geometryDistance, S context) { return builder; } + @Override + public StringBuilder visit(Intersects intersects, S context) { + deparse(intersects, " # ", null); + return builder; + } + @Override public StringBuilder visit(TSQLLeftJoin tsqlLeftJoin, S context) { this.deparse(tsqlLeftJoin, " *= ", null); @@ -1754,76 +1846,13 @@ public StringBuilder visit(TSQLRightJoin tsqlRightJoin, S context) { } @Override - public StringBuilder visit(StructType structType, S context) { - if (structType.getDialect() != StructType.Dialect.DUCKDB - && structType.getKeyword() != null) { - builder.append(structType.getKeyword()); - } - - if (structType.getDialect() != StructType.Dialect.DUCKDB - && structType.getParameters() != null && !structType.getParameters().isEmpty()) { - builder.append("<"); - int i = 0; - for (Map.Entry e : structType.getParameters()) { - if (0 < i++) { - builder.append(","); - } - // optional name - if (e.getKey() != null && !e.getKey().isEmpty()) { - builder.append(e.getKey()).append(" "); - } - - // mandatory type - builder.append(e.getValue()); - } - - builder.append(">"); - } - - if (structType.getArguments() != null && !structType.getArguments().isEmpty()) { - if (structType.getDialect() == StructType.Dialect.DUCKDB) { - builder.append("{ "); - int i = 0; - for (SelectItem e : structType.getArguments()) { - if (0 < i++) { - builder.append(","); - } - builder.append(e.getAlias().getName()); - builder.append(" : "); - e.getExpression().accept(this, context); - } - builder.append(" }"); - } else { - builder.append("("); - int i = 0; - for (SelectItem e : structType.getArguments()) { - if (0 < i++) { - builder.append(","); - } - e.getExpression().accept(this, context); - if (e.getAlias() != null) { - builder.append(" as "); - builder.append(e.getAlias().getName()); - } - } - builder.append(")"); - } - } + public StringBuilder visit(RowPatternFunction function, S context) { + return function.appendTo(builder, expression -> expression.accept(this, context)); + } - if (structType.getDialect() == StructType.Dialect.DUCKDB - && structType.getParameters() != null && !structType.getParameters().isEmpty()) { - builder.append("::STRUCT( "); - int i = 0; - for (Map.Entry e : structType.getParameters()) { - if (0 < i++) { - builder.append(","); - } - builder.append(e.getKey()).append(" "); - builder.append(e.getValue()); - } - builder.append(")"); - } - return builder; + @Override + public StringBuilder visit(StructType structType, S context) { + return structType.appendTo(builder, expression -> expression.accept(this, context)); } @Override @@ -1844,6 +1873,43 @@ public StringBuilder visit(LambdaExpression lambdaExpression, S context) { return builder; } + @Override + public StringBuilder visit(ColumnsExpression columnsExpression, S context) { + columnsExpression.getColumns().accept(this, context); + for (ColumnsTransformer transformer : columnsExpression.getTransformers()) { + switch (transformer.getType()) { + case APPLY: + builder.append(" APPLY("); + transformer.getApplyExpression().accept(this, context); + builder.append(")"); + break; + case EXCEPT: + builder.append(" EXCEPT "); + transformer.getExceptColumns().accept(this, context); + break; + case REPLACE: + builder.append(" REPLACE("); + boolean first = true; + for (SelectItem item : transformer.getReplaceItems()) { + if (!first) { + builder.append(", "); + } + first = false; + item.getExpression().accept(this, context); + if (item.getAlias() != null) { + builder.append(item.getAlias()); + } + } + builder.append(")"); + break; + default: + throw new IllegalStateException( + "Unhandled ColumnsTransformerType: " + transformer.getType()); + } + } + return builder; + } + @Override public StringBuilder visit(HighExpression highExpression, S context) { return builder.append(highExpression.toString()); diff --git a/src/main/java/net/sf/jsqlparser/util/deparser/GrantDeParser.java b/src/main/java/net/sf/jsqlparser/util/deparser/GrantDeParser.java index 4a0f997f66..97eda71431 100644 --- a/src/main/java/net/sf/jsqlparser/util/deparser/GrantDeParser.java +++ b/src/main/java/net/sf/jsqlparser/util/deparser/GrantDeParser.java @@ -2,47 +2,22 @@ * #%L * JSQLParser library * %% - * Copyright (C) 2004 - 2020 JSQLParser + * Copyright (C) 2004 - 2026 JSQLParser * %% * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 * #L% */ package net.sf.jsqlparser.util.deparser; -import java.util.Iterator; - import net.sf.jsqlparser.statement.grant.Grant; public class GrantDeParser extends AbstractDeParser { - public GrantDeParser(StringBuilder buffer) { super(buffer); } @Override public void deParse(Grant grant) { - builder.append("GRANT "); - if (grant.getRole() != null) { - builder.append(grant.getRole()); - } else { - for (Iterator iter = grant.getPrivileges().iterator(); iter.hasNext();) { - String privilege = iter.next(); - builder.append(privilege); - if (iter.hasNext()) { - builder.append(", "); - } - } - builder.append(" ON "); - builder.append(grant.getObjectName()); - } - builder.append(" TO "); - for (Iterator iter = grant.getUsers().iterator(); iter.hasNext();) { - String user = iter.next(); - builder.append(user); - if (iter.hasNext()) { - builder.append(", "); - } - } + grant.appendTo(builder, builder::append); } - } diff --git a/src/main/java/net/sf/jsqlparser/util/deparser/GroupByDeParser.java b/src/main/java/net/sf/jsqlparser/util/deparser/GroupByDeParser.java index fc20bd4cd9..86bcc0ca6e 100644 --- a/src/main/java/net/sf/jsqlparser/util/deparser/GroupByDeParser.java +++ b/src/main/java/net/sf/jsqlparser/util/deparser/GroupByDeParser.java @@ -10,41 +10,24 @@ package net.sf.jsqlparser.util.deparser; import net.sf.jsqlparser.expression.ExpressionVisitor; -import net.sf.jsqlparser.expression.operators.relational.ExpressionList; import net.sf.jsqlparser.statement.select.GroupByElement; public class GroupByDeParser extends AbstractDeParser { private final ExpressionListDeParser expressionListDeParser; + private final ExpressionVisitor expressionVisitor; public GroupByDeParser(ExpressionVisitor expressionVisitor, StringBuilder buffer) { super(buffer); + this.expressionVisitor = expressionVisitor; this.expressionListDeParser = new ExpressionListDeParser<>(expressionVisitor, buffer); this.builder = buffer; } @Override - @SuppressWarnings({"PMD.CyclomaticComplexity", "PMD.NPathComplexity"}) public void deParse(GroupByElement groupBy) { - builder.append("GROUP BY "); - expressionListDeParser.deParse(groupBy.getGroupByExpressionList()); - - int i = 0; - if (!groupBy.getGroupingSets().isEmpty()) { - if (builder.charAt(builder.length() - 1) != ' ') { - builder.append(' '); - } - builder.append("GROUPING SETS ("); - for (ExpressionList expressionList : groupBy.getGroupingSets()) { - builder.append(i++ > 0 ? ", " : ""); - expressionListDeParser.deParse(expressionList); - } - builder.append(")"); - } - - if (groupBy.isMysqlWithRollup()) { - builder.append(" WITH ROLLUP"); - } + groupBy.appendTo(builder, expressionListDeParser::deParse, + expression -> expression.accept(expressionVisitor, null)); } } diff --git a/src/main/java/net/sf/jsqlparser/util/deparser/InsertDeParser.java b/src/main/java/net/sf/jsqlparser/util/deparser/InsertDeParser.java index 901e328651..7abcbc0c57 100644 --- a/src/main/java/net/sf/jsqlparser/util/deparser/InsertDeParser.java +++ b/src/main/java/net/sf/jsqlparser/util/deparser/InsertDeParser.java @@ -104,8 +104,12 @@ public void deParse(Insert insert) { builder.append(")"); } + if (insert.getColumnMatching() != null) { + builder.append(" ").append(insert.getColumnMatching()); + } + if (insert.isOverriding()) { - builder.append("OVERRIDING SYSTEM VALUE "); + builder.append(" OVERRIDING ").append(insert.getOverridingMode()).append(" VALUE"); } if (insert.getPartitions() != null) { @@ -147,14 +151,13 @@ public void deParse(Insert insert) { builder.append(" ON CONFLICT"); if (insert.getConflictTarget() != null) { - insert.getConflictTarget().appendTo(builder); + insert.getConflictTarget().appendTo(builder, + expression -> expression.accept(expressionVisitor, null)); } insert.getConflictAction().appendTo(builder); } - if (insert.getReturningClause() != null) { - insert.getReturningClause().appendTo(builder); - } + deparseReturningClause(insert.getReturningClause(), expressionVisitor); } public ExpressionVisitor getExpressionVisitor() { diff --git a/src/main/java/net/sf/jsqlparser/util/deparser/MergeDeParser.java b/src/main/java/net/sf/jsqlparser/util/deparser/MergeDeParser.java index 003527770f..fba2c17754 100644 --- a/src/main/java/net/sf/jsqlparser/util/deparser/MergeDeParser.java +++ b/src/main/java/net/sf/jsqlparser/util/deparser/MergeDeParser.java @@ -60,9 +60,21 @@ public void deParse(Merge merge) { operations.forEach(operation -> operation.accept(this, null)); } + if (merge.getReturningClause() != null) { + merge.getReturningClause().appendTo(builder, + item -> item.accept(selectDeParser, null)); + } + if (merge.getOutputClause() != null) { merge.getOutputClause().appendTo(builder); } + + if (merge.getOption() != null) { + if (merge.getOutputClause() != null) { + builder.setLength(builder.length() - 1); + } + builder.append(merge.getOption()); + } } @Override diff --git a/src/main/java/net/sf/jsqlparser/util/deparser/OrderByDeParser.java b/src/main/java/net/sf/jsqlparser/util/deparser/OrderByDeParser.java index 605c54dc28..a45a49ed70 100644 --- a/src/main/java/net/sf/jsqlparser/util/deparser/OrderByDeParser.java +++ b/src/main/java/net/sf/jsqlparser/util/deparser/OrderByDeParser.java @@ -14,6 +14,7 @@ import net.sf.jsqlparser.expression.ExpressionVisitor; import net.sf.jsqlparser.statement.select.OrderByElement; +import net.sf.jsqlparser.statement.select.WithFill; public class OrderByDeParser extends AbstractDeParser> { @@ -35,6 +36,11 @@ public void deParse(List orderByElementList) { } public void deParse(boolean oracleSiblings, List orderByElementList) { + deParse(oracleSiblings, orderByElementList, null); + } + + public void deParse(boolean oracleSiblings, List orderByElementList, + S context) { if (oracleSiblings) { builder.append(" ORDER SIBLINGS BY "); } else { @@ -44,7 +50,12 @@ public void deParse(boolean oracleSiblings, List orderByElementL for (Iterator iterator = orderByElementList.iterator(); iterator .hasNext();) { OrderByElement orderByElement = iterator.next(); - deParseElement(orderByElement); + if (context == null) { + // Preserve the customization point used by existing subclasses. + deParseElement(orderByElement); + } else { + deParseElement(orderByElement, context); + } if (iterator.hasNext()) { builder.append(", "); } @@ -52,7 +63,11 @@ public void deParse(boolean oracleSiblings, List orderByElementL } public void deParseElement(OrderByElement orderBy) { - orderBy.getExpression().accept(expressionVisitor, null); + deParseElement(orderBy, null); + } + + public void deParseElement(OrderByElement orderBy, S context) { + orderBy.getExpression().accept(expressionVisitor, context); if (!orderBy.isAsc()) { builder.append(" DESC"); } else if (orderBy.isAscDescPresent()) { @@ -64,11 +79,35 @@ public void deParseElement(OrderByElement orderBy) { ? "NULLS FIRST" : "NULLS LAST"); } + if (orderBy.getWithFill() != null) { + builder.append(' '); + deParseWithFill(orderBy.getWithFill(), context); + } if (orderBy.isMysqlWithRollup()) { builder.append(" WITH ROLLUP"); } } + private void deParseWithFill(WithFill withFill, S context) { + builder.append("WITH FILL"); + if (withFill.getFrom() != null) { + builder.append(" FROM "); + withFill.getFrom().accept(expressionVisitor, context); + } + if (withFill.getTo() != null) { + builder.append(" TO "); + withFill.getTo().accept(expressionVisitor, context); + } + if (withFill.getStep() != null) { + builder.append(" STEP "); + withFill.getStep().accept(expressionVisitor, context); + } + if (withFill.getStaleness() != null) { + builder.append(" STALENESS "); + withFill.getStaleness().accept(expressionVisitor, context); + } + } + void setExpressionVisitor(ExpressionVisitor expressionVisitor) { this.expressionVisitor = expressionVisitor; } diff --git a/src/main/java/net/sf/jsqlparser/util/deparser/SelectDeParser.java b/src/main/java/net/sf/jsqlparser/util/deparser/SelectDeParser.java index 50edf3b70a..d5d14aaecf 100644 --- a/src/main/java/net/sf/jsqlparser/util/deparser/SelectDeParser.java +++ b/src/main/java/net/sf/jsqlparser/util/deparser/SelectDeParser.java @@ -9,6 +9,8 @@ */ package net.sf.jsqlparser.util.deparser; +import net.sf.jsqlparser.statement.select.MatchRecognize; + import static java.util.stream.Collectors.joining; import java.lang.reflect.InvocationTargetException; @@ -51,6 +53,7 @@ import net.sf.jsqlparser.statement.select.First; import net.sf.jsqlparser.statement.select.FromItem; import net.sf.jsqlparser.statement.select.FromItemVisitor; +import net.sf.jsqlparser.statement.select.InterpolateElement; import net.sf.jsqlparser.statement.select.Join; import net.sf.jsqlparser.statement.select.LateralSubSelect; import net.sf.jsqlparser.statement.select.LateralView; @@ -61,6 +64,8 @@ import net.sf.jsqlparser.statement.select.ParenthesedFromItem; import net.sf.jsqlparser.statement.select.ParenthesedSelect; import net.sf.jsqlparser.statement.select.Pivot; +import net.sf.jsqlparser.statement.select.PivotQuery; +import net.sf.jsqlparser.statement.select.UnPivotQuery; import net.sf.jsqlparser.statement.select.PivotVisitor; import net.sf.jsqlparser.statement.select.PivotXml; import net.sf.jsqlparser.statement.select.PlainSelect; @@ -77,7 +82,6 @@ import net.sf.jsqlparser.statement.select.UnPivot; import net.sf.jsqlparser.statement.select.Values; import net.sf.jsqlparser.statement.select.WithItem; -import net.sf.jsqlparser.statement.update.UpdateSet; @SuppressWarnings({"PMD.CyclomaticComplexity", "PMD.NPathComplexity"}) public class SelectDeParser extends AbstractDeParser @@ -136,6 +140,11 @@ public StringBuilder visit(ParenthesedSelect select, S context) { if (select.getOrderByElements() != null) { new OrderByDeParser(expressionVisitor, builder).deParse(select.isOracleSiblings(), select.getOrderByElements()); + deParseInterpolate(select.getInterpolate()); + } + + if (select.getOption() != null) { + builder.append(select.getOption()); } Alias alias = select.getAlias(); @@ -192,6 +201,143 @@ public StringBuilder visit(PlainSelect plainSelect, S context) { } } + if (plainSelect.isFromFirst()) { + // DuckDB allows the FROM clause to lead, with an optional SELECT clause behind it + deparseFromClause(plainSelect, context); + if (plainSelect.getSelectItems() != null && !plainSelect.getSelectItems().isEmpty()) { + builder.append(" "); + deparseSelectClause(plainSelect, context); + } + } else { + deparseSelectClause(plainSelect, context); + deparseFromClause(plainSelect, context); + } + + deparsePreWhereClause(plainSelect); + deparseWhereClause(plainSelect); + + if (plainSelect.getOracleHierarchical() != null) { + plainSelect.getOracleHierarchical().accept(expressionVisitor, context); + } + + if (plainSelect.getPreferringClause() != null) { + builder.append(" ").append(plainSelect.getPreferringClause().toString()); + } + + if (plainSelect.getGroupBy() != null) { + builder.append(" "); + new GroupByDeParser(expressionVisitor, builder).deParse(plainSelect.getGroupBy()); + } + + if (plainSelect.getHaving() != null) { + builder.append(" HAVING "); + plainSelect.getHaving().accept(expressionVisitor, context); + } + if (plainSelect.getQualify() != null) { + builder.append(" QUALIFY "); + plainSelect.getQualify().accept(expressionVisitor, context); + } + if (plainSelect.getWindowDefinitions() != null) { + builder.append(" WINDOW "); + builder.append(plainSelect.getWindowDefinitions().stream() + .map(WindowDefinition::toString).collect(joining(", "))); + } + Alias alias = plainSelect.getAlias(); + if (alias != null) { + builder.append(alias); + } + Pivot pivot = plainSelect.getPivot(); + if (pivot != null) { + pivot.accept(this, context); + } + UnPivot unpivot = plainSelect.getUnPivot(); + if (unpivot != null) { + unpivot.accept(this, context); + } + + if (!plainSelect.isForUpdateBeforeOrderBy()) { + deparseOrderByElementsClause(plainSelect, plainSelect.getOrderByElements()); + } + + if (plainSelect.getForClause() != null) { + plainSelect.getForClause().appendTo(builder); + } + + if (plainSelect.getOption() != null) { + builder.append(plainSelect.getOption()); + } + + plainSelect.appendEmitClauseTo(builder); + if (plainSelect.getLimitBy() != null) { + new LimitDeparser(expressionVisitor, builder).deParse(plainSelect.getLimitBy()); + } + if (plainSelect.getLimit() != null) { + new LimitDeparser(expressionVisitor, builder).deParse(plainSelect.getLimit()); + } + if (plainSelect.getOffset() != null) { + visit(plainSelect.getOffset()); + } + if (plainSelect.getMySqlProcedureAnalyse() != null) { + plainSelect.getMySqlProcedureAnalyse().appendTo(builder); + } + if (plainSelect.getFetch() != null) { + visit(plainSelect.getFetch()); + } + if (plainSelect.getIsolation() != null) { + builder.append(plainSelect.getIsolation().toString()); + } + if (plainSelect.getForMode() != null) { + builder.append(" FOR "); + builder.append(plainSelect.getForMode().getValue()); + + List
forUpdateTables = plainSelect.getForUpdateTables(); + if (forUpdateTables != null && !forUpdateTables.isEmpty()) { + builder.append(" OF "); + for (int i = 0; i < forUpdateTables.size(); i++) { + if (i > 0) { + builder.append(", "); + } + builder.append(forUpdateTables.get(i)); + } + } + if (plainSelect.getWait() != null) { + // wait's toString will do the formatting for us + builder.append(plainSelect.getWait()); + } + if (plainSelect.isNoWait()) { + builder.append(" NOWAIT"); + } else if (plainSelect.isSkipLocked()) { + builder.append(" SKIP LOCKED"); + } + } + + if (plainSelect.isForUpdateBeforeOrderBy()) { + deparseOrderByElementsClause(plainSelect, plainSelect.getOrderByElements()); + } + deparseMySqlSelectInto(plainSelect, MySqlSelectIntoClause.Position.TRAILING, context); + if (plainSelect.getSettings() != null && !plainSelect.getSettings().isEmpty()) { + builder.append(" SETTINGS "); + deparseUpdateSets(plainSelect.getSettings(), builder, expressionVisitor); + } + if (plainSelect.getOptimizeFor() != null) { + deparseOptimizeFor(plainSelect.getOptimizeFor()); + } + if (plainSelect.getForXmlPath() != null) { + builder.append(" FOR XML PATH(").append(plainSelect.getForXmlPath()).append(")"); + } + if (plainSelect.getIntoTempTable() != null) { + builder.append(" INTO TEMP ").append(plainSelect.getIntoTempTable()); + } + if (plainSelect.isUseWithNoLog()) { + builder.append(" WITH NO LOG"); + } + + + + return builder; + } + + private void deparseSelectClause(PlainSelect plainSelect, S context) { builder.append("SELECT "); if (plainSelect.getMySqlHintStraightJoin()) { @@ -251,14 +397,12 @@ public StringBuilder visit(PlainSelect plainSelect, S context) { } } - if (plainSelect.getMySqlSelectIntoClause() != null - && plainSelect.getMySqlSelectIntoClause() - .getPosition() == MySqlSelectIntoClause.Position.BEFORE_FROM) { - builder.append(" ").append(plainSelect.getMySqlSelectIntoClause()); - } + deparseMySqlSelectInto(plainSelect, MySqlSelectIntoClause.Position.BEFORE_FROM, context); + } + private void deparseFromClause(PlainSelect plainSelect, S context) { if (plainSelect.getFromItem() != null) { - builder.append(" FROM "); + builder.append(plainSelect.isFromFirst() ? "FROM " : " FROM "); if (plainSelect.isUsingOnly()) { builder.append("ONLY "); } @@ -292,127 +436,92 @@ public StringBuilder visit(PlainSelect plainSelect, S context) { builder.append(" WINDOW "); builder.append(plainSelect.getKsqlWindow().toString()); } + } - deparsePreWhereClause(plainSelect); - deparseWhereClause(plainSelect); - if (plainSelect.getOracleHierarchical() != null) { - plainSelect.getOracleHierarchical().accept(expressionVisitor, context); - } + @Override + public StringBuilder visit(UnPivotQuery unPivotQuery, S context) { + unPivotQuery.appendSelectBodyTo(builder); + return builder; + } - if (plainSelect.getPreferringClause() != null) { - builder.append(" ").append(plainSelect.getPreferringClause().toString()); - } + @Override + public void visit(UnPivotQuery unPivotQuery) { + SelectVisitor.super.visit(unPivotQuery); + } - if (plainSelect.getGroupBy() != null) { - builder.append(" "); - new GroupByDeParser(expressionVisitor, builder).deParse(plainSelect.getGroupBy()); + @Override + public StringBuilder visit(PivotQuery pivotQuery, S context) { + List> withItemsList = pivotQuery.getWithItemsList(); + if (withItemsList != null && !withItemsList.isEmpty()) { + builder.append("WITH "); + for (Iterator> iter = withItemsList.iterator(); iter.hasNext();) { + iter.next().accept((SelectVisitor) this, context); + if (iter.hasNext()) { + builder.append(","); + } + builder.append(" "); + } } - if (plainSelect.getHaving() != null) { - builder.append(" HAVING "); - plainSelect.getHaving().accept(expressionVisitor, context); + builder.append("PIVOT "); + pivotQuery.getFromItem().accept(this, context); + + if (pivotQuery.getOnExpressions() != null) { + builder.append(" ON "); + pivotQuery.getOnExpressions().accept(expressionVisitor, context); } - if (plainSelect.getQualify() != null) { - builder.append(" QUALIFY "); - plainSelect.getQualify().accept(expressionVisitor, context); + if (pivotQuery.getUsingItems() != null) { + builder.append(" USING "); + for (Iterator> iter = + pivotQuery.getUsingItems().iterator(); iter.hasNext();) { + iter.next().accept(this, context); + if (iter.hasNext()) { + builder.append(", "); + } + } } - if (plainSelect.getWindowDefinitions() != null) { - builder.append(" WINDOW "); - builder.append(plainSelect.getWindowDefinitions().stream() - .map(WindowDefinition::toString).collect(joining(", "))); + if (pivotQuery.getGroupByExpressions() != null) { + builder.append(" GROUP BY "); + pivotQuery.getGroupByExpressions().accept(expressionVisitor, context); } - Alias alias = plainSelect.getAlias(); + + Alias alias = pivotQuery.getAlias(); if (alias != null) { builder.append(alias); } - Pivot pivot = plainSelect.getPivot(); + Pivot pivot = pivotQuery.getPivot(); if (pivot != null) { pivot.accept(this, context); } - UnPivot unpivot = plainSelect.getUnPivot(); + UnPivot unpivot = pivotQuery.getUnPivot(); if (unpivot != null) { unpivot.accept(this, context); } - if (!plainSelect.isForUpdateBeforeOrderBy()) { - deparseOrderByElementsClause(plainSelect, plainSelect.getOrderByElements()); + if (pivotQuery.getOrderByElements() != null) { + new OrderByDeParser(expressionVisitor, builder).deParse(pivotQuery.isOracleSiblings(), + pivotQuery.getOrderByElements()); + deParseInterpolate(pivotQuery.getInterpolate()); } - - if (plainSelect.getForClause() != null) { - plainSelect.getForClause().appendTo(builder); + if (pivotQuery.getOption() != null) { + builder.append(pivotQuery.getOption()); } - - if (plainSelect.isEmitChanges()) { - builder.append(" EMIT CHANGES"); + if (pivotQuery.getLimitBy() != null) { + new LimitDeparser(expressionVisitor, builder).deParse(pivotQuery.getLimitBy()); } - if (plainSelect.getLimitBy() != null) { - new LimitDeparser(expressionVisitor, builder).deParse(plainSelect.getLimitBy()); + if (pivotQuery.getLimit() != null) { + new LimitDeparser(expressionVisitor, builder).deParse(pivotQuery.getLimit()); } - if (plainSelect.getLimit() != null) { - new LimitDeparser(expressionVisitor, builder).deParse(plainSelect.getLimit()); - } - if (plainSelect.getOffset() != null) { - visit(plainSelect.getOffset()); + if (pivotQuery.getOffset() != null) { + visit(pivotQuery.getOffset()); } - if (plainSelect.getFetch() != null) { - visit(plainSelect.getFetch()); + if (pivotQuery.getFetch() != null) { + visit(pivotQuery.getFetch()); } - if (plainSelect.getIsolation() != null) { - builder.append(plainSelect.getIsolation().toString()); + if (pivotQuery.getIsolation() != null) { + builder.append(pivotQuery.getIsolation()); } - if (plainSelect.getForMode() != null) { - builder.append(" FOR "); - builder.append(plainSelect.getForMode().getValue()); - - List
forUpdateTables = plainSelect.getForUpdateTables(); - if (forUpdateTables != null && !forUpdateTables.isEmpty()) { - builder.append(" OF "); - for (int i = 0; i < forUpdateTables.size(); i++) { - if (i > 0) { - builder.append(", "); - } - builder.append(forUpdateTables.get(i)); - } - } - if (plainSelect.getWait() != null) { - // wait's toString will do the formatting for us - builder.append(plainSelect.getWait()); - } - if (plainSelect.isNoWait()) { - builder.append(" NOWAIT"); - } else if (plainSelect.isSkipLocked()) { - builder.append(" SKIP LOCKED"); - } - } - - if (plainSelect.isForUpdateBeforeOrderBy()) { - deparseOrderByElementsClause(plainSelect, plainSelect.getOrderByElements()); - } - if (plainSelect.getMySqlSelectIntoClause() != null - && plainSelect.getMySqlSelectIntoClause() - .getPosition() == MySqlSelectIntoClause.Position.TRAILING) { - builder.append(" ").append(plainSelect.getMySqlSelectIntoClause()); - } - if (plainSelect.getSettings() != null && !plainSelect.getSettings().isEmpty()) { - builder.append(" SETTINGS "); - deparseUpdateSets(plainSelect.getSettings(), builder, expressionVisitor); - } - if (plainSelect.getOptimizeFor() != null) { - deparseOptimizeFor(plainSelect.getOptimizeFor()); - } - if (plainSelect.getForXmlPath() != null) { - builder.append(" FOR XML PATH(").append(plainSelect.getForXmlPath()).append(")"); - } - if (plainSelect.getIntoTempTable() != null) { - builder.append(" INTO TEMP ").append(plainSelect.getIntoTempTable()); - } - if (plainSelect.isUseWithNoLog()) { - builder.append(" WITH NO LOG"); - } - - - return builder; } @@ -471,7 +580,31 @@ protected void deparseOrderByElementsClause(PlainSelect plainSelect, if (orderByElements != null) { new OrderByDeParser(expressionVisitor, builder).deParse(plainSelect.isOracleSiblings(), orderByElements); + deParseInterpolate(plainSelect.getInterpolate()); + } + } + + private void deParseInterpolate(List interpolate) { + if (interpolate == null) { + return; + } + if (interpolate.isEmpty()) { + builder.append(" INTERPOLATE"); + return; + } + builder.append(" INTERPOLATE ("); + for (Iterator iter = interpolate.iterator(); iter.hasNext();) { + InterpolateElement element = iter.next(); + element.getColumn().accept(expressionVisitor, null); + if (element.getExpression() != null) { + builder.append(" AS "); + element.getExpression().accept(expressionVisitor, null); + } + if (iter.hasNext()) { + builder.append(", "); + } } + builder.append(")"); } @Override @@ -484,6 +617,15 @@ public StringBuilder visit(SelectItem selectItem, S context) { } + @Override + public StringBuilder visit(MatchRecognize matchRecognize, S context) { + OrderByDeParser ordering = new OrderByDeParser(expressionVisitor, builder); + return matchRecognize.appendTo(builder, input -> input.accept(this, context), + expression -> expression.accept(expressionVisitor, context), + order -> ordering.deParseElement(order, context), + pivot -> pivot.accept(this, context), unpivot -> unpivot.accept(this, context)); + } + @Override public StringBuilder visit(Table table, S context) { builder.append(table.getFullyQualifiedName()); @@ -609,6 +751,15 @@ public void setExpressionVisitor(ExpressionVisitor visitor) { expressionVisitor = visitor; } + private void deparseMySqlSelectInto(PlainSelect select, + MySqlSelectIntoClause.Position position, S context) { + MySqlSelectIntoClause into = select.getMySqlSelectIntoClause(); + if (into != null && into.getPosition() == position) { + builder.append(' '); + into.appendTo(builder, expression -> expression.accept(expressionVisitor, context)); + } + } + @SuppressWarnings({"PMD.CyclomaticComplexity"}) public void deparseJoin(Join join) { if (join.isGlobal()) { @@ -621,47 +772,9 @@ public void deparseJoin(Join join) { builder.append(", "); } else { - if (join.isNatural()) { - builder.append(" NATURAL"); - } - - if (join.isAny()) { - builder.append(" ANY"); - } else if (join.isAll()) { - builder.append(" ALL"); - } - - if (join.isRight()) { - builder.append(" RIGHT"); - } else if (join.isFull()) { - builder.append(" FULL"); - } else if (join.isLeft()) { - builder.append(" LEFT"); - } else if (join.isCross()) { - builder.append(" CROSS"); - } - - if (join.isOuter()) { - builder.append(" OUTER"); - } else if (join.isInner()) { - builder.append(" INNER"); - } else if (join.isSemi()) { - builder.append(" SEMI"); - } - - if (join.isStraight()) { - builder.append(" STRAIGHT_JOIN "); - } else if (join.isApply()) { - builder.append(" APPLY "); - } else { - if (join.getJoinHint() != null) { - builder.append(" ").append(join.getJoinHint()); - } - builder.append(" JOIN "); - if (join.isFetch()) { - builder.append("FETCH "); - } - } + builder.append(' '); + join.appendJoinTypeTo(builder); + join.appendJoinKeywordTo(builder); } @@ -671,6 +784,12 @@ public void deparseJoin(Join join) { builder.append(" WITHIN "); builder.append(join.getJoinWindow().toString()); } + if (join.getApproxNearest() != null) { + builder.append(" APPROX NEAREST ").append(join.getApproxNearest()) + .append(" BY SIMILARITY "); + join.getSimilarity().accept(expressionVisitor, null); + } + for (Expression onExpression : join.getOnExpressions()) { builder.append(" ON "); onExpression.accept(expressionVisitor, null); @@ -729,6 +848,11 @@ public StringBuilder visit(SetOperationList list, S context) { } if (list.getOrderByElements() != null) { new OrderByDeParser(expressionVisitor, builder).deParse(list.getOrderByElements()); + deParseInterpolate(list.getInterpolate()); + } + + if (list.getOption() != null) { + builder.append(list.getOption()); } if (list.getLimit() != null) { @@ -762,31 +886,8 @@ public StringBuilder visit(SetOperationList list, S context) { @Override public StringBuilder visit(WithItem withItem, S context) { - if (withItem.getWithFunctionDeclaration() == null) { - if (withItem.isRecursive()) { - builder.append("RECURSIVE "); - } - builder.append(withItem.getAlias().getName()); - if (withItem.getWithItemList() != null) { - builder.append(" ") - .append(PlainSelect.getStringList(withItem.getWithItemList(), true, true)); - } - builder.append(" AS "); - if (withItem.isMaterialized()) { - builder.append(withItem.isUsingNot() - ? "NOT MATERIALIZED " - : "MATERIALIZED "); - } - StatementDeParser statementDeParser = - new StatementDeParser((ExpressionDeParser) expressionVisitor, this, builder); - statementDeParser.deParse(withItem.getParenthesedStatement()); - if (withItem.getSearchClause() != null) { - builder.append(" ").append(withItem.getSearchClause()); - } - } else { - builder.append(withItem.getWithFunctionDeclaration().toString()); - } - return builder; + return new DmlDeParserSupport(expressionVisitor, this, builder) + .deparseWithItem(withItem, context); } @Override @@ -865,6 +966,11 @@ public void visit(PlainSelect plainSelect) { visit(plainSelect, null); } + @Override + public void visit(PivotQuery pivotQuery) { + visit(pivotQuery, null); + } + public void visit(SelectItem selectExpressionItem) { visit(selectExpressionItem, null); } @@ -1045,9 +1151,11 @@ public StringBuilder visit(JoinPipeOperator join, Void context) { @Override public StringBuilder visit(LimitPipeOperator limit, Void context) { - builder.append("|> ").append("LIMIT ").append(limit.getLimitExpression()); + builder.append("|> LIMIT "); + limit.getLimitExpression().accept(expressionVisitor, context); if (limit.getOffsetExpression() != null) { - builder.append(" OFFSET ").append(limit.getOffsetExpression()); + builder.append(" OFFSET "); + limit.getOffsetExpression().accept(expressionVisitor, context); } return builder; } @@ -1092,7 +1200,8 @@ public StringBuilder visit(SelectPipeOperator select, Void context) { int i = 0; for (SelectItem selectItem : select.getSelectItems()) { - builder.append(i++ > 0 ? ", " : " ").append(selectItem); + builder.append(i++ > 0 ? ", " : " "); + selectItem.accept(this, context); } builder.append("\n"); return builder; @@ -1100,10 +1209,10 @@ public StringBuilder visit(SelectPipeOperator select, Void context) { @Override public StringBuilder visit(SetPipeOperator set, Void context) { - builder.append("|> ").append("SET"); - int i = 0; - for (UpdateSet updateSet : set.getUpdateSets()) { - builder.append(i++ > 0 ? ", " : " ").append(updateSet); + builder.append("|> SET"); + if (!set.getUpdateSets().isEmpty()) { + builder.append(' '); + deparseUpdateSets(set.getUpdateSets(), builder, expressionVisitor); } builder.append("\n"); return builder; diff --git a/src/main/java/net/sf/jsqlparser/util/deparser/SetStatementDeParser.java b/src/main/java/net/sf/jsqlparser/util/deparser/SetStatementDeParser.java index 31ff0f5997..9dee3a23a8 100644 --- a/src/main/java/net/sf/jsqlparser/util/deparser/SetStatementDeParser.java +++ b/src/main/java/net/sf/jsqlparser/util/deparser/SetStatementDeParser.java @@ -9,11 +9,9 @@ */ package net.sf.jsqlparser.util.deparser; -import net.sf.jsqlparser.expression.Expression; import net.sf.jsqlparser.expression.ExpressionVisitor; import net.sf.jsqlparser.statement.SetStatement; -import java.util.List; public class SetStatementDeParser extends AbstractDeParser { @@ -27,28 +25,7 @@ public SetStatementDeParser(ExpressionVisitor expressionVisitor, @Override public void deParse(SetStatement set) { - builder.append("SET "); - if (set.getEffectParameter() != null) { - builder.append(set.getEffectParameter()).append(" "); - } - for (int i = 0; i < set.getCount(); i++) { - if (i > 0) { - builder.append(", "); - } - builder.append(set.getName(i)); - if (set.isUseEqual(i)) { - builder.append(" ="); - } - builder.append(" "); - List expressions = set.getExpressions(i); - for (int j = 0; j < expressions.size(); j++) { - if (j > 0) { - builder.append(", "); - } - expressions.get(j).accept(expressionVisitor, null); - } - } - + set.appendTo(builder, expression -> expression.accept(expressionVisitor, null)); } public ExpressionVisitor getExpressionVisitor() { diff --git a/src/main/java/net/sf/jsqlparser/util/deparser/StatementDeParser.java b/src/main/java/net/sf/jsqlparser/util/deparser/StatementDeParser.java index 751c4bf64d..ac5490fa0a 100644 --- a/src/main/java/net/sf/jsqlparser/util/deparser/StatementDeParser.java +++ b/src/main/java/net/sf/jsqlparser/util/deparser/StatementDeParser.java @@ -9,6 +9,25 @@ */ package net.sf.jsqlparser.util.deparser; +import net.sf.jsqlparser.statement.oracle.OracleBlock; +import net.sf.jsqlparser.statement.oracle.OracleAssignment; +import net.sf.jsqlparser.statement.oracle.OracleNullStatement; + +import net.sf.jsqlparser.statement.role.CreateRole; +import net.sf.jsqlparser.statement.role.AlterRole; +import net.sf.jsqlparser.statement.grant.Revoke; +import net.sf.jsqlparser.statement.grant.AlterDefaultPrivileges; +import net.sf.jsqlparser.statement.create.type.CreateType; +import net.sf.jsqlparser.statement.alter.AlterType; +import net.sf.jsqlparser.statement.create.domain.CreateDomain; +import net.sf.jsqlparser.statement.alter.AlterDomain; +import net.sf.jsqlparser.statement.create.extension.CreateExtension; +import net.sf.jsqlparser.statement.alter.AlterExtension; +import net.sf.jsqlparser.statement.create.publication.CreatePublication; +import net.sf.jsqlparser.statement.alter.AlterPublication; +import net.sf.jsqlparser.statement.create.subscription.CreateSubscription; +import net.sf.jsqlparser.statement.alter.AlterSubscription; + import java.lang.reflect.InvocationTargetException; import java.util.List; import java.util.stream.Collectors; @@ -19,6 +38,7 @@ import net.sf.jsqlparser.statement.CreateFunctionalStatement; import net.sf.jsqlparser.statement.DeclareStatement; import net.sf.jsqlparser.statement.DescribeStatement; +import net.sf.jsqlparser.statement.DoStatement; import net.sf.jsqlparser.statement.ExplainStatement; import net.sf.jsqlparser.statement.IfElseStatement; import net.sf.jsqlparser.statement.PurgeStatement; @@ -34,6 +54,7 @@ import net.sf.jsqlparser.statement.Statements; import net.sf.jsqlparser.statement.UnsupportedStatement; import net.sf.jsqlparser.statement.UseStatement; +import net.sf.jsqlparser.statement.SetIdentityInsertStatement; import net.sf.jsqlparser.statement.alter.Alter; import net.sf.jsqlparser.statement.alter.AlterSession; import net.sf.jsqlparser.statement.alter.AlterSystemStatement; @@ -41,12 +62,17 @@ import net.sf.jsqlparser.statement.alter.sequence.AlterSequence; import net.sf.jsqlparser.statement.analyze.Analyze; import net.sf.jsqlparser.statement.comment.Comment; +import net.sf.jsqlparser.statement.create.database.CreateDatabase; +import net.sf.jsqlparser.statement.create.event.AlterEvent; +import net.sf.jsqlparser.statement.create.event.CreateEvent; import net.sf.jsqlparser.statement.create.index.CreateIndex; import net.sf.jsqlparser.statement.create.policy.CreatePolicy; import net.sf.jsqlparser.statement.create.schema.CreateSchema; import net.sf.jsqlparser.statement.create.sequence.CreateSequence; import net.sf.jsqlparser.statement.create.synonym.CreateSynonym; import net.sf.jsqlparser.statement.create.table.CreateTable; +import net.sf.jsqlparser.statement.create.trigger.CreateTrigger; +import net.sf.jsqlparser.statement.create.user.CreateUser; import net.sf.jsqlparser.statement.create.view.AlterView; import net.sf.jsqlparser.statement.create.view.CreateView; import net.sf.jsqlparser.statement.delete.Delete; @@ -57,6 +83,7 @@ import net.sf.jsqlparser.statement.grant.Grant; import net.sf.jsqlparser.statement.imprt.Import; import net.sf.jsqlparser.statement.insert.Insert; +import net.sf.jsqlparser.statement.insert.InsertBulk; import net.sf.jsqlparser.statement.insert.ParenthesedInsert; import net.sf.jsqlparser.statement.lock.LockStatement; import net.sf.jsqlparser.statement.merge.Merge; @@ -70,6 +97,20 @@ import net.sf.jsqlparser.statement.update.ParenthesedUpdate; import net.sf.jsqlparser.statement.update.Update; import net.sf.jsqlparser.statement.upsert.Upsert; +import net.sf.jsqlparser.statement.PragmaStatement; +import net.sf.jsqlparser.statement.ExtensionStatement; +import net.sf.jsqlparser.statement.AttachStatement; +import net.sf.jsqlparser.statement.DetachStatement; +import net.sf.jsqlparser.statement.ConnectStatement; +import net.sf.jsqlparser.statement.DisconnectStatement; +import net.sf.jsqlparser.statement.PrepareStatement; +import net.sf.jsqlparser.statement.DeallocateStatement; +import net.sf.jsqlparser.statement.CopyStatement; +import net.sf.jsqlparser.statement.create.macro.CreateMacro; +import net.sf.jsqlparser.statement.create.extension.CreateExtensionRepository; +import net.sf.jsqlparser.statement.AssertStatement; +import net.sf.jsqlparser.statement.export.ExportDataStatement; +import net.sf.jsqlparser.statement.load.LoadDataStatement; public class StatementDeParser extends AbstractDeParser implements StatementVisitor { @@ -121,11 +162,24 @@ public StatementDeParser(ExpressionDeParser expressionDeParser, SelectDeParser s @Override public StringBuilder visit(CreateIndex createIndex, S context) { - CreateIndexDeParser createIndexDeParser = new CreateIndexDeParser(builder); + CreateIndexDeParser createIndexDeParser = + new CreateIndexDeParser(builder, expressionDeParser); createIndexDeParser.deParse(createIndex); return builder; } + @Override + public StringBuilder visit(DoStatement statement, S context) { + return statement.appendTo(builder, code -> code.accept(expressionDeParser, context)); + } + + @Override + public StringBuilder visit(InsertBulk statement, S context) { + OrderByDeParser order = new OrderByDeParser(expressionDeParser, builder); + return statement.appendTo(builder, value -> value.accept(expressionDeParser, context), + element -> order.deParseElement(element, context)); + } + @Override public StringBuilder visit(CreateTable createTable, S context) { CreateTableDeParser createTableDeParser = new CreateTableDeParser(this, builder); @@ -133,9 +187,15 @@ public StringBuilder visit(CreateTable createTable, S context) { return builder; } + @Override + public StringBuilder visit(CreateTrigger createTrigger, S context) { + createTrigger.appendTo(builder, e -> e.accept(expressionDeParser, context)); + return builder; + } + @Override public StringBuilder visit(CreateView createView, S context) { - CreateViewDeParser createViewDeParser = new CreateViewDeParser(builder); + CreateViewDeParser createViewDeParser = new CreateViewDeParser(builder, selectDeParser); createViewDeParser.deParse(createView); return builder; } @@ -156,14 +216,16 @@ public StringBuilder visit(AlterView alterView, S context) { @Override public StringBuilder visit(Delete delete, S context) { - DeleteDeParser deleteDeParser = new DeleteDeParser(expressionDeParser, builder); + DeleteDeParser deleteDeParser = + new DeleteDeParser(expressionDeParser, selectDeParser, builder); deleteDeParser.deParse(delete); return builder; } @Override public StringBuilder visit(Drop drop, S context) { - DropDeParser dropDeParser = new DropDeParser(builder); + DropDeParser dropDeParser = + new DropDeParser(builder, table -> table.accept(selectDeParser, context)); dropDeParser.deParse(drop); return builder; } @@ -255,25 +317,31 @@ public StringBuilder visit(Truncate truncate, S context) { @Override public StringBuilder visit(Update update, S context) { - UpdateDeParser updateDeParser = new UpdateDeParser(expressionDeParser, builder); + UpdateDeParser updateDeParser = + new UpdateDeParser(expressionDeParser, selectDeParser, builder); updateDeParser.deParse(update); return builder; } public StringBuilder visit(Analyze analyzer, S context) { - builder.append("ANALYZE "); - builder.append(analyzer.getTable()); + builder.append(analyzer); return builder; } @Override public StringBuilder visit(Alter alter, S context) { - AlterDeParser alterDeParser = new AlterDeParser(builder); + AlterDeParser alterDeParser = new AlterDeParser(builder, expressionDeParser); alterDeParser.deParse(alter); return builder; } + @Override + public StringBuilder visit(AlterEvent alterEvent, S context) { + builder.append(alterEvent); + return builder; + } + @Override public StringBuilder visit(Statements statements, S context) { statements.accept(this, context); @@ -336,6 +404,11 @@ public StringBuilder visit(Upsert upsert, S context) { return builder; } + @Override + public StringBuilder visit(SetIdentityInsertStatement statement, S context) { + return statement.appendTo(builder, table -> table.accept(selectDeParser, context)); + } + @Override public StringBuilder visit(UseStatement use, S context) { new UseStatementDeParser(builder).deParse(use); @@ -378,27 +451,31 @@ public StringBuilder visit(Block block, S context) { @Override public StringBuilder visit(Comment comment, S context) { - builder.append(comment.toString()); - return builder; + return comment.appendTo(builder, + table -> table.accept(selectDeParser, context), + column -> column.accept(expressionDeParser, context), + literal -> literal.accept(expressionDeParser, context)); } @Override public StringBuilder visit(DescribeStatement describe, S context) { builder.append(describe.getDescribeType()); builder.append(" "); - builder.append(describe.getTable()); + if (describe.getSelect() != null) { + describe.getSelect().accept(this, context); + } else { + builder.append(describe.getTable()); + } return builder; } @Override public StringBuilder visit(ExplainStatement explainStatement, S context) { - builder.append(explainStatement.getKeyword()).append(" "); + builder.append(explainStatement.getKeyword()); if (explainStatement.getTable() != null) { - builder.append(explainStatement.getTable()); - } else if (explainStatement.getOptions() != null) { - builder.append(explainStatement.getOptions().values().stream() - .map(ExplainStatement.Option::formatOption).collect(Collectors.joining(" "))); - builder.append(" "); + builder.append(" ").append(explainStatement.getTable()); + } else { + explainStatement.appendOptionsTo(builder).append(" "); } if (explainStatement.getStatement() != null) { explainStatement.getStatement().accept(this, context); @@ -420,8 +497,7 @@ public StringBuilder visit(DeclareStatement declareStatement, S context) { @Override public StringBuilder visit(Grant grant, S context) { - GrantDeParser grantDeParser = new GrantDeParser(builder); - grantDeParser.deParse(grant); + grant.appendTo(builder, e -> e.accept(expressionDeParser, context)); return builder; } @@ -431,6 +507,24 @@ public StringBuilder visit(CreateSchema aThis, S context) { return builder; } + @Override + public StringBuilder visit(CreateDatabase aThis, S context) { + builder.append(aThis.toString()); + return builder; + } + + @Override + public StringBuilder visit(CreateUser createUser, S context) { + builder.append(createUser); + return builder; + } + + @Override + public StringBuilder visit(CreateEvent createEvent, S context) { + builder.append(createEvent); + return builder; + } + @Override public StringBuilder visit(CreateSequence createSequence, S context) { new CreateSequenceDeParser(builder).deParse(createSequence); @@ -445,7 +539,8 @@ public StringBuilder visit(AlterSequence alterSequence, S context) { @Override public StringBuilder visit(CreateFunctionalStatement createFunctionalStatement, S context) { - builder.append(createFunctionalStatement.toString()); + createFunctionalStatement.appendTo(builder, + new TableElementDeParser(builder, expressionDeParser)::deParse); return builder; } @@ -478,6 +573,90 @@ public StringBuilder visit(RenameTableStatement renameTableStatement, S cont return builder; } + @Override + public StringBuilder visit(PragmaStatement pragmaStatement, S context) { + pragmaStatement.appendTo(builder); + return builder; + } + + @Override + public StringBuilder visit(AssertStatement assertStatement, S context) { + assertStatement.appendTo(builder); + return builder; + } + + @Override + public StringBuilder visit(ExtensionStatement extensionStatement, S context) { + extensionStatement.appendTo(builder); + return builder; + } + + @Override + public StringBuilder visit(ExportDataStatement exportDataStatement, S context) { + exportDataStatement.appendTo(builder); + return builder; + } + + @Override + public StringBuilder visit(AttachStatement attachStatement, S context) { + attachStatement.appendTo(builder); + return builder; + } + + @Override + public StringBuilder visit(DetachStatement detachStatement, S context) { + detachStatement.appendTo(builder); + return builder; + } + + @Override + public StringBuilder visit(ConnectStatement connectStatement, S context) { + connectStatement.appendTo(builder); + return builder; + } + + @Override + public StringBuilder visit(DisconnectStatement disconnectStatement, S context) { + disconnectStatement.appendTo(builder); + return builder; + } + + @Override + public StringBuilder visit(PrepareStatement prepareStatement, S context) { + prepareStatement.appendTo(builder); + return builder; + } + + @Override + public StringBuilder visit(DeallocateStatement deallocateStatement, S context) { + deallocateStatement.appendTo(builder); + return builder; + } + + @Override + public StringBuilder visit(CopyStatement copyStatement, S context) { + copyStatement.appendTo(builder); + return builder; + } + + @Override + public StringBuilder visit(CreateMacro createMacro, S context) { + createMacro.appendTo(builder); + return builder; + } + + @Override + public StringBuilder visit(CreateExtensionRepository createExtensionRepository, S context) { + createExtensionRepository.appendTo(builder); + return builder; + } + + @Override + public StringBuilder visit(LoadDataStatement loadDataStatement, S context) { + loadDataStatement.appendTo(builder); + return builder; + } + @Override public StringBuilder visit(PurgeStatement purgeStatement, S context) { purgeStatement.appendTo(builder); @@ -524,7 +703,109 @@ public StringBuilder visit(LockStatement lock, S context) { @Override public StringBuilder visit(CreatePolicy createPolicy, S context) { - builder.append(createPolicy.toString()); + new CreatePolicyDeParser(expressionDeParser, builder).deParse(createPolicy); + return builder; + } + + @Override + public StringBuilder visit(CreateRole statement, S context) { + statement.appendTo(builder, e -> e.accept(expressionDeParser, context)); + return builder; + } + + @Override + public StringBuilder visit(AlterRole statement, S context) { + statement.appendTo(builder, e -> e.accept(expressionDeParser, context)); + return builder; + } + + @Override + public StringBuilder visit(Revoke statement, S context) { + statement.appendTo(builder, e -> e.accept(expressionDeParser, context)); + return builder; + } + + @Override + public StringBuilder visit(AlterDefaultPrivileges statement, S context) { + statement.appendTo(builder, e -> e.accept(expressionDeParser, context)); + return builder; + } + + @Override + public StringBuilder visit(CreateType statement, S context) { + builder.append(statement); + return builder; + } + + @Override + public StringBuilder visit(AlterType statement, S context) { + builder.append(statement); + return builder; + } + + @Override + public StringBuilder visit(CreateDomain statement, S context) { + statement.appendTo(builder, expression -> expression.accept(expressionDeParser, context)); + return builder; + } + + @Override + public StringBuilder visit(AlterDomain statement, S context) { + statement.appendTo(builder, expression -> expression.accept(expressionDeParser, context)); + return builder; + } + + @Override + public StringBuilder visit(CreateExtension statement, S context) { + builder.append(statement); + return builder; + } + + @Override + public StringBuilder visit(AlterExtension statement, S context) { + builder.append(statement); + return builder; + } + + @Override + public StringBuilder visit(CreatePublication statement, S context) { + statement.appendTo(builder, expression -> expression.accept(expressionDeParser, context)); return builder; } + + @Override + public StringBuilder visit(AlterPublication statement, S context) { + statement.appendTo(builder, expression -> expression.accept(expressionDeParser, context)); + return builder; + } + + @Override + public StringBuilder visit(CreateSubscription statement, S context) { + statement.appendTo(builder, expression -> expression.accept(expressionDeParser, context)); + return builder; + } + + @Override + public StringBuilder visit(AlterSubscription statement, S context) { + statement.appendTo(builder, expression -> expression.accept(expressionDeParser, context)); + return builder; + } + + @Override + public StringBuilder visit(OracleBlock block, S context) { + return block.appendTo(builder, expression -> expression.accept(expressionDeParser, context), + statement -> statement.accept(this, context)); + } + + @Override + public StringBuilder visit(OracleAssignment assignment, S context) { + return assignment.appendTo(builder, + expression -> expression.accept(expressionDeParser, context)); + } + + @Override + public StringBuilder visit(OracleNullStatement statement, S context) { + return builder.append("NULL"); + } + } diff --git a/src/main/java/net/sf/jsqlparser/util/deparser/TableElementDeParser.java b/src/main/java/net/sf/jsqlparser/util/deparser/TableElementDeParser.java new file mode 100644 index 0000000000..d0f3860345 --- /dev/null +++ b/src/main/java/net/sf/jsqlparser/util/deparser/TableElementDeParser.java @@ -0,0 +1,40 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.util.deparser; + +import net.sf.jsqlparser.expression.ExpressionVisitor; +import net.sf.jsqlparser.statement.create.table.ColumnDefinition; +import net.sf.jsqlparser.statement.create.table.Index; +import net.sf.jsqlparser.statement.create.table.TableElement; + +/** Deparses table elements while preserving expression visitor customization. */ +public class TableElementDeParser extends AbstractDeParser { + private final ExpressionVisitor expressionVisitor; + + public TableElementDeParser(StringBuilder builder, + ExpressionVisitor expressionVisitor) { + super(builder); + this.expressionVisitor = expressionVisitor; + } + + @Override + public void deParse(TableElement element) { + if (element instanceof Index) { + ((Index) element).appendTo(builder, + expression -> expression.accept(expressionVisitor, null)); + } else if (element instanceof ColumnDefinition) { + ((ColumnDefinition) element).appendTo(builder, + expression -> expression.accept(expressionVisitor, null)); + } else { + builder.append(element); + } + } + +} diff --git a/src/main/java/net/sf/jsqlparser/util/deparser/UpdateDeParser.java b/src/main/java/net/sf/jsqlparser/util/deparser/UpdateDeParser.java index d5dcac7f10..8c6b3ab490 100644 --- a/src/main/java/net/sf/jsqlparser/util/deparser/UpdateDeParser.java +++ b/src/main/java/net/sf/jsqlparser/util/deparser/UpdateDeParser.java @@ -11,45 +11,40 @@ import net.sf.jsqlparser.expression.ExpressionVisitor; import net.sf.jsqlparser.expression.ExpressionVisitorAdapter; -import net.sf.jsqlparser.statement.select.Join; import net.sf.jsqlparser.statement.select.OrderByElement; import net.sf.jsqlparser.statement.select.OrderByVisitor; -import net.sf.jsqlparser.statement.select.WithItem; import net.sf.jsqlparser.statement.update.Update; -import java.util.Iterator; - public class UpdateDeParser extends AbstractDeParser implements OrderByVisitor { private ExpressionVisitor expressionVisitor = new ExpressionVisitorAdapter<>(); + private SelectDeParser selectDeParser; + public UpdateDeParser() { super(new StringBuilder()); } public UpdateDeParser(ExpressionVisitor expressionVisitor, StringBuilder buffer) { + this(expressionVisitor, null, buffer); + } + + public UpdateDeParser(ExpressionVisitor expressionVisitor, + SelectDeParser selectDeParser, StringBuilder buffer) { super(buffer); this.expressionVisitor = expressionVisitor; + this.selectDeParser = selectDeParser; } @Override @SuppressWarnings({"PMD.CyclomaticComplexity", "PMD.NPathComplexity", "PMD.ExcessiveMethodLength"}) public void deParse(Update update) { - if (update.getWithItemsList() != null && !update.getWithItemsList().isEmpty()) { - builder.append("WITH "); - for (Iterator> iter = update.getWithItemsList().iterator(); iter - .hasNext();) { - WithItem withItem = iter.next(); - builder.append(withItem); - if (iter.hasNext()) { - builder.append(","); - } - builder.append(" "); - } - } + DmlDeParserSupport sources = + new DmlDeParserSupport(expressionVisitor, selectDeParser, builder); + sources.deparseWithItems(update.getWithItemsList(), null); builder.append("UPDATE "); if (update.getOracleHint() != null) { builder.append(update.getOracleHint()).append(" "); @@ -61,14 +56,12 @@ public void deParse(Update update) { builder.append("IGNORE "); } builder.append(update.getTable()); + deparseForPortionClause(update.getForPortionClause(), expressionVisitor); if (update.getStartJoins() != null) { - for (Join join : update.getStartJoins()) { - if (join.isSimple()) { - builder.append(", ").append(join); - } else { - builder.append(" ").append(join); - } - } + sources.deparseJoins(update.getStartJoins()); + } + if (update.isFromBeforeSet()) { + sources.deparseFrom(update.getFromItem(), update.getJoins()); } builder.append(" SET "); @@ -78,17 +71,8 @@ public void deParse(Update update) { update.getOutputClause().appendTo(builder); } - if (update.getFromItem() != null) { - builder.append(" FROM ").append(update.getFromItem()); - if (update.getJoins() != null) { - for (Join join : update.getJoins()) { - if (join.isSimple()) { - builder.append(", ").append(join); - } else { - builder.append(" ").append(join); - } - } - } + if (!update.isFromBeforeSet()) { + sources.deparseFrom(update.getFromItem(), update.getJoins()); } deparseWhereClause(update); @@ -103,9 +87,11 @@ public void deParse(Update update) { new LimitDeparser(expressionVisitor, builder).deParse(update.getLimit()); } - if (update.getReturningClause() != null) { - update.getReturningClause().appendTo(builder); + if (update.getOption() != null) { + builder.append(update.getOption()); } + + deparseReturningClause(update.getReturningClause(), expressionVisitor); } protected void deparseWhereClause(Update update) { @@ -119,7 +105,6 @@ protected void deparseUpdateSetsClause(Update update) { deparseUpdateSets(update.getUpdateSets(), builder, expressionVisitor); } - public ExpressionVisitor getExpressionVisitor() { return expressionVisitor; } diff --git a/src/main/java/net/sf/jsqlparser/util/validation/feature/FeaturesAllowed.java b/src/main/java/net/sf/jsqlparser/util/validation/feature/FeaturesAllowed.java index 431e4d7622..e1202fd123 100644 --- a/src/main/java/net/sf/jsqlparser/util/validation/feature/FeaturesAllowed.java +++ b/src/main/java/net/sf/jsqlparser/util/validation/feature/FeaturesAllowed.java @@ -45,7 +45,7 @@ public class FeaturesAllowed implements FeatureSetValidation, ModifyableFeatureS */ public static final FeaturesAllowed SELECT = new FeaturesAllowed("SELECT", // select features - Feature.select, + Feature.select, Feature.matchRecognize, Feature.matchRecognizeOptions, Feature.selectGroupBy, Feature.selectHaving, @@ -59,9 +59,11 @@ public class FeaturesAllowed implements FeatureSetValidation, ModifyableFeatureS Feature.joinCross, Feature.joinOuter, Feature.joinSemi, + Feature.joinAnti, Feature.joinInner, Feature.joinStraight, Feature.joinApply, + Feature.joinAsOf, Feature.joinWindow, Feature.joinUsingColumns, @@ -84,6 +86,7 @@ public class FeaturesAllowed implements FeatureSetValidation, ModifyableFeatureS Feature.orderBy, Feature.orderByNullOrdering, Feature.tableStatement, + Feature.tableVariable, Feature.function).unmodifyable(); /** @@ -136,6 +139,7 @@ public class FeaturesAllowed implements FeatureSetValidation, ModifyableFeatureS * all {@link Feature}' for SQL UPDATE including {@link #SELECT} */ public static final FeaturesAllowed UPDATE = new FeaturesAllowed("UPDATE", Feature.update, + Feature.forPortion, Feature.updateJoins, Feature.updateFrom, Feature.updateLimit, Feature.updateOrderBy, Feature.updateReturning, Feature.updateUseSelect) @@ -144,7 +148,7 @@ public class FeaturesAllowed implements FeatureSetValidation, ModifyableFeatureS * all {@link Feature}' for SQL UPDATE including {@link #SELECT} */ public static final FeaturesAllowed DELETE = - new FeaturesAllowed("DELETE", Feature.delete, Feature.deleteJoin, + new FeaturesAllowed("DELETE", Feature.delete, Feature.forPortion, Feature.deleteJoin, Feature.deleteLimit, Feature.deleteOrderBy, Feature.deleteTables, Feature.deleteReturningExpressionList, Feature.truncate) diff --git a/src/main/java/net/sf/jsqlparser/util/validation/feature/MariaDbVersion.java b/src/main/java/net/sf/jsqlparser/util/validation/feature/MariaDbVersion.java index 742e84b976..0d322863cc 100644 --- a/src/main/java/net/sf/jsqlparser/util/validation/feature/MariaDbVersion.java +++ b/src/main/java/net/sf/jsqlparser/util/validation/feature/MariaDbVersion.java @@ -26,10 +26,8 @@ public enum MariaDbVersion implements Version { V10_5_4("10.5.4", EnumSet.of(// supported if used with jdbc Feature.jdbcParameter, - Feature.jdbcNamedParameter, - // expressions - Feature.exprLike, - // https://mariadb.com/kb/en/select/ + Feature.jdbcNamedParameter, // expressions + Feature.exprLike, // https://mariadb.com/kb/en/select/ Feature.select, Feature.selectGroupBy, Feature.function, Feature.selectHaving, @@ -49,12 +47,9 @@ public enum MariaDbVersion implements Version { // https://mariadb.com/kb/en/select/#distinct Feature.distinct, - Feature.setOperation, - // https://mariadb.com/kb/en/union/ - Feature.setOperationUnion, - // https://mariadb.com/kb/en/intersect/ - Feature.setOperationIntersect, - // https://mariadb.com/kb/en/except/ + Feature.setOperation, // https://mariadb.com/kb/en/union/ + Feature.setOperationUnion, // https://mariadb.com/kb/en/intersect/ + Feature.setOperationIntersect, // https://mariadb.com/kb/en/except/ Feature.setOperationExcept, // https://mariadb.com/kb/en/common-table-expressions/ @@ -71,7 +66,8 @@ public enum MariaDbVersion implements Version { Feature.insertReturningExpressionList, // https://mariadb.com/kb/en/update/ - Feature.update, + Feature.update, // https://mariadb.com/kb/en/application-time-periods/ + Feature.forPortion, Feature.updateJoins, Feature.updateOrderBy, Feature.updateLimit, @@ -87,17 +83,12 @@ public enum MariaDbVersion implements Version { Feature.execute, Feature.executeCall, // https://mariadb.com/kb/en/drop/ - Feature.drop, - // https://mariadb.com/kb/en/drop-index/ - Feature.dropIndex, - // https://mariadb.com/kb/en/drop-table/ - Feature.dropTable, - // https://mariadb.com/kb/en/drop-database/ + Feature.drop, // https://mariadb.com/kb/en/drop-index/ + Feature.dropIndex, // https://mariadb.com/kb/en/drop-table/ + Feature.dropTable, // https://mariadb.com/kb/en/drop-database/ // SCHEMA = DATABASE - Feature.dropSchema, - // https://mariadb.com/kb/en/drop-view/ - Feature.dropView, - // https://mariadb.com/kb/en/drop-sequence/ + Feature.dropSchema, // https://mariadb.com/kb/en/drop-view/ + Feature.dropView, // https://mariadb.com/kb/en/drop-sequence/ Feature.dropSequence, Feature.dropTableIfExists, Feature.dropIndexIfExists, Feature.dropViewIfExists, Feature.dropSchemaIfExists, Feature.dropSequenceIfExists, @@ -106,12 +97,9 @@ public enum MariaDbVersion implements Version { Feature.upsert, // https://mariadb.com/kb/en/alter/ - Feature.alterTable, - // https://mariadb.com/kb/en/alter-sequence/ - Feature.alterSequence, - // https://mariadb.com/kb/en/alter-view/ - Feature.alterView, - // https://mariadb.com/kb/en/create-view/ + Feature.alterTable, // https://mariadb.com/kb/en/alter-sequence/ + Feature.alterSequence, // https://mariadb.com/kb/en/alter-view/ + Feature.alterView, // https://mariadb.com/kb/en/create-view/ Feature.createView, Feature.createOrReplaceView, Feature.createViewWithComment, @@ -119,35 +107,22 @@ public enum MariaDbVersion implements Version { // https://mariadb.com/kb/en/create-table/ Feature.createTable, Feature.createTableCreateOptionStrings, Feature.createTableTableOptionStrings, - Feature.createTableFromSelect, Feature.createTableIfNotExists, - // https://mariadb.com/kb/en/create-index/ - Feature.createIndex, - // https://mariadb.com/kb/en/create-sequence/ - Feature.createSequence, - // https://mariadb.com/kb/en/create-database/ - Feature.createSchema, - // https://mariadb.com/kb/en/create-trigger/ + Feature.createTableFromSelect, Feature.createTableIfNotExists, // https://mariadb.com/kb/en/create-index/ + Feature.createIndex, // https://mariadb.com/kb/en/create-sequence/ + Feature.createSequence, // https://mariadb.com/kb/en/create-database/ + Feature.createSchema, // https://mariadb.com/kb/en/create-trigger/ Feature.createTrigger, // https://mariadb.com/kb/en/describe/ - Feature.describe, - // https://mariadb.com/kb/en/explain/ - Feature.explain, - // https://mariadb.com/kb/en/show/ - Feature.show, - // https://mariadb.com/kb/en/show-tables/ - Feature.showTables, - // https://mariadb.com/kb/en/show-columns/ - Feature.showColumns, - // https://mariadb.com/kb/en/show-index/ - Feature.showIndex, - // https://mariadb.com/kb/en/use/ - Feature.use, - // https://mariadb.com/kb/en/grant/ - Feature.grant, - // https://mariadb.com/kb/en/commit/ - Feature.commit, - // https://mariadb.com/kb/en/optimizer-hints/ + Feature.describe, // https://mariadb.com/kb/en/explain/ + Feature.explain, // https://mariadb.com/kb/en/show/ + Feature.show, // https://mariadb.com/kb/en/show-tables/ + Feature.showTables, // https://mariadb.com/kb/en/show-columns/ + Feature.showColumns, // https://mariadb.com/kb/en/show-index/ + Feature.showIndex, // https://mariadb.com/kb/en/use/ + Feature.use, // https://mariadb.com/kb/en/grant/ + Feature.grant, // https://mariadb.com/kb/en/commit/ + Feature.commit, // https://mariadb.com/kb/en/optimizer-hints/ Feature.mySqlHintStraightJoin, Feature.mysqlCalcFoundRows, Feature.mysqlSqlCacheFlag)), diff --git a/src/main/java/net/sf/jsqlparser/util/validation/feature/OracleVersion.java b/src/main/java/net/sf/jsqlparser/util/validation/feature/OracleVersion.java index 37b1938207..8d94ab72c1 100644 --- a/src/main/java/net/sf/jsqlparser/util/validation/feature/OracleVersion.java +++ b/src/main/java/net/sf/jsqlparser/util/validation/feature/OracleVersion.java @@ -24,20 +24,15 @@ */ public enum OracleVersion implements Version { V19C("19c", - EnumSet.of( - // supported if used with jdbc + EnumSet.of(// supported if used with jdbc Feature.jdbcParameter, - Feature.jdbcNamedParameter, - // expressions - Feature.exprLike, - // common features + Feature.jdbcNamedParameter, // expressions + Feature.exprLike, // common features // https://docs.oracle.com/en/database/oracle/oracle-database/19/sqlrf/SELECT.html - Feature.select, - // https://www.oracletutorial.com/oracle-basics/oracle-group-by/ - Feature.selectGroupBy, Feature.function, - // https://www.oracletutorial.com/oracle-basics/oracle-grouping-sets/ - Feature.selectGroupByGroupingSets, - // https://www.oracletutorial.com/oracle-basics/oracle-having/ + Feature.matchRecognize, + Feature.select, // https://www.oracletutorial.com/oracle-basics/oracle-group-by/ + Feature.selectGroupBy, Feature.function, // https://www.oracletutorial.com/oracle-basics/oracle-grouping-sets/ + Feature.selectGroupByGroupingSets, // https://www.oracletutorial.com/oracle-basics/oracle-having/ Feature.selectHaving, // https://docs.oracle.com/en/database/oracle/oracle-database/19/sqlrf/SELECT.html @@ -91,8 +86,7 @@ public enum OracleVersion implements Version { // https://docs.oracle.com/en/database/oracle/oracle-database/19/sqlrf/INSERT.html Feature.insert, Feature.insertValues, - Feature.values, - // https://docs.oracle.com/en/database/oracle/oracle-database/19/sqlrf/INSERT.html + Feature.values, // https://docs.oracle.com/en/database/oracle/oracle-database/19/sqlrf/INSERT.html // see "single_table_insert" Feature.insertFromSelect, @@ -108,38 +102,25 @@ public enum OracleVersion implements Version { // https://www.oracletutorial.com/oracle-basics/oracle-truncate-table/ Feature.truncate, - Feature.drop, - // https://docs.oracle.com/en/database/oracle/oracle-database/19/sqlrf/DROP-TABLE.html - Feature.dropTable, - // https://docs.oracle.com/en/database/oracle/oracle-database/19/sqlrf/DROP-INDEX.html - Feature.dropIndex, - // https://docs.oracle.com/en/database/oracle/oracle-database/19/sqlrf/DROP-VIEW.html - Feature.dropView, - // https://docs.oracle.com/en/database/oracle/oracle-database/19/sqlrf/DROP-SEQUENCE.html + Feature.drop, // https://docs.oracle.com/en/database/oracle/oracle-database/19/sqlrf/DROP-TABLE.html + Feature.dropTable, // https://docs.oracle.com/en/database/oracle/oracle-database/19/sqlrf/DROP-INDEX.html + Feature.dropIndex, // https://docs.oracle.com/en/database/oracle/oracle-database/19/sqlrf/DROP-VIEW.html + Feature.dropView, // https://docs.oracle.com/en/database/oracle/oracle-database/19/sqlrf/DROP-SEQUENCE.html Feature.dropSequence, // https://docs.oracle.com/en/database/oracle/oracle-database/19/sqlrf/ALTER-TABLE.html - Feature.alterTable, - // https://docs.oracle.com/en/database/oracle/oracle-database/19/sqlrf/ALTER-SEQUENCE.html - Feature.alterSequence, - // https://docs.oracle.com/en/database/oracle/oracle-database/19/lnpls/EXECUTE-IMMEDIATE-statement.html - Feature.executeStatementImmediate, - // https://docs.oracle.com/en/database/oracle/oracle-database/19/sqlrf/CREATE-VIEW.html + Feature.alterTable, // https://docs.oracle.com/en/database/oracle/oracle-database/19/sqlrf/ALTER-SEQUENCE.html + Feature.alterSequence, // https://docs.oracle.com/en/database/oracle/oracle-database/19/lnpls/EXECUTE-IMMEDIATE-statement.html + Feature.executeStatementImmediate, // https://docs.oracle.com/en/database/oracle/oracle-database/19/sqlrf/CREATE-VIEW.html Feature.createView, - Feature.createViewForce, Feature.createOrReplaceView, - // https://docs.oracle.com/en/database/oracle/oracle-database/19/sqlrf/CREATE-MATERIALIZED-VIEW.htm - Feature.createViewMaterialized, - // https://docs.oracle.com/en/database/oracle/oracle-database/19/sqlrf/CREATE-TABLE.html + Feature.createViewForce, Feature.createOrReplaceView, // https://docs.oracle.com/en/database/oracle/oracle-database/19/sqlrf/CREATE-MATERIALIZED-VIEW.htm + Feature.createViewMaterialized, // https://docs.oracle.com/en/database/oracle/oracle-database/19/sqlrf/CREATE-TABLE.html Feature.createTable, Feature.createTableCreateOptionStrings, Feature.createTableTableOptionStrings, - Feature.createTableFromSelect, Feature.createTableRowMovement, - // https://docs.oracle.com/en/database/oracle/oracle-database/19/sqlrf/CREATE-INDEX.html - Feature.createIndex, - // https://docs.oracle.com/en/database/oracle/oracle-database/19/sqlrf/CREATE-SEQUENCE.html - Feature.createSequence, - // https://docs.oracle.com/en/database/oracle/oracle-database/19/sqlrf/CREATE-TRIGGER.html - Feature.createTrigger, - // https://docs.oracle.com/en/database/oracle/oracle-database/19/sqlrf/CREATE-SCHEMA.html + Feature.createTableFromSelect, Feature.createTableRowMovement, // https://docs.oracle.com/en/database/oracle/oracle-database/19/sqlrf/CREATE-INDEX.html + Feature.createIndex, // https://docs.oracle.com/en/database/oracle/oracle-database/19/sqlrf/CREATE-SEQUENCE.html + Feature.createSequence, // https://docs.oracle.com/en/database/oracle/oracle-database/19/sqlrf/CREATE-TRIGGER.html + Feature.createTrigger, // https://docs.oracle.com/en/database/oracle/oracle-database/19/sqlrf/CREATE-SCHEMA.html Feature.createSchema, Feature.commit, @@ -158,7 +139,7 @@ public enum OracleVersion implements Version { Feature.merge, Feature.createFunction, Feature.createProcedure, Feature.functionalStatement, - Feature.block, + Feature.block, Feature.oracleBlock, Feature.execute, Feature.declare, // special oracle features diff --git a/src/main/java/net/sf/jsqlparser/util/validation/feature/PostgresqlVersion.java b/src/main/java/net/sf/jsqlparser/util/validation/feature/PostgresqlVersion.java index 386274fa1e..5663a4031b 100644 --- a/src/main/java/net/sf/jsqlparser/util/validation/feature/PostgresqlVersion.java +++ b/src/main/java/net/sf/jsqlparser/util/validation/feature/PostgresqlVersion.java @@ -24,19 +24,16 @@ */ public enum PostgresqlVersion implements Version { V10("10", - EnumSet.of( - // supported if used with jdbc + EnumSet.of(// supported if used with jdbc Feature.jdbcParameter, - Feature.jdbcNamedParameter, - // expressions + Feature.jdbcNamedParameter, // expressions + Feature.doStatement, Feature.exprLike, - Feature.exprSimilarTo, - // https://www.postgresql.org/docs/current/sql-select.html + Feature.exprSimilarTo, // https://www.postgresql.org/docs/current/sql-select.html Feature.select, Feature.selectGroupBy, Feature.function, Feature.tableFunction, Feature.lateralSubSelect, - Feature.selectHaving, - // https://www.postgresql.org/docs/current/queries-table-expressions.html#QUERIES-GROUPING-SETS + Feature.selectHaving, // https://www.postgresql.org/docs/current/queries-table-expressions.html#QUERIES-GROUPING-SETS Feature.selectGroupByGroupingSets, // https://www.postgresql.org/docs/current/sql-select.html#join_type @@ -93,22 +90,26 @@ public enum PostgresqlVersion implements Version { Feature.commentOnTable, Feature.commentOnColumn, Feature.commentOnView, + Feature.commentOnIndex, Feature.commentOnSchema, Feature.commentOnSequence, + Feature.commentOnDomain, Feature.commentOnType, + Feature.commentOnMaterializedView, + Feature.commentOnFunction, Feature.commentOnConstraint, // https://www.postgresql.org/docs/current/sql-createsequence.html Feature.createSequence, - // https://www.postgresql.org/docs/current/sql-altersequence.html - Feature.alterSequence, - // https://www.postgresql.org/docs/current/sql-createschema.html - Feature.createSchema, - // https://www.postgresql.org/docs/current/sql-createindex.html - Feature.createIndex, - // https://www.postgresql.org/docs/current/sql-createtable.html + Feature.createPublication, Feature.alterPublication, + Feature.createSubscription, Feature.alterSubscription, + Feature.createType, Feature.alterType, + Feature.createDomain, Feature.alterDomain, + Feature.createExtension, Feature.alterExtension, // https://www.postgresql.org/docs/current/sql-altersequence.html + Feature.alterSequence, // https://www.postgresql.org/docs/current/sql-createschema.html + Feature.createSchema, // https://www.postgresql.org/docs/current/sql-createindex.html + Feature.createIndex, // https://www.postgresql.org/docs/current/sql-createtable.html Feature.createTable, Feature.createTableUnlogged, Feature.createTableCreateOptionStrings, Feature.createTableTableOptionStrings, - Feature.createTableFromSelect, Feature.createTableIfNotExists, - // https://www.postgresql.org/docs/current/sql-createview.html + Feature.createTableFromSelect, Feature.createTableIfNotExists, // https://www.postgresql.org/docs/current/sql-createview.html Feature.createView, Feature.createViewTemporary, Feature.createOrReplaceView, - // https://www.postgresql.org/docs/current/sql-alterview.html + Feature.createViewMaterialized, // https://www.postgresql.org/docs/current/sql-alterview.html // Feature.alterView, // https://www.postgresql.org/docs/16/sql-refreshmaterializedview.html @@ -122,44 +123,33 @@ public enum PostgresqlVersion implements Version { Feature.values, Feature.insertFromSelect, Feature.insertReturningAll, - Feature.insertReturningExpressionList, - // https://www.postgresql.org/docs/current/sql-update.html + Feature.insertReturningExpressionList, // https://www.postgresql.org/docs/current/sql-update.html Feature.update, - Feature.updateReturning, - // https://www.postgresql.org/docs/current/sql-delete.html + Feature.updateReturning, // https://www.postgresql.org/docs/current/sql-delete.html Feature.delete, - Feature.deleteReturningExpressionList, - // https://www.postgresql.org/docs/current/sql-truncate.html + Feature.deleteReturningExpressionList, // https://www.postgresql.org/docs/current/sql-truncate.html Feature.truncate, // https://www.postgresql.org/docs/current/sql-call.html Feature.execute, Feature.executeCall, - Feature.drop, - // https://www.postgresql.org/docs/current/sql-droptable.html - Feature.dropTable, - // https://www.postgresql.org/docs/current/sql-dropindex.html - Feature.dropIndex, - // https://www.postgresql.org/docs/current/sql-dropview.html - Feature.dropView, - // https://www.postgresql.org/docs/current/sql-dropschema.html - Feature.dropSchema, - // https://www.postgresql.org/docs/current/sql-dropsequence.html + Feature.drop, // https://www.postgresql.org/docs/current/sql-droptable.html + Feature.dropTable, // https://www.postgresql.org/docs/current/sql-dropindex.html + Feature.dropIndex, // https://www.postgresql.org/docs/current/sql-dropview.html + Feature.dropView, // https://www.postgresql.org/docs/current/sql-dropschema.html + Feature.dropSchema, // https://www.postgresql.org/docs/current/sql-dropsequence.html Feature.dropSequence, Feature.dropTableIfExists, Feature.dropIndexIfExists, Feature.dropViewIfExists, Feature.dropSchemaIfExists, Feature.dropSequenceIfExists, // https://www.postgresql.org/docs/current/sql-altertable.html - Feature.alterTable, - // https://www.postgresql.org/docs/current/using-explain.html - Feature.explain, - // https://www.postgresql.org/docs/current/sql-grant.html + Feature.alterTable, // https://www.postgresql.org/docs/current/using-explain.html + Feature.explain, // https://www.postgresql.org/docs/current/sql-grant.html Feature.grant, - // https://www.postgresql.org/docs/current/sql-set.html - Feature.set, - // https://www.postgresql.org/docs/current/sql-reset.html - Feature.reset, - // https://www.postgresql.org/docs/current/sql-commit.html + Feature.revoke, Feature.createRole, Feature.alterRole, + Feature.alterDefaultPrivileges, Feature.createTrigger, // https://www.postgresql.org/docs/current/sql-set.html + Feature.set, // https://www.postgresql.org/docs/current/sql-reset.html + Feature.reset, // https://www.postgresql.org/docs/current/sql-commit.html Feature.commit)), V11("11", V10.copy().getFeatures()), V12("12", V11.copy().getFeatures()), V13("13", V12.copy().getFeatures()), V14("14", V13.copy().getFeatures()); diff --git a/src/main/java/net/sf/jsqlparser/util/validation/feature/SqlServerVersion.java b/src/main/java/net/sf/jsqlparser/util/validation/feature/SqlServerVersion.java index a59e787162..e7c11c9a7b 100644 --- a/src/main/java/net/sf/jsqlparser/util/validation/feature/SqlServerVersion.java +++ b/src/main/java/net/sf/jsqlparser/util/validation/feature/SqlServerVersion.java @@ -22,26 +22,20 @@ */ public enum SqlServerVersion implements Version { V2019("2019", - EnumSet.of( - // supported if used with jdbc + EnumSet.of(// supported if used with jdbc Feature.jdbcParameter, - Feature.jdbcNamedParameter, - // expressions - Feature.exprLike, - // https://docs.microsoft.com/en-us/sql/t-sql/queries/select-transact-sql?view=sql-server-ver15 + Feature.jdbcNamedParameter, // expressions + Feature.exprLike, // https://docs.microsoft.com/en-us/sql/t-sql/queries/select-transact-sql?view=sql-server-ver15 Feature.select, Feature.selectInto, Feature.withItem, - Feature.selectGroupBy, Feature.function, - // https://docs.microsoft.com/en-us/sql/t-sql/queries/from-transact-sql?view=sql-server-ver15 + Feature.selectGroupBy, Feature.function, // https://docs.microsoft.com/en-us/sql/t-sql/queries/from-transact-sql?view=sql-server-ver15 // see user_defined_function Feature.tableFunction, Feature.selectHaving, Feature.orderBy, Feature.distinct, - Feature.withItem, Feature.withItemRecursive, - // https://docs.microsoft.com/en-us/sql/t-sql/queries/top-transact-sql?view=sql-server-ver15 - Feature.top, - // https://docs.microsoft.com/en-us/sql/t-sql/queries/select-order-by-clause-transact-sql?view=sql-server-ver15 + Feature.withItem, Feature.withItemRecursive, // https://docs.microsoft.com/en-us/sql/t-sql/queries/top-transact-sql?view=sql-server-ver15 + Feature.top, // https://docs.microsoft.com/en-us/sql/t-sql/queries/select-order-by-clause-transact-sql?view=sql-server-ver15 Feature.offset, Feature.offsetParam, Feature.fetch, Feature.fetchFirst, Feature.fetchNext, @@ -66,27 +60,21 @@ public enum SqlServerVersion implements Version { // https://docs.microsoft.com/en-us/sql/t-sql/statements/insert-transact-sql?view=sql-server-ver15 Feature.insert, + Feature.insertBulk, Feature.insertValues, - Feature.insertFromSelect, - // https://docs.microsoft.com/en-us/sql/t-sql/queries/update-transact-sql?view=sql-server-ver15 + Feature.insertFromSelect, // https://docs.microsoft.com/en-us/sql/t-sql/queries/update-transact-sql?view=sql-server-ver15 Feature.update, Feature.updateFrom, // https://docs.microsoft.com/en-us/sql/t-sql/statements/delete-transact-sql?view=sql-server-ver15 - Feature.delete, - // https://docs.microsoft.com/en-us/sql/t-sql/statements/truncate-table-transact-sql?view=sql-server-ver15 + Feature.delete, // https://docs.microsoft.com/en-us/sql/t-sql/statements/truncate-table-transact-sql?view=sql-server-ver15 Feature.truncate, - Feature.drop, - // https://docs.microsoft.com/en-us/sql/t-sql/statements/drop-table-transact-sql?view=sql-server-ver15 - Feature.dropTable, - // https://docs.microsoft.com/en-us/sql/t-sql/statements/drop-index-transact-sql?view=sql-server-ver15 - Feature.dropIndex, - // https://docs.microsoft.com/en-us/sql/t-sql/statements/drop-view-transact-sql?view=sql-server-ver15 - Feature.dropView, - // https://docs.microsoft.com/en-us/sql/t-sql/statements/drop-schema-transact-sql?view=sql-server-ver15 - Feature.dropSchema, - // https://docs.microsoft.com/en-us/sql/t-sql/statements/drop-sequence-transact-sql?view=sql-server-ver15 + Feature.drop, // https://docs.microsoft.com/en-us/sql/t-sql/statements/drop-table-transact-sql?view=sql-server-ver15 + Feature.dropTable, // https://docs.microsoft.com/en-us/sql/t-sql/statements/drop-index-transact-sql?view=sql-server-ver15 + Feature.dropIndex, // https://docs.microsoft.com/en-us/sql/t-sql/statements/drop-view-transact-sql?view=sql-server-ver15 + Feature.dropView, // https://docs.microsoft.com/en-us/sql/t-sql/statements/drop-schema-transact-sql?view=sql-server-ver15 + Feature.dropSchema, // https://docs.microsoft.com/en-us/sql/t-sql/statements/drop-sequence-transact-sql?view=sql-server-ver15 Feature.dropSequence, Feature.dropTableIfExists, Feature.dropIndexIfExists, Feature.dropViewIfExists, Feature.dropSchemaIfExists, Feature.dropSequenceIfExists, @@ -96,44 +84,32 @@ public enum SqlServerVersion implements Version { Feature.executeExec, Feature.executeExecute, // https://docs.microsoft.com/en-us/sql/t-sql/language-elements/set-local-variable-transact-sql?view=sql-server-ver15 - Feature.set, + Feature.set, Feature.sqlServerSetOptions, // https://docs.microsoft.com/en-us/sql/t-sql/statements/alter-table-transact-sql?view=sql-server-ver15 - Feature.alterTable, - // https://docs.microsoft.com/en-us/sql/t-sql/statements/alter-sequence-transact-sql?view=sql-server-ver15 - Feature.alterSequence, - // https://docs.microsoft.com/en-us/sql/t-sql/statements/alter-view-transact-sql?view=sql-server-ver15 - Feature.alterView, - // https://docs.microsoft.com/en-us/sql/t-sql/statements/alter-index-transact-sql?view=sql-server-ver15 - Feature.alterIndex, - // https://docs.microsoft.com/en-us/sql/t-sql/statements/create-index-transact-sql?view=sql-server-ver15 - Feature.createIndex, - // https://docs.microsoft.com/en-us/sql/t-sql/statements/create-sequence-transact-sql?view=sql-server-ver15 - Feature.createSequence, - // https://docs.microsoft.com/en-us/sql/t-sql/statements/create-table-transact-sql?view=sql-server-ver15 + Feature.alterTable, // https://docs.microsoft.com/en-us/sql/t-sql/statements/alter-sequence-transact-sql?view=sql-server-ver15 + Feature.alterSequence, // https://docs.microsoft.com/en-us/sql/t-sql/statements/alter-view-transact-sql?view=sql-server-ver15 + Feature.alterView, // https://docs.microsoft.com/en-us/sql/t-sql/statements/alter-index-transact-sql?view=sql-server-ver15 + Feature.alterIndex, // https://docs.microsoft.com/en-us/sql/t-sql/statements/create-index-transact-sql?view=sql-server-ver15 + Feature.createIndex, // https://docs.microsoft.com/en-us/sql/t-sql/statements/create-sequence-transact-sql?view=sql-server-ver15 + Feature.createSequence, // https://docs.microsoft.com/en-us/sql/t-sql/statements/create-table-transact-sql?view=sql-server-ver15 Feature.createTable, Feature.createTableTableOptionStrings, - Feature.createTableFromSelect, - // https://docs.microsoft.com/en-us/sql/t-sql/statements/create-function-transact-sql?view=sql-server-ver15 + Feature.createTableFromSelect, // https://docs.microsoft.com/en-us/sql/t-sql/statements/create-function-transact-sql?view=sql-server-ver15 // https://docs.microsoft.com/en-us/sql/t-sql/statements/create-procedure-transact-sql?view=sql-server-ver15 Feature.functionalStatement, Feature.createProcedure, Feature.createFunction, + Feature.alterFunction, Feature.alterProcedure, Feature.createOrAlterRoutine, Feature.block, Feature.declare, - // https://docs.microsoft.com/en-us/sql/t-sql/statements/create-schema-transact-sql?view=sql-server-ver15 - Feature.createSchema, - // https://docs.microsoft.com/en-us/sql/t-sql/statements/create-view-transact-sql?view=sql-server-ver15 - Feature.createView, - // https://docs.microsoft.com/en-us/sql/t-sql/statements/create-trigger-transact-sql?view=sql-server-ver15 - Feature.createTrigger, - // https://docs.microsoft.com/en-us/sql/t-sql/statements/merge-transact-sql?view=sql-server-ver15 - Feature.merge, - // https://docs.microsoft.com/en-us/sql/t-sql/statements/grant-transact-sql?view=sql-server-ver15 - Feature.grant, - // https://docs.microsoft.com/en-us/sql/t-sql/language-elements/commit-transaction-transact-sql?view=sql-server-ver15 - Feature.commit, - // special sql-server features + Feature.tableVariable, // https://docs.microsoft.com/en-us/sql/t-sql/statements/create-schema-transact-sql?view=sql-server-ver15 + Feature.createSchema, // https://docs.microsoft.com/en-us/sql/t-sql/statements/create-view-transact-sql?view=sql-server-ver15 + Feature.createView, // https://docs.microsoft.com/en-us/sql/t-sql/statements/create-trigger-transact-sql?view=sql-server-ver15 + Feature.createTrigger, // https://docs.microsoft.com/en-us/sql/t-sql/statements/merge-transact-sql?view=sql-server-ver15 + Feature.merge, // https://docs.microsoft.com/en-us/sql/t-sql/statements/grant-transact-sql?view=sql-server-ver15 + Feature.grant, // https://docs.microsoft.com/en-us/sql/t-sql/language-elements/commit-transaction-transact-sql?view=sql-server-ver15 + Feature.commit, // special sql-server features // https://docs.microsoft.com/en-us/sql/relational-databases/xml/for-xml-sql-server?view=sql-server-ver15 Feature.selectForXmlPath, - Feature.use, Feature.allowSquareBracketQuotation, // + Feature.use, Feature.setIdentityInsert, Feature.allowSquareBracketQuotation, // Feature.pivot, Feature.unpivot, Feature.pivotXml, Feature.selectGroupByGroupingSets)); diff --git a/src/main/java/net/sf/jsqlparser/util/validation/validator/AbstractValidator.java b/src/main/java/net/sf/jsqlparser/util/validation/validator/AbstractValidator.java index 9e810ca822..4955783764 100644 --- a/src/main/java/net/sf/jsqlparser/util/validation/validator/AbstractValidator.java +++ b/src/main/java/net/sf/jsqlparser/util/validation/validator/AbstractValidator.java @@ -9,7 +9,11 @@ */ package net.sf.jsqlparser.util.validation.validator; +import net.sf.jsqlparser.statement.ForPortionClause; import net.sf.jsqlparser.expression.Expression; +import net.sf.jsqlparser.statement.update.UpdateSet; +import net.sf.jsqlparser.statement.select.Select; +import net.sf.jsqlparser.statement.select.SelectVisitor; import net.sf.jsqlparser.parser.feature.Feature; import net.sf.jsqlparser.statement.select.FromItem; import net.sf.jsqlparser.statement.select.OrderByElement; @@ -129,6 +133,15 @@ protected > void validateOptionalList(List elementL } } + protected void validateOptionalForPortionClause(ForPortionClause clause) { + if (clause != null) { + for (ValidationCapability capability : getCapabilities()) { + validateFeature(capability, Feature.forPortion); + } + clause.accept(getValidator(ExpressionValidator.class), null); + } + } + protected void validateOptionalExpression(Expression expression) { validateOptional(expression, e -> e.accept(getValidator(ExpressionValidator.class), null)); } @@ -137,6 +150,25 @@ protected void validateOptionalExpression(Expression expression, ExpressionValid validateOptional(expression, e -> e.accept(v, null)); } + protected void validateOptionalUpdateSets(List updateSets) { + if (updateSets != null) { + for (UpdateSet updateSet : updateSets) { + validateOptionalExpressions(updateSet.getColumns()); + if (updateSet.getValues() != null) { + for (Expression value : updateSet.getValues()) { + if (value instanceof Select) { + ((Select) value).accept( + (SelectVisitor) getValidator(SelectValidator.class), + null); + } else { + validateOptionalExpression(value); + } + } + } + } + } + } + protected void validateOptionalExpressions(List expressions) { validateOptionalList(expressions, () -> getValidator(ExpressionValidator.class), (o, v) -> o.accept(v, null)); diff --git a/src/main/java/net/sf/jsqlparser/util/validation/validator/AlterSequenceValidator.java b/src/main/java/net/sf/jsqlparser/util/validation/validator/AlterSequenceValidator.java index 80d9e73a12..3e3830c599 100644 --- a/src/main/java/net/sf/jsqlparser/util/validation/validator/AlterSequenceValidator.java +++ b/src/main/java/net/sf/jsqlparser/util/validation/validator/AlterSequenceValidator.java @@ -21,6 +21,9 @@ public class AlterSequenceValidator extends AbstractValidator { @Override public void validate(AlterSequence statement) { + if (statement.getSequence().getOwnership() != null) { + validateOptionalExpression(statement.getSequence().getOwnership().getColumn()); + } validateFeatureAndName(Feature.alterSequence, NamedObject.sequence, statement.getSequence().getFullyQualifiedName()); } diff --git a/src/main/java/net/sf/jsqlparser/util/validation/validator/AlterValidator.java b/src/main/java/net/sf/jsqlparser/util/validation/validator/AlterValidator.java index 4db652b960..7702630e0f 100644 --- a/src/main/java/net/sf/jsqlparser/util/validation/validator/AlterValidator.java +++ b/src/main/java/net/sf/jsqlparser/util/validation/validator/AlterValidator.java @@ -17,7 +17,10 @@ import net.sf.jsqlparser.statement.alter.AlterExpression; import net.sf.jsqlparser.statement.alter.AlterExpression.ColumnDataType; import net.sf.jsqlparser.statement.alter.AlterExpression.ColumnDropNotNull; +import net.sf.jsqlparser.statement.alter.AlterExpression.ColumnSetNotNull; import net.sf.jsqlparser.statement.alter.AlterOperation; +import net.sf.jsqlparser.statement.create.table.DefaultConstraint; +import net.sf.jsqlparser.util.TableDefinitionTraversal; import net.sf.jsqlparser.util.validation.ValidationCapability; import net.sf.jsqlparser.util.validation.ValidationUtil; import net.sf.jsqlparser.util.validation.metadata.NamedObject; @@ -37,6 +40,8 @@ public void validate(Alter alter) { } public void validate(Alter alter, AlterExpression e) { + TableDefinitionTraversal.visit(e, this::validateOptionalExpression, + this::validateOptionalFromItem); for (ValidationCapability c : getCapabilities()) { validateOptionalColumnName(c, e.getColumnOldName()); @@ -47,6 +52,11 @@ public void validate(Alter alter, AlterExpression e) { ColumnDropNotNull::getColumnName)); } + if (e.getColumnSetNotNullList() != null) { + validateOptionalColumnNames(c, ValidationUtil.map(e.getColumnSetNotNullList(), + ColumnSetNotNull::getColumnName)); + } + if (e.getColDataTypeList() != null) { boolean validateForExist = !EnumSet.of(AlterOperation.ADD).contains(e.getOperation()); @@ -72,7 +82,10 @@ public void validate(Alter alter, AlterExpression e) { validateOptionalColumnNames(c, e.getUkColumns(), NamedObject.uniqueConstraint); } - if (e.getIndex() != null) { + if (e.getIndex() instanceof DefaultConstraint) { + validateOptionalName(c, NamedObject.constraint, e.getIndex().getName(), null, false, + NamedObject.table); + } else if (e.getIndex() != null) { validateName(c, NamedObject.index, e.getIndex().getName()); if (e.getIndex().getColumns() != null) { validateOptionalColumnNames(c, diff --git a/src/main/java/net/sf/jsqlparser/util/validation/validator/AnalyzeValidator.java b/src/main/java/net/sf/jsqlparser/util/validation/validator/AnalyzeValidator.java index ac5de0c192..f2f3a1b584 100644 --- a/src/main/java/net/sf/jsqlparser/util/validation/validator/AnalyzeValidator.java +++ b/src/main/java/net/sf/jsqlparser/util/validation/validator/AnalyzeValidator.java @@ -19,7 +19,8 @@ public class AnalyzeValidator extends AbstractValidator { public void validate(Analyze analyze) { for (ValidationCapability c : getCapabilities()) { validateFeature(c, Feature.analyze); - validateName(c, NamedObject.table, analyze.getTable().getFullyQualifiedName(), true); + analyze.getTables().forEach(table -> validateName(c, NamedObject.table, + table.getFullyQualifiedName(), true)); } } } diff --git a/src/main/java/net/sf/jsqlparser/util/validation/validator/CommentValidator.java b/src/main/java/net/sf/jsqlparser/util/validation/validator/CommentValidator.java new file mode 100644 index 0000000000..b5ad37f8bb --- /dev/null +++ b/src/main/java/net/sf/jsqlparser/util/validation/validator/CommentValidator.java @@ -0,0 +1,53 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.util.validation.validator; + +import net.sf.jsqlparser.parser.feature.Feature; +import net.sf.jsqlparser.statement.comment.Comment; +import net.sf.jsqlparser.statement.comment.CommentTarget; +import net.sf.jsqlparser.util.validation.ValidationCapability; + +public class CommentValidator extends AbstractValidator { + @Override + public void validate(Comment comment) { + for (ValidationCapability capability : getCapabilities()) { + validateFeature(capability, Feature.comment); + validateOptionalFeature(capability, comment.getTable(), Feature.commentOnTable); + validateOptionalFeature(capability, comment.getColumn(), Feature.commentOnColumn); + validateOptionalFeature(capability, comment.getView(), Feature.commentOnView); + if (comment.getTarget() != null) { + validateFeature(capability, targetFeature(comment.getTarget().getKind())); + } + } + } + + private static Feature targetFeature(CommentTarget.Kind kind) { + switch (kind) { + case INDEX: + return Feature.commentOnIndex; + case SCHEMA: + return Feature.commentOnSchema; + case SEQUENCE: + return Feature.commentOnSequence; + case DOMAIN: + return Feature.commentOnDomain; + case TYPE: + return Feature.commentOnType; + case MATERIALIZED_VIEW: + return Feature.commentOnMaterializedView; + case FUNCTION: + return Feature.commentOnFunction; + case CONSTRAINT: + return Feature.commentOnConstraint; + default: + throw new IllegalArgumentException("Unknown COMMENT target: " + kind); + } + } +} diff --git a/src/main/java/net/sf/jsqlparser/util/validation/validator/CreateIndexValidator.java b/src/main/java/net/sf/jsqlparser/util/validation/validator/CreateIndexValidator.java index d613784975..2a6a5b447a 100644 --- a/src/main/java/net/sf/jsqlparser/util/validation/validator/CreateIndexValidator.java +++ b/src/main/java/net/sf/jsqlparser/util/validation/validator/CreateIndexValidator.java @@ -14,6 +14,7 @@ import net.sf.jsqlparser.parser.feature.Feature; import net.sf.jsqlparser.statement.create.index.CreateIndex; import net.sf.jsqlparser.statement.create.table.Index; +import net.sf.jsqlparser.util.TableDefinitionTraversal; import net.sf.jsqlparser.util.validation.ValidationCapability; import net.sf.jsqlparser.util.validation.metadata.NamedObject; @@ -38,6 +39,9 @@ public void validate(CreateIndex createIndex) { NamedObject.table); } } + TableDefinitionTraversal.visit(createIndex, this::validateOptionalExpression, table -> { + // Table and index names are validated above for each capability. + }); } } diff --git a/src/main/java/net/sf/jsqlparser/util/validation/validator/CreateSequenceValidator.java b/src/main/java/net/sf/jsqlparser/util/validation/validator/CreateSequenceValidator.java index f2b95d897a..b6a99a5d12 100644 --- a/src/main/java/net/sf/jsqlparser/util/validation/validator/CreateSequenceValidator.java +++ b/src/main/java/net/sf/jsqlparser/util/validation/validator/CreateSequenceValidator.java @@ -22,6 +22,9 @@ public class CreateSequenceValidator extends AbstractValidator { @Override public void validate(CreateSequence statement) { + if (statement.getSequence().getOwnership() != null) { + validateOptionalExpression(statement.getSequence().getOwnership().getColumn()); + } for (ValidationCapability c : getCapabilities()) { validateFeature(Feature.createSequence); validateName(c, NamedObject.sequence, statement.getSequence().getFullyQualifiedName(), diff --git a/src/main/java/net/sf/jsqlparser/util/validation/validator/CreateTableValidator.java b/src/main/java/net/sf/jsqlparser/util/validation/validator/CreateTableValidator.java index e4b7a3c83b..caead25b6b 100644 --- a/src/main/java/net/sf/jsqlparser/util/validation/validator/CreateTableValidator.java +++ b/src/main/java/net/sf/jsqlparser/util/validation/validator/CreateTableValidator.java @@ -12,6 +12,7 @@ import net.sf.jsqlparser.parser.feature.Feature; import net.sf.jsqlparser.statement.create.table.CreateTable; import net.sf.jsqlparser.statement.create.table.Index; +import net.sf.jsqlparser.util.TableDefinitionTraversal; import net.sf.jsqlparser.util.validation.ValidationCapability; import net.sf.jsqlparser.util.validation.metadata.NamedObject; @@ -43,6 +44,8 @@ public void validate(CreateTable createTable) { false); } + TableDefinitionTraversal.visit(createTable, this::validateOptionalExpression, + this::validateOptionalFromItem); if (createTable.getSelect() != null) { getValidator(StatementValidator.class).validate(createTable.getSelect()); } diff --git a/src/main/java/net/sf/jsqlparser/util/validation/validator/CreateViewValidator.java b/src/main/java/net/sf/jsqlparser/util/validation/validator/CreateViewValidator.java index 12c406f2cd..b7659f12aa 100644 --- a/src/main/java/net/sf/jsqlparser/util/validation/validator/CreateViewValidator.java +++ b/src/main/java/net/sf/jsqlparser/util/validation/validator/CreateViewValidator.java @@ -25,6 +25,7 @@ public class CreateViewValidator extends AbstractValidator { @Override public void validate(CreateView createView) { + createView.validateOptions(); for (ValidationCapability c : getCapabilities()) { validateFeature(c, Feature.createView); validateFeature(c, createView.isOrReplace(), Feature.createOrReplaceView); diff --git a/src/main/java/net/sf/jsqlparser/util/validation/validator/DeleteValidator.java b/src/main/java/net/sf/jsqlparser/util/validation/validator/DeleteValidator.java index 19588e3b7b..d030b9f309 100644 --- a/src/main/java/net/sf/jsqlparser/util/validation/validator/DeleteValidator.java +++ b/src/main/java/net/sf/jsqlparser/util/validation/validator/DeleteValidator.java @@ -11,6 +11,7 @@ import net.sf.jsqlparser.parser.feature.Feature; import net.sf.jsqlparser.statement.delete.Delete; +import net.sf.jsqlparser.statement.select.OptionHint; import net.sf.jsqlparser.util.validation.ValidationCapability; /** @@ -39,6 +40,7 @@ public void validate(Delete delete) { delete.getTables().forEach(t -> t.accept(v, null)); } + validateOptionalForPortionClause(delete.getForPortionClause()); validateOptionalExpression(delete.getWhere()); validateOptionalOrderByElements(delete.getOrderByElements()); @@ -48,6 +50,15 @@ public void validate(Delete delete) { getValidator(LimitValidator.class).validate(delete.getLimit()); } + if (delete.getOption() != null) { + for (OptionHint optionHint : delete.getOption().getOptionHints()) { + validateOptionalExpression(optionHint.getValue()); + if (optionHint.getParameters() != null) { + optionHint.getParameters().forEach(this::validateOptionalExpression); + } + } + } + if (delete.getReturningClause() != null) { delete.getReturningClause().forEach(c -> c.accept(v, null)); } diff --git a/src/main/java/net/sf/jsqlparser/util/validation/validator/DropValidator.java b/src/main/java/net/sf/jsqlparser/util/validation/validator/DropValidator.java index 04ea1cf8f9..42dc044a41 100644 --- a/src/main/java/net/sf/jsqlparser/util/validation/validator/DropValidator.java +++ b/src/main/java/net/sf/jsqlparser/util/validation/validator/DropValidator.java @@ -45,9 +45,12 @@ public void validate(Drop drop) { Feature.dropSequenceIfExists); } + if (drop.getTable() != null) { + validateName(NamedObject.table, drop.getTable().getFullyQualifiedName()); + } NamedObject named = NamedObject.forName(type); if (Arrays.asList(NamedObject.table, NamedObject.view).contains(named)) { - validateName(named, drop.getName().getFullyQualifiedName()); + drop.getNames().forEach(name -> validateName(named, name.getFullyQualifiedName())); } } diff --git a/src/main/java/net/sf/jsqlparser/util/validation/validator/ExecuteValidator.java b/src/main/java/net/sf/jsqlparser/util/validation/validator/ExecuteValidator.java index 3c91b91285..968bd13b15 100644 --- a/src/main/java/net/sf/jsqlparser/util/validation/validator/ExecuteValidator.java +++ b/src/main/java/net/sf/jsqlparser/util/validation/validator/ExecuteValidator.java @@ -25,6 +25,8 @@ public class ExecuteValidator extends AbstractValidator { public void validate(Execute execute) { for (ValidationCapability c : getCapabilities()) { validateFeature(c, Feature.execute); + validateFeature(c, ExecType.IMPLICIT.equals(execute.getExecType()), + Feature.oracleBlock); validateFeature(c, ExecType.EXECUTE.equals(execute.getExecType()), Feature.executeExecute); validateFeature(c, ExecType.EXEC.equals(execute.getExecType()), Feature.executeExec); diff --git a/src/main/java/net/sf/jsqlparser/util/validation/validator/ExpressionValidator.java b/src/main/java/net/sf/jsqlparser/util/validation/validator/ExpressionValidator.java index a18a21ab79..2e20fd69a1 100644 --- a/src/main/java/net/sf/jsqlparser/util/validation/validator/ExpressionValidator.java +++ b/src/main/java/net/sf/jsqlparser/util/validation/validator/ExpressionValidator.java @@ -9,6 +9,11 @@ */ package net.sf.jsqlparser.util.validation.validator; +import java.util.Set; +import java.util.Collections; +import net.sf.jsqlparser.statement.select.MatchRecognize; + +import java.util.Map; import net.sf.jsqlparser.expression.AllValue; import net.sf.jsqlparser.expression.AnalyticExpression; import net.sf.jsqlparser.expression.AnyComparisonExpression; @@ -19,6 +24,7 @@ import net.sf.jsqlparser.expression.CaseExpression; import net.sf.jsqlparser.expression.CastExpression; import net.sf.jsqlparser.expression.CollateExpression; +import net.sf.jsqlparser.expression.ColumnsExpression; import net.sf.jsqlparser.expression.ConnectByPriorOperator; import net.sf.jsqlparser.expression.ConnectByRootOperator; import net.sf.jsqlparser.expression.DateTimeLiteralExpression; @@ -39,12 +45,12 @@ import net.sf.jsqlparser.expression.JsonExpression; import net.sf.jsqlparser.expression.JsonFunction; import net.sf.jsqlparser.expression.JsonTableFunction; -import net.sf.jsqlparser.expression.XmlTableFunction; import net.sf.jsqlparser.expression.KeepExpression; import net.sf.jsqlparser.expression.KeyExpression; import net.sf.jsqlparser.expression.LambdaExpression; import net.sf.jsqlparser.expression.LongValue; import net.sf.jsqlparser.expression.LowExpression; +import net.sf.jsqlparser.expression.MapExpression; import net.sf.jsqlparser.expression.MySQLGroupConcat; import net.sf.jsqlparser.expression.NextValExpression; import net.sf.jsqlparser.expression.NotExpression; @@ -61,6 +67,7 @@ import net.sf.jsqlparser.expression.SignedExpression; import net.sf.jsqlparser.expression.StringValue; import net.sf.jsqlparser.expression.StructType; +import net.sf.jsqlparser.expression.TernaryExpression; import net.sf.jsqlparser.expression.TimeKeyExpression; import net.sf.jsqlparser.expression.TimeValue; import net.sf.jsqlparser.expression.TimestampValue; @@ -74,6 +81,7 @@ import net.sf.jsqlparser.expression.WindowOffset; import net.sf.jsqlparser.expression.WindowRange; import net.sf.jsqlparser.expression.XMLSerializeExpr; +import net.sf.jsqlparser.expression.XmlTableFunction; import net.sf.jsqlparser.expression.operators.arithmetic.Addition; import net.sf.jsqlparser.expression.operators.arithmetic.BitwiseAnd; import net.sf.jsqlparser.expression.operators.arithmetic.BitwiseLeftShift; @@ -104,6 +112,7 @@ import net.sf.jsqlparser.expression.operators.relational.GreaterThanEquals; import net.sf.jsqlparser.expression.operators.relational.InExpression; import net.sf.jsqlparser.expression.operators.relational.IncludesExpression; +import net.sf.jsqlparser.expression.operators.relational.Intersects; import net.sf.jsqlparser.expression.operators.relational.IsBooleanExpression; import net.sf.jsqlparser.expression.operators.relational.IsDistinctExpression; import net.sf.jsqlparser.expression.operators.relational.IsNullExpression; @@ -142,6 +151,8 @@ @SuppressWarnings({"PMD.CyclomaticComplexity"}) public class ExpressionValidator extends AbstractValidator implements ExpressionVisitor { + private Set rowPatternVariables = Collections.emptySet(); + @Override public Void visit(Addition addition, S context) { visitBinaryExpression(addition, " + "); @@ -529,13 +540,30 @@ public Void visit(ParenthesedSelect selectBody, S context) { return null; } + void validateMatchRecognizeExpressions(MatchRecognize matchRecognize) { + Set previous = rowPatternVariables; + try { + rowPatternVariables = matchRecognize.getPatternVariableNames(); + matchRecognize + .forEachPatternExpression( + expression -> validateOptionalExpression(expression, this)); + } finally { + rowPatternVariables = previous; + } + } + @Override public Void visit(Column tableColumn, S context) { if (tableColumn .getOldOracleJoinSyntax() != SupportsOldOracleJoinSyntax.NO_ORACLE_JOIN) { validateFeature(Feature.oracleOldJoinSyntax); } - validateName(NamedObject.column, tableColumn.getFullyQualifiedName()); + // A pattern qualifier is not a schema/table name. Resolving its columns + // requires binding the input relation, which metadata validation does not do. + if (tableColumn.getTable() == null || !rowPatternVariables.contains( + MatchRecognize.normalizeVariableName(tableColumn.getTable().getName()))) { + validateName(NamedObject.column, tableColumn.getFullyQualifiedName()); + } return null; } @@ -812,6 +840,12 @@ public Void visit(JsonOperator jsonExpr, S context) { return null; } + @Override + public Void visit(Intersects intersects, S context) { + visitBinaryExpression(intersects, " # "); + return null; + } + @Override public Void visit(UserVariable var, S context) { // nothing to validate @@ -913,6 +947,10 @@ public void visit(JsonOperator jsonExpr) { visit(jsonExpr, null); } + public void visit(Intersects intersects) { + visit(intersects, null); + } + public void visit(UserVariable var) { visit(var, null); } @@ -1007,6 +1045,15 @@ public Void visit(ArrayConstructor aThis, S context) { return null; } + @Override + public Void visit(MapExpression mapExpression, S context) { + for (Map.Entry entry : mapExpression.getEntries()) { + entry.getKey().accept(this, context); + entry.getValue().accept(this, context); + } + return null; + } + @Override public void validate(Expression expression) { expression.accept(this, null); @@ -1029,6 +1076,10 @@ public Void visit(TimezoneExpression a, S context) { @Override public Void visit(XMLSerializeExpr xml, S context) { + xml.validateOptions(); + for (Expression expression : xml.getExpressions()) { + expression.accept(this, context); + } return null; } @@ -1126,7 +1177,8 @@ public Void visit(GeometryDistance geometryDistance, S context) { @Override public Void visit(Select select, S context) { - return null; + return select.accept((net.sf.jsqlparser.statement.select.SelectVisitor) getValidator( + SelectValidator.class), context); } @Override @@ -1153,6 +1205,14 @@ public Void visit(RangeExpression rangeExpression, S context) { return null; } + @Override + public Void visit(TernaryExpression ternaryExpression, S context) { + ternaryExpression.getCondition().accept(this, context); + ternaryExpression.getThenExpression().accept(this, context); + ternaryExpression.getElseExpression().accept(this, context); + return null; + } + @Override public Void visit(TSQLLeftJoin tsqlLeftJoin, S context) { tsqlLeftJoin.getLeftExpression().accept(this, context); @@ -1183,6 +1243,16 @@ public Void visit(LambdaExpression lambdaExpression, S context) { return null; } + @Override + public Void visit(ColumnsExpression columnsExpression, S context) { + for (Expression expression : columnsExpression.getAllExpressions()) { + if (expression != null) { + expression.accept(this, context); + } + } + return null; + } + @Override public Void visit(HighExpression highExpression, S context) { highExpression.getExpression().accept(this, context); @@ -1241,6 +1311,10 @@ public void visit(ArrayConstructor aThis) { visit(aThis, null); } + public void visit(MapExpression mapExpression) { + visit(mapExpression, null); + } + public void visit(VariableAssignment a) { visit(a, null); @@ -1310,6 +1384,10 @@ public void visit(RangeExpression rangeExpression) { visit(rangeExpression, null); } + public void visit(TernaryExpression ternaryExpression) { + visit(ternaryExpression, null); + } + public void visit(TSQLLeftJoin tsqlLeftJoin) { visit(tsqlLeftJoin, null); } diff --git a/src/main/java/net/sf/jsqlparser/util/validation/validator/GrantValidator.java b/src/main/java/net/sf/jsqlparser/util/validation/validator/GrantValidator.java index ceb7bd9daa..c37f214ad1 100644 --- a/src/main/java/net/sf/jsqlparser/util/validation/validator/GrantValidator.java +++ b/src/main/java/net/sf/jsqlparser/util/validation/validator/GrantValidator.java @@ -26,11 +26,10 @@ public void validate(Grant grant) { if (isNotEmpty(grant.getUsers())) { grant.getUsers().forEach(u -> validateName(NamedObject.user, u)); } - if (grant.getRole() != null) { - validateName(NamedObject.role, grant.getRole()); - } + grant.getRoles().forEach(role -> validateName(NamedObject.role, role)); - // can't validate grant.getObjectName() - don't know the kind of this object. + grant.getClause().visit(this::validateOptionalFromItem, + this::validateOptionalExpression); } } diff --git a/src/main/java/net/sf/jsqlparser/util/validation/validator/GroupByValidator.java b/src/main/java/net/sf/jsqlparser/util/validation/validator/GroupByValidator.java index ee2d7155f8..0d5ed5fe96 100644 --- a/src/main/java/net/sf/jsqlparser/util/validation/validator/GroupByValidator.java +++ b/src/main/java/net/sf/jsqlparser/util/validation/validator/GroupByValidator.java @@ -31,6 +31,9 @@ public void validate(GroupByElement groupBy) { public Void visit(GroupByElement groupBy, S context) { for (ValidationCapability c : getCapabilities()) { validateFeature(c, Feature.selectGroupBy); + if (groupBy.hasGroupBySortDirections()) { + validateFeature(c, Feature.selectGroupByOrdering); + } if (isNotEmpty(groupBy.getGroupingSets())) { validateFeature(c, Feature.selectGroupByGroupingSets); } diff --git a/src/main/java/net/sf/jsqlparser/util/validation/validator/InsertBulkValidator.java b/src/main/java/net/sf/jsqlparser/util/validation/validator/InsertBulkValidator.java new file mode 100644 index 0000000000..dcf6a9af64 --- /dev/null +++ b/src/main/java/net/sf/jsqlparser/util/validation/validator/InsertBulkValidator.java @@ -0,0 +1,31 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.util.validation.validator; + +import java.util.stream.Collectors; +import net.sf.jsqlparser.parser.feature.Feature; +import net.sf.jsqlparser.statement.insert.InsertBulk; +import net.sf.jsqlparser.statement.create.table.ColumnDefinition; +import net.sf.jsqlparser.util.validation.ValidationCapability; +import net.sf.jsqlparser.util.validation.metadata.NamedObject; + +public class InsertBulkValidator extends AbstractValidator { + @Override + public void validate(InsertBulk statement) { + validateFeature(Feature.insertBulk); + validateOptionalFromItem(statement.getTable()); + for (ValidationCapability capability : getCapabilities()) { + validateOptionalColumnNames(capability, statement.getColumns().stream() + .map(ColumnDefinition::getColumnName).collect(Collectors.toList()), + NamedObject.table); + } + statement.visitExpressions(this::validateOptionalExpression); + } +} diff --git a/src/main/java/net/sf/jsqlparser/util/validation/validator/InsertValidator.java b/src/main/java/net/sf/jsqlparser/util/validation/validator/InsertValidator.java index 99f6ad3ea2..717e10bf0d 100644 --- a/src/main/java/net/sf/jsqlparser/util/validation/validator/InsertValidator.java +++ b/src/main/java/net/sf/jsqlparser/util/validation/validator/InsertValidator.java @@ -94,6 +94,10 @@ public void validate(Insert insert) { } } + if (insert.getConflictTarget() != null) { + insert.getConflictTarget().accept(getValidator(ExpressionValidator.class), null); + } + if (insert.getReturningClause() != null) { SelectValidator v = getValidator(SelectValidator.class); insert.getReturningClause().forEach(c -> c.accept(v, null)); diff --git a/src/main/java/net/sf/jsqlparser/util/validation/validator/MatchRecognizeValidator.java b/src/main/java/net/sf/jsqlparser/util/validation/validator/MatchRecognizeValidator.java new file mode 100644 index 0000000000..051ca5dba3 --- /dev/null +++ b/src/main/java/net/sf/jsqlparser/util/validation/validator/MatchRecognizeValidator.java @@ -0,0 +1,245 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.util.validation.validator; + +import java.math.BigInteger; +import java.util.HashSet; +import java.util.Locale; +import java.util.Set; +import net.sf.jsqlparser.expression.Expression; +import net.sf.jsqlparser.expression.ExpressionVisitorAdapter; +import net.sf.jsqlparser.expression.JdbcParameter; +import net.sf.jsqlparser.expression.LongValue; +import net.sf.jsqlparser.expression.RowPatternFunction; +import net.sf.jsqlparser.expression.UserVariable; +import net.sf.jsqlparser.parser.AbstractJSqlParser.Dialect; +import net.sf.jsqlparser.parser.feature.Feature; +import net.sf.jsqlparser.schema.MultiPartName; +import net.sf.jsqlparser.statement.select.MatchRecognize; +import net.sf.jsqlparser.statement.select.RowPattern; +import net.sf.jsqlparser.statement.select.RowPatternVisitorAdapter; +import net.sf.jsqlparser.util.validation.ValidationCapability; +import net.sf.jsqlparser.util.validation.ValidationException; + +/** + * Static dialect checks; schema binding, function eligibility and runtime checks remain with the + * DB. + */ +public class MatchRecognizeValidator extends AbstractValidator { + @Override + public void validate(MatchRecognize match) { + String dialect = context().getAsString(Feature.dialect); + boolean bigQuery = Dialect.BIGQUERY.name().equals(dialect); + boolean oracle = Dialect.ORACLE.name().equals(dialect); + boolean snowflake = Dialect.SNOWFLAKE.name().equals(dialect); + if (!bigQuery && !oracle && !snowflake) { + return; + } + validateDialectOptions(match, bigQuery, oracle); + validateRowOutput(match, oracle); + Set used = validateVariables(match, bigQuery, oracle); + validateTargets(match, used, bigQuery); + match.getPattern().accept(new PatternValidator(match, bigQuery, oracle), null); + match.getMeasures() + .forEach(measure -> checkFunctions(measure.getExpression(), bigQuery, false)); + match.getDefinitions() + .forEach(definition -> checkFunctions(definition.getExpression(), bigQuery, true)); + } + + private void validateDialectOptions(MatchRecognize match, boolean bigQuery, boolean oracle) { + if (bigQuery) { + if (match.getOrderByElements().isEmpty() || match.getMeasures().isEmpty()) { + error("BigQuery MATCH_RECOGNIZE requires ORDER BY and MEASURES"); + } + if (match.getRowsPerMatch() != null || match.getSkipVariable() != null) { + error("BigQuery does not support ROWS PER MATCH or SKIP TO a variable"); + } + } else if (match.getOptions() != null) { + error("MATCH_RECOGNIZE OPTIONS is specific to BigQuery"); + } + if (!oracle && !match.getSubsets().isEmpty()) { + error("SUBSET is specific to Oracle"); + } + } + + private void validateRowOutput(MatchRecognize match, boolean oracle) { + if (match.getEmptyMatchMode() != null + && match.getRowsPerMatch() != MatchRecognize.RowsPerMatch.ALL) { + error("Empty match options require ALL ROWS PER MATCH"); + } + match.getMeasures().forEach(measure -> { + if (measure.getAlias() == null || oracle && !measure.getAlias().isUseAs()) { + error("A measure requires an alias (with AS in Oracle)"); + } + }); + + } + + private Set validateVariables(MatchRecognize match, boolean bigQuery, boolean oracle) { + Set used = new HashSet<>(); + match.getPattern().accept(new RowPatternVisitorAdapter() { + @Override + public Void visit(RowPattern.Variable variable, S context) { + used.add(normalize(variable.getName(), bigQuery)); + return null; + } + }, null); + Set defined = new HashSet<>(); + for (MatchRecognize.Definition definition : match.getDefinitions()) { + String name = normalize(definition.getName(), bigQuery); + if (!defined.add(name)) { + error("Duplicate row pattern variable: " + definition.getName()); + } + if ((bigQuery || oracle) && !used.contains(name)) { + error("Unused row pattern variable: " + definition.getName()); + } + } + if (bigQuery) { + for (String name : used) { + if (!defined.contains(name)) { + error("Undefined row pattern variable: " + name); + } + } + } + return used; + } + + private void validateTargets(MatchRecognize match, Set used, boolean bigQuery) { + Set subsetNames = new HashSet<>(); + for (MatchRecognize.Subset subset : match.getSubsets()) { + String name = normalize(subset.getName(), false); + if (used.contains(name) || !subsetNames.add(name)) { + error("Duplicate SUBSET variable: " + subset.getName()); + } + for (String variable : subset.getVariables()) { + if (!used.contains(normalize(variable, false))) { + error("SUBSET requires a primary pattern variable: " + variable); + } + } + } + if (match.getSkipVariable() != null + && !used.contains(normalize(match.getSkipVariable(), bigQuery)) + && !subsetNames.contains(normalize(match.getSkipVariable(), false))) { + error("Unknown AFTER MATCH SKIP variable: " + match.getSkipVariable()); + } + } + + private class PatternValidator extends RowPatternVisitorAdapter { + private final MatchRecognize match; + private final boolean bigQuery; + private final boolean oracle; + + private PatternValidator(MatchRecognize match, boolean bigQuery, boolean oracle) { + this.match = match; + this.bigQuery = bigQuery; + this.oracle = oracle; + } + + @Override + public Void visit(RowPattern.Quantified quantified, S context) { + checkBound(quantified.getLowerBound(), bigQuery); + checkBound(quantified.getUpperBound(), bigQuery); + if (quantified.getLowerBound() instanceof LongValue + && quantified.getUpperBound() instanceof LongValue + && ((LongValue) quantified.getLowerBound()).getBigIntegerValue() + .compareTo(((LongValue) quantified.getUpperBound()) + .getBigIntegerValue()) > 0) { + error("Row pattern upper bound is smaller than its lower bound"); + } + if (bigQuery && quantified.getType() == RowPattern.Quantified.Type.EXACT + && quantified.isReluctant()) { + error("BigQuery does not support reluctant fixed quantifiers"); + } + if (oracle) { + Expression maximum = quantified.getType() == RowPattern.Quantified.Type.EXACT + ? quantified.getLowerBound() + : quantified.getUpperBound(); + if (maximum instanceof LongValue + && ((LongValue) maximum).getBigIntegerValue().signum() == 0) { + error("Oracle row pattern quantifier maximum must be positive"); + } + } + return super.visit(quantified, context); + } + + @Override + public Void visit(RowPattern.Operation operation, S context) { + if (oracle && operation.getType() == RowPattern.Operation.Type.ALTERNATION + && operation.getPatterns().stream() + .anyMatch(RowPattern.Empty.class::isInstance)) { + error("Oracle requires a pattern on each side of an alternative"); + } + return super.visit(operation, context); + } + + @Override + public Void visit(RowPattern.Exclusion exclusion, S context) { + if (bigQuery) { + error("BigQuery does not support row pattern exclusion"); + } + if (match.getEmptyMatchMode() == MatchRecognize.EmptyMatchMode.WITH_UNMATCHED) { + error("Row pattern exclusion cannot be combined with WITH UNMATCHED ROWS"); + } + return super.visit(exclusion, context); + } + + @Override + public Void visit(RowPattern.Permute permute, S context) { + if (bigQuery) { + error("BigQuery does not support PERMUTE"); + } + return super.visit(permute, context); + } + } + + private void checkFunctions(Expression expression, boolean bigQuery, boolean definition) { + expression.accept(new ExpressionVisitorAdapter() { + @Override + public Void visit(RowPatternFunction function, S context) { + if (bigQuery) { + error("BigQuery does not support RUNNING or FINAL function prefixes"); + } + if (definition && function + .getEvaluationMode() == RowPatternFunction.EvaluationMode.FINAL) { + error("FINAL is only allowed in MATCH_RECOGNIZE MEASURES"); + } + return function.getFunction().accept(this, context); + } + }, null); + } + + private static String normalize(String name, boolean bigQuery) { + String unquoted = MultiPartName.unquote(name); + return bigQuery || name.equals(unquoted) ? unquoted.toUpperCase(Locale.ROOT) : unquoted; + } + + private void checkBound(Expression bound, boolean bigQuery) { + if (bound == null) { + return; + } + if (bound instanceof LongValue) { + BigInteger value = ((LongValue) bound).getBigIntegerValue(); + if (value.signum() < 0 || bigQuery && value.compareTo(BigInteger.valueOf(10000)) > 0) { + error(bigQuery ? "BigQuery row pattern bounds must be between 0 and 10000" + : "Row pattern bounds must be nonnegative"); + } + } else if (!bigQuery || !(bound instanceof JdbcParameter) + && !(bound instanceof UserVariable && !((UserVariable) bound).isDoubleAdd())) { + error(bigQuery ? "A row pattern bound must be an integer literal or query parameter" + : "A row pattern bound must be an integer literal"); + } + } + + private void error(String message) { + for (ValidationCapability capability : getCapabilities()) { + putError(capability, new ValidationException(message)); + } + } +} diff --git a/src/main/java/net/sf/jsqlparser/util/validation/validator/MergeValidator.java b/src/main/java/net/sf/jsqlparser/util/validation/validator/MergeValidator.java index e82ebc91dc..ab5dfa9b1d 100644 --- a/src/main/java/net/sf/jsqlparser/util/validation/validator/MergeValidator.java +++ b/src/main/java/net/sf/jsqlparser/util/validation/validator/MergeValidator.java @@ -11,7 +11,7 @@ import net.sf.jsqlparser.parser.feature.Feature; import net.sf.jsqlparser.statement.merge.*; -import net.sf.jsqlparser.statement.update.UpdateSet; +import net.sf.jsqlparser.statement.select.OptionHint; import net.sf.jsqlparser.util.validation.ValidationCapability; /** @@ -26,11 +26,23 @@ public void validate(Merge merge) { for (ValidationCapability c : getCapabilities()) { validateFeature(c, Feature.merge); } + if (merge.getReturningClause() != null) { + SelectValidator validator = getValidator(SelectValidator.class); + merge.getReturningClause().forEach(item -> item.accept(validator, null)); + } validateOptionalExpression(merge.getOnCondition()); if (merge.getOperations() != null) { merge.getOperations().forEach(operation -> operation.accept(this, null)); } validateOptionalFromItems(merge.getFromItem()); + if (merge.getOption() != null) { + for (OptionHint optionHint : merge.getOption().getOptionHints()) { + validateOptionalExpression(optionHint.getValue()); + if (optionHint.getParameters() != null) { + optionHint.getParameters().forEach(this::validateOptionalExpression); + } + } + } } @Override @@ -46,10 +58,7 @@ public void visit(MergeDelete mergeDelete) { @Override public Void visit(MergeUpdate mergeUpdate, S context) { validateOptionalExpression(mergeUpdate.getAndPredicate()); - for (UpdateSet updateSet : mergeUpdate.getUpdateSets()) { - validateOptionalExpressions(updateSet.getColumns()); - validateOptionalExpressions(updateSet.getValues()); - } + validateOptionalUpdateSets(mergeUpdate.getUpdateSets()); validateOptionalExpression(mergeUpdate.getDeleteWhereCondition()); validateOptionalExpression(mergeUpdate.getWhereCondition()); return null; diff --git a/src/main/java/net/sf/jsqlparser/util/validation/validator/OrderByValidator.java b/src/main/java/net/sf/jsqlparser/util/validation/validator/OrderByValidator.java index 858702ddae..8e04d7df95 100644 --- a/src/main/java/net/sf/jsqlparser/util/validation/validator/OrderByValidator.java +++ b/src/main/java/net/sf/jsqlparser/util/validation/validator/OrderByValidator.java @@ -32,6 +32,12 @@ public Void visit(OrderByElement orderBy, S context) { validateOptionalFeature(c, orderBy.getNullOrdering(), Feature.orderByNullOrdering); } getValidator(ExpressionValidator.class).validate(orderBy.getExpression()); + if (orderBy.getWithFill() != null) { + validateOptionalExpression(orderBy.getWithFill().getFrom()); + validateOptionalExpression(orderBy.getWithFill().getTo()); + validateOptionalExpression(orderBy.getWithFill().getStep()); + validateOptionalExpression(orderBy.getWithFill().getStaleness()); + } return null; } diff --git a/src/main/java/net/sf/jsqlparser/util/validation/validator/SelectValidator.java b/src/main/java/net/sf/jsqlparser/util/validation/validator/SelectValidator.java index 5cab04030e..8b593f14b4 100644 --- a/src/main/java/net/sf/jsqlparser/util/validation/validator/SelectValidator.java +++ b/src/main/java/net/sf/jsqlparser/util/validation/validator/SelectValidator.java @@ -9,6 +9,8 @@ */ package net.sf.jsqlparser.util.validation.validator; +import net.sf.jsqlparser.statement.select.MatchRecognize; + import java.util.List; import net.sf.jsqlparser.expression.Expression; import net.sf.jsqlparser.expression.MySQLIndexHint; @@ -21,15 +23,20 @@ import net.sf.jsqlparser.statement.select.Fetch; import net.sf.jsqlparser.statement.select.ForMode; import net.sf.jsqlparser.statement.select.FromItemVisitor; +import net.sf.jsqlparser.statement.select.InterpolateElement; import net.sf.jsqlparser.statement.select.IntersectOp; import net.sf.jsqlparser.statement.select.Join; import net.sf.jsqlparser.statement.select.LateralSubSelect; import net.sf.jsqlparser.statement.select.MinusOp; import net.sf.jsqlparser.statement.select.MySqlSelectIntoClause; import net.sf.jsqlparser.statement.select.Offset; +import net.sf.jsqlparser.statement.select.OptionClause; +import net.sf.jsqlparser.statement.select.OptionHint; import net.sf.jsqlparser.statement.select.ParenthesedFromItem; import net.sf.jsqlparser.statement.select.ParenthesedSelect; import net.sf.jsqlparser.statement.select.Pivot; +import net.sf.jsqlparser.statement.select.PivotQuery; +import net.sf.jsqlparser.statement.select.UnPivotQuery; import net.sf.jsqlparser.statement.select.PivotVisitor; import net.sf.jsqlparser.statement.select.PivotXml; import net.sf.jsqlparser.statement.select.PlainSelect; @@ -55,6 +62,22 @@ public class SelectValidator extends AbstractValidator> PivotVisitor { @SuppressWarnings({"PMD.CyclomaticComplexity"}) + @Override + public Void visit(MatchRecognize matchRecognize, S context) { + validateFeature(Feature.matchRecognize); + if (matchRecognize.getOptions() != null) { + validateFeature(Feature.matchRecognizeOptions); + } + validateOptionalFromItem(matchRecognize.getInput()); + validateOptional(matchRecognize.getPivot(), p -> p.accept(this, context)); + validateOptional(matchRecognize.getUnPivot(), p -> p.accept(this, context)); + validateOptionalExpressions(matchRecognize.getPartitionBy()); + validateOptionalOrderByElements(matchRecognize.getOrderByElements()); + getValidator(MatchRecognizeValidator.class).validate(matchRecognize); + getValidator(ExpressionValidator.class).validateMatchRecognizeExpressions(matchRecognize); + return null; + } + @Override public Void visit(PlainSelect plainSelect, S context) { if (isNotEmpty(plainSelect.getWithItemsList())) { @@ -136,6 +159,8 @@ public Void visit(PlainSelect plainSelect, S context) { validateOptionalExpression(plainSelect.getHaving()); validateOptionalOrderByElements(plainSelect.getOrderByElements()); + validateOptionalInterpolate(plainSelect.getInterpolate()); + validateOptionalOption(plainSelect.getOption()); if (plainSelect.getLimit() != null) { getValidator(LimitValidator.class).validate(plainSelect.getLimit()); @@ -154,6 +179,55 @@ public Void visit(PlainSelect plainSelect, S context) { return null; } + @Override + public Void visit(UnPivotQuery unPivotQuery, S context) { + validateFeature(Feature.pivot); + validateOptionalFromItem(unPivotQuery.getFromItem()); + validateOptionalExpressions(unPivotQuery.getOnExpressions()); + return null; + } + + @Override + public Void visit(PivotQuery pivotQuery, S context) { + if (isNotEmpty(pivotQuery.getWithItemsList())) { + pivotQuery.getWithItemsList() + .forEach(withItem -> withItem.accept((SelectVisitor) this, context)); + } + + validateFeature(Feature.pivot); + validateOptionalFromItem(pivotQuery.getFromItem()); + validateOptionalExpressions(pivotQuery.getOnExpressions()); + if (isNotEmpty(pivotQuery.getUsingItems())) { + pivotQuery.getUsingItems().forEach(item -> item.accept(this, context)); + } + validateOptionalExpressions(pivotQuery.getGroupByExpressions()); + validateOptionalOrderByElements(pivotQuery.getOrderByElements()); + validateOptionalInterpolate(pivotQuery.getInterpolate()); + validateOptionalOption(pivotQuery.getOption()); + + if (pivotQuery.getLimit() != null) { + getValidator(LimitValidator.class).validate(pivotQuery.getLimit()); + } + if (pivotQuery.getOffset() != null) { + validateOffset(pivotQuery.getOffset()); + } + if (pivotQuery.getFetch() != null) { + validateFetch(pivotQuery.getFetch()); + } + return null; + } + + private void validateOptionalOption(OptionClause option) { + if (option != null) { + for (OptionHint optionHint : option.getOptionHints()) { + validateOptionalExpression(optionHint.getValue()); + if (optionHint.getParameters() != null) { + optionHint.getParameters().forEach(this::validateOptionalExpression); + } + } + } + } + private void validateMySqlSelectIntoClause(MySqlSelectIntoClause mySqlSelectIntoClause) { validateOptionalExpression(mySqlSelectIntoClause.getFileName()); validateOptionalExpression(mySqlSelectIntoClause.getFieldsTerminatedBy()); @@ -163,6 +237,15 @@ private void validateMySqlSelectIntoClause(MySqlSelectIntoClause mySqlSelectInto validateOptionalExpression(mySqlSelectIntoClause.getLinesTerminatedBy()); } + private void validateOptionalInterpolate(List interpolate) { + if (interpolate != null) { + for (InterpolateElement element : interpolate) { + validateOptionalExpression(element.getColumn()); + validateOptionalExpression(element.getExpression()); + } + } + } + @Override public Void visit(SelectItem selectExpressionItem, S context) { selectExpressionItem.getExpression().accept(getValidator(ExpressionValidator.class), @@ -183,8 +266,12 @@ public Void visit(ParenthesedSelect selectBody, S context) { @Override public Void visit(Table table, S context) { - validateNameWithAlias(NamedObject.table, table.getFullyQualifiedName(), - ValidationUtil.getAlias(table.getAlias())); + if (table.isTableVariable()) { + validateFeature(Feature.tableVariable); + } else { + validateNameWithAlias(NamedObject.table, table.getFullyQualifiedName(), + ValidationUtil.getAlias(table.getAlias())); + } validateOptional(table.getPivot(), p -> p.accept(this, context)); validateOptional(table.getUnPivot(), up -> up.accept(this, context)); @@ -268,8 +355,10 @@ public void validateOptionalJoin(Join join) { validateFeature(c, join.isOuter(), Feature.joinOuter); validateFeature(c, join.isInner(), Feature.joinInner); validateFeature(c, join.isSemi(), Feature.joinSemi); + validateFeature(c, join.isAnti(), Feature.joinAnti); validateFeature(c, join.isStraight(), Feature.joinStraight); validateFeature(c, join.isApply(), Feature.joinApply); + validateFeature(c, join.isAsOf(), Feature.joinAsOf); validateFeature(c, join.isWindowJoin(), Feature.joinWindow); validateOptionalFeature(c, join.getUsingColumns(), Feature.joinUsingColumns); } @@ -308,6 +397,8 @@ public Void visit(SetOperationList setOperation, S context) { } validateOptionalOrderByElements(setOperation.getOrderByElements()); + validateOptionalInterpolate(setOperation.getInterpolate()); + validateOptionalOption(setOperation.getOption()); if (setOperation.getLimit() != null) { getValidator(LimitValidator.class).validate(setOperation.getLimit()); @@ -332,7 +423,13 @@ public Void visit(WithItem withItem, S context) { if (isNotEmpty(withItem.getWithItemList())) { withItem.getWithItemList().forEach(wi -> wi.accept(this, context)); } - withItem.getSelect().accept((SelectVisitor) this, context); + if (withItem.getCycleClause() != null) { + withItem.getCycleClause().accept(getValidator(ExpressionValidator.class), context); + } + if (withItem.getExpression() != null) { + withItem.getExpression().accept(getValidator(ExpressionValidator.class), context); + } + withItem.accept(getValidator(StatementValidator.class), context); return null; } @@ -393,6 +490,15 @@ public void visit(PlainSelect plainSelect) { visit(plainSelect, null); } + public void visit(PivotQuery pivotQuery) { + visit(pivotQuery, null); + } + + @Override + public void visit(UnPivotQuery unPivotQuery) { + visit(unPivotQuery, null); + } + public void visit(SelectItem selectExpressionItem) { visit(selectExpressionItem, null); } diff --git a/src/main/java/net/sf/jsqlparser/util/validation/validator/SetIdentityInsertValidator.java b/src/main/java/net/sf/jsqlparser/util/validation/validator/SetIdentityInsertValidator.java new file mode 100644 index 0000000000..9bb0b49537 --- /dev/null +++ b/src/main/java/net/sf/jsqlparser/util/validation/validator/SetIdentityInsertValidator.java @@ -0,0 +1,22 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.util.validation.validator; + +import net.sf.jsqlparser.parser.feature.Feature; +import net.sf.jsqlparser.statement.SetIdentityInsertStatement; +import net.sf.jsqlparser.util.validation.metadata.NamedObject; + +public class SetIdentityInsertValidator extends AbstractValidator { + @Override + public void validate(SetIdentityInsertStatement statement) { + validateFeatureAndName(Feature.setIdentityInsert, NamedObject.table, + statement.getTable().getFullyQualifiedName()); + } +} diff --git a/src/main/java/net/sf/jsqlparser/util/validation/validator/SetStatementValidator.java b/src/main/java/net/sf/jsqlparser/util/validation/validator/SetStatementValidator.java index 9e66b47be0..ee2e7b6c3e 100644 --- a/src/main/java/net/sf/jsqlparser/util/validation/validator/SetStatementValidator.java +++ b/src/main/java/net/sf/jsqlparser/util/validation/validator/SetStatementValidator.java @@ -23,6 +23,9 @@ public class SetStatementValidator extends AbstractValidator { public void validate(SetStatement set) { for (ValidationCapability c : getCapabilities()) { validateFeature(c, Feature.set); + if (set.getOnOffOptions() != null) { + validateFeature(c, Feature.sqlServerSetOptions); + } } for (int i = 0; i < set.getCount(); i++) { validateOptionalExpressions(set.getExpressions(i)); diff --git a/src/main/java/net/sf/jsqlparser/util/validation/validator/StatementValidator.java b/src/main/java/net/sf/jsqlparser/util/validation/validator/StatementValidator.java index 9e073a227c..1567a975eb 100644 --- a/src/main/java/net/sf/jsqlparser/util/validation/validator/StatementValidator.java +++ b/src/main/java/net/sf/jsqlparser/util/validation/validator/StatementValidator.java @@ -9,10 +9,33 @@ */ package net.sf.jsqlparser.util.validation.validator; +import net.sf.jsqlparser.statement.oracle.OracleBlock; +import net.sf.jsqlparser.statement.oracle.OracleAssignment; +import net.sf.jsqlparser.statement.oracle.OracleNullStatement; + +import net.sf.jsqlparser.statement.role.CreateRole; +import net.sf.jsqlparser.statement.role.AlterRole; +import net.sf.jsqlparser.statement.role.RoleOption; +import net.sf.jsqlparser.statement.grant.Revoke; +import net.sf.jsqlparser.statement.grant.AlterDefaultPrivileges; +import net.sf.jsqlparser.schema.Table; + +import net.sf.jsqlparser.statement.create.type.CreateType; +import net.sf.jsqlparser.statement.alter.AlterType; +import net.sf.jsqlparser.statement.create.domain.CreateDomain; +import net.sf.jsqlparser.statement.alter.AlterDomain; +import net.sf.jsqlparser.statement.create.extension.CreateExtension; +import net.sf.jsqlparser.statement.alter.AlterExtension; +import net.sf.jsqlparser.statement.create.publication.CreatePublication; +import net.sf.jsqlparser.statement.alter.AlterPublication; +import net.sf.jsqlparser.statement.create.subscription.CreateSubscription; +import net.sf.jsqlparser.statement.alter.AlterSubscription; + import net.sf.jsqlparser.parser.feature.Feature; import net.sf.jsqlparser.statement.Block; import net.sf.jsqlparser.statement.Commit; import net.sf.jsqlparser.statement.CreateFunctionalStatement; +import net.sf.jsqlparser.statement.DoStatement; import net.sf.jsqlparser.statement.DeclareStatement; import net.sf.jsqlparser.statement.DescribeStatement; import net.sf.jsqlparser.statement.ExplainStatement; @@ -30,6 +53,7 @@ import net.sf.jsqlparser.statement.Statements; import net.sf.jsqlparser.statement.UnsupportedStatement; import net.sf.jsqlparser.statement.UseStatement; +import net.sf.jsqlparser.statement.SetIdentityInsertStatement; import net.sf.jsqlparser.statement.alter.Alter; import net.sf.jsqlparser.statement.alter.AlterSession; import net.sf.jsqlparser.statement.alter.AlterSystemStatement; @@ -37,6 +61,9 @@ import net.sf.jsqlparser.statement.alter.sequence.AlterSequence; import net.sf.jsqlparser.statement.analyze.Analyze; import net.sf.jsqlparser.statement.comment.Comment; +import net.sf.jsqlparser.statement.create.database.CreateDatabase; +import net.sf.jsqlparser.statement.create.event.AlterEvent; +import net.sf.jsqlparser.statement.create.event.CreateEvent; import net.sf.jsqlparser.statement.create.function.CreateFunction; import net.sf.jsqlparser.statement.create.index.CreateIndex; import net.sf.jsqlparser.statement.create.policy.CreatePolicy; @@ -45,6 +72,8 @@ import net.sf.jsqlparser.statement.create.sequence.CreateSequence; import net.sf.jsqlparser.statement.create.synonym.CreateSynonym; import net.sf.jsqlparser.statement.create.table.CreateTable; +import net.sf.jsqlparser.statement.create.trigger.CreateTrigger; +import net.sf.jsqlparser.statement.create.user.CreateUser; import net.sf.jsqlparser.statement.create.view.AlterView; import net.sf.jsqlparser.statement.create.view.CreateView; import net.sf.jsqlparser.statement.delete.Delete; @@ -55,6 +84,7 @@ import net.sf.jsqlparser.statement.grant.Grant; import net.sf.jsqlparser.statement.imprt.Import; import net.sf.jsqlparser.statement.insert.Insert; +import net.sf.jsqlparser.statement.insert.InsertBulk; import net.sf.jsqlparser.statement.insert.ParenthesedInsert; import net.sf.jsqlparser.statement.lock.LockStatement; import net.sf.jsqlparser.statement.merge.Merge; @@ -67,8 +97,21 @@ import net.sf.jsqlparser.statement.update.ParenthesedUpdate; import net.sf.jsqlparser.statement.update.Update; import net.sf.jsqlparser.statement.upsert.Upsert; -import net.sf.jsqlparser.util.validation.ValidationCapability; import net.sf.jsqlparser.util.validation.metadata.NamedObject; +import net.sf.jsqlparser.statement.PragmaStatement; +import net.sf.jsqlparser.statement.ExtensionStatement; +import net.sf.jsqlparser.statement.AttachStatement; +import net.sf.jsqlparser.statement.DetachStatement; +import net.sf.jsqlparser.statement.ConnectStatement; +import net.sf.jsqlparser.statement.DisconnectStatement; +import net.sf.jsqlparser.statement.PrepareStatement; +import net.sf.jsqlparser.statement.DeallocateStatement; +import net.sf.jsqlparser.statement.CopyStatement; +import net.sf.jsqlparser.statement.create.macro.CreateMacro; +import net.sf.jsqlparser.statement.create.extension.CreateExtensionRepository; +import net.sf.jsqlparser.statement.AssertStatement; +import net.sf.jsqlparser.statement.export.ExportDataStatement; +import net.sf.jsqlparser.statement.load.LoadDataStatement; /** * @author gitmotte @@ -87,6 +130,19 @@ public Void visit(CreateTable createTable, S context) { return null; } + @Override + public Void visit(CreateTrigger createTrigger, S context) { + if (createTrigger.getRoutine() != null) { + validateFeature(Feature.createTrigger); + } + createTrigger.visit(this::validateOptionalFromItem, + this::validateOptionalExpression); + if (createTrigger.getBody() != null) { + createTrigger.getBody().accept(this, context); + } + return null; + } + @Override public Void visit(CreateView createView, S context) { getValidator(CreateViewValidator.class).validate(createView); @@ -172,6 +228,14 @@ public Void visit(Alter alter, S context) { return null; } + @Override + public Void visit(AlterEvent alterEvent, S context) { + if (alterEvent.getBody() != null) { + alterEvent.getBody().accept(this, context); + } + return null; + } + @Override public Void visit(Statements statements, S context) { statements.forEach(s -> s.accept(this, context)); @@ -184,6 +248,18 @@ public Void visit(Execute execute, S context) { return null; } + @Override + public Void visit(DoStatement statement, S context) { + validateFeature(Feature.doStatement); + return null; + } + + @Override + public Void visit(InsertBulk statement, S context) { + getValidator(InsertBulkValidator.class).validate(statement); + return null; + } + @Override public Void visit(SetStatement set, S context) { getValidator(SetStatementValidator.class).validate(set); @@ -214,6 +290,12 @@ public Void visit(Upsert upsert, S context) { return null; } + @Override + public Void visit(SetIdentityInsertStatement statement, S context) { + getValidator(SetIdentityInsertValidator.class).validate(statement); + return null; + } + @Override public Void visit(UseStatement use, S context) { getValidator(UseStatementValidator.class).validate(use); @@ -253,12 +335,7 @@ public Void visit(Block block, S context) { @Override public Void visit(Comment comment, S context) { - for (ValidationCapability c : getCapabilities()) { - validateFeature(c, Feature.comment); - validateOptionalFeature(c, comment.getTable(), Feature.commentOnTable); - validateOptionalFeature(c, comment.getColumn(), Feature.commentOnColumn); - validateOptionalFeature(c, comment.getView(), Feature.commentOnView); - } + getValidator(CommentValidator.class).validate(comment); return null; } @@ -299,6 +376,26 @@ public Void visit(CreateSchema aThis, S context) { return null; } + @Override + public Void visit(CreateDatabase aThis, S context) { + validateFeatureAndName(Feature.createDatabase, NamedObject.database, + aThis.getDatabaseName()); + return null; + } + + @Override + public Void visit(CreateUser createUser, S context) { + return null; + } + + @Override + public Void visit(CreateEvent createEvent, S context) { + if (createEvent.getBody() != null) { + createEvent.getBody().accept(this, context); + } + return null; + } + @Override public Void visit(CreateSequence createSequence, S context) { getValidator(CreateSequenceValidator.class).validate(createSequence); @@ -314,10 +411,27 @@ public Void visit(AlterSequence alterSequence, S context) { @Override public Void visit(CreateFunctionalStatement createFunctionalStatement, S context) { validateFeature(Feature.functionalStatement); + if (createFunctionalStatement + .getOperation() == CreateFunctionalStatement.Operation.CREATE_OR_ALTER) { + validateFeature(Feature.createOrAlterRoutine); + } + if (createFunctionalStatement.getReturnType() != null + && createFunctionalStatement.getReturnType().getTableElements() != null) { + createFunctionalStatement.getReturnType().getTableElements() + .forEach(element -> net.sf.jsqlparser.util.TableDefinitionTraversal.visit( + element, + this::validateOptionalExpression, this::validateOptionalFromItem)); + } if (createFunctionalStatement instanceof CreateFunction) { - validateFeature(Feature.createFunction); + validateFeature(createFunctionalStatement + .getOperation() == CreateFunctionalStatement.Operation.ALTER + ? Feature.alterFunction + : Feature.createFunction); } else if (createFunctionalStatement instanceof CreateProcedure) { - validateFeature(Feature.createProcedure); + validateFeature(createFunctionalStatement + .getOperation() == CreateFunctionalStatement.Operation.ALTER + ? Feature.alterProcedure + : Feature.createProcedure); } return null; } @@ -371,6 +485,90 @@ public Void visit(RenameTableStatement renameTableStatement, S context) { return null; } + @Override + public Void visit(PragmaStatement pragmaStatement, S context) { + // TODO: not yet implemented + return null; + } + + @Override + public Void visit(AssertStatement assertStatement, S context) { + // TODO: not yet implemented + return null; + } + + @Override + public Void visit(ExtensionStatement extensionStatement, S context) { + // TODO: not yet implemented + return null; + } + + @Override + public Void visit(ExportDataStatement exportDataStatement, S context) { + // TODO: not yet implemented + return null; + } + + @Override + public Void visit(AttachStatement attachStatement, S context) { + // TODO: not yet implemented + return null; + } + + @Override + public Void visit(DetachStatement detachStatement, S context) { + // TODO: not yet implemented + return null; + } + + @Override + public Void visit(ConnectStatement connectStatement, S context) { + // TODO: not yet implemented + return null; + } + + @Override + public Void visit(DisconnectStatement disconnectStatement, S context) { + // TODO: not yet implemented + return null; + } + + @Override + public Void visit(PrepareStatement prepareStatement, S context) { + // TODO: not yet implemented + return null; + } + + @Override + public Void visit(DeallocateStatement deallocateStatement, S context) { + // TODO: not yet implemented + return null; + } + + @Override + public Void visit(CopyStatement copyStatement, S context) { + // TODO: not yet implemented + return null; + } + + @Override + public Void visit(CreateMacro createMacro, S context) { + // TODO: not yet implemented + return null; + } + + @Override + public Void visit(CreateExtensionRepository createExtensionRepository, S context) { + // TODO: not yet implemented + return null; + } + + @Override + public Void visit(LoadDataStatement loadDataStatement, S context) { + // TODO: not yet implemented + return null; + } + @Override public Void visit(PurgeStatement purgeStatement, S context) { // TODO: not yet implemented @@ -415,6 +613,10 @@ public void visit(CreateTable createTable) { visit(createTable, null); } + public void visit(CreateTrigger createTrigger) { + visit(createTrigger, null); + } + public void visit(CreateView createView) { visit(createView, null); } @@ -455,6 +657,10 @@ public void visit(Alter alter) { visit(alter, null); } + public void visit(AlterEvent alterEvent) { + visit(alterEvent, null); + } + public void visit(Statements statements) { visit(statements, null); } @@ -531,6 +737,14 @@ public void visit(CreateSchema aThis) { visit(aThis, null); } + public void visit(CreateUser createUser) { + visit(createUser, null); + } + + public void visit(CreateEvent createEvent) { + visit(createEvent, null); + } + public void visit(CreateSequence createSequence) { visit(createSequence, null); } @@ -571,6 +785,62 @@ public void visit(RenameTableStatement renameTableStatement) { visit(renameTableStatement, null); } + public void visit(PragmaStatement pragmaStatement) { + visit(pragmaStatement, null); + } + + public void visit(ExtensionStatement extensionStatement) { + visit(extensionStatement, null); + } + + public void visit(AttachStatement attachStatement) { + visit(attachStatement, null); + } + + public void visit(DetachStatement detachStatement) { + visit(detachStatement, null); + } + + public void visit(ConnectStatement connectStatement) { + visit(connectStatement, null); + } + + public void visit(DisconnectStatement disconnectStatement) { + visit(disconnectStatement, null); + } + + public void visit(PrepareStatement prepareStatement) { + visit(prepareStatement, null); + } + + public void visit(DeallocateStatement deallocateStatement) { + visit(deallocateStatement, null); + } + + public void visit(CopyStatement copyStatement) { + visit(copyStatement, null); + } + + public void visit(CreateMacro createMacro) { + visit(createMacro, null); + } + + public void visit(CreateExtensionRepository createExtensionRepository) { + visit(createExtensionRepository, null); + } + + public void visit(AssertStatement assertStatement) { + visit(assertStatement, null); + } + + public void visit(ExportDataStatement exportDataStatement) { + visit(exportDataStatement, null); + } + + public void visit(LoadDataStatement loadDataStatement) { + visit(loadDataStatement, null); + } + public void visit(PurgeStatement purgeStatement) { visit(purgeStatement, null); } @@ -593,11 +863,158 @@ public void visit(Export export) { @Override public Void visit(CreatePolicy createPolicy, S context) { - // TODO: not yet implemented + validateOptionalFromItem(createPolicy.getTable()); + validateOptionalExpression(createPolicy.getUsingExpression()); + validateOptionalExpression(createPolicy.getWithCheckExpression()); return null; } public void visit(CreatePolicy createPolicy) { visit(createPolicy, null); } + + @Override + public Void visit(CreateRole statement, S context) { + validateFeature(Feature.createRole); + for (RoleOption option : statement.getOptions()) { + validateOptionalExpression(option.getValue()); + } + return null; + } + + @Override + public Void visit(AlterRole statement, S context) { + validateFeature(Feature.alterRole); + for (RoleOption option : statement.getOptions()) { + validateOptionalExpression(option.getValue()); + } + if (statement.getValues() != null) { + statement.getValues().forEach(this::validateOptionalExpression); + } + return null; + } + + @Override + public Void visit(Revoke statement, S context) { + validateFeature(Feature.revoke); + statement.getClause().visit(this::validateOptionalFromItem, + this::validateOptionalExpression); + return null; + } + + @Override + public Void visit(AlterDefaultPrivileges statement, S context) { + validateFeature(Feature.alterDefaultPrivileges); + if (statement.getGrant() != null) { + statement.getGrant().accept(this, context); + } + if (statement.getRevoke() != null) { + statement.getRevoke().accept(this, context); + } + return null; + } + + @Override + public Void visit(CreateType statement, S context) { + validateFeature(Feature.createType); + + return null; + } + + @Override + public Void visit(AlterType statement, S context) { + validateFeature(Feature.alterType); + + return null; + } + + @Override + public Void visit(CreateDomain statement, S context) { + validateFeature(Feature.createDomain); + validateOptionalExpression(statement.getDefaultExpression()); + statement.getConstraints() + .forEach(constraint -> validateOptionalExpression(constraint.getExpression())); + return null; + } + + @Override + public Void visit(AlterDomain statement, S context) { + validateFeature(Feature.alterDomain); + validateOptionalExpression(statement.getDefaultExpression()); + if (statement.getConstraint() != null) { + validateOptionalExpression(statement.getConstraint().getExpression()); + } + return null; + } + + @Override + public Void visit(CreateExtension statement, S context) { + validateFeature(Feature.createExtension); + + return null; + } + + @Override + public Void visit(AlterExtension statement, S context) { + validateFeature(Feature.alterExtension); + if (statement.getMember() != null && statement.getMember().isTable()) { + validateOptionalFromItem( + new Table(statement.getMember().getName())); + } + return null; + } + + @Override + public Void visit(CreatePublication statement, S context) { + validateFeature(Feature.createPublication); + statement.getTargets().forEach(target -> target.visit( + this::validateOptionalFromItem, this::validateOptionalExpression)); + return null; + } + + @Override + public Void visit(AlterPublication statement, S context) { + validateFeature(Feature.alterPublication); + statement.getTargets().forEach(target -> target.visit( + this::validateOptionalFromItem, this::validateOptionalExpression)); + return null; + } + + @Override + public Void visit(CreateSubscription statement, S context) { + validateFeature(Feature.createSubscription); + + return null; + } + + @Override + public Void visit(AlterSubscription statement, S context) { + validateFeature(Feature.alterSubscription); + + return null; + } + + @Override + public Void visit(OracleBlock block, S context) { + validateFeature(Feature.block); + validateFeature(Feature.oracleBlock); + block.visitChildren(this::validateOptionalExpression, + statement -> statement.accept(this, context)); + return null; + } + + @Override + public Void visit(OracleAssignment assignment, S context) { + validateFeature(Feature.oracleBlock); + validateOptionalExpression(assignment.getTarget()); + validateOptionalExpression(assignment.getValue()); + return null; + } + + @Override + public Void visit(OracleNullStatement statement, S context) { + validateFeature(Feature.oracleBlock); + return null; + } + } diff --git a/src/main/java/net/sf/jsqlparser/util/validation/validator/UpdateValidator.java b/src/main/java/net/sf/jsqlparser/util/validation/validator/UpdateValidator.java index 058b6aa9fc..b7695b3d42 100644 --- a/src/main/java/net/sf/jsqlparser/util/validation/validator/UpdateValidator.java +++ b/src/main/java/net/sf/jsqlparser/util/validation/validator/UpdateValidator.java @@ -10,7 +10,7 @@ package net.sf.jsqlparser.util.validation.validator; import net.sf.jsqlparser.parser.feature.Feature; -import net.sf.jsqlparser.statement.select.SelectVisitor; +import net.sf.jsqlparser.statement.select.OptionHint; import net.sf.jsqlparser.statement.update.Update; import net.sf.jsqlparser.util.validation.ValidationCapability; @@ -26,26 +26,20 @@ public void validate(Update update) { validateFeature(c, Feature.update); validateOptionalFeature(c, update.getFromItem(), Feature.updateFrom); validateOptionalFeature(c, update.getStartJoins(), Feature.updateJoins); - validateFeature(c, update.isUseSelect(), Feature.updateUseSelect); validateOptionalFeature(c, update.getOrderByElements(), Feature.updateOrderBy); validateOptionalFeature(c, update.getLimit(), Feature.updateLimit); validateOptionalFeature(c, update.getReturningClause(), Feature.updateReturning); } - validateOptionalFromItem(update.getTable()); + if (!update.isTargetTableAlias()) { + validateOptionalFromItem(update.getTable()); + } validateOptional(update.getStartJoins(), j -> getValidator(SelectValidator.class).validateOptionalJoins(j)); - if (update.isUseSelect()) { - validateOptionalExpressions(update.getColumns()); - validateOptional(update.getSelect(), - e -> e.accept((SelectVisitor) getValidator(SelectValidator.class), null)); - } else { - validateOptionalExpressions(update.getColumns()); - validateOptionalExpressions(update.getExpressions()); - } + validateOptionalUpdateSets(update.getUpdateSets()); if (update.getFromItem() != null) { validateOptionalFromItem(update.getFromItem()); @@ -53,6 +47,7 @@ public void validate(Update update) { j -> getValidator(SelectValidator.class).validateOptionalJoins(j)); } + validateOptionalForPortionClause(update.getForPortionClause()); validateOptionalExpression(update.getWhere()); validateOptionalOrderByElements(update.getOrderByElements()); @@ -60,6 +55,15 @@ public void validate(Update update) { getValidator(LimitValidator.class).validate(update.getLimit()); } + if (update.getOption() != null) { + for (OptionHint optionHint : update.getOption().getOptionHints()) { + validateOptionalExpression(optionHint.getValue()); + if (optionHint.getParameters() != null) { + optionHint.getParameters().forEach(this::validateOptionalExpression); + } + } + } + if (update.getReturningClause() != null) { SelectValidator v = getValidator(SelectValidator.class); update.getReturningClause().forEach(c -> c.accept(v, null)); diff --git a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt index 9ac62f42c5..fd6b41f64b 100644 --- a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt +++ b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt @@ -33,6 +33,10 @@ PARSER_BEGIN(CCJSqlParser) package net.sf.jsqlparser.parser; +import net.sf.jsqlparser.statement.oracle.OracleBlock; +import net.sf.jsqlparser.statement.oracle.OracleAssignment; +import net.sf.jsqlparser.statement.oracle.OracleNullStatement; + import java.lang.reflect.Field; import java.lang.Integer; @@ -47,18 +51,29 @@ import net.sf.jsqlparser.statement.analyze.*; import net.sf.jsqlparser.statement.alter.*; import net.sf.jsqlparser.statement.alter.sequence.*; import net.sf.jsqlparser.statement.comment.*; +import net.sf.jsqlparser.statement.create.database.*; +import net.sf.jsqlparser.statement.create.publication.*; +import net.sf.jsqlparser.statement.create.subscription.*; +import net.sf.jsqlparser.statement.create.type.*; +import net.sf.jsqlparser.statement.create.domain.*; +import net.sf.jsqlparser.statement.create.extension.*; +import net.sf.jsqlparser.statement.create.event.*; import net.sf.jsqlparser.statement.create.function.*; import net.sf.jsqlparser.statement.create.index.*; +import net.sf.jsqlparser.statement.create.macro.*; import net.sf.jsqlparser.statement.create.policy.*; import net.sf.jsqlparser.statement.create.procedure.*; import net.sf.jsqlparser.statement.create.schema.*; import net.sf.jsqlparser.statement.create.synonym.*; import net.sf.jsqlparser.statement.create.sequence.*; import net.sf.jsqlparser.statement.create.table.*; +import net.sf.jsqlparser.statement.create.trigger.*; +import net.sf.jsqlparser.statement.create.user.*; import net.sf.jsqlparser.statement.create.view.*; import net.sf.jsqlparser.statement.delete.*; import net.sf.jsqlparser.statement.drop.*; import net.sf.jsqlparser.statement.insert.*; +import net.sf.jsqlparser.statement.load.*; import net.sf.jsqlparser.statement.execute.*; import net.sf.jsqlparser.statement.piped.*; import net.sf.jsqlparser.statement.select.*; @@ -69,11 +84,13 @@ import net.sf.jsqlparser.statement.update.*; import net.sf.jsqlparser.statement.upsert.*; import net.sf.jsqlparser.statement.merge.*; import net.sf.jsqlparser.statement.grant.*; +import net.sf.jsqlparser.statement.role.*; import net.sf.jsqlparser.statement.imprt.*; import net.sf.jsqlparser.statement.export.*; import net.sf.jsqlparser.statement.lock.*; import java.util.*; import java.util.AbstractMap.SimpleEntry; +import java.util.concurrent.CancellationException; import net.sf.jsqlparser.statement.select.SetOperationList.SetOperationType; import java.util.logging.Level; @@ -86,14 +103,21 @@ public class CCJSqlParser extends AbstractJSqlParser { public final static Logger LOGGER = Logger.getLogger(CCJSqlParser.class.getName()); public int bracketsCounter = 0; public int caseCounter = 0; - public boolean interrupted = false; + public volatile boolean interrupted = false; public CCJSqlParser withConfiguration(FeatureConfiguration configuration) { token_source.configuration = configuration; + jj_input_stream.featureConfiguration = configuration; return this; } public FeatureConfiguration getConfiguration() { + // wire the stream before parsing starts: every feature mutation goes + // through here (the consumers run pre-parse), default-off parsing + // never wires the stream and stays on the zero-cost path + if (jj_input_stream.featureConfiguration != token_source.configuration) { + jj_input_stream.featureConfiguration = token_source.configuration; + } return token_source.configuration; } @@ -101,15 +125,59 @@ public class CCJSqlParser extends AbstractJSqlParser { return this; } + // depth of open ClickHouse-style ternary then-branches: a ":" directly after the + // then-branch closes the ternary and must not be taken as the JSON path operator + private int ternaryThenBranchDepth = 0; + + // depth of DuckDB MAP keys: the following ":" separates a key from its value + // and must not be taken as the JSON path operator + private int mapKeyDepth = 0; + + private Expression createOutputArgument(Expression argument) throws ParseException { + Expression value = argument; + if (argument instanceof VariableAssignment) { + value = ((VariableAssignment) argument).getExpression(); + } else if (argument instanceof EqualsTo + && ((EqualsTo) argument).getLeftExpression() instanceof UserVariable) { + value = ((EqualsTo) argument).getRightExpression(); + } + if (!(value instanceof UserVariable || value instanceof JdbcParameter + || value instanceof JdbcNamedParameter)) { + throw new ParseException("An OUTPUT argument must be a variable or bind parameter"); + } + return new net.sf.jsqlparser.statement.execute.ExecuteArgument(argument, true); + } + private void linkAST(ASTNodeAccess access, Node node) { access.setASTNode(node); node.jjtSetValue(access); } + /** + * Links a node whose production takes its left operand as a parameter, starting the node's + * token range at that operand rather than at the operator. The range is left as JJTree set it + * when the operand carries no node of its own. + */ + private void linkAST(ASTNodeAccess access, Node node, Expression leftExpression) { + linkAST(access, node); + if (leftExpression instanceof ASTNodeAccess) { + Node leftNode = ((ASTNodeAccess) leftExpression).getASTNode(); + if (leftNode != null && leftNode.jjtGetFirstToken() != null) { + node.jjtSetFirstToken(leftNode.jjtGetFirstToken()); + } + } + } + public Node getASTRoot() { return jjtree.rootNode(); } + private boolean isMySqlDatabaseOptionAhead() { + int offset = getToken(1).kind == K_DEFAULT ? 2 : 1; + int kind = getToken(offset).kind; + return kind == K_CHARACTER || kind == K_COLLATE || kind == K_ENCRYPTION; + } + private static class ObjectNames { private final List names; @@ -182,6 +250,11 @@ public class CCJSqlParser extends AbstractJSqlParser { // comparison operator follows after "(" "+" ")" return isComparisonOperator(getToken(4)); } + if ("?".equals(token.image) && isTernaryAhead()) { + // ClickHouse ternary `cond ? then : else` — the `?` must not be taken + // as the PostgreSQL JSON operator here, it is consumed by prattExpressionRest + return false; + } return isComparisonOperator(token); } catch (Exception e) { return false; @@ -212,6 +285,98 @@ public class CCJSqlParser extends AbstractJSqlParser { } } + /** + * True when the pending "?" starts a ClickHouse-style ternary conditional + * {@code cond ? then : else} rather than the PostgreSQL JSON operator: + * a standalone ":" closes the then-branch at the same nesting depth before + * any expression boundary (",", ";", EOF, an unbalanced closing bracket or a + * clause keyword such as FROM/WHERE). A ":" in operand position (directly + * after an operator) starts a JDBC named parameter instead and cannot close + * the then-branch. + */ + protected boolean isTernaryAhead() { + try { + int depth = 0; + Token prev = null; + for (int i = 2; ; i++) { + Token t = getToken(i); + if (t == null || t.kind == EOF) { + return false; + } + String image = t.image; + if ("(".equals(image) || "[".equals(image)) { + depth++; + } else if (")".equals(image) || "]".equals(image)) { + if (depth == 0) { + return false; + } + depth--; + } else if (depth == 0) { + if (":".equals(image) && canEndExpression(prev)) { + return true; + } + if (",".equals(image) || ";".equals(image)) { + return false; + } + switch (t.kind) { + case K_SELECT: case K_FROM: case K_WHERE: case K_GROUP: + case K_HAVING: case K_ORDER: case K_LIMIT: case K_UNION: + case K_INTERSECT: case K_EXCEPT: case K_MINUS: case K_INTO: + case K_VALUES: case K_FETCH: case K_OFFSET: + return false; + default: + break; + } + } + prev = t; + } + } catch (Exception e) { + return false; + } + } + + /** + * True when the token can be the LAST token of an expression: an + * identifier, a literal, a JDBC parameter, or a balanced closing bracket. + * Only such a token may directly precede the ":" closing the ternary + * then-branch, since the then-branch must be a complete expression. A ":" + * in operand position instead (directly after an operator, e.g. `x = :name` + * or directly after the leading "?") starts a JDBC named parameter. + * DATA_TYPE tokens end expressions as well, because a cast's target type + * (`b :: int`) or a bare type keyword closes the then-branch. The bare "?" + * positional parameter has no token-kind constant (anonymous token), so it + * is recognized by its image, like the closing brackets. + */ + private boolean canEndExpression(Token t) { + if (t == null) { + return false; + } + switch (t.kind) { + case S_IDENTIFIER: case S_QUOTED_IDENTIFIER: case S_PARAMETER: + case S_LONG: case S_DOUBLE: case S_HEX: case S_CHAR_LITERAL: + case K_DATE_LITERAL: case K_DATETIMELITERAL: case DATA_TYPE: + case K_NULL: case K_TRUE: case K_FALSE: + return true; + default: + return (t.kind >= MIN_NON_RESERVED_WORD && t.kind <= MAX_NON_RESERVED_WORD) + || ")".equals(t.image) || "]".equals(t.image) + || "?".equals(t.image); + } + } + + /** + * Whether the next two tokens form a character-set-introduced string literal, + * for example {@code _utf8mb4'text'} or {@code _ISO8859_1 'text'}. + */ + private boolean isCharacterSetIntroducedStringAhead() { + Token introducer = getToken(1); + Token literal = getToken(2); + return introducer.kind == S_IDENTIFIER + && introducer.image != null + && introducer.image.length() > 1 + && introducer.image.charAt(0) == '_' + && literal.kind == S_CHAR_LITERAL; + } /** * Tokens that have dedicated branches in PrimaryExpression AFTER the Function branch. @@ -234,15 +399,55 @@ public class CCJSqlParser extends AbstractJSqlParser { * from column references like col, schema.col, a.b.c.col. * * Replaces LOOKAHEAD(16) on Function() with a targeted O(chain-length) check. + * Long chains are memoized per start token: the phase-2 lookahead routines + * re-evaluate the same walk many times per position and each chain part + * would walk the very same suffix again, which makes long chains parse in + * quadratic time. Chains up to CHAIN_CACHE_THRESHOLD parts skip the cache + * entirely, keeping the normal short-name path allocation- and map-free. */ + private static final int CHAIN_CACHE_THRESHOLD = 8; + + // Sentinel returned by the chain walks when the non-filling pass ran past + // CHAIN_CACHE_THRESHOLD delimiter/part pairs and the caller must retry + // through the per-token cache. + private static final int LONG_CHAIN = -1; + + private final Map isFunctionAheadCache = new HashMap(); + protected boolean isFunctionAhead() { + int r = isFunctionAheadWalk(false); + if (r != LONG_CHAIN) { + return r == 1; + } + Token key = getToken(1); + Boolean cached = isFunctionAheadCache.get(key); + if (cached != null) { + return cached; + } + boolean result = isFunctionAheadWalk(true) == 1; + isFunctionAheadCache.put(key, result); + return result; + } + + // True when a fresh evaluation started at this token would pass all + // first-token guards and run the same chain walk. + private boolean isFunctionAheadChainStartEligible(Token t) { + return !t.image.equals("{") && t.kind != K_APPROXIMATE && !isNonFunctionKeyword(t) + && t.kind != S_LONG && t.kind != S_DOUBLE && t.kind != S_HEX + && t.kind != S_CHAR_LITERAL && t.kind != OPENING_BRACKET + && t.kind != CLOSING_BRACKET && t.kind != EOF; + } + + // Returns 1 for true, 0 for false, or LONG_CHAIN when the walk exceeded + // CHAIN_CACHE_THRESHOLD delimiter/part pairs (only in the non-filling pass). + private int isFunctionAheadWalk(boolean fill) { try { int i = 1; Token t = getToken(i); // JDBC escape function: {fn ...} — must check for FN keyword if (t.image.equals("{")) { - return getToken(2).kind == K_FN; + return getToken(2).kind == K_FN ? 1 : 0; } // Optional APPROXIMATE keyword @@ -254,22 +459,30 @@ public class CCJSqlParser extends AbstractJSqlParser { // Exclude tokens that have their own dedicated branches // after Function() in PrimaryExpression if (isNonFunctionKeyword(t)) { - return false; + return 0; } // First token must not be a literal, bracket, or EOF if (t.kind == S_LONG || t.kind == S_DOUBLE || t.kind == S_HEX || t.kind == S_CHAR_LITERAL || t.kind == OPENING_BRACKET || t.kind == CLOSING_BRACKET || t.kind == EOF) { - return false; + return 0; } i++; // Walk through dotted name chain + int delimiters = 0; + List chainParts = fill ? new ArrayList() : null; while (true) { t = getToken(i); if (t.image.equals(".") || t.image.equals("..") || t.image.equals("...") || t.image.equals(":")) { + if (!fill && ++delimiters > CHAIN_CACHE_THRESHOLD) { + return LONG_CHAIN; + } + if (fill) { + chainParts.add(getToken(i + 1)); + } i++; // skip delimiter i++; // skip next name part } else { @@ -277,20 +490,23 @@ public class CCJSqlParser extends AbstractJSqlParser { } } - // Must be followed by ( - if (getToken(i).kind != OPENING_BRACKET) { - return false; - } - - // Exclude Oracle join syntax: column(+) - if (getToken(i + 1).image.equals("+") - && getToken(i + 2).kind == CLOSING_BRACKET) { - return false; + // Must be followed by (, and not by the Oracle join syntax column(+) + boolean result = getToken(i).kind == OPENING_BRACKET + && !(getToken(i + 1).image.equals("+") + && getToken(i + 2).kind == CLOSING_BRACKET); + + // Each eligible chain part would walk the same remaining suffix and + // obtain the same answer, so memoize them all in one pass. + if (fill) { + for (Token part : chainParts) { + if (isFunctionAheadChainStartEligible(part)) { + isFunctionAheadCache.put(part, result); + } + } } - - return true; + return result ? 1 : 0; } catch (TokenMgrException e) { - return false; + return 0; } } @@ -328,12 +544,35 @@ public class CCJSqlParser extends AbstractJSqlParser { */ protected Expression prattExpressionRest(Expression left, int minPrec) throws ParseException { while (!interrupted) { - int op = getToken(1).kind; + Token t = getToken(1); + int op = t.kind; + boolean ternary = "?".equals(t.image) && isTernaryAhead(); int prec; if (op == K_AND || op == OP_DOUBLEAND) prec = 4; else if (op == K_XOR || op == K_OR) prec = 2; + else if (ternary) prec = 2; else break; if (prec < minPrec) break; + if (ternary) { + // ClickHouse-style ternary conditional: cond ? then : else + // Right-associative: both branches parse at the full expression + // level (prec 2), nested ternaries are absorbed by the else-branch. + jj_consume_token(op, t.image); + Expression thenExpression; + ternaryThenBranchDepth++; + try { + thenExpression = prattExpressionRest(Condition(), 2); + } finally { + ternaryThenBranchDepth--; + } + if (!":".equals(getToken(1).image)) { + throw new ParseException("Expected ':' closing the ternary conditional operator"); + } + jj_consume_token(getToken(1).kind, getToken(1).image); + Expression elseExpression = prattExpressionRest(Condition(), 2); + left = new TernaryExpression(left, thenExpression, elseExpression); + continue; + } jj_consume_token(op, getToken(1).image); // +1 makes OR/AND/XOR left-associative Expression right = prattExpressionRest(Condition(), prec + 1); @@ -358,9 +597,11 @@ public class CCJSqlParser extends AbstractJSqlParser { int prec; // Named tokens: OP_SLASH(/), OP_CARET(^), K_DIV, OP_CONCAT(||), - // OP_PIPE(|), OP_LSHIFT(<<), OP_RSHIFT(>>) + // OP_PIPE(|), OP_LSHIFT(<<), OP_RSHIFT(>>), + // S_HASH_OPERATOR(#) // String-literal tokens: *, +, -, %, & (unnamed in JavaCC grammar) if (op == OP_SLASH || op == OP_CARET || op == K_DIV) prec = 6; + else if (op == S_HASH_OPERATOR) prec = 5; else if (op == OP_CONCAT || op == OP_PIPE || op == OP_LSHIFT || op == OP_RSHIFT) prec = 5; else { @@ -386,6 +627,7 @@ public class CCJSqlParser extends AbstractJSqlParser { Expression right = prattArithRest(PrimaryExpression(), prec + 1); if (op == OP_SLASH) { Division r = new Division(); r.setLeftExpression(left); r.setRightExpression(right); left = r; } + else if (op == S_HASH_OPERATOR) { Intersects r = new Intersects(); r.setLeftExpression(left); r.setRightExpression(right); left = r; } else if (op == OP_CARET) { net.sf.jsqlparser.expression.operators.arithmetic.BitwiseXor r = new net.sf.jsqlparser.expression.operators.arithmetic.BitwiseXor(); r.setLeftExpression(left); r.setRightExpression(right); left = r; } else if (op == K_DIV) { IntegerDivision r = new IntegerDivision(); r.setLeftExpression(left); r.setRightExpression(right); left = r; } else if (op == OP_CONCAT) { Concat r = new Concat(); r.setLeftExpression(left); r.setRightExpression(right); left = r; } @@ -411,10 +653,10 @@ public class CCJSqlParser extends AbstractJSqlParser { try { if (getToken(1).kind != OPENING_BRACKET) return false; int k2 = getToken(2).kind; - // Direct subquery: ( SELECT/WITH ) → ParenthesedSelect handles it + // Direct subquery: ( SELECT/WITH/PIVOT ) → ParenthesedSelect handles it // ( VALUES ) can be ParenthesedFromItem(Values) or ParenthesedSelect; // ParenthesedFromItem is checked first so Values-as-FROM-item works - if (k2 == K_SELECT || k2 == K_WITH) return false; + if (k2 == K_SELECT || k2 == K_WITH || k2 == K_PIVOT) return false; // ( ( SELECT ) UNION ( SELECT ) ) → isNestedSetOperationAhead handles it // Other (( patterns: ( (SELECT...) JOIN ... ) or ( (table) ) → ParenthesedFromItem if (k2 == K_LATERAL) return false; // handled by LateralSubSelect @@ -471,6 +713,90 @@ public class CCJSqlParser extends AbstractJSqlParser { } } + /** The literal tokens that may complete a typed literal in {@link #isImplicitCastAhead()}. */ + private boolean isTypedLiteralFollower(int kind) { + return kind == S_CHAR_LITERAL || kind == S_LONG || kind == S_DOUBLE; + } + + /** + * Whether the token could start a {@link #RelObjectName()}, mirroring the token + * alternatives accepted there (base identifiers, DATA_TYPE, non-reserved keywords + * and the reserved-keyword-as-identifier list). + */ + private static boolean isIdentifierishNameToken(Token t) { + switch (t.kind) { + case S_IDENTIFIER: case S_QUOTED_IDENTIFIER: case DATA_TYPE: + case K_DATETIMELITERAL: case K_DATE_LITERAL: + case K_ALL: case K_ANY: case K_CASEWHEN: case K_CONNECT: case K_CREATE: + case K_DEFAULT: case K_GLOBAL: case K_GROUP: case K_GROUPING: case K_IF: + case K_IIF: case K_IGNORE: case K_IN: case K_INTERVAL: case K_LEFT: + case K_LIMIT: case K_NEXTVAL: case K_OFFSET: case K_ON: case K_OPTIMIZE: + case K_ORDER: case K_PROCEDURE: case K_PUBLIC: case K_QUALIFY: case K_RIGHT: + case K_FILE: case K_SET: case K_SOME: case K_START: case K_TABLES: + case K_TOP: case K_VALUE: case K_VALUES: + return true; + default: + return t.kind >= MIN_NON_RESERVED_WORD && t.kind <= MAX_NON_RESERVED_WORD; + } + } + + private Token nextWithItemToken(Token current) { + if (current.next == null) { + current.next = token_source.getNextToken(); + } + return current.next; + } + + private Token skipBalancedBracketGroup(Token current) { + int depth = 0; + while (!interrupted && current.kind != EOF) { + if (current.kind == OPENING_BRACKET) { + depth++; + } else if (current.kind == CLOSING_BRACKET) { + if (--depth == 0) { + return nextWithItemToken(current); + } + } + current = nextWithItemToken(current); + } + if (interrupted) { + throw new CancellationException("Parsing interrupted"); + } + return null; + } + + private boolean isWithExpressionAliasAhead() { + try { + Token t1 = getToken(1); + if (t1.kind == K_FUNCTION || t1.kind == K_RECURSIVE) { + return false; + } + if (!isIdentifierishNameToken(t1)) { + return true; + } + Token next = getToken(2); + if (next.kind == OPENING_BRACKET) { + next = skipBalancedBracketGroup(next); + if (next == null) { + return false; + } + } + if (next.kind != K_AS) { + return true; + } + next = nextWithItemToken(next); + if (next.kind == K_NOT) { + next = nextWithItemToken(next); + } + if (next.kind == K_MATERIALIZED) { + next = nextWithItemToken(next); + } + return next.kind != OPENING_BRACKET; + } catch (TokenMgrException e) { + return false; + } + } + protected boolean isImplicitCastAhead() { try { int k1 = getToken(1).kind; @@ -481,18 +807,33 @@ public class CCJSqlParser extends AbstractJSqlParser { if (k1 == K_JSON) return getToken(2).kind == S_CHAR_LITERAL; if (k1 != DATA_TYPE) return false; int k2 = getToken(2).kind; - if (k2 != OPENING_BRACKET) return true; // DATA_TYPE literal - simple cast - // DATA_TYPE( ... ) - precision cast if content is only S_LONG literals - // function call otherwise (e.g. UUID(), VARCHAR(col)) + if (k2 != OPENING_BRACKET) { + // a bare DATA_TYPE is a typed literal (INT '5') only when a trailing + // literal completes it, possibly after further type-name tokens + // (DOUBLE PRECISION '1'); otherwise it is an identifier, e.g. a + // column named number or a keyword-named lambda alias + if (isTypedLiteralFollower(k2)) { + return true; + } + return k2 == DATA_TYPE && isTypedLiteralFollower(getToken(3).kind); + } + // DATA_TYPE( ... ) - precision cast only when a trailing literal completes + // the typed literal (DATA_TYPE(N) 'lit'); a function call otherwise, + // e.g. double(5), int(5), VARCHAR(col), UUID() int k3 = getToken(3).kind; if (k3 == CLOSING_BRACKET) return false; // DATA_TYPE() - empty call if (k3 != S_LONG) return false; // DATA_TYPE(expr) - function call int k4 = getToken(4).kind; - if (k4 == CLOSING_BRACKET) return true; // DATA_TYPE(N) - precision cast - if (k4 != K_COMMA) return false; // DATA_TYPE(N expr) - function call - int k5 = getToken(5).kind; - if (k5 != S_LONG) return false; // DATA_TYPE(N, expr) - function call - return getToken(6).kind == CLOSING_BRACKET; // DATA_TYPE(N,M) - precision cast + if (k4 == K_COMMA) { + int k5 = getToken(5).kind; + if (k5 != S_LONG) return false; // DATA_TYPE(N, expr) - function call + if (getToken(6).kind != CLOSING_BRACKET) { + return false; // DATA_TYPE(N,M expr) - function call + } + return isTypedLiteralFollower(getToken(7).kind); // DATA_TYPE(N,M) 'lit' + } + if (k4 != CLOSING_BRACKET) return false; // DATA_TYPE(N expr) - function call + return isTypedLiteralFollower(getToken(5).kind); // DATA_TYPE(N) 'lit' } catch (TokenMgrException e) { return false; } @@ -530,6 +871,66 @@ public class CCJSqlParser extends AbstractJSqlParser { } } + /** + * XML constructor functions take a bare leading keyword before their first argument, + * e.g. XMLPARSE(CONTENT expr), XMLPARSE(DOCUMENT expr), XMLELEMENT(NAME ident, ...). + * Guarded by the function name so that CONTENT/DOCUMENT/NAME stay plain identifiers + * everywhere else, e.g. f(content). + */ + private void consumeXmlLeadingKeyword(Function function) { + String name = function.getName(); + if (name == null) { + return; + } + name = name.toUpperCase(java.util.Locale.ROOT); + if (!"XMLPARSE".equals(name) && !"XMLELEMENT".equals(name) && !"XMLFOREST".equals(name)) { + return; + } + Token token = getToken(1); + if (token.kind != S_IDENTIFIER && token.kind != K_NAME) { + return; + } + String keyword = token.image.toUpperCase(java.util.Locale.ROOT); + if (!"CONTENT".equals(keyword) && !"DOCUMENT".equals(keyword) && !"NAME".equals(keyword)) { + return; + } + // only a bare leading keyword: an expression must follow + Token next = getToken(2); + if (next.kind == EOF || next.kind == K_AS || ")".equals(next.image) || ",".equals(next.image) + || ".".equals(next.image) || "(".equals(next.image)) { + return; + } + function.setExtraKeyword(getNextToken().image); + } + + private void normalizeOdbcTimestampInterval(Function function) { + if (!function.isEscaped() || function.getMultipartName().size() != 1 + || !("TIMESTAMPADD".equalsIgnoreCase(function.getName()) + || "TIMESTAMPDIFF".equalsIgnoreCase(function.getName()))) { + return; + } + ExpressionList parameters = function.getParameters(); + if (parameters == null || parameters.size() != 3 + || parameters instanceof ParenthesedExpressionList + || !(parameters.get(0) instanceof Column)) { + return; + } + Column column = (Column) parameters.get(0); + if (column.getTable() != null) { + return; + } + DateUnitExpression.DateUnit unit = + DateUnitExpression.DateUnit.fromOdbcInterval(column.getColumnName()); + if (unit != null) { + DateUnitExpression interval = new DateUnitExpression(unit); + linkAST(interval, column.getASTNode()); + ExpressionList normalized = new ExpressionList(parameters); + normalized.setASTNode(parameters.getASTNode()); + normalized.set(0, interval); + function.setParameters(normalized); + } + } + private boolean isKeywordArgumentAhead() { Token t = getToken(1); if (t.kind == EOF || t.image.equals(")")) return false; @@ -565,8 +966,30 @@ public class CCJSqlParser extends AbstractJSqlParser { /** * Scans ahead through a dotted identifier chain and checks if '*' follows. * Identifies table.* patterns for AllTableColumns. + * Long chains are memoized per start token like {@link #isFunctionAhead()} + * (one walk fills every eligible chain part); short chains skip the cache. */ + private final Map isAllTableColumnsAheadCache = + new HashMap(); + protected boolean isAllTableColumnsAhead() { + int r = isAllTableColumnsWalk(false); + if (r != LONG_CHAIN) { + return r == 1; + } + Token key = getToken(1); + Boolean cached = isAllTableColumnsAheadCache.get(key); + if (cached != null) { + return cached; + } + boolean result = isAllTableColumnsWalk(true) == 1; + isAllTableColumnsAheadCache.put(key, result); + return result; + } + + // Returns 1 for true, 0 for false, or LONG_CHAIN when the walk exceeded + // CHAIN_CACHE_THRESHOLD delimiter/part pairs (only in the non-filling pass). + private int isAllTableColumnsWalk(boolean fill) { int i = 1; Token t = getToken(i); @@ -574,15 +997,23 @@ public class CCJSqlParser extends AbstractJSqlParser { if (t.kind == S_LONG || t.kind == S_DOUBLE || t.kind == S_HEX || t.kind == S_CHAR_LITERAL || t.kind == OPENING_BRACKET || t.kind == CLOSING_BRACKET || t.kind == EOF) { - return false; + return 0; } i++; // Walk through dotted name chain + int delimiters = 0; + List chainParts = fill ? new ArrayList() : null; while (true) { t = getToken(i); if (t.image.equals(".") || t.image.equals("..") || t.image.equals("...")) { + if (!fill && ++delimiters > CHAIN_CACHE_THRESHOLD) { + return LONG_CHAIN; + } + if (fill) { + chainParts.add(getToken(i + 1)); + } i++; // skip delimiter i++; // skip next part (could be "*") } else { @@ -593,7 +1024,109 @@ public class CCJSqlParser extends AbstractJSqlParser { // It's AllTableColumns if the chain ended on "*" // i.e., the last name part we skipped over was "*" // Back up: the last token consumed was at (i-1) - return getToken(i - 1).image.equals("*"); + boolean result = getToken(i - 1).image.equals("*"); + + // Each chain part that would pass the name-like guard walks the same + // remaining suffix and obtains the same answer. + if (fill) { + for (Token part : chainParts) { + if (part.kind == S_LONG || part.kind == S_DOUBLE || part.kind == S_HEX + || part.kind == S_CHAR_LITERAL || part.kind == OPENING_BRACKET + || part.kind == CLOSING_BRACKET || part.kind == EOF) { + continue; + } + isAllTableColumnsAheadCache.put(part, result); + } + } + return result ? 1 : 0; + } + + /** + * Detects PostgreSQL composite row expansion {@code (function()).*} (and any + * number of surrounding parentheses), where a parenthesised expression wrapping + * a single {@link Function} is immediately followed by {@code .*}. + * + *

This is a constant-time follower check (unwraps the already-parsed + * {@code retval} and peeks the next two tokens). It deliberately avoids the + * speculative syntactic lookahead that previously degraded performance, see + * issue #2207. + * + * @param retval the expression parsed so far within the + * {@code ParenthesedExpressionList} branch of {@code PrimaryExpression} + */ + protected boolean isFunctionAllColumnsAhead(Expression retval) { + // Fast follower gate: the overwhelming majority of parenthesised + // expressions are not followed by ".*", so reject on the token stream + // before ever touching the already-parsed expression. + if (!getToken(1).image.equals(".") || !getToken(2).image.equals("*")) { + return false; + } + if (retval == null) { + return false; + } + + Expression inner = retval; + while (inner instanceof ParenthesedExpressionList) { + ParenthesedExpressionList parenthesed = (ParenthesedExpressionList) inner; + if (parenthesed.size() != 1) { + return false; + } + inner = parenthesed.get(0); + } + + return inner instanceof Function; + } + + /** + * Unwraps any number of surrounding parentheses from a parenthesised + * {@link Function} and returns the inner function. Only call this after + * {@link #isFunctionAllColumnsAhead(Expression)} has confirmed the shape. + */ + protected Function unwrapParenthesedFunction(Expression retval) { + Expression inner = retval; + while (inner instanceof ParenthesedExpressionList) { + inner = ((ParenthesedExpressionList) inner).get(0); + } + return (Function) inner; + } + + /** + * Whether the next token can continue the (possibly multi-word) keyword of + * an OPTION query hint, e.g. any word of {@code HASH JOIN}, {@code KEEP PLAN}, + * {@code OPTIMIZE FOR UNKNOWN} or {@code IGNORE_NONCLUSTERED_COLUMNSTORE_INDEX}. + * + * Reserved words usable inside hint keywords are enumerated here because + * they cannot be matched by {@code RelObjectName()}. + */ + private boolean isOptionHintWordAhead() { + int kind = getToken(1).kind; + if (kind == S_IDENTIFIER + || (kind >= MIN_NON_RESERVED_WORD && kind <= MAX_NON_RESERVED_WORD)) { + return true; + } + switch (kind) { + case K_JOIN: case K_FOR: case K_ORDER: + case K_UNION: case K_EXCEPT: case K_INTERSECT: + case K_USE: case K_OPTIMIZE: case K_FORCE: case K_UNKNOWN: + return true; + default: + return false; + } + } + + /** + * Whether the next token can start the single value argument of an OPTION + * query hint, e.g. the {@code 100} of {@code FAST 100} or the {@code 25} + * of {@code MAX_GRANT_PERCENT = 25}. + */ + private boolean isOptionHintValueAhead() { + switch (getToken(1).kind) { + case S_LONG: case S_DOUBLE: case S_CHAR_LITERAL: + case S_PARAMETER: + return true; + default: + return false; + } } /** @@ -621,19 +1154,24 @@ public class CCJSqlParser extends AbstractJSqlParser { switch (kind) { // Safe as implicit identifiers (no clause collision) - case K_TABLES: case K_OPTIMIZE: case K_PROCEDURE: case K_PUBLIC: + case K_TABLES: case K_OPTIMIZE: case K_PUBLIC: case K_CASEWHEN: case K_IIF: return true; // Safe when the follower doesn't form a clause + case K_PROCEDURE: return nextKind != K_ANALYSE; case K_GROUP: return nextKind != K_BY; case K_ORDER: return nextKind != K_BY && nextKind != K_SIBLINGS; case K_CONNECT: return nextKind != K_BY; case K_START: return nextKind != K_WITH; case K_LEFT: return nextKind != K_JOIN && nextKind != K_OUTER - && nextKind != K_SEMI; + && nextKind != K_SEMI && nextKind != K_ANTI + && nextKind != K_ARRAY_LITERAL; case K_RIGHT: return nextKind != K_JOIN && nextKind != K_OUTER - && nextKind != K_SEMI; + && nextKind != K_SEMI && nextKind != K_ANTI + && nextKind != K_ARRAY_LITERAL; + case K_ARRAY_LITERAL: + return nextKind != K_JOIN; case K_ALL: return nextKind != K_JOIN; case K_ANY: return nextKind != OPENING_BRACKET; case K_SOME: return nextKind != OPENING_BRACKET; @@ -775,6 +1313,99 @@ public class CCJSqlParser extends AbstractJSqlParser { return isReservedKeywordSafeByFollower(); } + /** + * DuckDB's FROM-first query: a FROM clause followed by query clauses the pipe syntax cannot + * consume. Scanning stops at a pipe operator or the end of the statement, and clause keywords + * nested in brackets (a sub-select, for instance) do not count. + */ + private boolean isFromFirstSelectAhead() { + if (getToken(1).kind != K_FROM) { + return false; + } + int depth = 0; + for (int i = 2;; i++) { + Token token = getToken(i); + if (token == null || token.kind == EOF || ";".equals(token.image) + || "|>".equals(token.image)) { + return false; + } + if ("(".equals(token.image)) { + depth++; + } else if (")".equals(token.image)) { + depth--; + } else if (depth == 0) { + switch (token.kind) { + case K_SELECT: + case K_WHERE: + case K_GROUP: + case K_HAVING: + case K_QUALIFY: + case K_ORDER: + case K_LIMIT: + case K_OFFSET: + return true; + default: + break; + } + } + } + } + + /** CREATE [TEMP|TEMPORARY] MACRO, DuckDB's inlined function definition. */ + private boolean isCreateMacroAhead() { + int offset = 1; + int kind = getToken(offset).kind; + if (kind == K_TEMP || kind == K_TEMPORARY) { + offset++; + } + return getToken(offset).kind == K_MACRO; + } + + /** DuckDB sampling methods, which are ordinary identifiers except for SYSTEM/BERNOULLI. */ + private boolean isSampleMethod(Token token) { + return token.kind == K_SYSTEM || token.kind == K_BERNOULLI + || (token.image != null && "RESERVOIR".equalsIgnoreCase(token.image)); + } + + /** DuckDB's INSERT ... BY NAME|POSITION column matching, never a table alias. */ + private boolean isColumnMatchingAhead() { + return getToken(1).kind == K_BY + && ("NAME".equalsIgnoreCase(getToken(2).image) + || "POSITION".equalsIgnoreCase(getToken(2).image)); + } + + /** DuckDB 2.0's APPROX NEAREST similarity join, never a table alias. */ + private boolean isApproxNearestAhead() { + return getToken(1).kind == K_APPROX && getToken(2).kind == K_NEAREST; + } + + /** SEMI/ANTI stay contextual identifiers unless they prefix a JOIN. */ + private boolean isSemiAntiJoinAhead() { + int kind = getToken(1).kind; + if (kind != K_SEMI && kind != K_ANTI) { + return false; + } + return getToken(2).kind == K_JOIN; + } + + /** ASOF stays a contextual identifier outside a join prefix. */ + private boolean isAsOfJoinAhead() { + if (!isKeywordAhead("ASOF")) { + return false; + } + int offset = 2; + int kind = getToken(offset).kind; + if (kind == K_LEFT || kind == K_RIGHT || kind == K_FULL) { + offset++; + if (getToken(offset).kind == K_OUTER) { + offset++; + } + } else if (kind == K_INNER) { + offset++; + } + return getToken(offset).kind == K_JOIN; + } + /** * Checks whether the next token(s) can plausibly start an {@code Alias}. * @@ -785,7 +1416,23 @@ public class CCJSqlParser extends AbstractJSqlParser { * Instead it goes directly to follower-based disambiguation for * reserved keywords. */ + private boolean isMatchRecognizeAhead() { + return isKeywordAhead("MATCH_RECOGNIZE") && getToken(2).kind == OPENING_BRACKET; + } + + private boolean isRowPatternPrimaryAhead() { + Token next = getToken(1); + return next.kind == OPENING_BRACKET || next.kind == OP_CARET + || next.kind == OPENING_CURLY_BRACKET && "-".equals(getToken(2).image) + || next.kind == S_IDENTIFIER || next.kind == S_QUOTED_IDENTIFIER + || next.kind == DATA_TYPE + || next.kind >= MIN_NON_RESERVED_WORD && next.kind <= MAX_NON_RESERVED_WORD; + } + private boolean isAliasAhead() { + if (isAsOfJoinAhead() || isSemiAntiJoinAhead() || isApproxNearestAhead()) { + return false; + } Token t = getToken(1); int kind = t.kind; @@ -795,16 +1442,15 @@ public class CCJSqlParser extends AbstractJSqlParser { // String-literal alias: SELECT col 'myAlias' if (kind == S_CHAR_LITERAL) return true; - // Base identifier tokens - if (kind == S_IDENTIFIER || kind == S_QUOTED_IDENTIFIER - || kind == DATA_TYPE || kind == K_DATETIMELITERAL - || kind == K_DATE_LITERAL) { - return true; + // WITHIN followed by a duration starts a ksqlDB JOIN window. + if (kind == K_WITHIN && (getToken(2).kind == S_LONG + || getToken(2).kind == OPENING_BRACKET && getToken(3).kind == S_LONG)) { + return false; } - // Non-reserved keywords - if (kind >= MIN_NON_RESERVED_WORD && kind <= MAX_NON_RESERVED_WORD) { - return true; + // OPTION (...) introduces a query hint clause, not an alias + if (kind == K_OPTION && getToken(2).kind == OPENING_BRACKET) { + return false; } // For reserved keywords in alias position, skip the structural- @@ -812,7 +1458,7 @@ public class CCJSqlParser extends AbstractJSqlParser { // Only check keywords that are actually in RelObjectName's token // alternatives — don't fire for brackets, operators, literals, etc. switch (kind) { - case K_ALL: case K_ANY: case K_CASEWHEN: case K_CONNECT: + case K_ALL: case K_ARRAY_LITERAL: case K_ANY: case K_CASEWHEN: case K_CONNECT: case K_CREATE: case K_DEFAULT: case K_GLOBAL: case K_GROUP: case K_GROUPING: case K_IF: case K_IIF: case K_IGNORE: case K_IN: case K_INTERVAL: @@ -823,22 +1469,377 @@ public class CCJSqlParser extends AbstractJSqlParser { case K_TOP: case K_VALUE: case K_VALUES: return isReservedKeywordSafeByFollower(); default: - return false; + break; } - } - /** - * Lightweight lookahead for SpecialStringFunctionWithNamedParameters: - * scans forward from the current position (just past the opening '(') - * looking for FROM, IN, or PLACING at bracket nesting depth 0. - * - * This replaces an expensive syntactic LOOKAHEAD(NamedExpressionListExprFirst()) - * that caused exponential backtracking with deeply nested expressions. - */ - protected boolean isNamedExprListAhead() { - int depth = 0; - for (int i = 1; ; i++) { - Token t = getToken(i); + // Base identifier tokens + if (kind == S_IDENTIFIER || kind == S_QUOTED_IDENTIFIER + || kind == DATA_TYPE || kind == K_DATETIMELITERAL + || kind == K_DATE_LITERAL) { + return true; + } + + // Non-reserved keywords + if (kind >= MIN_NON_RESERVED_WORD && kind <= MAX_NON_RESERVED_WORD) { + return true; + } + + return false; + } + + private static void requireTypeDdlSyntax(boolean valid, String message) throws ParseException { + if (!valid) { throw new ParseException(message); } + } + + private static > E typeDdlEnum(Class type, String value) throws ParseException { + try { return Enum.valueOf(type, value.toUpperCase(Locale.ROOT)); } + catch (IllegalArgumentException exception) { throw new ParseException("Unknown " + type.getSimpleName() + ": " + value); } + } + + private static void requireReplicationSyntax(boolean valid, String message) throws ParseException { + if (!valid) { throw new ParseException(message); } + } + + private static > E replicationEnum(Class type, String value) throws ParseException { + try { return Enum.valueOf(type, MultiPartName.unquote(value).toUpperCase(Locale.ROOT)); } + catch (IllegalArgumentException exception) { throw new ParseException("Unknown " + type.getSimpleName() + ": " + value); } + } + + private boolean isKeywordAhead(String keyword) { + Token token = getToken(1); + return token.image != null && keyword.equalsIgnoreCase(token.image); + } + + private boolean isXmlTypeName(String name) { + return "XML".equalsIgnoreCase(name) || "[XML]".equalsIgnoreCase(name) + || "\"XML\"".equalsIgnoreCase(name); + } + + private boolean isMySqlTableOptionAhead() { + int kind = getToken(1).kind; + if (kind == K_ENGINE || kind == K_COLLATE || kind == K_COMMENT + || kind == K_AUTO_INCREMENT) { + return true; + } + if (kind == K_CHARACTER && getToken(2).kind == K_SET + || kind == K_CHAR && getToken(2).kind == K_SET) { + return true; + } + if (kind == S_IDENTIFIER && "CHARSET".equalsIgnoreCase(getToken(1).image)) { + return true; + } + if (kind != K_DEFAULT) { + return false; + } + int secondKind = getToken(2).kind; + return secondKind == K_CHARACTER || secondKind == K_CHAR || secondKind == K_COLLATE + || secondKind == S_IDENTIFIER + && "CHARSET".equalsIgnoreCase(getToken(2).image); + } + + private boolean isTableIndexAhead() { + switch (getToken(1).kind) { + case K_PRIMARY: + case K_UNIQUE: + case K_INDEX: + case K_KEY: + case K_FULLTEXT: + case K_SPATIAL: + return true; + default: + return false; + } + } + + /** + * True when the pending {@code ON} opens the ON CONFLICT clause of the enclosing + * INSERT rather than another trailing join condition: after a join, the trailing + * ON expressions of Issue #1302 and the INSERT clause claim the same ON token. + * The clause shapes are ON CONFLICT DO, ON CONFLICT ON CONSTRAINT and + * ON CONFLICT ( target ) [ WHERE ] DO, so the parenthesized target has to be + * separated from an expression by scanning past its balanced brackets. + */ + private boolean isInsertOnConflictAhead() { + if (getToken(1).kind != K_ON || getToken(2).kind != K_CONFLICT) { + return false; + } + Token t = getToken(3); + if (t.kind == K_DO) { + return true; + } + if (t.kind == K_ON) { + return getToken(4).kind == K_CONSTRAINT; + } + if (!"(".equals(t.image)) { + return false; + } + int depth = 1; + int i = 4; + while (depth > 0) { + t = getToken(i++); + if (t == null || t.kind == EOF) { + return false; + } + if ("(".equals(t.image)) { + depth++; + } else if (")".equals(t.image)) { + depth--; + } + } + t = getToken(i); + if (t.kind == K_DO) { + return true; + } + if (t.kind != K_WHERE) { + return false; + } + // the index predicate ends where the conflict action's DO begins + depth = 0; + for (i++; ; i++) { + t = getToken(i); + if (t == null || t.kind == EOF) { + return false; + } + if ("(".equals(t.image)) { + depth++; + } else if (")".equals(t.image)) { + depth--; + } else if (t.kind == K_DO && depth == 0) { + return true; + } + } + } + + private static void requireAccessSyntax(boolean condition, String message) throws ParseException { + if (!condition) { throw new ParseException(message); } + } + + private static > E accessEnum(Class type, String value) throws ParseException { + try { return Enum.valueOf(type, value.toUpperCase(Locale.ROOT)); } + catch (IllegalArgumentException ex) { throw new ParseException("Invalid " + type.getSimpleName() + ": " + value); } + } + + private boolean isRoleOptionAhead(int offset) { + if (getToken(offset).kind == EOF) { return false; } + String word = getToken(offset).image.toUpperCase(Locale.ROOT); + return Arrays.asList("SUPERUSER", "NOSUPERUSER", "CREATEDB", "NOCREATEDB", "CREATEROLE", "NOCREATEROLE", + "INHERIT", "NOINHERIT", "LOGIN", "NOLOGIN", "REPLICATION", "NOREPLICATION", "BYPASSRLS", "NOBYPASSRLS", + "CONNECTION", "ENCRYPTED", "PASSWORD", "VALID", "IN", "ROLE", "USER", "ADMIN", "SYSID").contains(word); + } + + private boolean isPostgreSqlRoleAhead() { + if (isAccessKeywordAhead("ROLE") || isAccessKeywordAhead("GROUP")) { return true; } + return isAccessKeywordAhead("USER") && !"MAPPING".equalsIgnoreCase(getToken(2).image) + && (Dialect.POSTGRESQL.name().equals(getAsString(Feature.dialect)) + || "WITH".equalsIgnoreCase(getToken(3).image) || isRoleOptionAhead(3)); + } + + private boolean isAccessKeywordAhead(String word) { + return getToken(1).kind != EOF && isKeywordAhead(word); + } + + private boolean isAccessTargetKindAhead() { + Token next = getToken(2); + return next.kind != EOF && next.kind != K_TO && next.kind != K_FROM + && !",".equals(next.image) && !".".equals(next.image) + && isAccessTargetKindAhead(1); + } + + private boolean isAccessTargetKindAhead(int offset) { + return Arrays.asList("TABLE", "SEQUENCE", "DATABASE", "DOMAIN", "FOREIGN", "FUNCTION", "PROCEDURE", + "ROUTINE", "LANGUAGE", "LARGE", "PARAMETER", "SCHEMA", "TABLESPACE", "TYPE") + .contains(getToken(offset).image.toUpperCase(Locale.ROOT)); + } + + private boolean isObjectPrivilegeAhead() { + int depth = 0; + for (int i = 1; ; i++) { + Token token = getToken(i); + if (token.kind == EOF || ";".equals(token.image)) { return false; } + if ("(".equals(token.image)) { depth++; } + else if (")".equals(token.image)) { depth--; } + else if (depth == 0) { + if (token.kind == K_ON) { return true; } + if (token.kind == K_TO || token.kind == K_FROM) { return false; } + } + } + } + + private boolean isPostgreSqlTriggerAhead() { + if (getToken(1).kind == K_CONSTRAINT) { return true; } + if (getToken(1).kind != K_TRIGGER) { return false; } + for (int i = 2; ; i++) { + Token token = getToken(i); + if (token.kind == EOF || ";".equals(token.image) || token.kind == K_BEGIN) { return false; } + if (token.kind == K_EXECUTE && (getToken(i + 1).kind == K_FUNCTION || getToken(i + 1).kind == K_PROCEDURE)) { return true; } + // DuckDB 2.0 uses the standard header with a statement body instead of EXECUTE + if ("REFERENCING".equalsIgnoreCase(token.image) || token.kind == K_STATEMENT) { return true; } + } + } + + private boolean isMySqlDialect() { + String dialect = getAsString(Feature.dialect); + return Dialect.MYSQL.name().equals(dialect) || Dialect.MARIADB.name().equals(dialect); + } + + private boolean isMySqlSelectIntoAhead() { + if (getToken(1).kind != K_INTO) { return false; } + int kind = getToken(2).kind; + return kind == K_OUTFILE || kind == K_DUMPFILE + || isMySqlDialect() && (kind == S_AT_IDENTIFIER || kind == K_AT_SIGN); + } + + private static UserVariable requireMySqlIntoVariable(UserVariable variable) throws ParseException { + if (variable.isDoubleAdd()) { + throw new ParseException("INTO requires a user variable with a single @ prefix"); + } + return variable; + } + + private ExplainStatement.OptionType postgresqlExplainOptionType(String name) throws ParseException { + try { + return ExplainStatement.OptionType.from(name); + } catch (IllegalArgumentException ex) { + throw new ParseException("Unknown PostgreSQL EXPLAIN option: " + name); + } + } + + private void validatePostgresqlExplain(ExplainStatement explain) throws ParseException { + if (explain.getTable() != null) { + throw new ParseException("PostgreSQL EXPLAIN requires a statement"); + } + int previous = -1; + for (ExplainStatement.Option option : explain.getOptionList()) { + ExplainStatement.OptionType type = option.getType(); + String value = option.getValue(); + if (!explain.isParenthesizedOptions()) { + int position = type == ExplainStatement.OptionType.ANALYZE ? 0 + : type == ExplainStatement.OptionType.VERBOSE ? 1 : -1; + if (position <= previous || value != null) { + throw new ParseException("PostgreSQL EXPLAIN options require parentheses"); + } + previous = position; + } else { + boolean valid; + switch (type) { + case FORMAT: + valid = value != null && value.matches("(?i)TEXT|XML|JSON|YAML"); + break; + case SERIALIZE: + valid = value == null || value.matches("(?i)NONE|TEXT|BINARY"); + break; + case PLAN: case PLAN_FOR: + valid = false; + break; + default: + valid = value == null || value.matches("(?i)TRUE|FALSE|ON|OFF|0|1"); + } + if (!valid) { + throw new ParseException("Invalid PostgreSQL EXPLAIN option: " + option.formatOption()); + } + } + } + } + + private void requireDdlSyntax(boolean valid, String message) throws ParseException { + if (!valid) { + throw new ParseException(message); + } + } + + private LikeClause.OptionKind likeOptionKind(String name) throws ParseException { + try { + return LikeClause.OptionKind.valueOf(name.toUpperCase(Locale.ROOT)); + } catch (IllegalArgumentException ex) { + throw new ParseException("Unknown LIKE option: " + name); + } + } + + private boolean isIdentityAlterationAhead() { + int first = getToken(1).kind; + int second = getToken(2).kind; + if ((first == K_ADD || first == K_SET) + && "GENERATED".equalsIgnoreCase(getToken(2).image)) { + return true; + } + if (first == K_RESTART || (first == K_DROP && second == K_IDENTITY)) { + return true; + } + if (first == K_SET) { + switch (second) { + case K_INCREMENT: case K_START: case K_MINVALUE: case K_MAXVALUE: + case K_NO: case K_CACHE: case K_CYCLE: + return true; + default: + return false; + } + } + return false; + } + + /** Attaches the structured index and populates the remaining legacy constraint metadata. */ + private static void setAlterTableIndex(AlterExpression alterExp, Index index) { + alterExp.setIndex(index); + if (index.getKind() == Index.Kind.UNIQUE) { + alterExp.setUkName(index instanceof NamedConstraint + ? ((NamedConstraint) index).getIndexName() : index.getName()); + alterExp.setUk(index.getType().toUpperCase(Locale.ROOT).contains("KEY")); + alterExp.setUkTypeSpecified(index.getIndexKeyword() != null); + for (String option : new ArrayList(index.getIndexSpec())) { + if (option.toUpperCase(Locale.ROOT).startsWith("USING ")) { + alterExp.addParameters("USING"); + alterExp.addParameters(option.substring("USING ".length())); + index.getIndexSpec().remove(option); + } else if (option.toUpperCase(Locale.ROOT).startsWith("COMMENT ")) { + index.setCommentText(option.substring("COMMENT ".length())); + index.getIndexSpec().remove(option); + } + } + } + if (index instanceof ForeignKeyIndex) { + ForeignKeyIndex foreignKey = (ForeignKeyIndex) index; + alterExp.setFkColumns(foreignKey.getColumnsNames()); + if (foreignKey.getTable() != null) { + alterExp.setFkSourceSchema(foreignKey.getTable().getSchemaName()); + alterExp.setFkSourceTable(foreignKey.getTable().getName()); + } + alterExp.setFkSourceColumns(foreignKey.getReferencedColumnNames()); + for (ReferentialAction.Type type : ReferentialAction.Type.values()) { + ReferentialAction action = foreignKey.getReferentialAction(type); + if (action != null) { + alterExp.setReferentialAction(action.getType(), action.getAction()); + } + } + } + } + + private void requireNoDropIndexOwner(Table owner) throws ParseException { + if (owner != null) { + throw new ParseException("Duplicate DROP INDEX ON clause"); + } + } + + private static boolean hasStructuredColumnOption(List options) { + for (ColumnOption option : options) { + if (option.getKind() != ColumnOption.Kind.OTHER) { + return true; + } + } + return false; + } + + /** + * Lightweight lookahead for SpecialStringFunctionWithNamedParameters: + * scans forward from the current position (just past the opening '(') + * looking for FROM, IN, or PLACING at bracket nesting depth 0. + * + * This replaces an expensive syntactic LOOKAHEAD(NamedExpressionListExprFirst()) + * that caused exponential backtracking with deeply nested expressions. + */ + protected boolean isNamedExprListAhead() { + int depth = 0; + for (int i = 1; ; i++) { + Token t = getToken(i); if (t.kind == EOF) { return false; } @@ -860,6 +1861,24 @@ public class CCJSqlParser extends AbstractJSqlParser { } } + /** + * Semantic lookahead for the adjacent string literal concatenation in PrimaryExpression: + * true when Feature.adjacentStringLiterals is on and the next token is a string literal + * meeting the mode's separation rule (WHITESPACE: any, NEWLINE: a newline between the + * tokens, detected through the token line numbers). + */ + protected boolean isAdjacentStringConcat() { + String mode = getAsString(Feature.adjacentStringLiterals); + if (mode == null || AdjacentStringLiterals.OFF.name().equals(mode)) { + return false; + } + if (getToken(1).kind != S_CHAR_LITERAL) { + return false; + } + return AdjacentStringLiterals.WHITESPACE.name().equals(mode) + || getToken(1).beginLine > getToken(0).endLine; + } + /** * Checks if the next token can start a condition suffix * (comparison, IN, BETWEEN, LIKE, IS NULL, etc.) @@ -873,6 +1892,9 @@ public class CCJSqlParser extends AbstractJSqlParser { if (t.kind == OPENING_BRACKET && getToken(2).image.equals("+")) { return isComparisonOperator(getToken(4)); } + // ClickHouse ternary `cond ? then : else` — the `?` must not be taken as the + // PostgreSQL JSON operator, it is consumed by prattExpressionRest instead + if ("?".equals(t.image) && isTernaryAhead()) return false; if (isComparisonOperator(t)) return true; switch (t.kind) { // Each suffix's start token: @@ -891,6 +1913,7 @@ public class CCJSqlParser extends AbstractJSqlParser { case K_RLIKE: // RLIKE case K_REGEXP_LIKE: // REGEXP_LIKE case K_REGEXP: // REGEXP + case K_GLOB: // GLOB (DuckDB/SQLite) case K_SIMILAR_TO: // SIMILAR TO (in LikeExpression) case K_SIMILAR: // SIMILAR TO (in SimilarToExpression) case K_MATCH_ANY: // MATCH_ANY @@ -898,8 +1921,10 @@ public class CCJSqlParser extends AbstractJSqlParser { case K_MATCH_PHRASE: // MATCH_PHRASE case K_MATCH_PHRASE_PREFIX: // MATCH_PHRASE_PREFIX case K_MATCH_REGEXP: // MATCH_REGEXP - case K_NOT: // NOT IN / NOT BETWEEN / NOT LIKE / NOT ISNULL / NOT SIMILAR return true; + case K_NOT: // NOT IN / NOT BETWEEN / NOT LIKE / NOT ISNULL / NOT SIMILAR + // A column's NOT NULL constraint starts after its DEFAULT expression. + return getToken(2).kind != K_NULL; // Oracle (+) before IN: col(+) IN (...) case OPENING_BRACKET: return getToken(2).image.equals("+"); @@ -908,6 +1933,32 @@ public class CCJSqlParser extends AbstractJSqlParser { } } + private boolean isLargeObjectTypeAhead() { + int offset = 1; + if (isKeywordAhead("NATIONAL")) { + offset++; + if (!"CHARACTER".equalsIgnoreCase(getToken(offset).image)) { + return false; + } + } + String name = getToken(offset).image; + return ("CHARACTER".equalsIgnoreCase(name) || "CHAR".equalsIgnoreCase(name) + || "NCHAR".equalsIgnoreCase(name) || "BINARY".equalsIgnoreCase(name)) + && "LARGE".equalsIgnoreCase(getToken(offset + 1).image) + && "OBJECT".equalsIgnoreCase(getToken(offset + 2).image); + } + + /** + * Extracts the numeric precision embedded in a DT_ZONE token image such as + * "TIMESTAMP(3) WITH TIME ZONE", or null when the image carries no parameter. + */ + private static Integer zonedTypePrecision(String image) { + int open = image.indexOf('('); + if (open < 0) { + return null; + } + return Integer.valueOf(image.substring(open + 1, image.indexOf(')', open)).trim()); + } } @@ -957,10 +2008,23 @@ TOKEN_MGR_DECLS : { return -1; } + private RowPatternTokenContext rowPatternContext; + public void CommonTokenAction(Token t) { t.absoluteBegin = getCurrentTokenAbsolutePosition(); t.absoluteEnd = t.absoluteBegin + t.image.length(); + // Track the lexical region independently of parser lookahead: nested SELECT + // disambiguation can fetch PATTERN tokens before its production is entered. + if (rowPatternContext == null && t.kind == S_IDENTIFIER + && "MATCH_RECOGNIZE".equalsIgnoreCase(t.image) + && AbstractJSqlParser.Dialect.BIGQUERY.name().equals(configuration.getValue(Feature.dialect))) { + rowPatternContext = new RowPatternTokenContext(); + } + if (rowPatternContext != null) { + rowPatternContext.accept(t); + if (rowPatternContext.isIdle()) { rowPatternContext = null; } + } } public int getCurrentTokenAbsolutePosition() @@ -970,33 +2034,27 @@ TOKEN_MGR_DECLS : { return -1; } - private static boolean endsWithDelimiter(Deque windowQueue, String delimiter) { - if (windowQueue.size() != delimiter.length()) { - return false; - } - - int i = 0; - for (char ch : windowQueue) { - if (ch != delimiter.charAt(i++)) { - return false; + /** Scans a literal in linear time without tokenizing or rebuilding its whitespace. */ + public void consumeDollarQuotedString(String closingQuote) { + int[] prefix = new int[closingQuote.length()]; + for (int i = 1, matched = 0; i < closingQuote.length(); i++) { + while (matched > 0 && closingQuote.charAt(i) != closingQuote.charAt(matched)) { + matched = prefix[matched - 1]; } + if (closingQuote.charAt(i) == closingQuote.charAt(matched)) { + matched++; + } + prefix[i] = matched; } - return true; - } - - public void consumeDollarQuotedString(String closingQuote) { - Deque windowQueue = new ArrayDeque(); - int delimiterLength = closingQuote.length(); - try { - while (true) { + int matched = 0; + while (matched < closingQuote.length()) { char ch = input_stream.readChar(); - windowQueue.addLast(ch); - if (windowQueue.size() > delimiterLength) { - windowQueue.removeFirst(); + while (matched > 0 && ch != closingQuote.charAt(matched)) { + matched = prefix[matched - 1]; } - if (endsWithDelimiter(windowQueue, closingQuote)) { - return; + if (ch == closingQuote.charAt(matched)) { + matched++; } } } catch (java.io.IOException e) { @@ -1004,6 +2062,24 @@ TOKEN_MGR_DECLS : { } } + /** Rewinds any identifier suffix consumed by longest-match lexing before scanning the body. */ + private void consumeDollarQuotedToken(Token token, String delimiter) { + input_stream.backup(token.image.length() - delimiter.length()); + consumeDollarQuotedString(delimiter); + token.image = input_stream.GetImage(); + token.kind = charLiteralIndex; + token.endLine = input_stream.getEndLine(); + token.endColumn = input_stream.getEndColumn(); + } + + /** Splits a longest-match identifier without losing token positions. */ + private void truncateIdentifierToken(Token token, int length) { + input_stream.backup(token.image.length() - length); + token.image = token.image.substring(0, length); + token.endLine = input_stream.getEndLine(); + token.endColumn = input_stream.getEndColumn(); + } + /** * Consumes the body of a block comment after the opening delimiter has been matched, * honouring nesting, up to and including the outermost closing delimiter. Then backs @@ -1065,15 +2141,20 @@ String NonReservedWord() : | tk= | tk= | tk= + | tk= | tk= + | tk= | tk= | tk= + | tk= | tk= | tk= + | tk= | tk= | tk= | tk= | tk= + | tk= | tk= | tk= | tk= @@ -1090,6 +2171,7 @@ String NonReservedWord() : | tk= | tk= | tk= + | tk= | tk= | tk= | tk= @@ -1108,12 +2190,15 @@ String NonReservedWord() : | tk= | tk= | tk= + | tk= | tk= | tk= | tk= | tk= + | tk= | tk= | tk= + | tk= | tk= | tk= | tk= @@ -1128,6 +2213,7 @@ String NonReservedWord() : | tk= | tk= | tk= + | tk= | tk= | tk= | tk= @@ -1135,9 +2221,11 @@ String NonReservedWord() : | tk= | tk= | tk= + | tk= | tk= | tk= | tk= + | tk= | tk= | tk= | tk= @@ -1156,11 +2244,14 @@ String NonReservedWord() : | tk= | tk= | tk= + | tk= | tk= | tk= | tk= | tk= | tk= + | tk= + | tk= | tk= | tk= | tk= @@ -1176,19 +2267,24 @@ String NonReservedWord() : | tk= | tk= | tk= + | tk= + | tk= | tk= | tk= | tk= | tk= | tk= | tk= + | tk= | tk= + | tk= | tk= | tk= | tk= | tk= | tk= | tk= + | tk= | tk= | tk= | tk= @@ -1197,9 +2293,11 @@ String NonReservedWord() : | tk= | tk= | tk= + | tk= | tk= | tk= | tk= + | tk= | tk= | tk= | tk= @@ -1221,7 +2319,10 @@ String NonReservedWord() : | tk= | tk= | tk= + | tk= | tk= + | tk= + | tk= | tk= | tk= | tk= @@ -1238,25 +2339,31 @@ String NonReservedWord() : | tk= | tk= | tk= + | tk= | tk= | tk= + | tk= | tk= | tk= | tk= | tk= + | tk= | tk= | tk= + | tk= | tk= | tk= | tk= | tk= | tk= | tk= + | tk= | tk= | tk= | tk= | tk= | tk= + | tk= | tk= | tk= | tk= @@ -1268,6 +2375,7 @@ String NonReservedWord() : | tk= | tk= | tk= + | tk= | tk= | tk= | tk= @@ -1284,11 +2392,15 @@ String NonReservedWord() : | tk= | tk= | tk= + | tk= | tk= + | tk= | tk= | tk= | tk= | tk= + | tk= + | tk= | tk= | tk= | tk= @@ -1297,6 +2409,7 @@ String NonReservedWord() : | tk= | tk= | tk= + | tk= | tk= | tk= | tk= @@ -1312,12 +2425,14 @@ String NonReservedWord() : | tk= | tk= | tk= + | tk= | tk= | tk= | tk= | tk= | tk= | tk= + | tk= | tk= | tk= | tk= @@ -1333,6 +2448,7 @@ String NonReservedWord() : | tk= | tk= | tk= + | tk= | tk= | tk= | tk= @@ -1347,16 +2463,24 @@ String NonReservedWord() : | tk= | tk= | tk= + | tk= | tk= | tk= + | tk= | tk= + | tk= + | tk= | tk= + | tk= | tk= + | tk= | tk= | tk= | tk= | tk= | tk= + | tk= + | tk= | tk= | tk= | tk= @@ -1414,6 +2538,7 @@ String NonReservedWord() : | tk= | tk= | tk= + | tk= | tk= ) { return tk.image; } @@ -1470,7 +2595,6 @@ TOKEN: /* Reserved SQL Keywords and structural tokens */ | | | -| | | | @@ -1586,6 +2710,8 @@ TOKEN : /* Date/Time with time zones */ TOKEN : /* Data Types */ { + // Dedicated keyword tokens (e.g. K_BINARY/K_CHAR) are consumed by DataType(). + // Repeating them here cannot match DATA_TYPE and causes unreachable-token warnings. | | | | | | | | | | | | | | @@ -1594,15 +2720,15 @@ TOKEN : /* Data Types */ ) > | <#TYPE_BIT: "BISTRING"> - | <#TYPE_BLOB: "BLOB" | "BYTEA" | | "VARBINARY" | > - | <#TYPE_BOOLEAN: | "BOOL" > + | <#TYPE_BLOB: "BLOB" | "BYTEA" | "VARBINARY" > + | <#TYPE_BOOLEAN: "BOOL" > | <#TYPE_CLOB: "CLOB"> | <#TYPE_ENUM: "ENUM" > | <#TYPE_MAP: "MAP" > | <#TYPE_DECIMAL: "DECIMAL" | "NUMBER" | "NUMERIC" > | <#TYPE_TINYINT: "TINYINT" | "INT1" > | <#TYPE_SMALLINT: "SMALLINT" | "INT2" | "SHORT" > - | <#TYPE_INTEGER: ( "INTEGER" | "INT" | "INT4" | | ) > + | <#TYPE_INTEGER: ( "INTEGER" | "INT" | "INT4" ) > | <#TYPE_BIGINT: "BIGINT" | "INT8" | "LONG" > | <#TYPE_HUGEINT: "HUGEINT" > | <#TYPE_UTINYINT: "UTINYINT" > @@ -1612,7 +2738,7 @@ TOKEN : /* Data Types */ | <#TYPE_UHUGEINT: "UHUGEINT" > | <#TYPE_REAL: "REAL" | "FLOAT4" | "FLOAT"> | <#TYPE_DOUBLE: "DOUBLE" | "PRECISION" | "FLOAT8" | "FLOAT64"> - | <#TYPE_VARCHAR: "NVARCHAR" | "VARCHAR" | "NCHAR" | | "BPCHAR" | "TEXT" | "STRING" | | "VARYING"> + | <#TYPE_VARCHAR: "NVARCHAR" | "VARCHAR" | "NCHAR" | "BPCHAR" | "TEXT" | "STRING" | "VARYING"> | <#TYPE_TIME: "TIMETZ" > | <#TYPE_TIMESTAMP: "TIMESTAMP_NS" | "TIMESTAMP_MS" | "TIMESTAMP_S" > @@ -1639,6 +2765,19 @@ SPECIAL_TOKEN: < LINE_COMMENT: ("--" | "//") (~["\r","\n"])*> } +// MySQL `#` line comment (#2499), flag-gated. Under +// Feature.allowHashLineComments the stream rewrites a token-start `#` to +// SimpleCharStream.HASH_SUBSTITUTION, a character no other rule starts with, +// so this production wins the match for every `#` form (`# c`, `#c`, `#>`, +// ...) without touching the identifier and JSON-operator lexing of the +// default mode. The action restores the `#` in the token image; unquoted +// identifiers end at their first `#` via the S_IDENTIFIER action, which +// re-lexes the remainder as this comment. +SPECIAL_TOKEN: +{ + < HASH_LINE_COMMENT: "\u0001" (~["\r","\n"])*> +} + // Nested block comments: /* ... /* ... */ ... */ // // The body is consumed manually in the MORE action below because JavaCC 8 @@ -1666,18 +2805,82 @@ TOKEN: | { - consumeDollarQuotedString(matchedToken.image); - matchedToken.image = input_stream.GetImage(); - matchedToken.kind = charLiteralIndex; + if (rowPatternContext != null && rowPatternContext.isInPattern()) { + truncateIdentifierToken(matchedToken, 1); + matchedToken.kind = S_IDENTIFIER; + } else { + consumeDollarQuotedToken(matchedToken, matchedToken.image); + } } | +// Bare `#` as a binary operator (PostgreSQL bitwise XOR / geometric +// intersection). Declared before to win the length tie on a +// lone `#`; `#temp`-style identifiers and `#>` / `#>>` keep their usual +// lexing by longest match (#1197, #1695). + +| ()*) | "$" | ("$" ()*)> +{ + if (rowPatternContext != null && rowPatternContext.isInPattern()) { + int dollar = matchedToken.image.indexOf('$'); + if (dollar >= 0) { truncateIdentifierToken(matchedToken, Math.max(1, dollar)); } + } + if (matchedToken.image.charAt(0) == '$' + && Boolean.TRUE.equals(configuration.getValue(Feature.allowDollarQuotedStringTags))) { + String delimiter = StringValue.getDollarQuoteDelimiter(matchedToken.image); + if (delimiter != null) { + consumeDollarQuotedToken(matchedToken, delimiter); + } + } + // MySQL `#` line comments (#2499): under the flag an unquoted identifier + // ends at its first `#`, the rest of the line becomes a comment via the + // stream-level substitution (real MySQL reads `42#24` as `42` plus + // comment too). Quoted identifiers and strings keep their `#`. + // the wiring check keeps the flag lookup off the hot path of parses + // that never opted in (the stream is only wired through the feature + // consumers / withConfiguration); getValue avoids the String-based + // getAsBoolean roundtrip + if (matchedToken.kind == S_IDENTIFIER && input_stream.featureConfiguration != null + && Boolean.TRUE.equals(configuration.getValue(Feature.allowHashLineComments))) { + int hashIndex = matchedToken.image.indexOf('#'); + if (hashIndex > 0) { + truncateIdentifierToken(matchedToken, hashIndex); + } + } + // PostgreSQL does not allow # in unquoted identifiers. Re-lex it as an + // operator, including #> and #>>, even when it touches the left operand. + if (matchedToken.kind == S_IDENTIFIER + && AbstractJSqlParser.Dialect.POSTGRESQL.name().equals(configuration.getValue(Feature.dialect))) { + int hashIndex = matchedToken.image.indexOf('#'); + if (hashIndex > 0) { + truncateIdentifierToken(matchedToken, hashIndex); + } else if (hashIndex == 0) { + truncateIdentifierToken(matchedToken, 1); + matchedToken.kind = S_HASH_OPERATOR; + } + } +} | <#LETTER: | | [ "#", "_" ] // Not SQL:2016 compliant! > | <#PART_LETTER_NO_DOLLAR: | | [ "#", "_" , "@" ] > | <#PART_LETTER: | | [ "$" , "#", "_" , "@" ] > | ()? > +{ + // same truncation as S_IDENTIFIER: `@@#name` ends at the `#` under + // allowHashLineComments, the rest of the line becomes the comment + // the wiring check keeps the flag lookup off the hot path of parses + // that never opted in (the stream is only wired through the feature + // consumers / withConfiguration); getValue avoids the String-based + // getAsBoolean roundtrip + if (input_stream.featureConfiguration != null + && Boolean.TRUE.equals(configuration.getValue(Feature.allowHashLineComments))) { + int hashIndex = matchedToken.image.indexOf('#'); + if (hashIndex > 0) { + truncateIdentifierToken(matchedToken, hashIndex); + } + } +} // Unicode characters and categories are defined here: https://www.unicode.org/Public/UNIDATA/UnicodeData.txt // SQL:2016 states: @@ -1730,22 +2933,22 @@ TOKEN: if (!allowEscape) { pos = indexOfSequence(img, "\\'"); if (pos > 0) { - matchedToken.image = "'" + image.substring( 0, image.indexOf("\\'") + 1 ) + "'"; + matchedToken.image = image.substring( 0, image.indexOf("\\'") + 1 ) + "'"; // `charLiteralIndex` defined in TokenManagerDeclaration above matchedToken.kind = charLiteralIndex; - input_stream.backup(image.length() + 1 - matchedToken.image.length()); + input_stream.backup(image.length() - matchedToken.image.length()); } } else { pos = lastIndexOfSequence(img, "\\''"); if (pos > 0) { - matchedToken.image = "'" + image.substring( 0, image.lastIndexOf("\\'") + 3); + matchedToken.image = image.substring( 0, image.lastIndexOf("\\'") + 3); // `charLiteralIndex` defined in TokenManagerDeclaration above matchedToken.kind = charLiteralIndex; - input_stream.backup(image.length() + 1 - matchedToken.image.length() ); + input_stream.backup(image.length() - matchedToken.image.length() ); } } } -| < S_QUOTED_IDENTIFIER: "\"" ( "\"\"" | ~["\n","\r","\""])* "\"" | ("`" (~["\n","\r","`"])+ "`") | ( "[" (~["\n","\r","]"])* "]" ) > +| < S_QUOTED_IDENTIFIER: "\"" ( "\"\"" | ~["\n","\r","\""])* "\"" | ("`" ("``" | ~["\n","\r","`"])* "`") | ( "[" (~["\n","\r","]"])* "]" ) > { if ( !configuration.getAsBoolean(Feature.allowSquareBracketQuotation) && matchedToken.image.charAt(0) == '[' ) { @@ -1755,6 +2958,11 @@ TOKEN: matchedToken.kind = squaredBracketOpenIndex; input_stream.backup(image.length() - 1); } + if ( configuration.getAsBoolean(Feature.allowDoubleQuotedStrings) + && matchedToken.image.charAt(0) == '"' ) { + // `charLiteralIndex` defined in TokenManagerDeclaration above + matchedToken.kind = charLiteralIndex; + } } } @@ -1786,6 +2994,7 @@ Token KeywordOrIdentifier(): | tk = | tk = | tk = + | tk = ) { return tk; } } @@ -1852,6 +3061,21 @@ Statement SingleStatement() : } { ( + LOOKAHEAD({ Dialect.ORACLE.name().equals(getAsString(Feature.dialect)) + && (getToken(1).kind == K_DECLARE || getToken(1).kind == K_BEGIN) }) + stm = OracleBlock() + | + LOOKAHEAD({ Dialect.SQLSERVER.name().equals(getAsString(Feature.dialect)) + && ((getToken(1).kind == K_ALTER && (getToken(2).kind == K_FUNCTION || getToken(2).kind == K_PROCEDURE)) + || (getToken(1).kind == K_CREATE && (getToken(2).kind == K_FUNCTION || getToken(2).kind == K_PROCEDURE + || (getToken(2).kind == K_OR && getToken(3).kind == K_ALTER + && (getToken(4).kind == K_FUNCTION || getToken(4).kind == K_PROCEDURE))))) }) + stm = SqlServerRoutine() + | + LOOKAHEAD({ Dialect.SQLSERVER.name().equals(getAsString(Feature.dialect)) + && getToken(1).kind == K_INSERT && "BULK".equalsIgnoreCase(getToken(2).image) }) + stm = InsertBulk() + | LOOKAHEAD(3) ( [ LOOKAHEAD(2) with=WithList() ] ( @@ -1888,6 +3112,15 @@ Statement SingleStatement() : | stm = Execute() | + LOOKAHEAD({ getToken(1).kind == K_DO && Dialect.POSTGRESQL.name().equals(getAsString(Feature.dialect)) }) + stm = DoStatement() + | + LOOKAHEAD({ Dialect.SQLSERVER.name().equals(getAsString(Feature.dialect)) + && getToken(1).kind == K_SET && getToken(2).kind == S_IDENTIFIER + && "IDENTITY_INSERT".equalsIgnoreCase(getToken(2).image) + && !"=".equals(getToken(3).image) && !".".equals(getToken(3).image) }) + stm = SetIdentityInsert() + | stm = Set() | stm = Reset() @@ -1914,6 +3147,37 @@ Statement SingleStatement() : | stm = Grant() | + LOOKAHEAD({ isKeywordAhead("REVOKE") }) stm = Revoke(false) + | + LOOKAHEAD({ getToken(1).kind == K_PRAGMA }) stm = PragmaStatement() + | + LOOKAHEAD({ getToken(1).kind == K_INSTALL + || (getToken(1).kind == K_LOAD && getToken(2).kind != K_DATA) + || (getToken(1).kind == K_FORCE && getToken(2).kind == K_INSTALL) }) + stm = ExtensionStatement() + | + LOOKAHEAD({ getToken(1).kind == K_ATTACH }) stm = AttachStatement() + | + LOOKAHEAD({ getToken(1).kind == K_DETACH }) stm = DetachStatement() + | + LOOKAHEAD({ getToken(1).kind == K_CONNECT && getToken(2).kind != K_BY }) + stm = ConnectStatement() + | + LOOKAHEAD({ getToken(1).kind == K_DISCONNECT && getToken(2).kind != K_SESSION }) + stm = DisconnectStatement() + | + LOOKAHEAD({ getToken(1).kind == K_PREPARE }) stm = PrepareStatement() + | + LOOKAHEAD({ getToken(1).kind == K_DEALLOCATE }) stm = DeallocateStatement() + | + LOOKAHEAD({ getToken(1).kind == K_COPY }) stm = CopyStatement() + | + LOOKAHEAD({ getToken(1).kind == K_ASSERT }) stm = AssertStatement() + | + LOOKAHEAD({ getToken(1).kind == K_EXPORT && getToken(2).kind == K_DATA }) stm = ExportDataStatement() + | + LOOKAHEAD({ getToken(1).kind == K_LOAD && getToken(2).kind == K_DATA }) stm = LoadDataStatement() + | stm = PurgeStatement() | stm = SessionStatement() @@ -1925,21 +3189,121 @@ Statement SingleStatement() : { return stm; } } -Block Block() #Block : { - Statements stmts = new Statements(); - List list = new ArrayList(); - Statement stm; - Block block = new Block(); +OracleBlock OracleBlock(): +{ + OracleBlock block = new OracleBlock(); + OracleBlock.VariableDeclaration declaration; + OracleBlock.ExceptionHandler handler; + Statements statements; } { - - ()* - ( - stm = SingleStatement() - | stm = Block() - ) - - { list.add(stm); } + [ declaration=OracleVariableDeclaration() { block.getDeclarations().add(declaration); } + ( LOOKAHEAD({ getToken(1).kind != K_BEGIN }) + declaration=OracleVariableDeclaration() { block.getDeclarations().add(declaration); } )* ] + statements=OracleBlockStatements() { block.setStatements(statements); } + [ LOOKAHEAD({ isKeywordAhead("EXCEPTION") }) AccessKeyword("EXCEPTION") + handler=OracleExceptionHandler() { block.getExceptionHandlers().add(handler); } + ( handler=OracleExceptionHandler() { block.getExceptionHandlers().add(handler); } )* ] + + { block.setSemicolonAfterEnd(true); return block; } +} + +OracleBlock.VariableDeclaration OracleVariableDeclaration(): +{ + OracleBlock.VariableDeclaration declaration = new OracleBlock.VariableDeclaration(); + String name; ColDataType dataType; Expression initializer; +} +{ + name=RelObjectName() { declaration.setName(name); } + [ LOOKAHEAD({ isKeywordAhead("CONSTANT") }) AccessKeyword("CONSTANT") { declaration.setConstant(true); } ] + dataType=ColDataType() { declaration.setDataType(dataType); } + [ { declaration.setNotNull(true); } ] + [ ( ":=" | { declaration.setInitializerOperator("DEFAULT"); } ) + initializer=Expression() { declaration.setInitializer(initializer); } ] + + { requireDdlSyntax((!declaration.isConstant() && !declaration.isNotNull()) || declaration.getInitializer() != null, + "A CONSTANT or NOT NULL variable requires an initializer"); return declaration; } +} + +Statements OracleBlockStatements(): +{ Statements statements = new Statements(); Statement statement; } +{ + statement=OracleBlockStatement() { statements.add(statement); } + ( LOOKAHEAD({ getToken(1).kind != K_END && getToken(1).kind != K_WHEN && !isKeywordAhead("EXCEPTION") }) + statement=OracleBlockStatement() { statements.add(statement); } )* + { return statements; } +} + +Statement OracleBlockStatement(): +{ Statement statement; Expression target; Expression value; } +{ + ( + LOOKAHEAD({ getToken(1).kind == K_DECLARE || getToken(1).kind == K_BEGIN }) statement=OracleBlock() + | LOOKAHEAD(Column() ":=") target=Column() ":=" value=Expression() + { statement = new OracleAssignment(target, value); } + | LOOKAHEAD(JdbcNamedParameter() ":=") target=JdbcNamedParameter() ":=" value=Expression() + { statement = new OracleAssignment(target, value); } + | { statement = new OracleNullStatement(); } + | LOOKAHEAD(ColumnIdentifier() ("(" | )) + statement=OracleImplicitCall() + | statement=SingleStatement() + ) + { return statement; } +} + +Execute OracleImplicitCall(): +{ Execute call = new Execute(); ObjectNames name; ExpressionList arguments; } +{ + name=ColumnIdentifier() { call.setName(name.getNames()); call.setExecType(Execute.ExecType.IMPLICIT); + requireDdlSyntax(!Arrays.asList("RAISE", "RETURN", "GOTO", "EXIT", "CONTINUE", "LOOP", "WHILE", "FOR") + .contains(call.getName().toUpperCase(java.util.Locale.ROOT)), "Unsupported PL/SQL control statement"); } + [ LOOKAHEAD("(") arguments=ExpressionList() { call.setExprList(arguments); } ] + { return call; } +} + +OracleBlock.ExceptionHandler OracleExceptionHandler(): +{ OracleBlock.ExceptionHandler handler = new OracleBlock.ExceptionHandler(); String name; Statements body; } +{ + name=TypeDdlName() { handler.getExceptions().add(name); } + ( name=TypeDdlName() { handler.getExceptions().add(name); } )* + body=OracleBlockStatements() { handler.setStatements(body); return handler; } +} + +DoStatement DoStatement(): +{ + DoStatement statement = new DoStatement(); + String language; + Token code; +} +{ + + ( + LOOKAHEAD({ isKeywordAhead("LANGUAGE") }) + AccessKeyword("LANGUAGE") language=RelObjectName() code= + { statement.setLanguage(language); statement.setLanguageBeforeCode(true); } + | + code= + [ LOOKAHEAD({ isKeywordAhead("LANGUAGE") }) AccessKeyword("LANGUAGE") + language=RelObjectName() { statement.setLanguage(language); } ] + ) + { statement.setCode(new StringValue(code.image)); return statement; } +} + +Block Block() #Block : { + Statements stmts = new Statements(); + List list = new ArrayList(); + Statement stm; + Block block = new Block(); +} +{ + + ()* + ( + stm = SingleStatement() + | stm = Block() + ) + + { list.add(stm); } ( ( @@ -2125,37 +3489,22 @@ LikeClause LikeClause(): { LikeClause likeClause = new LikeClause(); Table table; List> columnsList; + boolean including; + String option; } { table = Table() { likeClause.setTable(table); } - [ "(" columnsList = ColumnSelectItemsList() ")" { likeClause.setColumnsList(columnsList); }] - - [ - LOOKAHEAD(2) - ( - { likeClause.setIncludingDefaults(true); } - | { likeClause.setExcludingDefaults(true); } - ) - - ] - - [ - LOOKAHEAD(2) - ( - { likeClause.setIncludingIdentity(true); } - | { likeClause.setExcludingIdentity(true); } - ) - - ] - - [ - LOOKAHEAD(2) + [ LOOKAHEAD(2) "(" columnsList = ColumnSelectItemsList() ")" { likeClause.setColumnsList(columnsList); }] + ( ( - { likeClause.setIncludingComments(true); } - | { likeClause.setExcludingComments(true); } + { including = true; } + | { including = false; } ) - - ] + option=RelObjectName() + { + likeClause.addOption(likeOptionKind(option), including); + } + )* { return likeClause; @@ -3080,7 +4429,22 @@ SessionStatement SessionStatement(): } SetStatement Set(): { - String namePart; + SetStatement set; +} +{ + + ( + LOOKAHEAD({ Dialect.SQLSERVER.name().equals(getAsString(Feature.dialect)) + && getToken(1).kind == S_IDENTIFIER + && SetStatement.OnOffOption.fromName(getToken(1).image) != null + && !"=".equals(getToken(2).image) && !".".equals(getToken(2).image) }) + set=SqlServerSetOnOffOptions() + | set=SetAssignments() + ) + { return set; } +} + +SetStatement SetAssignments(): { Object name; ExpressionList expList; boolean useEqual = false; @@ -3090,7 +4454,6 @@ SetStatement Set(): { String effectParameter = null; } { - [LOOKAHEAD(3) (tk = | tk = ) {effectParameter = tk.image; } ] ( LOOKAHEAD(2) @@ -3138,6 +4501,33 @@ SetStatement Set(): { { return set; } } +SetStatement SqlServerSetOnOffOptions(): { + SetStatement set = new SetStatement(); + List options = new ArrayList(); + SetStatement.OnOffOption option; + Token name; + boolean on; +} +{ + name= + { + option = accessEnum(SetStatement.OnOffOption.class, name.image); + options.add(option); + } + ( + "," name= + { + option = accessEnum(SetStatement.OnOffOption.class, name.image); + options.add(option); + } + )* + ( { on = true; } | { on = false; } ) + { + set.setOnOffOptions(new SetStatement.OnOffOptions(options, on)); + return set; + } +} + ResetStatement Reset(): { String name; ResetStatement reset; @@ -3189,138 +4579,546 @@ RenameTableStatement RenameTableStatement(): { } } -PurgeStatement PurgeStatement(): { - PurgeStatement purgeStatement = null; - Table table; - Index index; - Token tableSpaceToken; - Token userToken = null; +/** + * DuckDB's CREATE [OR REPLACE] [TEMPORARY] MACRO name (parameters) AS [TABLE] body. + */ +CreateMacro CreateMacro(boolean isUsingOrReplace) #CreateMacro: +{ + CreateMacro createMacro = new CreateMacro(); + List parameters = new ArrayList(); + ObjectNames name; + Expression expression; + Select select; + String parameterName; + Expression defaultValue; } { - + { createMacro.setOrReplace(isUsingOrReplace); } + [ ( | ) { createMacro.setTemporary(true); } ] + name=RelObjectNames() { createMacro.setName(String.join(".", name.getNames())); } + "(" + [ + parameterName=RelObjectName() { defaultValue = null; } + [ ":=" defaultValue=Expression() ] + { parameters.add(new CreateMacro.Parameter(parameterName, defaultValue)); } + ( + "," parameterName=RelObjectName() { defaultValue = null; } + [ ":=" defaultValue=Expression() ] + { parameters.add(new CreateMacro.Parameter(parameterName, defaultValue)); } + )* + ] + ")" { createMacro.setParameters(parameters); } + ( - table=Table() { purgeStatement = new PurgeStatement(table); } + LOOKAHEAD(2) select=Select() { createMacro.setSelect(select); } | - index=Index() { purgeStatement = new PurgeStatement(index); } + expression=Expression() { createMacro.setExpression(expression); } + ) + { return createMacro; } +} + +/** + * DuckDB's COPY table [(columns)] FROM path [(options)] and + * COPY {table | (query)} TO path [(options)]. + */ +CopyStatement CopyStatement() #CopyStatement: +{ + CopyStatement copyStatement = new CopyStatement(); + ExpressionList columns = null; + List options = new ArrayList(); + String option; + Select select; + Table table; + Token token; +} +{ + + ( + LOOKAHEAD(2) "(" select=Select() ")" { copyStatement.setSelect(select); } | - { purgeStatement = new PurgeStatement(PurgeObjectType.RECYCLEBIN); } + table=Table() { copyStatement.setTable(table); } + [ LOOKAHEAD(2) "(" columns=ColumnList() ")" { copyStatement.setColumns(columns); } ] + ) + ( + { copyStatement.setFrom(true); } | - { purgeStatement = new PurgeStatement(PurgeObjectType.DBA_RECYCLEBIN); } + + ) + ( + token= { copyStatement.setPath(token.image); } | - tableSpaceToken= [ userToken= ] { - purgeStatement = new PurgeStatement( - PurgeObjectType.TABLESPACE - , tableSpaceToken.image - , userToken!=null ? userToken.image : null); - } + token= { copyStatement.setPath(token.image); } ) + [ + LOOKAHEAD(2) "(" option=DatabaseOption() { options.add(option); } + ( "," option=DatabaseOption() { options.add(option); } )* + ")" { copyStatement.setOptions(options); } + ] + { return copyStatement; } +} - { - return purgeStatement; - } +/** + * PREPARE name AS statement, storing a parameterised statement for later EXECUTE. + */ +PrepareStatement PrepareStatement() #PrepareStatement: +{ + PrepareStatement prepareStatement = new PrepareStatement(); + ObjectNames name; + Statement statement; +} +{ + name=RelObjectNames() + { prepareStatement.setName(String.join(".", name.getNames())); } + statement=SingleStatement() { prepareStatement.setStatement(statement); } + { return prepareStatement; } } -DescribeStatement Describe(): { - Table table; - DescribeStatement stmt = new DescribeStatement(); - Token tk = null; -} { - (tk= | tk=) - table = Table() { stmt.setDescribeType(tk.image).setTable(table); } - { - return stmt; - } +/** + * DEALLOCATE [PREPARE] name. + */ +DeallocateStatement DeallocateStatement() #DeallocateStatement: +{ + DeallocateStatement deallocateStatement = new DeallocateStatement(); + ObjectNames name; +} +{ + + [ LOOKAHEAD(2) { deallocateStatement.setUsingPrepareKeyword(true); } ] + name=RelObjectNames() { deallocateStatement.setName(String.join(".", name.getNames())); } + { return deallocateStatement; } } -ExplainStatement Explain(): +/** + * DuckDB's CONNECT target [AS alias], routing the session to a remote database. + */ +ConnectStatement ConnectStatement() #ConnectStatement: { - Token tk; - Statement statement; - List> with = null; - Table table; - List options; - ExplainStatement es; + ConnectStatement connectStatement = new ConnectStatement(); + ObjectNames name; + Token token; } { - ( tk= | tk = ) + ( - LOOKAHEAD(3)( - options= ExplainStatementOptions() - ( - [ LOOKAHEAD(2) with=WithList() ] - ( - statement = SelectWithWithItems( with ) - | statement = InsertWithWithItems( with ) - | statement = UpdateWithWithItems( with ) - | statement = DeleteWithWithItems( with ) - | statement = Merge( with ) - ) - ) - { - es = new ExplainStatement(tk.image, statement, options); - } - ) + token= { connectStatement.setTarget(token.image); } | - ( - table=Table( ) { es = new ExplainStatement(tk.image, table); } - ) + name=RelObjectNames() { connectStatement.setTarget(String.join(".", name.getNames())); } ) - { - return es; - } + [ name=RelObjectNames() { connectStatement.setAlias(String.join(".", name.getNames())); } ] + { return connectStatement; } } /** - * Postgres supports TRUE,ON,1,FALSE,OFF,0 as values + * DuckDB's DISCONNECT [name]. */ -String ExplainOptionBoolean(): +DisconnectStatement DisconnectStatement() #DisconnectStatement: { - Token tk = null; + DisconnectStatement disconnectStatement = new DisconnectStatement(); + ObjectNames name; } { - // intentionally not supporting 0,1 at the moment - [( tk= | tk= | tk= | tk= )] // optional - { - return tk != null ? tk.image : null; - } + + [ + LOOKAHEAD(2) + name=RelObjectNames() { disconnectStatement.setName(String.join(".", name.getNames())); } + ] + { return disconnectStatement; } } /** - * The output format, which can be TEXT, XML, JSON, or YAML + * DuckDB's ATTACH [DATABASE] [IF NOT EXISTS] path [AS alias] [(options)]. */ -String ExplainFormatOption(): +AttachStatement AttachStatement() #AttachStatement: { - Token tk = null; + AttachStatement attachStatement = new AttachStatement(); + List options = new ArrayList(); + String option; + ObjectNames name; + Token token; } { - // TODO support Text - [( tk= | tk= | tk= )] // optional - { - return tk != null ? tk.image : null; - } + + // greedy: DATABASE right after ATTACH is the keyword, not the database name + [ LOOKAHEAD(2) { attachStatement.setUsingDatabaseKeyword(true); } ] + [ LOOKAHEAD(2) { attachStatement.setIfNotExists(true); } ] + ( + token= { attachStatement.setDatabasePath(token.image); } + | + name=RelObjectNames() { attachStatement.setDatabasePath(String.join(".", name.getNames())); } + ) + [ name=RelObjectNames() { attachStatement.setAlias(String.join(".", name.getNames())); } ] + [ + LOOKAHEAD(2) "(" option=DatabaseOption() { options.add(option); } + ( "," option=DatabaseOption() { options.add(option); } )* + ")" { attachStatement.setOptions(options); } + ] + { return attachStatement; } } /** - * Options for explain, see https://www.postgresql.org/docs/9.1/sql-explain.html + * One ATTACH or COPY option, such as READ_ONLY, TYPE SQLITE or FORMAT PARQUET. Option names and + * values are free-form keywords, so the tokens are collected verbatim up to the next comma or the + * closing bracket. */ -List ExplainStatementOptions(): +JAVACODE +String DatabaseOption() { + StringBuilder builder = new StringBuilder(); + int depth = 0; + while (true) { + Token token = getToken(1); + if (token == null || token.kind == EOF) { + break; + } + if (depth == 0 && (",".equals(token.image) || ")".equals(token.image))) { + break; + } + if ("(".equals(token.image)) { + depth++; + } else if (")".equals(token.image)) { + depth--; + } + getNextToken(); + if (builder.length() > 0) { + builder.append(" "); + } + builder.append(token.image); + } + return builder.toString(); +} + +/** + * DuckDB's DETACH [DATABASE] [IF EXISTS] name. + */ +DetachStatement DetachStatement() #DetachStatement: { - List options = new ArrayList(); - ExplainStatement.Option option = null; - Token token = null; - String value = null; + DetachStatement detachStatement = new DetachStatement(); + ObjectNames name; } { - ( - ( value=ExplainOptionBoolean() - { - option = new ExplainStatement.Option(ExplainStatement.OptionType.ANALYZE); - option.setValue(value); - options.add(option); - } + + // greedy: DATABASE right after DETACH is the keyword, not the database name + [ LOOKAHEAD(2) { detachStatement.setUsingDatabaseKeyword(true); } ] + [ LOOKAHEAD(2) { detachStatement.setIfExists(true); } ] + name=RelObjectNames() { detachStatement.setDatabaseName(String.join(".", name.getNames())); } + { return detachStatement; } +} + +/** + * DuckDB's [FORCE] INSTALL extension [FROM repository] and LOAD extension. + */ +ExtensionStatement ExtensionStatement() #ExtensionStatement: +{ + ExtensionStatement extensionStatement = new ExtensionStatement(); + ObjectNames name; + Token token; +} +{ + [ { extensionStatement.setForce(true); } ] + ( + { extensionStatement.setOperation(ExtensionStatement.Operation.INSTALL); } + | + { extensionStatement.setOperation(ExtensionStatement.Operation.LOAD); } ) - | - ( value=ExplainOptionBoolean() + ( + token= { extensionStatement.setExtensionName(token.image); } + | + name=RelObjectNames() { extensionStatement.setExtensionName(String.join(".", name.getNames())); } + ) + [ + LOOKAHEAD(2) + ( + token= { extensionStatement.setRepository(token.image); } + | + name=RelObjectNames() { extensionStatement.setRepository(String.join(".", name.getNames())); } + ) + ] + { return extensionStatement; } +} + +/** + * DuckDB/SQLite PRAGMA name [(arguments) | = value]. + */ +PragmaStatement PragmaStatement() #PragmaStatement: +{ + PragmaStatement pragmaStatement = new PragmaStatement(); + ObjectNames name; + ExpressionList parameters; + Expression value; +} +{ + name=RelObjectNames() + { pragmaStatement.setName(String.join(".", name.getNames())); } + // greedy: parentheses right after the pragma name are its arguments + [ + LOOKAHEAD(2) + ( + "(" parameters=ExpressionList() ")" { pragmaStatement.setParameters(parameters); } + | + "=" value=SimpleExpression() { pragmaStatement.setValue(value); } + ) + ] + { return pragmaStatement; } +} + +/** + * BigQuery's LOAD DATA {INTO | OVERWRITE} table [(column_definitions)] [OPTIONS (option_list)] + * FROM FILES (file_options) [WITH PARTITION COLUMNS] [WITH CONNECTION connection]. + */ +LoadDataStatement LoadDataStatement() #LoadDataStatement: +{ + LoadDataStatement loadDataStatement = new LoadDataStatement(); + List columnDefinitions = new ArrayList(); + ColumnDefinition columnDefinition; + ExpressionList options; + ObjectNames connectionName; + Table table; +} +{ + + ( | { loadDataStatement.setOverwrite(true); } ) + table=Table() { loadDataStatement.setTable(table); } + [ + LOOKAHEAD(2) "(" + columnDefinition=ColumnDefinition() { columnDefinitions.add(columnDefinition); } + ( "," columnDefinition=ColumnDefinition() { columnDefinitions.add(columnDefinition); } )* + ")" { loadDataStatement.setColumnDefinitions(columnDefinitions); } + ] + [ + LOOKAHEAD({ isKeywordAhead("OPTIONS") }) + "(" options=ExpressionList() ")" { loadDataStatement.setOptions(options); } + ] + + LOOKAHEAD({ isKeywordAhead("FILES") }) + "(" options=ExpressionList() ")" { loadDataStatement.setFileOptions(options); } + [ + LOOKAHEAD(2) + { loadDataStatement.setWithPartitionColumns(true); } + ] + [ + LOOKAHEAD({ getToken(1).kind == K_WITH && "CONNECTION".equalsIgnoreCase(getToken(2).image) }) + connectionName=RelObjectNames() + { loadDataStatement.setConnectionName(String.join(".", connectionName.getNames())); } + ] + { return loadDataStatement; } +} + +/** + * BigQuery's EXPORT DATA [WITH CONNECTION connection] OPTIONS (option_list) AS query. + */ +ExportDataStatement ExportDataStatement() #ExportDataStatement: +{ + ExportDataStatement exportDataStatement = new ExportDataStatement(); + ExpressionList options; + ObjectNames connectionName; + Select select; +} +{ + + [ + LOOKAHEAD({ getToken(1).kind == K_WITH && "CONNECTION".equalsIgnoreCase(getToken(2).image) }) + connectionName=RelObjectNames() + { exportDataStatement.setConnectionName(String.join(".", connectionName.getNames())); } + ] + [ + LOOKAHEAD({ isKeywordAhead("OPTIONS") }) + "(" options=ExpressionList() ")" + { exportDataStatement.setOptions(options); } + ] + select=Select() { exportDataStatement.setSelect(select); } + { return exportDataStatement; } +} + +/** + * BigQuery's ASSERT expression [AS description] debugging statement. + */ +AssertStatement AssertStatement() #AssertStatement: +{ + AssertStatement assertStatement = new AssertStatement(); + Expression expression; + Token token; +} +{ + expression=Expression() { assertStatement.setExpression(expression); } + [ token= + { assertStatement.setDescription(new StringValue(token.image)); } ] + { return assertStatement; } +} + +PurgeStatement PurgeStatement(): { + PurgeStatement purgeStatement = null; + Table table; + Index index; + Token tableSpaceToken; + Token userToken = null; +} +{ + + ( + table=Table() { purgeStatement = new PurgeStatement(table); } + | + index=Index() { purgeStatement = new PurgeStatement(index); } + | + { purgeStatement = new PurgeStatement(PurgeObjectType.RECYCLEBIN); } + | + { purgeStatement = new PurgeStatement(PurgeObjectType.DBA_RECYCLEBIN); } + | + tableSpaceToken= [ userToken= ] { + purgeStatement = new PurgeStatement( + PurgeObjectType.TABLESPACE + , tableSpaceToken.image + , userToken!=null ? userToken.image : null); + } + ) + + { + return purgeStatement; + } +} + +DescribeStatement Describe(): { + Table table; + Select select; + DescribeStatement stmt = new DescribeStatement(); + Token tk = null; +} { + (tk= | tk=) + ( + // DuckDB describes the result of a query, not only a table + LOOKAHEAD({ getToken(1).kind == K_SELECT || getToken(1).kind == K_WITH + || getToken(1).kind == K_FROM || getToken(1).kind == OPENING_BRACKET }) + select = Select() { stmt.setDescribeType(tk.image).setSelect(select); } + | + table = Table() { stmt.setDescribeType(tk.image).setTable(table); } + ) + { + return stmt; + } +} + +ExplainStatement Explain(): +{ + Token tk; + Statement statement; + List> with = null; + Table table; + List options; + ExplainStatement es; + boolean parenthesized = false; +} +{ + ( tk= | tk = ) + ( + LOOKAHEAD(3)( + ( + LOOKAHEAD(2) "(" options = PostgresqlExplainOptions() ")" { parenthesized = true; } + | options = ExplainStatementOptions() + ) + ( + [ LOOKAHEAD(2) with=WithList() ] + ( + statement = SelectWithWithItems( with ) + | statement = InsertWithWithItems( with ) + | statement = UpdateWithWithItems( with ) + | statement = DeleteWithWithItems( with ) + | statement = Merge( with ) + ) + ) + { + es = new ExplainStatement(tk.image, statement, options); + es.setParenthesizedOptions(parenthesized); + } + ) + | + ( + table=Table( ) { es = new ExplainStatement(tk.image, table); } + ) + ) + { + if (parenthesized || Dialect.POSTGRESQL.name().equals(getAsString(Feature.dialect))) { + validatePostgresqlExplain(es); + } + return es; + } +} + +List PostgresqlExplainOptions(): +{ + List options = new ArrayList(); + ExplainStatement.Option option; +} +{ + option = PostgresqlExplainOption() { options.add(option); } + ( "," option = PostgresqlExplainOption() { options.add(option); } )* + { return options; } +} + +ExplainStatement.Option PostgresqlExplainOption(): +{ + Token name; + Token value = null; +} +{ + ( name = | name = | name = + | name = | name = | name = + | name = ) + [ ( value = | value = | value = | value = + | value = | value = | value = | value = + | value = | value = | value = | value = ) ] + { + return new ExplainStatement.Option(postgresqlExplainOptionType(name.image)) + .withValue(value == null ? null : value.image); + } +} + +/** + * Boolean values in the legacy unparenthesized syntax. + */ +String ExplainOptionBoolean(): +{ + Token tk = null; +} +{ + [( tk= | tk= | tk= | tk= )] + { + return tk != null ? tk.image : null; + } +} + +/** + * Preserve the optional format in the legacy unparenthesized syntax. + */ +String ExplainFormatOption(): +{ + Token tk = null; +} +{ + [( tk= | tk= | tk= )] + { + return tk != null ? tk.image : null; + } +} + +/** + * Options for explain, see https://www.postgresql.org/docs/9.1/sql-explain.html + */ +List ExplainStatementOptions(): +{ + List options = new ArrayList(); + ExplainStatement.Option option = null; + Token token = null; + String value = null; +} +{ + ( + ( value=ExplainOptionBoolean() + { + option = new ExplainStatement.Option(ExplainStatement.OptionType.ANALYZE); + option.setValue(value); + options.add(option); + } + ) + | + ( value=ExplainOptionBoolean() { option = new ExplainStatement.Option(ExplainStatement.OptionType.BUFFERS); option.setValue(value); @@ -3368,6 +5166,22 @@ List ExplainStatementOptions(): } } +SetIdentityInsertStatement SetIdentityInsert(): { + Table table; + boolean on; +} +{ + AccessKeyword("IDENTITY_INSERT") table=Table() + { + requireAccessSyntax(table.getNameParts().size() <= 3, + "IDENTITY_INSERT requires a table name with at most three parts"); + } + ( { on = true; } | { on = false; } ) + { + return new SetIdentityInsertStatement(table, on); + } +} + UseStatement Use(): { String name; boolean hasSchemaKeyword = false; @@ -3591,14 +5405,14 @@ Update Update(): { Update update = new Update(); Table table = null; + ForPortionClause forPortionClause; List startJoins = null; List> with = null; List updateSets; Expression where = null; PreferringClause preferringClause = null; - FromItem fromItem = null; - List joins = null; Limit limit = null; + OptionClause optionClause = null; List orderByElements; boolean useColumnsBrackets = false; ReturningClause returningClause; @@ -3612,33 +5426,64 @@ Update Update(): { update.setOracleHint(getOracleHint()); } [ LOOKAHEAD(2) { modifierPriority = UpdateModifierPriority.LOW_PRIORITY; }] [ LOOKAHEAD(2) { modifierIgnore = true; }] - table=TableWithAliasAndMysqlIndexHint() [ startJoins=JoinsList() ] + table=TableWithAliasAndMysqlIndexHint() + [ forPortionClause=ForPortionClause() { update.setForPortionClause(forPortionClause); } ] + [ startJoins=JoinsList() ] + [ LOOKAHEAD(, { Dialect.TERADATA.name().equals(getAsString(Feature.dialect)) }) + UpdateFromClause(update) { update.setFromBeforeSet(true); } ] updateSets = UpdateSets() { update.setUpdateSets(updateSets); } [ outputClause = OutputClause() {update.setOutputClause(outputClause); } ] - [ LOOKAHEAD(2) - fromItem=FromItem() - [ LOOKAHEAD(2) joins=JoinsList() ] ] + [ LOOKAHEAD(2, { !Dialect.TERADATA.name().equals(getAsString(Feature.dialect)) }) + UpdateFromClause(update) ] [ where=WhereClause() { update.setWhere(where); } ] [ preferringClause=PreferringClause() { update.setPreferringClause(preferringClause); } ] [ orderByElements = OrderByElements() { update.setOrderByElements(orderByElements); } ] [ limit = PlainLimit() { update.setLimit(limit); } ] + [ LOOKAHEAD(2) optionClause = OptionClause() { update.setOption(optionClause); } ] [ returningClause = ReturningClause() { update.setReturningClause(returningClause); } ] { return update.withWithItemsList(with) .withTable(table) .withStartJoins(startJoins) - .withFromItem(fromItem) - .withJoins(joins) .withModifierPriority(modifierPriority) .withModifierIgnore(modifierIgnore); } } +/** Shared structured interval for application-time UPDATE and DELETE. */ +ForPortionClause ForPortionClause() #ForPortionClause: +{ + String periodName; + Expression fromExpression; + Expression toExpression; + ForPortionClause clause; +} +{ + AccessKeyword("PORTION") periodName=RelObjectName() + fromExpression=Expression() toExpression=Expression() + { + clause = new ForPortionClause(periodName, fromExpression, toExpression); + linkAST(clause, jjtThis); + return clause; + } +} + +void UpdateFromClause(Update update): +{ + FromItem fromItem; + List joins = null; +} +{ + fromItem=FromItem() + [ LOOKAHEAD(2) joins=JoinsList() ] + { update.setFromItem(fromItem); update.setJoins(joins); } +} + List UpdateSets(): { ArrayList updateSets = new ArrayList(); @@ -3709,7 +5554,7 @@ List Partitions(): ( LOOKAHEAD(2) ( "," - tableColumn=Column() [ "=" valueExpression=Expression() ] + tableColumn=Column() { valueExpression = null; } [ "=" valueExpression=Expression() ] { partitions.add( new Partition (tableColumn, valueExpression)); } ) )* @@ -3719,30 +5564,109 @@ List Partitions(): } } -Insert InsertWithWithItems( List> withItems ): +InsertBulk InsertBulk(): { - Insert insert; + InsertBulk statement = new InsertBulk(); + Table table; + ColumnDefinition column; + InsertBulk.Option option; + Set kinds = EnumSet.noneOf(InsertBulk.Option.Kind.class); +} +{ + AccessKeyword("BULK") table=Table() { statement.setTable(table); } + "(" column=InsertBulkColumn() { statement.getColumns().add(column); } + ( "," column=InsertBulkColumn() { statement.getColumns().add(column); } )* ")" + // LOOKAHEAD(2) is required, not cosmetic: Statements() permits two statements to be + // adjacent without a separator, so FIRST(SingleStatement) -- including -- is in + // the FOLLOW set of every statement tail. `WITH (` cannot start a CTE, so two tokens + // separate the INSERT BULK option list from a following WITH statement. + [ LOOKAHEAD(2) "(" option=InsertBulkOption() { + statement.getOptions().add(option); kinds.add(option.getKind()); + } + ( "," option=InsertBulkOption() { + requireDdlSyntax(kinds.add(option.getKind()), "Duplicate INSERT BULK option"); + statement.getOptions().add(option); + } )* ")" ] + { return statement; } } + +ColumnDefinition InsertBulkColumn(): { - insert = Insert() { insert.setWithItemsList( withItems ); - return insert; + String name; + String collation; + ColDataType type; + ColumnDefinition column; } +{ + name=RelObjectName() type=ColDataType() { column = new ColumnDefinition(name, type); } + [ collation=RelObjectName() { column.setColumnSpecs(Arrays.asList("COLLATE", collation)); } ] + { return column; } } -Insert Insert(): +InsertBulk.Option InsertBulkOption(): { - Insert insert = new Insert(); - Table table = null; - List> with = null; - Column tableColumn = null; - ExpressionList columns = new ExpressionList(); - List partitions = new ArrayList(); - Expression exp = null; - ReturningClause returningClause; - Select select = null; - Token tk = null; - InsertModifierPriority modifierPriority = null; - boolean modifierIgnore = false; + String name; + Token value; + Column column; + Token direction; + OrderByElement order; + InsertBulk.Option option; +} +{ + ( + // RelObjectName() below accepts as an identifier (isReservedKeywordAsIdentifier() + // returns true for ORDER unless BY or SIBLINGS follows), so ORDER is a genuine common + // prefix of the first and the last alternative. The option form always has "(" after it. + LOOKAHEAD( "(") + { option = new InsertBulk.Option(InsertBulk.Option.Kind.ORDER); } + "(" column=Column() { + order = new OrderByElement().withExpression(column); + option.getOrderByElements().add(order); + } + [ (direction= | direction=) { order.setAsc(direction.kind == K_ASC); order.setAscDescPresent(true); } ] + ( "," column=Column() { + order = new OrderByElement().withExpression(column); + option.getOrderByElements().add(order); + } + [ (direction= | direction=) { order.setAsc(direction.kind == K_ASC); order.setAscDescPresent(true); } ] + )* ")" + | + LOOKAHEAD({ isKeywordAhead("ROWS_PER_BATCH") }) AccessKeyword("ROWS_PER_BATCH") "=" value= + { option = new InsertBulk.Option(InsertBulk.Option.Kind.ROWS_PER_BATCH); option.setValue(new LongValue(value.image)); } + | + name=RelObjectName() { + option = new InsertBulk.Option(typeDdlEnum(InsertBulk.Option.Kind.class, name)); + requireDdlSyntax(option.getKind() != InsertBulk.Option.Kind.ORDER + && option.getKind() != InsertBulk.Option.Kind.ROWS_PER_BATCH, "Missing INSERT BULK option value"); + } + ) + { return option; } +} + +Insert InsertWithWithItems( List> withItems ): +{ + Insert insert; +} +{ + insert = Insert() { insert.setWithItemsList( withItems ); + return insert; +} +} + +Insert Insert(): +{ + Insert insert = new Insert(); + Table table = null; + List> with = null; + Column tableColumn = null; + ExpressionList columns = new ExpressionList(); + List partitions = new ArrayList(); + Expression exp = null; + ReturningClause returningClause; + Select select = null; + Token tk = null; + InsertModifierPriority modifierPriority = null; + boolean modifierIgnore = false; List updateSets; List duplicateUpdateSets; @@ -3826,14 +5750,32 @@ Insert Insert(): { insert.setOverwrite(true); insert.setTableKeyword(true); } | [ LOOKAHEAD(2) { insert.setTableKeyword(true); }] ) - ] table=Table() + ] table=TableReference() [ LOOKAHEAD(2) "(" partitions=Partitions() ")" ] - [ LOOKAHEAD({ isAliasAhead() }) [ { useAs = true; } ] name=RelObjectName() { table.setAlias(new Alias(name,useAs)); }] + [ LOOKAHEAD({ isAliasAhead() && !isColumnMatchingAhead() + && !(getToken(1).kind == K_OVERRIDING + && (getToken(2).kind == K_SYSTEM || getToken(2).kind == K_USER)) }) + [ { useAs = true; } ] name=RelObjectName() { table.setAlias(new Alias(name,useAs)); }] [ LOOKAHEAD(2) "(" columns=ColumnList() ")" ] - [ LOOKAHEAD(2) { insert.setOverriding(true); } ] + // DuckDB: INSERT INTO tbl BY NAME|POSITION SELECT ... + [ + LOOKAHEAD({ isColumnMatchingAhead() }) + + ( + { insert.setColumnMatching(Insert.ColumnMatching.BY_NAME); } + | + // POSITION is lexed as a string-function name + + { insert.setColumnMatching(Insert.ColumnMatching.BY_POSITION); } + ) + ] + + [ LOOKAHEAD(2) + ( { insert.setOverridingMode(Insert.OverridingMode.SYSTEM); } + | { insert.setOverridingMode(Insert.OverridingMode.USER); } ) ] [ outputClause = OutputClause() { insert.setOutputClause(outputClause); } ] @@ -3951,35 +5893,46 @@ List OracleMultiInsertClauseList(): } } -InsertConflictTarget InsertConflictTarget(): +InsertConflictTarget InsertConflictTarget(): { - String indexColumnName; - ArrayList indexColumnNames = new ArrayList(); - Expression indexExpression = null; - Expression whereExpression = null; - String constraintName = null ; + InsertConflictTarget target = new InsertConflictTarget(); + List elements = new ArrayList(); + Index.ColumnParams element; + Expression whereExpression; + String constraintName; } { ( - ( - "(" - indexColumnName = RelObjectNameExt() { indexColumnNames.add(indexColumnName); } - ( "," indexColumnName = RelObjectNameExt() { indexColumnNames.add(indexColumnName); } )* -// | -// ( -// "(" indexExpression = Expression() ")" -// ) - - ")" - [ whereExpression = WhereClause() ] - ) + "(" element=InsertConflictIndexElement() { elements.add(element); } + ( "," element=InsertConflictIndexElement() { elements.add(element); } )* ")" + { target.setIndexElements(elements); } + [ whereExpression=WhereClause() { target.setWhereExpression(whereExpression); } ] | - ( - constraintName = RelObjectNameExt() - ) + constraintName=RelObjectNameExt() + { target.setConstraintName(constraintName); } ) + { return target; } +} - { return new InsertConflictTarget(indexColumnNames, indexExpression, whereExpression, constraintName); } +Index.ColumnParams InsertConflictIndexElement(): +{ + Index.ColumnParams element; + String name; + Expression expression; + ObjectNames collation; + ObjectNames operatorClass; +} +{ + ( + LOOKAHEAD({ isFunctionAhead() }) expression=Function() + { element = new Index.ColumnParams(expression).withExpressionParenthesized(false); } + | name=RelObjectNameExt() { element = new Index.ColumnParams(name); } + | "(" expression=Expression() ")" { element = new Index.ColumnParams(expression); } + ) + [ LOOKAHEAD(2) collation=RelObjectNames() + { element.setCollation(new Table(collation.getNames()).getFullyQualifiedName()); } ] + [ LOOKAHEAD(2) operatorClass=RelObjectNames() { element.setOperatorClass(new Table(operatorClass.getNames()).getFullyQualifiedName()); } ] + { return element; } } InsertConflictAction InsertConflictAction(): @@ -4117,6 +6070,7 @@ Delete Delete(): { Delete delete = new Delete(); Table table = null; + ForPortionClause forPortionClause; List

tables = new ArrayList
(); List> with = null; FromItem usingFromItem = null; @@ -4125,6 +6079,7 @@ Delete Delete(): Expression where = null; PreferringClause preferringClause = null; Limit limit = null; + OptionClause optionClause = null; List orderByElements; boolean hasFrom = false; Token tk = null; @@ -4145,13 +6100,16 @@ Delete Delete(): [ outputClause = OutputClause() {delete.setOutputClause(outputClause); } ] | ) { hasFrom = true; }] - [ LOOKAHEAD(3) table=TableWithAlias() [ LOOKAHEAD(2) joins=JoinsList() ] ] + [ LOOKAHEAD(3) table=TableWithAlias() + [ forPortionClause=ForPortionClause() { delete.setForPortionClause(forPortionClause); } ] + [ LOOKAHEAD(2) joins=JoinsList() ] ] [ usingFromItem=FromItem() { usingFromItemList.add(usingFromItem); } ("," usingFromItem=FromItem() { usingFromItemList.add(usingFromItem); } )*] [where=WhereClause() { delete.setWhere(where); } ] [preferringClause=PreferringClause() { delete.setPreferringClause(preferringClause);} ] [orderByElements = OrderByElements() { delete.setOrderByElements(orderByElements); } ] [limit=PlainLimit() {delete.setLimit(limit); } ] + [ LOOKAHEAD(2) optionClause = OptionClause() { delete.setOption(optionClause); } ] [ returningClause = ReturningClause() { delete.setReturningClause(returningClause); } ] { @@ -4176,6 +6134,8 @@ Statement Merge( List> with ) : { Expression condition; List operations; OutputClause outputClause; + ReturningClause returningClause; + OptionClause optionClause = null; } { { merge.setOracleHint(getOracleHint()); } table=TableWithAlias() { merge.setTable(table); } @@ -4184,8 +6144,12 @@ Statement Merge( List> with ) : { operations = MergeOperations() { merge.setOperations(operations); } + [ returningClause = ReturningClause() { merge.setReturningClause(returningClause); } ] + [ outputClause = OutputClause() { merge.setOutputClause(outputClause); } ] + [ LOOKAHEAD(2) optionClause = OptionClause() { merge.setOption(optionClause); } ] + { return merge.withWithItemsList(with); } } @@ -4281,7 +6245,7 @@ MergeOperation MergeWhenNotMatched() : { | operation = MergeDeleteClause(predicate) { ((MergeDelete) operation).setSide(side); } ) - { return operation; } + { return MergeSide.validatePairing(side, operation); } } // table names seem to allow ":" delimiters, e.g. for Informix see #1134 @@ -4393,7 +6357,7 @@ String RelObjectName() : | tk= | tk= | tk= | tk= | tk= | tk= | tk= | tk= | tk= | tk= | tk= | tk= - | tk= | tk= | tk= + | tk= | tk= | tk= | tk= | tk= | tk= | tk= | tk= | tk= | tk= | tk= ) ) @@ -4448,13 +6412,39 @@ Table Table() #TableName : } } +/** A table source or DML target; catalog-only DDL continues to use Table(). */ +Table TableReference() #void : +{ + Table table; +} +{ + ( table=TableVariable() | table=Table() ) + { return table; } +} + +Table TableVariable() #TableName : +{ + Token variable; + Table table; +} +{ + variable= + { + requireAccessSyntax(!variable.image.startsWith("@@"), + "A table variable must be a local variable"); + table = new Table(variable.image, false).withTableVariable(true); + linkAST(table, jjtThis); + return table; + } +} + Table TableWithAlias(): { Table table = null; Alias alias = null; } { - table=Table() + table=TableReference() [ LOOKAHEAD({ isAliasAhead() }) alias=Alias() { table.setAlias(alias); }] { return table; } } @@ -4462,12 +6452,10 @@ Table TableWithAlias(): Table TableWithAliasAndMysqlIndexHint(): { Table table = null; - Alias alias = null; MySQLIndexHint indexHint = null; } { - table=Table() - [ LOOKAHEAD({ isAliasAhead() }) alias=Alias() { table.setAlias(alias); } ] + table=TableWithAlias() [ LOOKAHEAD(2) indexHint=MySQLIndexHint() { table.setHint(indexHint); } ] { return table; } } @@ -4501,6 +6489,7 @@ SampleClause SampleClause(): Number offsetArgument = null; Number repeatArgument=null; Number seedArgument=null; + boolean methodInBrackets = false; } { ( @@ -4517,9 +6506,13 @@ SampleClause SampleClause(): ) | ( - // Duck DB + // Duck DB: the method is optional and may follow the sample size instead { keyword = "USING SAMPLE"; } - ( token = | token = ) { method = token.image; } + [ + LOOKAHEAD({ isSampleMethod(getToken(1)) }) + ( token = | token = | token = ) + { method = token.image; } + ] ) ) @@ -4539,12 +6532,34 @@ SampleClause SampleClause(): [ LOOKAHEAD(2) "(" seedArgument = Number() ")" ] | percentageArgument = Number() { argumentInBrackets = false; } + // greedy: a unit directly after the sample size always belongs to the clause + [ + LOOKAHEAD(1) + ( + "%" { percentageUnit="%"; } + | + { percentageUnit="PERCENT"; } + | + { percentageUnit="ROWS"; } + ) + ] [ LOOKAHEAD(2) offsetArgument = Number() ] ) + // DuckDB: USING SAMPLE 10 PERCENT (bernoulli[, seed]) + [ + LOOKAHEAD({ method == null && getToken(1).kind == OPENING_BRACKET + && isSampleMethod(getToken(2)) }) + "(" ( token = | token = | token = ) + { method = token.image; methodInBrackets = true; } + [ "," seedArgument = Number() ] + ")" + ] + { sampleClause = new SampleClause(keyword, method, percentageArgument, percentageUnit, repeatArgument, seedArgument); sampleClause.setArgumentInBrackets(argumentInBrackets); + sampleClause.setMethodInBrackets(methodInBrackets); sampleClause.setOffsetArgument(offsetArgument); return sampleClause; } @@ -4565,6 +6580,8 @@ Select Select() #Select: Select select = null; List> with = null; List orderByElements = null; + List interpolateElements = null; + OptionClause optionClause = null; Limit limit = null; Offset offset = null; Fetch fetch = null; @@ -4572,13 +6589,18 @@ Select Select() #Select: Alias alias = null; } { - [ with=WithList() ] ( + LOOKAHEAD({ isFromFirstSelectAhead() }) select = FromFirstSelect() + | LOOKAHEAD(3) select = FromQuery() | ( ( + LOOKAHEAD(2) select = PivotQuery() + | + LOOKAHEAD(2) select = UnPivotQuery() + | LOOKAHEAD(3) select = PlainSelect() | LOOKAHEAD(3) select = Values() @@ -4589,6 +6611,8 @@ Select Select() #Select: [ LOOKAHEAD(2) select = SetOperationList(select) ] [ LOOKAHEAD( ) orderByElements = OrderByElements() { select.setOrderByElements(orderByElements); } ] + [ LOOKAHEAD(2) interpolateElements = InterpolateClause() { select.setInterpolate(interpolateElements); } ] + [ LOOKAHEAD(2, { !(select instanceof Values) }) optionClause = OptionClause() { select.setOption(optionClause); } ] [ LOOKAHEAD() limit=LimitWithOffset() {select.setLimit(limit);} ] [ LOOKAHEAD() offset = Offset() { select.setOffset(offset);} ] @@ -4602,6 +6626,53 @@ Select Select() #Select: } } +/** + * DuckDB's FROM-first query: FROM tbl [SELECT ...] [WHERE ...] [GROUP BY ...] [HAVING ...] + * [QUALIFY ...] [ORDER BY ...] [LIMIT ...] [OFFSET ...]. Without a SELECT clause all columns + * are returned. + */ +PlainSelect FromFirstSelect() #FromFirstSelect: +{ + PlainSelect plainSelect = new PlainSelect(); + List> selectItems = null; + FromItem fromItem; + List joins = null; + Expression where = null; + GroupByElement groupBy = null; + Expression having = null; + Expression qualify = null; + List orderByElements = null; + Limit limit = null; + Offset offset = null; +} +{ + fromItem=FromItem() + { + plainSelect.setFromItem(fromItem); + plainSelect.setFromFirst(true); + } + [ LOOKAHEAD(2) joins=JoinsList() { plainSelect.setJoins(joins); } ] + // greedy: a SELECT right after the FROM clause belongs to this query + [ + LOOKAHEAD(2) + + [ LOOKAHEAD(2) ( | { plainSelect.setDistinct(new Distinct()); } ) ] + selectItems=SelectItemsList() { plainSelect.setSelectItems(selectItems); } + ] + [ LOOKAHEAD(2) where=WhereClause() { plainSelect.setWhere(where); } ] + [ LOOKAHEAD(2) groupBy=GroupByColumnReferences() { plainSelect.setGroupByElement(groupBy); } ] + [ LOOKAHEAD(2) having=Having() { plainSelect.setHaving(having); } ] + [ LOOKAHEAD(2) qualify=Qualify() { plainSelect.setQualify(qualify); } ] + [ LOOKAHEAD( ) orderByElements=OrderByElements() + { plainSelect.setOrderByElements(orderByElements); } ] + [ LOOKAHEAD() limit=LimitWithOffset() { plainSelect.setLimit(limit); } ] + [ LOOKAHEAD() offset=Offset() { plainSelect.setOffset(offset); } ] + { + linkAST(plainSelect, jjtThis); + return plainSelect; + } +} + FromQuery FromQuery() #FromQuery: { FromQuery fromQuery; @@ -4863,7 +6934,8 @@ String SetOperationModifier(): [ ( tk= | tk="DISTINCT") { modifier+=tk.image; } ] { modifier+= " BY NAME"; } [ - "MATCHING" { modifier+= " MATCHING"; } + LOOKAHEAD({ isKeywordAhead("MATCHING") }) + ContextualKeyword("MATCHING") { modifier+= " MATCHING"; } "(" identifier = RelObjectName() { modifier+="(" + identifier; } ("," identifier = RelObjectName() { modifier+=", " + identifier; })* @@ -5212,7 +7284,9 @@ PlainSelect PlainSelect() #PlainSelect: Expression preWhere = null; Expression where = null; ForClause forClause = null; + OptionClause optionClause = null; List orderByElements; + List interpolateElements = null; GroupByElement groupBy = null; Expression having = null; Expression qualify; @@ -5231,6 +7305,7 @@ PlainSelect PlainSelect() #PlainSelect: boolean partitionByBrackets = false; List
intoTables = null; MySqlSelectIntoClause mySqlSelectIntoClause = null; + MySqlProcedureAnalyse mySqlProcedureAnalyse = null; Table updateTable = null; List
updateTables = new ArrayList
(); Wait wait = null; @@ -5294,12 +7369,14 @@ PlainSelect PlainSelect() #PlainSelect: selectItems=SelectItemsList() - [ LOOKAHEAD( ( | )) - mySqlSelectIntoClause = MySqlSelectIntoClause(MySqlSelectIntoClause.Position.BEFORE_FROM) - { plainSelect.setMySqlSelectIntoClause(mySqlSelectIntoClause); } - ] [ LOOKAHEAD() - intoTables = IntoClause() { plainSelect.setIntoTables(intoTables); } + ( + LOOKAHEAD({ isMySqlSelectIntoAhead() }) + mySqlSelectIntoClause = MySqlSelectIntoClause(MySqlSelectIntoClause.Position.BEFORE_FROM) + { plainSelect.setMySqlSelectIntoClause(mySqlSelectIntoClause); } + | + intoTables = IntoClause() { plainSelect.setIntoTables(intoTables); } + ) ] [ LOOKAHEAD(2) fromItem=FromItem() [ LOOKAHEAD(2) lateralViews=LateralViews() ] @@ -5341,12 +7418,31 @@ PlainSelect PlainSelect() #PlainSelect: { plainSelect.setWindowDefinitions(winDefs); } ] [ LOOKAHEAD( ) orderByElements = OrderByElements() { plainSelect.setOrderByElements(orderByElements); } ] + [ LOOKAHEAD(2) interpolateElements = InterpolateClause() { plainSelect.setInterpolate(interpolateElements); } ] [ LOOKAHEAD(2) forClause = ForClause() {plainSelect.setForClause(forClause);} ] - [ LOOKAHEAD(2) { plainSelect.setEmitChanges(true); } ] - [ LOOKAHEAD(7) limit = LimitBy() { plainSelect.setLimitBy(limit); } ] - [ LOOKAHEAD() limit = LimitWithOffset() { plainSelect.setLimit(limit); } ] + [ LOOKAHEAD(2) optionClause = OptionClause() { plainSelect.setOption(optionClause); } ] + [ LOOKAHEAD(2) + ( { plainSelect.setEmitMode(PlainSelect.EmitMode.CHANGES); } + | { plainSelect.setEmitMode(PlainSelect.EmitMode.FINAL); } ) ] + // Parse the LIMIT row count once (this accepts a parenthesized subquery too), then + // optionally attach ClickHouse's `LIMIT ... BY ...`. Checking BY right after the limit + // expression avoids a numeric LOOKAHEAD, which cannot see past a long parenthesized + // subquery and would wrongly commit to LIMIT BY (issue #2359). + [ LOOKAHEAD() limit = LimitWithOffset() + [ LOOKAHEAD() expressionList = ExpressionList() { limit.setByExpressions(expressionList); } ] + { + if (limit.getByExpressions() != null) { + plainSelect.setLimitBy(limit); + } else { + plainSelect.setLimit(limit); + } + } ] [ LOOKAHEAD() offset = Offset() { plainSelect.setOffset(offset); } ] [ LOOKAHEAD(, { limit==null }) limit = LimitWithOffset() { plainSelect.setLimit(limit); } ] + [ LOOKAHEAD( ) + mySqlProcedureAnalyse = MySqlProcedureAnalyse() + { plainSelect.setMySqlProcedureAnalyse(mySqlProcedureAnalyse); } + ] [ LOOKAHEAD() fetch = Fetch() { plainSelect.setFetch(fetch); } ] [ LOOKAHEAD( ) withIsolation = WithIsolation() { plainSelect.setIsolation(withIsolation); } ] [ LOOKAHEAD(2) @@ -5370,8 +7466,14 @@ PlainSelect PlainSelect() #PlainSelect: [ LOOKAHEAD( ) orderByElements = OrderByElements() { plainSelect.setOrderByElements(orderByElements); plainSelect.setForUpdateBeforeOrderBy(true); } ] + [ LOOKAHEAD(2) interpolateElements = InterpolateClause() { plainSelect.setInterpolate(interpolateElements); } ] ] - [ LOOKAHEAD( ( | )) + [ LOOKAHEAD({ isMySqlSelectIntoAhead() }) + { + if (mySqlSelectIntoClause != null || intoTables != null) { + throw new ParseException("Only one INTO clause is allowed per SELECT"); + } + } mySqlSelectIntoClause = MySqlSelectIntoClause(MySqlSelectIntoClause.Position.TRAILING) { plainSelect.setMySqlSelectIntoClause(mySqlSelectIntoClause); } ] @@ -5396,6 +7498,7 @@ PlainSelect PlainSelect() #PlainSelect: Select SetOperationList(Select select) #SetOperationList: { SetOperationList list = new SetOperationList(); List orderByElements = null; + List interpolateElements = null; Limit limit = null; Offset offset = null; Fetch fetch = null; @@ -5443,6 +7546,7 @@ Select SetOperationList(Select select) #SetOperationList: { )+ [ LOOKAHEAD(2) orderByElements=OrderByElements() {list.setOrderByElements(orderByElements);} ] + [ LOOKAHEAD(2) interpolateElements = InterpolateClause() { list.setInterpolate(interpolateElements); } ] [ LOOKAHEAD() limit = LimitWithOffset() { list.setLimit(limit); } ] [ LOOKAHEAD() offset = Offset() { list.setOffset(offset); } ] [ LOOKAHEAD(, { limit==null }) limit = LimitWithOffset() { list.setLimit(limit); } ] @@ -5460,6 +7564,14 @@ Select SetOperationList(Select select) #SetOperationList: { ps.setLimit(null); ps.setOffset(null); } + if (ps.getInterpolate() != null) { + list.setInterpolate(ps.getInterpolate()); + ps.setInterpolate(null); + } + if (ps.getOption() != null) { + list.setOption(ps.getOption()); + ps.setOption(null); + } if (ps.getFetch() != null) { list.setFetch(ps.getFetch()); ps.setFetch(null); @@ -5495,7 +7607,10 @@ WithItem WithItem() #WithItem: List> selectItems = null; WithFunctionDeclaration withFunctionDeclaration = null; ParenthesedStatement statement = null; + Expression expr = null; WithSearchClause withSearchClause = null; + WithCycleClause withCycleClause = null; + ExpressionList usingKeyExpressions = null; WithItem withItem; } { @@ -5506,10 +7621,13 @@ WithItem WithItem() #WithItem: withItem = new WithItem().withWithFunctionDeclaration(withFunctionDeclaration); } | + LOOKAHEAD({ !isWithExpressionAliasAhead() }) ( [ LOOKAHEAD(2) { recursive = true; } ] name=RelObjectName() [ "(" selectItems=SelectItemsList() ")" ] + // DuckDB 2.0: WITH RECURSIVE tbl (a, b) USING KEY (a, avg(b)) AS (...) + [ LOOKAHEAD(2) "(" usingKeyExpressions=ExpressionList() ")" ] [ LOOKAHEAD(2) [ { usingNot = true; } ] { materialized = true; } ] ( @@ -5525,15 +7643,52 @@ WithItem WithItem() #WithItem: withItem = new WithItem(statement, new Alias(name, false)) .withRecursive(recursive, usingNot, materialized) .withWithItemList(selectItems); + withItem.setUsingKeyExpressions(usingKeyExpressions); + } + ) + | + ( + // ClickHouse style expression alias: WITH AS + expr = WithItemExpression() + + name = RelObjectName() + { + withItem = new WithItem().withExpression(expr).withAlias(new Alias(name, false)); } ) ) [ withSearchClause = WithSearchClause() { withItem.setSearchClause(withSearchClause); } ] + [ withCycleClause = WithCycleClause() { withItem.setCycleClause(withCycleClause); } ] { return withItem; } } +Expression WithItemExpression(): +{ + LambdaExpression lambdaExpression; + Expression expr; +} +{ + ( + // unparenthesized parameter, e.g. x -> x * 2 + LOOKAHEAD( RelObjectName() "->" ) + expr = LambdaExpression() + | + // parenthesized parameter list, e.g. (value) -> value + 1 + LOOKAHEAD( ParenthesedColumnList() "->" ) + expr = LambdaExpression() + | + // whole lambda wrapped in parentheses, e.g. (x -> x * 2) + LOOKAHEAD( "(" ( RelObjectName() | ParenthesedColumnList() ) "->" ) + "(" lambdaExpression = LambdaExpression() ")" + { expr = new ParenthesedExpressionList(lambdaExpression); } + | + expr = Expression() + ) + { return expr; } +} + WithSearchClause WithSearchClause() #WithSearchClause: { Token orderingToken; @@ -5559,6 +7714,24 @@ WithSearchClause WithSearchClause() #WithSearchClause: } } +WithCycleClause WithCycleClause() #WithCycleClause: +{ + WithCycleClause cycle = new WithCycleClause(); + ExpressionList columns = new ExpressionList(); + String name; + Expression value; +} +{ + + name=RelObjectName() { columns.add(new Column(name)); } + ( "," name=RelObjectName() { columns.add(new Column(name)); } )* + name=RelObjectName() { cycle.setMarkColumnName(name); } + [ value=Expression() { cycle.setMarkValue(value); } + value=Expression() { cycle.setMarkDefault(value); } ] + name=RelObjectName() { cycle.setPathColumnName(name); } + { return cycle.withCycleColumns(columns); } +} + WithFunctionDeclaration WithFunctionDeclaration() #WithFunctionDeclaration: { String functionName; @@ -5652,22 +7825,119 @@ SelectItem SelectItem() #SelectItem: Expression expression; Alias alias = null; } +{ + expression = SelectItemExpression() + [ LOOKAHEAD({ isAliasAhead() }) alias=Alias() ] + { + SelectItem selectItem = new SelectItem(expression, alias); + linkAST(selectItem,jjtThis); + return selectItem; + } +} + +/** + * Recognizes a transformer without consuming it. APPLY(functionName) intentionally takes + * precedence over its ambiguous alias-column reading. Other aliases use the existing Alias + * grammar, including typed columns, at every transformer boundary. Only the transformer + * prefix is probed: expression bodies are parsed normally by the Pratt parser. + */ +boolean ColumnsTransformerAhead() #void: +{} +{ + LOOKAHEAD( "(" RelObjectName() ")" ) { return true; } + | LOOKAHEAD( ( | ) AliasColumns() ) { return false; } + | LOOKAHEAD( "(" | "(" | ParenthesedColumnList() ) + { return true; } + | { return false; } +} + +/** + * Keeps transformer consumption visible to enclosing syntactic lookaheads. The normal + * expression is parsed first; only a direct COLUMNS function can acquire transformers. + * The conditional node encloses both the matcher and its transformers, but adds no AST + * parent for ordinary expressions or alias-column lists. + */ +Expression SelectItemExpression() #ColumnsExpression(transformers != null): +{ + Expression expression; + ColumnsTransformer transformer; + List transformers = null; +} { // @fixme: Oracle's SEQUENCE.nextval is parsed as COLUMN with a name part nextval // @todo: parse a proper SEQUENCE instead of a COLUMN ( - LOOKAHEAD( 3 ) expression = ConnectByPriorOperator() + LOOKAHEAD(3) expression = ConnectByPriorOperator() | - ( expression=Condition() { expression = prattExpressionRest(expression, 2); } ) + ( expression = Condition() { expression = prattExpressionRest(expression, 2); } ) ) - [ LOOKAHEAD({ isAliasAhead() }) alias=Alias() ] + ( + LOOKAHEAD( "(" | "(" | ParenthesedColumnList() ) + { + if (!(expression instanceof Function)) { + break; + } + Function function = (Function) expression; + if (!"COLUMNS".equalsIgnoreCase(function.getName()) || function.isEscaped() + || function.getAttribute() != null || function.getChainedParameters() != null + || function.getKeep() != null || function.isIgnoreNullsOutside() + || !ColumnsTransformerAhead()) { + break; + } + } + transformer = ColumnsTransformer() + { + if (transformers == null) { + transformers = new ArrayList(); + } + transformers.add(transformer); + } + )* { - SelectItem selectItem = new SelectItem(expression, alias); - linkAST(selectItem,jjtThis); - return selectItem; + if (transformers == null) { + return expression; + } + ColumnsExpression columnsExpression = new ColumnsExpression(expression, transformers); + linkAST(columnsExpression, jjtThis); + return columnsExpression; } } +/** + * Parses a single ClickHouse transformer: APPLY(expression), EXCEPT (columns) or + * REPLACE (selectItems). ClickHouse parses these in a loop, so they may repeat + * and combine in any order. + */ +ColumnsTransformer ColumnsTransformer(): +{ + Expression applyExpression; + ParenthesedExpressionList exceptColumns; + List> replaceItems; + ColumnsTransformer transformer; +} +{ + ( + "(" + ( + LOOKAHEAD( RelObjectName() "->" ) applyExpression = LambdaExpression() + | + applyExpression = Expression() + ) + ")" + { transformer = new ColumnsTransformer(ColumnsTransformer.ColumnsTransformerType.APPLY) + .setApplyExpression(applyExpression); } + + | exceptColumns = ParenthesedColumnList() + { transformer = new ColumnsTransformer(ColumnsTransformer.ColumnsTransformerType.EXCEPT) + .setExceptColumns(exceptColumns); } + + | "(" replaceItems = SelectItemsList() ")" + { transformer = new ColumnsTransformer(ColumnsTransformer.ColumnsTransformerType.REPLACE) + .setReplaceItems(replaceItems); } + ) + { return transformer; } +} + /** * Parses the AllColumns-Pattern '*'. * @@ -5715,8 +7985,7 @@ Alias Alias(): Token token = null; boolean useAs = false; Alias alias; - String colname; - ColDataType colDataType = null; + List aliasColumns; } { ( @@ -5728,12 +7997,7 @@ Alias Alias(): [ LOOKAHEAD(2) name=RelObjectName() ] { alias = new Alias(name, true ); } - "(" { List list = new ArrayList(); } - colname = RelObjectName() [ colDataType = ColDataType() ] { list.add(new Alias.AliasColumn(colname, colDataType)); } - ( - "," { colDataType=null; } colname = RelObjectName() [ colDataType = ColDataType()] { list.add(new Alias.AliasColumn(colname, colDataType)); } - )* - ")" { alias.setAliasColumns(list); } + aliasColumns = AliasColumns() { alias.setAliasColumns(aliasColumns); } ) | @@ -5747,16 +8011,30 @@ Alias Alias(): ( name=RelObjectName() | token= { name=token.image; } ) { alias = new Alias(name,useAs); } - [ LOOKAHEAD(2) "(" { List list = new ArrayList(); } - colname = RelObjectName() [ colDataType = ColDataType() ] { list.add(new Alias.AliasColumn(colname, colDataType)); } - ("," { colDataType=null; } colname = RelObjectName() [ colDataType = ColDataType()] { list.add(new Alias.AliasColumn(colname, colDataType)); } )* - ")" { alias.setAliasColumns(list); } ] + [ LOOKAHEAD(2) aliasColumns = AliasColumns() { alias.setAliasColumns(aliasColumns); } ] ) ) { return alias; } } +List AliasColumns(): +{ + List columns = new ArrayList(); + String name; + ColDataType type = null; +} +{ + "(" name = RelObjectName() [ type = ColDataType() ] + { columns.add(new Alias.AliasColumn(name, type)); } + ( + "," { type = null; } name = RelObjectName() [ type = ColDataType() ] + { columns.add(new Alias.AliasColumn(name, type)); } + )* + ")" + { return columns; } +} + void SQLServerHint(SQLServerHints hints) : { String str; } @@ -5837,10 +8115,63 @@ List> PivotFunctionItems(): } { item = FunctionItem() {functionItems.add(item);} - ( "," item = FunctionItem() {functionItems.add(item);} )* + ( LOOKAHEAD(2) "," item = FunctionItem() {functionItems.add(item);} )* { return functionItems; } } +/** + * DuckDB's simplified UNPIVOT statement: + * UNPIVOT tbl ON columns [INTO NAME name VALUE values]. + */ +UnPivotQuery UnPivotQuery(): +{ + UnPivotQuery unPivotQuery = new UnPivotQuery(); + ExpressionList onExpressions; + ExpressionList valueColumns; + FromItem fromItem; + String nameColumn; +} +{ + fromItem=FromItem() { unPivotQuery.setFromItem(fromItem); } + onExpressions=ExpressionList() { unPivotQuery.setOnExpressions(onExpressions); } + // greedy: INTO after the ON list belongs to this UNPIVOT + [ + LOOKAHEAD(2) + nameColumn=RelObjectName() { unPivotQuery.setNameColumn(nameColumn); } + valueColumns=ColumnList() { unPivotQuery.setValueColumns(valueColumns); } + ] + { return unPivotQuery; } +} + +PivotQuery PivotQuery(): +{ + PivotQuery pivotQuery = new PivotQuery(); + FromItem fromItem; + ExpressionList onExpressions = null; + List> usingItems = null; + ExpressionList groupByExpressions = null; +} +{ + fromItem = FromItem() + ( + onExpressions = ExpressionList() + [ LOOKAHEAD(2) usingItems = PivotFunctionItems() ] + [ LOOKAHEAD(2) groupByExpressions = ExpressionList() ] + | + usingItems = PivotFunctionItems() + [ LOOKAHEAD(2) groupByExpressions = ExpressionList() ] + | + groupByExpressions = ExpressionList() + ) + { + pivotQuery.setFromItem(fromItem); + pivotQuery.setOnExpressions(onExpressions); + pivotQuery.setUsingItems(usingItems); + pivotQuery.setGroupByExpressions(groupByExpressions); + return pivotQuery; + } +} + SelectItem> ExpressionListItem(): { ExpressionList expressionList; @@ -5966,6 +8297,8 @@ MySqlSelectIntoClause MySqlSelectIntoClause(MySqlSelectIntoClause.Position posit { MySqlSelectIntoClause intoClause = new MySqlSelectIntoClause().withPosition(position); Token token; + ExpressionList variables = new ExpressionList(); + UserVariable variable; } { @@ -5976,12 +8309,58 @@ MySqlSelectIntoClause MySqlSelectIntoClause(MySqlSelectIntoClause.Position posit | { intoClause.setType(MySqlSelectIntoClause.Type.DUMPFILE); } token= { intoClause.setFileName(new StringValue(token.image)); } + | + LOOKAHEAD({ isMySqlDialect() }) + variable=MySqlIntoVariable() { variables.add(variable); } + // Same shape as IntoClause() above: a PlainSelect can be followed by "," (subselect in + // an expression list), so the repetition needs two tokens to decide. + ( LOOKAHEAD(2) "," variable=MySqlIntoVariable() { variables.add(variable); } )* + { intoClause.setType(MySqlSelectIntoClause.Type.VARIABLES); intoClause.setVariables(variables); } ) { return intoClause; } } +UserVariable MySqlIntoVariable(): +{ + UserVariable variable; + Token name; +} +{ + ( + variable=UserVariable() + | + (name= | name=) + { variable = new UserVariable("@" + name.image); } + ) + { return requireMySqlIntoVariable(variable); } +} + +MySqlProcedureAnalyse MySqlProcedureAnalyse(): +{ + MySqlProcedureAnalyse procedureAnalyse = new MySqlProcedureAnalyse(); + Token maxElements = null; + Token maxMemory = null; +} +{ + "(" + [ + maxElements= + [ "," maxMemory= ] + ] + ")" + { + if (maxElements != null) { + procedureAnalyse.setMaxElements(new LongValue(maxElements.image)); + } + if (maxMemory != null) { + procedureAnalyse.setMaxMemory(new LongValue(maxMemory.image)); + } + return procedureAnalyse; + } +} + void MySqlSelectIntoOutfileTail(MySqlSelectIntoClause intoClause): { Token token; @@ -6140,7 +8519,7 @@ FromItem FromItem() #FromItem: || (getToken(1).kind == K_LATERAL && getToken(2).kind != OPENING_BRACKET) }) fromItem=TableFunction() | - LOOKAHEAD(3) fromItem=Table() + LOOKAHEAD(3) fromItem=TableReference() | LOOKAHEAD({ isNestedSetOperationAhead() }) ( fromItem=ParenthesedSelect() @@ -6163,14 +8542,29 @@ FromItem FromItem() #FromItem: LOOKAHEAD({ getAsBoolean(Feature.allowUnparenthesizedSubSelects) }) fromItem=Select() ) - [ LOOKAHEAD({ isAliasAhead() }) alias=Alias() { fromItem.setAlias(alias); } ] + [ LOOKAHEAD({ !isMatchRecognizeAhead() && isAliasAhead() }) alias=Alias() { fromItem.setAlias(alias); } ] + [ + // BigQuery: UNNEST(array) AS alias WITH OFFSET AS offsetAlias + LOOKAHEAD(2, { fromItem instanceof TableFunction }) + { ((TableFunction) fromItem).setWithOffset(true); } + [ LOOKAHEAD({ isAliasAhead() }) alias=Alias() + { ((TableFunction) fromItem).setOffsetAlias(alias); } ] + ] [ LOOKAHEAD(2, {fromItem instanceof Table }) timeTravelStr = TimeTravelAfterAlias() { ((Table) fromItem).setTimeTravelStrAfterAlias(timeTravelStr); } ] - [ LOOKAHEAD(2) sampleClause = SampleClause() { fromItem.setSampleClause(sampleClause); } ] - [ LOOKAHEAD(2) unpivot=UnPivot() { fromItem.setUnPivot(unpivot); } ] - [ LOOKAHEAD(2) ( LOOKAHEAD(2) pivot=PivotXml() | pivot=Pivot() ) { fromItem.setPivot(pivot); } ] + ( + LOOKAHEAD({ isMatchRecognizeAhead() }) fromItem=MatchRecognize(fromItem) + [ LOOKAHEAD(2) ( LOOKAHEAD(2) pivot=PivotXml() | pivot=Pivot() ) { fromItem.setPivot(pivot); } ] + [ LOOKAHEAD(2) unpivot=UnPivot() { fromItem.setUnPivot(unpivot); } ] + [ LOOKAHEAD({ isAliasAhead() }) alias=Alias() { fromItem.setAlias(alias); } ] + [ LOOKAHEAD(2) sampleClause=SampleClause() { fromItem.setSampleClause(sampleClause); } ] + | + [ LOOKAHEAD(2) sampleClause = SampleClause() { fromItem.setSampleClause(sampleClause); } ] + [ LOOKAHEAD(2) unpivot=UnPivot() { fromItem.setUnPivot(unpivot); } ] + [ LOOKAHEAD(2) ( LOOKAHEAD(2) pivot=PivotXml() | pivot=Pivot() ) { fromItem.setPivot(pivot); } ] + ) [ LOOKAHEAD(2) ( @@ -6191,6 +8585,265 @@ FromItem FromItem() #FromItem: } } + +MatchRecognize MatchRecognize(FromItem input) #MatchRecognize: +{ + MatchRecognize result = new MatchRecognize(input); + ExpressionList partition; + List ordering; + List> measures; + MatchRecognize.Definition definition; + MatchRecognize.Subset subset; + RowPattern pattern; + MatchRecognize.Options options = null; + String variable; + Token value; +} +{ + ContextualKeyword("MATCH_RECOGNIZE") "(" + [ partition=SimpleExpressionList() { result.setPartitionBy(partition); } ] + [ LOOKAHEAD(2) ordering=OrderByElements() { result.setOrderByElements(ordering); } ] + [ LOOKAHEAD({ isKeywordAhead("MEASURES") }) ContextualKeyword("MEASURES") measures=SelectItemsList() + { + for (SelectItem measure : measures) { + if (measure.getAlias() == null) { throw new ParseException("MATCH_RECOGNIZE measures require an alias"); } + } + result.setMeasures(measures); + } + ] + [ + LOOKAHEAD({ isKeywordAhead("ONE") || getToken(1).kind == K_ALL }) + ( LOOKAHEAD({ isKeywordAhead("ONE") }) ContextualKeyword("ONE") ContextualKeyword("PER") + { result.setRowsPerMatch(MatchRecognize.RowsPerMatch.ONE); } + | ContextualKeyword("PER") + { result.setRowsPerMatch(MatchRecognize.RowsPerMatch.ALL); } + [ + ContextualKeyword("MATCHES") + { result.setEmptyMatchMode(MatchRecognize.EmptyMatchMode.SHOW); } + | LOOKAHEAD({ isKeywordAhead("OMIT") }) ContextualKeyword("OMIT") ContextualKeyword("MATCHES") + { result.setEmptyMatchMode(MatchRecognize.EmptyMatchMode.OMIT); } + | ContextualKeyword("UNMATCHED") + { result.setEmptyMatchMode(MatchRecognize.EmptyMatchMode.WITH_UNMATCHED); } + ] + ) + ] + [ LOOKAHEAD({ isKeywordAhead("AFTER") }) ContextualKeyword("AFTER") + ( LOOKAHEAD({ isKeywordAhead("PAST") }) ContextualKeyword("PAST") + { result.setSkipMode(MatchRecognize.SkipMode.PAST_LAST_ROW); } + | + ( LOOKAHEAD(2) { result.setSkipMode(MatchRecognize.SkipMode.TO_NEXT_ROW); } + | { result.setSkipMode(MatchRecognize.SkipMode.TO_VARIABLE); } + [ LOOKAHEAD({ (getToken(1).kind == K_FIRST || getToken(1).kind == K_LAST) + && !("PATTERN".equalsIgnoreCase(getToken(2).image) && getToken(3).kind == OPENING_BRACKET) }) + ( { result.setSkipMode(MatchRecognize.SkipMode.TO_FIRST); } + | { result.setSkipMode(MatchRecognize.SkipMode.TO_LAST); } ) ] + variable=RelObjectName() { result.setSkipVariable(variable); } + ) + ) + ] + ContextualKeyword("PATTERN") "(" pattern=RowPattern(0) ")" { result.setPattern(pattern); } + [ LOOKAHEAD({ isKeywordAhead("SUBSET") }) ContextualKeyword("SUBSET") subset=MatchRecognizeSubset() + { result.getSubsets().add(subset); } + ( LOOKAHEAD(2) "," subset=MatchRecognizeSubset() { result.getSubsets().add(subset); } )* + ] + ContextualKeyword("DEFINE") definition=MatchRecognizeDefinition() { result.getDefinitions().add(definition); } + ( LOOKAHEAD(2) "," definition=MatchRecognizeDefinition() { result.getDefinitions().add(definition); } )* + [ LOOKAHEAD({ isKeywordAhead("OPTIONS") }) ContextualKeyword("OPTIONS") "(" + { options = new MatchRecognize.Options(); } + [ LOOKAHEAD({ isKeywordAhead("use_longest_match") }) ContextualKeyword("use_longest_match") "=" + ( value= | value= ) { options.setUseLongestMatch(value.kind == K_TRUE); } + ] + ")" { result.setOptions(options); } + ] + ")" + { + if (Dialect.BIGQUERY.name().equals(getAsString(Feature.dialect))) { + if (result.getOrderByElements().isEmpty() || result.getMeasures().isEmpty()) { + throw new ParseException("BigQuery MATCH_RECOGNIZE requires ORDER BY and MEASURES"); + } + if (result.getRowsPerMatch() != null || result.getSkipVariable() != null || !result.getSubsets().isEmpty()) { + throw new ParseException("BigQuery does not support ROWS PER MATCH, SKIP TO a variable or SUBSET"); + } + } + linkAST(result, jjtThis); return result; + } +} + +MatchRecognize.Subset MatchRecognizeSubset() #MatchRecognizeSubset: +{ + String name; + String variable; + List variables = new ArrayList(); +} +{ + name=RelObjectName() "=" "(" variable=RelObjectName() { variables.add(variable); } + ( "," variable=RelObjectName() { variables.add(variable); } )* ")" + { + MatchRecognize.Subset result = new MatchRecognize.Subset(name, variables); + linkAST(result, jjtThis); return result; + } +} + +MatchRecognize.Definition MatchRecognizeDefinition() #MatchRecognizeDefinition: +{ + String name; + Expression expression; +} +{ + name=RelObjectName() expression=Expression() + { + MatchRecognize.Definition result = new MatchRecognize.Definition(name, expression); + linkAST(result, jjtThis); + return result; + } +} + +RowPattern RowPattern(int depth) #RowPattern: +{ + List alternatives = new ArrayList(); + RowPattern pattern; + Token separator; +} +{ + { + if (depth > getAsLong(Feature.allowedNestingDepth)) { + throw new ParseException("Row pattern exceeds the allowed nesting depth"); + } + } + ( + LOOKAHEAD({ Dialect.SNOWFLAKE.name().equals(getAsString(Feature.dialect)) }) + pattern=SnowflakeRowPatternSequence(depth) { alternatives.add(pattern); } + | + pattern=RowPatternSequence(depth) { alternatives.add(pattern); } + ( ( separator= | separator= ) + { if (separator.kind == OP_CONCAT) { alternatives.add(new RowPattern.Empty()); } } + pattern=RowPatternSequence(depth) { alternatives.add(pattern); } + )* + ) + { + RowPattern result = alternatives.size() == 1 ? alternatives.get(0) + : new RowPattern.Operation(RowPattern.Operation.Type.ALTERNATION, alternatives); + linkAST(result, jjtThis); + return result; + } +} + +RowPattern SnowflakeRowPatternSequence(int depth) #SnowflakeRowPatternSequence: +{ + List patterns = new ArrayList(); + RowPattern pattern; +} +{ + ( LOOKAHEAD({ isRowPatternPrimaryAhead() }) pattern=SnowflakeRowPatternAlternative(depth) { patterns.add(pattern); } )* + { + RowPattern result = patterns.isEmpty() ? new RowPattern.Empty() + : patterns.size() == 1 ? patterns.get(0) + : new RowPattern.Operation(RowPattern.Operation.Type.SEQUENCE, patterns); + linkAST(result, jjtThis); return result; + } +} + +RowPattern SnowflakeRowPatternAlternative(int depth) #SnowflakeRowPatternAlternative: +{ + List patterns = new ArrayList(); + RowPattern pattern; +} +{ + pattern=RowPatternFactor(depth) { patterns.add(pattern); } + ( pattern=RowPatternFactor(depth) { patterns.add(pattern); } )* + { + RowPattern result = patterns.size() == 1 ? patterns.get(0) + : new RowPattern.Operation(RowPattern.Operation.Type.ALTERNATION, patterns); + linkAST(result, jjtThis); return result; + } +} + +RowPattern RowPatternSequence(int depth): +{ + List patterns = new ArrayList(); + RowPattern pattern; +} +{ + ( LOOKAHEAD({ isRowPatternPrimaryAhead() }) pattern=RowPatternFactor(depth) { patterns.add(pattern); } )* + { + if (patterns.isEmpty()) { return new RowPattern.Empty(); } + return patterns.size() == 1 ? patterns.get(0) + : new RowPattern.Operation(RowPattern.Operation.Type.SEQUENCE, patterns); + } +} + +RowPattern RowPatternFactor(int depth) #RowPatternFactor: +{ + RowPattern pattern; + String name; + List permutations = new ArrayList(); +} +{ + ( + LOOKAHEAD({ isKeywordAhead("PERMUTE") && getToken(2).kind == OPENING_BRACKET }) + ContextualKeyword("PERMUTE") "(" + pattern=RowPattern(depth + 1) { permutations.add(pattern); } + ( "," pattern=RowPattern(depth + 1) { permutations.add(pattern); } )* ")" + { pattern = new RowPattern.Permute(permutations); } + | "{" "-" pattern=RowPattern(depth + 1) "-" "}" { pattern = new RowPattern.Exclusion(pattern); } + | { pattern = new RowPattern.Anchor(RowPattern.Anchor.Type.START); } + | LOOKAHEAD({ "$".equals(getToken(1).image) }) + { pattern = new RowPattern.Anchor(RowPattern.Anchor.Type.END); } + | "(" pattern=RowPattern(depth + 1) ")" { pattern = new RowPattern.Group(pattern); } + | name=RelObjectName() { pattern = new RowPattern.Variable(name); } + ) + { + if (Dialect.BIGQUERY.name().equals(getAsString(Feature.dialect)) + && (pattern instanceof RowPattern.Permute || pattern instanceof RowPattern.Exclusion)) { + throw new ParseException("BigQuery does not support PERMUTE or row pattern exclusion"); + } + } + [ LOOKAHEAD({ !(pattern instanceof RowPattern.Anchor) + && ("?".equals(getToken(1).image) || "*".equals(getToken(1).image) + || "+".equals(getToken(1).image) || getToken(1).kind == OPENING_CURLY_BRACKET) }) + pattern=RowPatternQuantifier(pattern) + ] + { linkAST(pattern, jjtThis); return pattern; } +} + +RowPattern RowPatternQuantifier(RowPattern pattern) #RowPatternQuantifier: +{ + RowPattern.Quantified result; + Expression lower = null; + Expression upper = null; + boolean range = false; +} +{ + ( + "*" { result = new RowPattern.Quantified(pattern, RowPattern.Quantified.Type.ZERO_OR_MORE); } + | "+" { result = new RowPattern.Quantified(pattern, RowPattern.Quantified.Type.ONE_OR_MORE); } + | "?" { result = new RowPattern.Quantified(pattern, RowPattern.Quantified.Type.OPTIONAL); } + | "{" [ LOOKAHEAD({ !",".equals(getToken(1).image) }) lower=RowPatternBound() ] + [ "," { range = true; } [ LOOKAHEAD({ getToken(1).kind != CLOSING_CURLY_BRACKET }) upper=RowPatternBound() ] ] "}" + { + if (lower == null && upper == null) { throw new ParseException("Row pattern quantifier requires a bound"); } + result = new RowPattern.Quantified(pattern, range ? RowPattern.Quantified.Type.RANGE : RowPattern.Quantified.Type.EXACT) + .setLowerBound(lower).setUpperBound(upper); + } + ) + [ LOOKAHEAD({ (result.getType() != RowPattern.Quantified.Type.EXACT + || !Dialect.BIGQUERY.name().equals(getAsString(Feature.dialect))) && "?".equals(getToken(1).image) }) "?" { result.setReluctant(true); } ] + { linkAST(result, jjtThis); return result; } +} + +Expression RowPatternBound(): +{ + Token value; + Expression result; +} +{ + ( value= { result = new LongValue(value.image); } + | "?" { result = new JdbcParameter(++jdbcParameterIndex, false, "?"); } + | result=UserVariable() + ) + { return result; } +} + List JoinsList(): { List joinsList = new ArrayList(); @@ -6222,6 +8875,21 @@ JoinHint JoinHint(): } } +JoinHint DorisJoinHint(): +{ + Token token; +} +{ + "[" + ( + LOOKAHEAD({ isKeywordAhead("SHUFFLE") }) token= + | + LOOKAHEAD({ isKeywordAhead("BROADCAST") }) token= + ) + "]" + { return new JoinHint(token.image, JoinHint.Position.AFTER_JOIN); } +} + Join JoinerExpression() #JoinerExpression: { Join join = new Join(); @@ -6231,23 +8899,29 @@ Join JoinerExpression() #JoinerExpression: List columns = null; KSQLJoinWindow joinWindow = null; JoinHint joinHint = null; + Token tk = null; } { + [ LOOKAHEAD({ isAsOfJoinAhead() }) { join.setAsOf(true); } ] [ { join.setGlobal(true); } ] [ { join.setAny(true); } | { join.setAll(true); } ] [ { join.setNatural(true); } ] [ ( - { join.setLeft(true); } [ { join.setSemi(true); } | { join.setOuter(true); } | { join.setAny(true); } | { join.setAll(true); } ] + { join.setLeft(true); } [ { join.setSemi(true); } | { join.setAnti(true); } | { join.setOuter(true); } | { join.setAny(true); } | { join.setAll(true); } ] | ( { join.setRight(true); } | { join.setFull(true); } - ) [ { join.setOuter(true); } | { join.setAny(true); } | { join.setAll(true); } ] + ) [ { join.setSemi(true); } | { join.setAnti(true); } | { join.setOuter(true); } | { join.setAny(true); } | { join.setAll(true); } ] | { join.setInner(true); } + | + // DuckDB allows SEMI/ANTI without a LEFT prefix + LOOKAHEAD({ isSemiAntiJoinAhead() }) + ( { join.setSemi(true); } | { join.setAnti(true); } ) ) | { join.setCross(true); } @@ -6255,10 +8929,20 @@ Join JoinerExpression() #JoinerExpression: { join.setOuter(true); } ] + [ { join.setArray(true); } ] + ( ( [ joinHint=JoinHint() {join.setJoinHint(joinHint); } ] + [ LOOKAHEAD("[", { Dialect.DORIS.name().equals(getAsString(Feature.dialect)) }) + { + if (joinHint != null) { + throw new ParseException("Only one join hint is allowed per JOIN"); + } + } + joinHint=DorisJoinHint() { join.setJoinHint(joinHint); } + ] [ { join.setFetch(true); } ] ) | @@ -6271,11 +8955,17 @@ Join JoinerExpression() #JoinerExpression: right=FromItem() + // DuckDB 2.0: JOIN products APPROX NEAREST 2 BY SIMILARITY array_cosine_similarity(...) + [ + LOOKAHEAD({ isApproxNearestAhead() }) + tk= { join.setApproxNearest(Long.valueOf(tk.image)); } + onExpression=Expression() { join.setSimilarity(onExpression); } + ] [ LOOKAHEAD(2) ( - [ "(" joinWindow = JoinWindow() ")" {join.setJoinWindow(joinWindow);} ] + [ joinWindow = KSQLJoinWindowClause() {join.setJoinWindow(joinWindow);} ] ( onExpression=Expression() { join.addOnExpression(onExpression); } - ( LOOKAHEAD(2) onExpression=Expression() { join.addOnExpression(onExpression); } )* + ( LOOKAHEAD({ getToken(1).kind == K_ON && !isInsertOnConflictAhead() }) onExpression=Expression() { join.addOnExpression(onExpression); } )* ) | ( @@ -6293,74 +8983,95 @@ Join JoinerExpression() #JoinerExpression: } +/** Shared duration parsing for JOIN, aggregation windows, and grace periods. */ +KSQLWindow.Duration KSQLDuration(): +{ + Token value; + Token unit; +} +{ + value= ( unit= | unit= ) + { return new KSQLWindow.Duration(Long.parseLong(value.image), KSQLWindow.TimeUnit.from(unit.image)); } +} + +KSQLWindow.Duration KSQLGracePeriod(): +{ + KSQLWindow.Duration duration; +} +{ + AccessKeyword("GRACE") AccessKeyword("PERIOD") duration=KSQLDuration() + { return duration; } +} + +KSQLJoinWindow KSQLJoinWindowClause(): +{ + KSQLJoinWindow window; + KSQLWindow.Duration duration; +} +{ + ( + "(" window=JoinWindow() ")" + | + duration=KSQLDuration() { + window = new KSQLJoinWindow().withDuration(duration.getValue()) + .withTimeUnit(duration.getTimeUnit()).withUsingBrackets(false); + } + ) + [ LOOKAHEAD({ isKeywordAhead("GRACE") }) duration=KSQLGracePeriod() { window.setGracePeriod(duration); } ] + { return window; } +} + KSQLJoinWindow JoinWindow(): { - KSQLJoinWindow retval = new KSQLJoinWindow(); - boolean beforeAfter; - Token beforeDurationToken = null; - Token beforeTimeUnitToken = null; - Token afterDurationToken = null; - Token afterTimeUnitToken = null; + KSQLJoinWindow window = new KSQLJoinWindow(); + KSQLWindow.Duration before; + KSQLWindow.Duration after = null; } { - beforeDurationToken= (beforeTimeUnitToken= | beforeTimeUnitToken=) - [ "," afterDurationToken= (afterTimeUnitToken= | afterTimeUnitToken=) ] + before=KSQLDuration() + [ "," after=KSQLDuration() ] { - if (afterDurationToken == null) { - retval.setDuration(Long.parseLong(beforeDurationToken.image)); - retval.setTimeUnit(KSQLWindow.TimeUnit.from(beforeTimeUnitToken.image)); - retval.setBeforeAfterWindow(false); - return retval; + if (after == null) { + window.setDuration(before.getValue()); + window.setTimeUnit(before.getTimeUnit()); + } else { + window.setBeforeDuration(before.getValue()); + window.setBeforeTimeUnit(before.getTimeUnit()); + window.setAfterDuration(after.getValue()); + window.setAfterTimeUnit(after.getTimeUnit()); + window.setBeforeAfterWindow(true); } - retval.setBeforeDuration(Long.parseLong(beforeDurationToken.image)); - retval.setBeforeTimeUnit(KSQLWindow.TimeUnit.from(beforeTimeUnitToken.image)); - retval.setAfterDuration(Long.parseLong(afterDurationToken.image)); - retval.setAfterTimeUnit(KSQLWindow.TimeUnit.from(afterTimeUnitToken.image)); - retval.setBeforeAfterWindow(true); - return retval; + return window; } } KSQLWindow KSQLWindowClause(): { - KSQLWindow retval = null; - Token sizeDurationToken = null; - Token sizeTimeUnitToken = null; - Token advanceDurationToken = null; - Token advanceTimeUnitToken = null; + KSQLWindow window = new KSQLWindow(); + KSQLWindow.Duration size; + KSQLWindow.Duration advance; + KSQLWindow.Duration grace; } { - { - retval=new KSQLWindow(); - retval.setHoppingWindow(false); - retval.setSessionWindow(false); - retval.setTumblingWindow(false); - } ( - "(" - sizeDurationToken= sizeTimeUnitToken= "," - advanceDurationToken= advanceTimeUnitToken= ")" - { - retval.setHoppingWindow(true); - } | - "(" sizeDurationToken= sizeTimeUnitToken= ")" - { - retval.setSessionWindow(true); - } | - "(" sizeDurationToken= sizeTimeUnitToken= ")" - { - retval.setTumblingWindow(true); + "(" size=KSQLDuration() + "," advance=KSQLDuration() { + window.setHoppingWindow(true); + window.setAdvanceDuration(advance.getValue()); + window.setAdvanceTimeUnit(advance.getTimeUnit()); } + | + "(" size=KSQLDuration() { window.setSessionWindow(true); } + | + "(" size=KSQLDuration() { window.setTumblingWindow(true); } ) + [ "," grace=KSQLGracePeriod() { window.setGracePeriod(grace); } ] + ")" { - retval.setSizeDuration(Long.parseLong(sizeDurationToken.image)); - retval.setSizeTimeUnit(KSQLWindow.TimeUnit.from(sizeTimeUnitToken.image)); - if (advanceDurationToken != null) { - retval.setAdvanceDuration(Long.parseLong(advanceDurationToken.image)); - retval.setAdvanceTimeUnit(KSQLWindow.TimeUnit.from(advanceTimeUnitToken.image)); - } - return retval; + window.setSizeDuration(size.getValue()); + window.setSizeTimeUnit(size.getTimeUnit()); + return window; } } @@ -6574,7 +9285,13 @@ GroupByElement GroupByColumnReferences(): ) | ( - list = ExpressionList() { groupBy.setGroupByExpressions(list); } + ( + LOOKAHEAD({ getAsBoolean(Feature.allowLegacyMySqlGroupBy) + && "MYSQL".equals(getAsString(Feature.dialect)) }) + LegacyMySqlGroupByExpressions(groupBy) + | + list = ExpressionList() { groupBy.setGroupByExpressions(list); } + ) ( LOOKAHEAD(2) "(" @@ -6590,6 +9307,27 @@ GroupByElement GroupByColumnReferences(): } } +void LegacyMySqlGroupByExpressions(GroupByElement groupBy): +{} +{ + LegacyMySqlGroupByExpression(groupBy) + ( LOOKAHEAD(2) "," LegacyMySqlGroupByExpression(groupBy) )* +} + +void LegacyMySqlGroupByExpression(GroupByElement groupBy): +{ + Expression expression; + Token direction; +} +{ + expression = Expression() { groupBy.getGroupByExpressionList().add(expression); } + [ LOOKAHEAD(2) (direction= | direction=) { + groupBy.setGroupBySortDirection(groupBy.getGroupByExpressionList().size() - 1, + direction.kind == K_ASC ? GroupByElement.SortDirection.ASC + : GroupByElement.SortDirection.DESC); + } ] +} + ExpressionList GroupingSet(): { ExpressionList list; @@ -6646,6 +9384,7 @@ OrderByElement OrderByElement(): OrderByElement orderByElement = new OrderByElement(); Expression columnReference = null; Token collateToken = null; + WithFill withFill = null; } { columnReference = Expression() @@ -6659,6 +9398,7 @@ OrderByElement OrderByElement(): ) ] ] + [ LOOKAHEAD(2) withFill = WithFill() { orderByElement.setWithFill(withFill); } ] [ LOOKAHEAD(2) { orderByElement.setMysqlWithRollup(true); } ] { orderByElement.setExpression(columnReference); @@ -6666,87 +9406,185 @@ OrderByElement OrderByElement(): } } -JdbcParameter JdbcParameter() : { - Token tk; - JdbcParameter retval; +WithFill WithFill(): +{ + WithFill withFill = new WithFill(); + Expression fillExpression = null; } { - ( tk="?" | tk= ) - { retval = new JdbcParameter(++jdbcParameterIndex, false, tk.image); } - - [ LOOKAHEAD(2) token = { retval.setUseFixedIndex(true); retval.setIndex(Integer.valueOf(token.image)); } ] + + [ LOOKAHEAD(2) fillExpression = Expression() { withFill.setFrom(fillExpression); } ] + [ LOOKAHEAD(2) fillExpression = Expression() { withFill.setTo(fillExpression); } ] + [ LOOKAHEAD(2) fillExpression = Expression() { withFill.setStep(fillExpression); } ] + [ LOOKAHEAD(2) fillExpression = Expression() { withFill.setStaleness(fillExpression); } ] + { return withFill; } +} - { return retval; } +List InterpolateClause(): +{ + List interpolateElements = new ArrayList(); + InterpolateElement interpolateElement = null; +} +{ + + [ LOOKAHEAD(2) "(" + [ interpolateElement = InterpolateElement() { interpolateElements.add(interpolateElement); } + ( "," interpolateElement = InterpolateElement() { interpolateElements.add(interpolateElement); } )* + ] + ")" ] + { return interpolateElements; } } +InterpolateElement InterpolateElement(): +{ + InterpolateElement interpolateElement = new InterpolateElement(); + Column column = null; + Expression expression = null; +} +{ + column = Column() { interpolateElement.setColumn(column); } + [ expression = Expression() { interpolateElement.setExpression(expression); } ] + { return interpolateElement; } +} -Limit LimitWithOffset() #LimitWithOffset: +Expression OptionHintParameter(): { - Limit limit = new Limit(); - Expression firstExpression; - Expression secondExpression; + UserVariable userVariable = null; + Expression parameter = null; } { - - ( - LOOKAHEAD(3) firstExpression = ParenthesedSelect() - | - firstExpression = Expression() - ) ( - // MySQL: LIMIT offset, row_count - "," - secondExpression = Expression() + LOOKAHEAD({ getToken(1).kind == S_AT_IDENTIFIER && getToken(2).kind == K_UNKNOWN }) + userVariable = UserVariable() { - limit.setOffset(firstExpression); - limit.setRowCount(secondExpression); + return new Column(userVariable + " UNKNOWN"); } | - // PostgreSQL: LIMIT row_count - { - limit.setRowCount(firstExpression); - } + parameter = Expression() { return parameter; } ) - { - linkAST(limit, jjtThis); - return limit; - } } -Limit PlainLimit() #PlainLimit: +OptionClause OptionClause(): { - Limit limit = new Limit(); - Expression rowCountExpression; + OptionClause optionClause = new OptionClause(); + OptionHint optionHint = null; } { - // mysql-postgresql-> LIMIT (row_count | ALL | NULL) - - ( - LOOKAHEAD(3) rowCountExpression = ParenthesedSelect() - | - rowCountExpression = Expression() - ) - { - limit.setRowCount(rowCountExpression); - linkAST(limit,jjtThis); + "(" optionHint = OptionHint() { optionClause.addOptionHint(optionHint); } + ( "," optionHint = OptionHint() { optionClause.addOptionHint(optionHint); } )* + ")" + { return optionClause; } +} + +OptionHint OptionHint(): +{ + OptionHint optionHint = new OptionHint(); + String name = null; + Expression value = null; + Expression parameter = null; +} +{ + name = OptionHintName() { optionHint.setName(name); } + [ + ( + LOOKAHEAD("=") "=" value = Expression() { optionHint.setValue(value); optionHint.setUseEquals(true); } + | + LOOKAHEAD("(") "(" parameter = OptionHintParameter() { optionHint.addParameter(parameter); } + ( "," parameter = OptionHintParameter() { optionHint.addParameter(parameter); } )* + ")" + | + LOOKAHEAD({ isOptionHintValueAhead() }) value = Expression() { optionHint.setValue(value); } + ) + ] + { return optionHint; } +} + +String OptionHintName(): +{ + Token tk = null; + String word = null; + StringBuilder name = new StringBuilder(); +} +{ + ( + LOOKAHEAD({ isOptionHintWordAhead() }) + ( tk= | word = NonReservedWord() + | tk= | tk= | tk= + | tk= | tk= | tk= + | tk= | tk= | tk= | tk= ) + { + if (name.length() > 0) { name.append(' '); } + name.append(tk != null ? tk.image : word); + tk = null; + word = null; + } + )+ + { return name.toString(); } +} + +JdbcParameter JdbcParameter() : { + Token tk; + JdbcParameter retval; +} +{ + ( tk="?" | tk= ) + { retval = new JdbcParameter(++jdbcParameterIndex, false, tk.image); } + + [ LOOKAHEAD(2) token = { retval.setUseFixedIndex(true); retval.setIndex(Integer.valueOf(token.image)); } ] + + { return retval; } +} + + +Limit LimitWithOffset() #LimitWithOffset: +{ + Limit limit = new Limit(); + Expression firstExpression; + Expression secondExpression; +} +{ + + ( + LOOKAHEAD(3) firstExpression = ParenthesedSelect() + | + firstExpression = Expression() + ) + ( + // MySQL: LIMIT offset, row_count + "," + secondExpression = Expression() + { + limit.setOffset(firstExpression); + limit.setRowCount(secondExpression); + } + | + // PostgreSQL: LIMIT row_count + { + limit.setRowCount(firstExpression); + } + ) + { + linkAST(limit, jjtThis); return limit; } } -/** - * Clickhouse LIMIT BY - * @see SELECT Query - */ -Limit LimitBy(): +Limit PlainLimit() #PlainLimit: { - Limit limit; - ExpressionList byExpressions; + Limit limit = new Limit(); + Expression rowCountExpression; } { - limit = LimitWithOffset() - byExpressions = ExpressionList() + // mysql-postgresql-> LIMIT (row_count | ALL | NULL) + + ( + LOOKAHEAD(3) rowCountExpression = ParenthesedSelect() + | + rowCountExpression = Expression() + ) { - limit.setByExpressions(byExpressions); + limit.setRowCount(rowCountExpression); + linkAST(limit,jjtThis); return limit; } } @@ -7003,13 +9841,16 @@ Expression Condition(): { Expression result; Expression left; - boolean not = false; - boolean exclamationMarkNot = false; + // Leading NOT / ! prefixes, leftmost token wraps outermost (issue #1170). + final Deque exclamationMarks = new ArrayDeque(); int oraclePrior = EqualsTo.NO_ORACLE_PRIOR; int oracleJoin = EqualsTo.NO_ORACLE_JOIN; } { - [ LOOKAHEAD(2, { getToken(1).kind == K_NOT || "!".equals(getToken(1).image) }) ( { not=true; } | "!" { not=true; exclamationMarkNot=true; })] + ( + LOOKAHEAD({ getToken(1).kind == K_NOT || "!".equals(getToken(1).image) }) + ( { exclamationMarks.addFirst(Boolean.FALSE); } | "!" { exclamationMarks.addFirst(Boolean.TRUE); } ) + )* ( result=ExistsExpression() | @@ -7054,7 +9895,10 @@ Expression Condition(): && result instanceof SupportsOldOracleJoinSyntax) { ((SupportsOldOracleJoinSyntax) result).setOraclePriorPosition(oraclePrior); } - return not ? new NotExpression(result, exclamationMarkNot) : result; + while (!exclamationMarks.isEmpty()) { + result = new NotExpression(result, exclamationMarks.pollFirst()); + } + return result; } } @@ -7121,7 +9965,7 @@ Expression RegularConditionRHS(Expression leftExpression, int oracleJoinRight) # } { - linkAST(result, jjtThis); + linkAST(result, jjtThis, leftExpression); return result; } } @@ -7210,7 +10054,7 @@ Expression InExpression(Expression leftExpression) #InExpression : .withOldOracleJoinSyntax(oldOracleJoin) .withNot(usingNot) .setGlobal(usingGlobal); - linkAST(inExpression,jjtThis); + linkAST(inExpression, jjtThis, leftExpression); return inExpression; } } @@ -7225,7 +10069,7 @@ Expression IncludesExpression(Expression leftExpression) #IncludesExpression : { IncludesExpression includesExpression = new IncludesExpression(leftExpression, rightExpression); - linkAST(includesExpression,jjtThis); + linkAST(includesExpression, jjtThis, leftExpression); return includesExpression; } } @@ -7240,7 +10084,7 @@ Expression ExcludesExpression(Expression leftExpression) #ExcludesExpression : { ExcludesExpression excludesExpression = new ExcludesExpression(leftExpression, rightExpression); - linkAST(excludesExpression,jjtThis); + linkAST(excludesExpression, jjtThis, leftExpression); return excludesExpression; } } @@ -7269,6 +10113,15 @@ Expression Between(Expression leftExpression) : LOOKAHEAD({ isComparisonOperatorAhead() }) betweenExpressionStart = RegularConditionRHS(betweenExpressionStart, EqualsTo.NO_ORACLE_JOIN) ] + [ + LOOKAHEAD("(" "+" ")") + "(" "+" ")" + { + if (betweenExpressionStart instanceof Column) { + ((Column) betweenExpressionStart).setOldOracleJoinSyntax(EqualsTo.ORACLE_JOIN_RIGHT); + } + } + ] ) @@ -7280,6 +10133,15 @@ Expression Between(Expression leftExpression) : LOOKAHEAD({ isComparisonOperatorAhead() }) betweenExpressionEnd = RegularConditionRHS(betweenExpressionEnd, EqualsTo.NO_ORACLE_JOIN) ] + [ + LOOKAHEAD("(" "+" ")") + "(" "+" ")" + { + if (betweenExpressionEnd instanceof Column) { + ((Column) betweenExpressionEnd).setOldOracleJoinSyntax(EqualsTo.ORACLE_JOIN_RIGHT); + } + } + ] ) { @@ -7305,6 +10167,7 @@ Expression LikeExpression(Expression leftExpression) #LikeExpression: | token = | token = | token = + | token = | token = | token = | token = @@ -7314,6 +10177,15 @@ Expression LikeExpression(Expression leftExpression) #LikeExpression: ) { result.setLikeKeyWord( LikeExpression.KeyWord.from(token.image)); } [ LOOKAHEAD(2) {result.setUseBinary(true); } ] rightExpression=SimpleExpression() + [ + LOOKAHEAD("(" "+" ")") + "(" "+" ")" + { + if (rightExpression instanceof Column) { + ((Column) rightExpression).setOldOracleJoinSyntax(EqualsTo.ORACLE_JOIN_RIGHT); + } + } + ] [ LOOKAHEAD(2) ( LOOKAHEAD(2) token = { result.setEscape( new StringValue( token.image ) ); } @@ -7324,7 +10196,7 @@ Expression LikeExpression(Expression leftExpression) #LikeExpression: { result.setLeftExpression(leftExpression); result.setRightExpression(rightExpression); - linkAST(result,jjtThis); + linkAST(result, jjtThis, leftExpression); return result; } } @@ -7338,11 +10210,20 @@ Expression SimilarToExpression(Expression leftExpression) #SimilarToExpression: [ { result.setNot(true); } ] rightExpression=SimpleExpression() + [ + LOOKAHEAD("(" "+" ")") + "(" "+" ")" + { + if (rightExpression instanceof Column) { + ((Column) rightExpression).setOldOracleJoinSyntax(EqualsTo.ORACLE_JOIN_RIGHT); + } + } + ] [ LOOKAHEAD(2) token= { result.setEscape((new StringValue(token.image)).getValue()); }] { result.setLeftExpression(leftExpression); result.setRightExpression(rightExpression); - linkAST(result,jjtThis); + linkAST(result, jjtThis, leftExpression); return result; } } @@ -7358,7 +10239,7 @@ Expression IsDistinctExpression(Expression leftExpression) #IsDistinctExpression { result.setLeftExpression(leftExpression); result.setRightExpression(rightExpression); - linkAST(result,jjtThis); + linkAST(result, jjtThis, leftExpression); return result; } } @@ -7509,7 +10390,7 @@ ExpressionList SimpleExpressionList(): ( LOOKAHEAD(2, {!interrupted} ) "," ( - LOOKAHEAD( 7 ) expr=LambdaExpression() + LOOKAHEAD( RelObjectName() "->" ) expr=LambdaExpression() | expr=SimpleExpression() ) @@ -7572,7 +10453,7 @@ ExpressionList ComplexExpressionList(): | LOOKAHEAD(2) expr=PostgresNamedFunctionParameter() | - LOOKAHEAD(7) expr=LambdaExpression() + LOOKAHEAD( RelObjectName() "->" ) expr=LambdaExpression() | expr=Expression() ) { expressions.add(expr); } @@ -7690,6 +10571,18 @@ Expression SimpleExpression(): } } +StringValue CharacterSetIntroducedString(): +{ + Token introducer; + Token literal; +} +{ + introducer= literal= + { + return new StringValue(literal.image).withPrefix(introducer.image); + } +} + /** Multiplicative/bitwise-shift — higher precedence */ Expression MulChain(): { @@ -7753,7 +10646,7 @@ Expression ArrayExpression(Expression obj): { Expression stopExpr = null; } { "[" - [LOOKAHEAD(3) idxExpr = SimpleExpression()] + [LOOKAHEAD(3) idxExpr = ArrayIndexExpression()] [ (":" { startExpr=idxExpr; idxExpr=null; }) [stopExpr = SimpleExpression()] @@ -7762,7 +10655,7 @@ Expression ArrayExpression(Expression obj): { { expr = new ArrayExpression(obj, idxExpr, startExpr, stopExpr); } ( LOOKAHEAD(2) "[" - [LOOKAHEAD(3) idxExpr = SimpleExpression()] + [LOOKAHEAD(3) idxExpr = ArrayIndexExpression()] [ (":" { startExpr=idxExpr; idxExpr=null; }) [stopExpr = SimpleExpression()] @@ -7773,16 +10666,61 @@ Expression ArrayExpression(Expression obj): { { return expr; } } +/** + * Array subscript index. BigQuery addresses array elements positionally with + * OFFSET(n), ORDINAL(n), SAFE_OFFSET(n) and SAFE_ORDINAL(n). Only OFFSET needs + * an explicit rule here, the others are plain identifiers and parse as functions. + */ +Expression ArrayIndexExpression() : +{ + Token token; + Expression expr; +} +{ + ( + LOOKAHEAD(2) token = "(" expr = SimpleExpression() ")" + { + return new Function() + .withName(token.image) + .withParameters(new ExpressionList(expr)); + } + | + expr = SimpleExpression() { return expr; } + ) +} + +Expression NavigationStep(Expression base): +{ + Expression step = null; + String nm = null; + Function method; +} +{ + ( + "." + ( + LOOKAHEAD({ Dialect.SQLSERVER.name().equals(getAsString(Feature.dialect)) && isFunctionAhead() }) + method=Function() { step = new MethodCallExpression(base, method); } + | + nm=RelObjectNameExt() { step = new RowGetExpression(base, nm); } + ) + | + step = ArrayExpression(base) + ) + { return step; } +} + Expression PrimaryExpression() #PrimaryExpression: { Expression retval = null; + Expression nxt = null; Expression expression = null; CastExpression castExpr = null; TimezoneExpression timezoneExpr = null; Expression timezoneRightExpr = null; Token token = null; Token sign = null; - String tmp = ""; + Token adjacentToken = null; ColDataType type = null; boolean not = false; boolean exclamationMarkNot = false; @@ -7801,6 +10739,10 @@ Expression PrimaryExpression() #PrimaryExpression: | LOOKAHEAD(2, {!interrupted}) retval=CharacterPrimary() + | LOOKAHEAD({ !interrupted && getToken(1).kind == DATA_TYPE + && "MAP".equalsIgnoreCase(getToken(1).image) + && getToken(2).kind == OPENING_CURLY_BRACKET }) retval=MapExpression() + | LOOKAHEAD({!interrupted && isImplicitCastAhead()}) retval=ImplicitCast() | retval = JdbcParameter() @@ -7823,6 +10765,9 @@ Expression PrimaryExpression() #PrimaryExpression: | LOOKAHEAD(2, {!interrupted}) retval= CastExpression() + | LOOKAHEAD({ !interrupted && (isKeywordAhead("RUNNING") || getToken(1).kind == K_FINAL) + && getToken(3).kind == OPENING_BRACKET }) retval=RowPatternFunction() + | LOOKAHEAD({ !interrupted && isFunctionAhead() }) retval = Function() [ LOOKAHEAD(2) retval = AnalyticExpression( (Function) retval ) ] @@ -7866,6 +10811,9 @@ Expression PrimaryExpression() #PrimaryExpression: | LOOKAHEAD(2, {!interrupted}) { retval = new AllValue(); } + | LOOKAHEAD({ !interrupted && isCharacterSetIntroducedStringAhead() }) + retval=CharacterSetIntroducedString() + | LOOKAHEAD(2, {!interrupted}) retval=Column() [ LOOKAHEAD( "(" "+" ")" ("," | ")") ) @@ -7875,6 +10823,13 @@ Expression PrimaryExpression() #PrimaryExpression: | LOOKAHEAD(2, {!interrupted}) (token= | token=) { retval = new BooleanValue(token.image); } | token= { retval = new StringValue(token.image); linkAST(retval,jjtThis); } + ( LOOKAHEAD({ isAdjacentStringConcat() }) + adjacentToken= + { + ((StringValue) retval) + .setValue(((StringValue) retval).getValue() + new StringValue(adjacentToken.image).getValue()); + } + )* | "{d" token= "}" { retval = new DateValue(token.image); } @@ -7912,8 +10867,17 @@ Expression PrimaryExpression() #PrimaryExpression: } ) - // RowGet Expressions - ( LOOKAHEAD(2) "." tmp=RelObjectName() { retval = new RowGetExpression(retval, tmp); } )* + // PostgreSQL composite row expansion: (function()).* + // Re-enables FunctionAllColumns, which was disabled in #2207 due to the + // performance toll of a speculative syntactic lookahead. A bounded + // semantic follower check instead reads the already-parsed retval and + // peeks the next two tokens, avoiding any speculative production. + [ LOOKAHEAD( { isFunctionAllColumnsAhead(retval) } ) + "." "*" + { retval = new FunctionAllColumns(unwrapParenthesedFunction(retval)); } ] + + // Share field and method navigation with the other primary expressions. + ( LOOKAHEAD(".") nxt=NavigationStep(retval) { retval = nxt; } )* ) ) @@ -7927,6 +10891,12 @@ Expression PrimaryExpression() #PrimaryExpression: [ LOOKAHEAD(2) retval = ArrayExpression(retval) ] + // navigation chains, e.g. Redshift SUPER col[0].field[1].sub: field accesses + // and subscripts may alternate freely after the primary expression + ( + LOOKAHEAD(2) nxt = NavigationStep(retval) { retval = nxt; } + )* + ( LOOKAHEAD(2) "::" type=ColDataType() { castExpr = new CastExpression(); castExpr.setUseCastKeyword(false); @@ -7938,7 +10908,8 @@ Expression PrimaryExpression() #PrimaryExpression: // Check for JSON operands [ - LOOKAHEAD(2) ( + LOOKAHEAD(2, { (ternaryThenBranchDepth == 0 && mapKeyDepth == 0) + || !":".equals(getToken(1).image) }) ( LOOKAHEAD(2) ( token="->" | @@ -8162,6 +11133,43 @@ ArrayConstructor ArrayConstructor(boolean arrayKeyword) : { { return array; } } +MapExpression MapExpression() #MapExpression: +{ + MapExpression mapExpression = new MapExpression(); + Expression key; + Expression value; +} +{ + + [ + key = MapKeyExpression() value = Expression() + { mapExpression.addEntry(key, value); } + ( + "," key = MapKeyExpression() value = Expression() + { mapExpression.addEntry(key, value); } + )* + ] + + { + linkAST(mapExpression, jjtThis); + return mapExpression; + } +} + +Expression MapKeyExpression(): +{ + Expression key; +} +{ + try { + { mapKeyDepth++; } + key = Expression() + } finally { + mapKeyDepth--; + } + { return key; } +} + List> StructParameters(): { String parameterName = ""; @@ -8268,7 +11276,8 @@ JsonExpression JsonExpression(Expression expr, List) { interval.setIntervalType(token.image); } + // The entry decision for this optional block has to be stated explicitly: + // also starts whatever follows IntervalExpression() at its call sites (an alias, for + // instance), so one token of lookahead cannot decide whether to enter. + LOOKAHEAD({ getToken(1).kind == K_DATE_LITERAL || getToken(1).kind == S_IDENTIFIER }) + ( + // Standard qualifier (field [TO field] [(precision)]) such as DAY or DAY TO SECOND. + LOOKAHEAD({ getToken(1).kind == K_DATE_LITERAL }) qualifier = IntervalQualifier() { interval.setIntervalQualifier(qualifier); } + | + // Non-standard single identifier field (e.g. MySQL `INTERVAL 1 foo`), kept for + // backwards compatibility. + token = { interval.setIntervalType(token.image); } + ) ] { return interval; @@ -9422,7 +12438,13 @@ CastExpression CastExpression(): ")" ) | - type=ColDataType() { retval.setColDataType(type); } + ( + type=ColDataType() { retval.setColDataType(type); } + // MySQL casts to an array of the given type when a multi-valued index key part is + // defined, e.g. CAST(data->'$.zips' AS UNSIGNED ARRAY). ARRAY reads as a further + // word of the type, the way INT UNSIGNED already does. + [ { type.setDataType(type.getDataType() + " ARRAY"); } ] + ) ) // BigQuery FORMAT clause @@ -9514,27 +12536,48 @@ EqualsTo VariableExpression(): { } Execute Execute(): { - Token token; ObjectNames funcName; - ExpressionList expressionList = null; + ExpressionList expressionList; Execute execute = new Execute(); - List namedExprList; - Expression expr; } { ( { execute.setExecType(Execute.ExecType.EXEC); } | { execute.setExecType(Execute.ExecType.EXECUTE); } | { execute.setExecType(Execute.ExecType.CALL); } ) - funcName=RelObjectNames() { execute.setName(funcName.getNames()); } + // A colon starts a bind argument here; it must not join the procedure name. + funcName=ColumnIdentifier() { execute.setName(funcName.getNames()); } + [ LOOKAHEAD(2) expressionList=ExecuteArguments() { execute.setExprList(expressionList); } ] + { return execute; } +} +ExpressionList ExecuteArguments(): { + ExpressionList arguments = new ExpressionList(); + Expression argument; +} +{ ( - LOOKAHEAD(2) expressionList=ExpressionList() { execute.setExprList(expressionList); } - )? + LOOKAHEAD("(") arguments=ExpressionList() + | + argument=ExecuteArgument() { arguments.add(argument); } + ( "," argument=ExecuteArgument() { arguments.add(argument); } )* + ) + { return arguments; } +} + +Expression ExecuteArgument(): { + Expression argument; + boolean output = false; +} +{ + argument=Expression() + [ + LOOKAHEAD({ isAccessKeywordAhead("OUTPUT") || isAccessKeywordAhead("OUT") }) + ( | AccessKeyword("OUT") ) + { output = true; } + ] + { return output ? createOutputArgument(argument) : argument; } - { - return execute; - } } FullTextSearch FullTextSearch() : { @@ -9586,6 +12629,23 @@ LambdaExpression LambdaExpression() #LambdaExpression: } } +RowPatternFunction RowPatternFunction() #RowPatternFunction: +{ + RowPatternFunction.EvaluationMode mode; + Function function; +} +{ + ( LOOKAHEAD({ isKeywordAhead("RUNNING") }) ContextualKeyword("RUNNING") + { mode = RowPatternFunction.EvaluationMode.RUNNING; } + | { mode = RowPatternFunction.EvaluationMode.FINAL; } + ) + function=Function() + { + RowPatternFunction result = new RowPatternFunction(mode, function); + linkAST(result, jjtThis); return result; + } +} + Function Function() #Function: { Function function; @@ -9593,6 +12653,7 @@ Function Function() #Function: { ( "{" function = InternalFunction(true) "}" + { normalizeOdbcTimestampInterval(function); } | LOOKAHEAD(3) function = SpecialStringFunctionWithNamedParameters() | function = InternalFunction(false) ) @@ -9658,13 +12719,14 @@ Function InternalFunction(boolean escaped): Column attributeColumn = null; Token attributeToken = null; Limit limit; + List orderByList = null; List keywordArgs = null; } { [ LOOKAHEAD(2) prefixToken = ] - funcName = RelObjectNames() { if (prefixToken!=null) funcName.getNames().add(0, prefixToken.image ); } + funcName = RelObjectNames() { if (prefixToken!=null) funcName.getNames().add(0, prefixToken.image ); retval.setName(funcName.getNames()); } - "(" + "(" { consumeXmlLeadingKeyword(retval); } [ LOOKAHEAD(2) [ LOOKAHEAD(2) ( @@ -9707,6 +12769,12 @@ Function InternalFunction(boolean escaped): ) ] + // BigQuery orders aggregate arguments after the null handling: + // ARRAY_AGG(expr [IGNORE NULLS] [ORDER BY key] [LIMIT n]) + [ + LOOKAHEAD(2) orderByList = OrderByElements() { retval.setOrderByElements(orderByList); } + ] + [ limit = PlainLimit() { retval.setLimit(limit); } ] @@ -9787,9 +12855,11 @@ ExpressionList FunctionArgumentList(Function retval): Token extraKeywordToken; } { + { if ("XMLFOREST".equalsIgnoreCase(retval.getName())) { return XmlForestArguments(); } } [ LOOKAHEAD(2) extraKeywordToken = { retval.setExtraKeyword(extraKeywordToken.image); } ] expressionList=ExpressionList() - [ orderByList = OrderByElements() { retval.setOrderByElements(orderByList); } ] + // explicit lookahead: InternalFunction accepts ORDER BY after IGNORE NULLS too + [ LOOKAHEAD(2) orderByList = OrderByElements() { retval.setOrderByElements(orderByList); } ] // https://docs.oracle.com/en/database/oracle/oracle-database/21/sqlrf/LISTAGG.html [ @@ -9805,27 +12875,75 @@ ExpressionList FunctionArgumentList(Function retval): } } +/** XMLFOREST keeps expressions in the ordinary function parameter list. */ +ExpressionList XmlForestArguments(): +{ + ExpressionList arguments = new ExpressionList(); + Expression expression; +} +{ + expression=XmlForestArgument() { arguments.add(expression); } + ( "," expression=XmlForestArgument() { arguments.add(expression); } )* + { return arguments; } +} + +Expression XmlForestArgument(): +{ + Expression expression; + String name; +} +{ + expression=Expression() + [ name=RelObjectName() { expression = new AliasedExpression(expression, new Alias(name, true)); } ] + { return expression; } +} + XMLSerializeExpr XMLSerializeExpr(): { - XMLSerializeExpr result; + XMLSerializeExpr result = new XMLSerializeExpr(); Expression expression; List orderByElements = null; ColDataType dataType; + Token token; } { - - "(" + "(" + ( + LOOKAHEAD({ isKeywordAhead("CONTENT") || isKeywordAhead("DOCUMENT") }) + token= + { result.setSerializationMode(XMLSerializeExpr.SerializationMode.valueOf(token.image.toUpperCase(Locale.ROOT))); } + expression=Expression() { result.setExpression(expression); } + [ dataType=ColDataType() { result.setDataType(dataType); } ] + [ token= { result.setEncoding(new StringValue(token.image)); } ] + [ token= { result.setVersion(new StringValue(token.image)); } ] + [ + LOOKAHEAD(2) token= { + requireDdlSyntax("INDENT".equalsIgnoreCase(token.image), "Expected INDENT after NO"); + result.setIndent(false); + } + | + LOOKAHEAD({ isKeywordAhead("INDENT") }) token= { result.setIndent(true); } + [ "=" token= { result.setIndentSize(new LongValue(token.image)); } ] + ] + [ + { result.setShowDefaults(true); } + | + LOOKAHEAD({ isKeywordAhead("HIDE") }) token= + { result.setShowDefaults(false); } + ] + | + "(" "(" expression=SimpleExpression() ")" [ orderByElements=OrderByElements() ] ")" - dataType=ColDataType() ")" - { - result = new XMLSerializeExpr(); - result.setExpression(expression); - result.setOrderByElements(orderByElements); - result.setDataType(dataType); - return result; - } + dataType=ColDataType() { + result.setExpression(expression); + result.setOrderByElements(orderByElements); + result.setDataType(dataType); + } + ) + ")" + { return result; } } @@ -10455,33 +13573,116 @@ List ColumnNamesWithParamsList() : { Index.ColumnParams IndexColumnWithParams(): { String columnName = null; + List columnParams = new ArrayList(); List parameter = null; Expression expression = null; + String collation = null; + String operatorClass = null; + List operatorClassParameters = null; + Token sortOrder = null; + Token nullOrdering = null; Index.ColumnParams column = null; + boolean postgres = Dialect.POSTGRESQL.name().equals(getAsString(Feature.dialect)); + boolean expressionParenthesized = true; } { ( + LOOKAHEAD({ Dialect.POSTGRESQL.name().equals(getAsString(Feature.dialect)) && isFunctionAhead() }) expression=Function() + { expressionParenthesized = false; } + | columnName=RelObjectName() - { parameter = null; } - [ parameter = CreateParameter() ] - { - column = new Index.ColumnParams(columnName, parameter); - } | "(" expression=Expression() ")" - { parameter = null; } - [ LOOKAHEAD(2) parameter = CreateParameter() ] + ) + // MySQL prefix lengths precede all PostgreSQL key attributes. + [ LOOKAHEAD("(", { !Dialect.POSTGRESQL.name().equals(getAsString(Feature.dialect)) }) parameter=CreateParameter() { columnParams.addAll(parameter); } ] + [ LOOKAHEAD(2) collation=IndexKeyAttributeName() + { if (!postgres) { columnParams.add("COLLATE"); columnParams.add(collation); } } ] + [ LOOKAHEAD(2, IndexKeyAttributeName(), { + getToken(1).kind == S_IDENTIFIER || getToken(1).kind == S_QUOTED_IDENTIFIER + || (Dialect.POSTGRESQL.name().equals(getAsString(Feature.dialect)) + && getToken(1).kind != K_ASC && getToken(1).kind != K_DESC + && getToken(1).kind != K_NULLS && getToken(1).kind != K_COLLATE + && getToken(1).kind != K_WITH) }) + operatorClass=IndexKeyAttributeName() + [ LOOKAHEAD(2) operatorClassParameters=PostgreSqlIndexOptions() ] { - column = new Index.ColumnParams(expression, parameter); + if (!postgres) { + columnParams.add(operatorClass); + if (operatorClassParameters != null) { + columnParams.add(PlainSelect.getStringList( + operatorClassParameters, true, true)); + } + } + } + ] + [ LOOKAHEAD(2) (sortOrder= | sortOrder=) + { if (!postgres) { columnParams.add(sortOrder.image); } } ] + [ LOOKAHEAD(2) (nullOrdering= | nullOrdering=) + { if (!postgres) { columnParams.add("NULLS"); columnParams.add(nullOrdering.image); } } ] + ( LOOKAHEAD(2, { !Dialect.POSTGRESQL.name().equals(getAsString(Feature.dialect)) && getToken(1).kind != K_WITH }) parameter=CreateParameter() { columnParams.addAll(parameter); } )* + { + column = expression != null + ? new Index.ColumnParams(expression, + columnParams.isEmpty() ? null : columnParams) + : new Index.ColumnParams(columnName, + columnParams.isEmpty() ? null : columnParams); + column.setCollation(collation); + column.setExpressionParenthesized(expressionParenthesized); + if (operatorClass != null) { + column.setOperatorClass(operatorClass); + column.setOperatorClassParameters(operatorClassParameters); + } + if (sortOrder != null) { + column.setSortOrder(Index.ColumnParams.SortOrder.valueOf( + sortOrder.image.toUpperCase(Locale.ROOT))); + } + if (nullOrdering != null) { + column.setNullOrdering(Index.ColumnParams.NullOrdering.valueOf( + nullOrdering.image.toUpperCase(Locale.ROOT))); } - ) - { return column; } } -List IndexColumnsWithParamsList() : { - List colNames = new ArrayList(); +/** Keeps qualified PostgreSQL collation and operator-class names out of expression parsing. */ +String IndexKeyAttributeName(): +{ + String name; + String part; +} +{ + name=RelObjectName() + ( LOOKAHEAD(".", { Dialect.POSTGRESQL.name().equals(getAsString(Feature.dialect)) }) + "." part=RelObjectNameExt() { name += "." + part; } + )* + { return name; } +} + +Index.Option PostgreSqlIndexOption(): +{ + String name = null; + Expression value = null; + boolean useEquals = false; +} +{ + name=RelObjectName() [ "=" { useEquals = true; } ] value=Expression() + { return new Index.Option(name, value, useEquals); } +} + +List PostgreSqlIndexOptions(): +{ + List options = new ArrayList(); + Index.Option option = null; +} +{ + "(" option=PostgreSqlIndexOption() { options.add(option); } + ( "," option=PostgreSqlIndexOption() { options.add(option); } )* ")" + { return options; } +} + +List IndexColumnsWithParamsList() : { + List colNames = new ArrayList(); Index.ColumnParams column = null; } { @@ -10511,26 +13712,61 @@ Index Index(): { name= RelObjectNames() { return new Index().withName(name.getNames()).withType(""); } } +/** Parses dialect-specific index modifiers while retaining the legacy single type token. */ +Index CreateIndexModifiers(CreateIndex statement): +{ + Index index = new Index(); + Token token; + List parameter; + Index.Clustering clustering; +} +{ + ( + LOOKAHEAD({ Dialect.SQLSERVER.name().equals(getAsString(Feature.dialect)) + && (getToken(1).kind == K_UNIQUE || isKeywordAhead("CLUSTERED") + || isKeywordAhead("NONCLUSTERED")) }) + [ token= { index.setType(token.image); } ] + clustering=SqlServerIndexClustering() { index.setClustering(clustering); } + | + LOOKAHEAD({ Dialect.SPANNER.name().equals(getAsString(Feature.dialect)) + && (getToken(1).kind == K_UNIQUE || isKeywordAhead("NULL_FILTERED")) }) + [ token= { index.setType(token.image); } ] + [ LOOKAHEAD({ isKeywordAhead("NULL_FILTERED") }) token= + { statement.setNullFiltered(true); } ] + | + [ parameter=CreateParameter() { index.setType(parameter.get(0)); } ] + ) + { return index; } +} + CreateIndex CreateIndex(): { CreateIndex createIndex = new CreateIndex(); Table table = null; List colNames; + List includeColumns; + Boolean nullsDistinct = null; + List storageParameters = null; + String tableSpace = null; + Expression where = null; String indexType; - Index index = null; + Index index; List parameter = new ArrayList(); List tailParameters = new ArrayList(); - List name; + ObjectNames name; } { - [ parameter=CreateParameter() ] + index=CreateIndexModifiers(createIndex) + [ LOOKAHEAD({ getToken(1).kind == K_CONCURRENTLY }) + { createIndex.setConcurrently(true); } ] [ LOOKAHEAD(2) { createIndex.setUsingIfNotExists(true);} ] - index = Index() { index.setType(parameter.isEmpty() ? null : parameter.get(0)); } + [ LOOKAHEAD({ getToken(1).kind != K_ON && getToken(1).kind != K_USING }) + name=RelObjectNames() { index.setName(name.getNames()); } ] ( LOOKAHEAD(3)( - table=Table() + [ { createIndex.setOnly(true); } ] table=Table() [ indexType=UsingIndexType() { index.setUsing(indexType); } ] ) | @@ -10540,11 +13776,28 @@ CreateIndex CreateIndex(): createIndex.setIndexTypeBeforeOn(true); } ] - table=Table() + [ { createIndex.setOnly(true); } ] table=Table() ) ) colNames = IndexColumnsWithParamsList() - ( LOOKAHEAD(2) parameter=CreateParameter() { tailParameters.addAll(parameter); } )* + [ includeColumns=ColumnsNamesList() + { createIndex.setIncludeColumns(includeColumns); } + ] + [ LOOKAHEAD(3) [ { nullsDistinct = false; } ] + { + if (nullsDistinct == null) { + nullsDistinct = true; + } + createIndex.setNullsDistinct(nullsDistinct); + } + ] + [ LOOKAHEAD(2) storageParameters=PostgreSqlIndexOptions() + { createIndex.setStorageParameters(storageParameters); } ] + [ LOOKAHEAD(2) tableSpace=RelObjectName() + { createIndex.setTableSpace(tableSpace); } ] + [ LOOKAHEAD(2) where=Expression() { createIndex.setWhere(where); } ] + ( LOOKAHEAD(2, { getToken(1).kind != K_LOCK || getToken(2).kind != K_TABLE }) + parameter=CreateIndexTailOption() { tailParameters.addAll(parameter); } )* { index.setColumns(colNames); createIndex.setIndex(index); @@ -10554,26 +13807,501 @@ CreateIndex CreateIndex(): } } +List CreateIndexTailOption(): +{ + List option; +} +{ + ( + LOOKAHEAD({ getToken(1).kind == K_ALGORITHM || getToken(1).kind == K_LOCK }) + option=IndexAlgorithmOrLockOption() + | + option=CreateParameter() + ) + { return option; } +} + +// CREATE INDEX and DROP INDEX share MySQL's algorithm_option / lock_option syntax. +// Keep the legacy token list, including optional equals signs and the original order. +List IndexAlgorithmOrLockOption(): +{ + List option = new ArrayList(); + Token keyword; + Token value; +} +{ + ( keyword= | keyword= ) { option.add(keyword.image); } + [ "=" { option.add("="); } ] + // COPY is one of MySQL's ALGORITHM values and also starts the COPY statement + ( value= | value= | value= | value= + | value= ) + { option.add(value.image); return option; } +} + +List ColumnDefinitionParameter(): { + Token tk = null; + List parameter = new ArrayList(); +} { + ( + parameter=CreateParameter() + | + ( + tk= + | tk= + | tk= + | tk= + | tk= + ) + { parameter.add(tk.image); } + ) + { return parameter; } +} + ColumnDefinition ColumnDefinition(): { ColumnDefinition coldef; String columnName; ColDataType colDataType; List columnSpecs = new ArrayList(); - List parameter; + List columnOptions = new ArrayList(); + ColumnOption option; } { columnName=RelObjectName() colDataType=ColDataType() - ( LOOKAHEAD(2) parameter=CreateParameter() { columnSpecs.addAll(parameter); } )* + ( LOOKAHEAD(2) option=ColumnDefinitionOption() { + columnOptions.add(option); + columnSpecs.addAll(option.getTokens()); + } )* { coldef = new ColumnDefinition(); coldef.setColumnName(columnName); coldef.setColDataType(colDataType); if (columnSpecs.size() > 0) coldef.setColumnSpecs(columnSpecs); + if (hasStructuredColumnOption(columnOptions)) + coldef.setColumnOptions(columnOptions); return coldef; } } +ColumnOption ColumnDefinitionOption(): { + Token tk; + List parameter; + ForeignKeyReference reference; + ColumnOption option; + IdentityDefinition identity; + NamedConstraint constraint; + Expression defaultExpression; + GeneratedColumnDefinition generated; + ObjectNames collationNames; + String collation; +} { + ( + LOOKAHEAD({ isKeywordAhead("GENERATED") + && (getToken(4).kind == K_IDENTITY || getToken(5).kind == K_IDENTITY) }) + identity=IdentityDefinition() { option = ColumnOption.identity(identity); } + | + LOOKAHEAD({ (isKeywordAhead("GENERATED") && getToken(2).kind == K_ALWAYS + && getToken(3).kind == K_AS && "(".equals(getToken(4).image)) + || (getToken(1).kind == K_AS && "(".equals(getToken(2).image)) }) + generated=GeneratedColumnDefinition() { option = ColumnOption.generated(generated); } + | + LOOKAHEAD( ) { option = ColumnOption.nullability(false); } + | + LOOKAHEAD() { option = ColumnOption.nullability(true); } + | + LOOKAHEAD() { option = ColumnOption.visibility(true); } + | + LOOKAHEAD() { option = ColumnOption.visibility(false); } + | + LOOKAHEAD() { option = ColumnOption.autoIncrement(); } + | + LOOKAHEAD() + ( tk= { collation = tk.image; } + | collationNames=RelObjectNames() { collation = String.join(".", collationNames.getNames()); } ) + { option = ColumnOption.collate(collation); } + | + LOOKAHEAD() tk= + { option = ColumnOption.comment(new StringValue(tk.image)); } + | + LOOKAHEAD( ) defaultExpression=Expression() + { option = ColumnOption.onUpdate(defaultExpression); } + | + LOOKAHEAD( ) + { option = ColumnOption.constraint(new NamedConstraint().withType("PRIMARY KEY")); } + | + LOOKAHEAD() constraint=ColumnUniqueConstraint() + { option = ColumnOption.constraint(constraint); } + | + LOOKAHEAD({ isKeywordAhead("SERIAL") + && getToken(2).kind == K_DEFAULT && getToken(3).kind == K_VALUE }) + tk= + { option = ColumnOption.serialDefaultValue(); } + | + LOOKAHEAD() reference=ForeignKeyReferenceSpec() + { option = ColumnOption.reference(reference); } + | + LOOKAHEAD( ) + { option = ColumnOption.raw("DROP", "DEFAULT"); } + | + LOOKAHEAD( ) + { option = ColumnOption.raw("DEFAULT", "ON", "NULL"); } + | + LOOKAHEAD() { option = ColumnOption.raw("DEFAULT"); } + // Db2 permits an omitted default value. Keep these implicit defaults raw. + [ LOOKAHEAD(2, { !(getToken(1).kind == K_NOT && getToken(2).kind == K_NULL) }) + defaultExpression=Expression() { option = ColumnOption.defaultValue(defaultExpression); } ] + | + parameter=ColumnDefinitionParameter() + { option = ColumnOption.raw(parameter); } + ) + { return option; } +} + +GeneratedColumnDefinition GeneratedColumnDefinition(): +{ + Expression expression; + boolean generatedAlways = false; + GeneratedColumnDefinition.Storage storage = null; + GeneratedColumnDefinition result; +} +{ + [ LOOKAHEAD({ isKeywordAhead("GENERATED") }) ContextualKeyword("GENERATED") + { generatedAlways = true; } ] + "(" expression=Expression() ")" + [ LOOKAHEAD({ getToken(1).kind == K_STORED || isKeywordAhead("VIRTUAL") }) + ( { storage = GeneratedColumnDefinition.Storage.STORED; } + | ContextualKeyword("VIRTUAL") { storage = GeneratedColumnDefinition.Storage.VIRTUAL; } ) ] + { + result = new GeneratedColumnDefinition(expression); + result.setGeneratedAlways(generatedAlways); + result.setStorage(storage); + return result; + } +} + +NamedConstraint ColumnUniqueConstraint(): +{ + NamedConstraint constraint = new NamedConstraint().withType("UNIQUE"); + Boolean nullsDistinct = null; +} +{ + + [ [ { nullsDistinct = false; } ] { + constraint.setNullsDistinct(nullsDistinct == null ? true : nullsDistinct); + } ] + { return constraint; } +} + +IdentityDefinition IdentityDefinition(): +{ + Token token; + IdentityDefinition.GenerationMode mode; + IdentityDefinition identity; + List parameters; +} +{ + token= { + requireDdlSyntax("GENERATED".equalsIgnoreCase(token.image), "Expected GENERATED"); + } + ( { mode = IdentityDefinition.GenerationMode.ALWAYS; } + | { mode = IdentityDefinition.GenerationMode.BY_DEFAULT; } ) + { identity = new IdentityDefinition(mode); } + [ LOOKAHEAD(2) "(" parameters=SequenceParameters() ")" { + requireDdlSyntax(!parameters.isEmpty(), "Expected an identity sequence parameter"); + identity.setParameters(parameters); + } ] + { return identity; } +} + +StringValue MySqlAccountNamePart(): +{ + Token token = null; + String name = null; + StringValue value = null; +} +{ + ( + token= { value = new StringValue(token.image); } + | + name=RelObjectName() { value = new StringValue(name).setQuoteStr(""); } + ) + { return value; } +} + +UserAuthentication MySqlUserAuthentication(): +{ + UserAuthentication authentication = new UserAuthentication(); + String plugin = null; + Token credential = null; +} +{ + + ( + + ( + credential= { + authentication.setMode(UserAuthentication.Mode.BY_PASSWORD); + authentication.setCredential(new StringValue(credential.image)); + } + | + { + authentication.setMode(UserAuthentication.Mode.BY_RANDOM_PASSWORD); + } + ) + | + plugin=RelObjectName() { + authentication.setMode(UserAuthentication.Mode.WITH_PLUGIN); + authentication.setPlugin(plugin); + } + [ + + ( + credential= { + authentication.setMode(UserAuthentication.Mode.WITH_PLUGIN_BY_PASSWORD); + authentication.setCredential(new StringValue(credential.image)); + } + | + { + authentication.setMode( + UserAuthentication.Mode.WITH_PLUGIN_BY_RANDOM_PASSWORD); + } + ) + | + credential= { + authentication.setMode(UserAuthentication.Mode.WITH_PLUGIN_AS_STRING); + authentication.setCredential(new StringValue(credential.image)); + } + ] + ) + { return authentication; } +} + +UserAccount MySqlUserAccount(): +{ + UserAccount account = new UserAccount(); + StringValue user = null; + StringValue host = null; + UserAuthentication authentication = null; +} +{ + user=MySqlAccountNamePart() { account.setUser(user); } + [ host=MySqlAccountNamePart() { account.setHost(host); } ] + [ authentication=MySqlUserAuthentication() { account.setAuthentication(authentication); } ] + { return account; } +} + +CreateUser CreateUser(): +{ + CreateUser createUser = new CreateUser(); + UserAccount account = null; +} +{ + + [ LOOKAHEAD(3) { createUser.setIfNotExists(true); } ] + account=MySqlUserAccount() { createUser.addAccounts(account); } + ( "," account=MySqlUserAccount() { createUser.addAccounts(account); } )* + { return createUser; } +} + +EventSchedule MySqlEventSchedule(): +{ + EventSchedule schedule = new EventSchedule(); + Expression expression = null; + Token unitToken = null; + String unit = null; +} +{ + + ( + expression=Expression() { + schedule.setType(EventSchedule.Type.AT); + schedule.setExecuteAt(expression); + } + | + expression=MySqlEventIntervalValue() { + schedule.setType(EventSchedule.Type.EVERY); + schedule.setInterval(expression); + } + ( LOOKAHEAD({ getToken(1).kind == K_DATE_LITERAL }) + unitToken= { schedule.setIntervalUnit(unitToken.image); } + | unit=RelObjectName() { schedule.setIntervalUnit(unit); } ) + [ expression=Expression() { schedule.setStarts(expression); } ] + [ expression=Expression() { schedule.setEnds(expression); } ] + ) + { return schedule; } +} + +Expression MySqlEventIntervalValue(): +{ + Token value = null; + Expression expression = null; +} +{ + ( + value= { expression = new LongValue(value.image); } + | + value= { expression = new DoubleValue(value.image); } + | + value= { expression = new StringValue(value.image); } + ) + { return expression; } +} + +Boolean MySqlEventCompletion(): +{ + boolean preserve = true; +} +{ + [ { preserve = false; } ] + LOOKAHEAD({ "PRESERVE".equalsIgnoreCase(getToken(1).image) }) + + { return preserve; } +} + +EventStatus MySqlEventStatus(): +{ + EventStatus status = null; +} +{ + ( + { status = EventStatus.ENABLE; } + | + { status = EventStatus.DISABLE; } + [ LOOKAHEAD(2) { status = EventStatus.DISABLE_ON_SLAVE; } ] + ) + { return status; } +} + +CreateEvent CreateEvent(): +{ + CreateEvent createEvent = new CreateEvent(); + Table event = null; + EventSchedule schedule = null; + Boolean preserve = null; + EventStatus status = null; + Token comment = null; + Statement body = null; +} +{ + + [ LOOKAHEAD(3) { createEvent.setIfNotExists(true); } ] + event=Table() { createEvent.setEvent(event); } + schedule=MySqlEventSchedule() { createEvent.setSchedule(schedule); } + [ LOOKAHEAD(2) preserve=MySqlEventCompletion() { + createEvent.setOnCompletionPreserve(preserve); + } ] + [ LOOKAHEAD({ getToken(1).kind == K_ENABLE || getToken(1).kind == K_DISABLE }) + status=MySqlEventStatus() { createEvent.setStatus(status); } ] + [ LOOKAHEAD({ getToken(1).kind == K_COMMENT }) comment= { + createEvent.setComment(new StringValue(comment.image)); + } ] + body=SingleStatement() { createEvent.setBody(body); } + { return createEvent; } +} + +AlterEvent AlterEvent(): +{ + AlterEvent alterEvent = new AlterEvent(); + Table event = null; + Table renameTo = null; + EventSchedule schedule = null; + Boolean preserve = null; + EventStatus status = null; + Token comment = null; + Statement body = null; +} +{ + event=Table() { alterEvent.setEvent(event); } + [ LOOKAHEAD({ getToken(1).kind == K_ON && getToken(2).kind == K_SCHEDULE }) + schedule=MySqlEventSchedule() { alterEvent.setSchedule(schedule); } ] + [ LOOKAHEAD({ getToken(1).kind == K_ON && getToken(2).kind == K_COMPLETION }) + preserve=MySqlEventCompletion() { alterEvent.setOnCompletionPreserve(preserve); } ] + [ LOOKAHEAD({ getToken(1).kind == K_RENAME }) + renameTo=Table() { alterEvent.setRenameTo(renameTo); } ] + [ LOOKAHEAD({ getToken(1).kind == K_ENABLE || getToken(1).kind == K_DISABLE }) + status=MySqlEventStatus() { alterEvent.setStatus(status); } ] + [ LOOKAHEAD({ getToken(1).kind == K_COMMENT }) comment= { + alterEvent.setComment(new StringValue(comment.image)); + } ] + [ LOOKAHEAD({ getToken(1).kind == K_DO }) + body=SingleStatement() { alterEvent.setBody(body); } ] + { return alterEvent; } +} + +TriggerDefiner MySqlTriggerDefiner(): +{ + TriggerDefiner definer = new TriggerDefiner(); + StringValue user = null; + StringValue host = null; +} +{ + LOOKAHEAD({ "DEFINER".equalsIgnoreCase(getToken(1).image) }) + "=" user=MySqlAccountNamePart() { definer.setUser(user); } + [ LOOKAHEAD({ getToken(1).kind == K_AT_SIGN }) + host=MySqlAccountNamePart() { definer.setHost(host); } ] + { return definer; } +} + +CreateTrigger.Timing MySqlTriggerTiming(): +{ + CreateTrigger.Timing timing = null; +} +{ + ( + { timing = CreateTrigger.Timing.BEFORE; } + | + LOOKAHEAD({ "AFTER".equalsIgnoreCase(getToken(1).image) }) + { timing = CreateTrigger.Timing.AFTER; } + ) + { return timing; } +} + +CreateTrigger.Order MySqlTriggerOrder(): +{ + CreateTrigger.Order order = null; + Token token = null; +} +{ + LOOKAHEAD({ "FOLLOWS".equalsIgnoreCase(getToken(1).image) + || "PRECEDES".equalsIgnoreCase(getToken(1).image) }) + token= { + order = "FOLLOWS".equalsIgnoreCase(token.image) + ? CreateTrigger.Order.FOLLOWS : CreateTrigger.Order.PRECEDES; + } + { return order; } +} + +CreateTrigger CreateTrigger(): +{ + CreateTrigger createTrigger = new CreateTrigger(); + TriggerDefiner definer = null; + Table trigger = null; + CreateTrigger.Timing timing = null; + Token event = null; + Table table = null; + CreateTrigger.Order order = null; + Table otherTrigger = null; + Statement body = null; +} +{ + [ LOOKAHEAD({ "DEFINER".equalsIgnoreCase(getToken(1).image) }) + definer=MySqlTriggerDefiner() { createTrigger.setDefiner(definer); } ] + trigger=Table() { createTrigger.setTrigger(trigger); } + timing=MySqlTriggerTiming() { createTrigger.setTiming(timing); } + ( event= { createTrigger.setEvent(CreateTrigger.Event.INSERT); } + | event= { createTrigger.setEvent(CreateTrigger.Event.UPDATE); } + | event= { createTrigger.setEvent(CreateTrigger.Event.DELETE); } ) + table=Table() { createTrigger.setTable(table); } + + LOOKAHEAD({ "EACH".equalsIgnoreCase(getToken(1).image) }) + [ order=MySqlTriggerOrder() otherTrigger=Table() { + createTrigger.setOrder(order); + createTrigger.setOtherTrigger(otherTrigger); + } ] + ( body=SingleStatement() | body=Block() ) { createTrigger.setBody(body); } + { return createTrigger; } +} + CreateSchema CreateSchema(): { Token tk = null; @@ -10582,6 +14310,7 @@ CreateSchema CreateSchema(): CreateSchema schema = new CreateSchema(); List schemaPath = null; List statements = new ArrayList(); + DatabaseOption databaseOption = null; } { @@ -10595,6 +14324,11 @@ CreateSchema CreateSchema(): )? ] + ( + LOOKAHEAD({ isMySqlDatabaseOptionAhead() }) + databaseOption=MySqlDatabaseOption() { schema.addDatabaseOptions(databaseOption); } + )* + [ (tk= | tk=) { schema.setAuthorization(tk.image); } ] @@ -10623,6 +14357,38 @@ CreateSchema CreateSchema(): } } +DatabaseOption MySqlDatabaseOption(): +{ + boolean useDefault = false; + boolean useEquals = false; + DatabaseOption.Kind kind = DatabaseOption.Kind.OTHER; + String name = null; + String value = null; + Token valueToken = null; +} +{ + [ { useDefault = true; } ] + ( + { + kind = DatabaseOption.Kind.CHARACTER_SET; + name = "CHARACTER SET"; + } + | + { + kind = DatabaseOption.Kind.COLLATE; + name = "COLLATE"; + } + | + { + kind = DatabaseOption.Kind.ENCRYPTION; + name = "ENCRYPTION"; + } + ) + [ "=" { useEquals = true; } ] + ( value=RelObjectName() | valueToken= { value = valueToken.image; } ) + { return new DatabaseOption(kind, name, value, useDefault, useEquals); } +} + List PathSpecification(): { Token tk; @@ -10636,128 +14402,310 @@ List PathSpecification(): } } -/** - * Parses a single table-level constraint inside CREATE TABLE (...). - * Handles INDEX, PRIMARY KEY, UNIQUE, KEY, FOREIGN KEY, CHECK, EXCLUDE. - * Returns an Index (which may be NamedConstraint, ForeignKeyIndex, CheckConstraint, ExcludeConstraint). - */ -Index CreateTableConstraint(): +CreateDatabase CreateDatabase(): { - Token tk = null; - Token tk2 = null; - Token tk3 = null; - String sk3 = null; - List colNames = null; - List parameter = new ArrayList(); - List idxSpec = new ArrayList(); - Index index = null; - ForeignKeyIndex fkIndex = null; - CheckConstraint checkCs = null; - ExcludeConstraint excludeC = null; - Expression exp = null; + CreateDatabase createDatabase = new CreateDatabase(); + String databaseName = null; + List databaseOptions = null; + DatabaseOption databaseOption = null; } { + + [ LOOKAHEAD(2) { createDatabase.setIfNotExists(true); } ] + databaseName = RelObjectName() { createDatabase.setDatabaseName(databaseName); } ( - LOOKAHEAD(4) ( - { idxSpec.clear(); tk3=null; } - [ tk3= ] - tk= - sk3=RelObjectName() - colNames = IndexColumnsWithParamsList() - ( parameter=CreateParameter() { idxSpec.addAll(parameter); } )* - { - index = new Index().withType((tk3!=null ? tk3.image + " " : "") + tk.image).withName(sk3).withColumns(colNames).withIndexSpec(new ArrayList(idxSpec)); - } - ) + LOOKAHEAD({ isMySqlDatabaseOptionAhead() }) + databaseOption=MySqlDatabaseOption() { createDatabase.addOptions(databaseOption); } + )* + [ databaseOptions = captureRest() { + createDatabase.addOptions(DatabaseOption.raw(databaseOptions)); + } ] + { + return createDatabase; + } +} + +/** Shares optional SQL Server constraint modifiers across CREATE and ALTER. */ +Index.Clustering SqlServerIndexClustering(): +{ + Token token; + Index.Clustering clustering = null; +} +{ + [ LOOKAHEAD({ Dialect.SQLSERVER.name().equals(getAsString(Feature.dialect)) + && (isKeywordAhead("CLUSTERED") || isKeywordAhead("NONCLUSTERED")) }) + token= { + clustering = Index.Clustering.valueOf(token.image.toUpperCase(Locale.ROOT)); + } + ] + { return clustering; } +} + +/** Parses PRIMARY, UNIQUE, plain, FULLTEXT, and SPATIAL indexes for CREATE and ALTER. */ +Index TableIndexSpec(boolean createContext): +{ + Token typeToken = null; + Token keywordToken = null; + String indexName = null; + String using = null; + List columns; + List indexOptions = new ArrayList(); + Index index; + Boolean nullsDistinct = null; + Index.Clustering clustering = null; +} +{ + ( + typeToken= keywordToken= + clustering=SqlServerIndexClustering() + [ LOOKAHEAD({ clustering == null && getToken(1).kind != OPENING_BRACKET }) indexName=RelObjectName() ] + columns=IndexColumnsWithParamsList() + TableIndexOptions(createContext, indexOptions) + { + index = new NamedConstraint() + .withIndexName(indexName) + .withType(typeToken.image + " " + keywordToken.image) + .withColumns(columns) + .withIndexSpec(indexOptions); + } | - LOOKAHEAD(3) ( - { - index = new NamedConstraint(); - tk2=null; - idxSpec.clear(); + typeToken= + [ LOOKAHEAD(2) [ { nullsDistinct = false; } ] { + if (nullsDistinct == null) { nullsDistinct = true; } + } ] + [ LOOKAHEAD(2) (keywordToken= | keywordToken=) ] + clustering=SqlServerIndexClustering() + [ LOOKAHEAD({ clustering == null && getToken(1).kind != OPENING_BRACKET + && getToken(1).kind != K_USING }) indexName=RelObjectName() ] + [ using=UsingIndexType() ] + columns=IndexColumnsWithParamsList() + TableIndexOptions(createContext, indexOptions) + { + if (createContext) { + index = new NamedConstraint() + .withIndexName(indexName) + .withType(typeToken.image + + (keywordToken != null ? " " + keywordToken.image : "")) + .withUsing(using).withColumns(columns).withIndexSpec(indexOptions); + } else { + index = new Index().withType(typeToken.image) + .withIndexKeyword(keywordToken != null ? keywordToken.image : null) + .withName(indexName).withUsing(using).withColumns(columns) + .withIndexSpec(indexOptions); } - [ sk3=RelObjectName() {index.setName(sk3);} ] - ( - tk= tk2= - | - tk= [ tk2= ] - ) - { - index.setType( tk.image + ( tk2!=null ? " " + tk2.image : "" )); - tk2=null; + } + | + typeToken= + [ LOOKAHEAD({ getToken(1).kind != OPENING_BRACKET + && getToken(1).kind != K_USING }) indexName=RelObjectName() ] + [ using=UsingIndexType() ] + columns=IndexColumnsWithParamsList() + TableIndexOptions(createContext, indexOptions) + { + index = new Index().withType(createContext ? typeToken.image : null) + .withIndexKeyword(typeToken.image).withKind(Index.Kind.INDEX) + .withName(indexName).withUsing(using).withColumns(columns) + .withIndexSpec(indexOptions); + } + | + typeToken= + [ LOOKAHEAD({ getToken(1).kind != OPENING_BRACKET + && getToken(1).kind != K_USING }) indexName=RelObjectName() ] + [ using=UsingIndexType() ] + columns=IndexColumnsWithParamsList() + TableIndexOptions(createContext, indexOptions) + { + index = new Index().withType(createContext ? typeToken.image : null) + .withIndexKeyword(typeToken.image).withKind(Index.Kind.INDEX) + .withName(indexName).withUsing(using).withColumns(columns) + .withIndexSpec(indexOptions); + } + | + (typeToken= | typeToken=) + [ LOOKAHEAD(2) (keywordToken= | keywordToken=) ] + [ LOOKAHEAD({ getToken(1).kind != OPENING_BRACKET }) indexName=RelObjectName() ] + columns=IndexColumnsWithParamsList() + TableIndexOptions(createContext, indexOptions) + { + index = new Index().withType(typeToken.image + + (createContext && keywordToken != null + ? " " + keywordToken.image : "")) + .withIndexKeyword(!createContext && keywordToken != null + ? keywordToken.image : null) + .withName(indexName).withColumns(columns).withIndexSpec(indexOptions); + } + ) + { index.setNullsDistinct(nullsDistinct); index.setClustering(clustering); } + PostgreSqlConstraintOptions(index) + { return index; } +} + +void TableIndexOptions(boolean createContext, List options): +{ + List parameter; +} +{ + ( + LOOKAHEAD({ createContext }) + ( LOOKAHEAD(2, { !(getToken(1).kind == K_WITH && getToken(2).kind == OPENING_BRACKET) + && !(getToken(1).kind == K_USING && getToken(2).kind == K_INDEX) + && getToken(1).kind != K_DEFERRABLE && !isKeywordAhead("INITIALLY") + && !(getToken(1).kind == K_NOT && (getToken(2).kind == K_DEFERRABLE + || "VALID".equalsIgnoreCase(getToken(2).image))) }) + parameter=CreateParameter() { options.addAll(parameter); } )* + | + LOOKAHEAD({ !createContext }) IndexOptionList(options) + ) +} + +void PostgreSqlConstraintOptions(Index index): +{ + List columns; + List parameters; + String tableSpace; +} +{ + [ columns=ColumnsNamesList() { index.setIncludeColumns(columns); } ] + [ LOOKAHEAD(2) parameters=PostgreSqlIndexOptions() { index.setStorageParameters(parameters); } ] + [ LOOKAHEAD(3) tableSpace=RelObjectName() { + index.setTableSpace(tableSpace); + } ] +} + +void PostgreSqlConstraintAttributes(Index index): +{ + ConstraintAttributes attributes = new ConstraintAttributes(); + boolean present = false; + boolean deferrable = true; + Token token; +} +{ + [ LOOKAHEAD(2) [ { deferrable = false; } ] { + attributes.setDeferrable(deferrable); present = true; + } ] + [ LOOKAHEAD({ isKeywordAhead("INITIALLY") }) token= token= { + requireDdlSyntax("IMMEDIATE".equalsIgnoreCase(token.image) || "DEFERRED".equalsIgnoreCase(token.image), + "Expected IMMEDIATE or DEFERRED"); + attributes.setInitially(ConstraintAttributes.Initially.valueOf(token.image.toUpperCase(Locale.ROOT))); + present = true; + } ] + [ LOOKAHEAD({ getToken(1).kind == K_NOT && "VALID".equalsIgnoreCase(getToken(2).image) }) + token= { attributes.setNotValid(true); present = true; } ] + { if (present) { index.setConstraintAttributes(attributes); } } +} + +/** + * Parses a single table-level constraint inside {@code CREATE TABLE (...)}. Index forms delegate + * to {@link #TableIndexSpec(boolean)} so CREATE and ALTER expose the same structured AST. + */ +Index CreateTableConstraint(): +{ Index index; } +{ index=TableConstraint(true) { return index; } } + +/** Shared table-constraint body; index option boundaries depend on CREATE versus ALTER. */ +Index TableConstraint(boolean createContext): +{ + String constraintName = null; + Index index = null; + ForeignKeyIndex fkIndex = null; + CheckConstraint checkConstraint = null; +} +{ + ( + LOOKAHEAD({ isTableIndexAhead() }) index=TableIndexSpec(createContext) + | + + [ LOOKAHEAD({ !isTableIndexAhead() && getToken(1).kind != K_FOREIGN + && getToken(1).kind != K_CHECK && getToken(1).kind != K_EXCLUDE }) constraintName=RelObjectName() ] + ( + LOOKAHEAD({ isTableIndexAhead() }) index=TableIndexSpec(createContext) { + if (index instanceof NamedConstraint) { + ((NamedConstraint) index).setUseConstraintKeyword(true); + index.setName(constraintName); + } } - colNames = ColumnNamesWithParamsList() - ( parameter=CreateParameter() { idxSpec.addAll(parameter); } )* - { - index.withColumns(colNames).withIndexSpec(new ArrayList(idxSpec)); + | + fkIndex=ForeignKeySpec(constraintName) { + fkIndex.setUseConstraintKeyword(true); + index = fkIndex; } - ) - | - LOOKAHEAD(3) ( - { - tk=null; - tk3=null; - idxSpec.clear(); - } - [ tk= ] - [ tk3= | tk3= ] tk2= - sk3=RelObjectName() - colNames = IndexColumnsWithParamsList() - ( parameter=CreateParameter() { idxSpec.addAll(parameter); } )* - { - index = new Index() - .withType( ( tk!=null ? tk.image + " " : "") + ( tk3!=null ? tk3.image + " ":"" ) + tk2.image) - .withName(sk3) - .withColumns(colNames) - .withIndexSpec(new ArrayList(idxSpec)); + | + checkConstraint=CheckConstraintSpec(constraintName) { + checkConstraint.setUseConstraintKeyword(true); + index = checkConstraint; } + | + index=PostgreSqlExcludeConstraint() { index.setName(constraintName); } ) | - LOOKAHEAD(3) ( - { sk3=null; } - [ sk3=RelObjectName() ] - fkIndex = ForeignKeySpec(sk3) - { index = fkIndex; } - ) + fkIndex=ForeignKeySpec(null) { index = fkIndex; } | - LOOKAHEAD(3) ( - { sk3 = null; } - [ sk3 = RelObjectName() ] - checkCs = CheckConstraintSpec(sk3) - { index = checkCs; } - ) + checkConstraint=CheckConstraintSpec(null) { index = checkConstraint; } | - LOOKAHEAD(2) ( - tk= { excludeC = new ExcludeConstraint(); } - (tk2= - ("(" exp = Expression() ")")* {excludeC.setExpression(exp);}) - { index = excludeC; } - ) + index=PostgreSqlExcludeConstraint() ) + PostgreSqlConstraintAttributes(index) + { return index; } +} + +ExcludeConstraint PostgreSqlExcludeConstraint(): +{ + ExcludeConstraint constraint = new ExcludeConstraint(); + String using; + List columns = new ArrayList(); + Index.ColumnParams column; + Expression predicate; +} +{ + + [ LOOKAHEAD(2) using=RelObjectName() { constraint.setUsing(using); } ] + [ LOOKAHEAD(2) "(" column=PostgreSqlExcludeElement() { columns.add(column); } + ( "," column=PostgreSqlExcludeElement() { columns.add(column); } )* ")" + { constraint.setColumns(columns); } ] + PostgreSqlConstraintOptions(constraint) + [ "(" predicate=Expression() ")" { constraint.setExpression(predicate); } ] { - return index; + requireDdlSyntax(constraint.getColumns() != null || constraint.getExpression() != null, + "Expected exclusion elements or predicate"); + return constraint; } } +Index.ColumnParams PostgreSqlExcludeElement(): +{ + Index.ColumnParams column; + Token operator; +} +{ + column=IndexColumnWithParams() + ( operator="=" | operator= | operator= | operator="<" | operator=">" + | operator= | operator= | operator="&&" | operator="@>" | operator="<@" ) + { column.setExclusionOperator(operator.image); return column; } +} + CreateTable CreateTable(boolean isUsingOrReplace): { CreateTable createTable = new CreateTable(); Table table = null; - List columnDefinitions = new ArrayList(); + List tableElements = new ArrayList(); + TableElement element; List tableOptions = new ArrayList(); + List typedTableOptions = new ArrayList(); List createOptions = new ArrayList(); Token tk = null; - ColumnDefinition coldef = null; - List indexes = new ArrayList(); - Index index = null; List parameter = new ArrayList(); + TableOption tableOption = null; SpannerInterleaveIn interleaveIn = null; Select select = null; Table likeTable = null; RowMovement rowMovement = null; String tableColumn = null; List columns = new ArrayList(); + TablePartitioning partitioning = null; + Table partitionOfTable = null; + PartitionBound partitionBound = null; + ColDataType ofType = null; } { { createTable.setOrReplace(isUsingOrReplace);} @@ -10771,35 +14719,51 @@ CreateTable CreateTable(boolean isUsingOrReplace): [ LOOKAHEAD(2) { createTable.setIfNotExists(true); }] table=Table() + [ + // BigQuery: CREATE [SNAPSHOT] TABLE target CLONE source [FOR SYSTEM_TIME AS OF ...] + LOOKAHEAD(2) likeTable=Table() + [ LOOKAHEAD(2) tableColumn=TimeTravelAfterAlias() { likeTable.setTimeTravel(tableColumn); } ] + { createTable.setCloneTable(likeTable); likeTable = null; } + ] + [ ofType=ColDataType() { createTable.setOfType(ofType); } ] + [ LOOKAHEAD(2) partitionOfTable=Table() + { createTable.setPartitionOf(partitionOfTable); } ] [ LOOKAHEAD(2) ( LOOKAHEAD(3) ( "(" tableColumn=RelObjectName() { columns.add(tableColumn); } ("," tableColumn=RelObjectName() { columns.add(tableColumn); } )* ")" ) | ( - "(" - coldef = ColumnDefinition() { columnDefinitions.add(coldef); } - - ( - "," - ( - LOOKAHEAD(3) ( - index = CreateTableConstraint() - { indexes.add(index); } - ) - | - ( - coldef = ColumnDefinition() - { columnDefinitions.add(coldef); } - ) - ) + "(" element=CreateTableElement(ofType != null) { tableElements.add(element); } + ( LOOKAHEAD(2) "," element=CreateTableElement(ofType != null) + { tableElements.add(element); } )* + [ LOOKAHEAD({ Dialect.SQLSERVER.name().equals(getAsString(Feature.dialect)) + && getToken(1).kind == K_COMMA && getToken(2).kind == CLOSING_BRACKET }) "," ] ")" ) ) ] - ( LOOKAHEAD(2, { getToken(1).kind != K_AS }) parameter=CreateParameter() { tableOptions.addAll(parameter); } )* + [ LOOKAHEAD({ partitionOfTable != null }) + partitionBound=PostgreSqlPartitionBound() + { createTable.setPartitionBound(partitionBound); } ] + ( LOOKAHEAD(2, { getToken(1).kind != K_AS + && !(getToken(1).kind == K_PARTITION && getToken(2).kind == K_BY) }) + ( + LOOKAHEAD({ isMySqlTableOptionAhead() }) + tableOption=MySqlTableOption() { + typedTableOptions.add(tableOption); + tableOptions.addAll(tableOption.getTokens()); + } + | + parameter=CreateParameter() { + typedTableOptions.add(TableOption.raw(parameter)); + tableOptions.addAll(parameter); + } + ) + )* + [ partitioning=CreateTablePartitioning() { createTable.setPartitioning(partitioning); } ] // see https://docs.oracle.com/cd/B19306_01/server.102/b14200/statements_7002.htm#i2126725 // table properties , all these are optional @@ -10812,20 +14776,125 @@ CreateTable CreateTable(boolean isUsingOrReplace): [ interleaveIn = SpannerInterleaveIn( ) { createTable.setSpannerInterleaveIn(interleaveIn); } ] { createTable.setTable(table); - if (indexes.size() > 0) - createTable.setIndexes(indexes); + if (tableElements.size() > 0) + createTable.setTableElements(tableElements); if (createOptions.size() > 0) createTable.setCreateOptionsStrings(createOptions); - if (tableOptions.size() > 0) - createTable.setTableOptionsStrings(tableOptions); - if (columnDefinitions.size() > 0) - createTable.setColumnDefinitions(columnDefinitions); + if (typedTableOptions.size() > 0) + createTable.setTableOptions(typedTableOptions); if (columns.size() > 0) createTable.setColumns(columns); return createTable; } } +TableElement CreateTableElement(boolean typed): +{ TableElement element; } +{ + ( + LOOKAHEAD() element=LikeClause() + | LOOKAHEAD(3) element=CreateTableConstraint() + | element=CreateTableColumnDefinition(typed) + ) + { return element; } +} + +ColumnDefinition CreateTableColumnDefinition(boolean typed): +{ + ColumnDefinition column = null; + String name; + List options = new ArrayList(); + ColumnOption option; + Token token; +} +{ + { if (!typed) { return ColumnDefinition(); } } + name=RelObjectName() + { column = new ColumnDefinition().withColumnName(name); } + [ LOOKAHEAD(2) token= { + if (!"OPTIONS".equalsIgnoreCase(token.image)) { + throw new ParseException("Expected OPTIONS after WITH"); + } + column.setWithOptions(true); + } ] + ( LOOKAHEAD(2) option=ColumnDefinitionOption() { options.add(option); } )* + { column.setColumnOptions(options); } + { if (true) { return column; } } +} + +TableOption MySqlTableOption(): { + Token tk; + Token tk2 = null; + String value; + boolean useEquals = false; + String name = ""; + TableOption.Kind kind = null; + TableOption option; +} { + ( + tk= { + name = tk.image; + kind = TableOption.Kind.ENGINE; + } + [ "=" { useEquals = true; } ] + value=MySqlTableOptionValue() + | + [ tk= { name = tk.image + " "; } ] + ( + tk= tk2= { + name += tk.image + " " + tk2.image; + } + | + tk= tk2= { + name += tk.image + " " + tk2.image; + } + | + tk= { name += tk.image; } + | + tk= { + name += tk.image; + kind = TableOption.Kind.COLLATE; + } + ) + { if (kind == null) { kind = TableOption.Kind.CHARACTER_SET; } } + [ "=" { useEquals = true; } ] + value=MySqlTableOptionValue() + | + tk= { + name = tk.image; + kind = TableOption.Kind.COMMENT; + } + [ "=" { useEquals = true; } ] + value=MySqlTableOptionValue() + | + tk= { + name = tk.image; + kind = TableOption.Kind.AUTO_INCREMENT; + } + [ "=" { useEquals = true; } ] + value=MySqlTableOptionValue() + ) + { + option = new TableOption(kind, name, value, useEquals); + return option; + } +} + +String MySqlTableOptionValue(): { + Token token; + String value = null; +} { + ( + value=RelObjectName() + | token= { value = token.image; } + | token= { value = token.image; } + | token= { value = token.image; } + | token= { value = token.image; } + | token= { value = token.image; } + ) + { return value; } +} + SpannerInterleaveIn SpannerInterleaveIn(): { Table table = null; @@ -10858,6 +14927,7 @@ ColDataType DataType(): List array = new ArrayList(); List name; ColDataType arrayType; + Integer zonePrecision = null; int precision = -1; int scale = -1; @@ -10878,13 +14948,20 @@ ColDataType DataType(): ( ( tk= | tk= | tk = | tk = | tk = | tk= | tk= | tk= | tk= | tk= - | tk= | tk= | tk= ) { type = tk.image; } - [ - // MySQL seems to allow: INT UNSIGNED - LOOKAHEAD(2) ( LOOKAHEAD(2) ( tk = | tk = | tk = - | tk= | tk= | tk= | tk= | tk= - | tk= | tk= | tk= ) { type += " " + tk.image; } )+ - ] + | tk= | tk= | tk= ) + { + type = tk.image; + // A DT_ZONE image already contains its parameter, e.g. "TIMESTAMP(3) WITH TIME ZONE". + zonePrecision = tk.kind == DT_ZONE ? zonedTypePrecision(tk.image) : null; + } + ( + // Signedness is parsed by ColDataType after optional precision/scale. Do not + // consume CHARACTER when it starts the trailing CHARACTER SET clause. + LOOKAHEAD(2, { getToken(1).kind != K_CHARACTER || getToken(2).kind != K_SET }) + ( tk = + | tk= | tk= | tk= | tk= | tk= + | tk= | tk= | tk= ) { type += " " + tk.image; } + )* [ LOOKAHEAD(2) "(" ( tk= { precision = Integer.valueOf(tk.image); } | tk= { precision = Integer.MAX_VALUE; } ) [ | ] @@ -10893,6 +14970,9 @@ ColDataType DataType(): ] { colDataType = new ColDataType(type, precision, scale); + if (zonePrecision != null) { + colDataType.setPrecision(zonePrecision); + } } ) ) @@ -10902,6 +14982,22 @@ ColDataType DataType(): } } +/** Shared SQL large-object names; length and array suffixes stay in ColDataType. */ +String LargeObjectTypeName(): +{ + Token national = null; + Token type; + Token large; + Token object; +} +{ + [ LOOKAHEAD() national= ] + ( type= | type= | type= | type= ) + large= object= + { return (national == null ? "" : national.image + " ") + + type.image + " " + large.image + " " + object.image; } +} + ColDataType ColDataType(): { ColDataType colDataType = new ColDataType(); @@ -10916,12 +15012,26 @@ ColDataType ColDataType(): ColDataType arrayType; ColDataType nestedType = null; IntervalQualifier intervalQualifier = null; + Integer zonePrecision = null; int precision = -1; int scale = -1; + Token national = null; + Token nationalType = null; + Token varying = null; + ColDataType.TypeModifier typeModifier = null; + XmlTypeModifier xmlTypeModifier = null; } { ( + LOOKAHEAD({ isLargeObjectTypeAhead() }) type=LargeObjectTypeName() + { colDataType.setDataType(type); } + | + LOOKAHEAD({ isXmlTypeName(getToken(1).image) && "(".equals(getToken(2).image) }) + ( tk= | tk= ) + { colDataType.setDataType(tk.image); } + xmlTypeModifier = XmlTypeModifier() { colDataType.setXmlTypeModifier(xmlTypeModifier); } + | ( "(" @@ -10935,8 +15045,35 @@ ColDataType ColDataType(): ")" { colDataType = new ColDataType("STRUCT"); } ) | + LOOKAHEAD({ isKeywordAhead("NATIONAL") }) ( + national= + (nationalType= | nationalType= | nationalType=) + [ LOOKAHEAD({ isKeywordAhead("VARYING") }) varying= ] + { + type = national.image + " " + nationalType.image + + (varying != null ? " " + varying.image : ""); + colDataType.setDataType(type); + colDataType.setNationalCharacterType( + nationalType.image.equalsIgnoreCase("VARCHAR") || varying != null + ? ColDataType.NationalCharacterType.VARCHAR + : ColDataType.NationalCharacterType.CHAR); + } + ) + | LOOKAHEAD(2) ( colDataType = DataType() + { + if (isMySqlDialect() + && colDataType.getDataType().toUpperCase(Locale.ROOT).startsWith("NCHAR")) { + colDataType.setNationalCharacterType( + colDataType.getDataType().toUpperCase(Locale.ROOT).contains("VARCHAR") + ? ColDataType.NationalCharacterType.VARCHAR + : ColDataType.NationalCharacterType.CHAR); + } else if (isMySqlDialect() && colDataType.getDataType().toUpperCase(Locale.ROOT) + .startsWith("NVARCHAR")) { + colDataType.setNationalCharacterType(ColDataType.NationalCharacterType.VARCHAR); + } + } ) | ( @@ -10955,7 +15092,12 @@ ColDataType ColDataType(): | tk= | tk= | tk= - ) { schema = tk.image; } + ) + { + schema = tk.image; + // A DT_ZONE image already contains its parameter, e.g. "TIMESTAMP(3) WITH TIME ZONE". + zonePrecision = tk.kind == DT_ZONE ? zonedTypePrecision(tk.image) : null; + } // Consume an optional INTERVAL qualifier such as `hour to minute` or // `day(9) to second`. Only applicable when the matched type is an INTERVAL and @@ -10985,6 +15127,9 @@ ColDataType ColDataType(): | tk= | + // DuckDB parameterises types with other types: MAP(VARCHAR, INTEGER) + tk= + | tk= ) ) @@ -11001,28 +15146,102 @@ ColDataType ColDataType(): )* ")" ] + ( LOOKAHEAD(1) typeModifier=MySqlTypeModifier() + { colDataType.addTypeModifier(typeModifier); } )* [ LOOKAHEAD(2) ( LOOKAHEAD(2) "[" {tk=null;} [ tk= ] { array.add(tk!=null?Integer.valueOf(tk.image):null); } "]" )+ { colDataType.setArrayData(array); } ] [ LOOKAHEAD(2) (tk= | tk=) { colDataType.setCharacterSet(tk.image); } ] { - if (argumentsStringList.size() > 0) + requireDdlSyntax(colDataType.getXmlTypeModifier() == null || argumentsStringList.isEmpty(), + "Typed XML cannot have additional type arguments"); + if (argumentsStringList.size() > 0) { colDataType.setArgumentsStringList(argumentsStringList); + // Digits-only arguments are the type's numeric parameters, e.g. mediumint(9). + if (argumentsStringList.size() == 1 && argumentsStringList.get(0).matches("\\d+")) { + colDataType.setPrecision(Integer.valueOf(argumentsStringList.get(0))); + } else if (argumentsStringList.size() == 2 && argumentsStringList.get(0).matches("\\d+") + && argumentsStringList.get(1).matches("\\d+")) { + colDataType.setPrecision(Integer.valueOf(argumentsStringList.get(0))); + colDataType.setScale(Integer.valueOf(argumentsStringList.get(1))); + } + } + if (zonePrecision != null) { + colDataType.setPrecision(zonePrecision); + } return colDataType; } } +XmlTypeModifier XmlTypeModifier(): +{ + XmlTypeModifier.Kind kind = null; + List names = new ArrayList(); + String name; + Token token; +} +{ + "(" + [ LOOKAHEAD({ (isKeywordAhead("CONTENT") || isKeywordAhead("DOCUMENT")) + && !".".equals(getToken(2).image) && !")".equals(getToken(2).image) }) + token= { kind = XmlTypeModifier.Kind.valueOf(token.image.toUpperCase(Locale.ROOT)); } + ] + name=RelObjectName() { names.add(name); } + [ "." name=RelObjectNameExt() { names.add(name); } ] + ")" + { return new XmlTypeModifier(kind, names); } +} + +ColDataType.TypeModifier MySqlTypeModifier(): +{ + ColDataType.TypeModifier modifier; +} +{ + ( + { modifier = ColDataType.TypeModifier.SIGNED; } + | { modifier = ColDataType.TypeModifier.UNSIGNED; } + | { modifier = ColDataType.TypeModifier.ZEROFILL; } + ) + { return modifier; } +} + Analyze Analyze(): { Analyze analyze = new Analyze(); Table table = null; + Column column = null; + Token modifier = null; + Token histogramAction = null; + Token bucketCount = null; } { - table=Table() + [ + LOOKAHEAD(2) + (modifier= | modifier=) + { analyze.setModifier(Analyze.Modifier.valueOf(modifier.image.toUpperCase(Locale.ROOT))); } + ] + [ LOOKAHEAD(2) { analyze.setTableKeyword(true); } ] + table=Table() { analyze.addTables(table); } + ( "," table=Table() { analyze.addTables(table); } )* + [ + LOOKAHEAD(2) + (histogramAction= | histogramAction=) + { + analyze.setHistogramAction(Analyze.HistogramAction.valueOf( + histogramAction.image.toUpperCase(Locale.ROOT))); + } + + column=Column() { analyze.addHistogramColumns(column); } + ( "," column=Column() { analyze.addHistogramColumns(column); } )* + [ + LOOKAHEAD(3) + bucketCount= + { analyze.setHistogramBucketCount(Integer.valueOf(bucketCount.image)); } + ] + ] { - analyze.setTable(table); - return analyze; + return analyze; } } @@ -11050,6 +15269,11 @@ CreateView CreateView(boolean isUsingOrReplace): ExpressionList columnNames = null; Token tk = null; List commentTokens = null; + String name = null; + List storageParameters = null; + List viewOptions = new ArrayList(); + ViewOption viewOption = null; + boolean withData = true; } { { createView.setOrReplace(isUsingOrReplace);} @@ -11063,16 +15287,63 @@ CreateView CreateView(boolean isUsingOrReplace): | { createView.setTemporary(TemporaryOption.TEMPORARY); } | { createView.setTemporary(TemporaryOption.VOLATILE); } ] + [ { createView.setRecursive(true); } ] [ { createView.setMaterialized(true);} ] - view=Table() { createView.setView(view); } + + [ LOOKAHEAD(3) { createView.setIfNotExists(true); } ] + view=Table() { createView.setView(view); } + [ LOOKAHEAD({ isKeywordAhead("BACKUP") }) tk= + (tk= | tk=) { createView.setBackup(tk.kind == K_YES); } ] [LOOKAHEAD(3) (tk= | tk=) { createView.setAutoRefresh(AutoRefreshOption.from(tk.image)); } ] - [LOOKAHEAD(3) {createView.setIfNotExists(true);}] + [LOOKAHEAD(3) { + createView.setIfNotExists(true); + createView.setIfNotExistsAfterViewName(true); + }] [ columnNames=ColumnWithCommentList( ) { createView.setColumnNames(columnNames); } ] [ commentTokens=CreateViewTailComment( ) { createView.setViewCommentOptions(commentTokens); } ] + [ name=RelObjectName() { createView.setAccessMethod(name); } ] + [ + ( + LOOKAHEAD({ createView.isMaterialized() }) + storageParameters=PostgreSqlIndexOptions() { createView.setStorageParameters(storageParameters); } + | + "(" viewOption=PostgreSqlViewOption() { viewOptions.add(viewOption); } + ( "," viewOption=PostgreSqlViewOption() { viewOptions.add(viewOption); } )* ")" + { createView.setOptions(viewOptions); } + ) + ] + [ name=RelObjectName() { createView.setTableSpace(name); } ] select=Select( ) { createView.setSelect(select); } - [ LOOKAHEAD(2) { createView.setWithReadOnly(true); } ] - { return createView; } + [ LOOKAHEAD(2) + ( + { createView.setWithReadOnly(true); } + | + [ { createView.setCheckOption(CreateView.CheckOption.LOCAL); } + | LOOKAHEAD({ isKeywordAhead("CASCADED") }) tk= + { createView.setCheckOption(CreateView.CheckOption.CASCADED); } ] + { + if (createView.getCheckOption() == null) { + createView.setCheckOption(CreateView.CheckOption.DEFAULT); + } + } + | + [ { withData = false; } ] { createView.setWithData(withData); } + ) + ] + { createView.validateOptions(); return createView; } +} + +ViewOption PostgreSqlViewOption(): +{ + String name; + Expression value = null; + boolean useEquals = false; +} +{ + name=RelObjectName() + [ "=" { useEquals = true; } value=PrimaryExpression() ] + { return new ViewOption(name, value, useEquals); } } List CreateViewTailComment(): @@ -11121,7 +15392,7 @@ ReferentialAction.Action Action(): * Parses optional referential actions: [ON DELETE|UPDATE action] [ON DELETE|UPDATE action] * Shared between CREATE TABLE FK and ALTER TABLE FK definitions. */ -void ReferentialActionsOnIndex(ForeignKeyIndex fkIndex): +void ReferentialActions(ForeignKeyReference reference): { Token tk; ReferentialAction.Action action = null; @@ -11130,63 +15401,99 @@ void ReferentialActionsOnIndex(ForeignKeyIndex fkIndex): [ LOOKAHEAD(2) ( ( tk= | tk= ) action = Action() - { fkIndex.setReferentialAction(ReferentialAction.Type.from(tk.image), action); } + { reference.setReferentialAction(ReferentialAction.Type.from(tk.image), action); } )] [ LOOKAHEAD(2) ( ( tk= | tk= ) action = Action() - { fkIndex.setReferentialAction(ReferentialAction.Type.from(tk.image), action); } + { reference.setReferentialAction(ReferentialAction.Type.from(tk.image), action); } )] } -/** - * Parses: CHECK ( expression ) - * Returns a CheckConstraint. Shared between CREATE TABLE and ALTER TABLE. - */ -CheckConstraint CheckConstraintSpec(String constraintName): +ForeignKeyReference ForeignKeyReferenceSpec(): { - Expression exp = null; + ForeignKeyReference reference = new ForeignKeyReference(); + ForeignKeyReference.MatchType matchType; + Token matchToken; + List refColNames = null; + Table fkTable; } { - ( LOOKAHEAD(2) "(" exp = Expression() ")" )* + fkTable=Table() [ LOOKAHEAD(2) refColNames=ColumnsNamesList() ] { - return new CheckConstraint().withName(constraintName).withExpression(exp); + reference.setTable(fkTable); + reference.setReferencedColumnNames(refColNames); } -} - -/** - * Parses: FOREIGN KEY columns REFERENCES table [columns] [referential actions] + [ + + ( + { matchType = ForeignKeyReference.MatchType.FULL; } + | matchToken= { + matchType = ForeignKeyReference.MatchType.valueOf( + matchToken.image.toUpperCase(Locale.ROOT)); + } + ) + { reference.setMatchType(matchType); } + ] + ReferentialActions(reference) + { + return reference; + } +} + +/** + * Parses: CHECK ( expression ) + * Returns a CheckConstraint. Shared between CREATE TABLE and ALTER TABLE. + */ +CheckConstraint CheckConstraintSpec(String constraintName): +{ + Expression exp = null; + Boolean enforced = null; + CheckConstraint checkConstraint; +} +{ + ( LOOKAHEAD(2) "(" exp = Expression() ")" )* + [ LOOKAHEAD(2) + [ { enforced = false; } ] + { if (enforced == null) { enforced = true; } } + ] + { + checkConstraint = new CheckConstraint().withName(constraintName).withExpression(exp) + .withEnforced(enforced); + checkConstraint.setKind(Index.Kind.CHECK); + return checkConstraint; + } +} + +/** + * Parses: FOREIGN KEY columns REFERENCES table [columns] [referential actions] * Returns a populated ForeignKeyIndex. Shared between CREATE TABLE and ALTER TABLE. */ ForeignKeyIndex ForeignKeySpec(String constraintName): { ForeignKeyIndex fkIndex = new ForeignKeyIndex(); + String indexName = null; Token tk; Token tk2; - List refColNames = null; List colNames; - Table fkTable; + ForeignKeyReference reference; } { tk= tk2= + [ LOOKAHEAD(2) indexName=RelObjectName() { fkIndex.setIndexName(indexName); } ] colNames = ColumnNamesWithParamsList() { if (constraintName != null) { fkIndex.setName(constraintName); } fkIndex.withType(tk.image + " " + tk2.image).withColumns(colNames); } - fkTable=Table() [ LOOKAHEAD(2) refColNames=ColumnsNamesList() ] - { - fkIndex.setTable(fkTable); - fkIndex.setReferencedColumnNames(refColNames); - } - ReferentialActionsOnIndex(fkIndex) + reference=ForeignKeyReferenceSpec() { fkIndex.setReference(reference); } { return fkIndex; } } /** - * Parses USING [INDEX] name clause in ALTER TABLE constraint tails. + * Parses USING [INDEX] [name] [TABLESPACE name] clause in ALTER TABLE constraint tails. */ void AlterExpressionUsingIndex(AlterExpression alterExp): { @@ -11195,7 +15502,14 @@ void AlterExpressionUsingIndex(AlterExpression alterExp): { { alterExp.addParameters("USING"); } [ LOOKAHEAD(2) { alterExp.addParameters("INDEX"); } ] - sk4=RelObjectName() { alterExp.addParameters(sk4); } + ( + LOOKAHEAD(2, ) sk4=RelObjectName() + { alterExp.addParameters("TABLESPACE"); alterExp.addParameters(sk4); } + | + sk4=RelObjectName() { alterExp.addParameters(sk4); } + [ LOOKAHEAD(2) sk4=RelObjectName() + { alterExp.addParameters("TABLESPACE"); alterExp.addParameters(sk4); } ] + ) } /** @@ -11248,7 +15562,7 @@ List CreateParameter(): | ( //@todo: implement a proper identifier - (tk= | tk= | tk=) + (tk= | tk= | tk= | tk=) { retval+=tk.image; } [ @@ -11274,6 +15588,14 @@ List CreateParameter(): | tk= | tk= | tk= | tk= | tk= | tk = | tk = | tk= | tk= | tk= | tk= | tk= | tk= | tk= + // MySQL index_option / algorithm_option / lock_option keywords, e.g. the trailing + // "KEY_BLOCK_SIZE = 8 ALGORITHM = INPLACE LOCK = NONE" of CREATE INDEX, the + // "WITH PARSER" option, and the FULLTEXT / SPATIAL index types of CREATE INDEX. + | tk= | tk= | tk= | tk= + | tk= | tk= | tk= + | tk= | tk= | tk= | tk= + | tk= | tk= | tk= + | tk= | tk= | tk="=" ) { param.add(tk.image); } @@ -11411,13 +15733,35 @@ List FuncArgsList(): } } +/** DROP names are lexer-separated identifiers, not strings to split again on dots. */ +Table DropObjectName() #TableName: +{ + ObjectNames names; + Token literal; + Table table; + String timeTravel = null; +} +{ + ( + names=RelObjectNames() [ LOOKAHEAD(2) timeTravel=TimeTravelBeforeAlias() ] + { table = names.getNames().size() == 1 ? new Table(names.getNames().get(0), false) + : new Table(names.getNames()); table.setTimeTravel(timeTravel); } + | + literal= { table = new Table(literal.image, false); } + ) + { linkAST(table, jjtThis); return table; } +} + Drop Drop(): { Drop drop = new Drop(); Token tk = null; Table name; + Table indexTable = null; + int indexTablePosition = 0; List dropArgs = new ArrayList(); List funcArgs = null; + List indexOption; boolean useTemporary = false; } { @@ -11434,31 +15778,61 @@ Drop Drop(): | tk= | + tk= + | + tk= + | tk= | tk= | + tk= + | tk= ) { drop.setType(tk.image); } [ LOOKAHEAD(2) {drop.setIfExists(true);} ] - name = Table() { drop.setName(name); } + name = DropObjectName() { drop.setName(name); } + ( "," name = DropObjectName() { drop.addNames(name); } )* [ LOOKAHEAD(2) funcArgs = FuncArgsList() ] ( + LOOKAHEAD({ getToken(1).kind == K_ALGORITHM + || (getToken(1).kind == K_LOCK && getToken(2).kind != K_TABLE) }) + indexOption=IndexAlgorithmOrLockOption() { dropArgs.addAll(indexOption); } + | ( tk= | tk= | tk= + // MySQL DROP INDEX accepts a trailing algorithm_option / lock_option, + // e.g. "DROP INDEX i ON t ALGORITHM = INPLACE LOCK = NONE". + | tk= | tk= | tk="=" ) { dropArgs.add(tk.image); } | ( - name = Table() { dropArgs.add("ON"); dropArgs.add(name.toString()); } + name = DropObjectName() { + if (drop.getObjectType() == Drop.ObjectType.INDEX) { + requireNoDropIndexOwner(indexTable); + indexTable = name; + indexTablePosition = dropArgs.size(); + } else { + dropArgs.add("ON"); dropArgs.add(name.toString()); + } + } ) + | + // The lock_option of DROP INDEX. LOCK also starts a LOCK TABLE statement, so it is only + // taken as a DROP argument when it cannot be the beginning of the next statement. + LOOKAHEAD({ getToken(1).kind == K_LOCK && getToken(2).kind != K_TABLE }) + tk= { dropArgs.add(tk.image); } )* { if (dropArgs.size() > 0) { drop.setParameters(dropArgs); } + if (indexTable != null) { + drop.setTable(indexTable, indexTablePosition); + } if (drop.getType().equals("FUNCTION")) { drop.getTypeToParameters().put("FUNCTION", funcArgs); } @@ -11506,13 +15880,15 @@ Truncate Truncate(): /** * Parses common column-level changes shared between the COLUMN-prefixed and bare forms: - * DROP DEFAULT, SET DEFAULT, SET VISIBLE/INVISIBLE, and bracketed multi-column definitions. + * DROP DEFAULT, SET DEFAULT, SET NOT NULL, SET VISIBLE/INVISIBLE, and bracketed multi-column + * definitions. */ void AlterExpressionColumnChanges(AlterExpression alterExp): { AlterExpression.ColumnDataType alterExpressionColumnDataType = null; AlterExpression.ColumnDropDefault alterExpressionColumnDropDefault = null; AlterExpression.ColumnSetDefault alterExpressionColumnSetDefault = null; + AlterExpression.ColumnSetNotNull alterExpressionColumnSetNotNull = null; AlterExpression.ColumnSetVisibility alterExpressionColumnSetVisibility = null; } { @@ -11523,6 +15899,9 @@ void AlterExpressionColumnChanges(AlterExpression alterExp): LOOKAHEAD(3) alterExpressionColumnSetDefault = AlterExpressionColumnSetDefault() { alterExp.addColSetDefault(alterExpressionColumnSetDefault); } | + LOOKAHEAD(4) alterExpressionColumnSetNotNull = AlterExpressionColumnSetNotNull() + { alterExp.addColSetNotNull(alterExpressionColumnSetNotNull); } + | LOOKAHEAD(3) alterExpressionColumnSetVisibility = AlterExpressionColumnSetVisibility() { alterExp.addColSetVisibility(alterExpressionColumnSetVisibility); } | @@ -11549,19 +15928,85 @@ AlterExpression.ColumnDataType AlterExpressionColumnDataType(): String columnName = null; boolean withType = false; ColDataType dataType = null; - List columnSpecs = null; - List parameter = null; + List columnSpecs = new ArrayList(); + List columnOptions = new ArrayList(); + ColumnOption option = null; + AlterExpression.ColumnDataType result; + Expression usingExpression = null; + List identityAlterations = null; + IdentityAlteration identityAlteration; } { - columnName = RelObjectName() { columnSpecs = new ArrayList(); } + columnName = RelObjectName() + ( + LOOKAHEAD({ isIdentityAlterationAhead() }) + { identityAlterations = new ArrayList(); } + identityAlteration=ColumnIdentityAlteration() { identityAlterations.add(identityAlteration); } + ( LOOKAHEAD({ isIdentityAlterationAhead() }) + identityAlteration=ColumnIdentityAlteration() { identityAlterations.add(identityAlteration); } )* + | ( LOOKAHEAD(2) { withType = true; } )? ( LOOKAHEAD(2) dataType = ColDataType() )? - ( LOOKAHEAD(2) parameter = CreateParameter() { columnSpecs.addAll(parameter); } )* + ( LOOKAHEAD(2, { !(withType && getToken(1).kind == K_USING) }) option = ColumnDefinitionOption() { + columnOptions.add(option); + columnSpecs.addAll(option.getTokens()); + } )* + [ LOOKAHEAD({ withType && getToken(1).kind == K_USING }) usingExpression=Expression() ] + ) { - return new AlterExpression.ColumnDataType(columnName, withType, dataType, columnSpecs); + result = new AlterExpression.ColumnDataType(columnName, withType, dataType, columnSpecs); + result.setUsingExpression(usingExpression); + result.setIdentityAlterations(identityAlterations); + if (hasStructuredColumnOption(columnOptions)) { + result.setColumnOptions(columnOptions); + } + return result; } } +IdentityAlteration ColumnIdentityAlteration(): +{ + IdentityAlteration alteration; + IdentityDefinition identity; + IdentityDefinition.GenerationMode mode; + Sequence.Parameter parameter; + Long restart = null; + Token token; +} +{ + ( + identity=IdentityDefinition() { + alteration = new IdentityAlteration(IdentityAlteration.Kind.ADD); + alteration.setIdentityDefinition(identity); + } + | + + ( + LOOKAHEAD({ isKeywordAhead("GENERATED") }) token= + ( { mode = IdentityDefinition.GenerationMode.ALWAYS; } + | { mode = IdentityDefinition.GenerationMode.BY_DEFAULT; } ) + { + alteration = new IdentityAlteration(IdentityAlteration.Kind.SET_GENERATED); + alteration.setGenerationMode(mode); + } + | + parameter=SequenceParameter() { + alteration = new IdentityAlteration(IdentityAlteration.Kind.SET_PARAMETER); + alteration.setParameters(Collections.singletonList(parameter)); + } + ) + | + [ LOOKAHEAD(2) restart=SequenceParameterValue() ] { + alteration = new IdentityAlteration(IdentityAlteration.Kind.RESTART); + alteration.setRestartWith(restart); + } + | + { alteration = new IdentityAlteration(IdentityAlteration.Kind.DROP); } + [ LOOKAHEAD(2) { alteration.setIfExists(true); } ] + ) + { return alteration; } +} + AlterExpression.ColumnDropNotNull AlterExpressionColumnDropNotNull(): { String columnName = null; @@ -11580,6 +16025,19 @@ AlterExpression.ColumnDropNotNull AlterExpressionColumnDropNotNull(): } } +AlterExpression.ColumnSetNotNull AlterExpressionColumnSetNotNull(): +{ + String columnName = null; +} +{ + columnName = RelObjectName() + + + { + return new AlterExpression.ColumnSetNotNull(columnName); + } +} + AlterExpression.ColumnDropDefault AlterExpressionColumnDropDefault(): { String columnName = null; @@ -11603,7 +16061,7 @@ AlterExpression.ColumnSetDefault AlterExpressionColumnSetDefault(): { columnName = RelObjectName() defaultValue = Expression() { - return new AlterExpression.ColumnSetDefault(columnName, defaultValue.toString()); + return AlterExpression.ColumnSetDefault.fromExpression(columnName, defaultValue); } } @@ -11730,179 +16188,505 @@ void IndexOption(List list) : ) } -List PartitionDefinitions(): +TablePartitioning CreateTablePartitioning(): { - Token tk; - List partitionDefinitions = new ArrayList(); - PartitionDefinition partitionDef = null; - String partitionName = null; - String partitionOperation = null; - String storageEngine = null; - Expression exp = null; + TablePartitioning partitioning = null; + TablePartitioning subPartitioning = null; + Expression expression = null; + ExpressionList expressionList = null; + ExpressionList columns = null; + List partitionDefinitions = null; + List parameter = null; + List partitionOptions = new ArrayList(); + Token tk = null; } { - "(" + + ( + // explicit lookaheads: the method keywords win over the BigQuery + // bare-expression alternative below, which they share a prefix with + LOOKAHEAD(2) partitioning=HashOrKeyPartitioning() + | + LOOKAHEAD(2) { partitioning = new TablePartitioning(TablePartitioning.Type.RANGE); } ( - - partitionName=RelObjectName() + "(" expressionList=SimpleExpressionList() ")" { + if (expressionList.size() == 1) { + partitioning.setExpression(expressionList.get(0)); + } else { + partitioning.setExpressionList(expressionList); + } + } + | + "(" columns=ColumnList() ")" { - List values = new ArrayList(); + partitioning.setColumnsSyntax(true); + partitioning.setColumns(columns); } - - ( - - ( - "(" exp = Expression() ")"{ - values.add(exp.toString()); - } - | { values.add("MAXVALUE"); } - ) { - partitionOperation = "VALUES LESS THAN"; + ) + | + LOOKAHEAD(2) { partitioning = new TablePartitioning(TablePartitioning.Type.LIST); } + ( + "(" expressionList=SimpleExpressionList() ")" { + if (expressionList.size() == 1) { + partitioning.setExpression(expressionList.get(0)); + } else { + partitioning.setExpressionList(expressionList); } - ) - [ "ENGINE" "=" tk= { storageEngine = tk.image; } ] + } + | + "(" columns=ColumnList() ")" { - partitionDef = new PartitionDefinition(partitionName, partitionOperation, values, storageEngine); - partitionDefinitions.add(partitionDef); + partitioning.setColumnsSyntax(true); + partitioning.setColumns(columns); } - [ "," ] - )* - ")" + ) + | + // BigQuery partitions by a bare expression: PARTITION BY d, PARTITION BY DATE(ts), + // PARTITION BY RANGE_BUCKET(id, GENERATE_ARRAY(0, 100, 10)) + LOOKAHEAD({ getToken(1).kind != K_HASH && getToken(1).kind != K_KEY + && getToken(1).kind != K_LINEAR && getToken(1).kind != K_RANGE + && getToken(1).kind != K_LIST }) + expression=SimpleExpression() + { + partitioning = new TablePartitioning(TablePartitioning.Type.EXPRESSION); + partitioning.setExpression(expression); + } + ) + [ LOOKAHEAD({ getToken(1).kind == K_PARTITIONS }) + tk= { partitioning.setPartitions(Long.valueOf(tk.image)); } ] + [ + LOOKAHEAD({ getToken(1).kind == K_SUBPARTITION }) + subPartitioning=HashOrKeyPartitioning() + [ LOOKAHEAD({ getToken(1).kind == K_SUBPARTITIONS }) + tk= + { subPartitioning.setPartitions(Long.valueOf(tk.image)); } ] + { partitioning.setSubPartitioning(subPartitioning); } + ] + [ LOOKAHEAD(2) partitionDefinitions=PartitionDefinitions() + { partitioning.setPartitionDefinitions(partitionDefinitions); } ] + ( LOOKAHEAD(2, { getToken(1).kind != K_AS }) + parameter=CreateParameter() { partitionOptions.addAll(parameter); } )* { - return partitionDefinitions; + if (!partitionOptions.isEmpty()) { + partitioning.setPartitionOptions(partitionOptions); + } + return partitioning; } } -List PartitionNamesList() : +PartitionBound PostgreSqlPartitionBound(): { - Token tk; - List partitionNames = new ArrayList(); + PartitionBound bound = null; + ExpressionList expressions = null; + Expression modulus = null; + Expression remainder = null; } { ( - tk = { - partitionNames.add(tk.image); - } + { bound = new PartitionBound(PartitionBound.Type.DEFAULT); } | - tk = { - partitionNames.add(tk.image); - } + ( - LOOKAHEAD(2) "," tk = { - partitionNames.add(tk.image); + "(" expressions=SimpleExpressionList() ")" + { + bound = new PartitionBound(PartitionBound.Type.RANGE); + bound.setFromExpressions(expressions); } - )* + "(" expressions=SimpleExpressionList() ")" + { bound.setToExpressions(expressions); } + | + "(" expressions=SimpleExpressionList() ")" + { + bound = new PartitionBound(PartitionBound.Type.LIST); + bound.setInExpressions(expressions); + } + | + "(" modulus=Expression() "," + remainder=Expression() ")" + { + bound = new PartitionBound(PartitionBound.Type.HASH); + bound.setModulus(modulus); + bound.setRemainder(remainder); + } + ) ) - { - return partitionNames; - } + { return bound; } } -/** - * Parses DISCARD/IMPORT (PARTITION names TABLESPACE | TABLESPACE). - * Both keywords share the same structure, differing only in operation enum. - */ -AlterExpression AlterExpressionDiscardOrImport(): +TablePartitioning HashOrKeyPartitioning(): { - AlterExpression alterExp; - List partitions = null; - AlterOperation partOp; - AlterOperation tableOp; + TablePartitioning partitioning = new TablePartitioning(); + ExpressionList expressionList = null; + ExpressionList columns = null; + Token tk = null; + boolean useEquals = false; } { + [ { partitioning.setLinear(true); } ] ( - { partOp = AlterOperation.DISCARD_PARTITION; tableOp = AlterOperation.DISCARD_TABLESPACE; } - | - { partOp = AlterOperation.IMPORT_PARTITION; tableOp = AlterOperation.IMPORT_TABLESPACE; } - ) - ( - - { alterExp = new AlterExpressionPartition(); alterExp.setOperation(partOp); } - partitions = PartitionNamesList() - { alterExp.setPartitions(partitions); } - - { alterExp.setTableOption("TABLESPACE"); } + "(" expressionList=SimpleExpressionList() ")" + { + partitioning.setType(TablePartitioning.Type.HASH); + if (expressionList.size() == 1) { + partitioning.setExpression(expressionList.get(0)); + } else { + partitioning.setExpressionList(expressionList); + } + } | - - { alterExp = new AlterExpressionTableOption(); alterExp.setOperation(tableOp); } + + [ [ "=" { useEquals = true; } ] tk= + { + partitioning.setAlgorithm(Integer.valueOf(tk.image)); + partitioning.setAlgorithmUseEquals(useEquals); + } + ] + "(" [ columns=ColumnList() ] ")" + { + partitioning.setType(TablePartitioning.Type.KEY); + if (columns == null) { + columns = new ExpressionList(); + } + partitioning.setColumns(columns); + } ) - { return alterExp; } + { + return partitioning; + } } - -/** - * Parses ADD/ALTER CONSTRAINT clause within AlterExpression. - * Handles: CONSTRAINT [UNIQUE [KEY|INDEX]] name columns - * CONSTRAINT name (FOREIGN KEY|PRIMARY KEY|UNIQUE|KEY|CHECK|[NOT] ENFORCED) - */ -void AlterExpressionAddConstraint(AlterExpression alterExp): +List PartitionValues(): { - Token tk; - Token tk2 = null; - String sk3 = null; - List columnNames = null; - ForeignKeyIndex fkIndex = null; - Index index = null; - Table fkTable; - List constraints = null; - CheckConstraint checkCs = null; + List values = new ArrayList(); + Expression expression = null; } { - ( - LOOKAHEAD(2) + LOOKAHEAD({ getToken(1).kind == K_MAXVALUE }) + { values.add("MAXVALUE"); } + | expression=Expression() { values.add(expression.toString()); } + ) + ( + "," ( - ( { alterExp.setConstraintType("UNIQUE KEY"); } - | { alterExp.setConstraintType("UNIQUE INDEX"); } - | { alterExp.setConstraintType("UNIQUE"); } ) - sk3=RelObjectName() { - alterExp.setConstraintSymbol(sk3); - index = new Index(); - } - columnNames=ColumnsNamesList() { - index.setColumnsNames(columnNames); - alterExp.setIndex(index); - } + LOOKAHEAD({ getToken(1).kind == K_MAXVALUE }) + { values.add("MAXVALUE"); } + | expression=Expression() { values.add(expression.toString()); } ) - | - sk3=RelObjectName() + )* + { + return values; + } +} + +PartitionDefinition PartitionDefinition(): +{ + PartitionDefinition partitionDefinition = new PartitionDefinition(); + List values = null; + List subPartitionDefinitions = null; + String partitionName = null; +} +{ + partitionName=RelObjectName() + { partitionDefinition.setPartitionName(partitionName); } + [ + ( - ( tk= tk2= - columnNames=ColumnsNamesList() - { - fkIndex = new ForeignKeyIndex() - .withName(sk3) - .withType(tk.image + " " + tk2.image) - .withColumnsNames(columnNames); - columnNames = null; - } - fkTable=Table() [ LOOKAHEAD(2) columnNames=ColumnsNamesList() ] - { - fkIndex.withTable(fkTable).withReferencedColumnNames(columnNames); - alterExp.setIndex(fkIndex); - } - ReferentialActionsOnIndex(fkIndex) - constraints=AlterExpressionConstraintState() { alterExp.setConstraints(constraints); } - ) - | - ( tk= tk2= - columnNames=ColumnsNamesList() + + { + partitionDefinition.setValueOperator( + PartitionDefinition.ValueOperator.LESS_THAN); + } + ( + { - index = new NamedConstraint() - .withName(sk3) - .withType(tk.image + " " + tk2.image) - .withColumnsNames(columnNames); - alterExp.setIndex(index); + partitionDefinition.setMaxValue(true); + partitionDefinition.setValues(Collections.singletonList("MAXVALUE")); } - AlterExpressionConstraintTail(alterExp, index) + | + "(" values=PartitionValues() ")" { partitionDefinition.setValues(values); } ) | - LOOKAHEAD(2) ( - { boolean enforced = true; } - [ tk = { enforced = false; } ] - { - alterExp.setEnforced(enforced); - alterExp.setConstraintType("CONSTRAINT"); + + { + partitionDefinition.setValueOperator(PartitionDefinition.ValueOperator.IN); + } + "(" values=PartitionValues() ")" { partitionDefinition.setValues(values); } + ) + ] + ( PartitionDefinitionOption(partitionDefinition) )* + [ subPartitionDefinitions=SubpartitionDefinitions() + { partitionDefinition.setSubPartitionDefinitions(subPartitionDefinitions); } ] + { + return partitionDefinition; + } +} + +List PartitionDefinitions(): +{ + List partitionDefinitions = new ArrayList(); + PartitionDefinition partitionDefinition = null; +} +{ + "(" partitionDefinition=PartitionDefinition() + { partitionDefinitions.add(partitionDefinition); } + ( "," partitionDefinition=PartitionDefinition() + { partitionDefinitions.add(partitionDefinition); } )* + ")" + { + return partitionDefinitions; + } +} + +PartitionDefinition SubpartitionDefinition(): +{ + PartitionDefinition partitionDefinition = new PartitionDefinition(); + String partitionName = null; +} +{ + partitionName=RelObjectName() + { + partitionDefinition.setPartitionName(partitionName); + partitionDefinition.setSubPartition(true); + } + ( PartitionDefinitionOption(partitionDefinition) )* + { + return partitionDefinition; + } +} + +List SubpartitionDefinitions(): +{ + List partitionDefinitions = new ArrayList(); + PartitionDefinition partitionDefinition = null; +} +{ + "(" partitionDefinition=SubpartitionDefinition() + { partitionDefinitions.add(partitionDefinition); } + ( "," partitionDefinition=SubpartitionDefinition() + { partitionDefinitions.add(partitionDefinition); } )* + ")" + { + return partitionDefinitions; + } +} + +void PartitionDefinitionOption(PartitionDefinition partitionDefinition): +{ + Token tk = null; + String value = null; + boolean useEquals = false; +} +{ + ( + [ { partitionDefinition.setStorageKeyword(true); } ] + [ "=" { useEquals = true; } ] value=RelObjectName() + { + partitionDefinition.setStorageEngine(value); + partitionDefinition.setStorageEngineUseEquals(useEquals); + } + | + [ "=" { useEquals = true; } ] tk= + { + partitionDefinition.setComment(tk.image); + partitionDefinition.setCommentUseEquals(useEquals); + } + | + [ "=" { useEquals = true; } ] tk= + { + partitionDefinition.setDataDirectory(tk.image); + partitionDefinition.setDataDirectoryUseEquals(useEquals); + } + | + [ "=" { useEquals = true; } ] tk= + { + partitionDefinition.setIndexDirectory(tk.image); + partitionDefinition.setIndexDirectoryUseEquals(useEquals); + } + | + [ "=" { useEquals = true; } ] tk= + { + partitionDefinition.setMaxRows(tk.image); + partitionDefinition.setMaxRowsUseEquals(useEquals); + } + | + [ "=" { useEquals = true; } ] tk= + { + partitionDefinition.setMinRows(tk.image); + partitionDefinition.setMinRowsUseEquals(useEquals); + } + | + [ "=" { useEquals = true; } ] value=RelObjectName() + { + partitionDefinition.setTablespace(value); + partitionDefinition.setTablespaceUseEquals(useEquals); + } + ) +} + +List PartitionNamesList() : +{ + String partitionName = null; + List partitionNames = new ArrayList(); +} +{ + partitionName = RelObjectName() { + partitionNames.add(partitionName); + } + ( + LOOKAHEAD(2) "," partitionName = RelObjectName() { + partitionNames.add(partitionName); + } + )* + { + return partitionNames; + } +} + +/** + * Parses DISCARD/IMPORT (PARTITION names TABLESPACE | TABLESPACE). + * Both keywords share the same structure, differing only in operation enum. + */ +AlterExpression AlterExpressionDiscardOrImport(): +{ + AlterExpression alterExp; + List partitions = null; + AlterOperation partOp; + AlterOperation tableOp; +} +{ + ( + { partOp = AlterOperation.DISCARD_PARTITION; tableOp = AlterOperation.DISCARD_TABLESPACE; } + | + { partOp = AlterOperation.IMPORT_PARTITION; tableOp = AlterOperation.IMPORT_TABLESPACE; } + ) + ( + + { alterExp = new AlterExpressionPartition(); alterExp.setOperation(partOp); } + partitions = PartitionNamesList() + { alterExp.setPartitions(partitions); } + + { alterExp.setTableOption("TABLESPACE"); } + | + + { alterExp = new AlterExpressionTableOption(); alterExp.setOperation(tableOp); } + ) + { return alterExp; } +} + + +/** + * Parses SQL Server's [CONSTRAINT name] DEFAULT expression FOR column [WITH VALUES]. + */ +DefaultConstraint DefaultConstraintSpec(): +{ + DefaultConstraint constraint = new DefaultConstraint(); + String name; + Expression expression; +} +{ + [ name=RelObjectName() { constraint.setName(name); } ] + expression=Expression() { constraint.setExpression(expression); } + name=RelObjectName() { constraint.setColumn(new Column(name)); } + // See InsertBulk(): is in the FOLLOW set of every statement tail. WITH VALUES wins + // over a hypothetical CTE named VALUES, which is the useful reading here. + [ LOOKAHEAD(2) { constraint.setWithValues(true); } ] + { return constraint; } +} + +/** Parses Informix's ADD CONSTRAINT definition [CONSTRAINT name]. */ +NamedConstraint InformixConstraint(): +{ + NamedConstraint constraint; + Token type; + List columns; + String name; +} +{ + + ( + ( type= | type= ) + columns=ColumnsNamesList() { + constraint = new NamedConstraint() + .withType(type.kind == K_PRIMARY ? "PRIMARY KEY" : type.image) + .withColumnsNames(columns); + } + | + constraint=ForeignKeySpec(null) + | + constraint=CheckConstraintSpec(null) + ) + [ name=RelObjectName() { constraint.setName(name); } ] + { + constraint.setUseConstraintKeyword(true); + constraint.setConstraintNamePosition(NamedConstraint.ConstraintNamePosition.AFTER); + return constraint; + } +} + +/** + * Parses ADD/ALTER CONSTRAINT clause within AlterExpression. + * Handles: CONSTRAINT [UNIQUE [KEY|INDEX]] name columns + * CONSTRAINT name (FOREIGN KEY|PRIMARY KEY|UNIQUE|KEY|CHECK|[NOT] ENFORCED) + */ +void AlterExpressionAddConstraint(AlterExpression alterExp): +{ + Token tk; + Token tk2 = null; + String sk3 = null; + List columnNames = null; + ForeignKeyIndex fkIndex = null; + Index index = null; + Table fkTable; + List constraints = null; + CheckConstraint checkCs = null; + Index.Clustering clustering = null; +} +{ + + ( + LOOKAHEAD(2) + ( + ( { alterExp.setConstraintType("UNIQUE KEY"); } + | { alterExp.setConstraintType("UNIQUE INDEX"); } + | { alterExp.setConstraintType("UNIQUE"); } ) + sk3=RelObjectName() { + alterExp.setConstraintSymbol(sk3); + index = new Index(); + } + columnNames=ColumnsNamesList() { + index.setColumnsNames(columnNames); + alterExp.setIndex(index); + } + ) + | + sk3=RelObjectName() + ( + ( fkIndex=ForeignKeySpec(sk3) + { alterExp.setIndex(fkIndex); } + constraints=AlterExpressionConstraintState() { alterExp.setConstraints(constraints); } + ) + | + ( tk= tk2= + clustering=SqlServerIndexClustering() + columnNames=ColumnsNamesList() + { + index = new NamedConstraint() + .withName(sk3) + .withType(tk.image + " " + tk2.image) + .withClustering(clustering) + .withColumnsNames(columnNames); + alterExp.setIndex(index); + } + AlterExpressionConstraintTail(alterExp, index) + ) + | + LOOKAHEAD(2) ( + { boolean enforced = true; } + [ tk = { enforced = false; } ] + { + alterExp.setEnforced(enforced); + alterExp.setConstraintType("CONSTRAINT"); alterExp.setConstraintSymbol(sk3); } ) @@ -11914,11 +16698,13 @@ void AlterExpressionAddConstraint(AlterExpression alterExp): | ( tk= (tk2= { alterExp.setUk(true); } | tk2=)? + clustering=SqlServerIndexClustering() columnNames=ColumnsNamesList() { index = new NamedConstraint() .withName(sk3) .withType(tk.image + (tk2!=null?" " + tk2.image:"")) + .withClustering(clustering) .withColumnsNames(columnNames); alterExp.setIndex(index); } @@ -11959,6 +16745,7 @@ AlterExpression AlterExpressionDrop(): ( ( { + alterExp = new AlterExpressionPartition(); alterExp.setOperation(AlterOperation.DROP_PARTITION); } partitions=PartitionNamesList() { @@ -12028,12 +16815,12 @@ AlterExpression AlterExpressionDrop(): */ AlterExpression AlterExpressionPartitionOp(): { - AlterExpression alterExp = new AlterExpressionPartition(); + AlterExpressionPartition alterExp = new AlterExpressionPartition(); Token tk; List partitions = null; List partitionDefinition = null; - List columnNames = null; - Expression exp = null; + Table exchangeTable = null; + TablePartitioning partitioning = null; } { ( @@ -12042,7 +16829,7 @@ AlterExpression AlterExpressionPartitionOp(): | LOOKAHEAD(2) tk= { alterExp.setOperation(AlterOperation.COALESCE_PARTITION); - alterExp.setCoalescePartitionNumber(Integer.valueOf(tk.image)); + alterExp.setCoalescePartitionCount(Integer.valueOf(tk.image)); } | LOOKAHEAD(2) @@ -12053,18 +16840,22 @@ AlterExpression AlterExpressionPartitionOp(): } | LOOKAHEAD(2) partitions=PartitionNamesList() - tk= + exchangeTable=Table() [ LOOKAHEAD(2) ( - { alterExp.setExchangePartitionWithValidation(true); } + { + alterExp.setExchangeValidationMode(AlterExpressionPartition.ExchangeValidationMode.WITH); + } | - { alterExp.setExchangePartitionWithoutValidation(false); } + { + alterExp.setExchangeValidationMode(AlterExpressionPartition.ExchangeValidationMode.WITHOUT); + } ) ] { alterExp.setOperation(AlterOperation.EXCHANGE_PARTITION); alterExp.setPartitions(partitions); - alterExp.setExchangePartitionTableName(tk.image); + alterExp.setExchangeTable(exchangeTable); } | LOOKAHEAD(2) { alterExp.setOperation(AlterOperation.ANALYZE_PARTITION); } @@ -12084,14 +16875,10 @@ AlterExpression AlterExpressionPartitionOp(): | LOOKAHEAD(2) { alterExp.setOperation(AlterOperation.REMOVE_PARTITIONING); } | - LOOKAHEAD(2) { alterExp.setOperation(AlterOperation.PARTITION_BY); } - { alterExp.setPartitionType("RANGE"); } - ( - "(" exp=Expression() ")" { alterExp.setPartitionExpression(exp); } - | - columnNames=ColumnsNamesList() { alterExp.setPartitionColumns(columnNames); } - ) - partitionDefinition=PartitionDefinitions() { alterExp.setPartitionDefinitions(partitionDefinition); } + LOOKAHEAD(2) partitioning=CreateTablePartitioning() { + alterExp.setOperation(AlterOperation.PARTITION_BY); + alterExp.setPartitioning(partitioning); + } ) { return alterExp; } } @@ -12127,65 +16914,42 @@ AlterExpression AlterExpressionAddAlterModify(): ) ( - LOOKAHEAD(2) ( columnNames=ColumnsNamesList() { alterExp.setPkColumns(columnNames); }) - - constraints=AlterExpressionConstraintState() { alterExp.setConstraints(constraints); } - [ - AlterExpressionUsingIndex(alterExp) - ] + LOOKAHEAD({ getToken(1).kind == K_DEFAULT || getToken(1).kind == K_CONSTRAINT + && getToken(3).kind == K_DEFAULT }) index=DefaultConstraintSpec() { + requireDdlSyntax(alterExp.getOperation() == AlterOperation.ADD, + "Table-level DEFAULT constraints require ADD"); + alterExp.setIndex(index); + } | - LOOKAHEAD(2) ( - (tk= { alterExp.setUk(true); } | tk=) - ( - LOOKAHEAD(3) - sk3 = RelObjectName() - [ LOOKAHEAD(2) sk4 = UsingIndexType() ] - [ LOOKAHEAD(2) indexColumnNames = IndexColumnsWithParamsList() ] - | - [ LOOKAHEAD(2) sk4 = UsingIndexType() ] - [ LOOKAHEAD(2) indexColumnNames = IndexColumnsWithParamsList() ] - ) - IndexOptionList(indexSpec = new ArrayList()) - { - index = new Index() - .withIndexKeyword(tk.image) - .withName(sk3) - .withUsing(sk4) - .withColumns(indexColumnNames) - .withIndexSpec(indexSpec); - + LOOKAHEAD({ alterExp.getOperation() == AlterOperation.ALTER + && getToken(1).kind == K_INDEX }) + tk= sk3=RelObjectName() IndexOptionList(indexSpec) { + index = new Index().withIndexKeyword(tk.image).withName(sk3) + .withKind(Index.Kind.INDEX).withIndexSpec(indexSpec); alterExp.setIndex(index); - } - constraints=AlterExpressionConstraintState() { alterExp.setConstraints(constraints); } - ) + } | - LOOKAHEAD(4) - ( - ( tk= | tk= ) - [ LOOKAHEAD(2) ( tk2= | tk2= ) ] - ( - sk3 = RelObjectName() - columnNames = ColumnsNamesList() - | - columnNames = ColumnsNamesList() - ) - IndexOptionList(indexSpec = new ArrayList()) - { - String type = tk.image; - String keyword = tk2 != null ? tk2.image : null; - index = new Index() - .withType(type) - .withIndexKeyword(keyword) - .withColumnsNames(columnNames) - .withIndexSpec(indexSpec); - - if (sk3 != null) { - index.setName(sk3); - } - - alterExp.setIndex(index); - } - ) + LOOKAHEAD( ( | + | | ) "(", + { Dialect.INFORMIX.name().equals(getAsString(Feature.dialect)) }) + index=InformixConstraint() { + requireDdlSyntax(alterExp.getOperation() == AlterOperation.ADD, + "Informix constraint definitions require ADD"); + alterExp.setIndex(index); + } + | + LOOKAHEAD({ alterExp.getOperation() == AlterOperation.ADD + && Dialect.POSTGRESQL.name().equals(getAsString(Feature.dialect)) + && (getToken(1).kind == K_CONSTRAINT || getToken(1).kind == K_PRIMARY + || getToken(1).kind == K_UNIQUE || getToken(1).kind == K_FOREIGN + || getToken(1).kind == K_CHECK || getToken(1).kind == K_EXCLUDE) }) + index=TableConstraint(false) { setAlterTableIndex(alterExp, index); } + | + LOOKAHEAD({ isTableIndexAhead() }) index=TableIndexSpec(false) { + setAlterTableIndex(alterExp, index); + } + constraints=AlterExpressionConstraintState() { alterExp.setConstraints(constraints); } + [ AlterExpressionUsingIndex(alterExp) ] | LOOKAHEAD(2) ( sk3=RelObjectName() tk= { alterExp.withColumnName(sk3).withCommentText(tk.image); } @@ -12193,6 +16957,7 @@ AlterExpression AlterExpressionAddAlterModify(): | LOOKAHEAD(3) ( { + alterExp = new AlterExpressionPartition(); alterExp.setOperation(AlterOperation.ADD_PARTITION); } partitionDefinition=PartitionDefinitions() { @@ -12208,58 +16973,33 @@ AlterExpression AlterExpressionAddAlterModify(): { alterExp.hasColumns(true); } ) )? - [ LOOKAHEAD(2) { alterExp.setUseIfNotExists(true); } ] + [ + LOOKAHEAD(2) + ( + { alterExp.setUseIfNotExists(true); } + | + { alterExp.setUsingIfExists(true); } + ) + ] ( LOOKAHEAD(3) AlterExpressionColumnChanges(alterExp) | - LOOKAHEAD(2) alterExpressionColumnDataType = AlterExpressionColumnDataType() - { alterExp.addColDataType(alterExpressionColumnDataType); } - | LOOKAHEAD(3) alterExpressionColumnDropNotNull = AlterExpressionColumnDropNotNull() { alterExp.addColDropNotNull( alterExpressionColumnDropNotNull);} + | + LOOKAHEAD(2) alterExpressionColumnDataType = AlterExpressionColumnDataType() + { alterExp.addColDataType(alterExpressionColumnDataType); } ) ) | LOOKAHEAD(3) AlterExpressionColumnChanges(alterExp) | - ( - - ( - ( - { alterExp.setUk(true); } - | { alterExp.setUk(false); } - ) - [ (tk= | tk=) { alterExp.setUkName(tk.image); } ] - | - (tk= | tk=) { - alterExp.setUkTypeSpecified(false); - alterExp.setUkName(tk.image); - } - )? - columnNames=ColumnsNamesList() { alterExp.setUkColumns(columnNames); } - [ - AlterExpressionUsingIndex(alterExp) - ] - [ LOOKAHEAD(2) index = IndexWithComment(index) { alterExp.setIndex(index); } ] - ) - | // Standalone FK now uses ForeignKeyIndex, same as CONSTRAINT FK ( - { ForeignKeyIndex fkIndex; ReferentialAction ra; } + { ForeignKeyIndex fkIndex; } fkIndex = ForeignKeySpec(null) { - alterExp.setIndex(fkIndex); - // backward compat: populate deprecated FK fields from ForeignKeyIndex - alterExp.setFkColumns(fkIndex.getColumnsNames()); - if (fkIndex.getTable() != null) { - alterExp.setFkSourceSchema(fkIndex.getTable().getSchemaName()); - alterExp.setFkSourceTable(fkIndex.getTable().getName()); - } - alterExp.setFkSourceColumns(fkIndex.getReferencedColumnNames()); - ra = fkIndex.getReferentialAction(ReferentialAction.Type.DELETE); - if (ra != null) { alterExp.setReferentialAction(ra.getType(), ra.getAction()); } - ra = fkIndex.getReferentialAction(ReferentialAction.Type.UPDATE); - if (ra != null) { alterExp.setReferentialAction(ra.getType(), ra.getAction()); } + setAlterTableIndex(alterExp, fkIndex); } ) | @@ -12281,9 +17021,61 @@ AlterExpression AlterExpressionAddAlterModify(): { return alterExp; } } -/** +/** Parses CockroachDB primary-key elements without legacy raw index parameters. */ +Index.ColumnParams CockroachPrimaryKeyColumn(): +{ + Index.ColumnParams column; + String name; + Expression expression; + Token token; +} +{ + ( + LOOKAHEAD({ isFunctionAhead() }) expression=Function() + { column = new Index.ColumnParams(expression).withExpressionParenthesized(false); } + | + name=RelObjectName() { column = new Index.ColumnParams(name); } + | + "(" expression=Expression() ")" { column = new Index.ColumnParams(expression); } + ) + [ (token= | token=) { column.setOperatorClass(token.image); } ] + [ (token= | token=) { + column.setSortOrder(Index.ColumnParams.SortOrder.valueOf(token.image.toUpperCase(Locale.ROOT))); + } ] + [ (token= | token=) { + column.setNullOrdering(Index.ColumnParams.NullOrdering.valueOf(token.image.toUpperCase(Locale.ROOT))); + } ] + { return column; } +} + +AlterExpressionPrimaryKey CockroachAlterPrimaryKey(): +{ + AlterExpressionPrimaryKey action = new AlterExpressionPrimaryKey(); + List columns = new ArrayList(); + Index.ColumnParams column; + List options; + Expression bucketCount; + Token token; +} +{ + + "(" column=CockroachPrimaryKeyColumn() { columns.add(column); } + ( "," column=CockroachPrimaryKeyColumn() { columns.add(column); } )* ")" + { action.getIndex().setColumns(columns); } + [ { action.setUsingHash(true); } + [ LOOKAHEAD( ) token= { + requireDdlSyntax("BUCKET_COUNT".equalsIgnoreCase(token.image), "Expected BUCKET_COUNT"); + } + "=" bucketCount=Expression() { action.setBucketCount(bucketCount); } ] + ] + // LOOKAHEAD(2) as in the identical constructs at CreateIndex() and AlterExpression(). + [ LOOKAHEAD(2) options=PostgreSqlIndexOptions() { action.getIndex().setStorageParameters(options); } ] + { return action; } +} + +/** * Parses all RENAME variants within ALTER TABLE. - * Handles: RENAME [COLUMN] old TO new, RENAME TO tablename, + * Handles: RENAME [COLUMN] old TO new, RENAME [TO|AS] tablename, RENAME tablename, * RENAME INDEX/KEY/CONSTRAINT old TO new. */ AlterExpression AlterExpressionRenameOp(): @@ -12314,10 +17106,15 @@ AlterExpression AlterExpressionRenameOp(): } | LOOKAHEAD(2) ( - { alterExp.setOperation(AlterOperation.RENAME_TABLE); } - (tk2= | tk2=) { alterExp.setNewTableName(tk2.image); } + (tk= | tk=) { + alterExp.setOperation(AlterOperation.RENAME_TABLE); + alterExp.setTableRenameKeyword(AlterExpression.TableRenameKeyword.valueOf( + tk.image.toUpperCase(Locale.ROOT))); + } + tk2=KeywordOrIdentifier() { alterExp.setNewTableName(tk2.image); } ) | + LOOKAHEAD(3) ( { alterExp.setOperation(AlterOperation.RENAME); } [ { alterExp.hasColumn(true); } ] @@ -12325,6 +17122,48 @@ AlterExpression AlterExpressionRenameOp(): (tk2=KeywordOrIdentifier()) { alterExp.setColumnName(tk2.image); } ) + | + ( + { + alterExp.setOperation(AlterOperation.RENAME_TABLE); + alterExp.setTableRenameKeyword(AlterExpression.TableRenameKeyword.NONE); + } + tk2=KeywordOrIdentifier() { alterExp.setNewTableName(tk2.image); } + ) + ) + { return alterExp; } +} + +AlterExpressionPartition AlterExpressionPostgreSqlPartitionOp(): +{ + AlterExpressionPartition alterExp = new AlterExpressionPartition(); + Table partitionTable = null; + PartitionBound partitionBound = null; +} +{ + ( + partitionTable=Table() + partitionBound=PostgreSqlPartitionBound() + { + alterExp.setOperation(AlterOperation.ATTACH_PARTITION); + alterExp.setPartitionTable(partitionTable); + alterExp.setPartitionBound(partitionBound); + } + | + partitionTable=Table() + { + alterExp.setOperation(AlterOperation.DETACH_PARTITION); + alterExp.setPartitionTable(partitionTable); + } + [ + { + alterExp.setDetachMode(AlterExpressionPartition.DetachMode.CONCURRENTLY); + } + | + { + alterExp.setDetachMode(AlterExpressionPartition.DetachMode.FINALIZE); + } + ] ) { return alterExp; } } @@ -12346,6 +17185,10 @@ AlterExpression AlterExpression(): { ( + LOOKAHEAD( , + { Dialect.COCKROACHDB.name().equals(getAsString(Feature.dialect)) }) + alterExp = CockroachAlterPrimaryKey() + | alterExp = AlterExpressionAddAlterModify() | ( @@ -12479,6 +17322,8 @@ AlterExpression AlterExpression(): ) | alterExp = AlterExpressionDiscardOrImport() + | + alterExp = AlterExpressionPostgreSqlPartitionOp() | LOOKAHEAD(4) ( { alterExp = new AlterExpression(); } @@ -12554,6 +17399,23 @@ Statement Alter(): ( ( + LOOKAHEAD({ isKeywordAhead("ROLE") || isKeywordAhead("USER") || isKeywordAhead("GROUP") }) + statement = AlterRole() + | + statement = AlterDefaultPrivileges() + | + LOOKAHEAD() statement = AlterType() + | + LOOKAHEAD() statement = AlterDomain() + | + LOOKAHEAD({ isKeywordAhead("EXTENSION") }) statement = AlterExtension() + | + LOOKAHEAD({ isKeywordAhead("PUBLICATION") }) statement = AlterPublication() + | + LOOKAHEAD({ isKeywordAhead("SUBSCRIPTION") }) statement = AlterSubscription() + | + statement = AlterEvent() + | statement = AlterTable() | statement = AlterSession() @@ -12698,14 +17560,14 @@ AlterSystemStatement AlterSystemStatement(): | ( ( - "DISTRIBUTED" "RECOVERY" { operation = AlterSystemOperation.ENABLE_DISTRIBUTED_RECOVERY; } + ContextualKeyword("DISTRIBUTED") ContextualKeyword("RECOVERY") { operation = AlterSystemOperation.ENABLE_DISTRIBUTED_RECOVERY; } | { operation = AlterSystemOperation.ENABLE_DISTRIBUTED_RECOVERY; } ) ) | ( ( - "DISTRIBUTED" "RECOVERY" { operation = AlterSystemOperation.DISABLE_DISTRIBUTED_RECOVERY; } + ContextualKeyword("DISTRIBUTED") ContextualKeyword("RECOVERY") { operation = AlterSystemOperation.DISABLE_DISTRIBUTED_RECOVERY; } | { operation = AlterSystemOperation.DISABLE_RESTRICTED_SESSION; } ) ) @@ -12803,14 +17665,20 @@ RollbackStatement RollbackStatement(): { { rollbackStatement = new RollbackStatement(); } [ { rollbackStatement.setUsingWorkKeyword(true); } ] - [ ( - [ { rollbackStatement.setUsingSavepointKeyword(true); }] - token= { rollbackStatement.setSavepointName(token.image); } - ) - | + // explicit lookahead: FORCE INSTALL starts a new statement, FORCE 'id' belongs here + [ + LOOKAHEAD(2) ( - token= { rollbackStatement.setForceDistributedTransactionIdentifier(token.image); } - ) ] + ( + [ { rollbackStatement.setUsingSavepointKeyword(true); }] + token= { rollbackStatement.setSavepointName(token.image); } + ) + | + ( + token= { rollbackStatement.setForceDistributedTransactionIdentifier(token.image); } + ) + ) + ] { return rollbackStatement; @@ -12830,6 +17698,7 @@ Commit Commit(): Comment Comment(): { + CommentTarget target; Comment result = new Comment(); Table table; Table view; @@ -12850,44 +17719,425 @@ Comment Comment(): ( view = Table() { result.setView(view); } ) + | + target=CommentTarget() { result.setTarget(target); } + ) + + ( + comment= { result.setComment(new StringValue(comment.image)); } + | ) - comment= { - result.setComment(new StringValue(comment.image)); return result; } } -Grant Grant(): +/** Catalog names are already tokenized into parts; quoted dots are part of one identifier. */ +Table CommentObjectName(): +{ ObjectNames names; } { - Grant grant = new Grant(); - ArrayList privileges = new ArrayList(); - List users; - Token tk = null; - ObjectNames objName; + names=RelObjectNames() + { return names.getNames().size() == 1 ? new Table(names.getNames().get(0), false) + : new Table(names.getNames()); } } + +CommentTarget CommentTarget(): { - - ( - ( - [readGrantTypes(privileges) ( readGrantTypes(privileges))*] - objName=RelObjectNames() { grant.setObjectName(objName.getNames()); } + CommentTarget result = new CommentTarget(); + Table name; + String constraintName; + RoutineReference routine; +} +{ + ( + routine=RoutineReference(false) + { result.setKind(CommentTarget.Kind.FUNCTION); result.setRoutine(routine); } + | + constraintName=RelObjectName() + { result.setKind(CommentTarget.Kind.CONSTRAINT); result.setName(new Table(constraintName, false)); } + + [ LOOKAHEAD({ getToken(1).kind == K_DOMAIN && !".".equals(getToken(2).image) + && getToken(2).kind != K_IS }) { result.setOnDomain(true); } ] + name=CommentObjectName() { result.setRelation(name); } + | + ( + { result.setKind(CommentTarget.Kind.INDEX); } + | { result.setKind(CommentTarget.Kind.SCHEMA); } + | { result.setKind(CommentTarget.Kind.SEQUENCE); } + | { result.setKind(CommentTarget.Kind.MATERIALIZED_VIEW); } + | { result.setKind(CommentTarget.Kind.DOMAIN); } + | { result.setKind(CommentTarget.Kind.TYPE); } ) + name=CommentObjectName() { result.setName(name); } + ) + { return result; } +} + +/** Consumes a keyword that remains an ordinary identifier outside this grammar context. */ +void ContextualKeyword(String expected): +{ Token keyword; } +{ + keyword= { + if (!expected.equalsIgnoreCase(keyword.image)) { + throw new ParseException("Expected " + expected + " but found " + keyword.image); + } + } +} + +void AccessKeyword(String expected): +{ Token token; } +{ (token= | token=) { requireAccessSyntax(expected.equalsIgnoreCase(token.image), "Expected " + expected); } } + +String AccessQualifiedName(): +{ String name; String part; } +{ name=RelObjectName() ( "." part=RelObjectNameExt() { name += "." + part; } )* { return name; } } + +List AccessNames(): +{ List names = new ArrayList(); String name; } +{ name=RelObjectName() { names.add(name); } ( "," name=RelObjectName() { names.add(name); } )* { return names; } } + +RoleOption RoleOption(boolean create): +{ RoleOption option = null; Token token; Token value; String sign = ""; Expression expression; List names; boolean encrypted = false; } +{ + ( + LOOKAHEAD({ isAccessKeywordAhead("CONNECTION") }) + AccessKeyword("CONNECTION") [ ( "+" | "-" { sign = "-"; } ) ] value= + { option = new RoleOption(RoleOption.Kind.CONNECTION_LIMIT); option.setValue(new LongValue(sign + value.image)); } + | + LOOKAHEAD({ isAccessKeywordAhead("PASSWORD") || isAccessKeywordAhead("ENCRYPTED") }) + [ AccessKeyword("ENCRYPTED") { encrypted = true; } ] + ( value= { expression = new StringValue(value.image); } | { expression = new NullValue(); } ) + { option = new RoleOption(RoleOption.Kind.PASSWORD); option.setEncrypted(encrypted); option.setValue(expression); } + | + LOOKAHEAD({ isAccessKeywordAhead("VALID") }) + AccessKeyword("VALID") AccessKeyword("UNTIL") value= + { option = new RoleOption(RoleOption.Kind.VALID_UNTIL); option.setValue(new StringValue(value.image)); } + | + + ( AccessKeyword("ROLE") { option = new RoleOption(RoleOption.Kind.IN_ROLE); } + | { option = new RoleOption(RoleOption.Kind.IN_GROUP); } ) + names=AccessNames() + { requireAccessSyntax(create, "Membership attributes require CREATE ROLE"); option.setRoles(names); } + | + LOOKAHEAD({ isAccessKeywordAhead("ROLE") || isAccessKeywordAhead("USER") || isAccessKeywordAhead("ADMIN") }) + ( token= | token= ) + names=AccessNames() + { requireAccessSyntax(create, "Membership attributes require CREATE ROLE"); option = new RoleOption(accessEnum(RoleOption.Kind.class, token.image)); option.setRoles(names); } + | + LOOKAHEAD({ isAccessKeywordAhead("SYSID") }) + AccessKeyword("SYSID") value= + { requireAccessSyntax(create, "SYSID requires CREATE ROLE"); option = new RoleOption(RoleOption.Kind.SYSID); option.setValue(new LongValue(value.image)); } + | + token= { option = new RoleOption(accessEnum(RoleOption.Kind.class, token.image)); } + ) + { return option; } +} + +CreateRole CreateRole(): +{ CreateRole role = new CreateRole(); String name; RoleOption option; Token token; } +{ + ( token= | token= | token= ) { role.setCommand(accessEnum(CreateRole.Command.class, token.image)); } + name=RelObjectName() { role.setName(name); } + [ LOOKAHEAD(1) { role.setUseWith(true); } ] + ( LOOKAHEAD({ isRoleOptionAhead(1) }) option=RoleOption(true) { role.getOptions().add(option); } )* + { requireAccessSyntax(!role.isUseWith() || !role.getOptions().isEmpty(), "WITH requires role attributes"); return role; } +} + +AlterRole AlterRole(): +{ + AlterRole role = new AlterRole(); String name; RoleOption option; Token token; Expression value; + ExpressionList values = new ExpressionList(); List names; +} +{ + ( token= | token= | token= ) { role.setCommand(accessEnum(CreateRole.Command.class, token.image)); } + ( LOOKAHEAD(1) { role.setName("ALL"); } | name=RelObjectName() { role.setName(name); } ) + [ LOOKAHEAD(2) name=RelObjectName() { role.setDatabase(name); } ] + ( + name=RelObjectName() { role.setAction(AlterRole.Action.RENAME); role.setNewName(name); } | + ( { role.setAction(AlterRole.Action.ADD_USER); } | { role.setAction(AlterRole.Action.DROP_USER); } ) + names=AccessNames() { requireAccessSyntax(role.getCommand() == CreateRole.Command.GROUP, "ADD/DROP USER requires ALTER GROUP"); role.setUsers(names); } + | + name=AccessQualifiedName() { role.setAction(AlterRole.Action.SET); role.setParameter(name); } ( - tk= { grant.setRole(tk.image); } + { role.setFromCurrent(true); } + | + ( | "=" { role.setUseEquals(true); } ) + ( + LOOKAHEAD(1) { role.setUseDefault(true); } + | + value=AccessSettingValue() { values.add(value); } + ( "," value=AccessSettingValue() { values.add(value); } )* + { role.setValues(values); } + ) ) + | + ( LOOKAHEAD(1) { role.setParameter("ALL"); } | name=AccessQualifiedName() { role.setParameter(name); } ) + { role.setAction(AlterRole.Action.RESET); } + | + [ { role.setUseWith(true); } ] + option=RoleOption(false) { role.getOptions().add(option); role.setAction(AlterRole.Action.OPTIONS); } + ( LOOKAHEAD({ isRoleOptionAhead(1) }) option=RoleOption(false) { role.getOptions().add(option); } )* ) - users = UsersList() - { - grant.setUsers(users); - if(privileges.size() > 0) { - grant.setPrivileges(privileges); + { + requireAccessSyntax(role.getDatabase() == null && !"ALL".equals(role.getName()) + || role.getAction() == AlterRole.Action.SET || role.getAction() == AlterRole.Action.RESET, + "ALL and IN DATABASE require SET or RESET"); + requireAccessSyntax(role.getCommand() != CreateRole.Command.GROUP || role.getAction() == AlterRole.Action.RENAME + || role.getAction() == AlterRole.Action.ADD_USER || role.getAction() == AlterRole.Action.DROP_USER, "Invalid ALTER GROUP action"); + return role; + } +} + +Expression AccessSettingValue(): +{ Expression value; Token token; } +{ ( LOOKAHEAD(1) token= { value = new Column(token.image); } | value=PrimaryExpression() ) { return value; } } + +Privilege AccessPrivilege(): +{ Privilege result; Token token; boolean all = false; String column; ExpressionList columns = null; } +{ + ( token= | token= | token= | token= | token= | token= | token= | token= | token= | token= | token= | token= | token= | token= | token= | token= | token= ) + { result = new Privilege(accessEnum(Privilege.Kind.class, token.image)); } + [ { requireAccessSyntax(result.getKind() == Privilege.Kind.ALTER, "SYSTEM requires ALTER"); result.setKind(Privilege.Kind.ALTER_SYSTEM); } ] + [ LOOKAHEAD({ isAccessKeywordAhead("PRIVILEGES") }) + AccessKeyword("PRIVILEGES") { requireAccessSyntax(result.getKind() == Privilege.Kind.ALL, "PRIVILEGES requires ALL"); result.setUsePrivileges(true); } ] + [ "(" { columns = new ExpressionList(); } + column=RelObjectName() { columns.add(new Column(column)); } + ( "," column=RelObjectName() { columns.add(new Column(column)); } )* + ")" { result.setColumns(columns); } ] + { return result; } +} + +PrivilegeTarget.Kind AccessObjectKind(boolean plural): +{ Token token; String name; } +{ + ( + + ( AccessKeyword("WRAPPER") { name = "FOREIGN_DATA_WRAPPER"; } + | AccessKeyword("SERVER") { name = "FOREIGN_SERVER"; } ) + | + LOOKAHEAD({ isAccessKeywordAhead("LARGE") }) + AccessKeyword("LARGE") ( token= ) { name = "LARGE_" + token.image.toUpperCase(Locale.ROOT); } + | + ( token= | token= | token= | token= | token= | token= | token= | token= | token= | token= | token= ) + { name = token.image.toUpperCase(Locale.ROOT); } + ) + { + if (plural) { + requireAccessSyntax(name.endsWith("S"), "Expected plural privilege target"); + name = name.substring(0, name.length() - 1); } - return grant; + return accessEnum(PrivilegeTarget.Kind.class, name); } } +RoutineReference.Argument AccessRoutineArgument(): +{ RoutineReference.Argument argument; } +{ argument=RoutineArgument(false) { return argument; } } + +RoutineReference AccessRoutine(): +{ RoutineReference result = new RoutineReference(); String name; RoutineReference.Argument argument; List arguments; } +{ + name=AccessQualifiedName() { result.setName(name); } + [ LOOKAHEAD(1) "(" { arguments = new ArrayList(); result.setArguments(arguments); } + [ argument=AccessRoutineArgument() { arguments.add(argument); } + ( "," argument=AccessRoutineArgument() { arguments.add(argument); } )* ] ")" ] + { return result; } +} + +PrivilegeTarget AccessTarget(boolean defaults): +{ PrivilegeTarget target = new PrivilegeTarget(); PrivilegeTarget.Kind kind; ObjectNames name; RoutineReference routine; Token number; } +{ + ( + LOOKAHEAD({ defaults }) + kind=AccessObjectKind(true) + { requireAccessSyntax(defaults && EnumSet.of(PrivilegeTarget.Kind.TABLE, PrivilegeTarget.Kind.SEQUENCE, PrivilegeTarget.Kind.FUNCTION, + PrivilegeTarget.Kind.ROUTINE, PrivilegeTarget.Kind.TYPE, PrivilegeTarget.Kind.SCHEMA, PrivilegeTarget.Kind.LARGE_OBJECT).contains(kind), + "Invalid default-privilege target"); + target.setKind(kind); target.setDefaultPrivileges(true); } + | + LOOKAHEAD(1) kind=AccessObjectKind(true) + { requireAccessSyntax(!defaults && EnumSet.of(PrivilegeTarget.Kind.TABLE, PrivilegeTarget.Kind.SEQUENCE, + PrivilegeTarget.Kind.FUNCTION, PrivilegeTarget.Kind.PROCEDURE, PrivilegeTarget.Kind.ROUTINE).contains(kind), + "Invalid ALL IN SCHEMA target"); target.setKind(kind); target.setAllInSchema(true); } + name=RelObjectNames() { target.getNames().add(name.getNames()); } + ( "," name=RelObjectNames() { target.getNames().add(name.getNames()); } )* + | + LOOKAHEAD({ isAccessTargetKindAhead() && (isAccessKeywordAhead("FUNCTION") || isAccessKeywordAhead("PROCEDURE") || isAccessKeywordAhead("ROUTINE")) }) + kind=AccessObjectKind(false) { target.setKind(kind); target.setExplicitKind(true); } + routine=AccessRoutine() { target.getRoutines().add(routine); } + ( "," routine=AccessRoutine() { target.getRoutines().add(routine); } )* + | + LOOKAHEAD({ isAccessTargetKindAhead() && isAccessKeywordAhead("LARGE") }) + kind=AccessObjectKind(false) { target.setKind(kind); target.setExplicitKind(true); } + number= { target.getNames().add(Collections.singletonList(number.image)); } + ( "," number= { target.getNames().add(Collections.singletonList(number.image)); } )* + | + [ LOOKAHEAD({ isAccessTargetKindAhead() }) kind=AccessObjectKind(false) { target.setKind(kind); target.setExplicitKind(true); } ] + name=RelObjectNames() { target.getNames().add(name.getNames()); } + ( "," name=RelObjectNames() { target.getNames().add(name.getNames()); } )* + ) + { requireAccessSyntax(target.isDefaultPrivileges() == defaults, "Invalid default-privilege target"); return target; } +} + +String AccessGrantee(): +{ String name; boolean group = false; } +{ [ LOOKAHEAD(1) { group = true; } ] name=RelObjectName() { return (group ? "GROUP " : "") + name; } } + +List AccessGrantees(): +{ List names = new ArrayList(); String name; } +{ name=AccessGrantee() { names.add(name); } ( "," name=AccessGrantee() { names.add(name); } )* { return names; } } + +PrivilegeClause AccessClause(boolean revoke, boolean defaults): +{ PrivilegeClause clause = new PrivilegeClause(); Privilege privilege; PrivilegeTarget target; List names; } +{ + ( + LOOKAHEAD({ isObjectPrivilegeAhead() }) + { clause.setPrivileges(new ArrayList()); } + privilege=AccessPrivilege() { clause.getPrivileges().add(privilege); } + ( "," privilege=AccessPrivilege() { clause.getPrivileges().add(privilege); } )* + target=AccessTarget(defaults) { clause.setTarget(target); } + | + names=AccessNames() { clause.setRoles(names); } + ) + ( { requireAccessSyntax(!revoke, "REVOKE requires FROM"); } | { requireAccessSyntax(revoke, "GRANT requires TO"); } ) + names=AccessGrantees() { clause.setGrantees(names); return clause; } +} + +Grant Grant(): +{ Grant grant; } +{ grant=AccessGrant(false) { return grant; } } + +Grant AccessGrant(boolean defaults): +{ Grant grant = new Grant(); PrivilegeClause clause; Token token; GrantOption option; String name; } +{ + clause=AccessClause(false, defaults) { grant.setClause(clause); } + [ LOOKAHEAD(1) + ( token= | token= | token= ) { option = new GrantOption(accessEnum(GrantOption.Kind.class, token.image)); } + ( token= | token= | token= ) + { option.setValue(accessEnum(GrantOption.Value.class, token.image)); grant.setOption(option); + requireAccessSyntax(clause.getPrivileges() == null ? option.getKind() != GrantOption.Kind.GRANT + : option.getKind() == GrantOption.Kind.GRANT && option.getValue() == GrantOption.Value.OPTION, "Invalid GRANT option"); } + ] + [ LOOKAHEAD({ isAccessKeywordAhead("GRANTED") }) AccessKeyword("GRANTED") name=RelObjectName() + { requireAccessSyntax(!defaults, "GRANTED BY is not part of default privileges"); clause.setGrantedBy(name); } ] + { return grant; } +} + +Revoke Revoke(boolean defaults): +{ Revoke revoke = new Revoke(); PrivilegeClause clause; Token token; String name; } +{ + + [ LOOKAHEAD(3) ( token= | token= | token= ) + { revoke.setOptionFor(accessEnum(GrantOption.Kind.class, token.image)); } ] + clause=AccessClause(true, defaults) { revoke.setClause(clause); } + [ LOOKAHEAD({ isAccessKeywordAhead("GRANTED") }) AccessKeyword("GRANTED") name=RelObjectName() + { requireAccessSyntax(!defaults, "GRANTED BY is not part of default privileges"); clause.setGrantedBy(name); } ] + [ ( { revoke.setBehavior(Revoke.Behavior.CASCADE); } | { revoke.setBehavior(Revoke.Behavior.RESTRICT); } ) ] + { requireAccessSyntax(revoke.getOptionFor() == null || (clause.getPrivileges() == null + ? revoke.getOptionFor() != GrantOption.Kind.GRANT : revoke.getOptionFor() == GrantOption.Kind.GRANT), "Invalid REVOKE option"); + return revoke; } +} + +AlterDefaultPrivileges AlterDefaultPrivileges(): +{ AlterDefaultPrivileges result = new AlterDefaultPrivileges(); Token token; List names; Grant grant; Revoke revoke; } +{ + AccessKeyword("PRIVILEGES") + [ ( token= | token= ) { result.setRoleKeyword(accessEnum(AlterDefaultPrivileges.RoleKeyword.class, token.image)); } + names=AccessNames() { result.setRoles(names); } ] + [ names=AccessNames() { result.setSchemas(names); } ] + ( grant=AccessGrant(true) { result.setGrant(grant); } + | revoke=Revoke(true) { result.setRevoke(revoke); } ) + { return result; } +} + +TriggerEvent PostgreSqlTriggerEvent(): +{ TriggerEvent result = null; String name; ExpressionList columns; } +{ + ( + { result = new TriggerEvent(CreateTrigger.Event.INSERT); } + | { result = new TriggerEvent(CreateTrigger.Event.DELETE); } + | { result = new TriggerEvent(CreateTrigger.Event.TRUNCATE); } + | { result = new TriggerEvent(CreateTrigger.Event.UPDATE); } + [ { columns = new ExpressionList(); } + name=RelObjectName() { columns.add(new Column(name)); } + ( "," name=RelObjectName() { columns.add(new Column(name)); } )* + { result.setColumns(columns); } ] + ) + { return result; } +} + +TransitionRelation PostgreSqlTransitionRelation(): +{ TransitionRelation result = new TransitionRelation(); Token token; String name; } +{ + ( token= ) { result.setImage(accessEnum(TransitionRelation.Image.class, token.image)); } + [ { result.setUseAs(true); } ] name=RelObjectName() { result.setName(name); return result; } +} + +CreateTrigger PostgreSqlTrigger(boolean replace): +{ + CreateTrigger result = new CreateTrigger(); String name; Table table; CreateTrigger.Timing timing; + TriggerEvent event; TransitionRelation transition; Expression expression; Token token; boolean negative = false; + Statement body = null; + Function routine = new Function(); ExpressionList arguments = new ExpressionList(); +} +{ + [ { result.setConstraint(true); } ] + name=RelObjectName() { result.setTrigger(new Table(name)); result.setOrReplace(replace); } + ( + LOOKAHEAD({ isAccessKeywordAhead("INSTEAD") }) AccessKeyword("INSTEAD") { result.setTiming(CreateTrigger.Timing.INSTEAD_OF); } + | timing=MySqlTriggerTiming() { result.setTiming(timing); } + ) + event=PostgreSqlTriggerEvent() { result.getEvents().add(event); } + ( event=PostgreSqlTriggerEvent() { result.getEvents().add(event); } )* + table=Table() { result.setTable(table); } + // greedy: FROM here names the referenced table, it never starts the trigger body + [ LOOKAHEAD(2) table=Table() { result.setReferencedTable(table); } ] + [ [ { negative = true; } ] { result.getConstraintAttributes().setDeferrable(!negative); } ] + [ LOOKAHEAD({ isAccessKeywordAhead("INITIALLY") }) + AccessKeyword("INITIALLY") ( token= ) + { result.getConstraintAttributes().setInitially(accessEnum(ConstraintAttributes.Initially.class, token.image)); } ] + [ LOOKAHEAD({ isAccessKeywordAhead("REFERENCING") }) AccessKeyword("REFERENCING") + transition=PostgreSqlTransitionRelation() { result.getTransitionRelations().add(transition); } + ( LOOKAHEAD({ isAccessKeywordAhead("OLD") || isAccessKeywordAhead("NEW") }) + transition=PostgreSqlTransitionRelation() { result.getTransitionRelations().add(transition); } )* ] + [ [ LOOKAHEAD({ isAccessKeywordAhead("EACH") }) AccessKeyword("EACH") { result.setUseEach(true); } ] + ( { result.setOrientation(CreateTrigger.Orientation.ROW); } + | { result.setOrientation(CreateTrigger.Orientation.STATEMENT); } ) ] + [ "(" expression=Expression() ")" { result.setWhenExpression(expression); } ] + ( + LOOKAHEAD(2) + ( | { result.setExecuteKeyword(CreateTrigger.ExecuteKeyword.PROCEDURE); } ) + name=AccessQualifiedName() { routine.setName(name); } + "(" [ expression=PostgreSqlTriggerArgument() { arguments.add(expression); } + ( "," expression=PostgreSqlTriggerArgument() { arguments.add(expression); } )* ] ")" + { routine.setParameters(arguments); result.setRoutine(routine); } + | + // DuckDB 2.0 runs a statement rather than a function + body=SingleStatement() { result.setBody(body); } + ) + { + requireAccessSyntax(!result.isConstraint() || !replace && result.getTiming() == CreateTrigger.Timing.AFTER + && result.getOrientation() == CreateTrigger.Orientation.ROW, "A constraint trigger requires AFTER FOR EACH ROW without OR REPLACE"); + requireAccessSyntax(result.isConstraint() || result.getReferencedTable() == null + && result.getConstraintAttributes().getDeferrable() == null && result.getConstraintAttributes().getInitially() == null, + "FROM and deferrability require a constraint trigger"); + return result; + } +} + +Expression PostgreSqlTriggerArgument(): +{ Expression result; Token token; String name; } +{ + ( + token= { result = new StringValue(token.image); } + | token= { result = new LongValue(token.image); } + | token= { result = new DoubleValue(token.image); } + | name=RelObjectName() { result = new Column(name); } + ) + { return result; } +} + List UsersList(): { List users = new ArrayList(); @@ -12930,111 +18180,70 @@ Sequence Sequence() #Sequence : List SequenceParameters(): { - List sequenceParameters = new ArrayList(); - Sequence.Parameter parameter = null; - Token token = null; - Token byToken = null; - Token withToken = null; + List parameters = new ArrayList(); + Sequence.Parameter parameter; +} +{ + ( LOOKAHEAD(2) parameter=SequenceParameter() { parameters.add(parameter); } )* + { return parameters; } +} + +Sequence.Parameter SequenceParameter(): +{ + Sequence.ParameterType type; + Long value = null; + Token token; + boolean withKeyword = false; } { ( - LOOKAHEAD(2) ( - ( - [ byToken= ] token= - { - if (byToken != null) { - parameter = new Sequence.Parameter(Sequence.ParameterType.INCREMENT_BY); - } else { - parameter = new Sequence.Parameter(Sequence.ParameterType.INCREMENT); - } - parameter.setValue(Long.parseLong(token.image)); - sequenceParameters.add(parameter); - } - ) - | - ( - [ withToken= ] token= - { - if (withToken != null) { - parameter = new Sequence.Parameter(Sequence.ParameterType.START_WITH); - } else { - parameter = new Sequence.Parameter(Sequence.ParameterType.START); - } - parameter.setValue(Long.parseLong(token.image)); - sequenceParameters.add(parameter); - } - ) - | - ( - [ LOOKAHEAD(2) token=] - { - parameter = new Sequence.Parameter(Sequence.ParameterType.RESTART_WITH); - if(token != null) { - parameter.setValue(Long.parseLong(token.image)); - } - sequenceParameters.add(parameter); - } - ) - | - - { - parameter = new Sequence.Parameter(Sequence.ParameterType.NOMAXVALUE); - sequenceParameters.add(parameter); - } - | - token= - { - parameter = new Sequence.Parameter(Sequence.ParameterType.MAXVALUE); - parameter.setValue(Long.parseLong(token.image)); - sequenceParameters.add(parameter); - } - | - - { - parameter = new Sequence.Parameter(Sequence.ParameterType.NOMINVALUE); - sequenceParameters.add(parameter); - } - | - token= - { - parameter = new Sequence.Parameter(Sequence.ParameterType.MINVALUE); - parameter.setValue(Long.parseLong(token.image)); - sequenceParameters.add(parameter); - } - | - { sequenceParameters.add(new Sequence.Parameter(Sequence.ParameterType.NOCYCLE)); } - | - { sequenceParameters.add(new Sequence.Parameter(Sequence.ParameterType.CYCLE)); } - | - - { - parameter = new Sequence.Parameter(Sequence.ParameterType.NOCACHE); - sequenceParameters.add(parameter); - } - | - token= - { - parameter = new Sequence.Parameter(Sequence.ParameterType.CACHE); - parameter.setValue(Long.parseLong(token.image)); - sequenceParameters.add(parameter); - } - | - { sequenceParameters.add(new Sequence.Parameter(Sequence.ParameterType.ORDER)); } - | - { sequenceParameters.add(new Sequence.Parameter(Sequence.ParameterType.NOORDER)); } - | - { sequenceParameters.add(new Sequence.Parameter(Sequence.ParameterType.KEEP)); } - | - { sequenceParameters.add(new Sequence.Parameter(Sequence.ParameterType.NOKEEP)); } - | - { sequenceParameters.add(new Sequence.Parameter(Sequence.ParameterType.SESSION)); } - | - { sequenceParameters.add(new Sequence.Parameter(Sequence.ParameterType.GLOBAL)); } - ) - )* - { - return sequenceParameters; + [ { withKeyword = true; } ] value=SequenceParameterValue() + { type = withKeyword ? Sequence.ParameterType.INCREMENT_BY : Sequence.ParameterType.INCREMENT; } + | + [ { withKeyword = true; } ] value=SequenceParameterValue() + { type = withKeyword ? Sequence.ParameterType.START_WITH : Sequence.ParameterType.START; } + | + [ LOOKAHEAD(2) value=SequenceParameterValue() ] + { type = Sequence.ParameterType.RESTART_WITH; } + | + ( token= | token= | token= ) value=SequenceParameterValue() + { type = Sequence.ParameterType.valueOf(token.image.toUpperCase(Locale.ROOT)); } + | + ( token= | token= | token= ) + { type = Sequence.ParameterType.valueOf("NO_" + token.image.toUpperCase(Locale.ROOT)); } + | + ( token= | token= | token= | token= + | token= | token= | token= | token= + | token= | token= | token= ) + { type = Sequence.ParameterType.valueOf(token.image.toUpperCase(Locale.ROOT)); } + ) + { return new Sequence.Parameter(type).withValue(value); } +} + +Long SequenceParameterValue(): +{ + Token sign = null; + Token value; +} +{ + [ sign="-" | sign="+" ] value= + { return Long.valueOf((sign == null ? "" : sign.image) + value.image); } +} + +SequenceOwnership SequenceOwnership(): +{ + Token token; + Column column; + SequenceOwnership ownership; +} +{ + token= { + requireDdlSyntax("OWNED".equalsIgnoreCase(token.image), "Expected OWNED"); } + + ( LOOKAHEAD(1) { ownership = SequenceOwnership.none(); } + | column=Column() { ownership = SequenceOwnership.ownedBy(column); } ) + { return ownership; } } CreateSequence CreateSequence(): @@ -13043,11 +18252,13 @@ CreateSequence CreateSequence(): Sequence sequence; List sequenceParameters; Token dataType = null; + SequenceOwnership ownership; } { sequence=Sequence() { createSequence.setSequence(sequence); } [ ( dataType= | dataType= ) { sequence.setDataType(dataType.image); } ] sequenceParameters = SequenceParameters() + [ LOOKAHEAD({ isKeywordAhead("OWNED") }) ownership=SequenceOwnership() { sequence.setOwnership(ownership); } ] { sequence.setParameters(sequenceParameters); return createSequence; @@ -13059,10 +18270,12 @@ AlterSequence AlterSequence(): AlterSequence alterSequence = new AlterSequence(); Sequence sequence; List sequenceParameters; + SequenceOwnership ownership; } { sequence=Sequence() { alterSequence.setSequence(sequence); } sequenceParameters = SequenceParameters() + [ LOOKAHEAD({ isKeywordAhead("OWNED") }) ownership=SequenceOwnership() { sequence.setOwnership(ownership); } ] { sequence.setParameters(sequenceParameters); return alterSequence; @@ -13079,26 +18292,57 @@ Statement Create(): { [ { isUsingOrReplace = true; } ] ( + LOOKAHEAD({ isPostgreSqlRoleAhead() }) statement = CreateRole() + { requireAccessSyntax(!isUsingOrReplace, "CREATE ROLE does not accept OR REPLACE"); } + | + LOOKAHEAD({ isPostgreSqlTriggerAhead() }) statement = PostgreSqlTrigger(isUsingOrReplace) + | + LOOKAHEAD() { requireTypeDdlSyntax(!isUsingOrReplace, "OR REPLACE is not supported for CREATE TYPE"); } statement = CreateType() + | + LOOKAHEAD() { requireTypeDdlSyntax(!isUsingOrReplace, "OR REPLACE is not supported for CREATE DOMAIN"); } statement = CreateDomain() + | + LOOKAHEAD({ isKeywordAhead("EXTENSION") + && "REPOSITORY".equalsIgnoreCase(getToken(2).image) }) + { requireTypeDdlSyntax(!isUsingOrReplace, + "OR REPLACE is not supported for CREATE EXTENSION REPOSITORY"); } + statement = CreateExtensionRepository() + | + LOOKAHEAD({ isKeywordAhead("EXTENSION") }) { requireTypeDdlSyntax(!isUsingOrReplace, "OR REPLACE is not supported for CREATE EXTENSION"); } statement = CreateExtension() + | + LOOKAHEAD({ isKeywordAhead("PUBLICATION") }) + { requireReplicationSyntax(!isUsingOrReplace, "OR REPLACE is not supported for CREATE PUBLICATION"); } + statement = CreatePublication() + | + LOOKAHEAD({ isKeywordAhead("SUBSCRIPTION") }) + { requireReplicationSyntax(!isUsingOrReplace, "OR REPLACE is not supported for CREATE SUBSCRIPTION"); } + statement = CreateSubscription() + | + statement = CreateUser() + | + statement = CreateEvent() + | + LOOKAHEAD({ getToken(1).kind == K_TRIGGER + || "DEFINER".equalsIgnoreCase(getToken(1).image) }) + statement = CreateTrigger() + | statement = CreateFunctionStatement(isUsingOrReplace) | statement = CreateSchema() | + statement = CreateDatabase() + | statement = CreateSequence() | statement = CreateSynonym(isUsingOrReplace) | + LOOKAHEAD({ isCreateMacroAhead() }) statement = CreateMacro(isUsingOrReplace) + | LOOKAHEAD(3) statement = CreateTable(isUsingOrReplace) | LOOKAHEAD(2) statement = CreateView(isUsingOrReplace) | statement = CreatePolicy() | - // @fixme: must appear with TRIGGER before INDEX or it will collide with INDEX's CreateParameter() production - ( tk= | tk= ) captureRest = captureRest() - { - statement = new UnsupportedStatement("CREATE " + tk.image, captureRest); - } - | /* @fixme * Create Index uses CreateParameter() which allows all kind of tokens * it can conflict with other statements and must be at the end right now @@ -13140,6 +18384,99 @@ CreateFunctionalStatement CreateFunctionStatement(boolean isUsingOrReplace): } } +CreateFunctionalStatement SqlServerRoutine(): +{ + CreateFunctionalStatement result; + CreateFunctionalStatement.Operation operation = CreateFunctionalStatement.Operation.CREATE; + List parts; + FunctionReturnType returnType; +} +{ + ( [ { operation = CreateFunctionalStatement.Operation.CREATE_OR_ALTER; } ] + | { operation = CreateFunctionalStatement.Operation.ALTER; } ) + ( + parts=captureSqlServerFunctionHeader() + returnType=SqlServerFunctionReturnType() + { result = new CreateFunction(parts); result.setReturnType(returnType); } + parts=captureSqlServerRoutineBody(false) { result.setRoutineBodyParts(parts); } + | parts=captureSqlServerRoutineBody(true) { result = new CreateProcedure(parts); } + ) + { result.setOperation(operation); return result; } +} + +FunctionReturnType SqlServerFunctionReturnType(): +{ + FunctionReturnType result = new FunctionReturnType(); + UserVariable variable; ColDataType dataType; + TableElement element; List elements = new ArrayList(); +} +{ + + ( + LOOKAHEAD({ getToken(1).kind == K_TABLE || getToken(1).kind == S_AT_IDENTIFIER }) + [ variable=UserVariable() { result.setTableVariable(variable.toString()); } ] + { result.setTable(true); } + [ "(" element=SqlServerReturnTableElement() { elements.add(element); } + ( "," element=SqlServerReturnTableElement() { elements.add(element); } )* ")" + { result.setTableElements(elements); } ] + { requireDdlSyntax(result.getTableVariable() == null || result.getTableElements() != null, + "A return table variable requires column definitions"); } + | dataType=ColDataType() { result.setDataType(dataType); } + ) + { return result; } +} + +TableElement SqlServerReturnTableElement(): +{ TableElement element; } +{ + ( LOOKAHEAD(3) element=CreateTableConstraint() | element=ColumnDefinition() ) + { return element; } +} + +JAVACODE +List captureSqlServerFunctionHeader() { + List parts = new ArrayList(); + int depth = 0; + while (true) { + Token next = getToken(1); + if (next.kind == K_RETURNS && depth == 0) break; + if (next.kind == EOF || next.kind == ST_SEMICOLON) { + throw new ParseException("A SQL Server function requires RETURNS"); + } + if ("(".equals(next.image)) depth++; + if (")".equals(next.image)) depth--; + if (depth < 0) throw new ParseException("Unbalanced function parameters"); + parts.add(getNextToken().image); + } + if (parts.size() < 3 || "(".equals(parts.get(0)) || !")".equals(parts.get(parts.size() - 1))) { + throw new ParseException("A SQL Server function requires a name and parameter parentheses"); + } + return parts; +} + +JAVACODE +List captureSqlServerRoutineBody(boolean procedure) { + List parts = new ArrayList(); + int depth = 0; + while (true) { + Token next = getToken(1); + // Procedure definitions occupy the rest of their batch, including statements + // after an END. Their opaque bodies may contain BEGIN TRANSACTION and TRY/CATCH. + if (next.kind == EOF || (!procedure && next.kind == ST_SEMICOLON && depth == 0)) break; + if (!procedure) { + if (next.kind == K_BEGIN || next.kind == K_CASE) { + depth++; + } else if (next.kind == K_END) { + if (--depth < 0) throw new ParseException("Unmatched END in routine"); + } + } + parts.add(getNextToken().image); + } + if (depth != 0) throw new ParseException("Unterminated SQL Server routine block"); + if (parts.isEmpty()) throw new ParseException("A routine body is required"); + return parts; +} + CreateSynonym CreateSynonym(boolean isUsingOrReplace): { CreateSynonym createSynonym = new CreateSynonym(); @@ -13173,11 +18510,541 @@ Synonym Synonym() #Synonym : } } + +String TypeDdlName(): +{ String name; String part; Token keyword; } +{ + ( name=RelObjectName() | keyword= { name = keyword.image; } ) + ( LOOKAHEAD(2) "." ( part=RelObjectNameExt() | keyword= { part = keyword.image; } ) + { name += "." + part; } )* + { return name; } +} + +void TypeDdlKeyword(String expected): +{ Token keyword; } +{ ( keyword= | keyword= ) { requireTypeDdlSyntax(expected.equalsIgnoreCase(keyword.image), "Expected " + expected); } } + +Expression ExtensionVersion(): +{ Token token; String name; Expression result; } +{ + ( token= { result = new StringValue(token.image); } + | name=RelObjectName() { result = new Column(name); } ) + { return result; } +} + +TypeAttribute TypeAttribute(): +{ TypeAttribute attribute = new TypeAttribute(); String name; ColDataType type; } +{ + name=RelObjectName() { attribute.setName(name); } + type=ColDataType() { attribute.setDataType(type); } + [ name=TypeDdlName() { attribute.setCollation(name); } ] + { return attribute; } +} + +CreateType CreateType(): +{ + CreateType result = new CreateType(); + String name; Token label; TypeAttribute attribute; + EnumTypeDefinition enumeration = new EnumTypeDefinition(); + CompositeTypeDefinition composite = new CompositeTypeDefinition(); + RangeTypeDefinition range = new RangeTypeDefinition(); + RangeTypeDefinition.Option option; +} +{ + name=TypeDdlName() { result.setName(name); } + [ + + ( + LOOKAHEAD({ isKeywordAhead("ENUM") }) TypeDdlKeyword("ENUM") "(" + [ label= { enumeration.getLabels().add(new StringValue(label.image)); } + ( "," label= { enumeration.getLabels().add(new StringValue(label.image)); } )* ] + ")" { result.setDefinition(enumeration); } + | "(" option=RangeTypeOption() { range.getOptions().add(option); } + ( "," option=RangeTypeOption() { range.getOptions().add(option); } )* ")" + { + requireTypeDdlSyntax(range.getSubtype() != null, "A range definition requires SUBTYPE"); + result.setDefinition(range); + } + | "(" [ attribute=TypeAttribute() { composite.getAttributes().add(attribute); } + ( "," attribute=TypeAttribute() { composite.getAttributes().add(attribute); } )* ] + ")" { result.setDefinition(composite); } + ) + ] + { return result; } +} + +RangeTypeDefinition.Option RangeTypeOption(): +{ String name; RangeTypeDefinition.OptionKind kind; ColDataType type; } +{ + name=RelObjectName() "=" { kind = typeDdlEnum(RangeTypeDefinition.OptionKind.class, name); } + ( + LOOKAHEAD({ kind == RangeTypeDefinition.OptionKind.SUBTYPE }) + type=ColDataType() { return new RangeTypeDefinition.Option(type); } + | name=TypeDdlName() { return new RangeTypeDefinition.Option(kind, name); } + ) +} + +AlterType.Behavior TypeAlterBehavior(): +{ AlterType.Behavior result; } +{ + ( { result = AlterType.Behavior.CASCADE; } + | { result = AlterType.Behavior.RESTRICT; } ) + { return result; } +} + +AlterType.AttributeChange TypeAttributeChange(): +{ + AlterType.AttributeChange result = new AlterType.AttributeChange(); + TypeAttribute attribute = new TypeAttribute(); + String name; ColDataType type; AlterType.Behavior behavior; +} +{ + ( + TypeDdlKeyword("ATTRIBUTE") attribute=TypeAttribute() + { result.setKind(AlterType.AttributeChange.Kind.ADD); } + | TypeDdlKeyword("ATTRIBUTE") [ LOOKAHEAD(2) { result.setIfExists(true); } ] + name=RelObjectName() { attribute.setName(name); result.setKind(AlterType.AttributeChange.Kind.DROP); } + | TypeDdlKeyword("ATTRIBUTE") name=RelObjectName() { attribute.setName(name); } + [ { result.setUseSetData(true); } ] + type=ColDataType() { attribute.setDataType(type); result.setKind(AlterType.AttributeChange.Kind.ALTER); } + [ name=TypeDdlName() { attribute.setCollation(name); } ] + ) + [ behavior=TypeAlterBehavior() { result.setBehavior(behavior); } ] + { result.setAttribute(attribute); return result; } +} + +AlterType AlterType(): +{ + AlterType result = new AlterType(); String name; Token label; + AlterType.AttributeChange change; AlterType.Behavior behavior; +} +{ + name=TypeDdlName() { result.setName(name); } + ( + LOOKAHEAD( ) + { result.setAction(AlterType.Action.ADD_VALUE); } + [ { result.setIfNotExists(true); } ] + label= { result.setValue(new StringValue(label.image)); } + [ ( { result.setPosition(AlterType.Position.BEFORE); } + | TypeDdlKeyword("AFTER") { result.setPosition(AlterType.Position.AFTER); } ) + label= { result.setNeighborValue(new StringValue(label.image)); } ] + | + ( + label= { result.setValue(new StringValue(label.image)); } + label= { result.setNewValue(new StringValue(label.image)); result.setAction(AlterType.Action.RENAME_VALUE); } + | LOOKAHEAD({ isKeywordAhead("ATTRIBUTE") }) TypeDdlKeyword("ATTRIBUTE") + name=RelObjectName() { result.setAttributeName(name); } + name=RelObjectName() { result.setNewName(name); result.setAction(AlterType.Action.RENAME_ATTRIBUTE); } + [ behavior=TypeAlterBehavior() { result.setBehavior(behavior); } ] + | name=RelObjectName() { result.setNewName(name); result.setAction(AlterType.Action.RENAME); } + ) + | name=RelObjectName() { result.setNewName(name); result.setAction(AlterType.Action.SET_SCHEMA); } + | LOOKAHEAD({ isKeywordAhead("OWNER") }) TypeDdlKeyword("OWNER") + name=RelObjectName() { result.setNewName(name); result.setAction(AlterType.Action.OWNER); } + | change=TypeAttributeChange() { result.setAction(AlterType.Action.ATTRIBUTES); result.getAttributeChanges().add(change); } + ( "," change=TypeAttributeChange() { result.getAttributeChanges().add(change); } )* + ) + { return result; } +} + +DomainConstraint DomainConstraint(boolean allowNull): +{ DomainConstraint result = new DomainConstraint(); String name; Expression expression; } +{ + [ name=RelObjectName() { result.setName(name); } ] + ( + { result.setKind(DomainConstraint.Kind.NOT_NULL); } + | { requireTypeDdlSyntax(allowNull, "ALTER DOMAIN ADD does not accept NULL"); result.setKind(DomainConstraint.Kind.NULL); } + | "(" expression=Expression() ")" + { result.setKind(DomainConstraint.Kind.CHECK); result.setExpression(expression); } + ) + { return result; } +} + +CreateDomain CreateDomain(): +{ CreateDomain result = new CreateDomain(); String name; ColDataType type; Expression expression; DomainConstraint constraint; } +{ + name=TypeDdlName() { result.setName(name); } + [ { result.setUseAs(true); } ] type=ColDataType() { result.setDataType(type); } + [ name=TypeDdlName() { result.setCollation(name); } ] + [ expression=Expression() { result.setDefaultExpression(expression); } ] + ( constraint=DomainConstraint(true) { result.getConstraints().add(constraint); } )* + { return result; } +} + +AlterDomain AlterDomain(): +{ AlterDomain result = new AlterDomain(); String name; Expression expression; DomainConstraint constraint; } +{ + name=TypeDdlName() { result.setName(name); } + ( + + ( + expression=Expression() { result.setAction(AlterDomain.Action.SET_DEFAULT); result.setDefaultExpression(expression); } + | { result.setAction(AlterDomain.Action.SET_NOT_NULL); } + | name=RelObjectName() { result.setAction(AlterDomain.Action.SET_SCHEMA); result.setNewName(name); } + ) + | + ( + { result.setAction(AlterDomain.Action.DROP_DEFAULT); } + | { result.setAction(AlterDomain.Action.DROP_NOT_NULL); } + | [ LOOKAHEAD(2) { result.setIfExists(true); } ] + name=RelObjectName() { result.setAction(AlterDomain.Action.DROP_CONSTRAINT); result.setConstraintName(name); } + [ { result.setBehavior(AlterDomain.Behavior.CASCADE); } + | { result.setBehavior(AlterDomain.Behavior.RESTRICT); } ] + ) + | constraint=DomainConstraint(false) { result.setAction(AlterDomain.Action.ADD_CONSTRAINT); result.setConstraint(constraint); } + [ TypeDdlKeyword("VALID") { + requireTypeDdlSyntax(constraint.getKind() == DomainConstraint.Kind.CHECK, "NOT VALID requires a CHECK constraint"); + result.setNotValid(true); + } ] + | + ( + name=RelObjectName() { result.setConstraintName(name); } + name=RelObjectName() { result.setNewName(name); result.setAction(AlterDomain.Action.RENAME_CONSTRAINT); } + | name=RelObjectName() { result.setNewName(name); result.setAction(AlterDomain.Action.RENAME); } + ) + | + name=RelObjectName() { result.setConstraintName(name); result.setAction(AlterDomain.Action.VALIDATE_CONSTRAINT); } + | LOOKAHEAD({ isKeywordAhead("OWNER") }) TypeDdlKeyword("OWNER") + name=RelObjectName() { result.setNewName(name); result.setAction(AlterDomain.Action.OWNER); } + ) + { return result; } +} + +/** + * DuckDB 2.0's CREATE EXTENSION REPOSITORY [IF NOT EXISTS] name [WITH PREFIX prefix]. + */ +CreateExtensionRepository CreateExtensionRepository() #CreateExtensionRepository: +{ + CreateExtensionRepository createExtensionRepository = new CreateExtensionRepository(); + Expression prefix; + String name; +} +{ + TypeDdlKeyword("EXTENSION") TypeDdlKeyword("REPOSITORY") + [ LOOKAHEAD(3) + { createExtensionRepository.setIfNotExists(true); } ] + name=RelObjectName() { createExtensionRepository.setName(name); } + // greedy: WITH PREFIX belongs to the repository definition + [ + LOOKAHEAD(2) TypeDdlKeyword("PREFIX") prefix=SimpleExpression() + { createExtensionRepository.setPrefix(prefix); } + ] + { return createExtensionRepository; } +} + +CreateExtension CreateExtension(): +{ CreateExtension result = new CreateExtension(); String name; Expression version; } +{ + TypeDdlKeyword("EXTENSION") + [ LOOKAHEAD(3) { result.setIfNotExists(true); } ] + name=RelObjectName() { result.setName(name); } + [ LOOKAHEAD(1) { result.setUseWith(true); } ] + ( + name=RelObjectName() { result.getOptions().add(new CreateExtension.Option(CreateExtension.OptionKind.SCHEMA, name, null)); } + | version=ExtensionVersion() { result.getOptions().add(new CreateExtension.Option(CreateExtension.OptionKind.VERSION, null, version)); } + | { result.getOptions().add(new CreateExtension.Option(CreateExtension.OptionKind.CASCADE, null, null)); } + )* + { return result; } +} + +RoutineReference.Argument RoutineSignatureArgument(): +{ RoutineReference.Argument argument; } +{ argument=RoutineArgument(true) { return argument; } } + +/** Common argument AST; only extension aggregate signatures may end a type at ORDER BY. */ +RoutineReference.Argument RoutineArgument(boolean allowOrderBy): +{ RoutineReference.Argument result = new RoutineReference.Argument(); String name; Token mode; ColDataType type; } +{ + [ + LOOKAHEAD({ isKeywordAhead("IN") || isKeywordAhead("OUT") || isKeywordAhead("INOUT") || isKeywordAhead("VARIADIC") }) + ( { result.setMode(RoutineReference.Argument.Mode.IN); } + | mode= { result.setMode(allowOrderBy + ? typeDdlEnum(RoutineReference.Argument.Mode.class, mode.image) + : accessEnum(RoutineReference.Argument.Mode.class, mode.image)); } ) + ] + ( + LOOKAHEAD(ColDataType() ("," | ")" | )) type=ColDataType() + | name=RelObjectName() { result.setName(name); } type=ColDataType() + ) + { + requireAccessSyntax(allowOrderBy || getToken(1).kind != K_ORDER, + "ORDER BY requires an aggregate signature"); + result.setDataType(type); + return result; + } +} + +RoutineReference RoutineReference(boolean aggregate): +{ + RoutineReference result = new RoutineReference(); String name; RoutineReference.Argument argument; + List arguments; List orderBy; +} +{ + name=TypeDdlName() { result.setName(name); } + [ + LOOKAHEAD(1) + "(" { arguments = new ArrayList(); result.setArguments(arguments); } + ( + "*" { requireTypeDdlSyntax(aggregate, "Only aggregate signatures accept *"); result.setAllArguments(true); } + | + [ argument=RoutineSignatureArgument() { arguments.add(argument); } + ( "," argument=RoutineSignatureArgument() { arguments.add(argument); } )* ] + [ { requireTypeDdlSyntax(aggregate, "ORDER BY requires an aggregate signature"); orderBy = new ArrayList(); } + argument=RoutineSignatureArgument() { orderBy.add(argument); } + ( "," argument=RoutineSignatureArgument() { orderBy.add(argument); } )* + { result.setOrderByArguments(orderBy); } ] + ) + ")" + ] + { requireTypeDdlSyntax(!aggregate || result.getArguments() != null, "An aggregate signature is required"); return result; } +} + +String ExtensionOperatorName(): +{ String schema = null; String operator = ""; Token token; } +{ + [ LOOKAHEAD(RelObjectName() ".") schema=RelObjectName() "." ] + ( token="+" | token="-" | token="*" | token="/" | token="%" | token="=" + | token= | token= | token="<" | token=">" + | token= | token= | token="&&" | token="@>" | token="<@" | token="~" + | token="~*" | token="!~" | token="!~*" | token="?" | token="?|" | token="?&" + | token= ) + { operator = token.image; return (schema == null ? "" : schema + ".") + operator; } +} + +ColDataType ExtensionOperatorType(): +{ ColDataType type = null; } +{ ( | type=ColDataType() ) { return type; } } + +ExtensionObject ExtensionObject(): +{ + ExtensionObject result = new ExtensionObject(); String name; String kind; + RoutineReference routine; ColDataType type; +} +{ + ( + LOOKAHEAD({ isKeywordAhead("FUNCTION") || isKeywordAhead("PROCEDURE") || isKeywordAhead("ROUTINE") || isKeywordAhead("AGGREGATE") }) + ( LOOKAHEAD(1) { kind = "PROCEDURE"; } | kind=RelObjectName() ) + routine=RoutineReference("AGGREGATE".equalsIgnoreCase(kind)) + { result.setKind(typeDdlEnum(ExtensionObject.Kind.class, kind)); result.setRoutine(routine); } + | LOOKAHEAD({ isKeywordAhead("CAST") }) "(" + type=ColDataType() { result.setSourceType(type); } + type=ColDataType() { result.setTargetType(type); } ")" { result.setKind(ExtensionObject.Kind.CAST); } + | LOOKAHEAD({ isKeywordAhead("OPERATOR") && !"CLASS".equalsIgnoreCase(getToken(2).image) && !"FAMILY".equalsIgnoreCase(getToken(2).image) }) + TypeDdlKeyword("OPERATOR") name=ExtensionOperatorName() "(" + type=ExtensionOperatorType() { result.setSourceType(type); } "," + type=ExtensionOperatorType() { result.setTargetType(type); } ")" + { result.setKind(ExtensionObject.Kind.OPERATOR); result.setName(name); } + | LOOKAHEAD({ isKeywordAhead("TRANSFORM") }) TypeDdlKeyword("TRANSFORM") + type=ColDataType() { result.setSourceType(type); } TypeDdlKeyword("LANGUAGE") + name=RelObjectName() { result.setLanguage(name); result.setKind(ExtensionObject.Kind.TRANSFORM); } + | + ( + + ( { kind = "FOREIGN_TABLE"; } + | TypeDdlKeyword("WRAPPER") { kind = "FOREIGN_DATA_WRAPPER"; } ) + | LOOKAHEAD(2) { kind = "MATERIALIZED_VIEW"; } + | LOOKAHEAD({ isKeywordAhead("TEXT") }) + name=RelObjectName() { kind = "TEXT_SEARCH_" + name; } + | LOOKAHEAD({ isKeywordAhead("EVENT") }) { kind = "EVENT_TRIGGER"; } + | LOOKAHEAD({ isKeywordAhead("ACCESS") }) TypeDdlKeyword("ACCESS") TypeDdlKeyword("METHOD") { kind = "ACCESS_METHOD"; } + | LOOKAHEAD({ isKeywordAhead("OPERATOR") }) TypeDdlKeyword("OPERATOR") + name=RelObjectName() { kind = "OPERATOR_" + name; } + | LOOKAHEAD({ isKeywordAhead("PROCEDURAL") }) TypeDdlKeyword("PROCEDURAL") TypeDdlKeyword("LANGUAGE") + { result.setProcedural(true); kind = "LANGUAGE"; } + | kind=RelObjectName() + ) + { result.setKind(typeDdlEnum(ExtensionObject.Kind.class, kind)); } + name=TypeDdlName() { result.setName(name); } + [ name=RelObjectName() { result.setAccessMethod(name); } ] + ) + { + requireTypeDdlSyntax((result.getKind() == ExtensionObject.Kind.OPERATOR_CLASS || result.getKind() == ExtensionObject.Kind.OPERATOR_FAMILY) + == (result.getAccessMethod() != null), "USING is required only for operator classes and families"); + return result; + } +} + +AlterExtension AlterExtension(): +{ AlterExtension result = new AlterExtension(); String name; Expression version; ExtensionObject member; } +{ + TypeDdlKeyword("EXTENSION") name=RelObjectName() { result.setName(name); } + ( + { result.setAction(AlterExtension.Action.UPDATE); } + [ version=ExtensionVersion() { result.setVersion(version); } ] + | name=RelObjectName() { result.setAction(AlterExtension.Action.SET_SCHEMA); result.setSchema(name); } + | ( { result.setAction(AlterExtension.Action.ADD); } | { result.setAction(AlterExtension.Action.DROP); } ) + member=ExtensionObject() { result.setMember(member); } + ) + { return result; } +} + +void ReplicationKeyword(String expected): +{ Token token; } +{ token= { requireReplicationSyntax(expected.equalsIgnoreCase(token.image), "Expected " + expected); } } + +Expression ReplicationOptionValue(): +{ Token token; Expression result; } +{ + ( LOOKAHEAD(1) token= { result = new Column(token.image); } | result=PrimaryExpression() ) + { return result; } +} + +PublicationOption PublicationOption(): +{ String name; Expression value = null; } +{ + name=RelObjectName() [ "=" value=ReplicationOptionValue() ] + { return new PublicationOption(name, replicationEnum(PublicationOption.Kind.class, name), value); } +} + +List PublicationOptions(): +{ List options = new ArrayList(); PublicationOption option; } +{ + "(" option=PublicationOption() { options.add(option); } + ( "," option=PublicationOption() { options.add(option); } )* ")" + { return options; } +} + +SubscriptionOption SubscriptionOption(): +{ String name; Expression value = null; } +{ + name=RelObjectName() [ "=" value=ReplicationOptionValue() ] + { return new SubscriptionOption(name, replicationEnum(SubscriptionOption.Kind.class, name), value); } +} + +List SubscriptionOptions(): +{ List options = new ArrayList(); SubscriptionOption option; } +{ + "(" option=SubscriptionOption() { options.add(option); } + ( "," option=SubscriptionOption() { options.add(option); } )* ")" + { return options; } +} + +PublicationTable PublicationTable(boolean drop): +{ PublicationTable result = new PublicationTable(); Table table; String name; Expression expression; ExpressionList columns; } +{ + [ { result.setOnly(true); } ] + table=Table() { result.setTable(table); } + [ "*" { result.setIncludeDescendants(true); } ] + [ LOOKAHEAD(1) "(" { columns = new ExpressionList(); } + name=RelObjectName() { columns.add(new Column(name)); } + ( "," name=RelObjectName() { columns.add(new Column(name)); } )* ")" + { result.setColumns(columns); } ] + [ "(" expression=Expression() ")" { result.setWhere(expression); } ] + { + requireReplicationSyntax(!(result.isOnly() && result.isIncludeDescendants()), "ONLY and * cannot be combined"); + requireReplicationSyntax(!drop || (result.getColumns() == null && result.getWhere() == null), "DROP publication tables cannot have column lists or filters"); + return result; + } +} + +PublicationTarget PublicationTarget(boolean drop): +{ PublicationTarget result = new PublicationTarget(); PublicationTable table; String schema; } +{ + ( + { result.setKind(PublicationTarget.Kind.TABLE); } + table=PublicationTable(drop) { result.getTables().add(table); } + ( LOOKAHEAD({ getToken(1).image.equals(",") && getToken(2).kind != K_TABLE && getToken(2).kind != K_TABLES }) + "," table=PublicationTable(drop) { result.getTables().add(table); } )* + | + { result.setKind(PublicationTarget.Kind.TABLES_IN_SCHEMA); } + schema=RelObjectName() { result.getSchemas().add(schema); } + ( LOOKAHEAD({ getToken(1).image.equals(",") && getToken(2).kind != K_TABLE && getToken(2).kind != K_TABLES }) + "," schema=RelObjectName() { result.getSchemas().add(schema); } )* + ) + { return result; } +} + +List PublicationTargets(boolean drop): +{ List targets = new ArrayList(); PublicationTarget target; } +{ + target=PublicationTarget(drop) { targets.add(target); } + ( "," target=PublicationTarget(drop) { targets.add(target); } )* + { return targets; } +} + +CreatePublication CreatePublication(): +{ CreatePublication result = new CreatePublication(); String name; List targets; List options; } +{ + ReplicationKeyword("PUBLICATION") name=RelObjectName() { result.setName(name); } + [ + + ( { result.setAllTables(true); } + | targets=PublicationTargets(false) { result.setTargets(targets); } ) + ] + [ LOOKAHEAD(2) options=PublicationOptions() { result.setOptions(options); } ] + { return result; } +} + +AlterPublication AlterPublication(): +{ AlterPublication result = new AlterPublication(); String name; List targets; List options; boolean drop = false; } +{ + ReplicationKeyword("PUBLICATION") name=RelObjectName() { result.setName(name); } + ( + LOOKAHEAD({ isKeywordAhead("OWNER") }) ReplicationKeyword("OWNER") + name=RelObjectName() { result.setNewName(name); result.setAction(AlterPublication.Action.OWNER); } + | name=RelObjectName() { result.setNewName(name); result.setAction(AlterPublication.Action.RENAME); } + | LOOKAHEAD( "(") options=PublicationOptions() { result.setOptions(options); result.setAction(AlterPublication.Action.SET_OPTIONS); } + | ( { result.setAction(AlterPublication.Action.ADD); } + | { result.setAction(AlterPublication.Action.SET); } + | { result.setAction(AlterPublication.Action.DROP); drop = true; } ) + targets=PublicationTargets(drop) { result.setTargets(targets); } + ) + { return result; } +} + +List SubscriptionPublications(): +{ List names = new ArrayList(); String name; } +{ + name=RelObjectName() { names.add(name); } + ( "," name=RelObjectName() { names.add(name); } )* + { return names; } +} + +CreateSubscription CreateSubscription(): +{ CreateSubscription result = new CreateSubscription(); String name; Token connection; List publications; List options; } +{ + ReplicationKeyword("SUBSCRIPTION") name=RelObjectName() { result.setName(name); } + ReplicationKeyword("CONNECTION") connection= { result.setConnection(new StringValue(connection.image)); } + ReplicationKeyword("PUBLICATION") publications=SubscriptionPublications() { result.setPublications(publications); } + [ LOOKAHEAD(2) options=SubscriptionOptions() { result.setOptions(options); } ] + { return result; } +} + +AlterSubscription AlterSubscription(): +{ AlterSubscription result = new AlterSubscription(); String name; Token connection; List publications; List options; } +{ + ReplicationKeyword("SUBSCRIPTION") name=RelObjectName() { result.setName(name); } + ( + LOOKAHEAD({ isKeywordAhead("CONNECTION") }) ReplicationKeyword("CONNECTION") + connection= { result.setConnection(new StringValue(connection.image)); result.setAction(AlterSubscription.Action.CONNECTION); } + | LOOKAHEAD({ isKeywordAhead("OWNER") }) ReplicationKeyword("OWNER") + name=RelObjectName() { result.setNewName(name); result.setAction(AlterSubscription.Action.OWNER); } + | name=RelObjectName() { result.setNewName(name); result.setAction(AlterSubscription.Action.RENAME); } + | { result.setAction(AlterSubscription.Action.ENABLE); } + | { result.setAction(AlterSubscription.Action.DISABLE); } + | options=SubscriptionOptions() { + requireReplicationSyntax(options.size() == 1 && options.get(0).getKind() == SubscriptionOption.Kind.LSN + && options.get(0).getValue() != null, "SKIP requires one lsn = value option"); + result.setOptions(options); result.setAction(AlterSubscription.Action.SKIP); + } + | LOOKAHEAD( "(") options=SubscriptionOptions() { result.setOptions(options); result.setAction(AlterSubscription.Action.SET_OPTIONS); } + | ReplicationKeyword("PUBLICATION") { result.setAction(AlterSubscription.Action.REFRESH_PUBLICATION); } + [ LOOKAHEAD(2) options=SubscriptionOptions() { result.setOptions(options); } ] + | ( { result.setAction(AlterSubscription.Action.SET_PUBLICATION); } + | { result.setAction(AlterSubscription.Action.ADD_PUBLICATION); } + | { result.setAction(AlterSubscription.Action.DROP_PUBLICATION); } ) + ReplicationKeyword("PUBLICATION") publications=SubscriptionPublications() { result.setPublications(publications); } + [ LOOKAHEAD(2) options=SubscriptionOptions() { result.setOptions(options); } ] + ) + { return result; } +} + CreatePolicy CreatePolicy() #CreatePolicy: { CreatePolicy createPolicy = new CreatePolicy(); String policyName; Table table; + String policyMode = null; Token commandToken = null; String roleName; Expression usingExpr = null; @@ -13187,6 +19054,12 @@ CreatePolicy CreatePolicy() #CreatePolicy: policyName=RelObjectName() { createPolicy.setPolicyName(policyName); } table=Table() { createPolicy.setTable(table); } + [ + LOOKAHEAD({ isKeywordAhead("PERMISSIVE") || isKeywordAhead("RESTRICTIVE") }) + policyMode=RelObjectName() + { createPolicy.setPolicyMode(PolicyMode.from(policyMode)); } + ] + [ ( commandToken= | commandToken= @@ -13194,7 +19067,7 @@ CreatePolicy CreatePolicy() #CreatePolicy: | commandToken= | commandToken= ) - { createPolicy.setCommand(commandToken.image); } + { createPolicy.setPolicyCommand(PolicyCommand.from(commandToken.image)); } ] [ @@ -13268,7 +19141,7 @@ List captureFunctionBody() { tokens.add(tok.image); } foundEnd |= (tok.kind == K_END) - || ( tok.image.trim().startsWith("$$") && tok.image.trim().endsWith("$$")) ; + || (tok.kind == S_CHAR_LITERAL && StringValue.getDollarQuoteDelimiter(tok.image) != null); tok = getNextToken(); } diff --git a/src/site/sphinx/contribution.rst b/src/site/sphinx/contribution.rst index 0114af33dd..41c327fbb4 100644 --- a/src/site/sphinx/contribution.rst +++ b/src/site/sphinx/contribution.rst @@ -87,10 +87,11 @@ The JSQLParser is generated by ``JavaCC`` based on the provided Grammar. The Gra .. code-block:: text :caption: JMH performance results - Benchmark (version) Mode Cnt Score Error Units - JSQLParserBenchmark.parseSQLStatements latest avgt 30 83.504 ± 1.557 ms/op - JSQLParserBenchmark.parseSQLStatements 5.2 avgt 30 400.876 ± 8.291 ms/op - JSQLParserBenchmark.parseSQLStatements 5.1 avgt 30 85.731 ± 1.288 ms/op + Benchmark (version) Mode Cnt Score Error Units + JSQLParserBenchmark.parseSQLStatements latest avgt 15 32.525 ± 0.413 ms/op + JSQLParserBenchmark.parseSQLStatements 5.3 avgt 15 983.459 ± 8.197 ms/op + JSQLParserBenchmark.parseSQLStatements 5.1 avgt 15 319.601 ± 4.081 ms/op + 8) Create your `GitHub Pull Request `_ @@ -174,4 +175,4 @@ Please consider using `Conventional Commits` and structure your commit message a * - **revert** - reverts one or many previous commits -Please visit `Better Programming `_ for more information and guidance. \ No newline at end of file +Please visit `Better Programming `_ for more information and guidance. diff --git a/src/site/sphinx/index.rst b/src/site/sphinx/index.rst index 9c5b6fbf77..283bc5c4d7 100644 --- a/src/site/sphinx/index.rst +++ b/src/site/sphinx/index.rst @@ -22,104 +22,231 @@ Java SQL Parser Library keywords changelog -.. image:: https://img.shields.io/github/release/JSQLParser/JSqlParser?include_prereleases=&sort=semver&color=blue - :alt: GitGub Release Badge - :target: https://github.com/JSQLParser/JSqlParser/releases - -.. image:: https://img.shields.io/github/issues/JSQLParser/JSqlParser - :alt: GitGub Issues Badge - :target: https://github.com/JSQLParser/JSqlParser/issues - -.. image:: https://badgen.net/maven/v/maven-central/com.github.jsqlparser/jsqlparser - :alt: Maven Badge - :target: https://mvnrepository.com/artifact/com.github.jsqlparser/jsqlparser - .. image:: https://github.com/JSQLParser/JSqlParser/actions/workflows/ci.yml/badge.svg :alt: CI Status :target: https://github.com/JSQLParser/JSqlParser/actions/workflows/ci.yml .. image:: https://coveralls.io/repos/JSQLParser/JSqlParser/badge.svg?branch=master :alt: Coverage Status - :target: https://coveralls.io/github/com.github.jsqlparser/jsqlparser?branch=master + :target: https://coveralls.io/r/JSQLParser/JSqlParser?branch=master .. image:: https://app.codacy.com/project/badge/Grade/6f9a2d7eb98f45969749e101322634a1 :alt: Codacy Status - :target: https://app.codacy.com/gh/com.github.jsqlparser/jsqlparser/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_grade + :target: https://www.codacy.com/gh/JSQLParser/JSqlParser/dashboard + +.. image:: https://img.shields.io/maven-central/v/com.manticore-projects.jsqlformatter/jsqlparser.svg?label=manticore%20build&color=ff420e + :alt: Manticore Build + :target: https://central.sonatype.com/artifact/com.manticore-projects.jsqlformatter/jsqlparser + +.. image:: https://img.shields.io/maven-central/v/com.github.jsqlparser/jsqlparser.svg?label=upstream%20release + :alt: Maven Central + :target: https://central.sonatype.com/artifact/com.github.jsqlparser/jsqlparser .. image:: https://www.javadoc.io/badge/com.github.jsqlparser/jsqlparser.svg :alt: Java Docs :target: https://javadoc.io/doc/com.github.jsqlparser/jsqlparser/latest/index.html +.. image:: https://img.shields.io/github/issues/JSQLParser/JSqlParser + :alt: GitHub Issues Badge + :target: https://github.com/JSQLParser/JSqlParser/issues -A huge thank you to our sponsor, `Starlake.ai `_ who simplifies data ingestion, transformation, and orchestration, enabling faster delivery of high-quality data. Starlake has been instrumental in providing Piped SQL and numerous test cases for BigQuery, Redshift, DataBricks, and DuckDB. Show your support for ongoing development by visiting Starlake.ai and giving us a star! +.. image:: https://img.shields.io/github/stars/JSQLParser/JSqlParser?style=flat&label=stars + :alt: GitHub Stars + :target: https://github.com/JSQLParser/JSqlParser/stargazers .. sidebar:: Java API Website .. image:: _images/JavaAST.png -**JSQLParser** is a SQL statement parser built from JavaCC. It translates SQLs in a traversable hierarchy of Java classes. -Since the 5.0 release JSQLParser depends on Java 11 and has introduced new Visitors. Please see the :ref:`Migration to 5.0` guide. +**Turn any SQL statement into a traversable tree of Java objects — and back again.** + +**JSQLParser** is an RDBMS-agnostic SQL parser for the JVM, built with JavaCC: one grammar, all major dialects, no native extensions. Give it SQL, get an AST you can walk, rewrite and print back out. Latest stable release: |JSQLPARSER_STABLE_VERSION_LINK| Development version: |JSQLPARSER_SNAPSHOT_VERSION_LINK| +.. note:: + + Since the 5.0 release JSQLParser requires Java 11 and has introduced new Visitors. Please see the :ref:`Migration to 5.0` guide. + + +****************************** +What it does +****************************** + +.. code-block:: sql + + SELECT 1 FROM dual WHERE a = b +.. raw:: html +
+
+    SQL Text
+     └─Statements: statement.select.PlainSelect
+        ├─selectItems: statement.select.SelectItem
+        │  └─LongValue: 1
+        ├─Table: dual
+        └─where: expression.operators.relational.EqualsTo
+           ├─Column: a
+           └─Column: b
+    
+
-.. code-block:: SQL - :caption: Sample SQL Statement +.. code-block:: java - SELECT /*+ PARALLEL */ - cfe.id_collateral_ref.nextval - , id_collateral - FROM ( SELECT DISTINCT - a.id_collateral - FROM cfe.collateral a - LEFT JOIN cfe.collateral_ref b - ON a.id_collateral = b.id_collateral - WHERE b.id_collateral_ref IS NULL ) - ; + PlainSelect select = (PlainSelect) CCJSqlParserUtil.parse("select 1 from dual where a=b"); + + Table table = (Table) select.getFromItem(); + Assertions.assertEquals("dual", table.getName()); + +The tree is traversable with the Visitor pattern, and the same object model works in reverse: build statements from Java with a fluent API and render them as SQL text. See :doc:`usage`. ****************************** -SQL Dialects +Install ****************************** -**JSqlParser** is RDBMS agnostic and provides support for many dialects such as: +Use the **Manticore builds**. They are immutable, versioned releases cut continuously from the current development line, and carry the grammar and performance work described below. The upstream ``com.github.jsqlparser`` release is considerably older. + +.. code-block:: xml -* Oracle Database -* MS SqlServer -* MySQL and MariaDB -* PostgreSQL -* H2 -* DuckDB -* Google BigQuery -* Amazon Redshift -* DataBricks -* Snowflake + + com.manticore-projects.jsqlformatter + jsqlparser + [5.3.218,) + -******************************* -Features -******************************* +Upstream coordinates, snapshots and Gradle are on the :ref:`Add JSQLParser to your Project` page. -* Comprehensive support for statements: - - QUERY: ``SELECT ...`` - - DML: ``INSERT ... INTO ...`` ``UPDATE ...`` ``MERGE ... INTO ...`` ``DELETE ... FROM ...`` - - DDL: ``CREATE ...`` ``ALTER ...`` ``DROP ...`` -* Nested Expressions (e.g. Sub-Selects) -* ``WITH`` clauses -* Old Oracle ``JOIN (+)`` -* PostgreSQL implicit ``CAST ::`` -* SQL Parameters (e.g. ``?`` or ``:parameter``) -* Arrays vs. T-SQL Squared Bracket Quotes -* Fluent API to create SQL Statements from java Code -* Statement De-Parser to write SQL from Java Objects -* Piped SQL (also known as FROM SQL) +****************************** +Performance +****************************** +**11× faster than 5.3**, and the fastest parser on real-world SQL of any of the parsers tested, in any language — 19× ahead of ``sqlglot[c]`` on JSQLParser's own ``SELECT`` test suite. +.. code-block:: text + Benchmark (version) Mode Cnt Score Error Units + JSQLParserBenchmark.parseSQLStatements latest avgt 15 7.602 ± 0.135 ms/op + JSQLParserBenchmark.parseSQLStatements 5.3 avgt 15 84.687 ± 3.321 ms/op +Methodology and the full cross-parser comparison against SQLGlot, ``sqlglot[c]`` and polyglot-sql: `jsqlparser-bench `_. + + +****************************** +What it parses +****************************** +One grammar covers the SQL standard plus all major RDBMS. Missing syntax gets added on demand — `open an issue `_. + +``BigQuery`` · ``Snowflake`` · ``DuckDB`` · ``Redshift`` · ``Oracle`` · ``MS SQL Server`` · ``Sybase`` · ``PostgreSQL`` · ``MySQL`` · ``MariaDB`` · ``DB2`` · ``H2`` · ``HSQLDB`` · ``Derby`` · ``SQLite`` + +.. list-table:: + :header-rows: 1 + :widths: 25 75 + + * - + - Statements + * - **Queries** + - ``SELECT`` · ``WITH …`` · Piped SQL + * - **DML** + - ``INSERT`` · ``UPDATE`` · ``UPSERT`` · ``MERGE`` · ``DELETE`` · ``TRUNCATE TABLE`` + * - **DDL** + - ``CREATE …`` · ``ALTER …`` · ``DROP …`` + * - **PostgreSQL RLS** + - ``CREATE POLICY`` · ``ALTER TABLE … ENABLE``/``DISABLE``/``FORCE``/``NO FORCE ROW LEVEL SECURITY`` + * - **Salesforce SOQL** + - ``INCLUDES`` · ``EXCLUDES`` + +Beyond statement shapes: nested sub-selects, bind parameters (``?``, ``:name``), window and analytic functions, Oracle hints, the old Oracle ``JOIN (+)``, PostgreSQL implicit ``CAST ::``, and the T-SQL square-bracket versus array-literal ambiguity. The complete reference is on the :doc:`syntax_stable` page; the gaps are on :doc:`unsupported`. + + +****************************** +Statement classification +****************************** + +Any parsed statement can say what it actually does — no second parse, no visitor to write: + +.. code-block:: java + + StatementFeatures features = CCJSqlParserUtil.parse(sqlStr).getFeatures(); + + // safeguard a read-only client before anything reaches the database + if (connection.isReadOnly() && features.mayModifyData()) { + throw new SQLException("rejected: " + features.getUnresolvedReferences()); + } + + // dispatch correctly + if (features.returnsResultSet()) { statement.executeQuery(sqlStr); } + else { statement.executeUpdate(sqlStr); } + +This is not ``sqlStr.startsWith("SELECT")`` with extra steps: ``RETURNING`` turns DML into a row source, a data-modifying CTE hides a ``DELETE`` inside a ``SELECT``, and ``INSERT INTO x SELECT ..`` contains a query but returns nothing. See :ref:`Classify a Statement`. + + +****************************** +Piped SQL +****************************** + +Support is progressing for Piped SQL, which writes queries in the order they actually execute rather than the order SQL historically demanded. + +.. code-block:: sql + + FROM Produce + |> WHERE + item != 'bananas' + AND category IN ('fruit', 'nut') + |> AGGREGATE COUNT(*) AS num_items, SUM(sales) AS total_sales + GROUP BY item + |> ORDER BY item DESC; + +Background reading: the `Google research paper `_, `BigQuery pipe syntax `_ and `DuckDB FROM-first syntax `_. + + +****************************** +Java version +****************************** + +.. list-table:: + :header-rows: 1 + :widths: 25 20 55 + + * - JSQLParser + - Runtime + - Notes + * - 4.9 + - JDK 8 + - last JDK 8 compatible release + * - 5.0 and later + - JDK 11 + - breaking changes to the AST Visitors, see :ref:`Migration to 5.0` + * - 5.1 and later + - JDK 11 + - building requires a **JDK 17 toolchain** (plugin requirement) + * - 5.4 and later + - JDK 11 + - parser generated with **JavaCC 8** + + +****************************** +Sister projects +****************************** + +- `JSQLFormatter `_ — pretty-printing and formatting of SQL text +- `JSQLTranspiler `_ — dialect-specific rewriting, column resolution and lineage, by `Starlake.ai `_ + + +****************************** +Sponsor +****************************** + +A huge thank you to our sponsor, `Starlake.ai `_, who simplify data ingestion, transformation and orchestration, enabling faster delivery of high-quality data. Starlake has been instrumental in providing Piped SQL support and numerous test cases for BigQuery, Redshift, Databricks and DuckDB. Show your support for ongoing development by visiting Starlake.ai and giving them a star. + + +****************************** +License +****************************** +Dual licensed under **LGPL 2.1** or the **Apache License, Version 2.0**. Take your pick. \ No newline at end of file diff --git a/src/site/sphinx/syntax_snapshot.rst b/src/site/sphinx/syntax_snapshot.rst deleted file mode 100644 index 76af0c6bf8..0000000000 --- a/src/site/sphinx/syntax_snapshot.rst +++ /dev/null @@ -1,20923 +0,0 @@ - -********************************************************************* -SQL Syntax |JSQLPARSER_SNAPSHOT_VERSION| -********************************************************************* - -The EBNF and Railroad Diagrams for |JSQLPARSER_SNAPSHOT_VERSION|. - - -====================================================================================================================== -NonReservedWord -====================================================================================================================== - - -.. raw:: html - - - - - - ACTION - - ACTIVE - - ADD - - ADVANCE - - ADVISE - - AGAINST - - AGGREGATE - - ALGORITHM - - ALIGN - - ALTER - - ALWAYS - - ANALYZE - - APPEND_ONLY - - APPLY - - APPROXIMATE - - ARCHIVE - - ARRAY - - ASYMMETRIC - - AT - - ASC - - AUTHORIZATION - - AUTO - - AUTO_INCREMENT - - AZURE - - BASE64 - - BEFORE - - BEGIN - - BERNOULLI - - BINARY - - BIT - - BLOBSTORAGE - - BLOCK - - BOOLEAN - - BREADTH - - BRANCH - - BROWSE - - BY - - BYTES - - CACHE - - BUFFERS - - BYTE - - CALL - - CASCADE - - CASE - - CAST - - CERTIFICATE - - CHARACTER - - CHANGE - - CHANGES - - CHECKPOINT - - CHAR - - CLOSE - - CLOUD - - COALESCE - - COLLATE - - COLUMN - - COLUMNS - - COMMIT - - COMMENT - - COMMENTS - - CONFLICT - - CONSTRAINTS - - CONVERT - - CORRESPONDING - - COSTS - - COUNT - - CREATED - - CYCLE - - DATABASE - - DATA - - DECLARE - - DBA_RECYCLEBIN - - DEFAULTS - - DEPTH - - DEFERRABLE - - DELAYED - - DELETE - - DELIMIT - - DELIMITER - - DESC - - DESCRIBE - - DISABLE - - DISCARD - - DISCONNECT - - DIV - - DDL - - DML - - DO - - DOMAIN - - DRIVER - - DROP - - DUMP - - DUPLICATE - - ELEMENTS - - EMIT - - ENABLE - - ENCODING - - ENCRYPTION - - END - - ENFORCED - - ENGINE - - ERROR - - ESCAPE - - EXA - - EXCHANGE - - EXCLUDE - - EXCLUDING - - EXCLUSIVE - - EXEC - - EXECUTE - - EXPLAIN - - EXPLICIT - - EXTENDED - - EXTRACT - - EXPORT - - K_ISOLATION - FILTER - - FIRST - - FLUSH - - FOLLOWING - - FORMAT - - FULLTEXT - - FUNCTION - - GRANT - - GROUP_CONCAT - - GUARD - - HASH - - HIGH - - HIGH_PRIORITY - - HISTORY - - HOPPING - - IDENTIFIED - - IDENTITY - - INCLUDE - - INCLUDE_NULL_VALUES - - INCLUDING - - INCREMENT - - INDEX - - INFORMATION - - INSERT - - INTERLEAVE - - INTERPRET - - INVALIDATE - - INVERSE - - INVISIBLE - - ISNULL - - JDBC - - JSON - - JSON_OBJECT - - JSON_OBJECTAGG - - JSON_ARRAY - - JSON_ARRAYAGG - - KEEP - - KEY_BLOCK_SIZE - - KEY - - KEYS - - KILL - - FN - - LAST - - LEADING - - LESS - - LEVEL - - LOCAL - - LOCK - - LOCKED - - LINK - - LOG - - LOOP - - LOW - - LOW_PRIORITY - - LTRIM - - MATCH - - MATCH_ANY - - MATCH_ALL - - MATCH_PHRASE - - MATCH_PHRASE_PREFIX - - MATCH_REGEXP - - MATCHED - - MATERIALIZED - - MAX - - MAXVALUE - - MEMBER - - MERGE - - MIN - - MINVALUE - - MODE - - MODIFY - - MOVEMENT - - NAMES - - NAME - - NEVER - - NEXT - - K_NEXTVAL - NO - - NOCACHE - - NOKEEP - - NOLOCK - - NOMAXVALUE - - NOMINVALUE - - NONE - - NOORDER - - NOTHING - - NOTNULL - - NOVALIDATE - - NULLS - - NOWAIT - - OF - - OFF - - OPEN - - ORA - - ORDINALITY - - OVER - - OVERFLOW - - OVERLAPS - - OVERRIDING - - OVERWRITE - - PADDING - - PARALLEL - - PARENT - - PARSER - - PARTITION - - PARTITIONING - - PATH - - PERCENT - - PLACING - - PLAN - - PLUS - - PRECEDING - - PRIMARY - - POLICY - - PURGE - - QUERY - - QUICK - - QUIESCE - - RANGE - - RAW - - READ - - REBUILD - - RECYCLEBIN - - RECURSIVE - - REFERENCES - - REFRESH - - REGEXP - - REJECT - - RESPECT - - RLIKE - - REGEXP_LIKE - - REGISTER - - REMOTE - - REMOVE - - RENAME - - REORGANIZE - - REPAIR - - REPEATABLE - - REPLACE - - RESET - - RESTART - - RESUMABLE - - RESUME - - RESTRICT - - RESTRICTED - - RETURN - - ROLLBACK - - ROLLUP - - ROOT - - ROW - - ROWS - - RTRIM - - SAFE_CAST - - SAFE_CONVERT - - SAVEPOINT - - SCHEMA - - SEARCH - - SECURE - - SECURITY - - SEED - - SEQUENCE - - SEPARATOR - - SESSION - - SETS - - SHOW - - SHUTDOWN - - SHARE - - SIBLINGS - - SIMILAR - - SIZE - - SKIP - - SPATIAL - - STORED - - STREAM - - STRICT - - STRING - - STRUCT - - SUMMARIZE - - SUSPEND - - SWITCH - - SYMMETRIC - - SYNONYM - - SYSTEM - - SYSTEM_TIME - - SYSTEM_TIMESTAMP - - SYSTEM_VERSION - - TABLE - - TABLESPACE - - TRIGGER - - THEN - - TEMP - - K_TEXT_LITERAL - TEMPORARY - - THAN - - K_TIME_KEY_EXPR - TIMEOUT - - TO - - TRIM - - TRUNCATE - - TRY_CAST - - TRY_CONVERT - - TUMBLING - - TYPE - - UNLIMITED - - UNLOGGED - - UPDATE - - UPSERT - - UNQIESCE - - USER - - SIGNED - - K_STRING_FUNCTION_NAME - UNSIGNED - - VALIDATE - - VALIDATION - - VERBOSE - - VERSION - - VIEW - - VISIBLE - - VOLATILE - - CONCURRENTLY - - WAIT - - WITH TIES - - WITHIN - - WITHOUT - - WITHOUT_ARRAY_WRAPPER - - WORK - - XML - - XMLAGG - - XMLDATA - - XMLSCHEMA - - XMLTEXT - - XSINIL - - YAML - - YES - - ZONE - - -
- - -
         ::= 'ACTION'
-
           | 'ACTIVE'
-
           | 'ADD'
-
           | 'ADVANCE'
-
           | 'ADVISE'
-
           | 'AGAINST'
-
           | 'AGGREGATE'
-
           | 'ALGORITHM'
-
           | 'ALIGN'
-
           | 'ALTER'
-
           | 'ALWAYS'
-
           | 'ANALYZE'
-
           | 'APPEND_ONLY'
-
           | 'APPLY'
-
           | 'APPROXIMATE'
-
           | 'ARCHIVE'
-
           | 'ARRAY'
-
           | 'ASYMMETRIC'
-
           | 'AT'
-
           | 'ASC'
-
           | 'AUTHORIZATION'
-
           | 'AUTO'
-
           | 'AUTO_INCREMENT'
-
           | 'AZURE'
-
           | 'BASE64'
-
           | 'BEFORE'
-
           | 'BEGIN'
-
           | 'BERNOULLI'
-
           | 'BINARY'
-
           | 'BIT'
-
           | 'BLOBSTORAGE'
-
           | 'BLOCK'
-
           | 'BOOLEAN'
-
           | 'BREADTH'
-
           | 'BRANCH'
-
           | 'BROWSE'
-
           | 'BY'
-
           | 'BYTES'
-
           | 'CACHE'
-
           | 'BUFFERS'
-
           | 'BYTE'
-
           | 'CALL'
-
           | 'CASCADE'
-
           | 'CASE'
-
           | 'CAST'
-
           | 'CERTIFICATE'
-
           | 'CHARACTER'
-
           | 'CHANGE'
-
           | 'CHANGES'
-
           | 'CHECKPOINT'
-
           | 'CHAR'
-
           | 'CLOSE'
-
           | 'CLOUD'
-
           | 'COALESCE'
-
           | 'COLLATE'
-
           | 'COLUMN'
-
           | 'COLUMNS'
-
           | 'COMMIT'
-
           | 'COMMENT'
-
           | 'COMMENTS'
-
           | 'CONFLICT'
-
           | 'CONSTRAINTS'
-
           | 'CONVERT'
-
           | 'CORRESPONDING'
-
           | 'COSTS'
-
           | 'COUNT'
-
           | 'CREATED'
-
           | 'CYCLE'
-
           | 'DATABASE'
-
           | 'DATA'
-
           | 'DECLARE'
-
           | 'DBA_RECYCLEBIN'
-
           | 'DEFAULTS'
-
           | 'DEPTH'
-
           | 'DEFERRABLE'
-
           | 'DELAYED'
-
           | 'DELETE'
-
           | 'DELIMIT'
-
           | 'DELIMITER'
-
           | 'DESC'
-
           | 'DESCRIBE'
-
           | 'DISABLE'
-
           | 'DISCARD'
-
           | 'DISCONNECT'
-
           | 'DIV'
-
           | 'DDL'
-
           | 'DML'
-
           | 'DO'
-
           | 'DOMAIN'
-
           | 'DRIVER'
-
           | 'DROP'
-
           | 'DUMP'
-
           | 'DUPLICATE'
-
           | 'ELEMENTS'
-
           | 'EMIT'
-
           | 'ENABLE'
-
           | 'ENCODING'
-
           | 'ENCRYPTION'
-
           | 'END'
-
           | 'ENFORCED'
-
           | 'ENGINE'
-
           | 'ERROR'
-
           | 'ESCAPE'
-
           | 'EXA'
-
           | 'EXCHANGE'
-
           | 'EXCLUDE'
-
           | 'EXCLUDING'
-
           | 'EXCLUSIVE'
-
           | 'EXEC'
-
           | 'EXECUTE'
-
           | 'EXPLAIN'
-
           | 'EXPLICIT'
-
           | 'EXTENDED'
-
           | 'EXTRACT'
-
           | 'EXPORT'
-
           | K_ISOLATION
-
           | 'FILTER'
-
           | 'FIRST'
-
           | 'FLUSH'
-
           | 'FOLLOWING'
-
           | 'FORMAT'
-
           | 'FULLTEXT'
-
           | 'FUNCTION'
-
           | 'GRANT'
-
           | 'GROUP_CONCAT'
-
           | 'GUARD'
-
           | 'HASH'
-
           | 'HIGH'
-
           | 'HIGH_PRIORITY'
-
           | 'HISTORY'
-
           | 'HOPPING'
-
           | 'IDENTIFIED'
-
           | 'IDENTITY'
-
           | 'INCLUDE'
-
           | 'INCLUDE_NULL_VALUES'
-
           | 'INCLUDING'
-
           | 'INCREMENT'
-
           | 'INDEX'
-
           | 'INFORMATION'
-
           | 'INSERT'
-
           | 'INTERLEAVE'
-
           | 'INTERPRET'
-
           | 'INVALIDATE'
-
           | 'INVERSE'
-
           | 'INVISIBLE'
-
           | 'ISNULL'
-
           | 'JDBC'
-
           | 'JSON'
-
           | 'JSON_OBJECT'
-
           | 'JSON_OBJECTAGG'
-
           | 'JSON_ARRAY'
-
           | 'JSON_ARRAYAGG'
-
           | 'KEEP'
-
           | 'KEY_BLOCK_SIZE'
-
           | 'KEY'
-
           | 'KEYS'
-
           | 'KILL'
-
           | 'FN'
-
           | 'LAST'
-
           | 'LEADING'
-
           | 'LESS'
-
           | 'LEVEL'
-
           | 'LOCAL'
-
           | 'LOCK'
-
           | 'LOCKED'
-
           | 'LINK'
-
           | 'LOG'
-
           | 'LOOP'
-
           | 'LOW'
-
           | 'LOW_PRIORITY'
-
           | 'LTRIM'
-
           | 'MATCH'
-
           | 'MATCH_ANY'
-
           | 'MATCH_ALL'
-
           | 'MATCH_PHRASE'
-
           | 'MATCH_PHRASE_PREFIX'
-
           | 'MATCH_REGEXP'
-
           | 'MATCHED'
-
           | 'MATERIALIZED'
-
           | 'MAX'
-
           | 'MAXVALUE'
-
           | 'MEMBER'
-
           | 'MERGE'
-
           | 'MIN'
-
           | 'MINVALUE'
-
           | 'MODE'
-
           | 'MODIFY'
-
           | 'MOVEMENT'
-
           | 'NAMES'
-
           | 'NAME'
-
           | 'NEVER'
-
           | 'NEXT'
-
           | K_NEXTVAL
-
           | 'NO'
-
           | 'NOCACHE'
-
           | 'NOKEEP'
-
           | 'NOLOCK'
-
           | 'NOMAXVALUE'
-
           | 'NOMINVALUE'
-
           | 'NONE'
-
           | 'NOORDER'
-
           | 'NOTHING'
-
           | 'NOTNULL'
-
           | 'NOVALIDATE'
-
           | 'NULLS'
-
           | 'NOWAIT'
-
           | 'OF'
-
           | 'OFF'
-
           | 'OPEN'
-
           | 'ORA'
-
           | 'ORDINALITY'
-
           | 'OVER'
-
           | 'OVERFLOW'
-
           | 'OVERLAPS'
-
           | 'OVERRIDING'
-
           | 'OVERWRITE'
-
           | 'PADDING'
-
           | 'PARALLEL'
-
           | 'PARENT'
-
           | 'PARSER'
-
           | 'PARTITION'
-
           | 'PARTITIONING'
-
           | 'PATH'
-
           | 'PERCENT'
-
           | 'PLACING'
-
           | 'PLAN'
-
           | 'PLUS'
-
           | 'PRECEDING'
-
           | 'PRIMARY'
-
           | 'POLICY'
-
           | 'PURGE'
-
           | 'QUERY'
-
           | 'QUICK'
-
           | 'QUIESCE'
-
           | 'RANGE'
-
           | 'RAW'
-
           | 'READ'
-
           | 'REBUILD'
-
           | 'RECYCLEBIN'
-
           | 'RECURSIVE'
-
           | 'REFERENCES'
-
           | 'REFRESH'
-
           | 'REGEXP'
-
           | 'REJECT'
-
           | 'RESPECT'
-
           | 'RLIKE'
-
           | 'REGEXP_LIKE'
-
           | 'REGISTER'
-
           | 'REMOTE'
-
           | 'REMOVE'
-
           | 'RENAME'
-
           | 'REORGANIZE'
-
           | 'REPAIR'
-
           | 'REPEATABLE'
-
           | 'REPLACE'
-
           | 'RESET'
-
           | 'RESTART'
-
           | 'RESUMABLE'
-
           | 'RESUME'
-
           | 'RESTRICT'
-
           | 'RESTRICTED'
-
           | 'RETURN'
-
           | 'ROLLBACK'
-
           | 'ROLLUP'
-
           | 'ROOT'
-
           | 'ROW'
-
           | 'ROWS'
-
           | 'RTRIM'
-
           | 'SAFE_CAST'
-
           | 'SAFE_CONVERT'
-
           | 'SAVEPOINT'
-
           | 'SCHEMA'
-
           | 'SEARCH'
-
           | 'SECURE'
-
           | 'SECURITY'
-
           | 'SEED'
-
           | 'SEQUENCE'
-
           | 'SEPARATOR'
-
           | 'SESSION'
-
           | 'SETS'
-
           | 'SHOW'
-
           | 'SHUTDOWN'
-
           | 'SHARE'
-
           | 'SIBLINGS'
-
           | 'SIMILAR'
-
           | 'SIZE'
-
           | 'SKIP'
-
           | 'SPATIAL'
-
           | 'STORED'
-
           | 'STREAM'
-
           | 'STRICT'
-
           | 'STRING'
-
           | 'STRUCT'
-
           | 'SUMMARIZE'
-
           | 'SUSPEND'
-
           | 'SWITCH'
-
           | 'SYMMETRIC'
-
           | 'SYNONYM'
-
           | 'SYSTEM'
-
           | 'SYSTEM_TIME'
-
           | 'SYSTEM_TIMESTAMP'
-
           | 'SYSTEM_VERSION'
-
           | 'TABLE'
-
           | 'TABLESPACE'
-
           | 'TRIGGER'
-
           | 'THEN'
-
           | 'TEMP'
-
           | K_TEXT_LITERAL
-
           | 'TEMPORARY'
-
           | 'THAN'
-
           | K_TIME_KEY_EXPR
-
           | 'TIMEOUT'
-
           | 'TO'
-
           | 'TRIM'
-
           | 'TRUNCATE'
-
           | 'TRY_CAST'
-
           | 'TRY_CONVERT'
-
           | 'TUMBLING'
-
           | 'TYPE'
-
           | 'UNLIMITED'
-
           | 'UNLOGGED'
-
           | 'UPDATE'
-
           | 'UPSERT'
-
           | 'UNQIESCE'
-
           | 'USER'
-
           | 'SIGNED'
-
           | K_STRING_FUNCTION_NAME
-
           | 'UNSIGNED'
-
           | 'VALIDATE'
-
           | 'VALIDATION'
-
           | 'VERBOSE'
-
           | 'VERSION'
-
           | 'VIEW'
-
           | 'VISIBLE'
-
           | 'VOLATILE'
-
           | 'CONCURRENTLY'
-
           | 'WAIT'
-
           | 'WITH TIES'
-
           | 'WITHIN'
-
           | 'WITHOUT'
-
           | 'WITHOUT_ARRAY_WRAPPER'
-
           | 'WORK'
-
           | 'XML'
-
           | 'XMLAGG'
-
           | 'XMLDATA'
-
           | 'XMLSCHEMA'
-
           | 'XMLTEXT'
-
           | 'XSINIL'
-
           | 'YAML'
-
           | 'YES'
-
           | 'ZONE'
-
- Referenced by: -
- - -====================================================================================================================== -KeywordOrIdentifier -====================================================================================================================== - - -.. raw:: html - - - - - - S_IDENTIFIER - - S_QUOTED_IDENTIFIER - NAME - - NEXT - - VALUE - - PUBLIC - - STRING - - DATA - - -
- - -
         ::= S_IDENTIFIER
-
           | S_QUOTED_IDENTIFIER
-
           | 'NAME'
-
           | 'NEXT'
-
           | 'VALUE'
-
           | 'PUBLIC'
-
           | 'STRING'
-
           | 'DATA'
-
- - -====================================================================================================================== -Statement -====================================================================================================================== - - -.. raw:: html - - - - - - IF - - Condition - - SingleStatement - - Block - - ST_SEMICOLON - ELSE - - SingleStatement - - Block - - ST_SEMICOLON - - SingleStatement - - Block - - ST_SEMICOLON - - EOF - - UnsupportedStatement - -
- - -
         ::= 'IF' Condition ( SingleStatement | Block ) ST_SEMICOLON? ( 'ELSE' ( SingleStatement | Block ) ST_SEMICOLON? )?
-
           | ( SingleStatement | Block ) ( ST_SEMICOLON | EOF )
-
           | UnsupportedStatement
-
- Not referenced by any. -
- - -====================================================================================================================== -SingleStatement -====================================================================================================================== - - -.. raw:: html - - - - - - WithList - - SelectWithWithItems - - InsertWithWithItems - - UpdateWithWithItems - - DeleteWithWithItems - - Merge - - Select - - TableStatement - - Upsert - - Alter - - RenameTableStatement - - Create - - Drop - - Analyze - - Truncate - - Execute - - Set - - Reset - - Show - - RefreshMaterializedView - - Use - - SavepointStatement - - RollbackStatement - COMMIT - - Comment - - Describe - - Explain - - Declare - - Grant - - PurgeStatement - - SessionStatement - - LockStatement - - Import - - Export - -
- - - -
           | Select
-
           | TableStatement
-
           | Upsert
-
           | Alter
-
           | RenameTableStatement
-
           | Create
-
           | Drop
-
           | Analyze
-
           | Truncate
-
           | Execute
-
           | Set
-
           | Reset
-
           | Show
-
           | RefreshMaterializedView
-
           | Use
-
           | SavepointStatement
-
           | RollbackStatement
-
           | 'COMMIT'
-
           | Comment
-
           | Describe
-
           | Explain
-
           | Declare
-
           | Grant
-
           | PurgeStatement
-
           | SessionStatement
-
           | LockStatement
-
           | Import
-
           | Export
-
- Referenced by: -
- - -====================================================================================================================== -Block -====================================================================================================================== - - -.. raw:: html - - - - - - BEGIN - - ST_SEMICOLON - - SingleStatement - - Block - - ST_SEMICOLON - END - - ST_SEMICOLON - -
- -
Block    ::= 'BEGIN' ST_SEMICOLON* ( ( SingleStatement | Block ) ST_SEMICOLON )+ 'END' ST_SEMICOLON?
-
- Referenced by: -
- - -====================================================================================================================== -Statements -====================================================================================================================== - - -.. raw:: html - - - - - - ST_SEMICOLON - IF - - Condition - - SingleStatement - - Block - - ST_SEMICOLON - ELSE - - SingleStatement - - Block - - ST_SEMICOLON - - SingleStatement - - Block - - ST_SEMICOLON - - EOF - - ST_SEMICOLON - IF - - Condition - - SingleStatement - - Block - - ST_SEMICOLON - ELSE - - SingleStatement - - Block - - ST_SEMICOLON - - UnsupportedStatement - - EOF - -
- - - -
- Not referenced by any. -
- - -====================================================================================================================== -LockStatement -====================================================================================================================== - - -.. raw:: html - - - - - - LOCK - - TABLE - - Table - IN - - ROW - - SHARE - - EXCLUSIVE - - SHARE - - ROW - - EXCLUSIVE - - UPDATE - - EXCLUSIVE - - MODE - - NOWAIT - - WAIT - - S_LONG - -
- - -
         ::= 'LOCK' 'TABLE' Table 'IN' ( 'ROW' ( 'SHARE' | 'EXCLUSIVE' ) | 'SHARE' ( 'ROW' 'EXCLUSIVE' | 'UPDATE' )? - | 'EXCLUSIVE' ) 'MODE' ( 'NOWAIT' | 'WAIT' S_LONG )?
-
- Referenced by: -
- - -====================================================================================================================== -LikeClause -====================================================================================================================== - - -.. raw:: html - - - - - - LIKE - - Table - ( - - ColumnSelectItemsList - ) - - INCLUDING - - EXCLUDING - - DEFAULTS - - INCLUDING - - EXCLUDING - - IDENTITY - - INCLUDING - - EXCLUDING - - COMMENTS - - -
- - -
         ::= 'LIKE' Table ( '(' ColumnSelectItemsList ')' )? ( ( 'INCLUDING' | 'EXCLUDING' ) 'DEFAULTS' )? ( ( 'INCLUDING' | 'EXCLUDING' - ) 'IDENTITY' )? ( ( 'INCLUDING' | 'EXCLUDING' ) 'COMMENTS' )?
-
- Referenced by: -
- - -====================================================================================================================== -Export -====================================================================================================================== - - -.. raw:: html - - - - - - EXPORT - - Table - - ParenthesedColumnList - - ParenthesedSelect - INTO - - ExportIntoItem - -
- - -
- Referenced by: -
- - -====================================================================================================================== -Import -====================================================================================================================== - - -.. raw:: html - - - - - - IMPORT - - INTO - - Table - - ParenthesedColumnList - - ImportColumns - FROM - - ImportFromItem - -
- -
Import   ::= 'IMPORT' ( 'INTO' ( Table ParenthesedColumnList? | ImportColumns ) )? 'FROM' ImportFromItem
-
- Referenced by: -
- - -====================================================================================================================== -SubImport -====================================================================================================================== - - -.. raw:: html - - - - - - ( - - IMPORT - - INTO - - ImportColumns - FROM - - ImportFromItem - ) - - -
- - -
         ::= '(' 'IMPORT' ( 'INTO' ImportColumns )? 'FROM' ImportFromItem ')'
-
- Referenced by: -
- - -====================================================================================================================== -ImportColumns -====================================================================================================================== - - -.. raw:: html - - - - - - ( - - ColumnDefinition - - LikeClause - , - - ) - - -
- - -
         ::= '(' ( ColumnDefinition | LikeClause ) ( ',' ( ColumnDefinition | LikeClause ) )* ')'
-
- Referenced by: -
- - -====================================================================================================================== -ExportIntoItem -====================================================================================================================== - - -.. raw:: html - - - - - - DBMSDestination - - FileDestination - - ScriptSourceDestination - - ErrorClause - -
- - - -
- Referenced by: -
- - -====================================================================================================================== -ImportFromItem -====================================================================================================================== - - -.. raw:: html - - - - - - DBMSSource - - FileSource - - ScriptSourceDestination - - ErrorClause - -
- - - -
- Referenced by: -
- - -====================================================================================================================== -DBMSDestination -====================================================================================================================== - - -.. raw:: html - - - - - - DBMSType - - ConnectionDefinition - TABLE - - Table - - ParenthesedColumnList - - DBMSTableDestinationOptionList - - ImportExportStatement - -
- - - -
- Referenced by: -
- - -====================================================================================================================== -DBMSTableDestinationOption -====================================================================================================================== - - -.. raw:: html - - - - - - REPLACE - - TRUNCATE - - CREATED - - BY - - S_CHAR_LITERAL - -
- - -
         ::= 'REPLACE'
-
           | 'TRUNCATE'
-
           | 'CREATED' 'BY' S_CHAR_LITERAL
-
- Referenced by: -
- - -====================================================================================================================== -DBMSTableDestinationOptionList -====================================================================================================================== - - -.. raw:: html - - - - - - DBMSTableDestinationOption - -
- - -
         ::= DBMSTableDestinationOption+
-
- Referenced by: -
- - -====================================================================================================================== -DBMSSource -====================================================================================================================== - - -.. raw:: html - - - - - - DBMSType - - ConnectionDefinition - TABLE - - Table - - ParenthesedColumnList - - ImportExportStatementsList - -
- - - -
- Referenced by: -
- - -====================================================================================================================== -DBMSType -====================================================================================================================== - - -.. raw:: html - - - - - - EXA - - ORA - - JDBC - - DRIVER - - = - - S_CHAR_LITERAL - -
- -
DBMSType ::= 'EXA'
-
           | 'ORA'
-
           | 'JDBC' ( 'DRIVER' '=' S_CHAR_LITERAL )?
-
- Referenced by: -
- - -====================================================================================================================== -FileType -====================================================================================================================== - - -.. raw:: html - - - - - - CSV - - FBV - - -
- -
FileType ::= 'CSV'
-
           | 'FBV'
-
- Referenced by: -
- - -====================================================================================================================== -ImportExportStatement -====================================================================================================================== - - -.. raw:: html - - - - - - STATEMENT - - S_CHAR_LITERAL - -
- - -
         ::= 'STATEMENT' S_CHAR_LITERAL
-
- - -====================================================================================================================== -ImportExportStatementsList -====================================================================================================================== - - -.. raw:: html - - - - - - ImportExportStatement - -
- - -
         ::= ImportExportStatement+
-
- Referenced by: -
- - -====================================================================================================================== -File -====================================================================================================================== - - -.. raw:: html - - - - - - FILE - - S_CHAR_LITERAL - -
- -
File     ::= 'FILE' S_CHAR_LITERAL
-
- Referenced by: -
- - -====================================================================================================================== -FileList -====================================================================================================================== - - -.. raw:: html - - - - - - File - -
- - -
- - -====================================================================================================================== -ConnectionFileDefinition -====================================================================================================================== - - -.. raw:: html - - - - - - ConnectionOrCloudConnectionDefinition - - FileList - -
- - - -
- Referenced by: -
- - -====================================================================================================================== -ConnectionFileDefinitionList -====================================================================================================================== - - -.. raw:: html - - - - - - ConnectionFileDefinition - -
- - -
         ::= ConnectionFileDefinition+
-
- Referenced by: -
- - -====================================================================================================================== -CSVDestinationColumn -====================================================================================================================== - - -.. raw:: html - - - - - - S_LONG - .. - - S_LONG - FORMAT - - = - - S_CHAR_LITERAL - DELIMIT - - = - - ALWAYS - - NEVER - - AUTO - - -
- - -
         ::= S_LONG ( '..' S_LONG | ( 'FORMAT' '=' S_CHAR_LITERAL )? ( 'DELIMIT' '=' ( 'ALWAYS' | 'NEVER' | 'AUTO' ) )? )
-
- Referenced by: -
- - -====================================================================================================================== -CSVDestinationColumnList -====================================================================================================================== - - -.. raw:: html - - - - - - CSVDestinationColumn - , - - -
- - -
         ::= CSVDestinationColumn ( ',' CSVDestinationColumn )*
-
- Referenced by: -
- - -====================================================================================================================== -CSVSourceColumn -====================================================================================================================== - - -.. raw:: html - - - - - - S_LONG - .. - - S_LONG - FORMAT - - = - - S_CHAR_LITERAL - -
- - -
         ::= S_LONG ( '..' S_LONG | 'FORMAT' '=' S_CHAR_LITERAL )?
-
- Referenced by: -
- - -====================================================================================================================== -CSVSourceColumnList -====================================================================================================================== - - -.. raw:: html - - - - - - CSVSourceColumn - , - - -
- - -
         ::= CSVSourceColumn ( ',' CSVSourceColumn )*
-
- Referenced by: -
- - -====================================================================================================================== -FBVDestinationColumn -====================================================================================================================== - - -.. raw:: html - - - - - - SIZE - - = - - S_LONG - FORMAT - - PADDING - - = - - S_CHAR_LITERAL - ALIGN - - = - - LEFT - - RIGHT - - -
- - -
         ::= 'SIZE' '=' S_LONG
-
           | ( 'FORMAT' | 'PADDING' ) '=' S_CHAR_LITERAL
-
           | 'ALIGN' '=' ( 'LEFT' | 'RIGHT' )
-
- Referenced by: -
- - -====================================================================================================================== -FBVDestinationColumnList -====================================================================================================================== - - -.. raw:: html - - - - - - FBVDestinationColumn - , - - -
- - -
         ::= FBVDestinationColumn ( ','? FBVDestinationColumn )*
-
- Referenced by: -
- - -====================================================================================================================== -FBVSourceColumn -====================================================================================================================== - - -.. raw:: html - - - - - - SIZE - - START - - = - - S_LONG - FORMAT - - PADDING - - = - - S_CHAR_LITERAL - ALIGN - - = - - LEFT - - RIGHT - - -
- - -
         ::= ( 'SIZE' | 'START' ) '=' S_LONG
-
           | ( 'FORMAT' | 'PADDING' ) '=' S_CHAR_LITERAL
-
           | 'ALIGN' '=' ( 'LEFT' | 'RIGHT' )
-
- Referenced by: -
- - -====================================================================================================================== -FBVSourceColumnList -====================================================================================================================== - - -.. raw:: html - - - - - - FBVSourceColumn - , - - -
- - -
         ::= FBVSourceColumn ( ','? FBVSourceColumn )*
-
- Referenced by: -
- - -====================================================================================================================== -FileDestinationOption -====================================================================================================================== - - -.. raw:: html - - - - - - REPLACE - - TRUNCATE - - WITH - - COLUMN - - NAMES - - ENCODING - - NULL - - BOOLEAN - - ROW - - SEPARATOR - - COLUMN - - SEPARATOR - - DELIMITER - - = - - S_CHAR_LITERAL - DELIMIT - - = - - ALWAYS - - NEVER - - AUTO - - -
- - -
         ::= 'REPLACE'
-
           | 'TRUNCATE'
-
           | 'WITH' 'COLUMN' 'NAMES'
-
           | ( 'ENCODING' | 'NULL' | 'BOOLEAN' | 'ROW' 'SEPARATOR' | 'COLUMN' ( 'SEPARATOR' - | 'DELIMITER' ) ) '=' S_CHAR_LITERAL
-
           | 'DELIMIT' '=' ( 'ALWAYS' | 'NEVER' | 'AUTO' )
-
- Referenced by: -
- - -====================================================================================================================== -FileDestinationOptionList -====================================================================================================================== - - -.. raw:: html - - - - - - FileDestinationOption - -
- - -
         ::= FileDestinationOption+
-
- Referenced by: -
- - -====================================================================================================================== -FileSourceOption -====================================================================================================================== - - -.. raw:: html - - - - - - TRIM - - LTRIM - - RTRIM - - ENCODING - - NULL - - COLUMN - - SEPARATOR - - DELIMITER - - = - - S_CHAR_LITERAL - SKIP - - = - - S_LONG - ROW - - SEPARATOR - - = - - S_CHAR_LITERAL - SIZE - - = - - S_LONG - -
- - -
         ::= 'TRIM'
-
           | 'LTRIM'
-
           | 'RTRIM'
-
           | ( 'ENCODING' | 'NULL' | 'COLUMN' ( 'SEPARATOR' | 'DELIMITER' ) ) '=' - S_CHAR_LITERAL
-
           | 'SKIP' '=' S_LONG
-
           | 'ROW' ( 'SEPARATOR' '=' S_CHAR_LITERAL | 'SIZE' '=' S_LONG )
-
- Referenced by: -
- - -====================================================================================================================== -FileSourceOptionList -====================================================================================================================== - - -.. raw:: html - - - - - - FileSourceOption - -
- - -
         ::= FileSourceOption+
-
- Referenced by: -
- - -====================================================================================================================== -FileDestination -====================================================================================================================== - - -.. raw:: html - - - - - - FileType - - ConnectionFileDefinitionList - LOCAL - - SECURE - - FileType - - FileList - ( - - CSVDestinationColumnList - - FBVDestinationColumnList - ) - - FileDestinationOptionList - - CertificateVerification - -
- - - -
- Referenced by: -
- - -====================================================================================================================== -FileSource -====================================================================================================================== - - -.. raw:: html - - - - - - FileType - - ConnectionFileDefinitionList - LOCAL - - SECURE - - FileType - - FileList - ( - - CSVSourceColumnList - - FBVSourceColumnList - ) - - FileSourceOptionList - - CertificateVerification - -
- - - -
- Referenced by: -
- - -====================================================================================================================== -CertificateVerification -====================================================================================================================== - - -.. raw:: html - - - - - - IGNORE - - VERIFY - - CERTIFICATE - - PUBLIC - - KEY - - S_CHAR_LITERAL - PUBLIC - - KEY - - S_CHAR_LITERAL - -
- - -
         ::= ( 'IGNORE' | 'VERIFY' ) 'CERTIFICATE' ( 'PUBLIC' 'KEY' S_CHAR_LITERAL )?
-
           | 'PUBLIC' 'KEY' S_CHAR_LITERAL
-
- - -====================================================================================================================== -ScriptSourceDestination -====================================================================================================================== - - -.. raw:: html - - - - - - SCRIPT - - Table - - ConnectionDefinition - WITH - - RelObjectName - = - - S_CHAR_LITERAL - -
- - -
         ::= 'SCRIPT' Table ConnectionDefinition? ( 'WITH' ( RelObjectName '=' S_CHAR_LITERAL )+ )?
-
- Referenced by: -
- - -====================================================================================================================== -UserIdentification -====================================================================================================================== - - -.. raw:: html - - - - - - USER - - S_CHAR_LITERAL - IDENTIFIED - - BY - - S_CHAR_LITERAL - -
- - -
         ::= 'USER' S_CHAR_LITERAL 'IDENTIFIED' 'BY' S_CHAR_LITERAL
-
- - -====================================================================================================================== -ConnectionDefinition -====================================================================================================================== - - -.. raw:: html - - - - - - AT - - RelObjectName - - S_CHAR_LITERAL - - UserIdentification - - CertificateVerification - -
- - - -
- - -====================================================================================================================== -CloudConnectionDefinition -====================================================================================================================== - - -.. raw:: html - - - - - - AT - - CLOUD - - NONE - - AZURE - - BLOBSTORAGE - - RelObjectName - - S_CHAR_LITERAL - - UserIdentification - -
- - -
         ::= 'AT' 'CLOUD' ( 'NONE' | 'AZURE' 'BLOBSTORAGE' ) ( RelObjectName | S_CHAR_LITERAL ) UserIdentification?
-
- - -====================================================================================================================== -ConnectionOrCloudConnectionDefinition -====================================================================================================================== - - -.. raw:: html - - - - - - CloudConnectionDefinition - - ConnectionDefinition - -
- - -
         ::= CloudConnectionDefinition
-
           | ConnectionDefinition
-
- - -====================================================================================================================== -ErrorClause -====================================================================================================================== - - -.. raw:: html - - - - - - ERRORS - - INTO - - ErrorDestination - ( - - Expression - ) - - REPLACE - - TRUNCATE - - RejectClause - - RejectClause - -
- - -
         ::= 'ERRORS' 'INTO' ErrorDestination ( '(' Expression ')' )? ( 'REPLACE' | 'TRUNCATE' )? RejectClause?
-
           | RejectClause
-
- Referenced by: -
- - -====================================================================================================================== -RejectClause -====================================================================================================================== - - -.. raw:: html - - - - - - REJECT - - LIMIT - - S_LONG - UNLIMITED - - ERRORS - - -
- - -
         ::= 'REJECT' 'LIMIT' ( S_LONG | 'UNLIMITED' ) 'ERRORS'?
-
- Referenced by: -
- - -====================================================================================================================== -ErrorDestination -====================================================================================================================== - - -.. raw:: html - - - - - - CSVFileDestination - - Table - -
- - -
         ::= CSVFileDestination
-
           | Table
-
- Referenced by: -
- - -====================================================================================================================== -CSVFileDestination -====================================================================================================================== - - -.. raw:: html - - - - - - CSV - - ConnectionOrCloudConnectionDefinition - LOCAL - - SECURE - - CSV - - File - -
- - -
         ::= ( 'CSV' ConnectionOrCloudConnectionDefinition | 'LOCAL' 'SECURE'? 'CSV' ) File
-
- Referenced by: -
- - -====================================================================================================================== -Declare -====================================================================================================================== - - -.. raw:: html - - - - - - DECLARE - - UserVariable - TABLE - - ( - - ColumnDefinition - , - - ) - - AS - - RelObjectName - - ColDataType - = - - Expression - - UserVariable - , - - -
- -
Declare  ::= 'DECLARE' UserVariable ( 'TABLE' '(' ColumnDefinition ( ',' ColumnDefinition )* ')' | 'AS' RelObjectName | ColDataType ( '=' Expression )? ( ',' UserVariable ColDataType ( '=' Expression )? )* )
-
- Referenced by: -
- - -====================================================================================================================== -SessionStatement -====================================================================================================================== - - -.. raw:: html - - - - - - SESSION - - BRANCH - - START - - APPLY - - DROP - - SHOW - - DESCRIBE - - S_IDENTIFIER - - S_QUOTED_IDENTIFIER - - S_CHAR_LITERAL - - S_LONG - . - - S_IDENTIFIER - - S_QUOTED_IDENTIFIER - - S_CHAR_LITERAL - - S_LONG - WITH - - S_IDENTIFIER - KEEP - - = - - S_IDENTIFIER - - S_QUOTED_IDENTIFIER - - S_CHAR_LITERAL - - S_LONG - TRUE - - FALSE - - ON - - OFF - - YES - - NO - - , - - -
- - -
         ::= ( 'SESSION' | 'BRANCH' ) ( 'START' | 'APPLY' | 'DROP' | 'SHOW' | 'DESCRIBE' - ) ( ( S_IDENTIFIER | S_QUOTED_IDENTIFIER | S_CHAR_LITERAL | S_LONG ) ( '.' ( S_IDENTIFIER | S_QUOTED_IDENTIFIER | S_CHAR_LITERAL | S_LONG ) )? )? ( 'WITH' ( S_IDENTIFIER | 'KEEP' ) '=' ( S_IDENTIFIER | S_QUOTED_IDENTIFIER | S_CHAR_LITERAL | S_LONG | 'TRUE' | 'FALSE' | 'ON' | 'OFF' | 'YES' | 'NO' ) ( ',' ( S_IDENTIFIER | 'KEEP' ) '=' ( S_IDENTIFIER | S_QUOTED_IDENTIFIER | S_CHAR_LITERAL | S_LONG | 'TRUE' | 'FALSE' | 'ON' | 'OFF' | 'YES' | 'NO' ) )* )?
-
- Referenced by: -
- - -====================================================================================================================== -Set -====================================================================================================================== - - -.. raw:: html - - - - - - SET - - LOCAL - - SESSION - - K_DATETIMELITERAL - ZONE - - UserVariable - - IdentifierChain - = - - Expression - ZONE - - K_DATETIMELITERAL - = - - RelObjectName - , - - -
- -
Set      ::= 'SET' ( 'LOCAL' | 'SESSION' )? ( K_DATETIMELITERAL 'ZONE' | ( UserVariable | IdentifierChain ) '='? ) Expression ( ',' ( K_DATETIMELITERAL 'ZONE' | RelObjectName '='? )? Expression )*
-
- Referenced by: -
- - -====================================================================================================================== -Reset -====================================================================================================================== - - -.. raw:: html - - - - - - RESET - - K_DATETIMELITERAL - ZONE - - RelObjectName - ALL - - -
- -
Reset    ::= 'RESET' ( K_DATETIMELITERAL 'ZONE' | RelObjectName | 'ALL' )
-
- Referenced by: -
- - -====================================================================================================================== -RenameTableStatement -====================================================================================================================== - - -.. raw:: html - - - - - - RENAME - - TABLE - - IF - - EXISTS - - Table - WAIT - - S_LONG - NOWAIT - - TO - - Table - - Table - , - - -
- - -
         ::= 'RENAME' 'TABLE'? ( 'IF' 'EXISTS' )? Table ( 'WAIT' S_LONG | 'NOWAIT' )? 'TO' Table ( ',' Table 'TO' Table )*
-
- Referenced by: -
- - -====================================================================================================================== -PurgeStatement -====================================================================================================================== - - -.. raw:: html - - - - - - PURGE - - TABLE - - Table - INDEX - - Index - RECYCLEBIN - - DBA_RECYCLEBIN - - TABLESPACE - - S_IDENTIFIER - USER - - S_IDENTIFIER - -
- - -
         ::= 'PURGE' ( 'TABLE' Table | 'INDEX' Index | 'RECYCLEBIN' | 'DBA_RECYCLEBIN' | 'TABLESPACE' S_IDENTIFIER ( 'USER' S_IDENTIFIER )? )
-
- Referenced by: -
- - -====================================================================================================================== -Describe -====================================================================================================================== - - -.. raw:: html - - - - - - DESCRIBE - - DESC - - Table - -
- -
Describe ::= ( 'DESCRIBE' | 'DESC' ) Table
-
- Referenced by: -
- - -====================================================================================================================== -Explain -====================================================================================================================== - - -.. raw:: html - - - - - - EXPLAIN - - SUMMARIZE - - ExplainStatementOptions - - WithList - - SelectWithWithItems - - InsertWithWithItems - - UpdateWithWithItems - - DeleteWithWithItems - - Merge - - Table - -
- - -
- Referenced by: -
- - -====================================================================================================================== -ExplainOptionBoolean -====================================================================================================================== - - -.. raw:: html - - - - - - TRUE - - FALSE - - ON - - OFF - - -
- - -
         ::= ( 'TRUE' | 'FALSE' | 'ON' | 'OFF' )?
-
- Referenced by: -
- - -====================================================================================================================== -ExplainFormatOption -====================================================================================================================== - - -.. raw:: html - - - - - - XML - - JSON - - YAML - - -
- - -
         ::= ( 'XML' | 'JSON' | 'YAML' )?
-
- Referenced by: -
- - -====================================================================================================================== -ExplainStatementOptions -====================================================================================================================== - - -.. raw:: html - - - - - - ANALYZE - - BUFFERS - - COSTS - - VERBOSE - - ExplainOptionBoolean - FORMAT - - PLAN - - FOR - - ExplainFormatOption - -
- - -
         ::= ( ( 'ANALYZE' | 'BUFFERS' | 'COSTS' | 'VERBOSE' ) ExplainOptionBoolean | ( 'FORMAT' | 'PLAN' 'FOR'? ) ExplainFormatOption )*
-
- Referenced by: -
- - -====================================================================================================================== -Use -====================================================================================================================== - - -.. raw:: html - - - - - - USE - - SCHEMA - - RelObjectName - -
- -
Use      ::= 'USE' 'SCHEMA'? RelObjectName
-
- Referenced by: -
- - -====================================================================================================================== -Show -====================================================================================================================== - - -.. raw:: html - - - - - - SHOW - - ShowColumns - - ShowIndex - - ShowTables - - captureRest - -
- -
Show     ::= 'SHOW' ( ShowColumns | ShowIndex | ShowTables | captureRest )
-
- Referenced by: -
- - -====================================================================================================================== -ShowColumns -====================================================================================================================== - - -.. raw:: html - - - - - - COLUMNS - - FROM - - RelObjectName - -
- - -
         ::= 'COLUMNS' 'FROM' RelObjectName
-
- Referenced by: -
- - -====================================================================================================================== -ShowIndex -====================================================================================================================== - - -.. raw:: html - - - - - - INDEX - - FROM - - RelObjectName - -
- - -
         ::= 'INDEX' 'FROM' RelObjectName
-
- Referenced by: -
- - -====================================================================================================================== -RefreshMaterializedView -====================================================================================================================== - - -.. raw:: html - - - - - - REFRESH - - MATERIALIZED - - VIEW - - CONCURRENTLY - - Table - WITH - - NO - - DATA - - captureRest - -
- - -
         ::= 'REFRESH' 'MATERIALIZED' 'VIEW' 'CONCURRENTLY'? Table ( 'WITH' 'NO'? 'DATA' )? captureRest
-
- Referenced by: -
- - -====================================================================================================================== -ShowTables -====================================================================================================================== - - -.. raw:: html - - - - - - EXTENDED - - FULL - - TABLES - - FROM - - IN - - RelObjectName - LIKE - - SimpleExpression - WHERE - - Expression - -
- - -
         ::= 'EXTENDED'? 'FULL'? 'TABLES' ( ( 'FROM' | 'IN' ) RelObjectName )? ( 'LIKE' SimpleExpression | 'WHERE' Expression )?
-
- Referenced by: -
- - -====================================================================================================================== -Values -====================================================================================================================== - - -.. raw:: html - - - - - - VALUES - - VALUE - - ExpressionList - -
- -
Values   ::= ( 'VALUES' | 'VALUE' ) ExpressionList
-
- Referenced by: -
- - -====================================================================================================================== -ReturningClause -====================================================================================================================== - - -.. raw:: html - - - - - - RETURNING - - RETURN - - ReturningOutputAliasList - - SelectItemsList - INTO - - Table - - UserVariable - , - - -
- - -
         ::= ( 'RETURNING' | 'RETURN' ) ReturningOutputAliasList? SelectItemsList ( 'INTO' ( Table | UserVariable ) ( ',' ( Table | UserVariable ) )* )?
-
- Referenced by: -
- - -====================================================================================================================== -ReturningReferenceKind -====================================================================================================================== - - -.. raw:: html - - - - - - RelObjectName - -
- - -
         ::= RelObjectName
-
- Referenced by: -
- - -====================================================================================================================== -ReturningOutputAliasDefinition -====================================================================================================================== - - -.. raw:: html - - - - - - ReturningReferenceKind - AS - - RelObjectName - -
- - -
         ::= ReturningReferenceKind 'AS' RelObjectName
-
- Referenced by: -
- - -====================================================================================================================== -ReturningOutputAliasList -====================================================================================================================== - - -.. raw:: html - - - - - - WITH - - ( - - ReturningOutputAliasDefinition - , - - ) - - -
- - -
         ::= 'WITH' '(' ReturningOutputAliasDefinition ( ',' ReturningOutputAliasDefinition )* ')'
-
- Referenced by: -
- - -====================================================================================================================== -UpdateWithWithItems -====================================================================================================================== - - -.. raw:: html - - - - - - Update - -
- - -
         ::= Update
-
- Referenced by: -
- - -====================================================================================================================== -Update -====================================================================================================================== - - -.. raw:: html - - - - - - UPDATE - - LOW_PRIORITY - - IGNORE - - TableWithAliasAndMysqlIndexHint - - JoinsList - SET - - UpdateSets - - OutputClause - FROM - - FromItem - - JoinsList - - WhereClause - - PreferringClause - - OrderByElements - - PlainLimit - - ReturningClause - -
- - -
- - -====================================================================================================================== -UpdateSets -====================================================================================================================== - - -.. raw:: html - - - - - - Column - = - - Expression - - ParenthesedExpressionList - = - - ParenthesedSelect - - ParenthesedExpressionList - , - - Column - = - - Expression - - ParenthesedExpressionList - = - - ParenthesedSelect - - ParenthesedExpressionList - -
- - - -
- - -====================================================================================================================== -Partitions -====================================================================================================================== - - -.. raw:: html - - - - - - Column - = - - Expression - , - - -
- - -
         ::= Column ( '=' Expression )? ( ',' Column ( '=' Expression )? )*
-
- Referenced by: -
- - -====================================================================================================================== -InsertWithWithItems -====================================================================================================================== - - -.. raw:: html - - - - - - Insert - -
- - -
         ::= Insert
-
- Referenced by: -
- - -====================================================================================================================== -Insert -====================================================================================================================== - - -.. raw:: html - - - - - - INSERT - - LOW_PRIORITY - - DELAYED - - HIGH_PRIORITY - - IGNORE - - ALL - - FIRST - - OracleMultiInsertClause - - OracleMultiInsertWhenBranch - - OracleMultiInsertElseBranch - - Select - OVERWRITE - - TABLE - - INTO - - TABLE - - Table - PARTITION - - ( - - Partitions - ) - - AS - - RelObjectName - ( - - ColumnList - ) - - OVERRIDING - - SYSTEM - - VALUE - - OutputClause - DEFAULT - - VALUES - - SET - - UpdateSets - - Select - - Alias - ON - - DUPLICATE - - KEY - - UPDATE - - InsertDuplicateAction - ON - - CONFLICT - - InsertConflictTarget - - InsertConflictAction - - ReturningClause - -
- -
Insert   ::= 'INSERT' ( 'LOW_PRIORITY' | 'DELAYED' | 'HIGH_PRIORITY' )? 'IGNORE'? ( ( 'ALL' - | 'FIRST' ) ( OracleMultiInsertClause+ | OracleMultiInsertWhenBranch+ OracleMultiInsertElseBranch? ) Select | ( 'OVERWRITE' 'TABLE' | 'INTO' 'TABLE'? )? Table ( 'PARTITION' '(' Partitions ')' )? ( 'AS'? RelObjectName )? ( '(' ColumnList ')' )? ( 'OVERRIDING' 'SYSTEM' 'VALUE' )? OutputClause? ( 'DEFAULT' 'VALUES' | 'SET' UpdateSets | Select ) Alias? ( 'ON' 'DUPLICATE' 'KEY' 'UPDATE' InsertDuplicateAction )? ( 'ON' 'CONFLICT' InsertConflictTarget? InsertConflictAction )? ReturningClause? )
-
- - -====================================================================================================================== -OracleMultiInsertClause -====================================================================================================================== - - -.. raw:: html - - - - - - INTO - - Table - ( - - ColumnList - ) - - Select - -
- - -
         ::= 'INTO' Table ( '(' ColumnList ')' )? Select
-
- - -====================================================================================================================== -OracleMultiInsertWhenBranch -====================================================================================================================== - - -.. raw:: html - - - - - - WHEN - - Expression - THEN - - OracleMultiInsertClauseList - -
- - -
         ::= 'WHEN' Expression 'THEN' OracleMultiInsertClauseList
-
- Referenced by: -
- - -====================================================================================================================== -OracleMultiInsertElseBranch -====================================================================================================================== - - -.. raw:: html - - - - - - ELSE - - OracleMultiInsertClauseList - -
- - -
         ::= 'ELSE' OracleMultiInsertClauseList
-
- Referenced by: -
- - -====================================================================================================================== -OracleMultiInsertClauseList -====================================================================================================================== - - -.. raw:: html - - - - - - OracleMultiInsertClause - -
- - -
         ::= OracleMultiInsertClause+
-
- - -====================================================================================================================== -InsertConflictTarget -====================================================================================================================== - - -.. raw:: html - - - - - - ( - - RelObjectNameExt - , - - ) - - WhereClause - ON - - CONSTRAINT - - RelObjectNameExt - -
- - -
         ::= '(' RelObjectNameExt ( ',' RelObjectNameExt )* ')' WhereClause?
-
           | 'ON' 'CONSTRAINT' RelObjectNameExt
-
- Referenced by: -
- - -====================================================================================================================== -InsertConflictAction -====================================================================================================================== - - -.. raw:: html - - - - - - DO - - NOTHING - - UPDATE - - SET - - UpdateSets - - WhereClause - -
- - -
         ::= 'DO' ( 'NOTHING' | 'UPDATE' 'SET' UpdateSets WhereClause? )
-
- Referenced by: -
- - -====================================================================================================================== -InsertDuplicateAction -====================================================================================================================== - - -.. raw:: html - - - - - - NOTHING - - UpdateSets - - WhereClause - -
- - -
         ::= 'NOTHING'
-
           | UpdateSets WhereClause?
-
- Referenced by: -
- - -====================================================================================================================== -OutputClause -====================================================================================================================== - - -.. raw:: html - - - - - - OUTPUT - - SelectItemsList - INTO - - UserVariable - - Table - - ColumnsNamesList - -
- - -
         ::= 'OUTPUT' SelectItemsList ( 'INTO' ( UserVariable | Table ) ColumnsNamesList? )?
-
- Referenced by: -
- - -====================================================================================================================== -Upsert -====================================================================================================================== - - -.. raw:: html - - - - - - UPSERT - - INSERT - - OR - - REPLACE - - INTO - - Table - - ParenthesedColumnList - SET - - UpdateSets - - Select - ON - - DUPLICATE - - KEY - - UPDATE - - InsertDuplicateAction - -
- -
Upsert   ::= ( 'UPSERT' | ( 'INSERT' 'OR' )? 'REPLACE' ) 'INTO'? Table ParenthesedColumnList? ( 'SET' UpdateSets | Select ) ( 'ON' 'DUPLICATE' 'KEY' 'UPDATE' InsertDuplicateAction )?
-
- Referenced by: -
- - -====================================================================================================================== -DeleteWithWithItems -====================================================================================================================== - - -.. raw:: html - - - - - - Delete - -
- - -
         ::= Delete
-
- Referenced by: -
- - -====================================================================================================================== -Delete -====================================================================================================================== - - -.. raw:: html - - - - - - DELETE - - LOW_PRIORITY - - QUICK - - IGNORE - - TableWithAlias - , - - OutputClause - FROM - - TableWithAlias - - JoinsList - USING - - FromItem - , - - WhereClause - - PreferringClause - - OrderByElements - - PlainLimit - - ReturningClause - -
- -
Delete   ::= 'DELETE' 'LOW_PRIORITY'? 'QUICK'? 'IGNORE'? ( ( TableWithAlias ( ',' TableWithAlias )* OutputClause? )? 'FROM' )? ( TableWithAlias JoinsList? )? ( 'USING' FromItem ( ',' FromItem )* )? WhereClause? PreferringClause? OrderByElements? PlainLimit? ReturningClause?
-
- - -====================================================================================================================== -Merge -====================================================================================================================== - - -.. raw:: html - - - - - - MERGE - - INTO - - TableWithAlias - USING - - FromItem - ON - - Expression - - MergeOperations - - OutputClause - -
- -
Merge    ::= 'MERGE' 'INTO' TableWithAlias 'USING' FromItem 'ON' Expression MergeOperations OutputClause?
-
- Referenced by: -
- - -====================================================================================================================== -MergeOperations -====================================================================================================================== - - -.. raw:: html - - - - - - MergeWhenMatched - - MergeWhenNotMatched - -
- - -
         ::= ( MergeWhenMatched | MergeWhenNotMatched )*
-
- Referenced by: -
- - -====================================================================================================================== -MergeWhenMatched -====================================================================================================================== - - -.. raw:: html - - - - - - WHEN - - MATCHED - - AND - - Expression - THEN - - DELETE - - MergeUpdateClause - -
- - -
         ::= 'WHEN' 'MATCHED' ( 'AND' Expression )? 'THEN' ( 'DELETE' | MergeUpdateClause )
-
- Referenced by: -
- - -====================================================================================================================== -MergeUpdateClause -====================================================================================================================== - - -.. raw:: html - - - - - - UPDATE - - SET - - UpdateSets - WHERE - - Expression - DELETE - - WHERE - - Expression - -
- - -
         ::= 'UPDATE' 'SET' UpdateSets ( 'WHERE' Expression )? ( 'DELETE' 'WHERE' Expression )?
-
- Referenced by: -
- - -====================================================================================================================== -MergeWhenNotMatched -====================================================================================================================== - - -.. raw:: html - - - - - - WHEN - - NOT - - MATCHED - - AND - - Expression - THEN - - INSERT - - ( - - ColumnList - ) - - VALUES - - ( - - SimpleExpressionList - ) - - WHERE - - Expression - -
- - -
         ::= 'WHEN' 'NOT' 'MATCHED' ( 'AND' Expression )? 'THEN' 'INSERT' ( '(' ColumnList ')' )? 'VALUES' '(' SimpleExpressionList ')' ( 'WHERE' Expression )?
-
- Referenced by: -
- - -====================================================================================================================== -RelObjectNames -====================================================================================================================== - - -.. raw:: html - - - - - - RelObjectName - ... - - .. - - . - - : - - RelObjectNameExt - -
- - -
         ::= RelObjectName ( ( '...' | '..' | '.' | ':' ) RelObjectNameExt )*
-
- - -====================================================================================================================== -ColumnIdentifier -====================================================================================================================== - - -.. raw:: html - - - - - - RelObjectName - ... - - .. - - . - - RelObjectNameExt - -
- - -
         ::= RelObjectName ( ( '...' | '..' | '.' ) RelObjectNameExt )*
-
- Referenced by: -
- - -====================================================================================================================== -Column -====================================================================================================================== - - -.. raw:: html - - - - - - ColumnIdentifier - COMMENT - - S_CHAR_LITERAL - . - - K_NEXTVAL - - ArrayConstructor - -
- - -
- - -====================================================================================================================== -RelObjectName -====================================================================================================================== - - -.. raw:: html - - - - - - DATA_TYPE - - S_IDENTIFIER - - S_QUOTED_IDENTIFIER - - K_DATETIMELITERAL - - K_DATE_LITERAL - - NonReservedWord - ALL - - ANY - - CASEWHEN - - CONNECT - - CREATE - - DEFAULT - - GLOBAL - - GROUP - - GROUPING - - IF - - IIF - - IGNORE - - IN - - INTERVAL - - LEFT - - LIMIT - - K_NEXTVAL - OFFSET - - ON - - OPTIMIZE - - ORDER - - PROCEDURE - - PUBLIC - - QUALIFY - - RIGHT - - SET - - SOME - - START - - TABLES - - TOP - - VALUE - - VALUES - - -
- - -
         ::= DATA_TYPE
-
           | S_IDENTIFIER
-
           | S_QUOTED_IDENTIFIER
-
           | K_DATETIMELITERAL
-
           | K_DATE_LITERAL
-
           | NonReservedWord
-
           | 'ALL'
-
           | 'ANY'
-
           | 'CASEWHEN'
-
           | 'CONNECT'
-
           | 'CREATE'
-
           | 'DEFAULT'
-
           | 'GLOBAL'
-
           | 'GROUP'
-
           | 'GROUPING'
-
           | 'IF'
-
           | 'IIF'
-
           | 'IGNORE'
-
           | 'IN'
-
           | 'INTERVAL'
-
           | 'LEFT'
-
           | 'LIMIT'
-
           | K_NEXTVAL
-
           | 'OFFSET'
-
           | 'ON'
-
           | 'OPTIMIZE'
-
           | 'ORDER'
-
           | 'PROCEDURE'
-
           | 'PUBLIC'
-
           | 'QUALIFY'
-
           | 'RIGHT'
-
           | 'SET'
-
           | 'SOME'
-
           | 'START'
-
           | 'TABLES'
-
           | 'TOP'
-
           | 'VALUE'
-
           | 'VALUES'
-
- - -====================================================================================================================== -RelObjectNameExt -====================================================================================================================== - - -.. raw:: html - - - - - - RelObjectName - FROM - - K_SELECT - CURRENT - - -
- - -
         ::= RelObjectName
-
           | 'FROM'
-
           | K_SELECT
-
           | 'CURRENT'
-
- - -====================================================================================================================== -Table -====================================================================================================================== - - -.. raw:: html - - - - - - RelObjectNames - - TimeTravelBeforeAlias - - S_CHAR_LITERAL - -
- - -
           | S_CHAR_LITERAL
-
- - -====================================================================================================================== -TableWithAlias -====================================================================================================================== - - -.. raw:: html - - - - - - Table - - Alias - -
- - -
         ::= Table Alias?
-
- Referenced by: -
- - -====================================================================================================================== -TableWithAliasAndMysqlIndexHint -====================================================================================================================== - - -.. raw:: html - - - - - - Table - - Alias - - MySQLIndexHint - -
- - -
         ::= Table Alias? MySQLIndexHint?
-
- Referenced by: -
- - -====================================================================================================================== -Number -====================================================================================================================== - - -.. raw:: html - - - - - - S_DOUBLE - - S_LONG - -
- -
Number   ::= S_DOUBLE
-
           | S_LONG
-
- Referenced by: -
- - -====================================================================================================================== -SampleClause -====================================================================================================================== - - -.. raw:: html - - - - - - SAMPLE - - BLOCK - - TABLESAMPLE - - USING - - SAMPLE - - SYSTEM - - BERNOULLI - - ( - - Number - % - - PERCENT - - ROWS - - ) - - REPEATABLE - - ( - - Number - ) - - SEED - - ( - - Number - ) - - Number - OFFSET - - Number - -
- - -
         ::= ( 'SAMPLE' 'BLOCK'? | ( 'TABLESAMPLE' | 'USING' 'SAMPLE' ) ( 'SYSTEM' - | 'BERNOULLI' ) ) ( '(' Number ( '%' | 'PERCENT' | 'ROWS' )? ')' ( 'REPEATABLE' '(' Number ')' )? ( 'SEED' '(' Number ')' )? | Number ( 'OFFSET' Number )? )
-
- Referenced by: -
- - -====================================================================================================================== -SelectWithWithItems -====================================================================================================================== - - -.. raw:: html - - - - - - Select - -
- - -
         ::= Select
-
- Referenced by: -
- - -====================================================================================================================== -Select -====================================================================================================================== - - -.. raw:: html - - - - - - WithList - - FromQuery - - PlainSelect - - Values - - ParenthesedSelect - - Alias - - FromQueryFromSelect - - SetOperationList - - OrderByElements - - LimitWithOffset - - Offset - - Fetch - - WithIsolation - -
- - -
- - -====================================================================================================================== -FromQuery -====================================================================================================================== - - -.. raw:: html - - - - - - FROM - - FromItem - - LateralViews - - JoinsList - |> - - PipeOperator - -
- - -
         ::= 'FROM' FromItem LateralViews? JoinsList? ( '|>' PipeOperator )*
-
- Referenced by: -
- - -====================================================================================================================== -FromQueryFromSelect -====================================================================================================================== - - -.. raw:: html - - - - - - |> - - PipeOperator - -
- - -
         ::= ( '|>' PipeOperator )+
-
- Referenced by: -
- - -====================================================================================================================== -PipeOperator -====================================================================================================================== - - -.. raw:: html - - - - - - SelectPipeOperator - - SetPipeOperator - - DropPipeOperator - - AsPipeOperator - - WherePipeOperator - - LimitPipeOperator - - AggregatePipeOperator - - OrderByPipeOperator - - SetOperationPipeOperator - - JoinPipeOperator - - CallPipeOperator - - TableSamplePipeOperator - - PivotPipeOperator - - UnPivotPipeOperator - -
- - -
         ::= SelectPipeOperator
-
           | SetPipeOperator
-
           | DropPipeOperator
-
           | AsPipeOperator
-
           | WherePipeOperator
-
           | LimitPipeOperator
-
           | AggregatePipeOperator
-
           | OrderByPipeOperator
-
           | SetOperationPipeOperator
-
           | JoinPipeOperator
-
           | CallPipeOperator
-
           | TableSamplePipeOperator
-
           | PivotPipeOperator
-
           | UnPivotPipeOperator
-
- Referenced by: -
- - -====================================================================================================================== -SelectPipeOperator -====================================================================================================================== - - -.. raw:: html - - - - - - K_SELECT - DISTINCT - - ALL - - EXTEND - - WINDOW - - RENAME - - SelectItem - , - - -
- - -
         ::= ( K_SELECT ( 'DISTINCT' | 'ALL' )? | 'EXTEND' | 'WINDOW' | 'RENAME' ) SelectItem ( ',' SelectItem )*
-
- Referenced by: -
- - -====================================================================================================================== -WherePipeOperator -====================================================================================================================== - - -.. raw:: html - - - - - - WHERE - - Expression - -
- - -
         ::= 'WHERE' Expression
-
- Referenced by: -
- - -====================================================================================================================== -OrderSuffix -====================================================================================================================== - - -.. raw:: html - - - - - - ASC - - DESC - - NULLS - - FIRST - - LAST - - -
- - -
         ::= ( 'ASC' | 'DESC' ) ( 'NULLS' ( 'FIRST' | 'LAST' ) )?
-
- Referenced by: -
- - -====================================================================================================================== -AggregatePipeOperator -====================================================================================================================== - - -.. raw:: html - - - - - - AGGREGATE - - SelectItem - - OrderSuffix - , - - GROUP - - AND - - ORDER - - BY - - SelectItem - - OrderSuffix - , - - -
- - -
         ::= 'AGGREGATE' SelectItem OrderSuffix? ( ',' SelectItem OrderSuffix? )* ( 'GROUP' ( 'AND' 'ORDER' )? 'BY' SelectItem OrderSuffix? ( ',' SelectItem OrderSuffix? )* )?
-
- Referenced by: -
- - -====================================================================================================================== -OrderByPipeOperator -====================================================================================================================== - - -.. raw:: html - - - - - - OrderByElements - -
- - -
         ::= OrderByElements
-
- Referenced by: -
- - -====================================================================================================================== -AsPipeOperator -====================================================================================================================== - - -.. raw:: html - - - - - - AS - - Alias - -
- - -
         ::= 'AS' Alias
-
- Referenced by: -
- - -====================================================================================================================== -JoinPipeOperator -====================================================================================================================== - - -.. raw:: html - - - - - - JoinerExpression - -
- - -
         ::= JoinerExpression
-
- Referenced by: -
- - -====================================================================================================================== -SetPipeOperator -====================================================================================================================== - - -.. raw:: html - - - - - - SET - - UpdateSets - -
- - -
         ::= 'SET' UpdateSets
-
- Referenced by: -
- - -====================================================================================================================== -DropPipeOperator -====================================================================================================================== - - -.. raw:: html - - - - - - DROP - - ColumnList - -
- - -
         ::= 'DROP' ColumnList
-
- Referenced by: -
- - -====================================================================================================================== -LimitPipeOperator -====================================================================================================================== - - -.. raw:: html - - - - - - LIMIT - - Expression - OFFSET - - Expression - -
- - -
         ::= 'LIMIT' Expression ( 'OFFSET' Expression )?
-
- Referenced by: -
- - -====================================================================================================================== -SetOperationModifier -====================================================================================================================== - - -.. raw:: html - - - - - - ALL - - DISTINCT - - BY - - NAME - - MATCHING - - ( - - RelObjectName - , - - ) - - STRICT - - CORRESPONDING - - ALL - - DISTINCT - - BY - - ALL - - DISTINCT - - ( - - RelObjectName - , - - ) - - ALL - - DISTINCT - - -
- - -
         ::= ( 'ALL' | 'DISTINCT' )? 'BY' 'NAME' ( 'MATCHING' '(' RelObjectName ( ',' RelObjectName )* ')' )?
-
           | 'STRICT'? 'CORRESPONDING' ( 'ALL' | 'DISTINCT' )? ( 'BY' ( 'ALL' | 'DISTINCT' - )? '(' RelObjectName ( ',' RelObjectName )* ')' )?
-
           | 'ALL'
-
           | 'DISTINCT'
-
- - -====================================================================================================================== -SetOperationPipeOperator -====================================================================================================================== - - -.. raw:: html - - - - - - UNION - - INTERSECT - - EXCEPT - - SetOperationModifier - - ParenthesedSelect - , - - -
- - -
         ::= ( 'UNION' | 'INTERSECT' | 'EXCEPT' ) SetOperationModifier? ParenthesedSelect ( ',' ParenthesedSelect )*
-
- Referenced by: -
- - -====================================================================================================================== -CallPipeOperator -====================================================================================================================== - - -.. raw:: html - - - - - - CALL - - TableFunction - - Alias - -
- - -
         ::= 'CALL' TableFunction Alias?
-
- Referenced by: -
- - -====================================================================================================================== -TableSamplePipeOperator -====================================================================================================================== - - -.. raw:: html - - - - - - TABLESAMPLE - - SYSTEM - - ( - - S_DOUBLE - - S_LONG - PERCENT - - ) - - -
- - -
         ::= 'TABLESAMPLE' 'SYSTEM' '(' ( S_DOUBLE | S_LONG ) 'PERCENT' ')'
-
- Referenced by: -
- - -====================================================================================================================== -PivotPipeOperator -====================================================================================================================== - - -.. raw:: html - - - - - - PIVOT - - ( - - Function - FOR - - Column - IN - - ( - - SelectItemsList - ) - - ) - - Alias - -
- - -
         ::= 'PIVOT' '(' Function 'FOR' Column 'IN' '(' SelectItemsList ')' ')' Alias?
-
- Referenced by: -
- - -====================================================================================================================== -UnPivotPipeOperator -====================================================================================================================== - - -.. raw:: html - - - - - - UNPIVOT - - ( - - Column - FOR - - Column - IN - - ( - - SelectItemsList - ) - - ) - - Alias - -
- - -
         ::= 'UNPIVOT' '(' Column 'FOR' Column 'IN' '(' SelectItemsList ')' ')' Alias?
-
- Referenced by: -
- - -====================================================================================================================== -TableStatement -====================================================================================================================== - - -.. raw:: html - - - - - - TABLE - - Table - - OrderByElements - - LimitWithOffset - - Offset - -
- - -
         ::= 'TABLE' Table OrderByElements? LimitWithOffset? Offset?
-
- Referenced by: -
- - -====================================================================================================================== -ParenthesedSelect -====================================================================================================================== - - -.. raw:: html - - - - - - ( - - Select - ) - - -
- - -
         ::= '(' Select ')'
-
- - -====================================================================================================================== -ParenthesedInsert -====================================================================================================================== - - -.. raw:: html - - - - - - ( - - Insert - ) - - -
- - -
         ::= '(' Insert ')'
-
- Referenced by: -
- - -====================================================================================================================== -ParenthesedUpdate -====================================================================================================================== - - -.. raw:: html - - - - - - ( - - Update - ) - - -
- - -
         ::= '(' Update ')'
-
- Referenced by: -
- - -====================================================================================================================== -ParenthesedDelete -====================================================================================================================== - - -.. raw:: html - - - - - - ( - - Delete - ) - - -
- - -
         ::= '(' Delete ')'
-
- Referenced by: -
- - -====================================================================================================================== -LateralView -====================================================================================================================== - - -.. raw:: html - - - - - - LATERAL - - VIEW - - OUTER - - Function - - RelObjectName - AS - - RelObjectName - , - - RelObjectName - -
- - -
         ::= 'LATERAL' 'VIEW' 'OUTER'? Function RelObjectName? 'AS' RelObjectName ( ',' RelObjectName )?
-
- Referenced by: -
- - -====================================================================================================================== -ForClause -====================================================================================================================== - - -.. raw:: html - - - - - - FOR - - BROWSE - - XML - - RAW - - ( - - S_CHAR_LITERAL - ) - - AUTO - - , - - BINARY - - BASE64 - - TYPE - - ROOT - - XMLSCHEMA - - ( - - S_CHAR_LITERAL - ) - - XMLDATA - - ELEMENTS - - XSINIL - - ABSENT - - EXPLICIT - - , - - BINARY - - BASE64 - - TYPE - - ROOT - - ( - - S_CHAR_LITERAL - ) - - XMLDATA - - PATH - - ( - - S_CHAR_LITERAL - ) - - , - - BINARY - - BASE64 - - TYPE - - ROOT - - ( - - S_CHAR_LITERAL - ) - - ELEMENTS - - XSINIL - - ABSENT - - JSON - - AUTO - - PATH - - , - - ROOT - - ( - - S_CHAR_LITERAL - ) - - INCLUDE_NULL_VALUES - - WITHOUT_ARRAY_WRAPPER - - -
- - -
         ::= 'FOR' ( 'BROWSE' | 'XML' ( ( 'RAW' ( '(' S_CHAR_LITERAL ')' )? | 'AUTO' ) ( ',' ( 'BINARY' 'BASE64' | 'TYPE' | ( 'ROOT' | 'XMLSCHEMA' ) ( - '(' S_CHAR_LITERAL ')' )? | 'XMLDATA' | 'ELEMENTS' ( 'XSINIL' | 'ABSENT' )? ) )* | 'EXPLICIT' ( ',' - ( 'BINARY' 'BASE64' | 'TYPE' | 'ROOT' ( '(' S_CHAR_LITERAL ')' )? | 'XMLDATA' ) )* | 'PATH' ( '(' S_CHAR_LITERAL ')' )? ( ',' ( 'BINARY' 'BASE64' | 'TYPE' | 'ROOT' ( '(' S_CHAR_LITERAL ')' )? | 'ELEMENTS' ( 'XSINIL' | 'ABSENT' )? ) )* ) | 'JSON' ( 'AUTO' | 'PATH' ) - ( ',' ( 'ROOT' ( '(' S_CHAR_LITERAL ')' )? | 'INCLUDE_NULL_VALUES' | 'WITHOUT_ARRAY_WRAPPER' ) )* )
-
- Referenced by: -
- - -====================================================================================================================== -LateralViews -====================================================================================================================== - - -.. raw:: html - - - - - - LateralView - -
- - -
         ::= LateralView+
-
- Referenced by: -
- - -====================================================================================================================== -LateralSubSelect -====================================================================================================================== - - -.. raw:: html - - - - - - LATERAL - - ( - - Select - ) - - -
- - -
         ::= 'LATERAL' '(' Select ')'
-
- Referenced by: -
- - -====================================================================================================================== -PlainSelect -====================================================================================================================== - - -.. raw:: html - - - - - - K_SELECT - STRAIGHT_JOIN - - Skip - - First - - Top - ALL - - DISTINCT - - ON - - ( - - SelectItemsList - ) - - DISTINCTROW - - UNIQUE - - SQL_CALC_FOUND_ROWS - - SQL_NO_CACHE - - SQL_CACHE - - AS - - STRUCT - - VALUE - - Top - - SelectItemsList - - IntoClause - FROM - - FromItem - - LateralViews - - JoinsList - FROM - - ONLY - - FromItem - - LateralViews - - JoinsList - FINAL - - KSQLWindowClause - - PreWhereClause - - WhereClause - - OracleHierarchicalQueryClause - - PreferringClause - PARTITION - - BY - - ComplexExpressionList - ( - - ComplexExpressionList - ) - - Having - - GroupByColumnReferences - - Having - - Qualify - - OrderByElements - WINDOW - - RelObjectName - AS - - windowDefinition - , - - OrderByElements - - ForClause - EMIT - - CHANGES - - LimitBy - - LimitWithOffset - - Offset - - LimitWithOffset - - Fetch - - WithIsolation - FOR - - NO - - KEY - - UPDATE - - KEY - - SHARE - - READ - - FETCH - - ONLY - - OF - - Table - - Wait - NOWAIT - - SKIP - - LOCKED - - SETTINGS - - UpdateSets - - OptimizeFor - INTO - - TEMP - - Table - WITH - - NO - - LOG - - -
- - -
         ::= K_SELECT 'STRAIGHT_JOIN'? Skip? First? Top? ( 'ALL' | 'DISTINCT' ( 'ON' '(' SelectItemsList ')' )? | 'DISTINCTROW' | 'UNIQUE' | 'SQL_CALC_FOUND_ROWS' | 'SQL_NO_CACHE' | 'SQL_CACHE' - )? ( 'AS' ( 'STRUCT' | 'VALUE' ) )? Top? SelectItemsList IntoClause? ( 'FROM' FromItem LateralViews? JoinsList? )? ( 'FROM' 'ONLY' FromItem LateralViews? JoinsList? )? 'FINAL'? KSQLWindowClause? PreWhereClause? WhereClause? OracleHierarchicalQueryClause? ( PreferringClause ( 'PARTITION' 'BY' ( ComplexExpressionList | '(' ComplexExpressionList ')' ) )? )? Having? GroupByColumnReferences? Having? Qualify? OrderByElements? ( 'WINDOW' RelObjectName 'AS' windowDefinition ( ',' RelObjectName 'AS' windowDefinition )* )? OrderByElements? ForClause? ( 'EMIT' 'CHANGES' )? LimitBy? LimitWithOffset? Offset? LimitWithOffset? Fetch? WithIsolation? ( 'FOR' ( ( 'NO' 'KEY' )? 'UPDATE' | 'KEY'? 'SHARE' | ( 'READ' | 'FETCH' ) 'ONLY' - ) ( 'OF' Table )? Wait? ( 'NOWAIT' | 'SKIP' 'LOCKED' )? )? ( 'SETTINGS' UpdateSets )? OptimizeFor? ( 'INTO' 'TEMP' Table )? ( 'WITH' 'NO' 'LOG' )?
-
- Referenced by: -
- - -====================================================================================================================== -SetOperationList -====================================================================================================================== - - -.. raw:: html - - - - - - UNION - - INTERSECT - - MINUS - - EXCEPT - - SetOperationModifier - - PlainSelect - - Values - - ParenthesedSelect - - OrderByElements - - LimitWithOffset - - Offset - - LimitWithOffset - - Fetch - - WithIsolation - -
- - -
         ::= ( ( 'UNION' | 'INTERSECT' | 'MINUS' | 'EXCEPT' ) SetOperationModifier? ( PlainSelect | Values | ParenthesedSelect ) )+ OrderByElements? LimitWithOffset? Offset? LimitWithOffset? Fetch? WithIsolation?
-
- Referenced by: -
- - -====================================================================================================================== -WithList -====================================================================================================================== - - -.. raw:: html - - - - - - WITH - - WithItem - , - - -
- -
WithList ::= 'WITH' WithItem ( ',' WithItem )*
-
- Referenced by: -
- - -====================================================================================================================== -WithItem -====================================================================================================================== - - -.. raw:: html - - - - - - FUNCTION - - WithFunctionDeclaration - RECURSIVE - - RelObjectName - ( - - SelectItemsList - ) - - AS - - NOT - - MATERIALIZED - - ParenthesedSelect - - ParenthesedInsert - - ParenthesedUpdate - - ParenthesedDelete - - WithSearchClause - -
- -
WithItem ::= ( 'FUNCTION' WithFunctionDeclaration | 'RECURSIVE'? RelObjectName ( '(' SelectItemsList ')' )? 'AS' ( 'NOT'? 'MATERIALIZED' )? ( ParenthesedSelect | ParenthesedInsert | ParenthesedUpdate | ParenthesedDelete ) ) WithSearchClause?
-
- Referenced by: -
- - -====================================================================================================================== -WithSearchClause -====================================================================================================================== - - -.. raw:: html - - - - - - SEARCH - - BREADTH - - DEPTH - - FIRST - - BY - - Column - , - - SET - - RelObjectName - -
- - -
         ::= 'SEARCH' ( 'BREADTH' | 'DEPTH' ) 'FIRST' 'BY' Column ( ',' Column )* 'SET' RelObjectName
-
- Referenced by: -
- - -====================================================================================================================== -WithFunctionDeclaration -====================================================================================================================== - - -.. raw:: html - - - - - - RelObjectName - ( - - WithFunctionParameter - , - - ) - - RETURNS - - RelObjectName - RETURN - - Expression - -
- - -
         ::= RelObjectName '(' ( WithFunctionParameter ( ',' WithFunctionParameter )* )? ')' 'RETURNS' RelObjectName 'RETURN' Expression
-
- Referenced by: -
- - -====================================================================================================================== -WithFunctionParameter -====================================================================================================================== - - -.. raw:: html - - - - - - RelObjectName - ARRAY - - < - - RelObjectName - > - - RelObjectName - -
- - -
         ::= RelObjectName ( 'ARRAY' '<' RelObjectName '>' | RelObjectName )
-
- Referenced by: -
- - -====================================================================================================================== -ColumnSelectItemsList -====================================================================================================================== - - -.. raw:: html - - - - - - SelectItem - , - - -
- - -
         ::= SelectItem ( ',' SelectItem )*
-
- Referenced by: -
- - -====================================================================================================================== -SelectItemsList -====================================================================================================================== - - -.. raw:: html - - - - - - SelectItem - , - - -
- - -
         ::= SelectItem ( ',' SelectItem )*
-
- - -====================================================================================================================== -FunctionAllColumns -====================================================================================================================== - - -.. raw:: html - - - - - - ( - - Function - ) - - . - - * - - -
- - -
         ::= '('+ Function ')'+ '.' '*'
-
- Not referenced by any. -
- - -====================================================================================================================== -SelectItem -====================================================================================================================== - - -.. raw:: html - - - - - - ConnectByPriorOperator - - XorExpression - - ConcatExpression - - Expression - - Alias - -
- - - -
- - -====================================================================================================================== -AllColumns -====================================================================================================================== - - -.. raw:: html - - - - - - * - - EXCEPT - - EXCLUDE - - ParenthesedColumnList - REPLACE - - ( - - SelectItemsList - ) - - -
- - -
         ::= '*' ( ( 'EXCEPT' | 'EXCLUDE' ) ParenthesedColumnList )? ( 'REPLACE' '(' SelectItemsList ')' )?
-
- - -====================================================================================================================== -AllTableColumns -====================================================================================================================== - - -.. raw:: html - - - - - - Table - . - - AllColumns - -
- - -
         ::= Table '.' AllColumns
-
- - -====================================================================================================================== -Alias -====================================================================================================================== - - -.. raw:: html - - - - - - AS - - RelObjectName - ( - - RelObjectName - - ColDataType - , - - ) - - AS - - RelObjectName - - S_CHAR_LITERAL - ( - - RelObjectName - - ColDataType - , - - ) - - -
- - -
           | 'AS'? ( RelObjectName | S_CHAR_LITERAL ) ( '(' RelObjectName ColDataType? ( ',' RelObjectName ColDataType? )* ')' )?
-
- - -====================================================================================================================== -SQLServerHint -====================================================================================================================== - - -.. raw:: html - - - - - - INDEX - - ( - - RelObjectName - ) - - NOLOCK - - -
- - -
         ::= 'INDEX' '(' RelObjectName ')'
-
           | 'NOLOCK'
-
- Referenced by: -
- - -====================================================================================================================== -SQLServerHints -====================================================================================================================== - - -.. raw:: html - - - - - - WITH - - ( - - SQLServerHint - , - - ) - - -
- - -
         ::= 'WITH' '(' SQLServerHint ( ',' SQLServerHint )* ')'
-
- Referenced by: -
- - -====================================================================================================================== -MySQLIndexHint -====================================================================================================================== - - -.. raw:: html - - - - - - USE - - SHOW - - IGNORE - - FORCE - - INDEX - - KEY - - ( - - RelObjectName - , - - ) - - -
- - -
         ::= ( 'USE' | 'SHOW' | 'IGNORE' | 'FORCE' ) ( 'INDEX' | 'KEY' ) '(' RelObjectName ( ',' RelObjectName )* ')'
-
- - -====================================================================================================================== -FunctionItem -====================================================================================================================== - - -.. raw:: html - - - - - - Function - - Alias - -
- - -
         ::= Function Alias?
-
- Referenced by: -
- - -====================================================================================================================== -PivotForColumns -====================================================================================================================== - - -.. raw:: html - - - - - - ParenthesedColumnList - - Column - -
- - -
         ::= ParenthesedColumnList
-
           | Column
-
- Referenced by: -
- - -====================================================================================================================== -PivotFunctionItems -====================================================================================================================== - - -.. raw:: html - - - - - - FunctionItem - , - - -
- - -
         ::= FunctionItem ( ',' FunctionItem )*
-
- Referenced by: -
- - -====================================================================================================================== -ExpressionListItem -====================================================================================================================== - - -.. raw:: html - - - - - - ParenthesedExpressionList - - Alias - -
- - -
         ::= ParenthesedExpressionList Alias?
-
- Referenced by: -
- - -====================================================================================================================== -PivotMultiInItems -====================================================================================================================== - - -.. raw:: html - - - - - - ExpressionListItem - , - - -
- - -
         ::= ExpressionListItem ( ',' ExpressionListItem )*
-
- Referenced by: -
- - -====================================================================================================================== -Pivot -====================================================================================================================== - - -.. raw:: html - - - - - - PIVOT - - ( - - PivotFunctionItems - FOR - - PivotForColumns - IN - - ( - - SelectItemsList - - PivotMultiInItems - ) - - ) - - Alias - -
- -
Pivot    ::= 'PIVOT' '(' PivotFunctionItems 'FOR' PivotForColumns 'IN' '(' ( SelectItemsList | PivotMultiInItems ) ')' ')' Alias?
-
- Referenced by: -
- - -====================================================================================================================== -PivotXml -====================================================================================================================== - - -.. raw:: html - - - - - - PIVOT - - XML - - ( - - PivotFunctionItems - FOR - - PivotForColumns - IN - - ( - - ANY - - Select - - SelectItemsList - - PivotMultiInItems - ) - - ) - - -
- -
PivotXml ::= 'PIVOT' 'XML' '(' PivotFunctionItems 'FOR' PivotForColumns 'IN' '(' ( 'ANY' | Select | SelectItemsList | PivotMultiInItems ) ')' ')'
-
- Referenced by: -
- - -====================================================================================================================== -UnPivot -====================================================================================================================== - - -.. raw:: html - - - - - - UNPIVOT - - INCLUDE - - EXCLUDE - - NULLS - - ( - - PivotForColumns - FOR - - PivotForColumns - IN - - ( - - SelectItemsList - ) - - ) - - Alias - -
- -
UnPivot  ::= 'UNPIVOT' ( ( 'INCLUDE' | 'EXCLUDE' ) 'NULLS' )? '(' PivotForColumns 'FOR' PivotForColumns 'IN' '(' SelectItemsList ')' ')' Alias?
-
- Referenced by: -
- - -====================================================================================================================== -IntoClause -====================================================================================================================== - - -.. raw:: html - - - - - - INTO - - Table - , - - -
- - -
         ::= 'INTO' Table ( ',' Table )*
-
- Referenced by: -
- - -====================================================================================================================== -ParenthesedFromItem -====================================================================================================================== - - -.. raw:: html - - - - - - ( - - FromItem - - JoinsList - ) - - -
- - -
         ::= '(' FromItem JoinsList? ')'
-
- Referenced by: -
- - -====================================================================================================================== -FromItem -====================================================================================================================== - - -.. raw:: html - - - - - - Values - - TableFunction - - Table - - ParenthesedFromItem - - ParenthesedSelect - - Pivot - - UnPivot - - LateralSubSelect - - SubImport - - Select - - Alias - - TimeTravelAfterAlias - - SampleClause - - UnPivot - - PivotXml - - Pivot - - MySQLIndexHint - - SQLServerHints - -
- - -
- - -====================================================================================================================== -JoinsList -====================================================================================================================== - - -.. raw:: html - - - - - - JoinerExpression - -
- - -
         ::= JoinerExpression+
-
- - -====================================================================================================================== -JoinHint -====================================================================================================================== - - -.. raw:: html - - - - - - LOOP - - HASH - - MERGE - - REMOTE - - -
- -
JoinHint ::= 'LOOP'
-
           | 'HASH'
-
           | 'MERGE'
-
           | 'REMOTE'
-
- Referenced by: -
- - -====================================================================================================================== -JoinerExpression -====================================================================================================================== - - -.. raw:: html - - - - - - GLOBAL - - ANY - - ALL - - NATURAL - - LEFT - - SEMI - - OUTER - - ANY - - ALL - - RIGHT - - FULL - - OUTER - - ANY - - ALL - - INNER - - CROSS - - OUTER - - JoinHint - JOIN - - FETCH - - , - - OUTER - - STRAIGHT_JOIN - - APPLY - - FromItem - WITHIN - - ( - - JoinWindow - ) - - ON - - Expression - USING - - ( - - Column - , - - ) - - -
- - -
         ::= 'GLOBAL'? ( 'ANY' | 'ALL' )? 'NATURAL'? ( 'LEFT' ( 'SEMI' | 'OUTER' | - 'ANY' | 'ALL' )? | ( 'RIGHT' | 'FULL' ) ( 'OUTER' | 'ANY' | 'ALL' )? | 'INNER' | 'CROSS' - | 'OUTER' )? ( JoinHint? 'JOIN' 'FETCH'? | ',' 'OUTER'? | 'STRAIGHT_JOIN' | 'APPLY' ) FromItem ( ( 'WITHIN' '(' JoinWindow ')' )? ( 'ON' Expression )+ | 'USING' '(' Column ( ',' Column )* ')' )?
-
- Referenced by: -
- - -====================================================================================================================== -JoinWindow -====================================================================================================================== - - -.. raw:: html - - - - - - S_LONG - - S_IDENTIFIER - - K_DATE_LITERAL - , - - S_LONG - - S_IDENTIFIER - - K_DATE_LITERAL - -
- - -
         ::= S_LONG ( S_IDENTIFIER | K_DATE_LITERAL ) ( ',' S_LONG ( S_IDENTIFIER | K_DATE_LITERAL ) )?
-
- Referenced by: -
- - -====================================================================================================================== -KSQLWindowClause -====================================================================================================================== - - -.. raw:: html - - - - - - WINDOW - - HOPPING - - ( - - SIZE - - S_LONG - - S_IDENTIFIER - , - - ADVANCE - - BY - - SESSION - - ( - - TUMBLING - - ( - - SIZE - - S_LONG - - S_IDENTIFIER - ) - - -
- - -
         ::= 'WINDOW' ( 'HOPPING' '(' 'SIZE' S_LONG S_IDENTIFIER ',' 'ADVANCE' 'BY' | 'SESSION' '(' | 'TUMBLING' '(' 'SIZE' ) S_LONG S_IDENTIFIER ')'
-
- Referenced by: -
- - -====================================================================================================================== -WhereClause -====================================================================================================================== - - -.. raw:: html - - - - - - WHERE - - Expression - -
- - -
         ::= 'WHERE' Expression
-
- - -====================================================================================================================== -PreWhereClause -====================================================================================================================== - - -.. raw:: html - - - - - - PREWHERE - - Expression - -
- - -
         ::= 'PREWHERE' Expression
-
- Referenced by: -
- - -====================================================================================================================== -OracleHierarchicalQueryClause -====================================================================================================================== - - -.. raw:: html - - - - - - START - - WITH - - XorExpression - CONNECT - - BY - - NOCYCLE - - CONNECT - - BY - - NOCYCLE - - XorExpression - START - - WITH - - XorExpression - -
- - -
         ::= ( 'START' 'WITH' XorExpression 'CONNECT' 'BY' 'NOCYCLE'? | 'CONNECT' 'BY' 'NOCYCLE'? ( XorExpression 'START' 'WITH' )? ) XorExpression
-
- Referenced by: -
- - -====================================================================================================================== -PreferringClause -====================================================================================================================== - - -.. raw:: html - - - - - - PREFERRING - - PreferenceTerm - -
- - -
         ::= 'PREFERRING' PreferenceTerm
-
- Referenced by: -
- - -====================================================================================================================== -PreferenceTerm -====================================================================================================================== - - -.. raw:: html - - - - - - Plus - -
- - -
         ::= Plus
-
- Referenced by: -
- - -====================================================================================================================== -Plus -====================================================================================================================== - - -.. raw:: html - - - - - - PriorTo - PLUS - - -
- -
Plus     ::= PriorTo ( 'PLUS' PriorTo )*
-
- Referenced by: -
- - -====================================================================================================================== -PriorTo -====================================================================================================================== - - -.. raw:: html - - - - - - PreferenceTermTerminal - ( - - PreferenceTerm - ) - - TO - - PRIOR - - -
- -
PriorTo  ::= ( PreferenceTermTerminal | '(' PreferenceTerm ')' ) ( 'PRIOR' 'TO' ( PreferenceTermTerminal | '(' PreferenceTerm ')' ) )*
-
- Referenced by: -
- - -====================================================================================================================== -PreferenceTermTerminal -====================================================================================================================== - - -.. raw:: html - - - - - - HighExpression - - LowExpression - - Inverse - - Condition - -
- - -
         ::= HighExpression
-
           | LowExpression
-
           | Inverse
-
           | Condition
-
- Referenced by: -
- - -====================================================================================================================== -HighExpression -====================================================================================================================== - - -.. raw:: html - - - - - - HIGH - - Expression - -
- - -
         ::= 'HIGH' Expression
-
- Referenced by: -
- - -====================================================================================================================== -LowExpression -====================================================================================================================== - - -.. raw:: html - - - - - - LOW - - Expression - -
- - -
         ::= 'LOW' Expression
-
- Referenced by: -
- - -====================================================================================================================== -Inverse -====================================================================================================================== - - -.. raw:: html - - - - - - INVERSE - - ( - - PreferenceTerm - ) - - -
- -
Inverse  ::= 'INVERSE' '(' PreferenceTerm ')'
-
- Referenced by: -
- - -====================================================================================================================== -GroupByColumnReferences -====================================================================================================================== - - -.. raw:: html - - - - - - GROUP - - BY - - GROUPING - - SETS - - ( - - GroupingSet - , - - ) - - ExpressionList - GROUPING - - SETS - - ( - - GroupingSet - , - - ) - - WITH - - ROLLUP - - -
- - -
         ::= 'GROUP' 'BY' ( 'GROUPING' 'SETS' '(' GroupingSet ( ',' GroupingSet )* ')' | ExpressionList ( 'GROUPING' 'SETS' '(' GroupingSet ( ',' GroupingSet )* ')' )? ( 'WITH' 'ROLLUP' )? )
-
- Referenced by: -
- - -====================================================================================================================== -GroupingSet -====================================================================================================================== - - -.. raw:: html - - - - - - ParenthesedExpressionList - - SimpleExpression - -
- - -
         ::= ParenthesedExpressionList
-
           | SimpleExpression
-
- Referenced by: -
- - -====================================================================================================================== -Having -====================================================================================================================== - - -.. raw:: html - - - - - - HAVING - - Expression - -
- -
Having   ::= 'HAVING' Expression
-
- Referenced by: -
- - -====================================================================================================================== -Qualify -====================================================================================================================== - - -.. raw:: html - - - - - - QUALIFY - - Expression - -
- -
Qualify  ::= 'QUALIFY' Expression
-
- Referenced by: -
- - -====================================================================================================================== -OrderByElements -====================================================================================================================== - - -.. raw:: html - - - - - - ORDER - - SIBLINGS - - BY - - OrderByElement - , - - -
- - -
         ::= 'ORDER' 'SIBLINGS'? 'BY' OrderByElement ( ',' OrderByElement )*
-
- - -====================================================================================================================== -OrderByElement -====================================================================================================================== - - -.. raw:: html - - - - - - Expression - COLLATE - - S_CHAR_LITERAL - - S_QUOTED_IDENTIFIER - ASC - - DESC - - NULLS - - FIRST - - LAST - - WITH - - ROLLUP - - -
- - -
         ::= Expression ( 'COLLATE' ( S_CHAR_LITERAL | S_QUOTED_IDENTIFIER ) )? ( 'ASC' | 'DESC' )? ( 'NULLS' ( 'FIRST' | 'LAST' )? )? ( 'WITH' 'ROLLUP' )?
-
- Referenced by: -
- - -====================================================================================================================== -JdbcParameter -====================================================================================================================== - - -.. raw:: html - - - - - - ? - - S_PARAMETER - - S_LONG - -
- - -
         ::= ( '?' | S_PARAMETER ) S_LONG?
-
- - -====================================================================================================================== -LimitWithOffset -====================================================================================================================== - - -.. raw:: html - - - - - - LIMIT - - ParenthesedSelect - - Expression - , - - Expression - -
- - -
         ::= 'LIMIT' ( ParenthesedSelect | Expression ) ( ',' Expression )?
-
- - -====================================================================================================================== -PlainLimit -====================================================================================================================== - - -.. raw:: html - - - - - - LIMIT - - ParenthesedSelect - - Expression - -
- - -
         ::= 'LIMIT' ( ParenthesedSelect | Expression )
-
- Referenced by: -
- - -====================================================================================================================== -LimitBy -====================================================================================================================== - - -.. raw:: html - - - - - - LimitWithOffset - BY - - ExpressionList - -
- - -
- Referenced by: -
- - -====================================================================================================================== -Offset -====================================================================================================================== - - -.. raw:: html - - - - - - OFFSET - - Expression - ROWS - - ROW - - -
- -
Offset   ::= 'OFFSET' Expression ( 'ROWS' | 'ROW' )?
-
- - -====================================================================================================================== -Fetch -====================================================================================================================== - - -.. raw:: html - - - - - - FETCH - - FIRST - - NEXT - - Expression - PERCENT - - ROWS - - ROW - - ONLY - - WITH TIES - - -
- -
Fetch    ::= 'FETCH' ( 'FIRST' | 'NEXT' ) ( Expression 'PERCENT'? )? ( 'ROWS' | 'ROW' ) ( 'ONLY' | 'WITH TIES' )
-
- - -====================================================================================================================== -WithIsolation -====================================================================================================================== - - -.. raw:: html - - - - - - WITH - - K_ISOLATION - -
- - -
         ::= 'WITH' K_ISOLATION
-
- - -====================================================================================================================== -OptimizeFor -====================================================================================================================== - - -.. raw:: html - - - - - - OPTIMIZE - - FOR - - S_LONG - ROWS - - -
- - -
         ::= 'OPTIMIZE' 'FOR' S_LONG 'ROWS'
-
- Referenced by: -
- - -====================================================================================================================== -Top -====================================================================================================================== - - -.. raw:: html - - - - - - TOP - - S_LONG - - JdbcParameter - : - - S_IDENTIFIER - ( - - AdditiveExpression - ) - - PERCENT - - WITH TIES - - -
- -
Top      ::= 'TOP' ( S_LONG | JdbcParameter | ':' S_IDENTIFIER? | '(' AdditiveExpression ')' ) 'PERCENT'? 'WITH TIES'?
-
- Referenced by: -
- - -====================================================================================================================== -Skip -====================================================================================================================== - - -.. raw:: html - - - - - - SKIP - - S_LONG - - S_IDENTIFIER - - JdbcParameter - -
- -
Skip     ::= 'SKIP' ( S_LONG | S_IDENTIFIER | JdbcParameter )
-
- Referenced by: -
- - -====================================================================================================================== -First -====================================================================================================================== - - -.. raw:: html - - - - - - FIRST - - LIMIT - - S_LONG - - S_IDENTIFIER - - JdbcParameter - -
- -
First    ::= ( 'FIRST' | 'LIMIT' ) ( S_LONG | S_IDENTIFIER | JdbcParameter )
-
- Referenced by: -
- - -====================================================================================================================== -Expression -====================================================================================================================== - - -.. raw:: html - - - - - - XorExpression - -
- - -
         ::= XorExpression
-
- - -====================================================================================================================== -XorExpression -====================================================================================================================== - - -.. raw:: html - - - - - - OrExpression - XOR - - -
- - -
         ::= OrExpression ( 'XOR' OrExpression )*
-
- - -====================================================================================================================== -OrExpression -====================================================================================================================== - - -.. raw:: html - - - - - - AndExpression - OR - - -
- - -
         ::= AndExpression ( 'OR' AndExpression )*
-
- Referenced by: -
- - -====================================================================================================================== -AndExpression -====================================================================================================================== - - -.. raw:: html - - - - - - Condition - NOT - - ! - - ( - - XorExpression - ) - - AND - - && - - -
- - -
         ::= ( Condition | ( 'NOT' | '!' )? '(' XorExpression ')' ) ( ( 'AND' | '&&' ) ( Condition | ( 'NOT' | '!' )? '(' XorExpression ')' ) )*
-
- Referenced by: -
- - -====================================================================================================================== -Condition -====================================================================================================================== - - -.. raw:: html - - - - - - NOT - - ! - - ExistsExpression - PRIOR - - SimpleExpression - ( - - + - - ) - - RegularConditionRHS - - OverlapsCondition - - InExpression - - ExcludesExpression - - IncludesExpression - - Between - - MemberOfExpression - - IsNullExpression - - IsBooleanExpression - - IsUnknownExpression - - LikeExpression - - IsDistinctExpression - - SimilarToExpression - -
- - - -
- - -====================================================================================================================== -RegularConditionRHS -====================================================================================================================== - - -.. raw:: html - - - - - - ( - - + - - ) - - > - - < - - = - - OP_GREATERTHANEQUALS - - OP_MINORTHANEQUALS - - OP_NOTEQUALSSTANDARD - - OP_NOTEQUALSBANG - - OP_NOTEQUALSHAT - *= - - =* - - && - - &> - - <& - - @@ - - ~ - - ~* - - !~ - - !~* - - @> - - <@ - - ? - - ?| - - ?& - - OP_CONCAT - - - - -# - - <-> - - <#> - - <=> - - PRIOR - - ComparisonItem - ( - - + - - ) - - -
- - -
         ::= ( '(' '+' ')' )? ( '>' | '<' | '=' | OP_GREATERTHANEQUALS | OP_MINORTHANEQUALS | OP_NOTEQUALSSTANDARD | OP_NOTEQUALSBANG | OP_NOTEQUALSHAT | '*=' | '=*' | '&&' | '&>' | '<&' | '@@' | '~' | '~*' | '!~' | '!~*' | '@>' | '<@' - | '?' | '?|' | '?&' | OP_CONCAT | '-' | '-#' | '<->' | '<#>' | '<=>' ) 'PRIOR'? ComparisonItem ( '(' '+' ')' )?
-
- Referenced by: -
- - -====================================================================================================================== -OverlapsCondition -====================================================================================================================== - - -.. raw:: html - - - - - - OVERLAPS - - ParenthesedExpressionList - -
- - -
         ::= 'OVERLAPS' ParenthesedExpressionList
-
- Referenced by: -
- - -====================================================================================================================== -SQLCondition -====================================================================================================================== - - -.. raw:: html - - - - - - ExistsExpression - - SimpleExpression - - OverlapsCondition - - InExpression - - ExcludesExpression - - IncludesExpression - - Between - - MemberOfExpression - - IsNullExpression - - IsBooleanExpression - - IsUnknownExpression - - LikeExpression - - IsDistinctExpression - - SimilarToExpression - -
- - -
         ::= ExistsExpression
-
-
- Not referenced by any. -
- - -====================================================================================================================== -InExpression -====================================================================================================================== - - -.. raw:: html - - - - - - ( - - + - - ) - - GLOBAL - - NOT - - IN - - S_CHAR_LITERAL - - PrimaryExpression - -
- - -
         ::= ( '(' '+' ')' )? 'GLOBAL'? 'NOT'? 'IN' ( S_CHAR_LITERAL | PrimaryExpression )
-
- Referenced by: -
- - -====================================================================================================================== -IncludesExpression -====================================================================================================================== - - -.. raw:: html - - - - - - INCLUDES - - ParenthesedExpressionList - -
- - -
         ::= 'INCLUDES' ParenthesedExpressionList
-
- Referenced by: -
- - -====================================================================================================================== -ExcludesExpression -====================================================================================================================== - - -.. raw:: html - - - - - - EXCLUDES - - ParenthesedExpressionList - -
- - -
         ::= 'EXCLUDES' ParenthesedExpressionList
-
- Referenced by: -
- - -====================================================================================================================== -Between -====================================================================================================================== - - -.. raw:: html - - - - - - NOT - - BETWEEN - - SYMMETRIC - - ASYMMETRIC - - ParenthesedSelect - - SimpleExpression - - RegularConditionRHS - AND - - ParenthesedSelect - - SimpleExpression - - RegularConditionRHS - -
- -
Between  ::= 'NOT'? 'BETWEEN' ( 'SYMMETRIC' | 'ASYMMETRIC' )? ( ParenthesedSelect | SimpleExpression RegularConditionRHS? ) 'AND' ( ParenthesedSelect | SimpleExpression RegularConditionRHS? )
-
- Referenced by: -
- - -====================================================================================================================== -LikeExpression -====================================================================================================================== - - -.. raw:: html - - - - - - NOT - - LIKE - - ILIKE - - RLIKE - - REGEXP_LIKE - - REGEXP - - K_SIMILAR_TO - MATCH_ANY - - MATCH_ALL - - MATCH_PHRASE - - MATCH_PHRASE_PREFIX - - MATCH_REGEXP - - BINARY - - SimpleExpression - ESCAPE - - S_CHAR_LITERAL - - Expression - -
- - -
         ::= 'NOT'? ( 'LIKE' | 'ILIKE' | 'RLIKE' | 'REGEXP_LIKE' | 'REGEXP' | K_SIMILAR_TO | 'MATCH_ANY' | 'MATCH_ALL' | 'MATCH_PHRASE' | 'MATCH_PHRASE_PREFIX' | 'MATCH_REGEXP' - ) 'BINARY'? SimpleExpression ( 'ESCAPE' ( S_CHAR_LITERAL | Expression ) )?
-
- Referenced by: -
- - -====================================================================================================================== -SimilarToExpression -====================================================================================================================== - - -.. raw:: html - - - - - - NOT - - SIMILAR - - TO - - SimpleExpression - ESCAPE - - S_CHAR_LITERAL - -
- - -
         ::= 'NOT'? 'SIMILAR' 'TO' SimpleExpression ( 'ESCAPE' S_CHAR_LITERAL )?
-
- Referenced by: -
- - -====================================================================================================================== -IsDistinctExpression -====================================================================================================================== - - -.. raw:: html - - - - - - IS - - NOT - - DISTINCT - - FROM - - SimpleExpression - -
- - -
         ::= 'IS' 'NOT'? 'DISTINCT' 'FROM' SimpleExpression
-
- Referenced by: -
- - -====================================================================================================================== -IsNullExpression -====================================================================================================================== - - -.. raw:: html - - - - - - NOT - - ISNULL - - NOTNULL - - IS - - NOT - - NULL - - -
- - -
         ::= 'NOT'? 'ISNULL'
-
           | 'NOTNULL'
-
           | 'IS' 'NOT'? 'NULL'
-
- Referenced by: -
- - -====================================================================================================================== -IsBooleanExpression -====================================================================================================================== - - -.. raw:: html - - - - - - IS - - NOT - - TRUE - - FALSE - - -
- - -
         ::= 'IS' 'NOT'? ( 'TRUE' | 'FALSE' )
-
- Referenced by: -
- - -====================================================================================================================== -IsUnknownExpression -====================================================================================================================== - - -.. raw:: html - - - - - - IS - - NOT - - UNKNOWN - - -
- - -
         ::= 'IS' 'NOT'? 'UNKNOWN'
-
- Referenced by: -
- - -====================================================================================================================== -ExistsExpression -====================================================================================================================== - - -.. raw:: html - - - - - - EXISTS - - SimpleExpression - -
- - -
         ::= 'EXISTS' SimpleExpression
-
- Referenced by: -
- - -====================================================================================================================== -MemberOfExpression -====================================================================================================================== - - -.. raw:: html - - - - - - MEMBER - - OF - - Expression - -
- - -
         ::= 'MEMBER' 'OF' Expression
-
- Referenced by: -
- - -====================================================================================================================== -ExpressionList -====================================================================================================================== - - -.. raw:: html - - - - - - ComplexExpressionList - - SimpleExpressionList - - ParenthesedExpressionList - -
- - -
         ::= ComplexExpressionList
-
           | SimpleExpressionList
-
           | ParenthesedExpressionList
-
- - -====================================================================================================================== -ParenthesedExpressionList -====================================================================================================================== - - -.. raw:: html - - - - - - ( - - ComplexExpressionList - - SimpleExpressionList - ) - - -
- - -
         ::= '(' ( ComplexExpressionList | SimpleExpressionList )? ')'
-
- - -====================================================================================================================== -SimpleExpressionList -====================================================================================================================== - - -.. raw:: html - - - - - - SimpleExpression - , - - LambdaExpression - - SimpleExpression - -
- - -
         ::= SimpleExpression ( ',' ( LambdaExpression | SimpleExpression ) )*
-
- - -====================================================================================================================== -ColumnList -====================================================================================================================== - - -.. raw:: html - - - - - - Column - , - - -
- - -
         ::= Column ( ',' Column )*
-
- - -====================================================================================================================== -ParenthesedColumnList -====================================================================================================================== - - -.. raw:: html - - - - - - ( - - ColumnList - ) - - -
- - -
         ::= '(' ColumnList ')'
-
- - -====================================================================================================================== -ComplexExpressionList -====================================================================================================================== - - -.. raw:: html - - - - - - OracleNamedFunctionParameter - - PostgresNamedFunctionParameter - - Expression - , - - OracleNamedFunctionParameter - - PostgresNamedFunctionParameter - - LambdaExpression - - Expression - -
- - - -
- - -====================================================================================================================== -NamedExpressionListExprFirst -====================================================================================================================== - - -.. raw:: html - - - - - - SimpleExpression - FROM - - IN - - PLACING - - SimpleExpression - FOR - - FROM - - SimpleExpression - FOR - - SimpleExpression - -
- - -
         ::= SimpleExpression ( 'FROM' | 'IN' | 'PLACING' ) SimpleExpression ( ( 'FOR' | 'FROM' ) SimpleExpression ( 'FOR' SimpleExpression )? )?
-
- - -====================================================================================================================== -ComparisonItem -====================================================================================================================== - - -.. raw:: html - - - - - - AnyComparisonExpression - - SimpleExpression - - ParenthesedExpressionList - - RowConstructor - - PrimaryExpression - -
- - -
         ::= AnyComparisonExpression
-
           | SimpleExpression
-
           | ParenthesedExpressionList
-
           | RowConstructor
-
           | PrimaryExpression
-
- Referenced by: -
- - -====================================================================================================================== -AnyComparisonExpression -====================================================================================================================== - - -.. raw:: html - - - - - - ANY - - SOME - - ALL - - ParenthesedSelect - -
- - -
         ::= ( 'ANY' | 'SOME' | 'ALL' ) ParenthesedSelect
-
- Referenced by: -
- - -====================================================================================================================== -SimpleExpression -====================================================================================================================== - - -.. raw:: html - - - - - - UserVariable - = - - := - - ConcatExpression - -
- - -
         ::= ( UserVariable ( '=' | ':=' ) )? ConcatExpression
-
- - -====================================================================================================================== -ConcatExpression -====================================================================================================================== - - -.. raw:: html - - - - - - BitwiseAndOr - - OP_CONCAT - -
- - -
         ::= BitwiseAndOr ( OP_CONCAT BitwiseAndOr )*
-
- Referenced by: -
- - -====================================================================================================================== -BitwiseAndOr -====================================================================================================================== - - -.. raw:: html - - - - - - AdditiveExpression - | - - & - - << - - >> - - -
- - -
         ::= AdditiveExpression ( ( '|' | '&' | '<<' | '>>' ) AdditiveExpression )*
-
- Referenced by: -
- - -====================================================================================================================== -AdditiveExpression -====================================================================================================================== - - -.. raw:: html - - - - - - MultiplicativeExpression - + - - - - - -
- - -
         ::= MultiplicativeExpression ( ( '+' | '-' ) MultiplicativeExpression )*
-
- Referenced by: -
- - -====================================================================================================================== -MultiplicativeExpression -====================================================================================================================== - - -.. raw:: html - - - - - - BitwiseXor - * - - / - - DIV - - % - - -
- - -
         ::= BitwiseXor ( ( '*' | '/' | 'DIV' | '%' ) BitwiseXor )*
-
- Referenced by: -
- - -====================================================================================================================== -BitwiseXor -====================================================================================================================== - - -.. raw:: html - - - - - - PrimaryExpression - ^ - - -
- - -
         ::= PrimaryExpression ( '^' PrimaryExpression )*
-
- Referenced by: -
- - -====================================================================================================================== -ArrayExpression -====================================================================================================================== - - -.. raw:: html - - - - - - [ - - SimpleExpression - : - - SimpleExpression - ] - - -
- - -
         ::= ( '[' SimpleExpression? ( ':' SimpleExpression? )? ']' )+
-
- Referenced by: -
- - -====================================================================================================================== -PrimaryExpression -====================================================================================================================== - - -.. raw:: html - - - - - - NOT - - ! - - + - - - - - ~ - - NULL - - CaseWhenExpression - - CharacterPrimary - - ImplicitCast - - JdbcParameter - - JdbcNamedParameter - - UserVariable - - NumericBind - - ExtractExpression - - XMLSerializeExpr - - JsonFunction - - JsonAggregateFunction - - FullTextSearch - - CastExpression - - Function - - AnalyticExpression - - DateUnitExpression - - IntervalExpression - - S_DOUBLE - - S_LONG - - S_HEX - - AllColumns - - AllTableColumns - - K_TIME_KEY_EXPR - CURRENT - - DateTimeLiteralExpression - - StructType - ARRAY - - < - - ColDataType - > - - ArrayConstructor - - NextValExpression - - ConnectByRootOperator - - ConnectByPriorOperator - ALL - - Column - ( - - + - - ) - - TRUE - - FALSE - - S_CHAR_LITERAL - {d - - {t - - {ts - - S_CHAR_LITERAL - } - - Select - - ParenthesedSelect - - ParenthesedExpressionList - -> - - Expression - . - - RelObjectName - COLLATE - - S_CHAR_LITERAL - - S_QUOTED_IDENTIFIER - - S_IDENTIFIER - - IntervalExpressionWithoutInterval - - ArrayExpression - :: - - ColDataType - -> - - : - - ->> - - #> - - #>> - - Expression - - SimpleExpression - - JsonExpression - AT - - K_DATETIMELITERAL - ZONE - - PrimaryExpression - -
- - -
         ::= ( 'NOT' | '!' )? ( '+' | '-' | '~' )? ( 'NULL' | CaseWhenExpression | CharacterPrimary | ImplicitCast | JdbcParameter | JdbcNamedParameter | UserVariable | NumericBind | ExtractExpression | XMLSerializeExpr | JsonFunction | JsonAggregateFunction | FullTextSearch | CastExpression | Function AnalyticExpression? | DateUnitExpression | IntervalExpression | S_DOUBLE | S_LONG | S_HEX | AllColumns | AllTableColumns | K_TIME_KEY_EXPR | 'CURRENT' | DateTimeLiteralExpression | StructType | ( 'ARRAY' ( '<' ColDataType '>' )? )? ArrayConstructor | NextValExpression | ConnectByRootOperator | ConnectByPriorOperator | 'ALL' | Column ( '(' '+' ')' )? | 'TRUE' | 'FALSE' | S_CHAR_LITERAL | ( '{d' | '{t' | '{ts' ) S_CHAR_LITERAL '}' | Select | ParenthesedSelect | ParenthesedExpressionList ( '->' Expression )? ( '.' RelObjectName )* ) ( 'COLLATE' ( S_CHAR_LITERAL | S_QUOTED_IDENTIFIER | S_IDENTIFIER ) )? IntervalExpressionWithoutInterval? ArrayExpression? ( '::' ColDataType )* ( ( ( '->' | ':' | '->>' | '#>' | '#>>' ) ( Expression | SimpleExpression ) )+ JsonExpression )? ( 'AT' K_DATETIMELITERAL 'ZONE' PrimaryExpression )*
-
- - -====================================================================================================================== -ConnectByRootOperator -====================================================================================================================== - - -.. raw:: html - - - - - - CONNECT_BY_ROOT - - Expression - -
- - -
         ::= 'CONNECT_BY_ROOT' Expression
-
- Referenced by: -
- - -====================================================================================================================== -ConnectByPriorOperator -====================================================================================================================== - - -.. raw:: html - - - - - - PRIOR - - Expression - -
- - -
         ::= 'PRIOR' Expression
-
- Referenced by: -
- - -====================================================================================================================== -NextValExpression -====================================================================================================================== - - -.. raw:: html - - - - - - K_NEXTVAL - - RelObjectNames - -
- - -
         ::= K_NEXTVAL RelObjectNames
-
- Referenced by: -
- - -====================================================================================================================== -JdbcNamedParameter -====================================================================================================================== - - -.. raw:: html - - - - - - : - - & - - IdentifierChain - -
- - -
         ::= ( ':' | '&' ) IdentifierChain
-
- - -====================================================================================================================== -OracleNamedFunctionParameter -====================================================================================================================== - - -.. raw:: html - - - - - - RelObjectNameExt - OUTER - - => - - Expression - -
- - -
         ::= ( RelObjectNameExt | 'OUTER' ) '=>' Expression
-
- Referenced by: -
- - -====================================================================================================================== -PostgresNamedFunctionParameter -====================================================================================================================== - - -.. raw:: html - - - - - - RelObjectNameExt - OUTER - - := - - Expression - -
- - -
         ::= ( RelObjectNameExt | 'OUTER' ) ':=' Expression
-
- Referenced by: -
- - -====================================================================================================================== -UserVariable -====================================================================================================================== - - -.. raw:: html - - - - - - S_AT_IDENTIFIER - - IdentifierChain2 - -
- - -
         ::= S_AT_IDENTIFIER IdentifierChain2
-
- - -====================================================================================================================== -NumericBind -====================================================================================================================== - - -.. raw:: html - - - - - - : - - S_LONG - -
- - -
         ::= ':' S_LONG
-
- Referenced by: -
- - -====================================================================================================================== -DateTimeLiteralExpression -====================================================================================================================== - - -.. raw:: html - - - - - - K_DATETIMELITERAL - - S_CHAR_LITERAL - - S_QUOTED_IDENTIFIER - -
- - - -
- Referenced by: -
- - -====================================================================================================================== -DateUnitExpression -====================================================================================================================== - - -.. raw:: html - - - - - - K_DATE_LITERAL - -
- - -
         ::= K_DATE_LITERAL
-
- Referenced by: -
- - -====================================================================================================================== -RangeExpression -====================================================================================================================== - - -.. raw:: html - - - - - - : - - Expression - -
- - -
         ::= ':' Expression
-
- Referenced by: -
- - -====================================================================================================================== -ArrayConstructor -====================================================================================================================== - - -.. raw:: html - - - - - - [ - - Expression - - RangeExpression - - ArrayConstructor - , - - ] - - -
- - -
         ::= '[' ( ( Expression RangeExpression? | ArrayConstructor ) ( ',' ( Expression RangeExpression? | ArrayConstructor ) )* )? ']'
-
- - -====================================================================================================================== -StructParameters -====================================================================================================================== - - -.. raw:: html - - - - - - RelObjectName - - ColDataType - , - - -
- - -
         ::= RelObjectName? ColDataType ( ',' RelObjectName? ColDataType )*
-
- Referenced by: -
- - -====================================================================================================================== -StructType -====================================================================================================================== - - -.. raw:: html - - - - - - STRUCT - - < - - StructParameters - > - - ( - - SelectItemsList - ) - - { - - RelObjectNameExt - - S_CHAR_LITERAL - : - - Expression - , - - } - - :: - - STRUCT - - ( - - StructParameters - ) - - -
- - -
         ::= 'STRUCT' ( '<' StructParameters '>' )? '(' SelectItemsList ')'
-
           | '{' ( RelObjectNameExt | S_CHAR_LITERAL ) ':' Expression ( ',' ( RelObjectNameExt | S_CHAR_LITERAL ) ':' Expression )* '}' ( '::' 'STRUCT' '(' StructParameters ')' )*
-
- Referenced by: -
- - -====================================================================================================================== -JsonExpression -====================================================================================================================== - - -.. raw:: html - - - - - - :: - - ColDataType - -> - - : - - ->> - - #> - - #>> - - Expression - - SimpleExpression - -
- - -
         ::= ( ( '::' ColDataType )+ ( ( '->' | ':' | '->>' | '#>' | '#>>' ) ( Expression | SimpleExpression ) )* )*
-
- Referenced by: -
- - -====================================================================================================================== -JsonKeyValuePair -====================================================================================================================== - - -.. raw:: html - - - - - - KEY - - S_CHAR_LITERAL - - Column - - AllTableColumns - - AllColumns - - Expression - VALUE - - : - - , - - Expression - FORMAT - - JSON - - ENCODING - - JsonEncoding - -
- - -
         ::= ( 'KEY'? ( S_CHAR_LITERAL | Column ) | AllTableColumns | AllColumns | Expression ) ( ( 'VALUE' | ':' | ',' ) Expression )? ( 'FORMAT' 'JSON' ( 'ENCODING' JsonEncoding )? )?
-
- Referenced by: -
- - -====================================================================================================================== -JsonObjectBody -====================================================================================================================== - - -.. raw:: html - - - - - - ( - - JsonKeyValuePair - , - - NULL - - ABSENT - - ON - - NULL - - STRICT - - WITH - - WITHOUT - - UNIQUE - - KEYS - - RETURNING - - ColDataType - FORMAT - - JSON - - ENCODING - - JsonEncoding - ) - - -
- - -
         ::= '(' ( JsonKeyValuePair ( ',' JsonKeyValuePair )* )? ( ( 'NULL' | 'ABSENT' ) 'ON' 'NULL' )? 'STRICT'? ( ( 'WITH' | 'WITHOUT' ) 'UNIQUE' - 'KEYS' )? ( 'RETURNING' ColDataType ( 'FORMAT' 'JSON' ( 'ENCODING' JsonEncoding )? )? )? ')'
-
- Referenced by: -
- - -====================================================================================================================== -JsonArrayBody -====================================================================================================================== - - -.. raw:: html - - - - - - ( - - NULL - - ON - - NULL - - Expression - FORMAT - - JSON - - ENCODING - - JsonEncoding - , - - ABSENT - - ON - - NULL - - RETURNING - - ColDataType - FORMAT - - JSON - - ENCODING - - JsonEncoding - ) - - -
- - -
         ::= '(' ( 'NULL' 'ON' 'NULL' | Expression ( 'FORMAT' 'JSON' ( 'ENCODING' JsonEncoding )? )? ( ',' Expression ( 'FORMAT' 'JSON' ( 'ENCODING' JsonEncoding )? )? )* )* ( 'ABSENT' 'ON' 'NULL' )? ( 'RETURNING' ColDataType ( 'FORMAT' 'JSON' ( 'ENCODING' JsonEncoding )? )? )? ')'
-
- Referenced by: -
- - -====================================================================================================================== -JsonKeyword -====================================================================================================================== - - -.. raw:: html - - - - - - S_IDENTIFIER - -
- - -
         ::= S_IDENTIFIER
-
- - -====================================================================================================================== -JsonEncoding -====================================================================================================================== - - -.. raw:: html - - - - - - S_IDENTIFIER - -
- - -
         ::= S_IDENTIFIER
-
- - -====================================================================================================================== -JsonValueOrQueryInputExpression -====================================================================================================================== - - -.. raw:: html - - - - - - Expression - FORMAT - - JSON - - ENCODING - - JsonEncoding - -
- - -
         ::= Expression ( 'FORMAT' 'JSON' ( 'ENCODING' JsonEncoding )? )?
-
- - -====================================================================================================================== -JsonValueOnResponseBehavior -====================================================================================================================== - - -.. raw:: html - - - - - - ERROR - - NULL - - DEFAULT - - Expression - -
- - -
         ::= 'ERROR'
-
           | 'NULL'
-
           | 'DEFAULT' Expression
-
- - -====================================================================================================================== -JsonQueryOnResponseBehavior -====================================================================================================================== - - -.. raw:: html - - - - - - ERROR - - NULL - - S_IDENTIFIER - ARRAY - - JsonKeyword - -
- - -
         ::= 'ERROR'
-
           | 'NULL'
-
           | S_IDENTIFIER ( 'ARRAY' | JsonKeyword )
-
- Referenced by: -
- - -====================================================================================================================== -JsonExistsOnResponseBehavior -====================================================================================================================== - - -.. raw:: html - - - - - - TRUE - - FALSE - - UNKNOWN - - ERROR - - -
- - -
         ::= 'TRUE'
-
           | 'FALSE'
-
           | 'UNKNOWN'
-
           | 'ERROR'
-
- Referenced by: -
- - -====================================================================================================================== -JsonExistsBody -====================================================================================================================== - - -.. raw:: html - - - - - - ( - - JsonValueOrQueryInputExpression - , - - Expression - - JsonKeyword - - Expression - , - - JsonExistsOnResponseBehavior - ON - - ERROR - - ) - - -
- - -
         ::= '(' JsonValueOrQueryInputExpression ',' Expression ( JsonKeyword Expression ( ',' Expression )* )? ( JsonExistsOnResponseBehavior 'ON' 'ERROR' )? ')'
-
- Referenced by: -
- - -====================================================================================================================== -JsonValueBody -====================================================================================================================== - - -.. raw:: html - - - - - - ( - - JsonValueOrQueryInputExpression - , - - Expression - - JsonKeyword - - Expression - , - - RETURNING - - ColDataType - - JsonValueOnResponseBehavior - ON - - JsonKeyword - - JsonValueOnResponseBehavior - ON - - ERROR - - ) - - -
- - -
         ::= '(' JsonValueOrQueryInputExpression ',' Expression ( JsonKeyword Expression ( ',' Expression )* )? ( 'RETURNING' ColDataType )? ( JsonValueOnResponseBehavior 'ON' JsonKeyword )? ( JsonValueOnResponseBehavior 'ON' 'ERROR' )? ')'
-
- Referenced by: -
- - -====================================================================================================================== -JsonQueryBody -====================================================================================================================== - - -.. raw:: html - - - - - - ( - - JsonValueOrQueryInputExpression - , - - Expression - - JsonKeyword - - Expression - , - - RETURNING - - ColDataType - FORMAT - - JSON - - ENCODING - - JsonEncoding - WITHOUT - - WITH - - S_IDENTIFIER - ARRAY - - JsonKeyword - KEEP - - JsonKeyword - - JsonKeyword - ON - - JsonKeyword - STRING - - JsonQueryOnResponseBehavior - ON - - JsonKeyword - - JsonQueryOnResponseBehavior - ON - - ERROR - - Expression - , - - ) - - -
- - -
         ::= '(' JsonValueOrQueryInputExpression ',' Expression ( JsonKeyword Expression ( ',' Expression )* )? ( 'RETURNING' ColDataType ( 'FORMAT' 'JSON' ( 'ENCODING' JsonEncoding )? )? )? ( ( 'WITHOUT' | 'WITH' S_IDENTIFIER? ) 'ARRAY'? JsonKeyword )? ( ( 'KEEP' | JsonKeyword ) JsonKeyword ( 'ON' JsonKeyword 'STRING' )? )? ( JsonQueryOnResponseBehavior 'ON' JsonKeyword )? ( JsonQueryOnResponseBehavior 'ON' 'ERROR' )? ( ',' Expression ( 'RETURNING' ColDataType ( 'FORMAT' 'JSON' ( 'ENCODING' JsonEncoding )? )? )? ( ( 'WITHOUT' | 'WITH' S_IDENTIFIER? ) 'ARRAY'? JsonKeyword )? ( ( 'KEEP' | JsonKeyword ) JsonKeyword ( 'ON' JsonKeyword 'STRING' )? )? ( JsonQueryOnResponseBehavior 'ON' JsonKeyword )? ( JsonQueryOnResponseBehavior 'ON' 'ERROR' )? )* ')'
-
- Referenced by: -
- - -====================================================================================================================== -JsonFunction -====================================================================================================================== - - -.. raw:: html - - - - - - JSON_OBJECT - - JsonObjectBody - JSON_ARRAY - - JsonArrayBody - - JsonKeyword - - JsonValueBody - - JsonQueryBody - - JsonExistsBody - -
- - -
         ::= 'JSON_OBJECT' JsonObjectBody
-
           | 'JSON_ARRAY' JsonArrayBody
-
           | JsonKeyword ( JsonValueBody | JsonQueryBody | JsonExistsBody )
-
- Referenced by: -
- - -====================================================================================================================== -JsonAggregateFunction -====================================================================================================================== - - -.. raw:: html - - - - - - JSON_OBJECTAGG - - ( - - KEY - - DT_ZONE - - S_DOUBLE - - S_LONG - - S_HEX - - S_CHAR_LITERAL - - Column - : - - , - - VALUE - - Expression - FORMAT - - JSON - - NULL - - ABSENT - - ON - - NULL - - WITH - - WITHOUT - - UNIQUE - - KEYS - - JSON_ARRAYAGG - - ( - - Expression - FORMAT - - JSON - - OrderByElements - NULL - - ABSENT - - ON - - NULL - - ) - - FILTER - - ( - - WHERE - - Expression - ) - - OVER - - ( - - PARTITION - - BY - - ComplexExpressionList - ( - - ComplexExpressionList - ) - - OrderByElements - - WindowElement - ) - - -
- - -
         ::= ( 'JSON_OBJECTAGG' '(' 'KEY'? ( DT_ZONE | S_DOUBLE | S_LONG | S_HEX | S_CHAR_LITERAL | Column ) ( ':' | ',' | 'VALUE' ) Expression ( 'FORMAT' 'JSON' )? ( ( 'NULL' | 'ABSENT' ) 'ON' 'NULL' )? ( ( 'WITH' | 'WITHOUT' - ) 'UNIQUE' 'KEYS' )? | 'JSON_ARRAYAGG' '(' Expression ( 'FORMAT' 'JSON' )? OrderByElements? ( ( 'NULL' | 'ABSENT' ) 'ON' 'NULL' )? ) ')' ( 'FILTER' '(' 'WHERE' Expression ')' )? ( 'OVER' '(' ( 'PARTITION' 'BY' ( ComplexExpressionList | '(' ComplexExpressionList ')' ) )? OrderByElements? WindowElement? ')' )?
-
- Referenced by: -
- - -====================================================================================================================== -IntervalExpression -====================================================================================================================== - - -.. raw:: html - - - - - - INTERVAL - - - - - S_LONG - - S_DOUBLE - - S_CHAR_LITERAL - - Expression - - S_IDENTIFIER - - K_DATE_LITERAL - -
- - -
         ::= 'INTERVAL' ( '-'? ( S_LONG | S_DOUBLE ) | S_CHAR_LITERAL | Expression ) ( S_IDENTIFIER | K_DATE_LITERAL )?
-
- Referenced by: -
- - -====================================================================================================================== -IntervalExpressionWithoutInterval -====================================================================================================================== - - -.. raw:: html - - - - - - K_DATE_LITERAL - -
- - -
         ::= K_DATE_LITERAL
-
- Referenced by: -
- - -====================================================================================================================== -KeepExpression -====================================================================================================================== - - -.. raw:: html - - - - - - KEEP - - ( - - S_IDENTIFIER - FIRST - - LAST - - OrderByElements - ) - - -
- - -
         ::= 'KEEP' '(' S_IDENTIFIER ( 'FIRST' | 'LAST' ) OrderByElements ')'
-
- Referenced by: -
- - -====================================================================================================================== -windowFun -====================================================================================================================== - - -.. raw:: html - - - - - - OVER - - WITHIN - - GROUP - - RelObjectName - - windowDefinition - OVER - - ( - - PARTITION - - BY - - ComplexExpressionList - ( - - ComplexExpressionList - ) - - ) - - -
- - -
         ::= ( 'OVER' | 'WITHIN' 'GROUP' ) ( RelObjectName | windowDefinition ( 'OVER' '(' ( 'PARTITION' 'BY' ( ComplexExpressionList | '(' ComplexExpressionList ')' ) )? ')' )? )
-
- Referenced by: -
- - -====================================================================================================================== -windowDefinition -====================================================================================================================== - - -.. raw:: html - - - - - - ( - - PARTITION - - BY - - ComplexExpressionList - ( - - ComplexExpressionList - ) - - OrderByElements - - WindowElement - ) - - -
- - -
         ::= '(' ( 'PARTITION' 'BY' ( ComplexExpressionList | '(' ComplexExpressionList ')' ) )? OrderByElements? WindowElement? ')'
-
- Referenced by: -
- - -====================================================================================================================== -AnalyticExpression -====================================================================================================================== - - -.. raw:: html - - - - - - FILTER - - ( - - WHERE - - Expression - ) - - windowFun - - windowFun - -
- - -
         ::= 'FILTER' '(' 'WHERE' Expression ')' windowFun?
-
           | windowFun
-
- Referenced by: -
- - -====================================================================================================================== -WindowElement -====================================================================================================================== - - -.. raw:: html - - - - - - ROWS - - RANGE - - BETWEEN - - WindowOffset - AND - - WindowOffset - -
- - -
         ::= ( 'ROWS' | 'RANGE' ) ( 'BETWEEN' WindowOffset 'AND' )? WindowOffset
-
- - -====================================================================================================================== -WindowOffset -====================================================================================================================== - - -.. raw:: html - - - - - - UNBOUNDED - - SimpleExpression - PRECEDING - - FOLLOWING - - CURRENT - - ROW - - -
- - -
         ::= ( 'UNBOUNDED' | SimpleExpression ) ( 'PRECEDING' | 'FOLLOWING' )
-
           | 'CURRENT' 'ROW'
-
- Referenced by: -
- - -====================================================================================================================== -ExtractExpression -====================================================================================================================== - - -.. raw:: html - - - - - - EXTRACT - - ( - - RelObjectName - - S_CHAR_LITERAL - FROM - - SimpleExpression - ) - - -
- - -
         ::= 'EXTRACT' '(' ( RelObjectName | S_CHAR_LITERAL ) 'FROM' SimpleExpression ')'
-
- Referenced by: -
- - -====================================================================================================================== -ImplicitCast -====================================================================================================================== - - -.. raw:: html - - - - - - DataType - - S_CHAR_LITERAL - - S_LONG - - S_DOUBLE - -
- - -
         ::= DataType ( S_CHAR_LITERAL | S_LONG | S_DOUBLE )
-
- Referenced by: -
- - -====================================================================================================================== -CastExpression -====================================================================================================================== - - -.. raw:: html - - - - - - CAST - - SAFE_CAST - - TRY_CAST - - INTERPRET - - ( - - SimpleExpression - AS - - ROW - - ( - - ColumnDefinition - , - - ) - - ColDataType - FORMAT - - S_CHAR_LITERAL - ) - - -
- - -
         ::= ( 'CAST' | 'SAFE_CAST' | 'TRY_CAST' | 'INTERPRET' ) '(' SimpleExpression 'AS' ( 'ROW' '(' ColumnDefinition ( ',' ColumnDefinition )* ')' | ColDataType ) ( 'FORMAT' S_CHAR_LITERAL )? ')'
-
- Referenced by: -
- - -====================================================================================================================== -CaseWhenExpression -====================================================================================================================== - - -.. raw:: html - - - - - - CASE - - Expression - - WhenThenSearchCondition - ELSE - - Expression - - SimpleExpression - END - - -
- - -
         ::= 'CASE' Expression? WhenThenSearchCondition+ ( 'ELSE' ( Expression | SimpleExpression ) )? 'END'
-
- Referenced by: -
- - -====================================================================================================================== -WhenThenSearchCondition -====================================================================================================================== - - -.. raw:: html - - - - - - WHEN - - Expression - THEN - - Expression - - SimpleExpression - -
- - -
         ::= 'WHEN' Expression 'THEN' ( Expression | SimpleExpression )
-
- Referenced by: -
- - -====================================================================================================================== -RowConstructor -====================================================================================================================== - - -.. raw:: html - - - - - - ROW - - ParenthesedExpressionList - -
- - -
         ::= 'ROW' ParenthesedExpressionList
-
- Referenced by: -
- - -====================================================================================================================== -VariableExpression -====================================================================================================================== - - -.. raw:: html - - - - - - UserVariable - = - - SimpleExpression - -
- - -
         ::= UserVariable '=' SimpleExpression
-
- Not referenced by any. -
- - -====================================================================================================================== -Execute -====================================================================================================================== - - -.. raw:: html - - - - - - EXEC - - EXECUTE - - CALL - - RelObjectNames - - ExpressionList - -
- -
Execute  ::= ( 'EXEC' | 'EXECUTE' | 'CALL' ) RelObjectNames ExpressionList?
-
- Referenced by: -
- - -====================================================================================================================== -FullTextSearch -====================================================================================================================== - - -.. raw:: html - - - - - - MATCH - - ( - - ColumnList - ) - - AGAINST - - ( - - S_CHAR_LITERAL - - JdbcParameter - - JdbcNamedParameter - IN NATURAL LANGUAGE MODE - - IN NATURAL LANGUAGE MODE WITH QUERY EXPANSION - - IN BOOLEAN MODE - - WITH QUERY EXPANSION - - ) - - -
- - -
         ::= 'MATCH' '(' ColumnList ')' 'AGAINST' '(' ( S_CHAR_LITERAL | JdbcParameter | JdbcNamedParameter ) ( 'IN NATURAL LANGUAGE MODE' | 'IN NATURAL LANGUAGE MODE WITH QUERY EXPANSION' - | 'IN BOOLEAN MODE' | 'WITH QUERY EXPANSION' )? ')'
-
- Referenced by: -
- - -====================================================================================================================== -LambdaExpression -====================================================================================================================== - - -.. raw:: html - - - - - - ParenthesedColumnList - - RelObjectName - -> - - Expression - -
- - -
         ::= ( ParenthesedColumnList | RelObjectName ) '->' Expression
-
- - -====================================================================================================================== -Function -====================================================================================================================== - - -.. raw:: html - - - - - - { - - FN - - InternalFunction - } - - SpecialStringFunctionWithNamedParameters - - InternalFunction - -
- -
Function ::= '{' 'FN' InternalFunction '}'
- -
           | InternalFunction
-
- - -====================================================================================================================== -SpecialStringFunctionWithNamedParameters -====================================================================================================================== - - -.. raw:: html - - - - - - K_STRING_FUNCTION_NAME - ( - - NamedExpressionListExprFirst - - ExpressionList - ) - - -
- - - -
- Referenced by: -
- - -====================================================================================================================== -InternalFunction -====================================================================================================================== - - -.. raw:: html - - - - - - APPROXIMATE - - RelObjectNames - ( - - DISTINCT - - ALL - - UNIQUE - - TABLE - - ExpressionList - - OrderByElements - ON - - OVERFLOW - - TRUNCATE - - ERROR - - S_CHAR_LITERAL - WITH - - WITHOUT - - COUNT - - Select - HAVING - - MIN - - MAX - - Expression - IGNORE - - RESPECT - - NULLS - - PlainLimit - ) - - ( - - ExpressionList - ) - - . - - Function - - Column - IGNORE - - RESPECT - - NULLS - - KeepExpression - -
- - -
         ::= 'APPROXIMATE'? RelObjectNames '(' ( ( 'DISTINCT' | 'ALL' | 'UNIQUE' )? ( 'TABLE'? ExpressionList OrderByElements? ( 'ON' 'OVERFLOW' ( 'TRUNCATE' | 'ERROR' ) ( S_CHAR_LITERAL ( ( 'WITH' | 'WITHOUT' ) 'COUNT' )? )? )? | Select ) )? ( 'HAVING' ( 'MIN' | 'MAX' ) Expression )? ( ( 'IGNORE' | 'RESPECT' ) 'NULLS' )? PlainLimit? ')' ( '(' ExpressionList ')' )? ( '.' ( Function | Column ) )? ( ( 'IGNORE' | 'RESPECT' ) 'NULLS' )? KeepExpression?
-
- Referenced by: -
- - -====================================================================================================================== -XMLSerializeExpr -====================================================================================================================== - - -.. raw:: html - - - - - - XMLSERIALIZE - - ( - - XMLAGG - - ( - - XMLTEXT - - ( - - SimpleExpression - ) - - OrderByElements - ) - - AS - - ColDataType - ) - - -
- - -
         ::= 'XMLSERIALIZE' '(' 'XMLAGG' '(' 'XMLTEXT' '(' SimpleExpression ')' OrderByElements? ')' 'AS' ColDataType ')'
-
- Referenced by: -
- - -====================================================================================================================== -JsonTablePassingClause -====================================================================================================================== - - -.. raw:: html - - - - - - Expression - AS - - RelObjectName - -
- - -
         ::= Expression 'AS' RelObjectName
-
- Referenced by: -
- - -====================================================================================================================== -JsonTableOnEmptyBehavior -====================================================================================================================== - - -.. raw:: html - - - - - - ERROR - - NULL - - DEFAULT - - Expression - - S_IDENTIFIER - - JsonKeyword - ARRAY - - -
- - -
         ::= 'ERROR'
-
           | 'NULL'
-
           | 'DEFAULT' Expression
-
           | S_IDENTIFIER ( JsonKeyword | 'ARRAY' )?
-
- Referenced by: -
- - -====================================================================================================================== -JsonTableWrapperClause -====================================================================================================================== - - -.. raw:: html - - - - - - WITHOUT - - WITH - - S_IDENTIFIER - ARRAY - - JsonKeyword - -
- - -
         ::= ( 'WITHOUT' | 'WITH' S_IDENTIFIER? ) 'ARRAY'? JsonKeyword
-
- Referenced by: -
- - -====================================================================================================================== -JsonTableQuotesClause -====================================================================================================================== - - -.. raw:: html - - - - - - KEEP - - JsonKeyword - - JsonKeyword - ON - - JsonKeyword - STRING - - -
- - -
         ::= ( 'KEEP' | JsonKeyword ) JsonKeyword ( 'ON' JsonKeyword 'STRING' )?
-
- Referenced by: -
- - -====================================================================================================================== -JsonTableColumnDefinition -====================================================================================================================== - - -.. raw:: html - - - - - - JsonKeyword - PATH - - Expression - AS - - RelObjectName - - JsonTableColumnsClause - - RelObjectName - FOR - - JsonKeyword - - ColDataType - FORMAT - - JSON - - ENCODING - - JsonEncoding - PATH - - Expression - - JsonTableWrapperClause - - JsonTableQuotesClause - - JsonTableOnEmptyBehavior - ON - - JsonKeyword - - JsonValueOnResponseBehavior - ON - - ERROR - - -
- - -
         ::= JsonKeyword 'PATH'? Expression ( 'AS' RelObjectName )? JsonTableColumnsClause
-
           | RelObjectName ( 'FOR' JsonKeyword | ColDataType ( 'FORMAT' 'JSON' ( 'ENCODING' JsonEncoding )? )? ( 'PATH' Expression )? JsonTableWrapperClause? JsonTableQuotesClause? ( JsonTableOnEmptyBehavior 'ON' JsonKeyword )? ( JsonValueOnResponseBehavior 'ON' 'ERROR' )? )
-
- Referenced by: -
- - -====================================================================================================================== -JsonTableColumnsClause -====================================================================================================================== - - -.. raw:: html - - - - - - COLUMNS - - ( - - JsonTableColumnDefinition - , - - ) - - -
- - -
         ::= 'COLUMNS' '(' ( JsonTableColumnDefinition ( ',' JsonTableColumnDefinition )* )? ')'
-
- - -====================================================================================================================== -JsonTablePlanTerm -====================================================================================================================== - - -.. raw:: html - - - - - - ( - - JsonTablePlanExpression - ) - - RelObjectName - - Expression - -
- - -
         ::= '(' JsonTablePlanExpression ')'
-
           | RelObjectName
-
           | Expression
-
- Referenced by: -
- - -====================================================================================================================== -JsonTablePlanExpression -====================================================================================================================== - - -.. raw:: html - - - - - - JsonTablePlanTerm - , - - INNER - - OUTER - - CROSS - - UNION - - -
- - -
         ::= JsonTablePlanTerm ( ( ',' | 'INNER' | 'OUTER' | 'CROSS' | 'UNION' ) JsonTablePlanTerm )*
-
- - -====================================================================================================================== -JsonTablePlanClause -====================================================================================================================== - - -.. raw:: html - - - - - - PLAN - - DEFAULT - - ( - - JsonTablePlanExpression - ) - - -
- - -
         ::= 'PLAN' 'DEFAULT'? '(' JsonTablePlanExpression ')'
-
- Referenced by: -
- - -====================================================================================================================== -JsonTableOnErrorClause -====================================================================================================================== - - -.. raw:: html - - - - - - ERROR - - S_IDENTIFIER - ON - - ERROR - - -
- - -
         ::= ( 'ERROR' | S_IDENTIFIER ) 'ON' 'ERROR'
-
- Referenced by: -
- - -====================================================================================================================== -JsonTableBody -====================================================================================================================== - - -.. raw:: html - - - - - - ( - - Expression - , - - Expression - AS - - RelObjectName - - JsonKeyword - - JsonTablePassingClause - , - - JsonTableColumnsClause - - JsonTablePlanClause - - JsonTableOnErrorClause - ) - - -
- - - -
- Referenced by: -
- - -====================================================================================================================== -TableFunction -====================================================================================================================== - - -.. raw:: html - - - - - - LATERAL - - JsonKeyword - - JsonTableBody - - Function - WITH - - OFFSET - - ORDINALITY - - -
- - -
         ::= 'LATERAL'? ( JsonKeyword JsonTableBody | Function ) ( 'WITH' ( 'OFFSET' | 'ORDINALITY' ) )?
-
- Referenced by: -
- - -====================================================================================================================== -ColumnNamesWithParamsList -====================================================================================================================== - - -.. raw:: html - - - - - - ( - - RelObjectName - - CreateParameter - , - - ) - - -
- - -
         ::= '(' RelObjectName CreateParameter? ( ',' RelObjectName CreateParameter? )* ')'
-
- - -====================================================================================================================== -IndexColumnWithParams -====================================================================================================================== - - -.. raw:: html - - - - - - RelObjectName - ( - - Expression - ) - - CreateParameter - -
- - -
         ::= ( RelObjectName | '(' Expression ')' ) CreateParameter?
-
- Referenced by: -
- - -====================================================================================================================== -IndexColumnsWithParamsList -====================================================================================================================== - - -.. raw:: html - - - - - - ( - - IndexColumnWithParams - , - - ) - - -
- - -
         ::= '(' IndexColumnWithParams ( ',' IndexColumnWithParams )* ')'
-
- - -====================================================================================================================== -Index -====================================================================================================================== - - -.. raw:: html - - - - - - RelObjectNames - -
- - -
- Referenced by: -
- - -====================================================================================================================== -CreateIndex -====================================================================================================================== - - -.. raw:: html - - - - - - CreateParameter - INDEX - - IF - - NOT - - EXISTS - - Index - ON - - Table - - UsingIndexType - - UsingIndexType - ON - - Table - - IndexColumnsWithParamsList - - CreateParameter - -
- - -
         ::= CreateParameter? 'INDEX' ( 'IF' 'NOT' 'EXISTS' )? Index ( 'ON' Table UsingIndexType? | UsingIndexType? 'ON' Table ) IndexColumnsWithParamsList CreateParameter*
-
- Referenced by: -
- - -====================================================================================================================== -ColumnDefinition -====================================================================================================================== - - -.. raw:: html - - - - - - RelObjectName - - ColDataType - - CreateParameter - -
- - - -
- - -====================================================================================================================== -CreateSchema -====================================================================================================================== - - -.. raw:: html - - - - - - SCHEMA - - IF - - NOT - - EXISTS - - S_IDENTIFIER - - S_QUOTED_IDENTIFIER - . - - S_IDENTIFIER - - S_QUOTED_IDENTIFIER - AUTHORIZATION - - S_IDENTIFIER - - S_QUOTED_IDENTIFIER - - PathSpecification - CREATE - - CreateTable - - CreateView - -
- - -
         ::= 'SCHEMA' ( 'IF' 'NOT' 'EXISTS' )? ( ( S_IDENTIFIER | S_QUOTED_IDENTIFIER ) ( '.' ( S_IDENTIFIER | S_QUOTED_IDENTIFIER ) )? )? ( 'AUTHORIZATION' ( S_IDENTIFIER | S_QUOTED_IDENTIFIER ) )? PathSpecification? ( 'CREATE' CreateTable | CreateView )*
-
- Referenced by: -
- - -====================================================================================================================== -PathSpecification -====================================================================================================================== - - -.. raw:: html - - - - - - PATH - - S_IDENTIFIER - - S_QUOTED_IDENTIFIER - , - - -
- - -
         ::= 'PATH' ( S_IDENTIFIER | S_QUOTED_IDENTIFIER ) ( ',' ( S_IDENTIFIER | S_QUOTED_IDENTIFIER ) )*
-
- Referenced by: -
- - -====================================================================================================================== -CreateTableConstraint -====================================================================================================================== - - -.. raw:: html - - - - - - INDEX - - UNIQUE - - FULLTEXT - - SPATIAL - - KEY - - RelObjectName - - IndexColumnsWithParamsList - - CreateParameter - CONSTRAINT - - RelObjectName - PRIMARY - - KEY - - UNIQUE - - KEY - - ColumnNamesWithParamsList - - CreateParameter - - ForeignKeySpec - - CheckConstraintSpec - EXCLUDE - - WHERE - - ( - - Expression - ) - - -
- - -
         ::= ( 'INDEX' | 'UNIQUE'? ( 'FULLTEXT' | 'SPATIAL' )? 'KEY' ) RelObjectName IndexColumnsWithParamsList CreateParameter*
-
           | ( 'CONSTRAINT' RelObjectName )? ( ( 'PRIMARY' 'KEY' | 'UNIQUE' 'KEY'? ) ColumnNamesWithParamsList CreateParameter* | ForeignKeySpec | CheckConstraintSpec )
-
           | 'EXCLUDE' 'WHERE' ( '(' Expression ')' )*
-
- Referenced by: -
- - -====================================================================================================================== -CreateTable -====================================================================================================================== - - -.. raw:: html - - - - - - UNLOGGED - - GLOBAL - - CreateParameter - TABLE - - IF - - NOT - - EXISTS - - Table - ( - - RelObjectName - , - - ColumnDefinition - , - - CreateTableConstraint - - ColumnDefinition - ) - - CreateParameter - - RowMovement - AS - - Select - LIKE - - ( - - Table - ) - - Table - , - - SpannerInterleaveIn - -
- - -
         ::= 'UNLOGGED'? 'GLOBAL'? CreateParameter* 'TABLE' ( 'IF' 'NOT' 'EXISTS' )? Table ( '(' ( RelObjectName ( ',' RelObjectName )* | ColumnDefinition ( ',' ( CreateTableConstraint | ColumnDefinition ) )* ) ')' )? CreateParameter* RowMovement? ( 'AS' Select )? ( 'LIKE' ( '(' Table ')' | Table ) )? ( ',' SpannerInterleaveIn )?
-
- Referenced by: -
- - -====================================================================================================================== -SpannerInterleaveIn -====================================================================================================================== - - -.. raw:: html - - - - - - INTERLEAVE - - IN - - PARENT - - Table - ON - - DELETE - - NO - - ACTION - - CASCADE - - -
- - -
         ::= 'INTERLEAVE' 'IN' 'PARENT' Table ( 'ON' 'DELETE' ( 'NO' 'ACTION' | 'CASCADE' ) )?
-
- Referenced by: -
- - -====================================================================================================================== -DataType -====================================================================================================================== - - -.. raw:: html - - - - - - K_DATETIMELITERAL - - DT_ZONE - - DATA_TYPE - SIGNED - - UNSIGNED - - CHARACTER - - BIT - - BYTES - - BINARY - - BOOLEAN - - CHAR - - JSON - - STRING - - DATA_TYPE - SIGNED - - UNSIGNED - - CHARACTER - - BIT - - BYTES - - BINARY - - BOOLEAN - - CHAR - - JSON - - STRING - - ( - - S_LONG - MAX - - , - - S_LONG - ) - - K_TEXT_LITERAL - ARRAY - - < - - ColDataType - > - - -
- - -
           | 'ARRAY' '<' ColDataType '>'
-
           | ( K_DATETIMELITERAL | DT_ZONE | DATA_TYPE | 'SIGNED' | 'UNSIGNED' | 'CHARACTER' | 'BIT' | 'BYTES' | 'BINARY' | 'BOOLEAN' | - 'CHAR' | 'JSON' | 'STRING' ) ( DATA_TYPE | 'SIGNED' | 'UNSIGNED' | 'CHARACTER' | 'BIT' | 'BYTES' | 'BINARY' | 'BOOLEAN' | - 'CHAR' | 'JSON' | 'STRING' )* ( '(' ( S_LONG | 'MAX' ) ( ',' S_LONG )? ')' )?
-
- Referenced by: -
- - -====================================================================================================================== -ColDataType -====================================================================================================================== - - -.. raw:: html - - - - - - STRUCT - - ( - - RelObjectNameExt - - ColDataType - , - - ) - - DataType - - S_IDENTIFIER - - S_QUOTED_IDENTIFIER - - K_DATETIMELITERAL - - K_DATE_LITERAL - XML - - INTERVAL - - DT_ZONE - CHAR - - SET - - BINARY - - JSON - - STRING - - PUBLIC - - DATA - - NAME - - . - - ColDataType - ( - - S_LONG - MAX - - BYTE - - CHAR - - S_CHAR_LITERAL - - S_IDENTIFIER - CHAR - - , - - ) - - [ - - S_LONG - ] - - CHARACTER - - SET - - S_IDENTIFIER - BINARY - - -
- - -
         ::= ( 'STRUCT' '(' RelObjectNameExt ColDataType ( ',' RelObjectNameExt ColDataType )* ')' | DataType | ( S_IDENTIFIER | S_QUOTED_IDENTIFIER | K_DATETIMELITERAL | K_DATE_LITERAL | 'XML' | 'INTERVAL' | DT_ZONE | 'CHAR' | 'SET' | 'BINARY' | 'JSON' | 'STRING' | 'PUBLIC' | 'DATA' | 'NAME' ) ( - '.' ColDataType )? ) ( '(' ( ( ( S_LONG | 'MAX' ) ( 'BYTE' | 'CHAR' )? | S_CHAR_LITERAL | S_IDENTIFIER | 'CHAR' ) ','? )* ')' )? ( '[' S_LONG? ']' )* ( 'CHARACTER' 'SET' ( S_IDENTIFIER | 'BINARY' ) )?
-
- - -====================================================================================================================== -Analyze -====================================================================================================================== - - -.. raw:: html - - - - - - ANALYZE - - Table - -
- -
Analyze  ::= 'ANALYZE' Table
-
- Referenced by: -
- - -====================================================================================================================== -ColumnWithCommentList -====================================================================================================================== - - -.. raw:: html - - - - - - ( - - Column - , - - ) - - -
- - -
         ::= '(' Column ( ',' Column )* ')'
-
- Referenced by: -
- - -====================================================================================================================== -CreateView -====================================================================================================================== - - -.. raw:: html - - - - - - NO - - FORCE - - SECURE - - TEMP - - TEMPORARY - - VOLATILE - - MATERIALIZED - - VIEW - - Table - AUTO - - REFRESH - - YES - - NO - - IF - - NOT - - EXISTS - - ColumnWithCommentList - - CreateViewTailComment - AS - - Select - WITH - - READ - - ONLY - - -
- - -
         ::= ( 'NO'? 'FORCE' )? 'SECURE'? ( 'TEMP' | 'TEMPORARY' | 'VOLATILE' )? 'MATERIALIZED'? - 'VIEW' Table ( 'AUTO' 'REFRESH' ( 'YES' | 'NO' ) )? ( 'IF' 'NOT' 'EXISTS' )? ColumnWithCommentList? CreateViewTailComment? 'AS' Select ( 'WITH' 'READ' 'ONLY' )?
-
- Referenced by: -
- - -====================================================================================================================== -CreateViewTailComment -====================================================================================================================== - - -.. raw:: html - - - - - - COMMENT - - = - - S_CHAR_LITERAL - -
- - -
         ::= 'COMMENT' '='? S_CHAR_LITERAL
-
- Referenced by: -
- - -====================================================================================================================== -Action -====================================================================================================================== - - -.. raw:: html - - - - - - CASCADE - - RESTRICT - - NO - - ACTION - - SET - - NULL - - DEFAULT - - -
- -
Action   ::= 'CASCADE'
-
           | 'RESTRICT'
-
           | 'NO' 'ACTION'
-
           | 'SET' ( 'NULL' | 'DEFAULT' )
-
- Referenced by: -
- - -====================================================================================================================== -ReferentialActionsOnIndex -====================================================================================================================== - - -.. raw:: html - - - - - - ON - - DELETE - - UPDATE - - Action - ON - - DELETE - - UPDATE - - Action - -
- - -
         ::= ( 'ON' ( 'DELETE' | 'UPDATE' ) Action )? ( 'ON' ( 'DELETE' | 'UPDATE' ) Action )?
-
- - -====================================================================================================================== -CheckConstraintSpec -====================================================================================================================== - - -.. raw:: html - - - - - - CHECK - - ( - - Expression - ) - - -
- - -
         ::= 'CHECK' ( '(' Expression ')' )*
-
- - -====================================================================================================================== -ForeignKeySpec -====================================================================================================================== - - -.. raw:: html - - - - - - FOREIGN - - KEY - - ColumnNamesWithParamsList - REFERENCES - - Table - - ColumnsNamesList - - ReferentialActionsOnIndex - -
- - -
         ::= 'FOREIGN' 'KEY' ColumnNamesWithParamsList 'REFERENCES' Table ColumnsNamesList? ReferentialActionsOnIndex
-
- - -====================================================================================================================== -AlterExpressionUsingIndex -====================================================================================================================== - - -.. raw:: html - - - - - - USING - - INDEX - - RelObjectName - -
- - -
         ::= 'USING' 'INDEX'? RelObjectName
-
- - -====================================================================================================================== -AlterExpressionConstraintTail -====================================================================================================================== - - -.. raw:: html - - - - - - AlterExpressionConstraintState - - AlterExpressionUsingIndex - - IndexWithComment - -
- - - -
- Referenced by: -
- - -====================================================================================================================== -AlterView -====================================================================================================================== - - -.. raw:: html - - - - - - VIEW - - Table - - ColumnsNamesList - AS - - Select - -
- - -
         ::= 'VIEW' Table ColumnsNamesList? 'AS' Select
-
- Referenced by: -
- - -====================================================================================================================== -CreateParameter -====================================================================================================================== - - -.. raw:: html - - - - - - K_NEXTVAL - ( - - S_CHAR_LITERAL - :: - - ColDataType - ) - - S_IDENTIFIER - - S_QUOTED_IDENTIFIER - NAME - - . - - S_IDENTIFIER - - S_QUOTED_IDENTIFIER - NAME - - USING - - INDEX - - TABLESPACE - - RelObjectName - - S_CHAR_LITERAL - NULL - - NOT - - AUTO_INCREMENT - - PRIMARY - - FOREIGN - - REFERENCES - - KEY - - STORED - - ON - - COMMIT - - DROP - - ROWS - - UNIQUE - - CASCADE - - DELETE - - UPDATE - - CONSTRAINT - - WITH - - EXCLUDE - - WHERE - - TEMP - - TEMPORARY - - PARTITION - - BY - - IN - - TYPE - - COMMENT - - USING - - COLLATE - - ASC - - DESC - - TRUE - - FALSE - - PARALLEL - - BINARY - - START - - ORDER - - K_TIME_KEY_EXPR - RAW - - HASH - - FIRST - - LAST - - SIGNED - - UNSIGNED - - ENGINE - - IDENTITY - - MATERIALIZED - - SAMPLE - - ALWAYS - - = - - DEFAULT - - AS - - CHECK - - ( - - Expression - ) - - + - - - - - S_LONG - - S_DOUBLE - - AList - CHARACTER - - SET - - ARRAY - - ArrayConstructor - :: - - ColDataType - -
- - -
         ::= K_NEXTVAL '(' S_CHAR_LITERAL '::' ColDataType ')'
-
           | ( S_IDENTIFIER | S_QUOTED_IDENTIFIER | 'NAME' ) ( '.' ( S_IDENTIFIER | S_QUOTED_IDENTIFIER | 'NAME' ) )?
-
           | ( 'USING' 'INDEX' )? 'TABLESPACE' RelObjectName
-
           | S_CHAR_LITERAL
-
           | 'NULL'
-
           | 'NOT'
-
           | 'AUTO_INCREMENT'
-
           | 'PRIMARY'
-
           | 'FOREIGN'
-
           | 'REFERENCES'
-
           | 'KEY'
-
           | 'STORED'
-
           | 'ON'
-
           | 'COMMIT'
-
           | 'DROP'
-
           | 'ROWS'
-
           | 'UNIQUE'
-
           | 'CASCADE'
-
           | 'DELETE'
-
           | 'UPDATE'
-
           | 'CONSTRAINT'
-
           | 'WITH'
-
           | 'EXCLUDE'
-
           | 'WHERE'
-
           | 'TEMP'
-
           | 'TEMPORARY'
-
           | 'PARTITION'
-
           | 'BY'
-
           | 'IN'
-
           | 'TYPE'
-
           | 'COMMENT'
-
           | 'USING'
-
           | 'COLLATE'
-
           | 'ASC'
-
           | 'DESC'
-
           | 'TRUE'
-
           | 'FALSE'
-
           | 'PARALLEL'
-
           | 'BINARY'
-
           | 'START'
-
           | 'ORDER'
-
           | K_TIME_KEY_EXPR
-
           | 'RAW'
-
           | 'HASH'
-
           | 'FIRST'
-
           | 'LAST'
-
           | 'SIGNED'
-
           | 'UNSIGNED'
-
           | 'ENGINE'
-
           | 'IDENTITY'
-
           | 'MATERIALIZED'
-
           | 'SAMPLE'
-
           | 'ALWAYS'
-
           | '='
-
           | ( 'DEFAULT' | 'AS' | 'CHECK' ) ( '(' Expression ')' )?
-
           | ( '+' | '-' )? S_LONG
-
           | S_DOUBLE
-
           | AList
-
           | 'CHARACTER' 'SET'
-
           | 'ARRAY' ArrayConstructor
-
           | '::' ColDataType
-
- - -====================================================================================================================== -RowMovement -====================================================================================================================== - - -.. raw:: html - - - - - - ENABLE - - DISABLE - - ROW - - MOVEMENT - - -
- - -
         ::= ( 'ENABLE' | 'DISABLE' ) 'ROW' 'MOVEMENT'
-
- Referenced by: -
- - -====================================================================================================================== -AList -====================================================================================================================== - - -.. raw:: html - - - - - - ( - - S_LONG - - S_DOUBLE - - S_CHAR_LITERAL - TRUE - - FALSE - - RelObjectName - , - - = - - ) - - -
- -
AList    ::= '(' ( ( S_LONG | S_DOUBLE | S_CHAR_LITERAL | 'TRUE' | 'FALSE' | RelObjectName ) ( ',' | '=' )? )* ')'
-
- Referenced by: -
- - -====================================================================================================================== -ColumnsNamesListItem -====================================================================================================================== - - -.. raw:: html - - - - - - RelObjectName - ( - - S_LONG - ) - - ASC - - DESC - - -
- - -
         ::= RelObjectName ( '(' S_LONG ')' )? ( 'ASC' | 'DESC' )?
-
- Referenced by: -
- - -====================================================================================================================== -ColumnsNamesList -====================================================================================================================== - - -.. raw:: html - - - - - - ( - - ColumnsNamesListItem - , - - ) - - -
- - -
         ::= '(' ColumnsNamesListItem ( ',' ColumnsNamesListItem )* ')'
-
- - -====================================================================================================================== -FuncArgsListItem -====================================================================================================================== - - -.. raw:: html - - - - - - RelObjectName - - RelObjectName - ( - - S_LONG - ) - - -
- - -
         ::= RelObjectName RelObjectName? ( '(' S_LONG ')' )?
-
- Referenced by: -
- - -====================================================================================================================== -FuncArgsList -====================================================================================================================== - - -.. raw:: html - - - - - - ( - - FuncArgsListItem - , - - ) - - -
- - -
         ::= '(' ( FuncArgsListItem ( ',' FuncArgsListItem )* )? ')'
-
- Referenced by: -
- - -====================================================================================================================== -Drop -====================================================================================================================== - - -.. raw:: html - - - - - - DROP - - MATERIALIZED - - S_IDENTIFIER - TEMPORARY - - TABLE - - INDEX - - VIEW - - SCHEMA - - SEQUENCE - - FUNCTION - - IF - - EXISTS - - Table - - FuncArgsList - - S_IDENTIFIER - CASCADE - - RESTRICT - - ON - - Table - -
- -
Drop     ::= 'DROP' 'MATERIALIZED'? ( S_IDENTIFIER | 'TEMPORARY'? 'TABLE' | 'INDEX' | 'VIEW' | 'SCHEMA' | 'SEQUENCE' | 'FUNCTION' ) - ( 'IF' 'EXISTS' )? Table FuncArgsList? ( S_IDENTIFIER | 'CASCADE' | 'RESTRICT' | 'ON' Table )*
-
- Referenced by: -
- - -====================================================================================================================== -Truncate -====================================================================================================================== - - -.. raw:: html - - - - - - TRUNCATE - - TABLE - - ONLY - - Table - , - - CASCADE - - -
- -
Truncate ::= 'TRUNCATE' 'TABLE'? 'ONLY'? Table ( ',' Table )* 'CASCADE'?
-
- Referenced by: -
- - -====================================================================================================================== -AlterExpressionColumnChanges -====================================================================================================================== - - -.. raw:: html - - - - - - AlterExpressionColumnDropDefault - - AlterExpressionColumnSetDefault - - AlterExpressionColumnSetVisibility - ( - - AlterExpressionColumnDataType - , - - ) - - -
- - -
         ::= AlterExpressionColumnDropDefault
-
           | AlterExpressionColumnSetDefault
-
           | AlterExpressionColumnSetVisibility
-
           | '(' AlterExpressionColumnDataType ( ',' AlterExpressionColumnDataType )* ')'
-
- Referenced by: -
- - -====================================================================================================================== -AlterExpressionColumnDataType -====================================================================================================================== - - -.. raw:: html - - - - - - RelObjectName - TYPE - - ColDataType - - CreateParameter - -
- - -
         ::= RelObjectName 'TYPE'? ColDataType? CreateParameter*
-
- - -====================================================================================================================== -AlterExpressionColumnDropNotNull -====================================================================================================================== - - -.. raw:: html - - - - - - RelObjectName - DROP - - NOT - - NULL - - -
- - -
         ::= RelObjectName 'DROP' 'NOT'? 'NULL'
-
- Referenced by: -
- - -====================================================================================================================== -AlterExpressionColumnDropDefault -====================================================================================================================== - - -.. raw:: html - - - - - - RelObjectName - DROP - - DEFAULT - - -
- - -
         ::= RelObjectName 'DROP' 'DEFAULT'
-
- Referenced by: -
- - -====================================================================================================================== -AlterExpressionColumnSetDefault -====================================================================================================================== - - -.. raw:: html - - - - - - RelObjectName - SET - - DEFAULT - - Expression - -
- - -
         ::= RelObjectName 'SET' 'DEFAULT' Expression
-
- Referenced by: -
- - -====================================================================================================================== -AlterExpressionColumnSetVisibility -====================================================================================================================== - - -.. raw:: html - - - - - - RelObjectName - SET - - VISIBLE - - INVISIBLE - - -
- - -
         ::= RelObjectName 'SET' ( 'VISIBLE' | 'INVISIBLE' )
-
- Referenced by: -
- - -====================================================================================================================== -AlterExpressionConstraintState -====================================================================================================================== - - -.. raw:: html - - - - - - NOT - - DEFERRABLE - - VALIDATE - - NOVALIDATE - - ENABLE - - DISABLE - - -
- - -
         ::= ( 'NOT'? 'DEFERRABLE' | 'VALIDATE' | 'NOVALIDATE' | 'ENABLE' | 'DISABLE' - )*
-
- - -====================================================================================================================== -IndexWithComment -====================================================================================================================== - - -.. raw:: html - - - - - - COMMENT - - S_CHAR_LITERAL - -
- - -
         ::= 'COMMENT' S_CHAR_LITERAL
-
- - -====================================================================================================================== -IndexOptionList -====================================================================================================================== - - -.. raw:: html - - - - - - IndexOption - -
- - -
         ::= IndexOption*
-
- Referenced by: -
- - -====================================================================================================================== -UsingIndexType -====================================================================================================================== - - -.. raw:: html - - - - - - USING - - RelObjectName - -
- - -
         ::= 'USING' RelObjectName
-
- - -====================================================================================================================== -IndexOption -====================================================================================================================== - - -.. raw:: html - - - - - - KEY_BLOCK_SIZE - - = - - S_LONG - WITH - - PARSER - - S_IDENTIFIER - COMMENT - - S_CHAR_LITERAL - VISIBLE - - INVISIBLE - - UsingIndexType - -
- - -
         ::= 'KEY_BLOCK_SIZE' '='? S_LONG
-
           | 'WITH' 'PARSER' S_IDENTIFIER
-
           | 'COMMENT' S_CHAR_LITERAL
-
           | 'VISIBLE'
-
           | 'INVISIBLE'
-
           | UsingIndexType
-
- Referenced by: -
- - -====================================================================================================================== -PartitionDefinitions -====================================================================================================================== - - -.. raw:: html - - - - - - ( - - PARTITION - - RelObjectName - VALUES - - LESS - - THAN - - ( - - Expression - ) - - MAXVALUE - - ENGINE - - = - - S_IDENTIFIER - , - - ) - - -
- - -
         ::= '(' ( 'PARTITION' RelObjectName 'VALUES' 'LESS' 'THAN' ( '(' Expression ')' | 'MAXVALUE' ) ( 'ENGINE' '=' S_IDENTIFIER )? ','? )* ')'
-
- - -====================================================================================================================== -PartitionNamesList -====================================================================================================================== - - -.. raw:: html - - - - - - ALL - - S_IDENTIFIER - , - - -
- - -
         ::= 'ALL'
-
           | S_IDENTIFIER ( ',' S_IDENTIFIER )*
-
- - -====================================================================================================================== -AlterExpressionDiscardOrImport -====================================================================================================================== - - -.. raw:: html - - - - - - DISCARD - - IMPORT - - PARTITION - - PartitionNamesList - TABLESPACE - - -
- - -
         ::= ( 'DISCARD' | 'IMPORT' ) ( 'PARTITION' PartitionNamesList )? 'TABLESPACE'
-
- Referenced by: -
- - -====================================================================================================================== -AlterExpressionAddConstraint -====================================================================================================================== - - -.. raw:: html - - - - - - CONSTRAINT - - UNIQUE - - KEY - - INDEX - - RelObjectName - - ColumnsNamesList - - RelObjectName - FOREIGN - - KEY - - ColumnsNamesList - REFERENCES - - Table - - ColumnsNamesList - - ReferentialActionsOnIndex - KEY - - ColumnsNamesList - - AlterExpressionConstraintState - PRIMARY - - KEY - - UNIQUE - - KEY - - INDEX - - ColumnsNamesList - - AlterExpressionConstraintTail - NOT - - ENFORCED - - CheckConstraintSpec - -
- - -
         ::= 'CONSTRAINT' ( 'UNIQUE' ( 'KEY' | 'INDEX' )? RelObjectName ColumnsNamesList | RelObjectName ( ( 'FOREIGN' 'KEY' ColumnsNamesList 'REFERENCES' Table ColumnsNamesList? ReferentialActionsOnIndex | 'KEY' ColumnsNamesList ) AlterExpressionConstraintState | ( 'PRIMARY' 'KEY' | 'UNIQUE' ( 'KEY' | 'INDEX' )? ) ColumnsNamesList AlterExpressionConstraintTail | 'NOT'? 'ENFORCED' | CheckConstraintSpec ) )
-
- Referenced by: -
- - -====================================================================================================================== -AlterExpressionDrop -====================================================================================================================== - - -.. raw:: html - - - - - - DROP - - PARTITION - - PartitionNamesList - - ColumnsNamesList - COLUMN - - IF - - EXISTS - - KeywordOrIdentifier - INVALIDATE - - CASCADE - - CONSTRAINTS - - INDEX - - KEY - - S_IDENTIFIER - - S_QUOTED_IDENTIFIER - UNIQUE - - FOREIGN - - KEY - - ColumnsNamesList - PRIMARY - - KEY - - CONSTRAINT - - IF - - EXISTS - - S_IDENTIFIER - - S_QUOTED_IDENTIFIER - CASCADE - - RESTRICT - - -
- - -
         ::= 'DROP' ( 'PARTITION' PartitionNamesList | ( ColumnsNamesList | 'COLUMN'? ( 'IF' 'EXISTS' )? KeywordOrIdentifier ) 'INVALIDATE'? ( 'CASCADE' 'CONSTRAINTS'? )? | ( 'INDEX' | 'KEY' ) ( S_IDENTIFIER | S_QUOTED_IDENTIFIER ) | ( ( 'UNIQUE' | 'FOREIGN' 'KEY' ) ColumnsNamesList | 'PRIMARY' 'KEY' | 'CONSTRAINT' ( 'IF' 'EXISTS' )? ( S_IDENTIFIER | S_QUOTED_IDENTIFIER ) ) ( 'CASCADE' | 'RESTRICT' )? )
-
- Referenced by: -
- - -====================================================================================================================== -AlterExpressionPartitionOp -====================================================================================================================== - - -.. raw:: html - - - - - - TRUNCATE - - ANALYZE - - CHECK - - OPTIMIZE - - REBUILD - - REPAIR - - PARTITION - - PartitionNamesList - COALESCE - - PARTITION - - S_LONG - REORGANIZE - - PARTITION - - PartitionNamesList - INTO - - PARTITION - - BY - - RANGE - - ( - - Expression - ) - - COLUMNS - - ColumnsNamesList - - PartitionDefinitions - EXCHANGE - - PARTITION - - PartitionNamesList - WITH - - TABLE - - S_IDENTIFIER - WITH - - WITHOUT - - VALIDATION - - REMOVE - - PARTITIONING - - -
- - -
         ::= ( 'TRUNCATE' | 'ANALYZE' | 'CHECK' | 'OPTIMIZE' | 'REBUILD' | 'REPAIR' - ) 'PARTITION' PartitionNamesList
-
           | 'COALESCE' 'PARTITION' S_LONG
-
           | ( 'REORGANIZE' 'PARTITION' PartitionNamesList 'INTO' | 'PARTITION' 'BY' 'RANGE' ( '(' Expression ')' | 'COLUMNS' ColumnsNamesList ) ) PartitionDefinitions
-
           | 'EXCHANGE' 'PARTITION' PartitionNamesList 'WITH' 'TABLE' S_IDENTIFIER ( ( 'WITH' | 'WITHOUT' ) 'VALIDATION' )?
-
           | 'REMOVE' 'PARTITIONING'
-
- Referenced by: -
- - -====================================================================================================================== -AlterExpressionAddAlterModify -====================================================================================================================== - - -.. raw:: html - - - - - - ADD - - ALTER - - MODIFY - - PRIMARY - - KEY - - ColumnsNamesList - - AlterExpressionConstraintState - - AlterExpressionUsingIndex - KEY - - INDEX - - RelObjectName - - UsingIndexType - - IndexColumnsWithParamsList - - IndexOptionList - - AlterExpressionConstraintState - SPATIAL - - FULLTEXT - - INDEX - - KEY - - RelObjectName - - ColumnsNamesList - - IndexOptionList - - RelObjectName - COMMENT - - S_CHAR_LITERAL - PARTITION - - PartitionDefinitions - COLUMN - - COLUMNS - - IF - - NOT - - EXISTS - - AlterExpressionColumnChanges - - AlterExpressionColumnDataType - - AlterExpressionColumnDropNotNull - - AlterExpressionColumnChanges - UNIQUE - - KEY - - INDEX - - S_IDENTIFIER - - S_QUOTED_IDENTIFIER - - ColumnsNamesList - - AlterExpressionUsingIndex - - IndexWithComment - - ForeignKeySpec - CHECK - - RelObjectName - NOT - - ENFORCED - - AlterExpressionAddConstraint - -
- - -
         ::= ( 'ADD' | 'ALTER' | 'MODIFY' ) ( 'PRIMARY' 'KEY' ColumnsNamesList AlterExpressionConstraintState AlterExpressionUsingIndex? | ( 'KEY' | 'INDEX' ) RelObjectName? UsingIndexType? IndexColumnsWithParamsList? IndexOptionList AlterExpressionConstraintState | ( 'SPATIAL' | 'FULLTEXT' ) ( 'INDEX' | 'KEY' )? RelObjectName? ColumnsNamesList IndexOptionList | RelObjectName 'COMMENT' S_CHAR_LITERAL | 'PARTITION' PartitionDefinitions | ( 'COLUMN' | 'COLUMNS' )? ( 'IF' 'NOT' 'EXISTS' )? ( AlterExpressionColumnChanges | AlterExpressionColumnDataType | AlterExpressionColumnDropNotNull ) | AlterExpressionColumnChanges | 'UNIQUE' ( 'KEY' | 'INDEX' )? ( S_IDENTIFIER | S_QUOTED_IDENTIFIER )? ColumnsNamesList AlterExpressionUsingIndex? IndexWithComment? | ForeignKeySpec | 'CHECK' RelObjectName 'NOT'? 'ENFORCED' | AlterExpressionAddConstraint )
-
- Referenced by: -
- - -====================================================================================================================== -AlterExpressionRenameOp -====================================================================================================================== - - -.. raw:: html - - - - - - RENAME - - INDEX - - KEY - - CONSTRAINT - - S_IDENTIFIER - - S_QUOTED_IDENTIFIER - TO - - S_IDENTIFIER - - S_QUOTED_IDENTIFIER - COLUMN - - KeywordOrIdentifier - TO - - KeywordOrIdentifier - -
- - -
         ::= 'RENAME' ( ( ( 'INDEX' | 'KEY' | 'CONSTRAINT' ) ( S_IDENTIFIER | S_QUOTED_IDENTIFIER ) )? 'TO' ( S_IDENTIFIER | S_QUOTED_IDENTIFIER ) | 'COLUMN'? KeywordOrIdentifier 'TO' KeywordOrIdentifier )
-
- Referenced by: -
- - -====================================================================================================================== -AlterExpression -====================================================================================================================== - - -.. raw:: html - - - - - - AlterExpressionAddAlterModify - CHANGE - - COLUMN - - KeywordOrIdentifier - - AlterExpressionColumnDataType - - AlterExpressionDrop - FORCE - - ROW - - LEVEL - - SECURITY - - NO - - FORCE - - ROW - - LEVEL - - SECURITY - - ALGORITHM - - LOCK - - ENGINE - - = - - RelObjectName - KEY_BLOCK_SIZE - - AUTO_INCREMENT - - = - - S_LONG - - AlterExpressionRenameOp - CONVERT - - TO - - CHARACTER - - SET - - S_IDENTIFIER - COLLATE - - DEFAULT - - CHARACTER - - SET - - = - - S_IDENTIFIER - COLLATE - - COLLATE - - = - - S_IDENTIFIER - COMMENT - - ENCRYPTION - - = - - S_CHAR_LITERAL - - AlterExpressionDiscardOrImport - DISABLE - - ENABLE - - ROW - - LEVEL - - SECURITY - - KEYS - - AlterExpressionPartitionOp - - captureRest - -
- - -
         ::= AlterExpressionAddAlterModify
-
           | 'CHANGE' 'COLUMN'? KeywordOrIdentifier AlterExpressionColumnDataType
-
           | AlterExpressionDrop
-
           | 'FORCE' ( 'ROW' 'LEVEL' 'SECURITY' )?
-
           | 'NO' 'FORCE' 'ROW' 'LEVEL' 'SECURITY'
-
           | ( 'ALGORITHM' | 'LOCK' | 'ENGINE' ) '='? RelObjectName
-
           | ( 'KEY_BLOCK_SIZE' | 'AUTO_INCREMENT' ) '='? S_LONG
-
           | AlterExpressionRenameOp
-
           | ( 'CONVERT' 'TO' 'CHARACTER' 'SET' ( S_IDENTIFIER 'COLLATE' )? | 'DEFAULT'? ( 'CHARACTER' 'SET' ( '='? S_IDENTIFIER 'COLLATE' )? | 'COLLATE' ) '='? ) S_IDENTIFIER
-
           | ( 'COMMENT' | 'ENCRYPTION' ) '='? S_CHAR_LITERAL
-
           | AlterExpressionDiscardOrImport
-
           | ( 'DISABLE' | 'ENABLE' ) ( 'ROW' 'LEVEL' 'SECURITY' | 'KEYS' )
-
           | AlterExpressionPartitionOp
-
           | captureRest
-
- Referenced by: -
- - -====================================================================================================================== -Alter -====================================================================================================================== - - -.. raw:: html - - - - - - ALTER - - AlterTable - - AlterSession - - AlterView - - AlterSystemStatement - - AlterSequence - - captureRest - REPLACE - - AlterView - - captureRest - -
- - -
           | 'REPLACE' ( AlterView | captureRest )
-
- Referenced by: -
- - -====================================================================================================================== -AlterTable -====================================================================================================================== - - -.. raw:: html - - - - - - TABLE - - ONLY - - IF - - EXISTS - - Table - - AlterExpression - , - - -
- - -
         ::= 'TABLE' 'ONLY'? ( 'IF' 'EXISTS' )? Table AlterExpression ( ',' AlterExpression )*
-
- Referenced by: -
- - -====================================================================================================================== -AlterSession -====================================================================================================================== - - -.. raw:: html - - - - - - SESSION - - ADVISE - - COMMIT - - ROLLBACK - - NOTHING - - CLOSE - - DATABASE - - LINK - - ENABLE - - DISABLE - - COMMIT - - IN - - PROCEDURE - - GUARD - - PARALLEL - - DML - - DDL - - QUERY - - RESUMABLE - - FORCE - - PARALLEL - - DML - - DDL - - QUERY - - SET - - S_CHAR_LITERAL - - S_IDENTIFIER - = - - S_LONG - PARALLEL - - -
- - -
         ::= 'SESSION' ( 'ADVISE' ( 'COMMIT' | 'ROLLBACK' | 'NOTHING' ) | 'CLOSE' - 'DATABASE' 'LINK' | ( 'ENABLE' | 'DISABLE' ) ( 'COMMIT' 'IN' 'PROCEDURE' | 'GUARD' - | 'PARALLEL' ( 'DML' | 'DDL' | 'QUERY' ) | 'RESUMABLE' ) | 'FORCE' 'PARALLEL' ( 'DML' - | 'DDL' | 'QUERY' ) | 'SET' ) ( S_CHAR_LITERAL | S_IDENTIFIER | '=' | S_LONG | 'PARALLEL' )*
-
- Referenced by: -
- - -====================================================================================================================== -AlterSystemStatement -====================================================================================================================== - - -.. raw:: html - - - - - - SYSTEM - - ARCHIVE - - LOG - - CHECKPOINT - - DUMP - - ACTIVE - - SESSION - - HISTORY - - ENABLE - - DISABLE - - DISTRIBUTED - - RECOVERY - - RESTRICTED - - SESSION - - FLUSH - - DISCONNECT - - KILL - - SESSION - - SWITCH - - SUSPEND - - RESUME - - QUIESCE - - RESTRICTED - - UNQIESCE - - SHUTDOWN - - REGISTER - - SET - - RESET - - captureRest - -
- - -
         ::= 'SYSTEM' ( 'ARCHIVE' 'LOG' | 'CHECKPOINT' | 'DUMP' 'ACTIVE' 'SESSION' - 'HISTORY' | ( 'ENABLE' | 'DISABLE' ) ( 'DISTRIBUTED' 'RECOVERY' | 'RESTRICTED' 'SESSION' - ) | 'FLUSH' | ( 'DISCONNECT' | 'KILL' ) 'SESSION' | 'SWITCH' | 'SUSPEND' | 'RESUME' - | 'QUIESCE' 'RESTRICTED' | 'UNQIESCE' | 'SHUTDOWN' | 'REGISTER' | 'SET' | 'RESET' - ) captureRest
-
- Referenced by: -
- - -====================================================================================================================== -Wait -====================================================================================================================== - - -.. raw:: html - - - - - - WAIT - - S_LONG - -
- -
Wait     ::= 'WAIT' S_LONG
-
- Referenced by: -
- - -====================================================================================================================== -SavepointStatement -====================================================================================================================== - - -.. raw:: html - - - - - - SAVEPOINT - - S_IDENTIFIER - -
- - -
         ::= 'SAVEPOINT' S_IDENTIFIER
-
- Referenced by: -
- - -====================================================================================================================== -RollbackStatement -====================================================================================================================== - - -.. raw:: html - - - - - - ROLLBACK - - WORK - - TO - - SAVEPOINT - - S_IDENTIFIER - FORCE - - S_CHAR_LITERAL - -
- - -
         ::= 'ROLLBACK' 'WORK'? ( 'TO' 'SAVEPOINT'? S_IDENTIFIER | 'FORCE' S_CHAR_LITERAL )?
-
- Referenced by: -
- - -====================================================================================================================== -Comment -====================================================================================================================== - - -.. raw:: html - - - - - - COMMENT - - ON - - TABLE - - VIEW - - Table - COLUMN - - Column - IS - - S_CHAR_LITERAL - -
- -
Comment  ::= 'COMMENT' 'ON' ( ( 'TABLE' | 'VIEW' ) Table | 'COLUMN' Column ) 'IS' S_CHAR_LITERAL
-
- Referenced by: -
- - -====================================================================================================================== -Grant -====================================================================================================================== - - -.. raw:: html - - - - - - GRANT - - readGrantTypes - , - - ON - - RelObjectNames - - S_IDENTIFIER - TO - - UsersList - -
- -
Grant    ::= 'GRANT' ( ( readGrantTypes ( ',' readGrantTypes )* )? 'ON' RelObjectNames | S_IDENTIFIER ) 'TO' UsersList
-
- Referenced by: -
- - -====================================================================================================================== -UsersList -====================================================================================================================== - - -.. raw:: html - - - - - - RelObjectName - , - - ColumnsNamesListItem - -
- - -
         ::= RelObjectName ( ',' ColumnsNamesListItem )*
-
- Referenced by: -
- - -====================================================================================================================== -readGrantTypes -====================================================================================================================== - - -.. raw:: html - - - - - - K_SELECT - INSERT - - UPDATE - - DELETE - - EXECUTE - - ALTER - - DROP - - -
- - -
         ::= K_SELECT
-
           | 'INSERT'
-
           | 'UPDATE'
-
           | 'DELETE'
-
           | 'EXECUTE'
-
           | 'ALTER'
-
           | 'DROP'
-
- Referenced by: -
- - -====================================================================================================================== -Sequence -====================================================================================================================== - - -.. raw:: html - - - - - - RelObjectNames - -
- - -
- Referenced by: -
- - -====================================================================================================================== -SequenceParameters -====================================================================================================================== - - -.. raw:: html - - - - - - INCREMENT - - BY - - START - - WITH - - MAXVALUE - - MINVALUE - - CACHE - - S_LONG - RESTART - - WITH - - S_LONG - NOMAXVALUE - - NOMINVALUE - - NOCYCLE - - CYCLE - - NOCACHE - - ORDER - - NOORDER - - KEEP - - NOKEEP - - SESSION - - GLOBAL - - -
- - -
         ::= ( ( 'INCREMENT' 'BY'? | 'START' 'WITH'? | 'MAXVALUE' | 'MINVALUE' | 'CACHE' - ) S_LONG | 'RESTART' ( 'WITH' S_LONG )? | 'NOMAXVALUE' | 'NOMINVALUE' | 'NOCYCLE' | 'CYCLE' | 'NOCACHE' | 'ORDER' | 'NOORDER' - | 'KEEP' | 'NOKEEP' | 'SESSION' | 'GLOBAL' )*
-
- Referenced by: -
- - -====================================================================================================================== -CreateSequence -====================================================================================================================== - - -.. raw:: html - - - - - - SEQUENCE - - Sequence - AS - - S_IDENTIFIER - - DATA_TYPE - - SequenceParameters - -
- - -
         ::= 'SEQUENCE' Sequence ( 'AS' ( S_IDENTIFIER | DATA_TYPE ) )? SequenceParameters
-
- Referenced by: -
- - -====================================================================================================================== -AlterSequence -====================================================================================================================== - - -.. raw:: html - - - - - - SEQUENCE - - Sequence - - SequenceParameters - -
- - -
         ::= 'SEQUENCE' Sequence SequenceParameters
-
- Referenced by: -
- - -====================================================================================================================== -Create -====================================================================================================================== - - -.. raw:: html - - - - - - CREATE - - OR - - REPLACE - - CreateFunctionStatement - - CreateSchema - - CreateSequence - - CreateSynonym - - CreateTable - - CreateView - - CreatePolicy - TRIGGER - - DOMAIN - - captureRest - - CreateIndex - -
- -
Create   ::= 'CREATE' ( 'OR' 'REPLACE' )? ( CreateFunctionStatement | CreateSchema | CreateSequence | CreateSynonym | CreateTable | CreateView | CreatePolicy | ( 'TRIGGER' | 'DOMAIN' )? captureRest | CreateIndex )
-
- Referenced by: -
- - -====================================================================================================================== -CreateFunctionStatement -====================================================================================================================== - - -.. raw:: html - - - - - - FUNCTION - - PROCEDURE - - captureFunctionBody - -
- - -
         ::= ( 'FUNCTION' | 'PROCEDURE' ) captureFunctionBody
-
- Referenced by: -
- - -====================================================================================================================== -CreateSynonym -====================================================================================================================== - - -.. raw:: html - - - - - - PUBLIC - - SYNONYM - - Synonym - FOR - - RelObjectNames - -
- - -
         ::= 'PUBLIC'? 'SYNONYM' Synonym 'FOR' RelObjectNames
-
- Referenced by: -
- - -====================================================================================================================== -Synonym -====================================================================================================================== - - -.. raw:: html - - - - - - RelObjectNames - -
- - -
- Referenced by: -
- - -====================================================================================================================== -CreatePolicy -====================================================================================================================== - - -.. raw:: html - - - - - - POLICY - - RelObjectName - ON - - Table - FOR - - ALL - - K_SELECT - INSERT - - UPDATE - - DELETE - - TO - - RelObjectName - , - - USING - - ( - - Expression - ) - - WITH - - CHECK - - ( - - Expression - ) - - -
- - -
         ::= 'POLICY' RelObjectName 'ON' Table ( 'FOR' ( 'ALL' | K_SELECT | 'INSERT' | 'UPDATE' | 'DELETE' ) )? ( 'TO' RelObjectName ( ',' RelObjectName )* )? ( 'USING' '(' Expression ')' )? ( 'WITH' 'CHECK' '(' Expression ')' )?
-
- Referenced by: -
- - -====================================================================================================================== -UnsupportedStatement -====================================================================================================================== - - -.. raw:: html - - - - - - captureUnsupportedStatementDeclaration - -
- - - -
- Referenced by: -
- - -====================================================================================================================== -IdentifierChain -====================================================================================================================== - - -.. raw:: html - - - - - - RelObjectNameExt - . - - -
- - -
         ::= RelObjectNameExt ( '.' RelObjectNameExt )*
-
- - -====================================================================================================================== -IdentifierChain2 -====================================================================================================================== - - -.. raw:: html - - - - - - . - - RelObjectNameExt - -
- - -
         ::= ( '.' RelObjectNameExt )*
-
- Referenced by: -
- - -====================================================================================================================== -CharacterPrimary -====================================================================================================================== - - -.. raw:: html - - - - - - TranscodingFunction - - TrimFunction - -
- - -
         ::= TranscodingFunction
-
           | TrimFunction
-
- Referenced by: -
- - -====================================================================================================================== -TranscodingFunction -====================================================================================================================== - - -.. raw:: html - - - - - - TRY_CONVERT - - SAFE_CONVERT - - CONVERT - - ( - - ColDataType - , - - Expression - , - - S_LONG - - Expression - USING - - IdentifierChain - ) - - -
- - -
         ::= ( 'TRY_CONVERT' | 'SAFE_CONVERT' | 'CONVERT' ) '(' ( ColDataType ',' Expression ( ',' S_LONG )? | Expression 'USING' IdentifierChain ) ')'
-
- Referenced by: -
- - -====================================================================================================================== -TrimFunction -====================================================================================================================== - - -.. raw:: html - - - - - - TRIM - - ( - - LEADING - - TRAILING - - BOTH - - Expression - , - - FROM - - Expression - ) - - -
- - -
         ::= 'TRIM' '(' ( 'LEADING' | 'TRAILING' | 'BOTH' )? Expression? ( ( ',' | 'FROM' ) Expression )? ')'
-
- Referenced by: -
- - -====================================================================================================================== -SnowflakeTimeTravelAt -====================================================================================================================== - - -.. raw:: html - - - - - - AT - - ( - - K_DATETIMELITERAL - OFFSET - - => - - Expression - STATEMENT - - => - - S_CHAR_LITERAL - - S_IDENTIFIER - - S_QUOTED_IDENTIFIER - STREAM - - => - - S_CHAR_LITERAL - ) - - -
- - -
         ::= 'AT' '(' ( ( K_DATETIMELITERAL | 'OFFSET' ) '=>' Expression | 'STATEMENT' '=>' ( S_CHAR_LITERAL | S_IDENTIFIER | S_QUOTED_IDENTIFIER ) | 'STREAM' '=>' S_CHAR_LITERAL ) ')'
-
- - -====================================================================================================================== -SnowflakeTimeTravelBefore -====================================================================================================================== - - -.. raw:: html - - - - - - BEFORE - - ( - - STATEMENT - - => - - S_CHAR_LITERAL - - S_IDENTIFIER - - S_QUOTED_IDENTIFIER - ) - - -
- - -
         ::= 'BEFORE' '(' 'STATEMENT' '=>' ( S_CHAR_LITERAL | S_IDENTIFIER | S_QUOTED_IDENTIFIER ) ')'
-
- - -====================================================================================================================== -SnowflakeTimeTravelChange -====================================================================================================================== - - -.. raw:: html - - - - - - CHANGES - - ( - - INFORMATION - - => - - DEFAULT - - APPEND_ONLY - - ) - - SnowflakeTimeTravelAt - - SnowflakeTimeTravelBefore - END - - ( - - K_DATETIMELITERAL - OFFSET - - => - - Expression - STATEMENT - - => - - S_CHAR_LITERAL - - S_IDENTIFIER - - S_QUOTED_IDENTIFIER - ) - - -
- - -
         ::= 'CHANGES' '(' 'INFORMATION' '=>' ( 'DEFAULT' | 'APPEND_ONLY' ) ')' ( - SnowflakeTimeTravelAt | SnowflakeTimeTravelBefore ) ( 'END' '(' ( ( K_DATETIMELITERAL | 'OFFSET' ) '=>' Expression | 'STATEMENT' '=>' ( S_CHAR_LITERAL | S_IDENTIFIER | S_QUOTED_IDENTIFIER ) ) ')' )?
-
- Referenced by: -
- - -====================================================================================================================== -DataBricksTemporalSpec -====================================================================================================================== - - -.. raw:: html - - - - - - @ - - @V - - S_CHAR_LITERAL - - S_LONG - FOR - - SYSTEM_TIMESTAMP - - K_DATETIMELITERAL - AS - - OF - - Expression - SYSTEM_VERSION - - VERSION - - AS - - OF - - S_LONG - - S_CHAR_LITERAL - -
- - -
         ::= ( '@' | '@V' ) ( S_CHAR_LITERAL | S_LONG )
-
           | 'FOR'? ( 'SYSTEM_TIMESTAMP' | K_DATETIMELITERAL ) 'AS' 'OF' Expression
-
           | ( 'SYSTEM_VERSION' | 'VERSION' ) 'AS' 'OF' ( S_LONG | S_CHAR_LITERAL )
-
- Referenced by: -
- - -====================================================================================================================== -BigQueryHistoricalVersion -====================================================================================================================== - - -.. raw:: html - - - - - - FOR - - SYSTEM_TIME - - AS - - OF - - Expression - -
- - -
         ::= 'FOR' 'SYSTEM_TIME' 'AS' 'OF' Expression
-
- Referenced by: -
- - -====================================================================================================================== -TimeTravelBeforeAlias -====================================================================================================================== - - -.. raw:: html - - - - - - SnowflakeTimeTravelAt - - SnowflakeTimeTravelBefore - - SnowflakeTimeTravelChange - - DataBricksTemporalSpec - -
- - -
         ::= SnowflakeTimeTravelAt
-
           | SnowflakeTimeTravelBefore
-
           | SnowflakeTimeTravelChange
-
           | DataBricksTemporalSpec
-
- Referenced by: -
- - -====================================================================================================================== -TimeTravelAfterAlias -====================================================================================================================== - - -.. raw:: html - - - - - - BigQueryHistoricalVersion - -
- - -
         ::= BigQueryHistoricalVersion
-
- Referenced by: -
- - -====================================================================================================================== -WHITESPACE -====================================================================================================================== - - -.. raw:: html - - - - - - - - [#x9] - - [#xD] - - [#xA] - - -
- - -
         ::= [ #x9#xD#xA]
-
- - -====================================================================================================================== -K_ISOLATION -====================================================================================================================== - - -.. raw:: html - - - - - - UR - - RS - - RR - - CS - - -
- - -
         ::= 'UR'
-
           | 'RS'
-
           | 'RR'
-
           | 'CS'
-
- Referenced by: -
- - -====================================================================================================================== -K_NEXTVAL -====================================================================================================================== - - -.. raw:: html - - - - - - NEXTVAL - - - - FOR - - NEXT - - - - VALUE - - - - FOR - - -
- - -
         ::= 'NEXTVAL' ( ' '+ 'FOR' )?
-
           | 'NEXT' ' '+ 'VALUE' ' '+ 'FOR'
-
- - -====================================================================================================================== -K_TEXT_LITERAL -====================================================================================================================== - - -.. raw:: html - - - - - - TEXT - - TINYTEXT - - MEDIUMTEXT - - LONGTEXT - - -
- - -
         ::= 'TEXT'
-
           | 'TINYTEXT'
-
           | 'MEDIUMTEXT'
-
           | 'LONGTEXT'
-
- Referenced by: -
- - -====================================================================================================================== -K_TIME_KEY_EXPR -====================================================================================================================== - - -.. raw:: html - - - - - - CURRENT - - _ - - - - TIMESTAMP - - TIME - - DATE - - TIMEZONE - - () - - -
- - -
         ::= 'CURRENT' ( '_' | ' '+ ) ( 'TIMESTAMP' | 'TIME' | 'DATE' | 'TIMEZONE' - ) '()'?
-
- - -====================================================================================================================== -K_STRING_FUNCTION_NAME -====================================================================================================================== - - -.. raw:: html - - - - - - SUBSTR - - SUBSTRING - - TRIM - - POSITION - - OVERLAY - - -
- - -
         ::= 'SUBSTR'
-
           | 'SUBSTRING'
-
           | 'TRIM'
-
           | 'POSITION'
-
           | 'OVERLAY'
-
- - -====================================================================================================================== -K_DATETIMELITERAL -====================================================================================================================== - - -.. raw:: html - - - - - - DATE - - DATETIME - - TIME - - TIMESTAMP - - TIMESTAMPTZ - - -
- - -
         ::= 'DATE'
-
           | 'DATETIME'
-
           | 'TIME'
-
           | 'TIMESTAMP'
-
           | 'TIMESTAMPTZ'
-
- - -====================================================================================================================== -K_DATE_LITERAL -====================================================================================================================== - - -.. raw:: html - - - - - - YEAR - - MONTH - - DAY - - HOUR - - MINUTE - - SECOND - - -
- - -
         ::= 'YEAR'
-
           | 'MONTH'
-
           | 'DAY'
-
           | 'HOUR'
-
           | 'MINUTE'
-
           | 'SECOND'
-
- - -====================================================================================================================== -K_SELECT -====================================================================================================================== - - -.. raw:: html - - - - - - SELECT - - SEL - - -
- -
K_SELECT ::= 'SELECT'
-
           | 'SEL'
-
- - -====================================================================================================================== -K_SIMILAR_TO -====================================================================================================================== - - -.. raw:: html - - - - - - SIMILAR - - - - TO - - -
- - -
         ::= 'SIMILAR' ' '+ 'TO'
-
- Referenced by: -
- - -====================================================================================================================== -ST_SEMICOLON -====================================================================================================================== - - -.. raw:: html - - - - - - ; - - [#xA] - - / - - [#xA] - - go - - [#xA] - - -
- - -
         ::= ';'
-
           | #xA ( [/#xA] | 'go' ) #xA
-
- Referenced by: -
- - -====================================================================================================================== -OP_GREATERTHANEQUALS -====================================================================================================================== - - -.. raw:: html - - - - - - > - - WHITESPACE - = - - -
- - -
         ::= '>' WHITESPACE* '='
-
- Referenced by: -
- - -====================================================================================================================== -OP_MINORTHANEQUALS -====================================================================================================================== - - -.. raw:: html - - - - - - < - - WHITESPACE - = - - -
- - -
         ::= '<' WHITESPACE* '='
-
- Referenced by: -
- - -====================================================================================================================== -OP_NOTEQUALSSTANDARD -====================================================================================================================== - - -.. raw:: html - - - - - - < - - WHITESPACE - > - - -
- - -
         ::= '<' WHITESPACE* '>'
-
- Referenced by: -
- - -====================================================================================================================== -OP_NOTEQUALSBANG -====================================================================================================================== - - -.. raw:: html - - - - - - ! - - WHITESPACE - = - - -
- - -
         ::= '!' WHITESPACE* '='
-
- Referenced by: -
- - -====================================================================================================================== -OP_NOTEQUALSHAT -====================================================================================================================== - - -.. raw:: html - - - - - - ^ - - WHITESPACE - = - - -
- - -
         ::= '^' WHITESPACE* '='
-
- Referenced by: -
- - -====================================================================================================================== -OP_CONCAT -====================================================================================================================== - - -.. raw:: html - - - - - - | - - WHITESPACE - | - - -
- - -
         ::= '|' WHITESPACE* '|'
-
- - -====================================================================================================================== -DT_ZONE -====================================================================================================================== - - -.. raw:: html - - - - - - K_DATETIMELITERAL - - WHITESPACE - ( - - S_LONG - ) - - WHITESPACE - WITH - - WITHOUT - - WHITESPACE - LOCAL - - WHITESPACE - TIME - - WHITESPACE - ZONE - - -
- -
DT_ZONE  ::= K_DATETIMELITERAL WHITESPACE* ( '(' S_LONG ')' )? WHITESPACE* ( 'WITH' | 'WITHOUT' ) WHITESPACE+ ( 'LOCAL' WHITESPACE+ )? 'TIME' WHITESPACE+ 'ZONE'
-
- - -====================================================================================================================== -DATA_TYPE -====================================================================================================================== - - -.. raw:: html - - - - - - BISTRING - - TYPE_BLOB - - TYPE_BOOLEAN - ENUM - - TYPE_REAL - - TYPE_DOUBLE - UUID - - MAP - - TYPE_TINYINT - - TYPE_SMALLINT - - TYPE_INTEGER - - TYPE_BIGINT - HUGEINT - - UTINYINT - - USMALLINT - - UINTEGER - - UBIGINT - - UHUGEINT - - TYPE_DECIMAL - - TYPE_VARCHAR - TIMETZ - - TYPE_TIMESTAMP - -
- - -
         ::= 'BISTRING'
-
           | TYPE_BLOB
-
           | TYPE_BOOLEAN
-
           | 'ENUM'
-
           | TYPE_REAL
-
           | TYPE_DOUBLE
-
           | 'UUID'
-
           | 'MAP'
-
           | TYPE_TINYINT
-
           | TYPE_SMALLINT
-
           | TYPE_INTEGER
-
           | TYPE_BIGINT
-
           | 'HUGEINT'
-
           | 'UTINYINT'
-
           | 'USMALLINT'
-
           | 'UINTEGER'
-
           | 'UBIGINT'
-
           | 'UHUGEINT'
-
           | TYPE_DECIMAL
-
           | TYPE_VARCHAR
-
           | 'TIMETZ'
-
           | TYPE_TIMESTAMP
-
- - -====================================================================================================================== -TYPE_BLOB -====================================================================================================================== - - -.. raw:: html - - - - - - BLOB - - BYTEA - - BINARY - - VARBINARY - - BYTES - - -
- - -
         ::= 'BLOB'
-
           | 'BYTEA'
-
           | 'BINARY'
-
           | 'VARBINARY'
-
           | 'BYTES'
-
- Referenced by: -
- - -====================================================================================================================== -TYPE_BOOLEAN -====================================================================================================================== - - -.. raw:: html - - - - - - BOOLEAN - - BOOL - - -
- - -
         ::= 'BOOLEAN'
-
           | 'BOOL'
-
- Referenced by: -
- - -====================================================================================================================== -TYPE_DECIMAL -====================================================================================================================== - - -.. raw:: html - - - - - - DECIMAL - - NUMBER - - NUMERIC - - -
- - -
         ::= 'DECIMAL'
-
           | 'NUMBER'
-
           | 'NUMERIC'
-
- Referenced by: -
- - -====================================================================================================================== -TYPE_TINYINT -====================================================================================================================== - - -.. raw:: html - - - - - - TINYINT - - INT1 - - -
- - -
         ::= 'TINYINT'
-
           | 'INT1'
-
- Referenced by: -
- - -====================================================================================================================== -TYPE_SMALLINT -====================================================================================================================== - - -.. raw:: html - - - - - - SMALLINT - - INT2 - - SHORT - - -
- - -
         ::= 'SMALLINT'
-
           | 'INT2'
-
           | 'SHORT'
-
- Referenced by: -
- - -====================================================================================================================== -TYPE_INTEGER -====================================================================================================================== - - -.. raw:: html - - - - - - INTEGER - - INT - - INT4 - - SIGNED - - UNSIGNED - - -
- - -
         ::= 'INTEGER'
-
           | 'INT'
-
           | 'INT4'
-
           | 'SIGNED'
-
           | 'UNSIGNED'
-
- Referenced by: -
- - -====================================================================================================================== -TYPE_BIGINT -====================================================================================================================== - - -.. raw:: html - - - - - - BIGINT - - INT8 - - LONG - - -
- - -
         ::= 'BIGINT'
-
           | 'INT8'
-
           | 'LONG'
-
- Referenced by: -
- - -====================================================================================================================== -TYPE_REAL -====================================================================================================================== - - -.. raw:: html - - - - - - REAL - - FLOAT4 - - FLOAT - - -
- - -
         ::= 'REAL'
-
           | 'FLOAT4'
-
           | 'FLOAT'
-
- Referenced by: -
- - -====================================================================================================================== -TYPE_DOUBLE -====================================================================================================================== - - -.. raw:: html - - - - - - DOUBLE - - PRECISION - - FLOAT8 - - FLOAT64 - - -
- - -
         ::= 'DOUBLE'
-
           | 'PRECISION'
-
           | 'FLOAT8'
-
           | 'FLOAT64'
-
- Referenced by: -
- - -====================================================================================================================== -TYPE_VARCHAR -====================================================================================================================== - - -.. raw:: html - - - - - - NVARCHAR - - VARCHAR - - NCHAR - - CHAR - - BPCHAR - - TEXT - - STRING - - CHARACTER - - VARYING - - -
- - -
         ::= 'NVARCHAR'
-
           | 'VARCHAR'
-
           | 'NCHAR'
-
           | 'CHAR'
-
           | 'BPCHAR'
-
           | 'TEXT'
-
           | 'STRING'
-
           | 'CHARACTER'
-
           | 'VARYING'
-
- Referenced by: -
- - -====================================================================================================================== -TYPE_TIMESTAMP -====================================================================================================================== - - -.. raw:: html - - - - - - TIMESTAMP_NS - - TIMESTAMP_MS - - TIMESTAMP_S - - -
- - -
         ::= 'TIMESTAMP_NS'
-
           | 'TIMESTAMP_MS'
-
           | 'TIMESTAMP_S'
-
- Referenced by: -
- - -====================================================================================================================== -S_DOUBLE -====================================================================================================================== - - -.. raw:: html - - - - - - S_LONG - . - - S_LONG - e - - E - - + - - [#x2D] - - S_LONG - - S_LONG - . - - e - - E - - + - - [#x2D] - - S_LONG - e - - E - - + - - [#x2D] - - S_LONG - -
- -
S_DOUBLE ::= S_LONG? '.' S_LONG ( [eE] [+#x2D]? S_LONG )?
-
           | S_LONG ( '.' ( [eE] [+#x2D]? S_LONG )? | [eE] [+#x2D]? S_LONG )
-
- - -====================================================================================================================== -S_LONG -====================================================================================================================== - - -.. raw:: html - - - - - - DIGIT - -
- -
S_LONG   ::= DIGIT+
-
- - -====================================================================================================================== -DIGIT -====================================================================================================================== - - -.. raw:: html - - - - - - [0-9] - - -
- -
DIGIT    ::= [0-9]
-
- Referenced by: -
- - -====================================================================================================================== -S_HEX -====================================================================================================================== - - -.. raw:: html - - - - - - X - - ' - - HEX_VALUE - ' - - - - 0x - - HEX_VALUE - -
- -
S_HEX    ::= 'X' ( "'" HEX_VALUE* "'" ' '* )+
-
           | '0x' HEX_VALUE+
-
- - -====================================================================================================================== -HEX_VALUE -====================================================================================================================== - - -.. raw:: html - - - - - - [0-9] - - [A-F] - - - - -
- - -
         ::= [0-9A-F ]
-
- Referenced by: -
- - -====================================================================================================================== -LINE_COMMENT -====================================================================================================================== - - -.. raw:: html - - - - - - -- - - // - - [^#xD#xA] - - -
- - -
         ::= ( '--' | '//' ) [^#xD#xA]*
-
- Not referenced by any. -
- - -====================================================================================================================== -MULTI_LINE_COMMENT -====================================================================================================================== - - -.. raw:: html - - - - - - . - - -
- - -
         ::= .
-
- Not referenced by any. -
- - -====================================================================================================================== -S_PARAMETER -====================================================================================================================== - - -.. raw:: html - - - - - - $ - - [0-9] - - -
- - -
         ::= '$' [0-9]+
-
- Referenced by: -
- - -====================================================================================================================== -S_IDENTIFIER -====================================================================================================================== - - -.. raw:: html - - - - - - LETTER - - PART_LETTER - -
- - -
         ::= LETTER PART_LETTER*
-
- - -====================================================================================================================== -LETTER -====================================================================================================================== - - -.. raw:: html - - - - - - UnicodeIdentifierStart - - Nd - $ - - _ - - [#x23] - - -
- - -
           | Nd
-
           | [$_#x23]
-
- Referenced by: -
- - -====================================================================================================================== -PART_LETTER -====================================================================================================================== - - -.. raw:: html - - - - - - UnicodeIdentifierStart - - UnicodeIdentifierExtend - $ - - _ - - @ - - [#x23] - - -
- - -
         ::= UnicodeIdentifierStart
-
           | UnicodeIdentifierExtend
-
           | [$_@#x23]
-
- Referenced by: -
- - -====================================================================================================================== -S_AT_IDENTIFIER -====================================================================================================================== - - -.. raw:: html - - - - - - @ - - @ - - S_IDENTIFIER - -
- - -
         ::= '@' '@'? S_IDENTIFIER
-
- Referenced by: -
- - -====================================================================================================================== -UnicodeIdentifierStart -====================================================================================================================== - - -.. raw:: html - - - - - - [#xB7] - - Ll - - Lm - - Lo - - Lt - - Lu - - Nl - - CJK - -
- - -
         ::= #xB7
-
           | Ll
-
           | Lm
-
           | Lo
-
           | Lt
-
           | Lu
-
           | Nl
-
           | CJK
-
- Referenced by: -
- - -====================================================================================================================== -Ll -====================================================================================================================== - - -.. raw:: html - - - - - - [a-z] - - [#xB5] - - [#xDF-#xF6] - - [#xF8-#xFF] - - [#x101] - - [#x103] - - [#x105] - - [#x107] - - [#x109] - - [#x10B] - - [#x10D] - - [#x10F] - - [#x111] - - [#x113] - - [#x115] - - [#x117] - - [#x119] - - [#x11B] - - [#x11D] - - [#x11F] - - [#x121] - - [#x123] - - [#x125] - - [#x127] - - [#x129] - - [#x12B] - - [#x12D] - - [#x12F] - - [#x131] - - [#x133] - - [#x135] - - [#x137-#x138] - - [#x13A] - - [#x13C] - - [#x13E] - - [#x140] - - [#x142] - - [#x144] - - [#x146] - - [#x148-#x149] - - [#x14B] - - [#x14D] - - [#x14F] - - [#x151] - - [#x153] - - [#x155] - - [#x157] - - [#x159] - - [#x15B] - - [#x15D] - - [#x15F] - - [#x161] - - [#x163] - - [#x165] - - [#x167] - - [#x169] - - [#x16B] - - [#x16D] - - [#x16F] - - [#x171] - - [#x173] - - [#x175] - - [#x177] - - [#x17A] - - [#x17C] - - [#x17E-#x180] - - [#x183] - - [#x185] - - [#x188] - - [#x18C-#x18D] - - [#x192] - - [#x195] - - [#x199-#x19B] - - [#x19E] - - [#x1A1] - - [#x1A3] - - [#x1A5] - - [#x1A8] - - [#x1AA-#x1AB] - - [#x1AD] - - [#x1B0] - - [#x1B4] - - [#x1B6] - - [#x1B9-#x1BA] - - [#x1BD-#x1BF] - - [#x1C6] - - [#x1C9] - - [#x1CC] - - [#x1CE] - - [#x1D0] - - [#x1D2] - - [#x1D4] - - [#x1D6] - - [#x1D8] - - [#x1DA] - - [#x1DC-#x1DD] - - [#x1DF] - - [#x1E1] - - [#x1E3] - - [#x1E5] - - [#x1E7] - - [#x1E9] - - [#x1EB] - - [#x1ED] - - [#x1EF-#x1F0] - - [#x1F3] - - [#x1F5] - - [#x1F9] - - [#x1FB] - - [#x1FD] - - [#x1FF] - - [#x201] - - [#x203] - - [#x205] - - [#x207] - - [#x209] - - [#x20B] - - [#x20D] - - [#x20F] - - [#x211] - - [#x213] - - [#x215] - - [#x217] - - [#x219] - - [#x21B] - - [#x21D] - - [#x21F] - - [#x221] - - [#x223] - - [#x225] - - [#x227] - - [#x229] - - [#x22B] - - [#x22D] - - [#x22F] - - [#x231] - - [#x233-#x239] - - [#x23C] - - [#x23F-#x240] - - [#x242] - - [#x247] - - [#x249] - - [#x24B] - - [#x24D] - - [#x24F-#x293] - - [#x295-#x2AF] - - [#x371] - - [#x373] - - [#x377] - - [#x37B-#x37D] - - [#x390] - - [#x3AC-#x3CE] - - [#x3D0-#x3D1] - - [#x3D5-#x3D7] - - [#x3D9] - - [#x3DB] - - [#x3DD] - - [#x3DF] - - [#x3E1] - - [#x3E3] - - [#x3E5] - - [#x3E7] - - [#x3E9] - - [#x3EB] - - [#x3ED] - - [#x3EF-#x3F3] - - [#x3F5] - - [#x3F8] - - [#x3FB-#x3FC] - - [#x430-#x45F] - - [#x461] - - [#x463] - - [#x465] - - [#x467] - - [#x469] - - [#x46B] - - [#x46D] - - [#x46F] - - [#x471] - - [#x473] - - [#x475] - - [#x477] - - [#x479] - - [#x47B] - - [#x47D] - - [#x47F] - - [#x481] - - [#x48B] - - [#x48D] - - [#x48F] - - [#x491] - - [#x493] - - [#x495] - - [#x497] - - [#x499] - - [#x49B] - - [#x49D] - - [#x49F] - - [#x4A1] - - [#x4A3] - - [#x4A5] - - [#x4A7] - - [#x4A9] - - [#x4AB] - - [#x4AD] - - [#x4AF] - - [#x4B1] - - [#x4B3] - - [#x4B5] - - [#x4B7] - - [#x4B9] - - [#x4BB] - - [#x4BD] - - [#x4BF] - - [#x4C2] - - [#x4C4] - - [#x4C6] - - [#x4C8] - - [#x4CA] - - [#x4CC] - - [#x4CE-#x4CF] - - [#x4D1] - - [#x4D3] - - [#x4D5] - - [#x4D7] - - [#x4D9] - - [#x4DB] - - [#x4DD] - - [#x4DF] - - [#x4E1] - - [#x4E3] - - [#x4E5] - - [#x4E7] - - [#x4E9] - - [#x4EB] - - [#x4ED] - - [#x4EF] - - [#x4F1] - - [#x4F3] - - [#x4F5] - - [#x4F7] - - [#x4F9] - - [#x4FB] - - [#x4FD] - - [#x4FF] - - [#x501] - - [#x503] - - [#x505] - - [#x507] - - [#x509] - - [#x50B] - - [#x50D] - - [#x50F] - - [#x511] - - [#x513] - - [#x515] - - [#x517] - - [#x519] - - [#x51B] - - [#x51D] - - [#x51F] - - [#x521] - - [#x523] - - [#x525] - - [#x527] - - [#x529] - - [#x52B] - - [#x52D] - - [#x52F] - - [#x560-#x588] - - [#x10D0-#x10FA] - - [#x10FD-#x10FF] - - [#x13F8-#x13FD] - - [#x1C80-#x1C88] - - [#x1D00-#x1D2B] - - [#x1D6B-#x1D77] - - [#x1D79-#x1D9A] - - [#x1E01] - - [#x1E03] - - [#x1E05] - - [#x1E07] - - [#x1E09] - - [#x1E0B] - - [#x1E0D] - - [#x1E0F] - - [#x1E11] - - [#x1E13] - - [#x1E15] - - [#x1E17] - - [#x1E19] - - [#x1E1B] - - [#x1E1D] - - [#x1E1F] - - [#x1E21] - - [#x1E23] - - [#x1E25] - - [#x1E27] - - [#x1E29] - - [#x1E2B] - - [#x1E2D] - - [#x1E2F] - - [#x1E31] - - [#x1E33] - - [#x1E35] - - [#x1E37] - - [#x1E39] - - [#x1E3B] - - [#x1E3D] - - [#x1E3F] - - [#x1E41] - - [#x1E43] - - [#x1E45] - - [#x1E47] - - [#x1E49] - - [#x1E4B] - - [#x1E4D] - - [#x1E4F] - - [#x1E51] - - [#x1E53] - - [#x1E55] - - [#x1E57] - - [#x1E59] - - [#x1E5B] - - [#x1E5D] - - [#x1E5F] - - [#x1E61] - - [#x1E63] - - [#x1E65] - - [#x1E67] - - [#x1E69] - - [#x1E6B] - - [#x1E6D] - - [#x1E6F] - - [#x1E71] - - [#x1E73] - - [#x1E75] - - [#x1E77] - - [#x1E79] - - [#x1E7B] - - [#x1E7D] - - [#x1E7F] - - [#x1E81] - - [#x1E83] - - [#x1E85] - - [#x1E87] - - [#x1E89] - - [#x1E8B] - - [#x1E8D] - - [#x1E8F] - - [#x1E91] - - [#x1E93] - - [#x1E95-#x1E9D] - - [#x1E9F] - - [#x1EA1] - - [#x1EA3] - - [#x1EA5] - - [#x1EA7] - - [#x1EA9] - - [#x1EAB] - - [#x1EAD] - - [#x1EAF] - - [#x1EB1] - - [#x1EB3] - - [#x1EB5] - - [#x1EB7] - - [#x1EB9] - - [#x1EBB] - - [#x1EBD] - - [#x1EBF] - - [#x1EC1] - - [#x1EC3] - - [#x1EC5] - - [#x1EC7] - - [#x1EC9] - - [#x1ECB] - - [#x1ECD] - - [#x1ECF] - - [#x1ED1] - - [#x1ED3] - - [#x1ED5] - - [#x1ED7] - - [#x1ED9] - - [#x1EDB] - - [#x1EDD] - - [#x1EDF] - - [#x1EE1] - - [#x1EE3] - - [#x1EE5] - - [#x1EE7] - - [#x1EE9] - - [#x1EEB] - - [#x1EED] - - [#x1EEF] - - [#x1EF1] - - [#x1EF3] - - [#x1EF5] - - [#x1EF7] - - [#x1EF9] - - [#x1EFB] - - [#x1EFD] - - [#x1EFF-#x1F07] - - [#x1F10-#x1F15] - - [#x1F20-#x1F27] - - [#x1F30-#x1F37] - - [#x1F40-#x1F45] - - [#x1F50-#x1F57] - - [#x1F60-#x1F67] - - [#x1F70-#x1F7D] - - [#x1F80-#x1F87] - - [#x1F90-#x1F97] - - [#x1FA0-#x1FA7] - - [#x1FB0-#x1FB4] - - [#x1FB6-#x1FB7] - - [#x1FBE] - - [#x1FC2-#x1FC4] - - [#x1FC6-#x1FC7] - - [#x1FD0-#x1FD3] - - [#x1FD6-#x1FD7] - - [#x1FE0-#x1FE7] - - [#x1FF2-#x1FF4] - - [#x1FF6-#x1FF7] - - [#x210A] - - [#x210E-#x210F] - - [#x2113] - - [#x212F] - - [#x2134] - - [#x2139] - - [#x213C-#x213D] - - [#x2146-#x2149] - - [#x214E] - - [#x2184] - - [#x2C30-#x2C5F] - - [#x2C61] - - [#x2C65-#x2C66] - - [#x2C68] - - [#x2C6A] - - [#x2C6C] - - [#x2C71] - - [#x2C73-#x2C74] - - [#x2C76-#x2C7B] - - [#x2C81] - - [#x2C83] - - [#x2C85] - - [#x2C87] - - [#x2C89] - - [#x2C8B] - - [#x2C8D] - - [#x2C8F] - - [#x2C91] - - [#x2C93] - - [#x2C95] - - [#x2C97] - - [#x2C99] - - [#x2C9B] - - [#x2C9D] - - [#x2C9F] - - [#x2CA1] - - [#x2CA3] - - [#x2CA5] - - [#x2CA7] - - [#x2CA9] - - [#x2CAB] - - [#x2CAD] - - [#x2CAF] - - [#x2CB1] - - [#x2CB3] - - [#x2CB5] - - [#x2CB7] - - [#x2CB9] - - [#x2CBB] - - [#x2CBD] - - [#x2CBF] - - [#x2CC1] - - [#x2CC3] - - [#x2CC5] - - [#x2CC7] - - [#x2CC9] - - [#x2CCB] - - [#x2CCD] - - [#x2CCF] - - [#x2CD1] - - [#x2CD3] - - [#x2CD5] - - [#x2CD7] - - [#x2CD9] - - [#x2CDB] - - [#x2CDD] - - [#x2CDF] - - [#x2CE1] - - [#x2CE3-#x2CE4] - - [#x2CEC] - - [#x2CEE] - - [#x2CF3] - - [#x2D00-#x2D25] - - [#x2D27] - - [#x2D2D] - - [#xA641] - - [#xA643] - - [#xA645] - - [#xA647] - - [#xA649] - - [#xA64B] - - [#xA64D] - - [#xA64F] - - [#xA651] - - [#xA653] - - [#xA655] - - [#xA657] - - [#xA659] - - [#xA65B] - - [#xA65D] - - [#xA65F] - - [#xA661] - - [#xA663] - - [#xA665] - - [#xA667] - - [#xA669] - - [#xA66B] - - [#xA66D] - - [#xA681] - - [#xA683] - - [#xA685] - - [#xA687] - - [#xA689] - - [#xA68B] - - [#xA68D] - - [#xA68F] - - [#xA691] - - [#xA693] - - [#xA695] - - [#xA697] - - [#xA699] - - [#xA69B] - - [#xA723] - - [#xA725] - - [#xA727] - - [#xA729] - - [#xA72B] - - [#xA72D] - - [#xA72F-#xA731] - - [#xA733] - - [#xA735] - - [#xA737] - - [#xA739] - - [#xA73B] - - [#xA73D] - - [#xA73F] - - [#xA741] - - [#xA743] - - [#xA745] - - [#xA747] - - [#xA749] - - [#xA74B] - - [#xA74D] - - [#xA74F] - - [#xA751] - - [#xA753] - - [#xA755] - - [#xA757] - - [#xA759] - - [#xA75B] - - [#xA75D] - - [#xA75F] - - [#xA761] - - [#xA763] - - [#xA765] - - [#xA767] - - [#xA769] - - [#xA76B] - - [#xA76D] - - [#xA76F] - - [#xA771-#xA778] - - [#xA77A] - - [#xA77C] - - [#xA77F] - - [#xA781] - - [#xA783] - - [#xA785] - - [#xA787] - - [#xA78C] - - [#xA78E] - - [#xA791] - - [#xA793-#xA795] - - [#xA797] - - [#xA799] - - [#xA79B] - - [#xA79D] - - [#xA79F] - - [#xA7A1] - - [#xA7A3] - - [#xA7A5] - - [#xA7A7] - - [#xA7A9] - - [#xA7AF] - - [#xA7B5] - - [#xA7B7] - - [#xA7B9] - - [#xA7BB] - - [#xA7BD] - - [#xA7BF] - - [#xA7C1] - - [#xA7C3] - - [#xA7C8] - - [#xA7CA] - - [#xA7D1] - - [#xA7D3] - - [#xA7D5] - - [#xA7D7] - - [#xA7D9] - - [#xA7F6] - - [#xA7FA] - - [#xAB30-#xAB5A] - - [#xAB60-#xAB68] - - [#xAB70-#xABBF] - - [#xFB00-#xFB06] - - [#xFB13-#xFB17] - - [#xFF41-#xFF5A] - - -
- -
Ll       ::= [a-z#xB5#xDF-#xF6#xF8-#xFF#x101#x103#x105#x107#x109#x10B#x10D#x10F#x111#x113#x115#x117#x119#x11B#x11D#x11F#x121#x123#x125#x127#x129#x12B#x12D#x12F#x131#x133#x135#x137-#x138#x13A#x13C#x13E#x140#x142#x144#x146#x148-#x149#x14B#x14D#x14F#x151#x153#x155#x157#x159#x15B#x15D#x15F#x161#x163#x165#x167#x169#x16B#x16D#x16F#x171#x173#x175#x177#x17A#x17C#x17E-#x180#x183#x185#x188#x18C-#x18D#x192#x195#x199-#x19B#x19E#x1A1#x1A3#x1A5#x1A8#x1AA-#x1AB#x1AD#x1B0#x1B4#x1B6#x1B9-#x1BA#x1BD-#x1BF#x1C6#x1C9#x1CC#x1CE#x1D0#x1D2#x1D4#x1D6#x1D8#x1DA#x1DC-#x1DD#x1DF#x1E1#x1E3#x1E5#x1E7#x1E9#x1EB#x1ED#x1EF-#x1F0#x1F3#x1F5#x1F9#x1FB#x1FD#x1FF#x201#x203#x205#x207#x209#x20B#x20D#x20F#x211#x213#x215#x217#x219#x21B#x21D#x21F#x221#x223#x225#x227#x229#x22B#x22D#x22F#x231#x233-#x239#x23C#x23F-#x240#x242#x247#x249#x24B#x24D#x24F-#x293#x295-#x2AF#x371#x373#x377#x37B-#x37D#x390#x3AC-#x3CE#x3D0-#x3D1#x3D5-#x3D7#x3D9#x3DB#x3DD#x3DF#x3E1#x3E3#x3E5#x3E7#x3E9#x3EB#x3ED#x3EF-#x3F3#x3F5#x3F8#x3FB-#x3FC#x430-#x45F#x461#x463#x465#x467#x469#x46B#x46D#x46F#x471#x473#x475#x477#x479#x47B#x47D#x47F#x481#x48B#x48D#x48F#x491#x493#x495#x497#x499#x49B#x49D#x49F#x4A1#x4A3#x4A5#x4A7#x4A9#x4AB#x4AD#x4AF#x4B1#x4B3#x4B5#x4B7#x4B9#x4BB#x4BD#x4BF#x4C2#x4C4#x4C6#x4C8#x4CA#x4CC#x4CE-#x4CF#x4D1#x4D3#x4D5#x4D7#x4D9#x4DB#x4DD#x4DF#x4E1#x4E3#x4E5#x4E7#x4E9#x4EB#x4ED#x4EF#x4F1#x4F3#x4F5#x4F7#x4F9#x4FB#x4FD#x4FF#x501#x503#x505#x507#x509#x50B#x50D#x50F#x511#x513#x515#x517#x519#x51B#x51D#x51F#x521#x523#x525#x527#x529#x52B#x52D#x52F#x560-#x588#x10D0-#x10FA#x10FD-#x10FF#x13F8-#x13FD#x1C80-#x1C88#x1D00-#x1D2B#x1D6B-#x1D77#x1D79-#x1D9A#x1E01#x1E03#x1E05#x1E07#x1E09#x1E0B#x1E0D#x1E0F#x1E11#x1E13#x1E15#x1E17#x1E19#x1E1B#x1E1D#x1E1F#x1E21#x1E23#x1E25#x1E27#x1E29#x1E2B#x1E2D#x1E2F#x1E31#x1E33#x1E35#x1E37#x1E39#x1E3B#x1E3D#x1E3F#x1E41#x1E43#x1E45#x1E47#x1E49#x1E4B#x1E4D#x1E4F#x1E51#x1E53#x1E55#x1E57#x1E59#x1E5B#x1E5D#x1E5F#x1E61#x1E63#x1E65#x1E67#x1E69#x1E6B#x1E6D#x1E6F#x1E71#x1E73#x1E75#x1E77#x1E79#x1E7B#x1E7D#x1E7F#x1E81#x1E83#x1E85#x1E87#x1E89#x1E8B#x1E8D#x1E8F#x1E91#x1E93#x1E95-#x1E9D#x1E9F#x1EA1#x1EA3#x1EA5#x1EA7#x1EA9#x1EAB#x1EAD#x1EAF#x1EB1#x1EB3#x1EB5#x1EB7#x1EB9#x1EBB#x1EBD#x1EBF#x1EC1#x1EC3#x1EC5#x1EC7#x1EC9#x1ECB#x1ECD#x1ECF#x1ED1#x1ED3#x1ED5#x1ED7#x1ED9#x1EDB#x1EDD#x1EDF#x1EE1#x1EE3#x1EE5#x1EE7#x1EE9#x1EEB#x1EED#x1EEF#x1EF1#x1EF3#x1EF5#x1EF7#x1EF9#x1EFB#x1EFD#x1EFF-#x1F07#x1F10-#x1F15#x1F20-#x1F27#x1F30-#x1F37#x1F40-#x1F45#x1F50-#x1F57#x1F60-#x1F67#x1F70-#x1F7D#x1F80-#x1F87#x1F90-#x1F97#x1FA0-#x1FA7#x1FB0-#x1FB4#x1FB6-#x1FB7#x1FBE#x1FC2-#x1FC4#x1FC6-#x1FC7#x1FD0-#x1FD3#x1FD6-#x1FD7#x1FE0-#x1FE7#x1FF2-#x1FF4#x1FF6-#x1FF7#x210A#x210E-#x210F#x2113#x212F#x2134#x2139#x213C-#x213D#x2146-#x2149#x214E#x2184#x2C30-#x2C5F#x2C61#x2C65-#x2C66#x2C68#x2C6A#x2C6C#x2C71#x2C73-#x2C74#x2C76-#x2C7B#x2C81#x2C83#x2C85#x2C87#x2C89#x2C8B#x2C8D#x2C8F#x2C91#x2C93#x2C95#x2C97#x2C99#x2C9B#x2C9D#x2C9F#x2CA1#x2CA3#x2CA5#x2CA7#x2CA9#x2CAB#x2CAD#x2CAF#x2CB1#x2CB3#x2CB5#x2CB7#x2CB9#x2CBB#x2CBD#x2CBF#x2CC1#x2CC3#x2CC5#x2CC7#x2CC9#x2CCB#x2CCD#x2CCF#x2CD1#x2CD3#x2CD5#x2CD7#x2CD9#x2CDB#x2CDD#x2CDF#x2CE1#x2CE3-#x2CE4#x2CEC#x2CEE#x2CF3#x2D00-#x2D25#x2D27#x2D2D#xA641#xA643#xA645#xA647#xA649#xA64B#xA64D#xA64F#xA651#xA653#xA655#xA657#xA659#xA65B#xA65D#xA65F#xA661#xA663#xA665#xA667#xA669#xA66B#xA66D#xA681#xA683#xA685#xA687#xA689#xA68B#xA68D#xA68F#xA691#xA693#xA695#xA697#xA699#xA69B#xA723#xA725#xA727#xA729#xA72B#xA72D#xA72F-#xA731#xA733#xA735#xA737#xA739#xA73B#xA73D#xA73F#xA741#xA743#xA745#xA747#xA749#xA74B#xA74D#xA74F#xA751#xA753#xA755#xA757#xA759#xA75B#xA75D#xA75F#xA761#xA763#xA765#xA767#xA769#xA76B#xA76D#xA76F#xA771-#xA778#xA77A#xA77C#xA77F#xA781#xA783#xA785#xA787#xA78C#xA78E#xA791#xA793-#xA795#xA797#xA799#xA79B#xA79D#xA79F#xA7A1#xA7A3#xA7A5#xA7A7#xA7A9#xA7AF#xA7B5#xA7B7#xA7B9#xA7BB#xA7BD#xA7BF#xA7C1#xA7C3#xA7C8#xA7CA#xA7D1#xA7D3#xA7D5#xA7D7#xA7D9#xA7F6#xA7FA#xAB30-#xAB5A#xAB60-#xAB68#xAB70-#xABBF#xFB00-#xFB06#xFB13-#xFB17#xFF41-#xFF5A]
-
- Referenced by: -
- - -====================================================================================================================== -Lm -====================================================================================================================== - - -.. raw:: html - - - - - - [#x2B0-#x2C1] - - [#x2C6-#x2D1] - - [#x2E0-#x2E4] - - [#x2EC] - - [#x2EE] - - [#x374] - - [#x37A] - - [#x559] - - [#x640] - - [#x6E5-#x6E6] - - [#x7F4-#x7F5] - - [#x7FA] - - [#x81A] - - [#x824] - - [#x828] - - [#x8C9] - - [#x971] - - [#xE46] - - [#xEC6] - - [#x10FC] - - [#x17D7] - - [#x1843] - - [#x1AA7] - - [#x1C78-#x1C7D] - - [#x1D2C-#x1D6A] - - [#x1D78] - - [#x1D9B-#x1DBF] - - [#x2071] - - [#x207F] - - [#x2090-#x209C] - - [#x2C7C-#x2C7D] - - [#x2D6F] - - [#x2E2F] - - [#x3005] - - [#x3031-#x3035] - - [#x303B] - - [#x309D-#x309E] - - [#x30FC-#x30FE] - - [#xA015] - - [#xA4F8-#xA4FD] - - [#xA60C] - - [#xA67F] - - [#xA69C-#xA69D] - - [#xA717-#xA71F] - - [#xA770] - - [#xA788] - - [#xA7F2-#xA7F4] - - [#xA7F8-#xA7F9] - - [#xA9CF] - - [#xA9E6] - - [#xAA70] - - [#xAADD] - - [#xAAF3-#xAAF4] - - [#xAB5C-#xAB5F] - - [#xAB69] - - [#xFF70] - - [#xFF9E-#xFF9F] - - -
- -
Lm       ::= [#x2B0-#x2C1#x2C6-#x2D1#x2E0-#x2E4#x2EC#x2EE#x374#x37A#x559#x640#x6E5-#x6E6#x7F4-#x7F5#x7FA#x81A#x824#x828#x8C9#x971#xE46#xEC6#x10FC#x17D7#x1843#x1AA7#x1C78-#x1C7D#x1D2C-#x1D6A#x1D78#x1D9B-#x1DBF#x2071#x207F#x2090-#x209C#x2C7C-#x2C7D#x2D6F#x2E2F#x3005#x3031-#x3035#x303B#x309D-#x309E#x30FC-#x30FE#xA015#xA4F8-#xA4FD#xA60C#xA67F#xA69C-#xA69D#xA717-#xA71F#xA770#xA788#xA7F2-#xA7F4#xA7F8-#xA7F9#xA9CF#xA9E6#xAA70#xAADD#xAAF3-#xAAF4#xAB5C-#xAB5F#xAB69#xFF70#xFF9E-#xFF9F]
-
- Referenced by: -
- - -====================================================================================================================== -Lo -====================================================================================================================== - - -.. raw:: html - - - - - - [#xAA] - - [#xBA] - - [#x1BB] - - [#x1C0-#x1C3] - - [#x294] - - [#x5D0-#x5EA] - - [#x5EF-#x5F2] - - [#x620-#x63F] - - [#x641-#x64A] - - [#x66E-#x66F] - - [#x671-#x6D3] - - [#x6D5] - - [#x6EE-#x6EF] - - [#x6FA-#x6FC] - - [#x6FF] - - [#x710] - - [#x712-#x72F] - - [#x74D-#x7A5] - - [#x7B1] - - [#x7CA-#x7EA] - - [#x800-#x815] - - [#x840-#x858] - - [#x860-#x86A] - - [#x870-#x887] - - [#x889-#x88E] - - [#x8A0-#x8C8] - - [#x904-#x939] - - [#x93D] - - [#x950] - - [#x958-#x961] - - [#x972-#x980] - - [#x985-#x98C] - - [#x98F-#x990] - - [#x993-#x9A8] - - [#x9AA-#x9B0] - - [#x9B2] - - [#x9B6-#x9B9] - - [#x9BD] - - [#x9CE] - - [#x9DC-#x9DD] - - [#x9DF-#x9E1] - - [#x9F0-#x9F1] - - [#x9FC] - - [#xA05-#xA0A] - - [#xA0F-#xA10] - - [#xA13-#xA28] - - [#xA2A-#xA30] - - [#xA32-#xA33] - - [#xA35-#xA36] - - [#xA38-#xA39] - - [#xA59-#xA5C] - - [#xA5E] - - [#xA72-#xA74] - - [#xA85-#xA8D] - - [#xA8F-#xA91] - - [#xA93-#xAA8] - - [#xAAA-#xAB0] - - [#xAB2-#xAB3] - - [#xAB5-#xAB9] - - [#xABD] - - [#xAD0] - - [#xAE0-#xAE1] - - [#xAF9] - - [#xB05-#xB0C] - - [#xB0F-#xB10] - - [#xB13-#xB28] - - [#xB2A-#xB30] - - [#xB32-#xB33] - - [#xB35-#xB39] - - [#xB3D] - - [#xB5C-#xB5D] - - [#xB5F-#xB61] - - [#xB71] - - [#xB83] - - [#xB85-#xB8A] - - [#xB8E-#xB90] - - [#xB92-#xB95] - - [#xB99-#xB9A] - - [#xB9C] - - [#xB9E-#xB9F] - - [#xBA3-#xBA4] - - [#xBA8-#xBAA] - - [#xBAE-#xBB9] - - [#xBD0] - - [#xC05-#xC0C] - - [#xC0E-#xC10] - - [#xC12-#xC28] - - [#xC2A-#xC39] - - [#xC3D] - - [#xC58-#xC5A] - - [#xC5D] - - [#xC60-#xC61] - - [#xC80] - - [#xC85-#xC8C] - - [#xC8E-#xC90] - - [#xC92-#xCA8] - - [#xCAA-#xCB3] - - [#xCB5-#xCB9] - - [#xCBD] - - [#xCDD-#xCDE] - - [#xCE0-#xCE1] - - [#xCF1-#xCF2] - - [#xD04-#xD0C] - - [#xD0E-#xD10] - - [#xD12-#xD3A] - - [#xD3D] - - [#xD4E] - - [#xD54-#xD56] - - [#xD5F-#xD61] - - [#xD7A-#xD7F] - - [#xD85-#xD96] - - [#xD9A-#xDB1] - - [#xDB3-#xDBB] - - [#xDBD] - - [#xDC0-#xDC6] - - [#xE01-#xE30] - - [#xE32-#xE33] - - [#xE40-#xE45] - - [#xE81-#xE82] - - [#xE84] - - [#xE86-#xE8A] - - [#xE8C-#xEA3] - - [#xEA5] - - [#xEA7-#xEB0] - - [#xEB2-#xEB3] - - [#xEBD] - - [#xEC0-#xEC4] - - [#xEDC-#xEDF] - - [#xF00] - - [#xF40-#xF47] - - [#xF49-#xF6C] - - [#xF88-#xF8C] - - [#x1000-#x102A] - - [#x103F] - - [#x1050-#x1055] - - [#x105A-#x105D] - - [#x1061] - - [#x1065-#x1066] - - [#x106E-#x1070] - - [#x1075-#x1081] - - [#x108E] - - [#x1100-#x1248] - - [#x124A-#x124D] - - [#x1250-#x1256] - - [#x1258] - - [#x125A-#x125D] - - [#x1260-#x1288] - - [#x128A-#x128D] - - [#x1290-#x12B0] - - [#x12B2-#x12B5] - - [#x12B8-#x12BE] - - [#x12C0] - - [#x12C2-#x12C5] - - [#x12C8-#x12D6] - - [#x12D8-#x1310] - - [#x1312-#x1315] - - [#x1318-#x135A] - - [#x1380-#x138F] - - [#x1401-#x166C] - - [#x166F-#x167F] - - [#x1681-#x169A] - - [#x16A0-#x16EA] - - [#x16F1-#x16F8] - - [#x1700-#x1711] - - [#x171F-#x1731] - - [#x1740-#x1751] - - [#x1760-#x176C] - - [#x176E-#x1770] - - [#x1780-#x17B3] - - [#x17DC] - - [#x1820-#x1842] - - [#x1844-#x1878] - - [#x1880-#x1884] - - [#x1887-#x18A8] - - [#x18AA] - - [#x18B0-#x18F5] - - [#x1900-#x191E] - - [#x1950-#x196D] - - [#x1970-#x1974] - - [#x1980-#x19AB] - - [#x19B0-#x19C9] - - [#x1A00-#x1A16] - - [#x1A20-#x1A54] - - [#x1B05-#x1B33] - - [#x1B45-#x1B4C] - - [#x1B83-#x1BA0] - - [#x1BAE-#x1BAF] - - [#x1BBA-#x1BE5] - - [#x1C00-#x1C23] - - [#x1C4D-#x1C4F] - - [#x1C5A-#x1C77] - - [#x1CE9-#x1CEC] - - [#x1CEE-#x1CF3] - - [#x1CF5-#x1CF6] - - [#x1CFA] - - [#x2135-#x2138] - - [#x2D30-#x2D67] - - [#x2D80-#x2D96] - - [#x2DA0-#x2DA6] - - [#x2DA8-#x2DAE] - - [#x2DB0-#x2DB6] - - [#x2DB8-#x2DBE] - - [#x2DC0-#x2DC6] - - [#x2DC8-#x2DCE] - - [#x2DD0-#x2DD6] - - [#x2DD8-#x2DDE] - - [#x3006] - - [#x303C] - - [#x3041-#x3096] - - [#x309F] - - [#x30A1-#x30FA] - - [#x30FF] - - [#x3105-#x312F] - - [#x3131-#x318E] - - [#x31A0-#x31BF] - - [#x31F0-#x31FF] - - [#x4DBF] - - [#x9FFF-#xA014] - - [#xA016-#xA48C] - - [#xA4D0-#xA4F7] - - [#xA500-#xA60B] - - [#xA610-#xA61F] - - [#xA62A-#xA62B] - - [#xA66E] - - [#xA6A0-#xA6E5] - - [#xA78F] - - [#xA7F7] - - [#xA7FB-#xA801] - - [#xA803-#xA805] - - [#xA807-#xA80A] - - [#xA80C-#xA822] - - [#xA840-#xA873] - - [#xA882-#xA8B3] - - [#xA8F2-#xA8F7] - - [#xA8FB] - - [#xA8FD-#xA8FE] - - [#xA90A-#xA925] - - [#xA930-#xA946] - - [#xA960-#xA97C] - - [#xA984-#xA9B2] - - [#xA9E0-#xA9E4] - - [#xA9E7-#xA9EF] - - [#xA9FA-#xA9FE] - - [#xAA00-#xAA28] - - [#xAA40-#xAA42] - - [#xAA44-#xAA4B] - - [#xAA60-#xAA6F] - - [#xAA71-#xAA76] - - [#xAA7A] - - [#xAA7E-#xAAAF] - - [#xAAB1] - - [#xAAB5-#xAAB6] - - [#xAAB9-#xAABD] - - [#xAAC0] - - [#xAAC2] - - [#xAADB-#xAADC] - - [#xAAE0-#xAAEA] - - [#xAAF2] - - [#xAB01-#xAB06] - - [#xAB09-#xAB0E] - - [#xAB11-#xAB16] - - [#xAB20-#xAB26] - - [#xAB28-#xAB2E] - - [#xABC0-#xABE2] - - [#xD7A3] - - [#xD7B0-#xD7C6] - - [#xD7CB-#xD7FB] - - [#xF900-#xFA6D] - - [#xFA70-#xFAD9] - - [#xFB1D] - - [#xFB1F-#xFB28] - - [#xFB2A-#xFB36] - - [#xFB38-#xFB3C] - - [#xFB3E] - - [#xFB40-#xFB41] - - [#xFB43-#xFB44] - - [#xFB46-#xFBB1] - - [#xFBD3-#xFD3D] - - [#xFD50-#xFD8F] - - [#xFD92-#xFDC7] - - [#xFDF0-#xFDFB] - - [#xFE70-#xFE74] - - [#xFE76-#xFEFC] - - [#xFF66-#xFF6F] - - [#xFF71-#xFF9D] - - [#xFFA0-#xFFBE] - - [#xFFC2-#xFFC7] - - [#xFFCA-#xFFCF] - - [#xFFD2-#xFFD7] - - [#xFFDA-#xFFDC] - - -
- -
Lo       ::= [#xAA#xBA#x1BB#x1C0-#x1C3#x294#x5D0-#x5EA#x5EF-#x5F2#x620-#x63F#x641-#x64A#x66E-#x66F#x671-#x6D3#x6D5#x6EE-#x6EF#x6FA-#x6FC#x6FF#x710#x712-#x72F#x74D-#x7A5#x7B1#x7CA-#x7EA#x800-#x815#x840-#x858#x860-#x86A#x870-#x887#x889-#x88E#x8A0-#x8C8#x904-#x939#x93D#x950#x958-#x961#x972-#x980#x985-#x98C#x98F-#x990#x993-#x9A8#x9AA-#x9B0#x9B2#x9B6-#x9B9#x9BD#x9CE#x9DC-#x9DD#x9DF-#x9E1#x9F0-#x9F1#x9FC#xA05-#xA0A#xA0F-#xA10#xA13-#xA28#xA2A-#xA30#xA32-#xA33#xA35-#xA36#xA38-#xA39#xA59-#xA5C#xA5E#xA72-#xA74#xA85-#xA8D#xA8F-#xA91#xA93-#xAA8#xAAA-#xAB0#xAB2-#xAB3#xAB5-#xAB9#xABD#xAD0#xAE0-#xAE1#xAF9#xB05-#xB0C#xB0F-#xB10#xB13-#xB28#xB2A-#xB30#xB32-#xB33#xB35-#xB39#xB3D#xB5C-#xB5D#xB5F-#xB61#xB71#xB83#xB85-#xB8A#xB8E-#xB90#xB92-#xB95#xB99-#xB9A#xB9C#xB9E-#xB9F#xBA3-#xBA4#xBA8-#xBAA#xBAE-#xBB9#xBD0#xC05-#xC0C#xC0E-#xC10#xC12-#xC28#xC2A-#xC39#xC3D#xC58-#xC5A#xC5D#xC60-#xC61#xC80#xC85-#xC8C#xC8E-#xC90#xC92-#xCA8#xCAA-#xCB3#xCB5-#xCB9#xCBD#xCDD-#xCDE#xCE0-#xCE1#xCF1-#xCF2#xD04-#xD0C#xD0E-#xD10#xD12-#xD3A#xD3D#xD4E#xD54-#xD56#xD5F-#xD61#xD7A-#xD7F#xD85-#xD96#xD9A-#xDB1#xDB3-#xDBB#xDBD#xDC0-#xDC6#xE01-#xE30#xE32-#xE33#xE40-#xE45#xE81-#xE82#xE84#xE86-#xE8A#xE8C-#xEA3#xEA5#xEA7-#xEB0#xEB2-#xEB3#xEBD#xEC0-#xEC4#xEDC-#xEDF#xF00#xF40-#xF47#xF49-#xF6C#xF88-#xF8C#x1000-#x102A#x103F#x1050-#x1055#x105A-#x105D#x1061#x1065-#x1066#x106E-#x1070#x1075-#x1081#x108E#x1100-#x1248#x124A-#x124D#x1250-#x1256#x1258#x125A-#x125D#x1260-#x1288#x128A-#x128D#x1290-#x12B0#x12B2-#x12B5#x12B8-#x12BE#x12C0#x12C2-#x12C5#x12C8-#x12D6#x12D8-#x1310#x1312-#x1315#x1318-#x135A#x1380-#x138F#x1401-#x166C#x166F-#x167F#x1681-#x169A#x16A0-#x16EA#x16F1-#x16F8#x1700-#x1711#x171F-#x1731#x1740-#x1751#x1760-#x176C#x176E-#x1770#x1780-#x17B3#x17DC#x1820-#x1842#x1844-#x1878#x1880-#x1884#x1887-#x18A8#x18AA#x18B0-#x18F5#x1900-#x191E#x1950-#x196D#x1970-#x1974#x1980-#x19AB#x19B0-#x19C9#x1A00-#x1A16#x1A20-#x1A54#x1B05-#x1B33#x1B45-#x1B4C#x1B83-#x1BA0#x1BAE-#x1BAF#x1BBA-#x1BE5#x1C00-#x1C23#x1C4D-#x1C4F#x1C5A-#x1C77#x1CE9-#x1CEC#x1CEE-#x1CF3#x1CF5-#x1CF6#x1CFA#x2135-#x2138#x2D30-#x2D67#x2D80-#x2D96#x2DA0-#x2DA6#x2DA8-#x2DAE#x2DB0-#x2DB6#x2DB8-#x2DBE#x2DC0-#x2DC6#x2DC8-#x2DCE#x2DD0-#x2DD6#x2DD8-#x2DDE#x3006#x303C#x3041-#x3096#x309F#x30A1-#x30FA#x30FF#x3105-#x312F#x3131-#x318E#x31A0-#x31BF#x31F0-#x31FF#x4DBF#x9FFF-#xA014#xA016-#xA48C#xA4D0-#xA4F7#xA500-#xA60B#xA610-#xA61F#xA62A-#xA62B#xA66E#xA6A0-#xA6E5#xA78F#xA7F7#xA7FB-#xA801#xA803-#xA805#xA807-#xA80A#xA80C-#xA822#xA840-#xA873#xA882-#xA8B3#xA8F2-#xA8F7#xA8FB#xA8FD-#xA8FE#xA90A-#xA925#xA930-#xA946#xA960-#xA97C#xA984-#xA9B2#xA9E0-#xA9E4#xA9E7-#xA9EF#xA9FA-#xA9FE#xAA00-#xAA28#xAA40-#xAA42#xAA44-#xAA4B#xAA60-#xAA6F#xAA71-#xAA76#xAA7A#xAA7E-#xAAAF#xAAB1#xAAB5-#xAAB6#xAAB9-#xAABD#xAAC0#xAAC2#xAADB-#xAADC#xAAE0-#xAAEA#xAAF2#xAB01-#xAB06#xAB09-#xAB0E#xAB11-#xAB16#xAB20-#xAB26#xAB28-#xAB2E#xABC0-#xABE2#xD7A3#xD7B0-#xD7C6#xD7CB-#xD7FB#xF900-#xFA6D#xFA70-#xFAD9#xFB1D#xFB1F-#xFB28#xFB2A-#xFB36#xFB38-#xFB3C#xFB3E#xFB40-#xFB41#xFB43-#xFB44#xFB46-#xFBB1#xFBD3-#xFD3D#xFD50-#xFD8F#xFD92-#xFDC7#xFDF0-#xFDFB#xFE70-#xFE74#xFE76-#xFEFC#xFF66-#xFF6F#xFF71-#xFF9D#xFFA0-#xFFBE#xFFC2-#xFFC7#xFFCA-#xFFCF#xFFD2-#xFFD7#xFFDA-#xFFDC]
-
- Referenced by: -
- - -====================================================================================================================== -Lt -====================================================================================================================== - - -.. raw:: html - - - - - - [#x1C5] - - [#x1C8] - - [#x1CB] - - [#x1F2] - - [#x1F88-#x1F8F] - - [#x1F98-#x1F9F] - - [#x1FA8-#x1FAF] - - [#x1FBC] - - [#x1FCC] - - [#x1FFC] - - -
- -
Lt       ::= [#x1C5#x1C8#x1CB#x1F2#x1F88-#x1F8F#x1F98-#x1F9F#x1FA8-#x1FAF#x1FBC#x1FCC#x1FFC]
-
- Referenced by: -
- - -====================================================================================================================== -Lu -====================================================================================================================== - - -.. raw:: html - - - - - - [A-Z] - - [#xC0-#xD6] - - [#xD8-#xDE] - - [#x100] - - [#x102] - - [#x104] - - [#x106] - - [#x108] - - [#x10A] - - [#x10C] - - [#x10E] - - [#x110] - - [#x112] - - [#x114] - - [#x116] - - [#x118] - - [#x11A] - - [#x11C] - - [#x11E] - - [#x120] - - [#x122] - - [#x124] - - [#x126] - - [#x128] - - [#x12A] - - [#x12C] - - [#x12E] - - [#x130] - - [#x132] - - [#x134] - - [#x136] - - [#x139] - - [#x13B] - - [#x13D] - - [#x13F] - - [#x141] - - [#x143] - - [#x145] - - [#x147] - - [#x14A] - - [#x14C] - - [#x14E] - - [#x150] - - [#x152] - - [#x154] - - [#x156] - - [#x158] - - [#x15A] - - [#x15C] - - [#x15E] - - [#x160] - - [#x162] - - [#x164] - - [#x166] - - [#x168] - - [#x16A] - - [#x16C] - - [#x16E] - - [#x170] - - [#x172] - - [#x174] - - [#x176] - - [#x178-#x179] - - [#x17B] - - [#x17D] - - [#x181-#x182] - - [#x184] - - [#x186-#x187] - - [#x189-#x18B] - - [#x18E-#x191] - - [#x193-#x194] - - [#x196-#x198] - - [#x19C-#x19D] - - [#x19F-#x1A0] - - [#x1A2] - - [#x1A4] - - [#x1A6-#x1A7] - - [#x1A9] - - [#x1AC] - - [#x1AE-#x1AF] - - [#x1B1-#x1B3] - - [#x1B5] - - [#x1B7-#x1B8] - - [#x1BC] - - [#x1C4] - - [#x1C7] - - [#x1CA] - - [#x1CD] - - [#x1CF] - - [#x1D1] - - [#x1D3] - - [#x1D5] - - [#x1D7] - - [#x1D9] - - [#x1DB] - - [#x1DE] - - [#x1E0] - - [#x1E2] - - [#x1E4] - - [#x1E6] - - [#x1E8] - - [#x1EA] - - [#x1EC] - - [#x1EE] - - [#x1F1] - - [#x1F4] - - [#x1F6-#x1F8] - - [#x1FA] - - [#x1FC] - - [#x1FE] - - [#x200] - - [#x202] - - [#x204] - - [#x206] - - [#x208] - - [#x20A] - - [#x20C] - - [#x20E] - - [#x210] - - [#x212] - - [#x214] - - [#x216] - - [#x218] - - [#x21A] - - [#x21C] - - [#x21E] - - [#x220] - - [#x222] - - [#x224] - - [#x226] - - [#x228] - - [#x22A] - - [#x22C] - - [#x22E] - - [#x230] - - [#x232] - - [#x23A-#x23B] - - [#x23D-#x23E] - - [#x241] - - [#x243-#x246] - - [#x248] - - [#x24A] - - [#x24C] - - [#x24E] - - [#x370] - - [#x372] - - [#x376] - - [#x37F] - - [#x386] - - [#x388-#x38A] - - [#x38C] - - [#x38E-#x38F] - - [#x391-#x3A1] - - [#x3A3-#x3AB] - - [#x3CF] - - [#x3D2-#x3D4] - - [#x3D8] - - [#x3DA] - - [#x3DC] - - [#x3DE] - - [#x3E0] - - [#x3E2] - - [#x3E4] - - [#x3E6] - - [#x3E8] - - [#x3EA] - - [#x3EC] - - [#x3EE] - - [#x3F4] - - [#x3F7] - - [#x3F9-#x3FA] - - [#x3FD-#x42F] - - [#x460] - - [#x462] - - [#x464] - - [#x466] - - [#x468] - - [#x46A] - - [#x46C] - - [#x46E] - - [#x470] - - [#x472] - - [#x474] - - [#x476] - - [#x478] - - [#x47A] - - [#x47C] - - [#x47E] - - [#x480] - - [#x48A] - - [#x48C] - - [#x48E] - - [#x490] - - [#x492] - - [#x494] - - [#x496] - - [#x498] - - [#x49A] - - [#x49C] - - [#x49E] - - [#x4A0] - - [#x4A2] - - [#x4A4] - - [#x4A6] - - [#x4A8] - - [#x4AA] - - [#x4AC] - - [#x4AE] - - [#x4B0] - - [#x4B2] - - [#x4B4] - - [#x4B6] - - [#x4B8] - - [#x4BA] - - [#x4BC] - - [#x4BE] - - [#x4C0-#x4C1] - - [#x4C3] - - [#x4C5] - - [#x4C7] - - [#x4C9] - - [#x4CB] - - [#x4CD] - - [#x4D0] - - [#x4D2] - - [#x4D4] - - [#x4D6] - - [#x4D8] - - [#x4DA] - - [#x4DC] - - [#x4DE] - - [#x4E0] - - [#x4E2] - - [#x4E4] - - [#x4E6] - - [#x4E8] - - [#x4EA] - - [#x4EC] - - [#x4EE] - - [#x4F0] - - [#x4F2] - - [#x4F4] - - [#x4F6] - - [#x4F8] - - [#x4FA] - - [#x4FC] - - [#x4FE] - - [#x500] - - [#x502] - - [#x504] - - [#x506] - - [#x508] - - [#x50A] - - [#x50C] - - [#x50E] - - [#x510] - - [#x512] - - [#x514] - - [#x516] - - [#x518] - - [#x51A] - - [#x51C] - - [#x51E] - - [#x520] - - [#x522] - - [#x524] - - [#x526] - - [#x528] - - [#x52A] - - [#x52C] - - [#x52E] - - [#x531-#x556] - - [#x10A0-#x10C5] - - [#x10C7] - - [#x10CD] - - [#x13A0-#x13F5] - - [#x1C90-#x1CBA] - - [#x1CBD-#x1CBF] - - [#x1E00] - - [#x1E02] - - [#x1E04] - - [#x1E06] - - [#x1E08] - - [#x1E0A] - - [#x1E0C] - - [#x1E0E] - - [#x1E10] - - [#x1E12] - - [#x1E14] - - [#x1E16] - - [#x1E18] - - [#x1E1A] - - [#x1E1C] - - [#x1E1E] - - [#x1E20] - - [#x1E22] - - [#x1E24] - - [#x1E26] - - [#x1E28] - - [#x1E2A] - - [#x1E2C] - - [#x1E2E] - - [#x1E30] - - [#x1E32] - - [#x1E34] - - [#x1E36] - - [#x1E38] - - [#x1E3A] - - [#x1E3C] - - [#x1E3E] - - [#x1E40] - - [#x1E42] - - [#x1E44] - - [#x1E46] - - [#x1E48] - - [#x1E4A] - - [#x1E4C] - - [#x1E4E] - - [#x1E50] - - [#x1E52] - - [#x1E54] - - [#x1E56] - - [#x1E58] - - [#x1E5A] - - [#x1E5C] - - [#x1E5E] - - [#x1E60] - - [#x1E62] - - [#x1E64] - - [#x1E66] - - [#x1E68] - - [#x1E6A] - - [#x1E6C] - - [#x1E6E] - - [#x1E70] - - [#x1E72] - - [#x1E74] - - [#x1E76] - - [#x1E78] - - [#x1E7A] - - [#x1E7C] - - [#x1E7E] - - [#x1E80] - - [#x1E82] - - [#x1E84] - - [#x1E86] - - [#x1E88] - - [#x1E8A] - - [#x1E8C] - - [#x1E8E] - - [#x1E90] - - [#x1E92] - - [#x1E94] - - [#x1E9E] - - [#x1EA0] - - [#x1EA2] - - [#x1EA4] - - [#x1EA6] - - [#x1EA8] - - [#x1EAA] - - [#x1EAC] - - [#x1EAE] - - [#x1EB0] - - [#x1EB2] - - [#x1EB4] - - [#x1EB6] - - [#x1EB8] - - [#x1EBA] - - [#x1EBC] - - [#x1EBE] - - [#x1EC0] - - [#x1EC2] - - [#x1EC4] - - [#x1EC6] - - [#x1EC8] - - [#x1ECA] - - [#x1ECC] - - [#x1ECE] - - [#x1ED0] - - [#x1ED2] - - [#x1ED4] - - [#x1ED6] - - [#x1ED8] - - [#x1EDA] - - [#x1EDC] - - [#x1EDE] - - [#x1EE0] - - [#x1EE2] - - [#x1EE4] - - [#x1EE6] - - [#x1EE8] - - [#x1EEA] - - [#x1EEC] - - [#x1EEE] - - [#x1EF0] - - [#x1EF2] - - [#x1EF4] - - [#x1EF6] - - [#x1EF8] - - [#x1EFA] - - [#x1EFC] - - [#x1EFE] - - [#x1F08-#x1F0F] - - [#x1F18-#x1F1D] - - [#x1F28-#x1F2F] - - [#x1F38-#x1F3F] - - [#x1F48-#x1F4D] - - [#x1F59] - - [#x1F5B] - - [#x1F5D] - - [#x1F5F] - - [#x1F68-#x1F6F] - - [#x1FB8-#x1FBB] - - [#x1FC8-#x1FCB] - - [#x1FD8-#x1FDB] - - [#x1FE8-#x1FEC] - - [#x1FF8-#x1FFB] - - [#x2102] - - [#x2107] - - [#x210B-#x210D] - - [#x2110-#x2112] - - [#x2115] - - [#x2119-#x211D] - - [#x2124] - - [#x2126] - - [#x2128] - - [#x212A-#x212D] - - [#x2130-#x2133] - - [#x213E-#x213F] - - [#x2145] - - [#x2183] - - [#x2C00-#x2C2F] - - [#x2C60] - - [#x2C62-#x2C64] - - [#x2C67] - - [#x2C69] - - [#x2C6B] - - [#x2C6D-#x2C70] - - [#x2C72] - - [#x2C75] - - [#x2C7E-#x2C80] - - [#x2C82] - - [#x2C84] - - [#x2C86] - - [#x2C88] - - [#x2C8A] - - [#x2C8C] - - [#x2C8E] - - [#x2C90] - - [#x2C92] - - [#x2C94] - - [#x2C96] - - [#x2C98] - - [#x2C9A] - - [#x2C9C] - - [#x2C9E] - - [#x2CA0] - - [#x2CA2] - - [#x2CA4] - - [#x2CA6] - - [#x2CA8] - - [#x2CAA] - - [#x2CAC] - - [#x2CAE] - - [#x2CB0] - - [#x2CB2] - - [#x2CB4] - - [#x2CB6] - - [#x2CB8] - - [#x2CBA] - - [#x2CBC] - - [#x2CBE] - - [#x2CC0] - - [#x2CC2] - - [#x2CC4] - - [#x2CC6] - - [#x2CC8] - - [#x2CCA] - - [#x2CCC] - - [#x2CCE] - - [#x2CD0] - - [#x2CD2] - - [#x2CD4] - - [#x2CD6] - - [#x2CD8] - - [#x2CDA] - - [#x2CDC] - - [#x2CDE] - - [#x2CE0] - - [#x2CE2] - - [#x2CEB] - - [#x2CED] - - [#x2CF2] - - [#xA640] - - [#xA642] - - [#xA644] - - [#xA646] - - [#xA648] - - [#xA64A] - - [#xA64C] - - [#xA64E] - - [#xA650] - - [#xA652] - - [#xA654] - - [#xA656] - - [#xA658] - - [#xA65A] - - [#xA65C] - - [#xA65E] - - [#xA660] - - [#xA662] - - [#xA664] - - [#xA666] - - [#xA668] - - [#xA66A] - - [#xA66C] - - [#xA680] - - [#xA682] - - [#xA684] - - [#xA686] - - [#xA688] - - [#xA68A] - - [#xA68C] - - [#xA68E] - - [#xA690] - - [#xA692] - - [#xA694] - - [#xA696] - - [#xA698] - - [#xA69A] - - [#xA722] - - [#xA724] - - [#xA726] - - [#xA728] - - [#xA72A] - - [#xA72C] - - [#xA72E] - - [#xA732] - - [#xA734] - - [#xA736] - - [#xA738] - - [#xA73A] - - [#xA73C] - - [#xA73E] - - [#xA740] - - [#xA742] - - [#xA744] - - [#xA746] - - [#xA748] - - [#xA74A] - - [#xA74C] - - [#xA74E] - - [#xA750] - - [#xA752] - - [#xA754] - - [#xA756] - - [#xA758] - - [#xA75A] - - [#xA75C] - - [#xA75E] - - [#xA760] - - [#xA762] - - [#xA764] - - [#xA766] - - [#xA768] - - [#xA76A] - - [#xA76C] - - [#xA76E] - - [#xA779] - - [#xA77B] - - [#xA77D-#xA77E] - - [#xA780] - - [#xA782] - - [#xA784] - - [#xA786] - - [#xA78B] - - [#xA78D] - - [#xA790] - - [#xA792] - - [#xA796] - - [#xA798] - - [#xA79A] - - [#xA79C] - - [#xA79E] - - [#xA7A0] - - [#xA7A2] - - [#xA7A4] - - [#xA7A6] - - [#xA7A8] - - [#xA7AA-#xA7AE] - - [#xA7B0-#xA7B4] - - [#xA7B6] - - [#xA7B8] - - [#xA7BA] - - [#xA7BC] - - [#xA7BE] - - [#xA7C0] - - [#xA7C2] - - [#xA7C4-#xA7C7] - - [#xA7C9] - - [#xA7D0] - - [#xA7D6] - - [#xA7D8] - - [#xA7F5] - - [#xFF21-#xFF3A] - - -
- -
Lu       ::= [A-Z#xC0-#xD6#xD8-#xDE#x100#x102#x104#x106#x108#x10A#x10C#x10E#x110#x112#x114#x116#x118#x11A#x11C#x11E#x120#x122#x124#x126#x128#x12A#x12C#x12E#x130#x132#x134#x136#x139#x13B#x13D#x13F#x141#x143#x145#x147#x14A#x14C#x14E#x150#x152#x154#x156#x158#x15A#x15C#x15E#x160#x162#x164#x166#x168#x16A#x16C#x16E#x170#x172#x174#x176#x178-#x179#x17B#x17D#x181-#x182#x184#x186-#x187#x189-#x18B#x18E-#x191#x193-#x194#x196-#x198#x19C-#x19D#x19F-#x1A0#x1A2#x1A4#x1A6-#x1A7#x1A9#x1AC#x1AE-#x1AF#x1B1-#x1B3#x1B5#x1B7-#x1B8#x1BC#x1C4#x1C7#x1CA#x1CD#x1CF#x1D1#x1D3#x1D5#x1D7#x1D9#x1DB#x1DE#x1E0#x1E2#x1E4#x1E6#x1E8#x1EA#x1EC#x1EE#x1F1#x1F4#x1F6-#x1F8#x1FA#x1FC#x1FE#x200#x202#x204#x206#x208#x20A#x20C#x20E#x210#x212#x214#x216#x218#x21A#x21C#x21E#x220#x222#x224#x226#x228#x22A#x22C#x22E#x230#x232#x23A-#x23B#x23D-#x23E#x241#x243-#x246#x248#x24A#x24C#x24E#x370#x372#x376#x37F#x386#x388-#x38A#x38C#x38E-#x38F#x391-#x3A1#x3A3-#x3AB#x3CF#x3D2-#x3D4#x3D8#x3DA#x3DC#x3DE#x3E0#x3E2#x3E4#x3E6#x3E8#x3EA#x3EC#x3EE#x3F4#x3F7#x3F9-#x3FA#x3FD-#x42F#x460#x462#x464#x466#x468#x46A#x46C#x46E#x470#x472#x474#x476#x478#x47A#x47C#x47E#x480#x48A#x48C#x48E#x490#x492#x494#x496#x498#x49A#x49C#x49E#x4A0#x4A2#x4A4#x4A6#x4A8#x4AA#x4AC#x4AE#x4B0#x4B2#x4B4#x4B6#x4B8#x4BA#x4BC#x4BE#x4C0-#x4C1#x4C3#x4C5#x4C7#x4C9#x4CB#x4CD#x4D0#x4D2#x4D4#x4D6#x4D8#x4DA#x4DC#x4DE#x4E0#x4E2#x4E4#x4E6#x4E8#x4EA#x4EC#x4EE#x4F0#x4F2#x4F4#x4F6#x4F8#x4FA#x4FC#x4FE#x500#x502#x504#x506#x508#x50A#x50C#x50E#x510#x512#x514#x516#x518#x51A#x51C#x51E#x520#x522#x524#x526#x528#x52A#x52C#x52E#x531-#x556#x10A0-#x10C5#x10C7#x10CD#x13A0-#x13F5#x1C90-#x1CBA#x1CBD-#x1CBF#x1E00#x1E02#x1E04#x1E06#x1E08#x1E0A#x1E0C#x1E0E#x1E10#x1E12#x1E14#x1E16#x1E18#x1E1A#x1E1C#x1E1E#x1E20#x1E22#x1E24#x1E26#x1E28#x1E2A#x1E2C#x1E2E#x1E30#x1E32#x1E34#x1E36#x1E38#x1E3A#x1E3C#x1E3E#x1E40#x1E42#x1E44#x1E46#x1E48#x1E4A#x1E4C#x1E4E#x1E50#x1E52#x1E54#x1E56#x1E58#x1E5A#x1E5C#x1E5E#x1E60#x1E62#x1E64#x1E66#x1E68#x1E6A#x1E6C#x1E6E#x1E70#x1E72#x1E74#x1E76#x1E78#x1E7A#x1E7C#x1E7E#x1E80#x1E82#x1E84#x1E86#x1E88#x1E8A#x1E8C#x1E8E#x1E90#x1E92#x1E94#x1E9E#x1EA0#x1EA2#x1EA4#x1EA6#x1EA8#x1EAA#x1EAC#x1EAE#x1EB0#x1EB2#x1EB4#x1EB6#x1EB8#x1EBA#x1EBC#x1EBE#x1EC0#x1EC2#x1EC4#x1EC6#x1EC8#x1ECA#x1ECC#x1ECE#x1ED0#x1ED2#x1ED4#x1ED6#x1ED8#x1EDA#x1EDC#x1EDE#x1EE0#x1EE2#x1EE4#x1EE6#x1EE8#x1EEA#x1EEC#x1EEE#x1EF0#x1EF2#x1EF4#x1EF6#x1EF8#x1EFA#x1EFC#x1EFE#x1F08-#x1F0F#x1F18-#x1F1D#x1F28-#x1F2F#x1F38-#x1F3F#x1F48-#x1F4D#x1F59#x1F5B#x1F5D#x1F5F#x1F68-#x1F6F#x1FB8-#x1FBB#x1FC8-#x1FCB#x1FD8-#x1FDB#x1FE8-#x1FEC#x1FF8-#x1FFB#x2102#x2107#x210B-#x210D#x2110-#x2112#x2115#x2119-#x211D#x2124#x2126#x2128#x212A-#x212D#x2130-#x2133#x213E-#x213F#x2145#x2183#x2C00-#x2C2F#x2C60#x2C62-#x2C64#x2C67#x2C69#x2C6B#x2C6D-#x2C70#x2C72#x2C75#x2C7E-#x2C80#x2C82#x2C84#x2C86#x2C88#x2C8A#x2C8C#x2C8E#x2C90#x2C92#x2C94#x2C96#x2C98#x2C9A#x2C9C#x2C9E#x2CA0#x2CA2#x2CA4#x2CA6#x2CA8#x2CAA#x2CAC#x2CAE#x2CB0#x2CB2#x2CB4#x2CB6#x2CB8#x2CBA#x2CBC#x2CBE#x2CC0#x2CC2#x2CC4#x2CC6#x2CC8#x2CCA#x2CCC#x2CCE#x2CD0#x2CD2#x2CD4#x2CD6#x2CD8#x2CDA#x2CDC#x2CDE#x2CE0#x2CE2#x2CEB#x2CED#x2CF2#xA640#xA642#xA644#xA646#xA648#xA64A#xA64C#xA64E#xA650#xA652#xA654#xA656#xA658#xA65A#xA65C#xA65E#xA660#xA662#xA664#xA666#xA668#xA66A#xA66C#xA680#xA682#xA684#xA686#xA688#xA68A#xA68C#xA68E#xA690#xA692#xA694#xA696#xA698#xA69A#xA722#xA724#xA726#xA728#xA72A#xA72C#xA72E#xA732#xA734#xA736#xA738#xA73A#xA73C#xA73E#xA740#xA742#xA744#xA746#xA748#xA74A#xA74C#xA74E#xA750#xA752#xA754#xA756#xA758#xA75A#xA75C#xA75E#xA760#xA762#xA764#xA766#xA768#xA76A#xA76C#xA76E#xA779#xA77B#xA77D-#xA77E#xA780#xA782#xA784#xA786#xA78B#xA78D#xA790#xA792#xA796#xA798#xA79A#xA79C#xA79E#xA7A0#xA7A2#xA7A4#xA7A6#xA7A8#xA7AA-#xA7AE#xA7B0-#xA7B4#xA7B6#xA7B8#xA7BA#xA7BC#xA7BE#xA7C0#xA7C2#xA7C4-#xA7C7#xA7C9#xA7D0#xA7D6#xA7D8#xA7F5#xFF21-#xFF3A]
-
- Referenced by: -
- - -====================================================================================================================== -Nl -====================================================================================================================== - - -.. raw:: html - - - - - - [#x16EE-#x16F0] - - [#x2160-#x2182] - - [#x2185-#x2188] - - [#x3007] - - [#x3021-#x3029] - - [#x3038-#x303A] - - [#xA6E6-#xA6EF] - - -
- -
Nl       ::= [#x16EE-#x16F0#x2160-#x2182#x2185-#x2188#x3007#x3021-#x3029#x3038-#x303A#xA6E6-#xA6EF]
-
- Referenced by: -
- - -====================================================================================================================== -UnicodeIdentifierExtend -====================================================================================================================== - - -.. raw:: html - - - - - - Mn - - Mc - - Nd - - Pc - - Cf - - CJK - -
- - -
         ::= Mn
-
           | Mc
-
           | Nd
-
           | Pc
-
           | Cf
-
           | CJK
-
- Referenced by: -
- - -====================================================================================================================== -Cf -====================================================================================================================== - - -.. raw:: html - - - - - - [#xAD] - - [#x600-#x605] - - [#x61C] - - [#x6DD] - - [#x70F] - - [#x890-#x891] - - [#x8E2] - - [#x180E] - - [#x200B-#x200F] - - [#x202A-#x202E] - - [#x2060-#x2064] - - [#x2066-#x206F] - - [#xFEFF] - - [#xFFF9-#xFFFB] - - -
- -
Cf       ::= [#xAD#x600-#x605#x61C#x6DD#x70F#x890-#x891#x8E2#x180E#x200B-#x200F#x202A-#x202E#x2060-#x2064#x2066-#x206F#xFEFF#xFFF9-#xFFFB]
-
- Referenced by: -
- - -====================================================================================================================== -Mc -====================================================================================================================== - - -.. raw:: html - - - - - - [#x903] - - [#x93B] - - [#x93E-#x940] - - [#x949-#x94C] - - [#x94E-#x94F] - - [#x982-#x983] - - [#x9BE-#x9C0] - - [#x9C7-#x9C8] - - [#x9CB-#x9CC] - - [#x9D7] - - [#xA03] - - [#xA3E-#xA40] - - [#xA83] - - [#xABE-#xAC0] - - [#xAC9] - - [#xACB-#xACC] - - [#xB02-#xB03] - - [#xB3E] - - [#xB40] - - [#xB47-#xB48] - - [#xB4B-#xB4C] - - [#xB57] - - [#xBBE-#xBBF] - - [#xBC1-#xBC2] - - [#xBC6-#xBC8] - - [#xBCA-#xBCC] - - [#xBD7] - - [#xC01-#xC03] - - [#xC41-#xC44] - - [#xC82-#xC83] - - [#xCBE] - - [#xCC0-#xCC4] - - [#xCC7-#xCC8] - - [#xCCA-#xCCB] - - [#xCD5-#xCD6] - - [#xCF3] - - [#xD02-#xD03] - - [#xD3E-#xD40] - - [#xD46-#xD48] - - [#xD4A-#xD4C] - - [#xD57] - - [#xD82-#xD83] - - [#xDCF-#xDD1] - - [#xDD8-#xDDF] - - [#xDF2-#xDF3] - - [#xF3E-#xF3F] - - [#xF7F] - - [#x102B-#x102C] - - [#x1031] - - [#x1038] - - [#x103B-#x103C] - - [#x1056-#x1057] - - [#x1062-#x1064] - - [#x1067-#x106D] - - [#x1083-#x1084] - - [#x1087-#x108C] - - [#x108F] - - [#x109A-#x109C] - - [#x1715] - - [#x1734] - - [#x17B6] - - [#x17BE-#x17C5] - - [#x17C7-#x17C8] - - [#x1923-#x1926] - - [#x1929-#x192B] - - [#x1930-#x1931] - - [#x1933-#x1938] - - [#x1A19-#x1A1A] - - [#x1A55] - - [#x1A57] - - [#x1A61] - - [#x1A63-#x1A64] - - [#x1A6D-#x1A72] - - [#x1B04] - - [#x1B35] - - [#x1B3B] - - [#x1B3D-#x1B41] - - [#x1B43-#x1B44] - - [#x1B82] - - [#x1BA1] - - [#x1BA6-#x1BA7] - - [#x1BAA] - - [#x1BE7] - - [#x1BEA-#x1BEC] - - [#x1BEE] - - [#x1BF2-#x1BF3] - - [#x1C24-#x1C2B] - - [#x1C34-#x1C35] - - [#x1CE1] - - [#x1CF7] - - [#x302E-#x302F] - - [#xA823-#xA824] - - [#xA827] - - [#xA880-#xA881] - - [#xA8B4-#xA8C3] - - [#xA952-#xA953] - - [#xA983] - - [#xA9B4-#xA9B5] - - [#xA9BA-#xA9BB] - - [#xA9BE-#xA9C0] - - [#xAA2F-#xAA30] - - [#xAA33-#xAA34] - - [#xAA4D] - - [#xAA7B] - - [#xAA7D] - - [#xAAEB] - - [#xAAEE-#xAAEF] - - [#xAAF5] - - [#xABE3-#xABE4] - - [#xABE6-#xABE7] - - [#xABE9-#xABEA] - - [#xABEC] - - -
- -
Mc       ::= [#x903#x93B#x93E-#x940#x949-#x94C#x94E-#x94F#x982-#x983#x9BE-#x9C0#x9C7-#x9C8#x9CB-#x9CC#x9D7#xA03#xA3E-#xA40#xA83#xABE-#xAC0#xAC9#xACB-#xACC#xB02-#xB03#xB3E#xB40#xB47-#xB48#xB4B-#xB4C#xB57#xBBE-#xBBF#xBC1-#xBC2#xBC6-#xBC8#xBCA-#xBCC#xBD7#xC01-#xC03#xC41-#xC44#xC82-#xC83#xCBE#xCC0-#xCC4#xCC7-#xCC8#xCCA-#xCCB#xCD5-#xCD6#xCF3#xD02-#xD03#xD3E-#xD40#xD46-#xD48#xD4A-#xD4C#xD57#xD82-#xD83#xDCF-#xDD1#xDD8-#xDDF#xDF2-#xDF3#xF3E-#xF3F#xF7F#x102B-#x102C#x1031#x1038#x103B-#x103C#x1056-#x1057#x1062-#x1064#x1067-#x106D#x1083-#x1084#x1087-#x108C#x108F#x109A-#x109C#x1715#x1734#x17B6#x17BE-#x17C5#x17C7-#x17C8#x1923-#x1926#x1929-#x192B#x1930-#x1931#x1933-#x1938#x1A19-#x1A1A#x1A55#x1A57#x1A61#x1A63-#x1A64#x1A6D-#x1A72#x1B04#x1B35#x1B3B#x1B3D-#x1B41#x1B43-#x1B44#x1B82#x1BA1#x1BA6-#x1BA7#x1BAA#x1BE7#x1BEA-#x1BEC#x1BEE#x1BF2-#x1BF3#x1C24-#x1C2B#x1C34-#x1C35#x1CE1#x1CF7#x302E-#x302F#xA823-#xA824#xA827#xA880-#xA881#xA8B4-#xA8C3#xA952-#xA953#xA983#xA9B4-#xA9B5#xA9BA-#xA9BB#xA9BE-#xA9C0#xAA2F-#xAA30#xAA33-#xAA34#xAA4D#xAA7B#xAA7D#xAAEB#xAAEE-#xAAEF#xAAF5#xABE3-#xABE4#xABE6-#xABE7#xABE9-#xABEA#xABEC]
-
- Referenced by: -
- - -====================================================================================================================== -Mn -====================================================================================================================== - - -.. raw:: html - - - - - - [#x300-#x36F] - - [#x483-#x487] - - [#x591-#x5BD] - - [#x5BF] - - [#x5C1-#x5C2] - - [#x5C4-#x5C5] - - [#x5C7] - - [#x610-#x61A] - - [#x64B-#x65F] - - [#x670] - - [#x6D6-#x6DC] - - [#x6DF-#x6E4] - - [#x6E7-#x6E8] - - [#x6EA-#x6ED] - - [#x711] - - [#x730-#x74A] - - [#x7A6-#x7B0] - - [#x7EB-#x7F3] - - [#x7FD] - - [#x816-#x819] - - [#x81B-#x823] - - [#x825-#x827] - - [#x829-#x82D] - - [#x859-#x85B] - - [#x898-#x89F] - - [#x8CA-#x8E1] - - [#x8E3-#x902] - - [#x93A] - - [#x93C] - - [#x941-#x948] - - [#x94D] - - [#x951-#x957] - - [#x962-#x963] - - [#x981] - - [#x9BC] - - [#x9C1-#x9C4] - - [#x9CD] - - [#x9E2-#x9E3] - - [#x9FE] - - [#xA01-#xA02] - - [#xA3C] - - [#xA41-#xA42] - - [#xA47-#xA48] - - [#xA4B-#xA4D] - - [#xA51] - - [#xA70-#xA71] - - [#xA75] - - [#xA81-#xA82] - - [#xABC] - - [#xAC1-#xAC5] - - [#xAC7-#xAC8] - - [#xACD] - - [#xAE2-#xAE3] - - [#xAFA-#xAFF] - - [#xB01] - - [#xB3C] - - [#xB3F] - - [#xB41-#xB44] - - [#xB4D] - - [#xB55-#xB56] - - [#xB62-#xB63] - - [#xB82] - - [#xBC0] - - [#xBCD] - - [#xC00] - - [#xC04] - - [#xC3C] - - [#xC3E-#xC40] - - [#xC46-#xC48] - - [#xC4A-#xC4D] - - [#xC55-#xC56] - - [#xC62-#xC63] - - [#xC81] - - [#xCBC] - - [#xCBF] - - [#xCC6] - - [#xCCC-#xCCD] - - [#xCE2-#xCE3] - - [#xD00-#xD01] - - [#xD3B-#xD3C] - - [#xD41-#xD44] - - [#xD4D] - - [#xD62-#xD63] - - [#xD81] - - [#xDCA] - - [#xDD2-#xDD4] - - [#xDD6] - - [#xE31] - - [#xE34-#xE3A] - - [#xE47-#xE4E] - - [#xEB1] - - [#xEB4-#xEBC] - - [#xEC8-#xECE] - - [#xF18-#xF19] - - [#xF35] - - [#xF37] - - [#xF39] - - [#xF71-#xF7E] - - [#xF80-#xF84] - - [#xF86-#xF87] - - [#xF8D-#xF97] - - [#xF99-#xFBC] - - [#xFC6] - - [#x102D-#x1030] - - [#x1032-#x1037] - - [#x1039-#x103A] - - [#x103D-#x103E] - - [#x1058-#x1059] - - [#x105E-#x1060] - - [#x1071-#x1074] - - [#x1082] - - [#x1085-#x1086] - - [#x108D] - - [#x109D] - - [#x135D-#x135F] - - [#x1712-#x1714] - - [#x1732-#x1733] - - [#x1752-#x1753] - - [#x1772-#x1773] - - [#x17B4-#x17B5] - - [#x17B7-#x17BD] - - [#x17C6] - - [#x17C9-#x17D3] - - [#x17DD] - - [#x180B-#x180D] - - [#x180F] - - [#x1885-#x1886] - - [#x18A9] - - [#x1920-#x1922] - - [#x1927-#x1928] - - [#x1932] - - [#x1939-#x193B] - - [#x1A17-#x1A18] - - [#x1A1B] - - [#x1A56] - - [#x1A58-#x1A5E] - - [#x1A60] - - [#x1A62] - - [#x1A65-#x1A6C] - - [#x1A73-#x1A7C] - - [#x1A7F] - - [#x1AB0-#x1ABD] - - [#x1ABF-#x1ACE] - - [#x1B00-#x1B03] - - [#x1B34] - - [#x1B36-#x1B3A] - - [#x1B3C] - - [#x1B42] - - [#x1B6B-#x1B73] - - [#x1B80-#x1B81] - - [#x1BA2-#x1BA5] - - [#x1BA8-#x1BA9] - - [#x1BAB-#x1BAD] - - [#x1BE6] - - [#x1BE8-#x1BE9] - - [#x1BED] - - [#x1BEF-#x1BF1] - - [#x1C2C-#x1C33] - - [#x1C36-#x1C37] - - [#x1CD0-#x1CD2] - - [#x1CD4-#x1CE0] - - [#x1CE2-#x1CE8] - - [#x1CED] - - [#x1CF4] - - [#x1CF8-#x1CF9] - - [#x1DC0-#x1DFF] - - [#x20D0-#x20DC] - - [#x20E1] - - [#x20E5-#x20F0] - - [#x2CEF-#x2CF1] - - [#x2D7F] - - [#x2DE0-#x2DFF] - - [#x302A-#x302D] - - [#x3099-#x309A] - - [#xA66F] - - [#xA674-#xA67D] - - [#xA69E-#xA69F] - - [#xA6F0-#xA6F1] - - [#xA802] - - [#xA806] - - [#xA80B] - - [#xA825-#xA826] - - [#xA82C] - - [#xA8C4-#xA8C5] - - [#xA8E0-#xA8F1] - - [#xA8FF] - - [#xA926-#xA92D] - - [#xA947-#xA951] - - [#xA980-#xA982] - - [#xA9B3] - - [#xA9B6-#xA9B9] - - [#xA9BC-#xA9BD] - - [#xA9E5] - - [#xAA29-#xAA2E] - - [#xAA31-#xAA32] - - [#xAA35-#xAA36] - - [#xAA43] - - [#xAA4C] - - [#xAA7C] - - [#xAAB0] - - [#xAAB2-#xAAB4] - - [#xAAB7-#xAAB8] - - [#xAABE-#xAABF] - - [#xAAC1] - - [#xAAEC-#xAAED] - - [#xAAF6] - - [#xABE5] - - [#xABE8] - - [#xABED] - - [#xFB1E] - - [#xFE00-#xFE0F] - - [#xFE20-#xFE2F] - - -
- -
Mn       ::= [#x300-#x36F#x483-#x487#x591-#x5BD#x5BF#x5C1-#x5C2#x5C4-#x5C5#x5C7#x610-#x61A#x64B-#x65F#x670#x6D6-#x6DC#x6DF-#x6E4#x6E7-#x6E8#x6EA-#x6ED#x711#x730-#x74A#x7A6-#x7B0#x7EB-#x7F3#x7FD#x816-#x819#x81B-#x823#x825-#x827#x829-#x82D#x859-#x85B#x898-#x89F#x8CA-#x8E1#x8E3-#x902#x93A#x93C#x941-#x948#x94D#x951-#x957#x962-#x963#x981#x9BC#x9C1-#x9C4#x9CD#x9E2-#x9E3#x9FE#xA01-#xA02#xA3C#xA41-#xA42#xA47-#xA48#xA4B-#xA4D#xA51#xA70-#xA71#xA75#xA81-#xA82#xABC#xAC1-#xAC5#xAC7-#xAC8#xACD#xAE2-#xAE3#xAFA-#xAFF#xB01#xB3C#xB3F#xB41-#xB44#xB4D#xB55-#xB56#xB62-#xB63#xB82#xBC0#xBCD#xC00#xC04#xC3C#xC3E-#xC40#xC46-#xC48#xC4A-#xC4D#xC55-#xC56#xC62-#xC63#xC81#xCBC#xCBF#xCC6#xCCC-#xCCD#xCE2-#xCE3#xD00-#xD01#xD3B-#xD3C#xD41-#xD44#xD4D#xD62-#xD63#xD81#xDCA#xDD2-#xDD4#xDD6#xE31#xE34-#xE3A#xE47-#xE4E#xEB1#xEB4-#xEBC#xEC8-#xECE#xF18-#xF19#xF35#xF37#xF39#xF71-#xF7E#xF80-#xF84#xF86-#xF87#xF8D-#xF97#xF99-#xFBC#xFC6#x102D-#x1030#x1032-#x1037#x1039-#x103A#x103D-#x103E#x1058-#x1059#x105E-#x1060#x1071-#x1074#x1082#x1085-#x1086#x108D#x109D#x135D-#x135F#x1712-#x1714#x1732-#x1733#x1752-#x1753#x1772-#x1773#x17B4-#x17B5#x17B7-#x17BD#x17C6#x17C9-#x17D3#x17DD#x180B-#x180D#x180F#x1885-#x1886#x18A9#x1920-#x1922#x1927-#x1928#x1932#x1939-#x193B#x1A17-#x1A18#x1A1B#x1A56#x1A58-#x1A5E#x1A60#x1A62#x1A65-#x1A6C#x1A73-#x1A7C#x1A7F#x1AB0-#x1ABD#x1ABF-#x1ACE#x1B00-#x1B03#x1B34#x1B36-#x1B3A#x1B3C#x1B42#x1B6B-#x1B73#x1B80-#x1B81#x1BA2-#x1BA5#x1BA8-#x1BA9#x1BAB-#x1BAD#x1BE6#x1BE8-#x1BE9#x1BED#x1BEF-#x1BF1#x1C2C-#x1C33#x1C36-#x1C37#x1CD0-#x1CD2#x1CD4-#x1CE0#x1CE2-#x1CE8#x1CED#x1CF4#x1CF8-#x1CF9#x1DC0-#x1DFF#x20D0-#x20DC#x20E1#x20E5-#x20F0#x2CEF-#x2CF1#x2D7F#x2DE0-#x2DFF#x302A-#x302D#x3099-#x309A#xA66F#xA674-#xA67D#xA69E-#xA69F#xA6F0-#xA6F1#xA802#xA806#xA80B#xA825-#xA826#xA82C#xA8C4-#xA8C5#xA8E0-#xA8F1#xA8FF#xA926-#xA92D#xA947-#xA951#xA980-#xA982#xA9B3#xA9B6-#xA9B9#xA9BC-#xA9BD#xA9E5#xAA29-#xAA2E#xAA31-#xAA32#xAA35-#xAA36#xAA43#xAA4C#xAA7C#xAAB0#xAAB2-#xAAB4#xAAB7-#xAAB8#xAABE-#xAABF#xAAC1#xAAEC-#xAAED#xAAF6#xABE5#xABE8#xABED#xFB1E#xFE00-#xFE0F#xFE20-#xFE2F]
-
- Referenced by: -
- - -====================================================================================================================== -Nd -====================================================================================================================== - - -.. raw:: html - - - - - - [0-9] - - [#x660-#x669] - - [#x6F0-#x6F9] - - [#x7C0-#x7C9] - - [#x966-#x96F] - - [#x9E6-#x9EF] - - [#xA66-#xA6F] - - [#xAE6-#xAEF] - - [#xB66-#xB6F] - - [#xBE6-#xBEF] - - [#xC66-#xC6F] - - [#xCE6-#xCEF] - - [#xD66-#xD6F] - - [#xDE6-#xDEF] - - [#xE50-#xE59] - - [#xED0-#xED9] - - [#xF20-#xF29] - - [#x1040-#x1049] - - [#x1090-#x1099] - - [#x17E0-#x17E9] - - [#x1810-#x1819] - - [#x1946-#x194F] - - [#x19D0-#x19D9] - - [#x1A80-#x1A89] - - [#x1A90-#x1A99] - - [#x1B50-#x1B59] - - [#x1BB0-#x1BB9] - - [#x1C40-#x1C49] - - [#x1C50-#x1C59] - - [#xA620-#xA629] - - [#xA8D0-#xA8D9] - - [#xA900-#xA909] - - [#xA9D0-#xA9D9] - - [#xA9F0-#xA9F9] - - [#xAA50-#xAA59] - - [#xABF0-#xABF9] - - [#xFF10-#xFF19] - - -
- -
Nd       ::= [0-9#x660-#x669#x6F0-#x6F9#x7C0-#x7C9#x966-#x96F#x9E6-#x9EF#xA66-#xA6F#xAE6-#xAEF#xB66-#xB6F#xBE6-#xBEF#xC66-#xC6F#xCE6-#xCEF#xD66-#xD6F#xDE6-#xDEF#xE50-#xE59#xED0-#xED9#xF20-#xF29#x1040-#x1049#x1090-#x1099#x17E0-#x17E9#x1810-#x1819#x1946-#x194F#x19D0-#x19D9#x1A80-#x1A89#x1A90-#x1A99#x1B50-#x1B59#x1BB0-#x1BB9#x1C40-#x1C49#x1C50-#x1C59#xA620-#xA629#xA8D0-#xA8D9#xA900-#xA909#xA9D0-#xA9D9#xA9F0-#xA9F9#xAA50-#xAA59#xABF0-#xABF9#xFF10-#xFF19]
-
- Referenced by: -
- - -====================================================================================================================== -Pc -====================================================================================================================== - - -.. raw:: html - - - - - - [#x203F-#x2040] - - [#x2054] - - [#xFE33-#xFE34] - - [#xFE4D-#xFE4F] - - [#xFF3F] - - -
- -
Pc       ::= [#x203F-#x2040#x2054#xFE33-#xFE34#xFE4D-#xFE4F#xFF3F]
-
- Referenced by: -
- - -====================================================================================================================== -CJK -====================================================================================================================== - - -.. raw:: html - - - - - - [#xAC00-#xD7A3] - - [#x4E00-#x9FFF] - - -
- -
CJK      ::= [#xAC00-#xD7A3#x4E00-#x9FFF]
-
- - -====================================================================================================================== -ESC -====================================================================================================================== - - -.. raw:: html - - - - - - \ - - n - - t - - b - - r - - f - - \ - - " - - -
- -
ESC      ::= '\' [ntbrf\"]
-
- Referenced by: -
- - -====================================================================================================================== -S_CHAR_LITERAL -====================================================================================================================== - - -.. raw:: html - - - - - - U - - E - - N - - R - - B - - RB - - _utf8 - - q'{ - - . - - }' - - ' - - ESC - \' - - [^'\] - - '' - - [^'] - - ' - - $$ - - [^$] - - $$ - - q'( - - . - - )' - - q'[ - - . - - ]' - - q'' - - . - - '' - - -
- - -
         ::= ( [UENRB] | 'RB' | '_utf8' )? ( "'" ( ( ESC | "\'" | [^'\] )* | ( "''" | [^'] )+ ) "'" | '$$' [^$]* '$$' | "q'{" .* "}'" | "q'(" .* ")'" | "q'[" .* "]'" | "q''" .* "''" )
-
- - -====================================================================================================================== -S_QUOTED_IDENTIFIER -====================================================================================================================== - - -.. raw:: html - - - - - - " - - "" - - [^"#xA#xD] - - " - - ` - - [^`#xA#xD] - - ` - - [ - - [^#x5D#xA#xD] - - ] - - -
- - -
         ::= '"' ( '""' | [^"#xA#xD] )* '"'
-
           | '`' [^`#xA#xD]+ '`'
-
           | '[' [^#x5D#xA#xD]* ']'
-
- - -====================================================================================================================== -EOF -====================================================================================================================== - - -.. raw:: html - - - - - - $ - - -
- -
EOF      ::= $
-
- Referenced by: -
- - \ No newline at end of file diff --git a/src/site/sphinx/syntax_stable.rst b/src/site/sphinx/syntax_stable.rst deleted file mode 100644 index 05384cba76..0000000000 --- a/src/site/sphinx/syntax_stable.rst +++ /dev/null @@ -1,21103 +0,0 @@ - -********************************************************************* -SQL Syntax |JSQLPARSER_VERSION| -********************************************************************* - -The EBNF and Railroad Diagrams for |JSQLPARSER_VERSION|. - - -====================================================================================================================== -NonReservedWord -====================================================================================================================== - - -.. raw:: html - - - - - - ACTION - - ACTIVE - - ADD - - ADVANCE - - ADVISE - - AGAINST - - AGGREGATE - - ALGORITHM - - ALIGN - - ALTER - - ALWAYS - - ANALYZE - - APPEND_ONLY - - APPLY - - APPROXIMATE - - ARCHIVE - - ARRAY - - ASYMMETRIC - - AT - - ASC - - AUTHORIZATION - - AUTO - - AUTO_INCREMENT - - AZURE - - BASE64 - - BEFORE - - BEGIN - - BERNOULLI - - BINARY - - BIT - - BLOBSTORAGE - - BLOCK - - BOOLEAN - - BREADTH - - BRANCH - - BROWSE - - BY - - BYTES - - CACHE - - BUFFERS - - BYTE - - CALL - - CASCADE - - CASE - - CAST - - CERTIFICATE - - CHARACTER - - CHANGE - - CHANGES - - CHECKPOINT - - CHAR - - CLOSE - - CLOUD - - COALESCE - - COLLATE - - COLUMN - - COLUMNS - - COMMIT - - COMMENT - - COMMENTS - - CONFLICT - - CONSTRAINTS - - CONVERT - - CORRESPONDING - - COSTS - - COUNT - - CREATED - - CYCLE - - DATABASE - - DATA - - DECLARE - - DBA_RECYCLEBIN - - DEFAULTS - - DEPTH - - DEFERRABLE - - DELAYED - - DELETE - - DELIMIT - - DELIMITER - - DESC - - DESCRIBE - - DISABLE - - DISCARD - - DISCONNECT - - DIV - - DDL - - DML - - DO - - DOMAIN - - DRIVER - - DROP - - DUMP - - DUMPFILE - - DUPLICATE - - ELEMENTS - - ENCLOSED - - EMIT - - EMPTY - - ENABLE - - ENCODING - - ENCRYPTION - - END - - ESCAPED - - ENFORCED - - ENGINE - - ERROR - - ESCAPE - - EXA - - EXCHANGE - - EXCLUDE - - EXCLUDING - - EXCLUSIVE - - EXEC - - EXECUTE - - EXPLAIN - - EXPLICIT - - EXTENDED - - EXTRACT - - EXPORT - - K_ISOLATION - FILTER - - FIELDS - - FIRST - - FLUSH - - FOLLOWING - - FORMAT - - FULLTEXT - - FUNCTION - - GRANT - - GROUP_CONCAT - - GUARD - - HASH - - HIGH - - HIGH_PRIORITY - - HISTORY - - HOPPING - - IDENTIFIED - - IDENTITY - - INCLUDE - - INCLUDE_NULL_VALUES - - INCLUDING - - INCREMENT - - INDEX - - INFORMATION - - INSERT - - INTERLEAVE - - INTERPRET - - INVALIDATE - - INVERSE - - INVISIBLE - - ISNULL - - JDBC - - JSON - - JSON_OBJECT - - JSON_OBJECTAGG - - JSON_ARRAY - - JSON_ARRAYAGG - - KEEP - - KEY_BLOCK_SIZE - - KEY - - KEYS - - KILL - - FN - - LAST - - LEADING - - LESS - - LEVEL - - LINES - - LOCAL - - LOCK - - LOCKED - - LINK - - LOG - - LOOP - - LOW - - LOW_PRIORITY - - LTRIM - - MATCH - - MATCH_ANY - - MATCH_ALL - - MATCH_PHRASE - - MATCH_PHRASE_PREFIX - - MATCH_REGEXP - - MATCHED - - MATERIALIZED - - MAX - - MAXVALUE - - MEMBER - - MERGE - - MIN - - MINVALUE - - MODE - - MODIFY - - MOVEMENT - - NAMES - - NAME - - NEVER - - NEXT - - K_NEXTVAL - NO - - NOCACHE - - NOKEEP - - NOLOCK - - NOMAXVALUE - - NOMINVALUE - - NONE - - NOORDER - - NOTHING - - NOTNULL - - NOVALIDATE - - NULLS - - NOWAIT - - OF - - OFF - - OPTIONALLY - - OPEN - - ORA - - ORDINALITY - - OUTFILE - - OVER - - OVERFLOW - - OVERLAPS - - OVERRIDING - - OVERWRITE - - PADDING - - PARALLEL - - PARENT - - PARSER - - PARTITION - - PARTITIONING - - PATH - - PERCENT - - PLACING - - PLAN - - PLUS - - PRECEDING - - PRIMARY - - POLICY - - PURGE - - QUERY - - QUICK - - QUIESCE - - RANGE - - RAW - - READ - - REBUILD - - RECYCLEBIN - - RECURSIVE - - REFERENCES - - REFRESH - - REGEXP - - REJECT - - RESPECT - - RLIKE - - REGEXP_LIKE - - REGISTER - - REMOTE - - REMOVE - - RENAME - - REORGANIZE - - REPAIR - - REPEATABLE - - REPLACE - - RESET - - RESTART - - RESUMABLE - - RESUME - - RESTRICT - - RESTRICTED - - RETURN - - ROLLBACK - - ROLLUP - - ROOT - - ROW - - ROWS - - RTRIM - - SAFE_CAST - - SAFE_CONVERT - - SAVEPOINT - - SCHEMA - - SEARCH - - SECURE - - SECURITY - - SEED - - SEQUENCE - - SEPARATOR - - SESSION - - SETS - - SHOW - - SHUTDOWN - - SHARE - - SIBLINGS - - SIMILAR - - SIZE - - SKIP - - SPATIAL - - STARTING - - STORED - - STREAM - - STRICT - - STRING - - STRUCT - - SUMMARIZE - - SUSPEND - - SWITCH - - SYMMETRIC - - SYNONYM - - SYSTEM - - SYSTEM_TIME - - SYSTEM_TIMESTAMP - - SYSTEM_VERSION - - TABLE - - TABLESPACE - - TERMINATED - - TRIGGER - - THEN - - TEMP - - K_TEXT_LITERAL - TEMPORARY - - THAN - - K_TIME_KEY_EXPR - TIMEOUT - - TO - - TRIM - - TRUNCATE - - TRY_CAST - - TRY_CONVERT - - TUMBLING - - TYPE - - UNLIMITED - - UNLOGGED - - UPDATE - - UPSERT - - UNQIESCE - - USER - - SIGNED - - K_STRING_FUNCTION_NAME - UNSIGNED - - VALIDATE - - VALIDATION - - VERBOSE - - VERSION - - VIEW - - VISIBLE - - VOLATILE - - CONCURRENTLY - - WAIT - - WITH TIES - - WITHIN - - WITHOUT - - WITHOUT_ARRAY_WRAPPER - - WORK - - XML - - XMLAGG - - XMLDATA - - XMLSCHEMA - - XMLTEXT - - XSINIL - - YAML - - YES - - ZONE - - -
- - -
         ::= 'ACTION'
-
           | 'ACTIVE'
-
           | 'ADD'
-
           | 'ADVANCE'
-
           | 'ADVISE'
-
           | 'AGAINST'
-
           | 'AGGREGATE'
-
           | 'ALGORITHM'
-
           | 'ALIGN'
-
           | 'ALTER'
-
           | 'ALWAYS'
-
           | 'ANALYZE'
-
           | 'APPEND_ONLY'
-
           | 'APPLY'
-
           | 'APPROXIMATE'
-
           | 'ARCHIVE'
-
           | 'ARRAY'
-
           | 'ASYMMETRIC'
-
           | 'AT'
-
           | 'ASC'
-
           | 'AUTHORIZATION'
-
           | 'AUTO'
-
           | 'AUTO_INCREMENT'
-
           | 'AZURE'
-
           | 'BASE64'
-
           | 'BEFORE'
-
           | 'BEGIN'
-
           | 'BERNOULLI'
-
           | 'BINARY'
-
           | 'BIT'
-
           | 'BLOBSTORAGE'
-
           | 'BLOCK'
-
           | 'BOOLEAN'
-
           | 'BREADTH'
-
           | 'BRANCH'
-
           | 'BROWSE'
-
           | 'BY'
-
           | 'BYTES'
-
           | 'CACHE'
-
           | 'BUFFERS'
-
           | 'BYTE'
-
           | 'CALL'
-
           | 'CASCADE'
-
           | 'CASE'
-
           | 'CAST'
-
           | 'CERTIFICATE'
-
           | 'CHARACTER'
-
           | 'CHANGE'
-
           | 'CHANGES'
-
           | 'CHECKPOINT'
-
           | 'CHAR'
-
           | 'CLOSE'
-
           | 'CLOUD'
-
           | 'COALESCE'
-
           | 'COLLATE'
-
           | 'COLUMN'
-
           | 'COLUMNS'
-
           | 'COMMIT'
-
           | 'COMMENT'
-
           | 'COMMENTS'
-
           | 'CONFLICT'
-
           | 'CONSTRAINTS'
-
           | 'CONVERT'
-
           | 'CORRESPONDING'
-
           | 'COSTS'
-
           | 'COUNT'
-
           | 'CREATED'
-
           | 'CYCLE'
-
           | 'DATABASE'
-
           | 'DATA'
-
           | 'DECLARE'
-
           | 'DBA_RECYCLEBIN'
-
           | 'DEFAULTS'
-
           | 'DEPTH'
-
           | 'DEFERRABLE'
-
           | 'DELAYED'
-
           | 'DELETE'
-
           | 'DELIMIT'
-
           | 'DELIMITER'
-
           | 'DESC'
-
           | 'DESCRIBE'
-
           | 'DISABLE'
-
           | 'DISCARD'
-
           | 'DISCONNECT'
-
           | 'DIV'
-
           | 'DDL'
-
           | 'DML'
-
           | 'DO'
-
           | 'DOMAIN'
-
           | 'DRIVER'
-
           | 'DROP'
-
           | 'DUMP'
-
           | 'DUMPFILE'
-
           | 'DUPLICATE'
-
           | 'ELEMENTS'
-
           | 'ENCLOSED'
-
           | 'EMIT'
-
           | 'EMPTY'
-
           | 'ENABLE'
-
           | 'ENCODING'
-
           | 'ENCRYPTION'
-
           | 'END'
-
           | 'ESCAPED'
-
           | 'ENFORCED'
-
           | 'ENGINE'
-
           | 'ERROR'
-
           | 'ESCAPE'
-
           | 'EXA'
-
           | 'EXCHANGE'
-
           | 'EXCLUDE'
-
           | 'EXCLUDING'
-
           | 'EXCLUSIVE'
-
           | 'EXEC'
-
           | 'EXECUTE'
-
           | 'EXPLAIN'
-
           | 'EXPLICIT'
-
           | 'EXTENDED'
-
           | 'EXTRACT'
-
           | 'EXPORT'
-
           | K_ISOLATION
-
           | 'FILTER'
-
           | 'FIELDS'
-
           | 'FIRST'
-
           | 'FLUSH'
-
           | 'FOLLOWING'
-
           | 'FORMAT'
-
           | 'FULLTEXT'
-
           | 'FUNCTION'
-
           | 'GRANT'
-
           | 'GROUP_CONCAT'
-
           | 'GUARD'
-
           | 'HASH'
-
           | 'HIGH'
-
           | 'HIGH_PRIORITY'
-
           | 'HISTORY'
-
           | 'HOPPING'
-
           | 'IDENTIFIED'
-
           | 'IDENTITY'
-
           | 'INCLUDE'
-
           | 'INCLUDE_NULL_VALUES'
-
           | 'INCLUDING'
-
           | 'INCREMENT'
-
           | 'INDEX'
-
           | 'INFORMATION'
-
           | 'INSERT'
-
           | 'INTERLEAVE'
-
           | 'INTERPRET'
-
           | 'INVALIDATE'
-
           | 'INVERSE'
-
           | 'INVISIBLE'
-
           | 'ISNULL'
-
           | 'JDBC'
-
           | 'JSON'
-
           | 'JSON_OBJECT'
-
           | 'JSON_OBJECTAGG'
-
           | 'JSON_ARRAY'
-
           | 'JSON_ARRAYAGG'
-
           | 'KEEP'
-
           | 'KEY_BLOCK_SIZE'
-
           | 'KEY'
-
           | 'KEYS'
-
           | 'KILL'
-
           | 'FN'
-
           | 'LAST'
-
           | 'LEADING'
-
           | 'LESS'
-
           | 'LEVEL'
-
           | 'LINES'
-
           | 'LOCAL'
-
           | 'LOCK'
-
           | 'LOCKED'
-
           | 'LINK'
-
           | 'LOG'
-
           | 'LOOP'
-
           | 'LOW'
-
           | 'LOW_PRIORITY'
-
           | 'LTRIM'
-
           | 'MATCH'
-
           | 'MATCH_ANY'
-
           | 'MATCH_ALL'
-
           | 'MATCH_PHRASE'
-
           | 'MATCH_PHRASE_PREFIX'
-
           | 'MATCH_REGEXP'
-
           | 'MATCHED'
-
           | 'MATERIALIZED'
-
           | 'MAX'
-
           | 'MAXVALUE'
-
           | 'MEMBER'
-
           | 'MERGE'
-
           | 'MIN'
-
           | 'MINVALUE'
-
           | 'MODE'
-
           | 'MODIFY'
-
           | 'MOVEMENT'
-
           | 'NAMES'
-
           | 'NAME'
-
           | 'NEVER'
-
           | 'NEXT'
-
           | K_NEXTVAL
-
           | 'NO'
-
           | 'NOCACHE'
-
           | 'NOKEEP'
-
           | 'NOLOCK'
-
           | 'NOMAXVALUE'
-
           | 'NOMINVALUE'
-
           | 'NONE'
-
           | 'NOORDER'
-
           | 'NOTHING'
-
           | 'NOTNULL'
-
           | 'NOVALIDATE'
-
           | 'NULLS'
-
           | 'NOWAIT'
-
           | 'OF'
-
           | 'OFF'
-
           | 'OPTIONALLY'
-
           | 'OPEN'
-
           | 'ORA'
-
           | 'ORDINALITY'
-
           | 'OUTFILE'
-
           | 'OVER'
-
           | 'OVERFLOW'
-
           | 'OVERLAPS'
-
           | 'OVERRIDING'
-
           | 'OVERWRITE'
-
           | 'PADDING'
-
           | 'PARALLEL'
-
           | 'PARENT'
-
           | 'PARSER'
-
           | 'PARTITION'
-
           | 'PARTITIONING'
-
           | 'PATH'
-
           | 'PERCENT'
-
           | 'PLACING'
-
           | 'PLAN'
-
           | 'PLUS'
-
           | 'PRECEDING'
-
           | 'PRIMARY'
-
           | 'POLICY'
-
           | 'PURGE'
-
           | 'QUERY'
-
           | 'QUICK'
-
           | 'QUIESCE'
-
           | 'RANGE'
-
           | 'RAW'
-
           | 'READ'
-
           | 'REBUILD'
-
           | 'RECYCLEBIN'
-
           | 'RECURSIVE'
-
           | 'REFERENCES'
-
           | 'REFRESH'
-
           | 'REGEXP'
-
           | 'REJECT'
-
           | 'RESPECT'
-
           | 'RLIKE'
-
           | 'REGEXP_LIKE'
-
           | 'REGISTER'
-
           | 'REMOTE'
-
           | 'REMOVE'
-
           | 'RENAME'
-
           | 'REORGANIZE'
-
           | 'REPAIR'
-
           | 'REPEATABLE'
-
           | 'REPLACE'
-
           | 'RESET'
-
           | 'RESTART'
-
           | 'RESUMABLE'
-
           | 'RESUME'
-
           | 'RESTRICT'
-
           | 'RESTRICTED'
-
           | 'RETURN'
-
           | 'ROLLBACK'
-
           | 'ROLLUP'
-
           | 'ROOT'
-
           | 'ROW'
-
           | 'ROWS'
-
           | 'RTRIM'
-
           | 'SAFE_CAST'
-
           | 'SAFE_CONVERT'
-
           | 'SAVEPOINT'
-
           | 'SCHEMA'
-
           | 'SEARCH'
-
           | 'SECURE'
-
           | 'SECURITY'
-
           | 'SEED'
-
           | 'SEQUENCE'
-
           | 'SEPARATOR'
-
           | 'SESSION'
-
           | 'SETS'
-
           | 'SHOW'
-
           | 'SHUTDOWN'
-
           | 'SHARE'
-
           | 'SIBLINGS'
-
           | 'SIMILAR'
-
           | 'SIZE'
-
           | 'SKIP'
-
           | 'SPATIAL'
-
           | 'STARTING'
-
           | 'STORED'
-
           | 'STREAM'
-
           | 'STRICT'
-
           | 'STRING'
-
           | 'STRUCT'
-
           | 'SUMMARIZE'
-
           | 'SUSPEND'
-
           | 'SWITCH'
-
           | 'SYMMETRIC'
-
           | 'SYNONYM'
-
           | 'SYSTEM'
-
           | 'SYSTEM_TIME'
-
           | 'SYSTEM_TIMESTAMP'
-
           | 'SYSTEM_VERSION'
-
           | 'TABLE'
-
           | 'TABLESPACE'
-
           | 'TERMINATED'
-
           | 'TRIGGER'
-
           | 'THEN'
-
           | 'TEMP'
-
           | K_TEXT_LITERAL
-
           | 'TEMPORARY'
-
           | 'THAN'
-
           | K_TIME_KEY_EXPR
-
           | 'TIMEOUT'
-
           | 'TO'
-
           | 'TRIM'
-
           | 'TRUNCATE'
-
           | 'TRY_CAST'
-
           | 'TRY_CONVERT'
-
           | 'TUMBLING'
-
           | 'TYPE'
-
           | 'UNLIMITED'
-
           | 'UNLOGGED'
-
           | 'UPDATE'
-
           | 'UPSERT'
-
           | 'UNQIESCE'
-
           | 'USER'
-
           | 'SIGNED'
-
           | K_STRING_FUNCTION_NAME
-
           | 'UNSIGNED'
-
           | 'VALIDATE'
-
           | 'VALIDATION'
-
           | 'VERBOSE'
-
           | 'VERSION'
-
           | 'VIEW'
-
           | 'VISIBLE'
-
           | 'VOLATILE'
-
           | 'CONCURRENTLY'
-
           | 'WAIT'
-
           | 'WITH TIES'
-
           | 'WITHIN'
-
           | 'WITHOUT'
-
           | 'WITHOUT_ARRAY_WRAPPER'
-
           | 'WORK'
-
           | 'XML'
-
           | 'XMLAGG'
-
           | 'XMLDATA'
-
           | 'XMLSCHEMA'
-
           | 'XMLTEXT'
-
           | 'XSINIL'
-
           | 'YAML'
-
           | 'YES'
-
           | 'ZONE'
-
- Referenced by: -
- - -====================================================================================================================== -KeywordOrIdentifier -====================================================================================================================== - - -.. raw:: html - - - - - - S_IDENTIFIER - - S_QUOTED_IDENTIFIER - NAME - - NEXT - - VALUE - - PUBLIC - - STRING - - DATA - - -
- - -
         ::= S_IDENTIFIER
-
           | S_QUOTED_IDENTIFIER
-
           | 'NAME'
-
           | 'NEXT'
-
           | 'VALUE'
-
           | 'PUBLIC'
-
           | 'STRING'
-
           | 'DATA'
-
- - -====================================================================================================================== -Statement -====================================================================================================================== - - -.. raw:: html - - - - - - IF - - Condition - - SingleStatement - - Block - - ST_SEMICOLON - ELSE - - SingleStatement - - Block - - ST_SEMICOLON - - SingleStatement - - Block - - ST_SEMICOLON - - EOF - - UnsupportedStatement - -
- - -
         ::= 'IF' Condition ( SingleStatement | Block ) ST_SEMICOLON? ( 'ELSE' ( SingleStatement | Block ) ST_SEMICOLON? )?
-
           | ( SingleStatement | Block ) ( ST_SEMICOLON | EOF )
-
           | UnsupportedStatement
-
- Not referenced by any. -
- - -====================================================================================================================== -SingleStatement -====================================================================================================================== - - -.. raw:: html - - - - - - WithList - - SelectWithWithItems - - InsertWithWithItems - - UpdateWithWithItems - - DeleteWithWithItems - - Merge - - Select - - TableStatement - - Upsert - - Alter - - RenameTableStatement - - Create - - Drop - - Analyze - - Truncate - - Execute - - Set - - Reset - - Show - - RefreshMaterializedView - - Use - - SavepointStatement - - RollbackStatement - COMMIT - - Comment - - Describe - - Explain - - Declare - - Grant - - PurgeStatement - - SessionStatement - - LockStatement - - Import - - Export - -
- - - -
           | Select
-
           | TableStatement
-
           | Upsert
-
           | Alter
-
           | RenameTableStatement
-
           | Create
-
           | Drop
-
           | Analyze
-
           | Truncate
-
           | Execute
-
           | Set
-
           | Reset
-
           | Show
-
           | RefreshMaterializedView
-
           | Use
-
           | SavepointStatement
-
           | RollbackStatement
-
           | 'COMMIT'
-
           | Comment
-
           | Describe
-
           | Explain
-
           | Declare
-
           | Grant
-
           | PurgeStatement
-
           | SessionStatement
-
           | LockStatement
-
           | Import
-
           | Export
-
- Referenced by: -
- - -====================================================================================================================== -Block -====================================================================================================================== - - -.. raw:: html - - - - - - BEGIN - - ST_SEMICOLON - - SingleStatement - - Block - - ST_SEMICOLON - END - - ST_SEMICOLON - -
- -
Block    ::= 'BEGIN' ST_SEMICOLON* ( ( SingleStatement | Block ) ST_SEMICOLON )+ 'END' ST_SEMICOLON?
-
- Referenced by: -
- - -====================================================================================================================== -Statements -====================================================================================================================== - - -.. raw:: html - - - - - - ST_SEMICOLON - IF - - Condition - - SingleStatement - - Block - - ST_SEMICOLON - ELSE - - SingleStatement - - Block - - ST_SEMICOLON - - SingleStatement - - Block - - ST_SEMICOLON - - EOF - - ST_SEMICOLON - IF - - Condition - - SingleStatement - - Block - - ST_SEMICOLON - ELSE - - SingleStatement - - Block - - ST_SEMICOLON - - UnsupportedStatement - - EOF - -
- - - -
- Not referenced by any. -
- - -====================================================================================================================== -LockStatement -====================================================================================================================== - - -.. raw:: html - - - - - - LOCK - - TABLE - - Table - IN - - ROW - - SHARE - - EXCLUSIVE - - SHARE - - ROW - - EXCLUSIVE - - UPDATE - - EXCLUSIVE - - MODE - - NOWAIT - - WAIT - - S_LONG - -
- - -
         ::= 'LOCK' 'TABLE' Table 'IN' ( 'ROW' ( 'SHARE' | 'EXCLUSIVE' ) | 'SHARE' ( 'ROW' 'EXCLUSIVE' | 'UPDATE' )? - | 'EXCLUSIVE' ) 'MODE' ( 'NOWAIT' | 'WAIT' S_LONG )?
-
- Referenced by: -
- - -====================================================================================================================== -LikeClause -====================================================================================================================== - - -.. raw:: html - - - - - - LIKE - - Table - ( - - ColumnSelectItemsList - ) - - INCLUDING - - EXCLUDING - - DEFAULTS - - INCLUDING - - EXCLUDING - - IDENTITY - - INCLUDING - - EXCLUDING - - COMMENTS - - -
- - -
         ::= 'LIKE' Table ( '(' ColumnSelectItemsList ')' )? ( ( 'INCLUDING' | 'EXCLUDING' ) 'DEFAULTS' )? ( ( 'INCLUDING' | 'EXCLUDING' - ) 'IDENTITY' )? ( ( 'INCLUDING' | 'EXCLUDING' ) 'COMMENTS' )?
-
- Referenced by: -
- - -====================================================================================================================== -Export -====================================================================================================================== - - -.. raw:: html - - - - - - EXPORT - - Table - - ParenthesedColumnList - - ParenthesedSelect - INTO - - ExportIntoItem - -
- - -
- Referenced by: -
- - -====================================================================================================================== -Import -====================================================================================================================== - - -.. raw:: html - - - - - - IMPORT - - INTO - - Table - - ParenthesedColumnList - - ImportColumns - FROM - - ImportFromItem - -
- -
Import   ::= 'IMPORT' ( 'INTO' ( Table ParenthesedColumnList? | ImportColumns ) )? 'FROM' ImportFromItem
-
- Referenced by: -
- - -====================================================================================================================== -SubImport -====================================================================================================================== - - -.. raw:: html - - - - - - ( - - IMPORT - - INTO - - ImportColumns - FROM - - ImportFromItem - ) - - -
- - -
         ::= '(' 'IMPORT' ( 'INTO' ImportColumns )? 'FROM' ImportFromItem ')'
-
- Referenced by: -
- - -====================================================================================================================== -ImportColumns -====================================================================================================================== - - -.. raw:: html - - - - - - ( - - ColumnDefinition - - LikeClause - , - - ) - - -
- - -
         ::= '(' ( ColumnDefinition | LikeClause ) ( ',' ( ColumnDefinition | LikeClause ) )* ')'
-
- Referenced by: -
- - -====================================================================================================================== -ExportIntoItem -====================================================================================================================== - - -.. raw:: html - - - - - - DBMSDestination - - FileDestination - - ScriptSourceDestination - - ErrorClause - -
- - - -
- Referenced by: -
- - -====================================================================================================================== -ImportFromItem -====================================================================================================================== - - -.. raw:: html - - - - - - DBMSSource - - FileSource - - ScriptSourceDestination - - ErrorClause - -
- - - -
- Referenced by: -
- - -====================================================================================================================== -DBMSDestination -====================================================================================================================== - - -.. raw:: html - - - - - - DBMSType - - ConnectionDefinition - TABLE - - Table - - ParenthesedColumnList - - DBMSTableDestinationOptionList - - ImportExportStatement - -
- - - -
- Referenced by: -
- - -====================================================================================================================== -DBMSTableDestinationOption -====================================================================================================================== - - -.. raw:: html - - - - - - REPLACE - - TRUNCATE - - CREATED - - BY - - S_CHAR_LITERAL - -
- - -
         ::= 'REPLACE'
-
           | 'TRUNCATE'
-
           | 'CREATED' 'BY' S_CHAR_LITERAL
-
- Referenced by: -
- - -====================================================================================================================== -DBMSTableDestinationOptionList -====================================================================================================================== - - -.. raw:: html - - - - - - DBMSTableDestinationOption - -
- - -
         ::= DBMSTableDestinationOption+
-
- Referenced by: -
- - -====================================================================================================================== -DBMSSource -====================================================================================================================== - - -.. raw:: html - - - - - - DBMSType - - ConnectionDefinition - TABLE - - Table - - ParenthesedColumnList - - ImportExportStatementsList - -
- - - -
- Referenced by: -
- - -====================================================================================================================== -DBMSType -====================================================================================================================== - - -.. raw:: html - - - - - - EXA - - ORA - - JDBC - - DRIVER - - = - - S_CHAR_LITERAL - -
- -
DBMSType ::= 'EXA'
-
           | 'ORA'
-
           | 'JDBC' ( 'DRIVER' '=' S_CHAR_LITERAL )?
-
- Referenced by: -
- - -====================================================================================================================== -FileType -====================================================================================================================== - - -.. raw:: html - - - - - - CSV - - FBV - - -
- -
FileType ::= 'CSV'
-
           | 'FBV'
-
- Referenced by: -
- - -====================================================================================================================== -ImportExportStatement -====================================================================================================================== - - -.. raw:: html - - - - - - STATEMENT - - S_CHAR_LITERAL - -
- - -
         ::= 'STATEMENT' S_CHAR_LITERAL
-
- - -====================================================================================================================== -ImportExportStatementsList -====================================================================================================================== - - -.. raw:: html - - - - - - ImportExportStatement - -
- - -
         ::= ImportExportStatement+
-
- Referenced by: -
- - -====================================================================================================================== -File -====================================================================================================================== - - -.. raw:: html - - - - - - FILE - - S_CHAR_LITERAL - -
- -
File     ::= 'FILE' S_CHAR_LITERAL
-
- Referenced by: -
- - -====================================================================================================================== -FileList -====================================================================================================================== - - -.. raw:: html - - - - - - File - -
- - -
- - -====================================================================================================================== -ConnectionFileDefinition -====================================================================================================================== - - -.. raw:: html - - - - - - ConnectionOrCloudConnectionDefinition - - FileList - -
- - - -
- Referenced by: -
- - -====================================================================================================================== -ConnectionFileDefinitionList -====================================================================================================================== - - -.. raw:: html - - - - - - ConnectionFileDefinition - -
- - -
         ::= ConnectionFileDefinition+
-
- Referenced by: -
- - -====================================================================================================================== -CSVDestinationColumn -====================================================================================================================== - - -.. raw:: html - - - - - - S_LONG - .. - - S_LONG - FORMAT - - = - - S_CHAR_LITERAL - DELIMIT - - = - - ALWAYS - - NEVER - - AUTO - - -
- - -
         ::= S_LONG ( '..' S_LONG | ( 'FORMAT' '=' S_CHAR_LITERAL )? ( 'DELIMIT' '=' ( 'ALWAYS' | 'NEVER' | 'AUTO' ) )? )
-
- Referenced by: -
- - -====================================================================================================================== -CSVDestinationColumnList -====================================================================================================================== - - -.. raw:: html - - - - - - CSVDestinationColumn - , - - -
- - -
         ::= CSVDestinationColumn ( ',' CSVDestinationColumn )*
-
- Referenced by: -
- - -====================================================================================================================== -CSVSourceColumn -====================================================================================================================== - - -.. raw:: html - - - - - - S_LONG - .. - - S_LONG - FORMAT - - = - - S_CHAR_LITERAL - -
- - -
         ::= S_LONG ( '..' S_LONG | 'FORMAT' '=' S_CHAR_LITERAL )?
-
- Referenced by: -
- - -====================================================================================================================== -CSVSourceColumnList -====================================================================================================================== - - -.. raw:: html - - - - - - CSVSourceColumn - , - - -
- - -
         ::= CSVSourceColumn ( ',' CSVSourceColumn )*
-
- Referenced by: -
- - -====================================================================================================================== -FBVDestinationColumn -====================================================================================================================== - - -.. raw:: html - - - - - - SIZE - - = - - S_LONG - FORMAT - - PADDING - - = - - S_CHAR_LITERAL - ALIGN - - = - - LEFT - - RIGHT - - -
- - -
         ::= 'SIZE' '=' S_LONG
-
           | ( 'FORMAT' | 'PADDING' ) '=' S_CHAR_LITERAL
-
           | 'ALIGN' '=' ( 'LEFT' | 'RIGHT' )
-
- Referenced by: -
- - -====================================================================================================================== -FBVDestinationColumnList -====================================================================================================================== - - -.. raw:: html - - - - - - FBVDestinationColumn - , - - -
- - -
         ::= FBVDestinationColumn ( ','? FBVDestinationColumn )*
-
- Referenced by: -
- - -====================================================================================================================== -FBVSourceColumn -====================================================================================================================== - - -.. raw:: html - - - - - - SIZE - - START - - = - - S_LONG - FORMAT - - PADDING - - = - - S_CHAR_LITERAL - ALIGN - - = - - LEFT - - RIGHT - - -
- - -
         ::= ( 'SIZE' | 'START' ) '=' S_LONG
-
           | ( 'FORMAT' | 'PADDING' ) '=' S_CHAR_LITERAL
-
           | 'ALIGN' '=' ( 'LEFT' | 'RIGHT' )
-
- Referenced by: -
- - -====================================================================================================================== -FBVSourceColumnList -====================================================================================================================== - - -.. raw:: html - - - - - - FBVSourceColumn - , - - -
- - -
         ::= FBVSourceColumn ( ','? FBVSourceColumn )*
-
- Referenced by: -
- - -====================================================================================================================== -FileDestinationOption -====================================================================================================================== - - -.. raw:: html - - - - - - REPLACE - - TRUNCATE - - WITH - - COLUMN - - NAMES - - ENCODING - - NULL - - BOOLEAN - - ROW - - SEPARATOR - - COLUMN - - SEPARATOR - - DELIMITER - - = - - S_CHAR_LITERAL - DELIMIT - - = - - ALWAYS - - NEVER - - AUTO - - -
- - -
         ::= 'REPLACE'
-
           | 'TRUNCATE'
-
           | 'WITH' 'COLUMN' 'NAMES'
-
           | ( 'ENCODING' | 'NULL' | 'BOOLEAN' | 'ROW' 'SEPARATOR' | 'COLUMN' ( 'SEPARATOR' - | 'DELIMITER' ) ) '=' S_CHAR_LITERAL
-
           | 'DELIMIT' '=' ( 'ALWAYS' | 'NEVER' | 'AUTO' )
-
- Referenced by: -
- - -====================================================================================================================== -FileDestinationOptionList -====================================================================================================================== - - -.. raw:: html - - - - - - FileDestinationOption - -
- - -
         ::= FileDestinationOption+
-
- Referenced by: -
- - -====================================================================================================================== -FileSourceOption -====================================================================================================================== - - -.. raw:: html - - - - - - TRIM - - LTRIM - - RTRIM - - ENCODING - - NULL - - COLUMN - - SEPARATOR - - DELIMITER - - = - - S_CHAR_LITERAL - SKIP - - = - - S_LONG - ROW - - SEPARATOR - - = - - S_CHAR_LITERAL - SIZE - - = - - S_LONG - -
- - -
         ::= 'TRIM'
-
           | 'LTRIM'
-
           | 'RTRIM'
-
           | ( 'ENCODING' | 'NULL' | 'COLUMN' ( 'SEPARATOR' | 'DELIMITER' ) ) '=' - S_CHAR_LITERAL
-
           | 'SKIP' '=' S_LONG
-
           | 'ROW' ( 'SEPARATOR' '=' S_CHAR_LITERAL | 'SIZE' '=' S_LONG )
-
- Referenced by: -
- - -====================================================================================================================== -FileSourceOptionList -====================================================================================================================== - - -.. raw:: html - - - - - - FileSourceOption - -
- - -
         ::= FileSourceOption+
-
- Referenced by: -
- - -====================================================================================================================== -FileDestination -====================================================================================================================== - - -.. raw:: html - - - - - - FileType - - ConnectionFileDefinitionList - LOCAL - - SECURE - - FileType - - FileList - ( - - CSVDestinationColumnList - - FBVDestinationColumnList - ) - - FileDestinationOptionList - - CertificateVerification - -
- - - -
- Referenced by: -
- - -====================================================================================================================== -FileSource -====================================================================================================================== - - -.. raw:: html - - - - - - FileType - - ConnectionFileDefinitionList - LOCAL - - SECURE - - FileType - - FileList - ( - - CSVSourceColumnList - - FBVSourceColumnList - ) - - FileSourceOptionList - - CertificateVerification - -
- - - -
- Referenced by: -
- - -====================================================================================================================== -CertificateVerification -====================================================================================================================== - - -.. raw:: html - - - - - - IGNORE - - VERIFY - - CERTIFICATE - - PUBLIC - - KEY - - S_CHAR_LITERAL - PUBLIC - - KEY - - S_CHAR_LITERAL - -
- - -
         ::= ( 'IGNORE' | 'VERIFY' ) 'CERTIFICATE' ( 'PUBLIC' 'KEY' S_CHAR_LITERAL )?
-
           | 'PUBLIC' 'KEY' S_CHAR_LITERAL
-
- - -====================================================================================================================== -ScriptSourceDestination -====================================================================================================================== - - -.. raw:: html - - - - - - SCRIPT - - Table - - ConnectionDefinition - WITH - - RelObjectName - = - - S_CHAR_LITERAL - -
- - -
         ::= 'SCRIPT' Table ConnectionDefinition? ( 'WITH' ( RelObjectName '=' S_CHAR_LITERAL )+ )?
-
- Referenced by: -
- - -====================================================================================================================== -UserIdentification -====================================================================================================================== - - -.. raw:: html - - - - - - USER - - S_CHAR_LITERAL - IDENTIFIED - - BY - - S_CHAR_LITERAL - -
- - -
         ::= 'USER' S_CHAR_LITERAL 'IDENTIFIED' 'BY' S_CHAR_LITERAL
-
- - -====================================================================================================================== -ConnectionDefinition -====================================================================================================================== - - -.. raw:: html - - - - - - AT - - RelObjectName - - S_CHAR_LITERAL - - UserIdentification - - CertificateVerification - -
- - - -
- - -====================================================================================================================== -CloudConnectionDefinition -====================================================================================================================== - - -.. raw:: html - - - - - - AT - - CLOUD - - NONE - - AZURE - - BLOBSTORAGE - - RelObjectName - - S_CHAR_LITERAL - - UserIdentification - -
- - -
         ::= 'AT' 'CLOUD' ( 'NONE' | 'AZURE' 'BLOBSTORAGE' ) ( RelObjectName | S_CHAR_LITERAL ) UserIdentification?
-
- - -====================================================================================================================== -ConnectionOrCloudConnectionDefinition -====================================================================================================================== - - -.. raw:: html - - - - - - CloudConnectionDefinition - - ConnectionDefinition - -
- - -
         ::= CloudConnectionDefinition
-
           | ConnectionDefinition
-
- - -====================================================================================================================== -ErrorClause -====================================================================================================================== - - -.. raw:: html - - - - - - ERRORS - - INTO - - ErrorDestination - ( - - Expression - ) - - REPLACE - - TRUNCATE - - RejectClause - - RejectClause - -
- - -
         ::= 'ERRORS' 'INTO' ErrorDestination ( '(' Expression ')' )? ( 'REPLACE' | 'TRUNCATE' )? RejectClause?
-
           | RejectClause
-
- Referenced by: -
- - -====================================================================================================================== -RejectClause -====================================================================================================================== - - -.. raw:: html - - - - - - REJECT - - LIMIT - - S_LONG - UNLIMITED - - ERRORS - - -
- - -
         ::= 'REJECT' 'LIMIT' ( S_LONG | 'UNLIMITED' ) 'ERRORS'?
-
- Referenced by: -
- - -====================================================================================================================== -ErrorDestination -====================================================================================================================== - - -.. raw:: html - - - - - - CSVFileDestination - - Table - -
- - -
         ::= CSVFileDestination
-
           | Table
-
- Referenced by: -
- - -====================================================================================================================== -CSVFileDestination -====================================================================================================================== - - -.. raw:: html - - - - - - CSV - - ConnectionOrCloudConnectionDefinition - LOCAL - - SECURE - - CSV - - File - -
- - -
         ::= ( 'CSV' ConnectionOrCloudConnectionDefinition | 'LOCAL' 'SECURE'? 'CSV' ) File
-
- Referenced by: -
- - -====================================================================================================================== -Declare -====================================================================================================================== - - -.. raw:: html - - - - - - DECLARE - - UserVariable - TABLE - - ( - - ColumnDefinition - , - - ) - - AS - - RelObjectName - - ColDataType - = - - Expression - - UserVariable - , - - -
- -
Declare  ::= 'DECLARE' UserVariable ( 'TABLE' '(' ColumnDefinition ( ',' ColumnDefinition )* ')' | 'AS' RelObjectName | ColDataType ( '=' Expression )? ( ',' UserVariable ColDataType ( '=' Expression )? )* )
-
- Referenced by: -
- - -====================================================================================================================== -SessionStatement -====================================================================================================================== - - -.. raw:: html - - - - - - SESSION - - BRANCH - - START - - APPLY - - DROP - - SHOW - - DESCRIBE - - S_IDENTIFIER - - S_QUOTED_IDENTIFIER - - S_CHAR_LITERAL - - S_LONG - . - - S_IDENTIFIER - - S_QUOTED_IDENTIFIER - - S_CHAR_LITERAL - - S_LONG - WITH - - S_IDENTIFIER - KEEP - - = - - S_IDENTIFIER - - S_QUOTED_IDENTIFIER - - S_CHAR_LITERAL - - S_LONG - TRUE - - FALSE - - ON - - OFF - - YES - - NO - - , - - -
- - -
         ::= ( 'SESSION' | 'BRANCH' ) ( 'START' | 'APPLY' | 'DROP' | 'SHOW' | 'DESCRIBE' - ) ( ( S_IDENTIFIER | S_QUOTED_IDENTIFIER | S_CHAR_LITERAL | S_LONG ) ( '.' ( S_IDENTIFIER | S_QUOTED_IDENTIFIER | S_CHAR_LITERAL | S_LONG ) )? )? ( 'WITH' ( S_IDENTIFIER | 'KEEP' ) '=' ( S_IDENTIFIER | S_QUOTED_IDENTIFIER | S_CHAR_LITERAL | S_LONG | 'TRUE' | 'FALSE' | 'ON' | 'OFF' | 'YES' | 'NO' ) ( ',' ( S_IDENTIFIER | 'KEEP' ) '=' ( S_IDENTIFIER | S_QUOTED_IDENTIFIER | S_CHAR_LITERAL | S_LONG | 'TRUE' | 'FALSE' | 'ON' | 'OFF' | 'YES' | 'NO' ) )* )?
-
- Referenced by: -
- - -====================================================================================================================== -Set -====================================================================================================================== - - -.. raw:: html - - - - - - SET - - LOCAL - - SESSION - - K_DATETIMELITERAL - ZONE - - UserVariable - - IdentifierChain - = - - Expression - ZONE - - K_DATETIMELITERAL - = - - RelObjectName - , - - -
- -
Set      ::= 'SET' ( 'LOCAL' | 'SESSION' )? ( K_DATETIMELITERAL 'ZONE' | ( UserVariable | IdentifierChain ) '='? ) Expression ( ',' ( K_DATETIMELITERAL 'ZONE' | RelObjectName '='? )? Expression )*
-
- Referenced by: -
- - -====================================================================================================================== -Reset -====================================================================================================================== - - -.. raw:: html - - - - - - RESET - - K_DATETIMELITERAL - ZONE - - RelObjectName - ALL - - -
- -
Reset    ::= 'RESET' ( K_DATETIMELITERAL 'ZONE' | RelObjectName | 'ALL' )
-
- Referenced by: -
- - -====================================================================================================================== -RenameTableStatement -====================================================================================================================== - - -.. raw:: html - - - - - - RENAME - - TABLE - - IF - - EXISTS - - Table - WAIT - - S_LONG - NOWAIT - - TO - - Table - - Table - , - - -
- - -
         ::= 'RENAME' 'TABLE'? ( 'IF' 'EXISTS' )? Table ( 'WAIT' S_LONG | 'NOWAIT' )? 'TO' Table ( ',' Table 'TO' Table )*
-
- Referenced by: -
- - -====================================================================================================================== -PurgeStatement -====================================================================================================================== - - -.. raw:: html - - - - - - PURGE - - TABLE - - Table - INDEX - - Index - RECYCLEBIN - - DBA_RECYCLEBIN - - TABLESPACE - - S_IDENTIFIER - USER - - S_IDENTIFIER - -
- - -
         ::= 'PURGE' ( 'TABLE' Table | 'INDEX' Index | 'RECYCLEBIN' | 'DBA_RECYCLEBIN' | 'TABLESPACE' S_IDENTIFIER ( 'USER' S_IDENTIFIER )? )
-
- Referenced by: -
- - -====================================================================================================================== -Describe -====================================================================================================================== - - -.. raw:: html - - - - - - DESCRIBE - - DESC - - Table - -
- -
Describe ::= ( 'DESCRIBE' | 'DESC' ) Table
-
- Referenced by: -
- - -====================================================================================================================== -Explain -====================================================================================================================== - - -.. raw:: html - - - - - - EXPLAIN - - SUMMARIZE - - ExplainStatementOptions - - WithList - - SelectWithWithItems - - InsertWithWithItems - - UpdateWithWithItems - - DeleteWithWithItems - - Merge - - Table - -
- - -
- Referenced by: -
- - -====================================================================================================================== -ExplainOptionBoolean -====================================================================================================================== - - -.. raw:: html - - - - - - TRUE - - FALSE - - ON - - OFF - - -
- - -
         ::= ( 'TRUE' | 'FALSE' | 'ON' | 'OFF' )?
-
- Referenced by: -
- - -====================================================================================================================== -ExplainFormatOption -====================================================================================================================== - - -.. raw:: html - - - - - - XML - - JSON - - YAML - - -
- - -
         ::= ( 'XML' | 'JSON' | 'YAML' )?
-
- Referenced by: -
- - -====================================================================================================================== -ExplainStatementOptions -====================================================================================================================== - - -.. raw:: html - - - - - - ANALYZE - - BUFFERS - - COSTS - - VERBOSE - - ExplainOptionBoolean - FORMAT - - PLAN - - FOR - - ExplainFormatOption - -
- - -
         ::= ( ( 'ANALYZE' | 'BUFFERS' | 'COSTS' | 'VERBOSE' ) ExplainOptionBoolean | ( 'FORMAT' | 'PLAN' 'FOR'? ) ExplainFormatOption )*
-
- Referenced by: -
- - -====================================================================================================================== -Use -====================================================================================================================== - - -.. raw:: html - - - - - - USE - - SCHEMA - - RelObjectName - -
- -
Use      ::= 'USE' 'SCHEMA'? RelObjectName
-
- Referenced by: -
- - -====================================================================================================================== -Show -====================================================================================================================== - - -.. raw:: html - - - - - - SHOW - - ShowColumns - - ShowIndex - - ShowTables - - captureRest - -
- -
Show     ::= 'SHOW' ( ShowColumns | ShowIndex | ShowTables | captureRest )
-
- Referenced by: -
- - -====================================================================================================================== -ShowColumns -====================================================================================================================== - - -.. raw:: html - - - - - - COLUMNS - - FROM - - RelObjectName - -
- - -
         ::= 'COLUMNS' 'FROM' RelObjectName
-
- Referenced by: -
- - -====================================================================================================================== -ShowIndex -====================================================================================================================== - - -.. raw:: html - - - - - - INDEX - - FROM - - RelObjectName - -
- - -
         ::= 'INDEX' 'FROM' RelObjectName
-
- Referenced by: -
- - -====================================================================================================================== -RefreshMaterializedView -====================================================================================================================== - - -.. raw:: html - - - - - - REFRESH - - MATERIALIZED - - VIEW - - CONCURRENTLY - - Table - WITH - - NO - - DATA - - captureRest - -
- - -
         ::= 'REFRESH' 'MATERIALIZED' 'VIEW' 'CONCURRENTLY'? Table ( 'WITH' 'NO'? 'DATA' )? captureRest
-
- Referenced by: -
- - -====================================================================================================================== -ShowTables -====================================================================================================================== - - -.. raw:: html - - - - - - EXTENDED - - FULL - - TABLES - - FROM - - IN - - RelObjectName - LIKE - - SimpleExpression - WHERE - - Expression - -
- - -
         ::= 'EXTENDED'? 'FULL'? 'TABLES' ( ( 'FROM' | 'IN' ) RelObjectName )? ( 'LIKE' SimpleExpression | 'WHERE' Expression )?
-
- Referenced by: -
- - -====================================================================================================================== -Values -====================================================================================================================== - - -.. raw:: html - - - - - - VALUES - - VALUE - - ExpressionList - -
- -
Values   ::= ( 'VALUES' | 'VALUE' ) ExpressionList
-
- Referenced by: -
- - -====================================================================================================================== -ReturningClause -====================================================================================================================== - - -.. raw:: html - - - - - - RETURNING - - RETURN - - ReturningOutputAliasList - - SelectItemsList - INTO - - Table - - UserVariable - , - - -
- - -
         ::= ( 'RETURNING' | 'RETURN' ) ReturningOutputAliasList? SelectItemsList ( 'INTO' ( Table | UserVariable ) ( ',' ( Table | UserVariable ) )* )?
-
- Referenced by: -
- - -====================================================================================================================== -ReturningReferenceKind -====================================================================================================================== - - -.. raw:: html - - - - - - RelObjectName - -
- - -
         ::= RelObjectName
-
- Referenced by: -
- - -====================================================================================================================== -ReturningOutputAliasDefinition -====================================================================================================================== - - -.. raw:: html - - - - - - ReturningReferenceKind - AS - - RelObjectName - -
- - -
         ::= ReturningReferenceKind 'AS' RelObjectName
-
- Referenced by: -
- - -====================================================================================================================== -ReturningOutputAliasList -====================================================================================================================== - - -.. raw:: html - - - - - - WITH - - ( - - ReturningOutputAliasDefinition - , - - ) - - -
- - -
         ::= 'WITH' '(' ReturningOutputAliasDefinition ( ',' ReturningOutputAliasDefinition )* ')'
-
- Referenced by: -
- - -====================================================================================================================== -UpdateWithWithItems -====================================================================================================================== - - -.. raw:: html - - - - - - Update - -
- - -
         ::= Update
-
- Referenced by: -
- - -====================================================================================================================== -Update -====================================================================================================================== - - -.. raw:: html - - - - - - UPDATE - - LOW_PRIORITY - - IGNORE - - TableWithAliasAndMysqlIndexHint - - JoinsList - SET - - UpdateSets - - OutputClause - FROM - - FromItem - - JoinsList - - WhereClause - - PreferringClause - - OrderByElements - - PlainLimit - - ReturningClause - -
- - -
- - -====================================================================================================================== -UpdateSets -====================================================================================================================== - - -.. raw:: html - - - - - - Column - = - - Expression - - ParenthesedExpressionList - = - - ParenthesedSelect - - ParenthesedExpressionList - , - - Column - = - - Expression - - ParenthesedExpressionList - = - - ParenthesedSelect - - ParenthesedExpressionList - -
- - - -
- - -====================================================================================================================== -Partitions -====================================================================================================================== - - -.. raw:: html - - - - - - Column - = - - Expression - , - - -
- - -
         ::= Column ( '=' Expression )? ( ',' Column ( '=' Expression )? )*
-
- Referenced by: -
- - -====================================================================================================================== -InsertWithWithItems -====================================================================================================================== - - -.. raw:: html - - - - - - Insert - -
- - -
         ::= Insert
-
- Referenced by: -
- - -====================================================================================================================== -Insert -====================================================================================================================== - - -.. raw:: html - - - - - - INSERT - - LOW_PRIORITY - - DELAYED - - HIGH_PRIORITY - - IGNORE - - ALL - - FIRST - - OracleMultiInsertClause - - OracleMultiInsertWhenBranch - - OracleMultiInsertElseBranch - - Select - OVERWRITE - - TABLE - - INTO - - TABLE - - Table - PARTITION - - ( - - Partitions - ) - - AS - - RelObjectName - ( - - ColumnList - ) - - OVERRIDING - - SYSTEM - - VALUE - - OutputClause - DEFAULT - - VALUES - - SET - - UpdateSets - - Select - - Alias - ON - - DUPLICATE - - KEY - - UPDATE - - InsertDuplicateAction - ON - - CONFLICT - - InsertConflictTarget - - InsertConflictAction - - ReturningClause - -
- -
Insert   ::= 'INSERT' ( 'LOW_PRIORITY' | 'DELAYED' | 'HIGH_PRIORITY' )? 'IGNORE'? ( ( 'ALL' - | 'FIRST' ) ( OracleMultiInsertClause+ | OracleMultiInsertWhenBranch+ OracleMultiInsertElseBranch? ) Select | ( 'OVERWRITE' 'TABLE' | 'INTO' 'TABLE'? )? Table ( 'PARTITION' '(' Partitions ')' )? ( 'AS'? RelObjectName )? ( '(' ColumnList ')' )? ( 'OVERRIDING' 'SYSTEM' 'VALUE' )? OutputClause? ( 'DEFAULT' 'VALUES' | 'SET' UpdateSets | Select ) Alias? ( 'ON' 'DUPLICATE' 'KEY' 'UPDATE' InsertDuplicateAction )? ( 'ON' 'CONFLICT' InsertConflictTarget? InsertConflictAction )? ReturningClause? )
-
- - -====================================================================================================================== -OracleMultiInsertClause -====================================================================================================================== - - -.. raw:: html - - - - - - INTO - - Table - ( - - ColumnList - ) - - Select - -
- - -
         ::= 'INTO' Table ( '(' ColumnList ')' )? Select
-
- - -====================================================================================================================== -OracleMultiInsertWhenBranch -====================================================================================================================== - - -.. raw:: html - - - - - - WHEN - - Expression - THEN - - OracleMultiInsertClauseList - -
- - -
         ::= 'WHEN' Expression 'THEN' OracleMultiInsertClauseList
-
- Referenced by: -
- - -====================================================================================================================== -OracleMultiInsertElseBranch -====================================================================================================================== - - -.. raw:: html - - - - - - ELSE - - OracleMultiInsertClauseList - -
- - -
         ::= 'ELSE' OracleMultiInsertClauseList
-
- Referenced by: -
- - -====================================================================================================================== -OracleMultiInsertClauseList -====================================================================================================================== - - -.. raw:: html - - - - - - OracleMultiInsertClause - -
- - -
         ::= OracleMultiInsertClause+
-
- - -====================================================================================================================== -InsertConflictTarget -====================================================================================================================== - - -.. raw:: html - - - - - - ( - - RelObjectNameExt - , - - ) - - WhereClause - ON - - CONSTRAINT - - RelObjectNameExt - -
- - -
         ::= '(' RelObjectNameExt ( ',' RelObjectNameExt )* ')' WhereClause?
-
           | 'ON' 'CONSTRAINT' RelObjectNameExt
-
- Referenced by: -
- - -====================================================================================================================== -InsertConflictAction -====================================================================================================================== - - -.. raw:: html - - - - - - DO - - NOTHING - - UPDATE - - SET - - UpdateSets - - WhereClause - -
- - -
         ::= 'DO' ( 'NOTHING' | 'UPDATE' 'SET' UpdateSets WhereClause? )
-
- Referenced by: -
- - -====================================================================================================================== -InsertDuplicateAction -====================================================================================================================== - - -.. raw:: html - - - - - - NOTHING - - UpdateSets - - WhereClause - -
- - -
         ::= 'NOTHING'
-
           | UpdateSets WhereClause?
-
- Referenced by: -
- - -====================================================================================================================== -OutputClause -====================================================================================================================== - - -.. raw:: html - - - - - - OUTPUT - - SelectItemsList - INTO - - UserVariable - - Table - - ColumnsNamesList - -
- - -
         ::= 'OUTPUT' SelectItemsList ( 'INTO' ( UserVariable | Table ) ColumnsNamesList? )?
-
- Referenced by: -
- - -====================================================================================================================== -Upsert -====================================================================================================================== - - -.. raw:: html - - - - - - UPSERT - - INSERT - - OR - - REPLACE - - INTO - - Table - - ParenthesedColumnList - SET - - UpdateSets - - Select - ON - - DUPLICATE - - KEY - - UPDATE - - InsertDuplicateAction - -
- -
Upsert   ::= ( 'UPSERT' | ( 'INSERT' 'OR' )? 'REPLACE' ) 'INTO'? Table ParenthesedColumnList? ( 'SET' UpdateSets | Select ) ( 'ON' 'DUPLICATE' 'KEY' 'UPDATE' InsertDuplicateAction )?
-
- Referenced by: -
- - -====================================================================================================================== -DeleteWithWithItems -====================================================================================================================== - - -.. raw:: html - - - - - - Delete - -
- - -
         ::= Delete
-
- Referenced by: -
- - -====================================================================================================================== -Delete -====================================================================================================================== - - -.. raw:: html - - - - - - DELETE - - LOW_PRIORITY - - QUICK - - IGNORE - - TableWithAlias - , - - OutputClause - FROM - - TableWithAlias - - JoinsList - USING - - FromItem - , - - WhereClause - - PreferringClause - - OrderByElements - - PlainLimit - - ReturningClause - -
- -
Delete   ::= 'DELETE' 'LOW_PRIORITY'? 'QUICK'? 'IGNORE'? ( ( TableWithAlias ( ',' TableWithAlias )* OutputClause? )? 'FROM' )? ( TableWithAlias JoinsList? )? ( 'USING' FromItem ( ',' FromItem )* )? WhereClause? PreferringClause? OrderByElements? PlainLimit? ReturningClause?
-
- - -====================================================================================================================== -Merge -====================================================================================================================== - - -.. raw:: html - - - - - - MERGE - - INTO - - TableWithAlias - USING - - FromItem - ON - - Expression - - MergeOperations - - OutputClause - -
- -
Merge    ::= 'MERGE' 'INTO' TableWithAlias 'USING' FromItem 'ON' Expression MergeOperations OutputClause?
-
- Referenced by: -
- - -====================================================================================================================== -MergeOperations -====================================================================================================================== - - -.. raw:: html - - - - - - MergeWhenMatched - - MergeWhenNotMatched - -
- - -
         ::= ( MergeWhenMatched | MergeWhenNotMatched )*
-
- Referenced by: -
- - -====================================================================================================================== -MergeWhenMatched -====================================================================================================================== - - -.. raw:: html - - - - - - WHEN - - MATCHED - - AND - - Expression - THEN - - DELETE - - MergeUpdateClause - -
- - -
         ::= 'WHEN' 'MATCHED' ( 'AND' Expression )? 'THEN' ( 'DELETE' | MergeUpdateClause )
-
- Referenced by: -
- - -====================================================================================================================== -MergeUpdateClause -====================================================================================================================== - - -.. raw:: html - - - - - - UPDATE - - SET - - UpdateSets - WHERE - - Expression - DELETE - - WHERE - - Expression - -
- - -
         ::= 'UPDATE' 'SET' UpdateSets ( 'WHERE' Expression )? ( 'DELETE' 'WHERE' Expression )?
-
- Referenced by: -
- - -====================================================================================================================== -MergeWhenNotMatched -====================================================================================================================== - - -.. raw:: html - - - - - - WHEN - - NOT - - MATCHED - - AND - - Expression - THEN - - INSERT - - ( - - ColumnList - ) - - VALUES - - ( - - SimpleExpressionList - ) - - WHERE - - Expression - -
- - -
         ::= 'WHEN' 'NOT' 'MATCHED' ( 'AND' Expression )? 'THEN' 'INSERT' ( '(' ColumnList ')' )? 'VALUES' '(' SimpleExpressionList ')' ( 'WHERE' Expression )?
-
- Referenced by: -
- - -====================================================================================================================== -RelObjectNames -====================================================================================================================== - - -.. raw:: html - - - - - - RelObjectName - ... - - .. - - . - - : - - RelObjectNameExt - -
- - -
         ::= RelObjectName ( ( '...' | '..' | '.' | ':' ) RelObjectNameExt )*
-
- - -====================================================================================================================== -ColumnIdentifier -====================================================================================================================== - - -.. raw:: html - - - - - - RelObjectName - ... - - .. - - . - - RelObjectNameExt - -
- - -
         ::= RelObjectName ( ( '...' | '..' | '.' ) RelObjectNameExt )*
-
- Referenced by: -
- - -====================================================================================================================== -Column -====================================================================================================================== - - -.. raw:: html - - - - - - ColumnIdentifier - COMMENT - - S_CHAR_LITERAL - . - - K_NEXTVAL - - ArrayConstructor - -
- - -
- - -====================================================================================================================== -RelObjectName -====================================================================================================================== - - -.. raw:: html - - - - - - DATA_TYPE - - S_IDENTIFIER - - S_QUOTED_IDENTIFIER - - K_DATETIMELITERAL - - K_DATE_LITERAL - - NonReservedWord - ALL - - ANY - - CASEWHEN - - CONNECT - - CREATE - - DEFAULT - - GLOBAL - - GROUP - - GROUPING - - IF - - IIF - - IGNORE - - IN - - INTERVAL - - LEFT - - LIMIT - - K_NEXTVAL - OFFSET - - ON - - OPTIMIZE - - ORDER - - PROCEDURE - - PUBLIC - - QUALIFY - - RIGHT - - SET - - SOME - - START - - TABLES - - TOP - - VALUE - - VALUES - - -
- - -
         ::= DATA_TYPE
-
           | S_IDENTIFIER
-
           | S_QUOTED_IDENTIFIER
-
           | K_DATETIMELITERAL
-
           | K_DATE_LITERAL
-
           | NonReservedWord
-
           | 'ALL'
-
           | 'ANY'
-
           | 'CASEWHEN'
-
           | 'CONNECT'
-
           | 'CREATE'
-
           | 'DEFAULT'
-
           | 'GLOBAL'
-
           | 'GROUP'
-
           | 'GROUPING'
-
           | 'IF'
-
           | 'IIF'
-
           | 'IGNORE'
-
           | 'IN'
-
           | 'INTERVAL'
-
           | 'LEFT'
-
           | 'LIMIT'
-
           | K_NEXTVAL
-
           | 'OFFSET'
-
           | 'ON'
-
           | 'OPTIMIZE'
-
           | 'ORDER'
-
           | 'PROCEDURE'
-
           | 'PUBLIC'
-
           | 'QUALIFY'
-
           | 'RIGHT'
-
           | 'SET'
-
           | 'SOME'
-
           | 'START'
-
           | 'TABLES'
-
           | 'TOP'
-
           | 'VALUE'
-
           | 'VALUES'
-
- - -====================================================================================================================== -RelObjectNameExt -====================================================================================================================== - - -.. raw:: html - - - - - - RelObjectName - FROM - - K_SELECT - CURRENT - - -
- - -
         ::= RelObjectName
-
           | 'FROM'
-
           | K_SELECT
-
           | 'CURRENT'
-
- - -====================================================================================================================== -Table -====================================================================================================================== - - -.. raw:: html - - - - - - RelObjectNames - - TimeTravelBeforeAlias - - S_CHAR_LITERAL - -
- - -
           | S_CHAR_LITERAL
-
- - -====================================================================================================================== -TableWithAlias -====================================================================================================================== - - -.. raw:: html - - - - - - Table - - Alias - -
- - -
         ::= Table Alias?
-
- Referenced by: -
- - -====================================================================================================================== -TableWithAliasAndMysqlIndexHint -====================================================================================================================== - - -.. raw:: html - - - - - - Table - - Alias - - MySQLIndexHint - -
- - -
         ::= Table Alias? MySQLIndexHint?
-
- Referenced by: -
- - -====================================================================================================================== -Number -====================================================================================================================== - - -.. raw:: html - - - - - - S_DOUBLE - - S_LONG - -
- -
Number   ::= S_DOUBLE
-
           | S_LONG
-
- Referenced by: -
- - -====================================================================================================================== -SampleClause -====================================================================================================================== - - -.. raw:: html - - - - - - SAMPLE - - BLOCK - - TABLESAMPLE - - USING - - SAMPLE - - SYSTEM - - BERNOULLI - - ( - - Number - % - - PERCENT - - ROWS - - ) - - REPEATABLE - - ( - - Number - ) - - SEED - - ( - - Number - ) - - Number - OFFSET - - Number - -
- - -
         ::= ( 'SAMPLE' 'BLOCK'? | ( 'TABLESAMPLE' | 'USING' 'SAMPLE' ) ( 'SYSTEM' - | 'BERNOULLI' ) ) ( '(' Number ( '%' | 'PERCENT' | 'ROWS' )? ')' ( 'REPEATABLE' '(' Number ')' )? ( 'SEED' '(' Number ')' )? | Number ( 'OFFSET' Number )? )
-
- Referenced by: -
- - -====================================================================================================================== -SelectWithWithItems -====================================================================================================================== - - -.. raw:: html - - - - - - Select - -
- - -
         ::= Select
-
- Referenced by: -
- - -====================================================================================================================== -Select -====================================================================================================================== - - -.. raw:: html - - - - - - WithList - - FromQuery - - PlainSelect - - Values - - ParenthesedSelect - - Alias - - FromQueryFromSelect - - SetOperationList - - OrderByElements - - LimitWithOffset - - Offset - - Fetch - - WithIsolation - -
- - -
- - -====================================================================================================================== -FromQuery -====================================================================================================================== - - -.. raw:: html - - - - - - FROM - - FromItem - - LateralViews - - JoinsList - |> - - PipeOperator - -
- - -
         ::= 'FROM' FromItem LateralViews? JoinsList? ( '|>' PipeOperator )*
-
- Referenced by: -
- - -====================================================================================================================== -FromQueryFromSelect -====================================================================================================================== - - -.. raw:: html - - - - - - |> - - PipeOperator - -
- - -
         ::= ( '|>' PipeOperator )+
-
- Referenced by: -
- - -====================================================================================================================== -PipeOperator -====================================================================================================================== - - -.. raw:: html - - - - - - SelectPipeOperator - - SetPipeOperator - - DropPipeOperator - - AsPipeOperator - - WherePipeOperator - - LimitPipeOperator - - AggregatePipeOperator - - OrderByPipeOperator - - SetOperationPipeOperator - - JoinPipeOperator - - CallPipeOperator - - TableSamplePipeOperator - - PivotPipeOperator - - UnPivotPipeOperator - -
- - -
         ::= SelectPipeOperator
-
           | SetPipeOperator
-
           | DropPipeOperator
-
           | AsPipeOperator
-
           | WherePipeOperator
-
           | LimitPipeOperator
-
           | AggregatePipeOperator
-
           | OrderByPipeOperator
-
           | SetOperationPipeOperator
-
           | JoinPipeOperator
-
           | CallPipeOperator
-
           | TableSamplePipeOperator
-
           | PivotPipeOperator
-
           | UnPivotPipeOperator
-
- Referenced by: -
- - -====================================================================================================================== -SelectPipeOperator -====================================================================================================================== - - -.. raw:: html - - - - - - K_SELECT - DISTINCT - - ALL - - EXTEND - - WINDOW - - RENAME - - SelectItem - , - - -
- - -
         ::= ( K_SELECT ( 'DISTINCT' | 'ALL' )? | 'EXTEND' | 'WINDOW' | 'RENAME' ) SelectItem ( ',' SelectItem )*
-
- Referenced by: -
- - -====================================================================================================================== -WherePipeOperator -====================================================================================================================== - - -.. raw:: html - - - - - - WHERE - - Expression - -
- - -
         ::= 'WHERE' Expression
-
- Referenced by: -
- - -====================================================================================================================== -OrderSuffix -====================================================================================================================== - - -.. raw:: html - - - - - - ASC - - DESC - - NULLS - - FIRST - - LAST - - -
- - -
         ::= ( 'ASC' | 'DESC' ) ( 'NULLS' ( 'FIRST' | 'LAST' ) )?
-
- Referenced by: -
- - -====================================================================================================================== -AggregatePipeOperator -====================================================================================================================== - - -.. raw:: html - - - - - - AGGREGATE - - SelectItem - - OrderSuffix - , - - GROUP - - AND - - ORDER - - BY - - SelectItem - - OrderSuffix - , - - -
- - -
         ::= 'AGGREGATE' SelectItem OrderSuffix? ( ',' SelectItem OrderSuffix? )* ( 'GROUP' ( 'AND' 'ORDER' )? 'BY' SelectItem OrderSuffix? ( ',' SelectItem OrderSuffix? )* )?
-
- Referenced by: -
- - -====================================================================================================================== -OrderByPipeOperator -====================================================================================================================== - - -.. raw:: html - - - - - - OrderByElements - -
- - -
         ::= OrderByElements
-
- Referenced by: -
- - -====================================================================================================================== -AsPipeOperator -====================================================================================================================== - - -.. raw:: html - - - - - - AS - - Alias - -
- - -
         ::= 'AS' Alias
-
- Referenced by: -
- - -====================================================================================================================== -JoinPipeOperator -====================================================================================================================== - - -.. raw:: html - - - - - - JoinerExpression - -
- - -
         ::= JoinerExpression
-
- Referenced by: -
- - -====================================================================================================================== -SetPipeOperator -====================================================================================================================== - - -.. raw:: html - - - - - - SET - - UpdateSets - -
- - -
         ::= 'SET' UpdateSets
-
- Referenced by: -
- - -====================================================================================================================== -DropPipeOperator -====================================================================================================================== - - -.. raw:: html - - - - - - DROP - - ColumnList - -
- - -
         ::= 'DROP' ColumnList
-
- Referenced by: -
- - -====================================================================================================================== -LimitPipeOperator -====================================================================================================================== - - -.. raw:: html - - - - - - LIMIT - - Expression - OFFSET - - Expression - -
- - -
         ::= 'LIMIT' Expression ( 'OFFSET' Expression )?
-
- Referenced by: -
- - -====================================================================================================================== -SetOperationModifier -====================================================================================================================== - - -.. raw:: html - - - - - - ALL - - DISTINCT - - BY - - NAME - - MATCHING - - ( - - RelObjectName - , - - ) - - STRICT - - CORRESPONDING - - ALL - - DISTINCT - - BY - - ALL - - DISTINCT - - ( - - RelObjectName - , - - ) - - ALL - - DISTINCT - - -
- - -
         ::= ( 'ALL' | 'DISTINCT' )? 'BY' 'NAME' ( 'MATCHING' '(' RelObjectName ( ',' RelObjectName )* ')' )?
-
           | 'STRICT'? 'CORRESPONDING' ( 'ALL' | 'DISTINCT' )? ( 'BY' ( 'ALL' | 'DISTINCT' - )? '(' RelObjectName ( ',' RelObjectName )* ')' )?
-
           | 'ALL'
-
           | 'DISTINCT'
-
- - -====================================================================================================================== -SetOperationPipeOperator -====================================================================================================================== - - -.. raw:: html - - - - - - UNION - - INTERSECT - - EXCEPT - - SetOperationModifier - - ParenthesedSelect - , - - -
- - -
         ::= ( 'UNION' | 'INTERSECT' | 'EXCEPT' ) SetOperationModifier? ParenthesedSelect ( ',' ParenthesedSelect )*
-
- Referenced by: -
- - -====================================================================================================================== -CallPipeOperator -====================================================================================================================== - - -.. raw:: html - - - - - - CALL - - TableFunction - - Alias - -
- - -
         ::= 'CALL' TableFunction Alias?
-
- Referenced by: -
- - -====================================================================================================================== -TableSamplePipeOperator -====================================================================================================================== - - -.. raw:: html - - - - - - TABLESAMPLE - - SYSTEM - - ( - - S_DOUBLE - - S_LONG - PERCENT - - ) - - -
- - -
         ::= 'TABLESAMPLE' 'SYSTEM' '(' ( S_DOUBLE | S_LONG ) 'PERCENT' ')'
-
- Referenced by: -
- - -====================================================================================================================== -PivotPipeOperator -====================================================================================================================== - - -.. raw:: html - - - - - - PIVOT - - ( - - Function - FOR - - Column - IN - - ( - - SelectItemsList - ) - - ) - - Alias - -
- - -
         ::= 'PIVOT' '(' Function 'FOR' Column 'IN' '(' SelectItemsList ')' ')' Alias?
-
- Referenced by: -
- - -====================================================================================================================== -UnPivotPipeOperator -====================================================================================================================== - - -.. raw:: html - - - - - - UNPIVOT - - ( - - Column - FOR - - Column - IN - - ( - - SelectItemsList - ) - - ) - - Alias - -
- - -
         ::= 'UNPIVOT' '(' Column 'FOR' Column 'IN' '(' SelectItemsList ')' ')' Alias?
-
- Referenced by: -
- - -====================================================================================================================== -TableStatement -====================================================================================================================== - - -.. raw:: html - - - - - - TABLE - - Table - - OrderByElements - - LimitWithOffset - - Offset - -
- - -
         ::= 'TABLE' Table OrderByElements? LimitWithOffset? Offset?
-
- Referenced by: -
- - -====================================================================================================================== -ParenthesedSelect -====================================================================================================================== - - -.. raw:: html - - - - - - ( - - Select - ) - - -
- - -
         ::= '(' Select ')'
-
- - -====================================================================================================================== -ParenthesedInsert -====================================================================================================================== - - -.. raw:: html - - - - - - ( - - Insert - ) - - -
- - -
         ::= '(' Insert ')'
-
- Referenced by: -
- - -====================================================================================================================== -ParenthesedUpdate -====================================================================================================================== - - -.. raw:: html - - - - - - ( - - Update - ) - - -
- - -
         ::= '(' Update ')'
-
- Referenced by: -
- - -====================================================================================================================== -ParenthesedDelete -====================================================================================================================== - - -.. raw:: html - - - - - - ( - - Delete - ) - - -
- - -
         ::= '(' Delete ')'
-
- Referenced by: -
- - -====================================================================================================================== -LateralView -====================================================================================================================== - - -.. raw:: html - - - - - - LATERAL - - VIEW - - OUTER - - Function - - RelObjectName - AS - - RelObjectName - , - - RelObjectName - -
- - -
         ::= 'LATERAL' 'VIEW' 'OUTER'? Function RelObjectName? 'AS' RelObjectName ( ',' RelObjectName )?
-
- Referenced by: -
- - -====================================================================================================================== -ForClause -====================================================================================================================== - - -.. raw:: html - - - - - - FOR - - BROWSE - - XML - - RAW - - ( - - S_CHAR_LITERAL - ) - - AUTO - - , - - BINARY - - BASE64 - - TYPE - - ROOT - - XMLSCHEMA - - ( - - S_CHAR_LITERAL - ) - - XMLDATA - - ELEMENTS - - XSINIL - - ABSENT - - EXPLICIT - - , - - BINARY - - BASE64 - - TYPE - - ROOT - - ( - - S_CHAR_LITERAL - ) - - XMLDATA - - PATH - - ( - - S_CHAR_LITERAL - ) - - , - - BINARY - - BASE64 - - TYPE - - ROOT - - ( - - S_CHAR_LITERAL - ) - - ELEMENTS - - XSINIL - - ABSENT - - JSON - - AUTO - - PATH - - , - - ROOT - - ( - - S_CHAR_LITERAL - ) - - INCLUDE_NULL_VALUES - - WITHOUT_ARRAY_WRAPPER - - -
- - -
         ::= 'FOR' ( 'BROWSE' | 'XML' ( ( 'RAW' ( '(' S_CHAR_LITERAL ')' )? | 'AUTO' ) ( ',' ( 'BINARY' 'BASE64' | 'TYPE' | ( 'ROOT' | 'XMLSCHEMA' ) ( - '(' S_CHAR_LITERAL ')' )? | 'XMLDATA' | 'ELEMENTS' ( 'XSINIL' | 'ABSENT' )? ) )* | 'EXPLICIT' ( ',' - ( 'BINARY' 'BASE64' | 'TYPE' | 'ROOT' ( '(' S_CHAR_LITERAL ')' )? | 'XMLDATA' ) )* | 'PATH' ( '(' S_CHAR_LITERAL ')' )? ( ',' ( 'BINARY' 'BASE64' | 'TYPE' | 'ROOT' ( '(' S_CHAR_LITERAL ')' )? | 'ELEMENTS' ( 'XSINIL' | 'ABSENT' )? ) )* ) | 'JSON' ( 'AUTO' | 'PATH' ) - ( ',' ( 'ROOT' ( '(' S_CHAR_LITERAL ')' )? | 'INCLUDE_NULL_VALUES' | 'WITHOUT_ARRAY_WRAPPER' ) )* )
-
- Referenced by: -
- - -====================================================================================================================== -LateralViews -====================================================================================================================== - - -.. raw:: html - - - - - - LateralView - -
- - -
         ::= LateralView+
-
- Referenced by: -
- - -====================================================================================================================== -LateralSubSelect -====================================================================================================================== - - -.. raw:: html - - - - - - LATERAL - - ( - - Select - ) - - -
- - -
         ::= 'LATERAL' '(' Select ')'
-
- Referenced by: -
- - -====================================================================================================================== -PlainSelect -====================================================================================================================== - - -.. raw:: html - - - - - - K_SELECT - STRAIGHT_JOIN - - Skip - - First - - Top - ALL - - DISTINCT - - ON - - ( - - SelectItemsList - ) - - DISTINCTROW - - UNIQUE - - SQL_CALC_FOUND_ROWS - - SQL_NO_CACHE - - SQL_CACHE - - AS - - STRUCT - - VALUE - - Top - - SelectItemsList - - MySqlSelectIntoClause - - IntoClause - FROM - - FromItem - - LateralViews - - JoinsList - FROM - - ONLY - - FromItem - - LateralViews - - JoinsList - FINAL - - KSQLWindowClause - - PreWhereClause - - WhereClause - - OracleHierarchicalQueryClause - - PreferringClause - PARTITION - - BY - - ComplexExpressionList - ( - - ComplexExpressionList - ) - - Having - - GroupByColumnReferences - - Having - - Qualify - - OrderByElements - WINDOW - - RelObjectName - AS - - windowDefinition - , - - OrderByElements - - ForClause - EMIT - - CHANGES - - LimitBy - - LimitWithOffset - - Offset - - LimitWithOffset - - Fetch - - WithIsolation - FOR - - NO - - KEY - - UPDATE - - KEY - - SHARE - - READ - - FETCH - - ONLY - - OF - - Table - - Wait - NOWAIT - - SKIP - - LOCKED - - MySqlSelectIntoClause - SETTINGS - - UpdateSets - - OptimizeFor - INTO - - TEMP - - Table - WITH - - NO - - LOG - - -
- - -
         ::= K_SELECT 'STRAIGHT_JOIN'? Skip? First? Top? ( 'ALL' | 'DISTINCT' ( 'ON' '(' SelectItemsList ')' )? | 'DISTINCTROW' | 'UNIQUE' | 'SQL_CALC_FOUND_ROWS' | 'SQL_NO_CACHE' | 'SQL_CACHE' - )? ( 'AS' ( 'STRUCT' | 'VALUE' ) )? Top? SelectItemsList MySqlSelectIntoClause? IntoClause? ( 'FROM' FromItem LateralViews? JoinsList? )? ( 'FROM' 'ONLY' FromItem LateralViews? JoinsList? )? 'FINAL'? KSQLWindowClause? PreWhereClause? WhereClause? OracleHierarchicalQueryClause? ( PreferringClause ( 'PARTITION' 'BY' ( ComplexExpressionList | '(' ComplexExpressionList ')' ) )? )? Having? GroupByColumnReferences? Having? Qualify? OrderByElements? ( 'WINDOW' RelObjectName 'AS' windowDefinition ( ',' RelObjectName 'AS' windowDefinition )* )? OrderByElements? ForClause? ( 'EMIT' 'CHANGES' )? LimitBy? LimitWithOffset? Offset? LimitWithOffset? Fetch? WithIsolation? ( 'FOR' ( ( 'NO' 'KEY' )? 'UPDATE' | 'KEY'? 'SHARE' | ( 'READ' | 'FETCH' ) 'ONLY' - ) ( 'OF' Table )? Wait? ( 'NOWAIT' | 'SKIP' 'LOCKED' )? )? MySqlSelectIntoClause? ( 'SETTINGS' UpdateSets )? OptimizeFor? ( 'INTO' 'TEMP' Table )? ( 'WITH' 'NO' 'LOG' )?
-
- Referenced by: -
- - -====================================================================================================================== -SetOperationList -====================================================================================================================== - - -.. raw:: html - - - - - - UNION - - INTERSECT - - MINUS - - EXCEPT - - SetOperationModifier - - PlainSelect - - Values - - ParenthesedSelect - - OrderByElements - - LimitWithOffset - - Offset - - LimitWithOffset - - Fetch - - WithIsolation - -
- - -
         ::= ( ( 'UNION' | 'INTERSECT' | 'MINUS' | 'EXCEPT' ) SetOperationModifier? ( PlainSelect | Values | ParenthesedSelect ) )+ OrderByElements? LimitWithOffset? Offset? LimitWithOffset? Fetch? WithIsolation?
-
- Referenced by: -
- - -====================================================================================================================== -WithList -====================================================================================================================== - - -.. raw:: html - - - - - - WITH - - WithItem - , - - -
- -
WithList ::= 'WITH' WithItem ( ',' WithItem )*
-
- Referenced by: -
- - -====================================================================================================================== -WithItem -====================================================================================================================== - - -.. raw:: html - - - - - - FUNCTION - - WithFunctionDeclaration - RECURSIVE - - RelObjectName - ( - - SelectItemsList - ) - - AS - - NOT - - MATERIALIZED - - ParenthesedSelect - - ParenthesedInsert - - ParenthesedUpdate - - ParenthesedDelete - - WithSearchClause - -
- -
WithItem ::= ( 'FUNCTION' WithFunctionDeclaration | 'RECURSIVE'? RelObjectName ( '(' SelectItemsList ')' )? 'AS' ( 'NOT'? 'MATERIALIZED' )? ( ParenthesedSelect | ParenthesedInsert | ParenthesedUpdate | ParenthesedDelete ) ) WithSearchClause?
-
- Referenced by: -
- - -====================================================================================================================== -WithSearchClause -====================================================================================================================== - - -.. raw:: html - - - - - - SEARCH - - BREADTH - - DEPTH - - FIRST - - BY - - Column - , - - SET - - RelObjectName - -
- - -
         ::= 'SEARCH' ( 'BREADTH' | 'DEPTH' ) 'FIRST' 'BY' Column ( ',' Column )* 'SET' RelObjectName
-
- Referenced by: -
- - -====================================================================================================================== -WithFunctionDeclaration -====================================================================================================================== - - -.. raw:: html - - - - - - RelObjectName - ( - - WithFunctionParameter - , - - ) - - RETURNS - - RelObjectName - RETURN - - Expression - -
- - -
         ::= RelObjectName '(' ( WithFunctionParameter ( ',' WithFunctionParameter )* )? ')' 'RETURNS' RelObjectName 'RETURN' Expression
-
- Referenced by: -
- - -====================================================================================================================== -WithFunctionParameter -====================================================================================================================== - - -.. raw:: html - - - - - - RelObjectName - ARRAY - - < - - RelObjectName - > - - RelObjectName - -
- - -
         ::= RelObjectName ( 'ARRAY' '<' RelObjectName '>' | RelObjectName )
-
- Referenced by: -
- - -====================================================================================================================== -ColumnSelectItemsList -====================================================================================================================== - - -.. raw:: html - - - - - - SelectItem - , - - -
- - -
         ::= SelectItem ( ',' SelectItem )*
-
- Referenced by: -
- - -====================================================================================================================== -SelectItemsList -====================================================================================================================== - - -.. raw:: html - - - - - - SelectItem - , - - -
- - -
         ::= SelectItem ( ',' SelectItem )*
-
- - -====================================================================================================================== -FunctionAllColumns -====================================================================================================================== - - -.. raw:: html - - - - - - ( - - Function - ) - - . - - * - - -
- - -
         ::= '('+ Function ')'+ '.' '*'
-
- Not referenced by any. -
- - -====================================================================================================================== -SelectItem -====================================================================================================================== - - -.. raw:: html - - - - - - ConnectByPriorOperator - - Condition - - Alias - -
- - -
         ::= ( ConnectByPriorOperator | Condition ) Alias?
-
- - -====================================================================================================================== -AllColumns -====================================================================================================================== - - -.. raw:: html - - - - - - * - - EXCEPT - - EXCLUDE - - ParenthesedColumnList - REPLACE - - ( - - SelectItemsList - ) - - -
- - -
         ::= '*' ( ( 'EXCEPT' | 'EXCLUDE' ) ParenthesedColumnList )? ( 'REPLACE' '(' SelectItemsList ')' )?
-
- - -====================================================================================================================== -AllTableColumns -====================================================================================================================== - - -.. raw:: html - - - - - - Table - . - - AllColumns - -
- - -
         ::= Table '.' AllColumns
-
- - -====================================================================================================================== -Alias -====================================================================================================================== - - -.. raw:: html - - - - - - AS - - RelObjectName - ( - - RelObjectName - - ColDataType - , - - ) - - AS - - RelObjectName - - S_CHAR_LITERAL - ( - - RelObjectName - - ColDataType - , - - ) - - -
- - -
           | 'AS'? ( RelObjectName | S_CHAR_LITERAL ) ( '(' RelObjectName ColDataType? ( ',' RelObjectName ColDataType? )* ')' )?
-
- - -====================================================================================================================== -SQLServerHint -====================================================================================================================== - - -.. raw:: html - - - - - - INDEX - - ( - - RelObjectName - ) - - NOLOCK - - -
- - -
         ::= 'INDEX' '(' RelObjectName ')'
-
           | 'NOLOCK'
-
- Referenced by: -
- - -====================================================================================================================== -SQLServerHints -====================================================================================================================== - - -.. raw:: html - - - - - - WITH - - ( - - SQLServerHint - , - - ) - - -
- - -
         ::= 'WITH' '(' SQLServerHint ( ',' SQLServerHint )* ')'
-
- Referenced by: -
- - -====================================================================================================================== -MySQLIndexHint -====================================================================================================================== - - -.. raw:: html - - - - - - USE - - SHOW - - IGNORE - - FORCE - - INDEX - - KEY - - ( - - RelObjectName - , - - ) - - -
- - -
         ::= ( 'USE' | 'SHOW' | 'IGNORE' | 'FORCE' ) ( 'INDEX' | 'KEY' ) '(' RelObjectName ( ',' RelObjectName )* ')'
-
- - -====================================================================================================================== -FunctionItem -====================================================================================================================== - - -.. raw:: html - - - - - - Function - - Alias - -
- - -
         ::= Function Alias?
-
- Referenced by: -
- - -====================================================================================================================== -PivotForColumns -====================================================================================================================== - - -.. raw:: html - - - - - - ParenthesedColumnList - - Column - -
- - -
         ::= ParenthesedColumnList
-
           | Column
-
- Referenced by: -
- - -====================================================================================================================== -PivotFunctionItems -====================================================================================================================== - - -.. raw:: html - - - - - - FunctionItem - , - - -
- - -
         ::= FunctionItem ( ',' FunctionItem )*
-
- Referenced by: -
- - -====================================================================================================================== -ExpressionListItem -====================================================================================================================== - - -.. raw:: html - - - - - - ParenthesedExpressionList - - Alias - -
- - -
         ::= ParenthesedExpressionList Alias?
-
- Referenced by: -
- - -====================================================================================================================== -PivotMultiInItems -====================================================================================================================== - - -.. raw:: html - - - - - - ExpressionListItem - , - - -
- - -
         ::= ExpressionListItem ( ',' ExpressionListItem )*
-
- Referenced by: -
- - -====================================================================================================================== -Pivot -====================================================================================================================== - - -.. raw:: html - - - - - - PIVOT - - ( - - PivotFunctionItems - FOR - - PivotForColumns - IN - - ( - - SelectItemsList - - PivotMultiInItems - ) - - ) - - Alias - -
- -
Pivot    ::= 'PIVOT' '(' PivotFunctionItems 'FOR' PivotForColumns 'IN' '(' ( SelectItemsList | PivotMultiInItems ) ')' ')' Alias?
-
- Referenced by: -
- - -====================================================================================================================== -PivotXml -====================================================================================================================== - - -.. raw:: html - - - - - - PIVOT - - XML - - ( - - PivotFunctionItems - FOR - - PivotForColumns - IN - - ( - - ANY - - Select - - SelectItemsList - - PivotMultiInItems - ) - - ) - - -
- -
PivotXml ::= 'PIVOT' 'XML' '(' PivotFunctionItems 'FOR' PivotForColumns 'IN' '(' ( 'ANY' | Select | SelectItemsList | PivotMultiInItems ) ')' ')'
-
- Referenced by: -
- - -====================================================================================================================== -UnPivot -====================================================================================================================== - - -.. raw:: html - - - - - - UNPIVOT - - INCLUDE - - EXCLUDE - - NULLS - - ( - - PivotForColumns - FOR - - PivotForColumns - IN - - ( - - SelectItemsList - ) - - ) - - Alias - -
- -
UnPivot  ::= 'UNPIVOT' ( ( 'INCLUDE' | 'EXCLUDE' ) 'NULLS' )? '(' PivotForColumns 'FOR' PivotForColumns 'IN' '(' SelectItemsList ')' ')' Alias?
-
- Referenced by: -
- - -====================================================================================================================== -IntoClause -====================================================================================================================== - - -.. raw:: html - - - - - - INTO - - Table - , - - -
- - -
         ::= 'INTO' Table ( ',' Table )*
-
- Referenced by: -
- - -====================================================================================================================== -MySqlSelectIntoClause -====================================================================================================================== - - -.. raw:: html - - - - - - INTO - - OUTFILE - - S_CHAR_LITERAL - - MySqlSelectIntoOutfileTail - DUMPFILE - - S_CHAR_LITERAL - -
- - -
         ::= 'INTO' ( 'OUTFILE' S_CHAR_LITERAL MySqlSelectIntoOutfileTail | 'DUMPFILE' S_CHAR_LITERAL )
-
- Referenced by: -
- - -====================================================================================================================== -MySqlSelectIntoOutfileTail -====================================================================================================================== - - -.. raw:: html - - - - - - CHARACTER - - SET - - S_IDENTIFIER - BINARY - - MySqlSelectIntoFieldsClause - - MySqlSelectIntoLinesClause - -
- - -
         ::= ( 'CHARACTER' 'SET' ( S_IDENTIFIER | 'BINARY' ) )? MySqlSelectIntoFieldsClause? MySqlSelectIntoLinesClause?
-
- Referenced by: -
- - -====================================================================================================================== -MySqlSelectIntoFieldsClause -====================================================================================================================== - - -.. raw:: html - - - - - - FIELDS - - COLUMNS - - TERMINATED - - BY - - S_CHAR_LITERAL - OPTIONALLY - - ENCLOSED - - BY - - S_CHAR_LITERAL - ESCAPED - - BY - - S_CHAR_LITERAL - -
- - -
         ::= ( 'FIELDS' | 'COLUMNS' ) ( 'TERMINATED' 'BY' S_CHAR_LITERAL )? ( 'OPTIONALLY'? 'ENCLOSED' 'BY' S_CHAR_LITERAL )? ( 'ESCAPED' 'BY' S_CHAR_LITERAL )?
-
- Referenced by: -
- - -====================================================================================================================== -MySqlSelectIntoLinesClause -====================================================================================================================== - - -.. raw:: html - - - - - - LINES - - STARTING - - BY - - S_CHAR_LITERAL - TERMINATED - - BY - - S_CHAR_LITERAL - -
- - -
         ::= 'LINES' ( 'STARTING' 'BY' S_CHAR_LITERAL )? ( 'TERMINATED' 'BY' S_CHAR_LITERAL )?
-
- Referenced by: -
- - -====================================================================================================================== -ParenthesedFromItem -====================================================================================================================== - - -.. raw:: html - - - - - - ( - - FromItem - - JoinsList - ) - - -
- - -
         ::= '(' FromItem JoinsList? ')'
-
- Referenced by: -
- - -====================================================================================================================== -FromItem -====================================================================================================================== - - -.. raw:: html - - - - - - Values - - TableFunction - - Table - - ParenthesedSelect - - Pivot - - UnPivot - - ParenthesedFromItem - - LateralSubSelect - - SubImport - - Select - - Alias - - TimeTravelAfterAlias - - SampleClause - - UnPivot - - PivotXml - - Pivot - - MySQLIndexHint - - SQLServerHints - -
- - -
- - -====================================================================================================================== -JoinsList -====================================================================================================================== - - -.. raw:: html - - - - - - JoinerExpression - -
- - -
         ::= JoinerExpression+
-
- - -====================================================================================================================== -JoinHint -====================================================================================================================== - - -.. raw:: html - - - - - - LOOP - - HASH - - MERGE - - REMOTE - - -
- -
JoinHint ::= 'LOOP'
-
           | 'HASH'
-
           | 'MERGE'
-
           | 'REMOTE'
-
- Referenced by: -
- - -====================================================================================================================== -JoinerExpression -====================================================================================================================== - - -.. raw:: html - - - - - - GLOBAL - - ANY - - ALL - - NATURAL - - LEFT - - SEMI - - OUTER - - ANY - - ALL - - RIGHT - - FULL - - OUTER - - ANY - - ALL - - INNER - - CROSS - - OUTER - - JoinHint - JOIN - - FETCH - - , - - OUTER - - STRAIGHT_JOIN - - APPLY - - FromItem - WITHIN - - ( - - JoinWindow - ) - - ON - - Expression - USING - - ( - - Column - , - - ) - - -
- - -
         ::= 'GLOBAL'? ( 'ANY' | 'ALL' )? 'NATURAL'? ( 'LEFT' ( 'SEMI' | 'OUTER' | - 'ANY' | 'ALL' )? | ( 'RIGHT' | 'FULL' ) ( 'OUTER' | 'ANY' | 'ALL' )? | 'INNER' | 'CROSS' - | 'OUTER' )? ( JoinHint? 'JOIN' 'FETCH'? | ',' 'OUTER'? | 'STRAIGHT_JOIN' | 'APPLY' ) FromItem ( ( 'WITHIN' '(' JoinWindow ')' )? ( 'ON' Expression )+ | 'USING' '(' Column ( ',' Column )* ')' )?
-
- Referenced by: -
- - -====================================================================================================================== -JoinWindow -====================================================================================================================== - - -.. raw:: html - - - - - - S_LONG - - S_IDENTIFIER - - K_DATE_LITERAL - , - - S_LONG - - S_IDENTIFIER - - K_DATE_LITERAL - -
- - -
         ::= S_LONG ( S_IDENTIFIER | K_DATE_LITERAL ) ( ',' S_LONG ( S_IDENTIFIER | K_DATE_LITERAL ) )?
-
- Referenced by: -
- - -====================================================================================================================== -KSQLWindowClause -====================================================================================================================== - - -.. raw:: html - - - - - - WINDOW - - HOPPING - - ( - - SIZE - - S_LONG - - S_IDENTIFIER - , - - ADVANCE - - BY - - SESSION - - ( - - TUMBLING - - ( - - SIZE - - S_LONG - - S_IDENTIFIER - ) - - -
- - -
         ::= 'WINDOW' ( 'HOPPING' '(' 'SIZE' S_LONG S_IDENTIFIER ',' 'ADVANCE' 'BY' | 'SESSION' '(' | 'TUMBLING' '(' 'SIZE' ) S_LONG S_IDENTIFIER ')'
-
- Referenced by: -
- - -====================================================================================================================== -WhereClause -====================================================================================================================== - - -.. raw:: html - - - - - - WHERE - - Expression - -
- - -
         ::= 'WHERE' Expression
-
- - -====================================================================================================================== -PreWhereClause -====================================================================================================================== - - -.. raw:: html - - - - - - PREWHERE - - Expression - -
- - -
         ::= 'PREWHERE' Expression
-
- Referenced by: -
- - -====================================================================================================================== -OracleHierarchicalQueryClause -====================================================================================================================== - - -.. raw:: html - - - - - - START - - WITH - - XorExpression - CONNECT - - BY - - NOCYCLE - - CONNECT - - BY - - NOCYCLE - - XorExpression - START - - WITH - - XorExpression - -
- - -
         ::= ( 'START' 'WITH' XorExpression 'CONNECT' 'BY' 'NOCYCLE'? | 'CONNECT' 'BY' 'NOCYCLE'? ( XorExpression 'START' 'WITH' )? ) XorExpression
-
- Referenced by: -
- - -====================================================================================================================== -PreferringClause -====================================================================================================================== - - -.. raw:: html - - - - - - PREFERRING - - PreferenceTerm - -
- - -
         ::= 'PREFERRING' PreferenceTerm
-
- Referenced by: -
- - -====================================================================================================================== -PreferenceTerm -====================================================================================================================== - - -.. raw:: html - - - - - - Plus - -
- - -
         ::= Plus
-
- Referenced by: -
- - -====================================================================================================================== -Plus -====================================================================================================================== - - -.. raw:: html - - - - - - PriorTo - PLUS - - -
- -
Plus     ::= PriorTo ( 'PLUS' PriorTo )*
-
- Referenced by: -
- - -====================================================================================================================== -PriorTo -====================================================================================================================== - - -.. raw:: html - - - - - - PreferenceTermTerminal - ( - - PreferenceTerm - ) - - TO - - PRIOR - - -
- -
PriorTo  ::= ( PreferenceTermTerminal | '(' PreferenceTerm ')' ) ( 'PRIOR' 'TO' ( PreferenceTermTerminal | '(' PreferenceTerm ')' ) )*
-
- Referenced by: -
- - -====================================================================================================================== -PreferenceTermTerminal -====================================================================================================================== - - -.. raw:: html - - - - - - HighExpression - - LowExpression - - Inverse - - Condition - -
- - -
         ::= HighExpression
-
           | LowExpression
-
           | Inverse
-
           | Condition
-
- Referenced by: -
- - -====================================================================================================================== -HighExpression -====================================================================================================================== - - -.. raw:: html - - - - - - HIGH - - Expression - -
- - -
         ::= 'HIGH' Expression
-
- Referenced by: -
- - -====================================================================================================================== -LowExpression -====================================================================================================================== - - -.. raw:: html - - - - - - LOW - - Expression - -
- - -
         ::= 'LOW' Expression
-
- Referenced by: -
- - -====================================================================================================================== -Inverse -====================================================================================================================== - - -.. raw:: html - - - - - - INVERSE - - ( - - PreferenceTerm - ) - - -
- -
Inverse  ::= 'INVERSE' '(' PreferenceTerm ')'
-
- Referenced by: -
- - -====================================================================================================================== -GroupByColumnReferences -====================================================================================================================== - - -.. raw:: html - - - - - - GROUP - - BY - - GROUPING - - SETS - - ( - - GroupingSet - , - - ) - - ExpressionList - GROUPING - - SETS - - ( - - GroupingSet - , - - ) - - WITH - - ROLLUP - - -
- - -
         ::= 'GROUP' 'BY' ( 'GROUPING' 'SETS' '(' GroupingSet ( ',' GroupingSet )* ')' | ExpressionList ( 'GROUPING' 'SETS' '(' GroupingSet ( ',' GroupingSet )* ')' )? ( 'WITH' 'ROLLUP' )? )
-
- Referenced by: -
- - -====================================================================================================================== -GroupingSet -====================================================================================================================== - - -.. raw:: html - - - - - - ParenthesedExpressionList - - SimpleExpression - -
- - -
         ::= ParenthesedExpressionList
-
           | SimpleExpression
-
- Referenced by: -
- - -====================================================================================================================== -Having -====================================================================================================================== - - -.. raw:: html - - - - - - HAVING - - Expression - -
- -
Having   ::= 'HAVING' Expression
-
- Referenced by: -
- - -====================================================================================================================== -Qualify -====================================================================================================================== - - -.. raw:: html - - - - - - QUALIFY - - Expression - -
- -
Qualify  ::= 'QUALIFY' Expression
-
- Referenced by: -
- - -====================================================================================================================== -OrderByElements -====================================================================================================================== - - -.. raw:: html - - - - - - ORDER - - SIBLINGS - - BY - - OrderByElement - , - - -
- - -
         ::= 'ORDER' 'SIBLINGS'? 'BY' OrderByElement ( ',' OrderByElement )*
-
- - -====================================================================================================================== -OrderByElement -====================================================================================================================== - - -.. raw:: html - - - - - - Expression - COLLATE - - S_CHAR_LITERAL - - S_QUOTED_IDENTIFIER - ASC - - DESC - - NULLS - - FIRST - - LAST - - WITH - - ROLLUP - - -
- - -
         ::= Expression ( 'COLLATE' ( S_CHAR_LITERAL | S_QUOTED_IDENTIFIER ) )? ( 'ASC' | 'DESC' )? ( 'NULLS' ( 'FIRST' | 'LAST' )? )? ( 'WITH' 'ROLLUP' )?
-
- Referenced by: -
- - -====================================================================================================================== -JdbcParameter -====================================================================================================================== - - -.. raw:: html - - - - - - ? - - S_PARAMETER - - S_LONG - -
- - -
         ::= ( '?' | S_PARAMETER ) S_LONG?
-
- Referenced by: -
- - -====================================================================================================================== -LimitWithOffset -====================================================================================================================== - - -.. raw:: html - - - - - - LIMIT - - ParenthesedSelect - - Expression - , - - Expression - -
- - -
         ::= 'LIMIT' ( ParenthesedSelect | Expression ) ( ',' Expression )?
-
- - -====================================================================================================================== -PlainLimit -====================================================================================================================== - - -.. raw:: html - - - - - - LIMIT - - ParenthesedSelect - - Expression - -
- - -
         ::= 'LIMIT' ( ParenthesedSelect | Expression )
-
- Referenced by: -
- - -====================================================================================================================== -LimitBy -====================================================================================================================== - - -.. raw:: html - - - - - - LimitWithOffset - BY - - ExpressionList - -
- - -
- Referenced by: -
- - -====================================================================================================================== -Offset -====================================================================================================================== - - -.. raw:: html - - - - - - OFFSET - - Expression - ROWS - - ROW - - -
- -
Offset   ::= 'OFFSET' Expression ( 'ROWS' | 'ROW' )?
-
- - -====================================================================================================================== -Fetch -====================================================================================================================== - - -.. raw:: html - - - - - - FETCH - - FIRST - - NEXT - - Expression - PERCENT - - ROWS - - ROW - - ONLY - - WITH TIES - - -
- -
Fetch    ::= 'FETCH' ( 'FIRST' | 'NEXT' ) ( Expression 'PERCENT'? )? ( 'ROWS' | 'ROW' ) ( 'ONLY' | 'WITH TIES' )
-
- - -====================================================================================================================== -WithIsolation -====================================================================================================================== - - -.. raw:: html - - - - - - WITH - - K_ISOLATION - -
- - -
         ::= 'WITH' K_ISOLATION
-
- - -====================================================================================================================== -OptimizeFor -====================================================================================================================== - - -.. raw:: html - - - - - - OPTIMIZE - - FOR - - S_LONG - ROWS - - -
- - -
         ::= 'OPTIMIZE' 'FOR' S_LONG 'ROWS'
-
- Referenced by: -
- - -====================================================================================================================== -Top -====================================================================================================================== - - -.. raw:: html - - - - - - TOP - - S_LONG - - JdbcParameter - : - - S_IDENTIFIER - ( - - SimpleExpression - ) - - PERCENT - - WITH TIES - - -
- -
Top      ::= 'TOP' ( S_LONG | JdbcParameter | ':' S_IDENTIFIER? | '(' SimpleExpression ')' ) 'PERCENT'? 'WITH TIES'?
-
- Referenced by: -
- - -====================================================================================================================== -Skip -====================================================================================================================== - - -.. raw:: html - - - - - - SKIP - - S_LONG - - S_IDENTIFIER - - JdbcParameter - -
- -
Skip     ::= 'SKIP' ( S_LONG | S_IDENTIFIER | JdbcParameter )
-
- Referenced by: -
- - -====================================================================================================================== -First -====================================================================================================================== - - -.. raw:: html - - - - - - FIRST - - LIMIT - - S_LONG - - S_IDENTIFIER - - JdbcParameter - -
- -
First    ::= ( 'FIRST' | 'LIMIT' ) ( S_LONG | S_IDENTIFIER | JdbcParameter )
-
- Referenced by: -
- - -====================================================================================================================== -Expression -====================================================================================================================== - - -.. raw:: html - - - - - - Condition - -
- - -
         ::= Condition
-
- - -====================================================================================================================== -XorExpression -====================================================================================================================== - - -.. raw:: html - - - - - - AndChain - OR - - XOR - - -
- - -
         ::= AndChain ( ( 'OR' | 'XOR' ) AndChain )*
-
- Referenced by: -
- - -====================================================================================================================== -AndChain -====================================================================================================================== - - -.. raw:: html - - - - - - Condition - AND - - && - - -
- -
AndChain ::= Condition ( ( 'AND' | '&&' ) Condition )*
-
- Referenced by: -
- - -====================================================================================================================== -Condition -====================================================================================================================== - - -.. raw:: html - - - - - - NOT - - ! - - ExistsExpression - PRIOR - - SimpleExpression - ( - - + - - ) - - RegularConditionRHS - - OverlapsCondition - - InExpression - - ExcludesExpression - - IncludesExpression - - Between - - MemberOfExpression - - IsNullExpression - - IsBooleanExpression - - IsUnknownExpression - - LikeExpression - - IsDistinctExpression - - SimilarToExpression - -
- - - -
- - -====================================================================================================================== -RegularConditionRHS -====================================================================================================================== - - -.. raw:: html - - - - - - ( - - + - - ) - - > - - < - - = - - OP_GREATERTHANEQUALS - - OP_MINORTHANEQUALS - - OP_NOTEQUALSSTANDARD - - OP_NOTEQUALSBANG - ^= - - *= - - =* - - && - - &> - - <& - - @@ - - ~ - - ~* - - !~ - - !~* - - @> - - <@ - - ? - - ?| - - ?& - - || - - - - - -# - - <-> - - <#> - - <=> - - PRIOR - - ComparisonItem - ( - - + - - ) - - -
- - -
         ::= ( '(' '+' ')' )? ( '>' | '<' | '=' | OP_GREATERTHANEQUALS | OP_MINORTHANEQUALS | OP_NOTEQUALSSTANDARD | OP_NOTEQUALSBANG | '^=' | '*=' | '=*' | '&&' | '&>' | '<&' | '@@' | '~' | '~*' | '!~' | '!~*' | '@>' - | '<@' | '?' | '?|' | '?&' | '||' | '-' | '-#' | '<->' | '<#>' | '<=>' ) 'PRIOR'? - ComparisonItem ( '(' '+' ')' )?
-
- Referenced by: -
- - -====================================================================================================================== -OverlapsCondition -====================================================================================================================== - - -.. raw:: html - - - - - - OVERLAPS - - ParenthesedExpressionList - -
- - -
         ::= 'OVERLAPS' ParenthesedExpressionList
-
- Referenced by: -
- - -====================================================================================================================== -SQLCondition -====================================================================================================================== - - -.. raw:: html - - - - - - ExistsExpression - - SimpleExpression - - OverlapsCondition - - InExpression - - ExcludesExpression - - IncludesExpression - - Between - - MemberOfExpression - - IsNullExpression - - IsBooleanExpression - - IsUnknownExpression - - LikeExpression - - IsDistinctExpression - - SimilarToExpression - -
- - -
         ::= ExistsExpression
-
-
- Not referenced by any. -
- - -====================================================================================================================== -InExpression -====================================================================================================================== - - -.. raw:: html - - - - - - ( - - + - - ) - - GLOBAL - - NOT - - IN - - S_CHAR_LITERAL - - ParenthesedSelect - - PrimaryExpression - -
- - -
         ::= ( '(' '+' ')' )? 'GLOBAL'? 'NOT'? 'IN' ( S_CHAR_LITERAL | ParenthesedSelect | PrimaryExpression )
-
- Referenced by: -
- - -====================================================================================================================== -IncludesExpression -====================================================================================================================== - - -.. raw:: html - - - - - - INCLUDES - - ParenthesedExpressionList - -
- - -
         ::= 'INCLUDES' ParenthesedExpressionList
-
- Referenced by: -
- - -====================================================================================================================== -ExcludesExpression -====================================================================================================================== - - -.. raw:: html - - - - - - EXCLUDES - - ParenthesedExpressionList - -
- - -
         ::= 'EXCLUDES' ParenthesedExpressionList
-
- Referenced by: -
- - -====================================================================================================================== -Between -====================================================================================================================== - - -.. raw:: html - - - - - - NOT - - BETWEEN - - SYMMETRIC - - ASYMMETRIC - - ParenthesedSelect - - SimpleExpression - - RegularConditionRHS - AND - - ParenthesedSelect - - SimpleExpression - - RegularConditionRHS - -
- -
Between  ::= 'NOT'? 'BETWEEN' ( 'SYMMETRIC' | 'ASYMMETRIC' )? ( ParenthesedSelect | SimpleExpression RegularConditionRHS? ) 'AND' ( ParenthesedSelect | SimpleExpression RegularConditionRHS? )
-
- Referenced by: -
- - -====================================================================================================================== -LikeExpression -====================================================================================================================== - - -.. raw:: html - - - - - - NOT - - LIKE - - ILIKE - - RLIKE - - REGEXP_LIKE - - REGEXP - - K_SIMILAR_TO - MATCH_ANY - - MATCH_ALL - - MATCH_PHRASE - - MATCH_PHRASE_PREFIX - - MATCH_REGEXP - - BINARY - - SimpleExpression - ESCAPE - - S_CHAR_LITERAL - - Expression - -
- - -
         ::= 'NOT'? ( 'LIKE' | 'ILIKE' | 'RLIKE' | 'REGEXP_LIKE' | 'REGEXP' | K_SIMILAR_TO | 'MATCH_ANY' | 'MATCH_ALL' | 'MATCH_PHRASE' | 'MATCH_PHRASE_PREFIX' | 'MATCH_REGEXP' - ) 'BINARY'? SimpleExpression ( 'ESCAPE' ( S_CHAR_LITERAL | Expression ) )?
-
- Referenced by: -
- - -====================================================================================================================== -SimilarToExpression -====================================================================================================================== - - -.. raw:: html - - - - - - NOT - - SIMILAR - - TO - - SimpleExpression - ESCAPE - - S_CHAR_LITERAL - -
- - -
         ::= 'NOT'? 'SIMILAR' 'TO' SimpleExpression ( 'ESCAPE' S_CHAR_LITERAL )?
-
- Referenced by: -
- - -====================================================================================================================== -IsDistinctExpression -====================================================================================================================== - - -.. raw:: html - - - - - - IS - - NOT - - DISTINCT - - FROM - - SimpleExpression - -
- - -
         ::= 'IS' 'NOT'? 'DISTINCT' 'FROM' SimpleExpression
-
- Referenced by: -
- - -====================================================================================================================== -IsNullExpression -====================================================================================================================== - - -.. raw:: html - - - - - - NOT - - ISNULL - - NOTNULL - - IS - - NOT - - NULL - - -
- - -
         ::= 'NOT'? 'ISNULL'
-
           | 'NOTNULL'
-
           | 'IS' 'NOT'? 'NULL'
-
- Referenced by: -
- - -====================================================================================================================== -IsBooleanExpression -====================================================================================================================== - - -.. raw:: html - - - - - - IS - - NOT - - TRUE - - FALSE - - -
- - -
         ::= 'IS' 'NOT'? ( 'TRUE' | 'FALSE' )
-
- Referenced by: -
- - -====================================================================================================================== -IsUnknownExpression -====================================================================================================================== - - -.. raw:: html - - - - - - IS - - NOT - - UNKNOWN - - -
- - -
         ::= 'IS' 'NOT'? 'UNKNOWN'
-
- Referenced by: -
- - -====================================================================================================================== -ExistsExpression -====================================================================================================================== - - -.. raw:: html - - - - - - EXISTS - - SimpleExpression - -
- - -
         ::= 'EXISTS' SimpleExpression
-
- Referenced by: -
- - -====================================================================================================================== -MemberOfExpression -====================================================================================================================== - - -.. raw:: html - - - - - - MEMBER - - OF - - Expression - -
- - -
         ::= 'MEMBER' 'OF' Expression
-
- Referenced by: -
- - -====================================================================================================================== -ExpressionList -====================================================================================================================== - - -.. raw:: html - - - - - - ComplexExpressionList - - SimpleExpressionList - - ParenthesedExpressionList - -
- - -
         ::= ComplexExpressionList
-
           | SimpleExpressionList
-
           | ParenthesedExpressionList
-
- - -====================================================================================================================== -ParenthesedExpressionList -====================================================================================================================== - - -.. raw:: html - - - - - - ( - - ComplexExpressionList - - SimpleExpressionList - ) - - -
- - -
         ::= '(' ( ComplexExpressionList | SimpleExpressionList )? ')'
-
- - -====================================================================================================================== -SimpleExpressionList -====================================================================================================================== - - -.. raw:: html - - - - - - SimpleExpression - , - - LambdaExpression - - SimpleExpression - -
- - -
         ::= SimpleExpression ( ',' ( LambdaExpression | SimpleExpression ) )*
-
- - -====================================================================================================================== -ColumnList -====================================================================================================================== - - -.. raw:: html - - - - - - Column - , - - -
- - -
         ::= Column ( ',' Column )*
-
- - -====================================================================================================================== -ParenthesedColumnList -====================================================================================================================== - - -.. raw:: html - - - - - - ( - - ColumnList - ) - - -
- - -
         ::= '(' ColumnList ')'
-
- - -====================================================================================================================== -ComplexExpressionList -====================================================================================================================== - - -.. raw:: html - - - - - - OracleNamedFunctionParameter - - PostgresNamedFunctionParameter - - Expression - , - - OracleNamedFunctionParameter - - PostgresNamedFunctionParameter - - LambdaExpression - - Expression - -
- - - -
- - -====================================================================================================================== -NamedExpressionListExprFirst -====================================================================================================================== - - -.. raw:: html - - - - - - SimpleExpression - FROM - - IN - - PLACING - - SimpleExpression - FOR - - FROM - - SimpleExpression - FOR - - SimpleExpression - -
- - -
         ::= SimpleExpression ( 'FROM' | 'IN' | 'PLACING' ) SimpleExpression ( ( 'FOR' | 'FROM' ) SimpleExpression ( 'FOR' SimpleExpression )? )?
-
- - -====================================================================================================================== -ComparisonItem -====================================================================================================================== - - -.. raw:: html - - - - - - AnyComparisonExpression - - RowConstructor - - SimpleExpression - -
- - -
         ::= AnyComparisonExpression
-
           | RowConstructor
-
           | SimpleExpression
-
- Referenced by: -
- - -====================================================================================================================== -AnyComparisonExpression -====================================================================================================================== - - -.. raw:: html - - - - - - ANY - - SOME - - ALL - - ParenthesedSelect - -
- - -
         ::= ( 'ANY' | 'SOME' | 'ALL' ) ParenthesedSelect
-
- Referenced by: -
- - -====================================================================================================================== -SimpleExpression -====================================================================================================================== - - -.. raw:: html - - - - - - UserVariable - = - - := - - PrimaryExpression - -
- - -
         ::= ( UserVariable ( '=' | ':=' ) )? PrimaryExpression
-
- - -====================================================================================================================== -MulChain -====================================================================================================================== - - -.. raw:: html - - - - - - PrimaryExpression - * - - / - - % - - ^ - - DIV - - -
- -
MulChain ::= PrimaryExpression ( ( '*' | '/' | '%' | '^' | 'DIV' ) PrimaryExpression )*
-
- Referenced by: -
- - -====================================================================================================================== -AddChain -====================================================================================================================== - - -.. raw:: html - - - - - - MulChain - + - - - - - || - - & - - << - - >> - - | - - | - - -
- -
AddChain ::= MulChain ( ( '+' | '-' | '||' | '&' | '<<' | '>>' | '|' '|'? ) MulChain )*
-
- Not referenced by any. -
- - -====================================================================================================================== -ArrayExpression -====================================================================================================================== - - -.. raw:: html - - - - - - [ - - SimpleExpression - : - - SimpleExpression - ] - - -
- - -
         ::= ( '[' SimpleExpression? ( ':' SimpleExpression? )? ']' )+
-
- Referenced by: -
- - -====================================================================================================================== -PrimaryExpression -====================================================================================================================== - - -.. raw:: html - - - - - - NOT - - ! - - + - - - - - ~ - - NULL - - CaseWhenExpression - - CharacterPrimary - - ImplicitCast - - JdbcParameter - - JdbcNamedParameter - - UserVariable - - NumericBind - - ExtractExpression - - XMLSerializeExpr - - JsonFunction - - JsonAggregateFunction - - FullTextSearch - - CastExpression - - Function - - AnalyticExpression - - DateUnitExpression - - IntervalExpression - - S_DOUBLE - - S_LONG - - S_HEX - - AllColumns - - AllTableColumns - - K_TIME_KEY_EXPR - CURRENT - - DateTimeLiteralExpression - - StructType - ARRAY - - < - - ColDataType - > - - ArrayConstructor - - NextValExpression - - ConnectByRootOperator - - ConnectByPriorOperator - - KeyExpression - ALL - - Column - ( - - + - - ) - - TRUE - - FALSE - - S_CHAR_LITERAL - {d - - {t - - {ts - - S_CHAR_LITERAL - } - - Select - - ParenthesedSelect - - ParenthesedExpressionList - -> - - Expression - . - - RelObjectName - COLLATE - - S_CHAR_LITERAL - - S_QUOTED_IDENTIFIER - - S_IDENTIFIER - - IntervalExpressionWithoutInterval - - ArrayExpression - :: - - ColDataType - -> - - : - - ->> - - #> - - #>> - - Expression - - SimpleExpression - - JsonExpression - AT - - K_DATETIMELITERAL - ZONE - - PrimaryExpression - -
- - -
         ::= ( 'NOT' | '!' )? ( '+' | '-' | '~' )? ( 'NULL' | CaseWhenExpression | CharacterPrimary | ImplicitCast | JdbcParameter | JdbcNamedParameter | UserVariable | NumericBind | ExtractExpression | XMLSerializeExpr | JsonFunction | JsonAggregateFunction | FullTextSearch | CastExpression | Function AnalyticExpression? | DateUnitExpression | IntervalExpression | S_DOUBLE | S_LONG | S_HEX | AllColumns | AllTableColumns | K_TIME_KEY_EXPR | 'CURRENT' | DateTimeLiteralExpression | StructType | ( 'ARRAY' ( '<' ColDataType '>' )? )? ArrayConstructor | NextValExpression | ConnectByRootOperator | ConnectByPriorOperator | KeyExpression | 'ALL' | Column ( '(' '+' ')' )? | 'TRUE' | 'FALSE' | S_CHAR_LITERAL | ( '{d' | '{t' | '{ts' ) S_CHAR_LITERAL '}' | Select | ParenthesedSelect | ParenthesedExpressionList ( '->' Expression )? ( '.' RelObjectName )* ) ( 'COLLATE' ( S_CHAR_LITERAL | S_QUOTED_IDENTIFIER | S_IDENTIFIER ) )? IntervalExpressionWithoutInterval? ArrayExpression? ( '::' ColDataType )* ( ( ( '->' | ':' | '->>' | '#>' | '#>>' ) ( Expression | SimpleExpression ) )+ JsonExpression )? ( 'AT' K_DATETIMELITERAL 'ZONE' PrimaryExpression )*
-
- - -====================================================================================================================== -ConnectByRootOperator -====================================================================================================================== - - -.. raw:: html - - - - - - CONNECT_BY_ROOT - - Expression - -
- - -
         ::= 'CONNECT_BY_ROOT' Expression
-
- Referenced by: -
- - -====================================================================================================================== -ConnectByPriorOperator -====================================================================================================================== - - -.. raw:: html - - - - - - PRIOR - - Expression - -
- - -
         ::= 'PRIOR' Expression
-
- Referenced by: -
- - -====================================================================================================================== -KeyExpression -====================================================================================================================== - - -.. raw:: html - - - - - - KEY - - Expression - -
- - -
         ::= 'KEY' Expression
-
- Referenced by: -
- - -====================================================================================================================== -NextValExpression -====================================================================================================================== - - -.. raw:: html - - - - - - K_NEXTVAL - - RelObjectNames - -
- - -
         ::= K_NEXTVAL RelObjectNames
-
- Referenced by: -
- - -====================================================================================================================== -JdbcNamedParameter -====================================================================================================================== - - -.. raw:: html - - - - - - : - - & - - IdentifierChain - -
- - -
         ::= ( ':' | '&' ) IdentifierChain
-
- Referenced by: -
- - -====================================================================================================================== -OracleNamedFunctionParameter -====================================================================================================================== - - -.. raw:: html - - - - - - RelObjectNameExt - OUTER - - => - - Expression - -
- - -
         ::= ( RelObjectNameExt | 'OUTER' ) '=>' Expression
-
- Referenced by: -
- - -====================================================================================================================== -PostgresNamedFunctionParameter -====================================================================================================================== - - -.. raw:: html - - - - - - RelObjectNameExt - OUTER - - := - - Expression - -
- - -
         ::= ( RelObjectNameExt | 'OUTER' ) ':=' Expression
-
- Referenced by: -
- - -====================================================================================================================== -UserVariable -====================================================================================================================== - - -.. raw:: html - - - - - - S_AT_IDENTIFIER - - IdentifierChain2 - -
- - -
         ::= S_AT_IDENTIFIER IdentifierChain2
-
- - -====================================================================================================================== -NumericBind -====================================================================================================================== - - -.. raw:: html - - - - - - : - - S_LONG - -
- - -
         ::= ':' S_LONG
-
- Referenced by: -
- - -====================================================================================================================== -DateTimeLiteralExpression -====================================================================================================================== - - -.. raw:: html - - - - - - K_DATETIMELITERAL - - S_CHAR_LITERAL - - S_QUOTED_IDENTIFIER - -
- - - -
- Referenced by: -
- - -====================================================================================================================== -DateUnitExpression -====================================================================================================================== - - -.. raw:: html - - - - - - K_DATE_LITERAL - -
- - -
         ::= K_DATE_LITERAL
-
- Referenced by: -
- - -====================================================================================================================== -RangeExpression -====================================================================================================================== - - -.. raw:: html - - - - - - : - - Expression - -
- - -
         ::= ':' Expression
-
- Referenced by: -
- - -====================================================================================================================== -ArrayConstructor -====================================================================================================================== - - -.. raw:: html - - - - - - [ - - Expression - - RangeExpression - - ArrayConstructor - , - - ] - - -
- - -
         ::= '[' ( ( Expression RangeExpression? | ArrayConstructor ) ( ',' ( Expression RangeExpression? | ArrayConstructor ) )* )? ']'
-
- - -====================================================================================================================== -StructParameters -====================================================================================================================== - - -.. raw:: html - - - - - - RelObjectName - - ColDataType - , - - -
- - -
         ::= RelObjectName? ColDataType ( ',' RelObjectName? ColDataType )*
-
- Referenced by: -
- - -====================================================================================================================== -StructType -====================================================================================================================== - - -.. raw:: html - - - - - - STRUCT - - < - - StructParameters - > - - ( - - SelectItemsList - ) - - { - - RelObjectNameExt - - S_CHAR_LITERAL - : - - Expression - , - - } - - :: - - STRUCT - - ( - - StructParameters - ) - - -
- - -
         ::= 'STRUCT' ( '<' StructParameters '>' )? '(' SelectItemsList ')'
-
           | '{' ( RelObjectNameExt | S_CHAR_LITERAL ) ':' Expression ( ',' ( RelObjectNameExt | S_CHAR_LITERAL ) ':' Expression )* '}' ( '::' 'STRUCT' '(' StructParameters ')' )*
-
- Referenced by: -
- - -====================================================================================================================== -JsonExpression -====================================================================================================================== - - -.. raw:: html - - - - - - :: - - ColDataType - -> - - : - - ->> - - #> - - #>> - - Expression - - SimpleExpression - -
- - -
         ::= ( ( '::' ColDataType )+ ( ( '->' | ':' | '->>' | '#>' | '#>>' ) ( Expression | SimpleExpression ) )* )*
-
- Referenced by: -
- - -====================================================================================================================== -JsonKeyValuePair -====================================================================================================================== - - -.. raw:: html - - - - - - KEY - - S_CHAR_LITERAL - - Column - - AllTableColumns - - AllColumns - - Expression - VALUE - - : - - , - - Expression - FORMAT - - JSON - - ENCODING - - JsonEncoding - -
- - -
         ::= ( 'KEY'? ( S_CHAR_LITERAL | Column ) | AllTableColumns | AllColumns | Expression ) ( ( 'VALUE' | ':' | ',' ) Expression )? ( 'FORMAT' 'JSON' ( 'ENCODING' JsonEncoding )? )?
-
- Referenced by: -
- - -====================================================================================================================== -JsonObjectBody -====================================================================================================================== - - -.. raw:: html - - - - - - ( - - JsonKeyValuePair - , - - NULL - - ABSENT - - ON - - NULL - - STRICT - - WITH - - WITHOUT - - UNIQUE - - KEYS - - RETURNING - - ColDataType - FORMAT - - JSON - - ENCODING - - JsonEncoding - ) - - -
- - -
         ::= '(' ( JsonKeyValuePair ( ',' JsonKeyValuePair )* )? ( ( 'NULL' | 'ABSENT' ) 'ON' 'NULL' )? 'STRICT'? ( ( 'WITH' | 'WITHOUT' ) 'UNIQUE' - 'KEYS' )? ( 'RETURNING' ColDataType ( 'FORMAT' 'JSON' ( 'ENCODING' JsonEncoding )? )? )? ')'
-
- Referenced by: -
- - -====================================================================================================================== -JsonArrayBody -====================================================================================================================== - - -.. raw:: html - - - - - - ( - - NULL - - ON - - NULL - - Expression - FORMAT - - JSON - - ENCODING - - JsonEncoding - , - - ABSENT - - ON - - NULL - - RETURNING - - ColDataType - FORMAT - - JSON - - ENCODING - - JsonEncoding - ) - - -
- - -
         ::= '(' ( 'NULL' 'ON' 'NULL' | Expression ( 'FORMAT' 'JSON' ( 'ENCODING' JsonEncoding )? )? ( ',' Expression ( 'FORMAT' 'JSON' ( 'ENCODING' JsonEncoding )? )? )* )* ( 'ABSENT' 'ON' 'NULL' )? ( 'RETURNING' ColDataType ( 'FORMAT' 'JSON' ( 'ENCODING' JsonEncoding )? )? )? ')'
-
- Referenced by: -
- - -====================================================================================================================== -JsonKeyword -====================================================================================================================== - - -.. raw:: html - - - - - - S_IDENTIFIER - -
- - -
         ::= S_IDENTIFIER
-
- - -====================================================================================================================== -JsonEncoding -====================================================================================================================== - - -.. raw:: html - - - - - - S_IDENTIFIER - -
- - -
         ::= S_IDENTIFIER
-
- - -====================================================================================================================== -JsonValueOrQueryInputExpression -====================================================================================================================== - - -.. raw:: html - - - - - - Expression - FORMAT - - JSON - - ENCODING - - JsonEncoding - -
- - -
         ::= Expression ( 'FORMAT' 'JSON' ( 'ENCODING' JsonEncoding )? )?
-
- - -====================================================================================================================== -JsonValueOnResponseBehavior -====================================================================================================================== - - -.. raw:: html - - - - - - ERROR - - NULL - - EMPTY - - DEFAULT - - Expression - -
- - -
         ::= 'ERROR'
-
           | 'NULL'
-
           | 'EMPTY'
-
           | 'DEFAULT' Expression
-
- Referenced by: -
- - -====================================================================================================================== -JsonQueryOnResponseBehavior -====================================================================================================================== - - -.. raw:: html - - - - - - ERROR - - NULL - - TRUE - - FALSE - - EMPTY - - ARRAY - - JsonKeyword - -
- - -
         ::= 'ERROR'
-
           | 'NULL'
-
           | 'TRUE'
-
           | 'FALSE'
-
           | 'EMPTY' ( 'ARRAY' | JsonKeyword )?
-
- - -====================================================================================================================== -JsonExistsOnResponseBehavior -====================================================================================================================== - - -.. raw:: html - - - - - - TRUE - - FALSE - - UNKNOWN - - ERROR - - -
- - -
         ::= 'TRUE'
-
           | 'FALSE'
-
           | 'UNKNOWN'
-
           | 'ERROR'
-
- Referenced by: -
- - -====================================================================================================================== -JsonExistsBody -====================================================================================================================== - - -.. raw:: html - - - - - - ( - - JsonValueOrQueryInputExpression - , - - Expression - - JsonKeyword - - Expression - , - - JsonExistsOnResponseBehavior - ON - - ERROR - - ) - - -
- - -
         ::= '(' JsonValueOrQueryInputExpression ',' Expression ( JsonKeyword Expression ( ',' Expression )* )? ( JsonExistsOnResponseBehavior 'ON' 'ERROR' )? ')'
-
- Referenced by: -
- - -====================================================================================================================== -JsonValueBody -====================================================================================================================== - - -.. raw:: html - - - - - - ( - - JsonValueOrQueryInputExpression - , - - Expression - - JsonKeyword - - Expression - , - - RETURNING - - ColDataType - - JsonValueOnResponseBehavior - ON - - EMPTY - - JsonValueOnResponseBehavior - ON - - ERROR - - ) - - -
- - -
         ::= '(' JsonValueOrQueryInputExpression ',' Expression ( JsonKeyword Expression ( ',' Expression )* )? ( 'RETURNING' ColDataType )? ( JsonValueOnResponseBehavior 'ON' 'EMPTY' )? ( JsonValueOnResponseBehavior 'ON' 'ERROR' )? ')'
-
- Referenced by: -
- - -====================================================================================================================== -JsonQueryBody -====================================================================================================================== - - -.. raw:: html - - - - - - ( - - JsonValueOrQueryInputExpression - , - - Expression - - JsonKeyword - - Expression - , - - RETURNING - - ColDataType - FORMAT - - JSON - - ENCODING - - JsonEncoding - WITHOUT - - WITH - - S_IDENTIFIER - ARRAY - - JsonKeyword - KEEP - - JsonKeyword - - JsonKeyword - ON - - JsonKeyword - STRING - - JsonQueryOnResponseBehavior - ON - - EMPTY - - JsonQueryOnResponseBehavior - ON - - ERROR - - Expression - , - - ) - - -
- - -
         ::= '(' JsonValueOrQueryInputExpression ',' Expression ( JsonKeyword Expression ( ',' Expression )* )? ( 'RETURNING' ColDataType ( 'FORMAT' 'JSON' ( 'ENCODING' JsonEncoding )? )? )? ( ( 'WITHOUT' | 'WITH' S_IDENTIFIER? ) 'ARRAY'? JsonKeyword )? ( ( 'KEEP' | JsonKeyword ) JsonKeyword ( 'ON' JsonKeyword 'STRING' )? )? ( JsonQueryOnResponseBehavior 'ON' 'EMPTY' )? ( JsonQueryOnResponseBehavior 'ON' 'ERROR' )? ( ',' Expression ( 'RETURNING' ColDataType ( 'FORMAT' 'JSON' ( 'ENCODING' JsonEncoding )? )? )? ( ( 'WITHOUT' | 'WITH' S_IDENTIFIER? ) 'ARRAY'? JsonKeyword )? ( ( 'KEEP' | JsonKeyword ) JsonKeyword ( 'ON' JsonKeyword 'STRING' )? )? ( JsonQueryOnResponseBehavior 'ON' 'EMPTY' )? ( JsonQueryOnResponseBehavior 'ON' 'ERROR' )? )* ')'
-
- Referenced by: -
- - -====================================================================================================================== -JsonFunction -====================================================================================================================== - - -.. raw:: html - - - - - - JSON_OBJECT - - JsonObjectBody - JSON_ARRAY - - JsonArrayBody - - JsonKeyword - - JsonValueBody - - JsonQueryBody - - JsonExistsBody - -
- - -
         ::= 'JSON_OBJECT' JsonObjectBody
-
           | 'JSON_ARRAY' JsonArrayBody
-
           | JsonKeyword ( JsonValueBody | JsonQueryBody | JsonExistsBody )
-
- Referenced by: -
- - -====================================================================================================================== -JsonAggregateFunction -====================================================================================================================== - - -.. raw:: html - - - - - - JSON_OBJECTAGG - - ( - - KEY - - DT_ZONE - - S_DOUBLE - - S_LONG - - S_HEX - - S_CHAR_LITERAL - - Column - : - - , - - VALUE - - Expression - FORMAT - - JSON - - NULL - - ABSENT - - ON - - NULL - - WITH - - WITHOUT - - UNIQUE - - KEYS - - JSON_ARRAYAGG - - ( - - Expression - FORMAT - - JSON - - OrderByElements - NULL - - ABSENT - - ON - - NULL - - ) - - FILTER - - ( - - WHERE - - Expression - ) - - OVER - - ( - - PARTITION - - BY - - ComplexExpressionList - ( - - ComplexExpressionList - ) - - OrderByElements - - WindowElement - ) - - -
- - -
         ::= ( 'JSON_OBJECTAGG' '(' 'KEY'? ( DT_ZONE | S_DOUBLE | S_LONG | S_HEX | S_CHAR_LITERAL | Column ) ( ':' | ',' | 'VALUE' ) Expression ( 'FORMAT' 'JSON' )? ( ( 'NULL' | 'ABSENT' ) 'ON' 'NULL' )? ( ( 'WITH' | 'WITHOUT' - ) 'UNIQUE' 'KEYS' )? | 'JSON_ARRAYAGG' '(' Expression ( 'FORMAT' 'JSON' )? OrderByElements? ( ( 'NULL' | 'ABSENT' ) 'ON' 'NULL' )? ) ')' ( 'FILTER' '(' 'WHERE' Expression ')' )? ( 'OVER' '(' ( 'PARTITION' 'BY' ( ComplexExpressionList | '(' ComplexExpressionList ')' ) )? OrderByElements? WindowElement? ')' )?
-
- Referenced by: -
- - -====================================================================================================================== -IntervalExpression -====================================================================================================================== - - -.. raw:: html - - - - - - INTERVAL - - - - - S_LONG - - S_DOUBLE - - S_CHAR_LITERAL - - Expression - - S_IDENTIFIER - - K_DATE_LITERAL - -
- - -
         ::= 'INTERVAL' ( '-'? ( S_LONG | S_DOUBLE ) | S_CHAR_LITERAL | Expression ) ( S_IDENTIFIER | K_DATE_LITERAL )?
-
- Referenced by: -
- - -====================================================================================================================== -IntervalExpressionWithoutInterval -====================================================================================================================== - - -.. raw:: html - - - - - - K_DATE_LITERAL - -
- - -
         ::= K_DATE_LITERAL
-
- Referenced by: -
- - -====================================================================================================================== -KeepExpression -====================================================================================================================== - - -.. raw:: html - - - - - - KEEP - - ( - - S_IDENTIFIER - FIRST - - LAST - - OrderByElements - ) - - -
- - -
         ::= 'KEEP' '(' S_IDENTIFIER ( 'FIRST' | 'LAST' ) OrderByElements ')'
-
- Referenced by: -
- - -====================================================================================================================== -windowFun -====================================================================================================================== - - -.. raw:: html - - - - - - OVER - - WITHIN - - GROUP - - RelObjectName - - windowDefinition - OVER - - ( - - PARTITION - - BY - - ComplexExpressionList - ( - - ComplexExpressionList - ) - - ) - - -
- - -
         ::= ( 'OVER' | 'WITHIN' 'GROUP' ) ( RelObjectName | windowDefinition ( 'OVER' '(' ( 'PARTITION' 'BY' ( ComplexExpressionList | '(' ComplexExpressionList ')' ) )? ')' )? )
-
- Referenced by: -
- - -====================================================================================================================== -windowDefinition -====================================================================================================================== - - -.. raw:: html - - - - - - ( - - PARTITION - - BY - - ComplexExpressionList - ( - - ComplexExpressionList - ) - - OrderByElements - - WindowElement - ) - - -
- - -
         ::= '(' ( 'PARTITION' 'BY' ( ComplexExpressionList | '(' ComplexExpressionList ')' ) )? OrderByElements? WindowElement? ')'
-
- Referenced by: -
- - -====================================================================================================================== -AnalyticExpression -====================================================================================================================== - - -.. raw:: html - - - - - - FILTER - - ( - - WHERE - - Expression - ) - - windowFun - - windowFun - -
- - -
         ::= 'FILTER' '(' 'WHERE' Expression ')' windowFun?
-
           | windowFun
-
- Referenced by: -
- - -====================================================================================================================== -WindowElement -====================================================================================================================== - - -.. raw:: html - - - - - - ROWS - - RANGE - - BETWEEN - - WindowOffset - AND - - WindowOffset - -
- - -
         ::= ( 'ROWS' | 'RANGE' ) ( 'BETWEEN' WindowOffset 'AND' )? WindowOffset
-
- - -====================================================================================================================== -WindowOffset -====================================================================================================================== - - -.. raw:: html - - - - - - UNBOUNDED - - SimpleExpression - PRECEDING - - FOLLOWING - - CURRENT - - ROW - - -
- - -
         ::= ( 'UNBOUNDED' | SimpleExpression ) ( 'PRECEDING' | 'FOLLOWING' )
-
           | 'CURRENT' 'ROW'
-
- Referenced by: -
- - -====================================================================================================================== -ExtractExpression -====================================================================================================================== - - -.. raw:: html - - - - - - EXTRACT - - ( - - RelObjectName - - S_CHAR_LITERAL - FROM - - SimpleExpression - ) - - -
- - -
         ::= 'EXTRACT' '(' ( RelObjectName | S_CHAR_LITERAL ) 'FROM' SimpleExpression ')'
-
- Referenced by: -
- - -====================================================================================================================== -ImplicitCast -====================================================================================================================== - - -.. raw:: html - - - - - - DataType - - S_CHAR_LITERAL - - S_LONG - - S_DOUBLE - -
- - -
         ::= DataType ( S_CHAR_LITERAL | S_LONG | S_DOUBLE )
-
- Referenced by: -
- - -====================================================================================================================== -CastExpression -====================================================================================================================== - - -.. raw:: html - - - - - - CAST - - SAFE_CAST - - TRY_CAST - - INTERPRET - - ( - - SimpleExpression - AS - - ROW - - ( - - ColumnDefinition - , - - ) - - ColDataType - FORMAT - - S_CHAR_LITERAL - ) - - -
- - -
         ::= ( 'CAST' | 'SAFE_CAST' | 'TRY_CAST' | 'INTERPRET' ) '(' SimpleExpression 'AS' ( 'ROW' '(' ColumnDefinition ( ',' ColumnDefinition )* ')' | ColDataType ) ( 'FORMAT' S_CHAR_LITERAL )? ')'
-
- Referenced by: -
- - -====================================================================================================================== -CaseWhenExpression -====================================================================================================================== - - -.. raw:: html - - - - - - CASE - - Expression - - WhenThenSearchCondition - ELSE - - Expression - - SimpleExpression - END - - -
- - -
         ::= 'CASE' Expression? WhenThenSearchCondition+ ( 'ELSE' ( Expression | SimpleExpression ) )? 'END'
-
- Referenced by: -
- - -====================================================================================================================== -WhenThenSearchCondition -====================================================================================================================== - - -.. raw:: html - - - - - - WHEN - - Expression - THEN - - Expression - - SimpleExpression - -
- - -
         ::= 'WHEN' Expression 'THEN' ( Expression | SimpleExpression )
-
- Referenced by: -
- - -====================================================================================================================== -RowConstructor -====================================================================================================================== - - -.. raw:: html - - - - - - ROW - - ParenthesedExpressionList - -
- - -
         ::= 'ROW' ParenthesedExpressionList
-
- Referenced by: -
- - -====================================================================================================================== -VariableExpression -====================================================================================================================== - - -.. raw:: html - - - - - - UserVariable - = - - SimpleExpression - -
- - -
         ::= UserVariable '=' SimpleExpression
-
- Not referenced by any. -
- - -====================================================================================================================== -Execute -====================================================================================================================== - - -.. raw:: html - - - - - - EXEC - - EXECUTE - - CALL - - RelObjectNames - - ExpressionList - -
- -
Execute  ::= ( 'EXEC' | 'EXECUTE' | 'CALL' ) RelObjectNames ExpressionList?
-
- Referenced by: -
- - -====================================================================================================================== -FullTextSearch -====================================================================================================================== - - -.. raw:: html - - - - - - MATCH - - ( - - ColumnList - ) - - AGAINST - - ( - - SimpleExpression - IN NATURAL LANGUAGE MODE - - IN NATURAL LANGUAGE MODE WITH QUERY EXPANSION - - IN BOOLEAN MODE - - WITH QUERY EXPANSION - - ) - - -
- - -
         ::= 'MATCH' '(' ColumnList ')' 'AGAINST' '(' SimpleExpression ( 'IN NATURAL LANGUAGE MODE' | 'IN NATURAL LANGUAGE MODE WITH QUERY EXPANSION' | - 'IN BOOLEAN MODE' | 'WITH QUERY EXPANSION' )? ')'
-
- Referenced by: -
- - -====================================================================================================================== -LambdaExpression -====================================================================================================================== - - -.. raw:: html - - - - - - ParenthesedColumnList - - RelObjectName - -> - - Expression - -
- - -
         ::= ( ParenthesedColumnList | RelObjectName ) '->' Expression
-
- - -====================================================================================================================== -Function -====================================================================================================================== - - -.. raw:: html - - - - - - { - - FN - - InternalFunction - } - - SpecialStringFunctionWithNamedParameters - - InternalFunction - -
- -
Function ::= '{' 'FN' InternalFunction '}'
- -
           | InternalFunction
-
- - -====================================================================================================================== -SpecialStringFunctionWithNamedParameters -====================================================================================================================== - - -.. raw:: html - - - - - - K_STRING_FUNCTION_NAME - ( - - NamedExpressionListExprFirst - - ExpressionList - ) - - -
- - - -
- Referenced by: -
- - -====================================================================================================================== -InternalFunction -====================================================================================================================== - - -.. raw:: html - - - - - - APPROXIMATE - - RelObjectNames - ( - - DISTINCT - - ALL - - UNIQUE - - TABLE - - ExpressionList - - OrderByElements - ON - - OVERFLOW - - TRUNCATE - - ERROR - - S_CHAR_LITERAL - WITH - - WITHOUT - - COUNT - - Select - HAVING - - MIN - - MAX - - Expression - IGNORE - - RESPECT - - NULLS - - PlainLimit - ) - - ( - - ExpressionList - ) - - . - - Function - - Column - IGNORE - - RESPECT - - NULLS - - KeepExpression - -
- - -
         ::= 'APPROXIMATE'? RelObjectNames '(' ( ( 'DISTINCT' | 'ALL' | 'UNIQUE' )? ( 'TABLE'? ExpressionList OrderByElements? ( 'ON' 'OVERFLOW' ( 'TRUNCATE' | 'ERROR' ) ( S_CHAR_LITERAL ( ( 'WITH' | 'WITHOUT' ) 'COUNT' )? )? )? | Select ) )? ( 'HAVING' ( 'MIN' | 'MAX' ) Expression )? ( ( 'IGNORE' | 'RESPECT' ) 'NULLS' )? PlainLimit? ')' ( '(' ExpressionList ')' )? ( '.' ( Function | Column ) )? ( ( 'IGNORE' | 'RESPECT' ) 'NULLS' )? KeepExpression?
-
- Referenced by: -
- - -====================================================================================================================== -XMLSerializeExpr -====================================================================================================================== - - -.. raw:: html - - - - - - XMLSERIALIZE - - ( - - XMLAGG - - ( - - XMLTEXT - - ( - - SimpleExpression - ) - - OrderByElements - ) - - AS - - ColDataType - ) - - -
- - -
         ::= 'XMLSERIALIZE' '(' 'XMLAGG' '(' 'XMLTEXT' '(' SimpleExpression ')' OrderByElements? ')' 'AS' ColDataType ')'
-
- Referenced by: -
- - -====================================================================================================================== -JsonTablePassingClause -====================================================================================================================== - - -.. raw:: html - - - - - - Expression - AS - - RelObjectName - -
- - -
         ::= Expression 'AS' RelObjectName
-
- Referenced by: -
- - -====================================================================================================================== -JsonTableOnEmptyBehavior -====================================================================================================================== - - -.. raw:: html - - - - - - ERROR - - NULL - - TRUE - - FALSE - - DEFAULT - - Expression - - S_IDENTIFIER - - JsonKeyword - ARRAY - - -
- - -
         ::= 'ERROR'
-
           | 'NULL'
-
           | 'TRUE'
-
           | 'FALSE'
-
           | 'DEFAULT' Expression
-
           | S_IDENTIFIER ( JsonKeyword | 'ARRAY' )?
-
- Referenced by: -
- - -====================================================================================================================== -JsonTableWrapperClause -====================================================================================================================== - - -.. raw:: html - - - - - - WITHOUT - - WITH - - S_IDENTIFIER - ARRAY - - JsonKeyword - -
- - -
         ::= ( 'WITHOUT' | 'WITH' S_IDENTIFIER? ) 'ARRAY'? JsonKeyword
-
- Referenced by: -
- - -====================================================================================================================== -JsonTableQuotesClause -====================================================================================================================== - - -.. raw:: html - - - - - - KEEP - - JsonKeyword - - JsonKeyword - ON - - JsonKeyword - STRING - - -
- - -
         ::= ( 'KEEP' | JsonKeyword ) JsonKeyword ( 'ON' JsonKeyword 'STRING' )?
-
- Referenced by: -
- - -====================================================================================================================== -JsonTableColumnDefinition -====================================================================================================================== - - -.. raw:: html - - - - - - JsonKeyword - PATH - - Expression - AS - - RelObjectName - - JsonTableColumnsClause - - RelObjectName - FOR - - ORDINALITY - - ColDataType - FORMAT - - JSON - - ENCODING - - JsonEncoding - - JsonKeyword - - JsonKeyword - EXISTS - - JsonTableWrapperClause - PATH - - Expression - - JsonTableWrapperClause - - JsonTableQuotesClause - - JsonTableOnEmptyBehavior - ON - - EMPTY - - JsonQueryOnResponseBehavior - ON - - ERROR - - JsonTableOnEmptyBehavior - ON - - EMPTY - - -
- - -
         ::= JsonKeyword 'PATH'? Expression ( 'AS' RelObjectName )? JsonTableColumnsClause
-
           | RelObjectName ( 'FOR' 'ORDINALITY' | ColDataType? ( 'FORMAT' 'JSON' ( 'ENCODING' JsonEncoding )? )? ( JsonKeyword JsonKeyword )? 'EXISTS'? JsonTableWrapperClause? ( 'PATH' Expression )? JsonTableWrapperClause? JsonTableQuotesClause? ( JsonTableOnEmptyBehavior 'ON' 'EMPTY' )? ( JsonQueryOnResponseBehavior 'ON' 'ERROR' )? ( JsonTableOnEmptyBehavior 'ON' 'EMPTY' )? )
-
- Referenced by: -
- - -====================================================================================================================== -JsonTableColumnsClause -====================================================================================================================== - - -.. raw:: html - - - - - - COLUMNS - - ( - - JsonTableColumnDefinition - , - - ) - - -
- - -
         ::= 'COLUMNS' '(' ( JsonTableColumnDefinition ( ',' JsonTableColumnDefinition )* )? ')'
-
- - -====================================================================================================================== -JsonTablePlanTerm -====================================================================================================================== - - -.. raw:: html - - - - - - ( - - JsonTablePlanExpression - ) - - RelObjectName - - Expression - -
- - -
         ::= '(' JsonTablePlanExpression ')'
-
           | RelObjectName
-
           | Expression
-
- Referenced by: -
- - -====================================================================================================================== -JsonTablePlanExpression -====================================================================================================================== - - -.. raw:: html - - - - - - JsonTablePlanTerm - , - - INNER - - OUTER - - CROSS - - UNION - - -
- - -
         ::= JsonTablePlanTerm ( ( ',' | 'INNER' | 'OUTER' | 'CROSS' | 'UNION' ) JsonTablePlanTerm )*
-
- - -====================================================================================================================== -JsonTablePlanClause -====================================================================================================================== - - -.. raw:: html - - - - - - PLAN - - DEFAULT - - ( - - JsonTablePlanExpression - ) - - -
- - -
         ::= 'PLAN' 'DEFAULT'? '(' JsonTablePlanExpression ')'
-
- Referenced by: -
- - -====================================================================================================================== -JsonTableOnErrorClause -====================================================================================================================== - - -.. raw:: html - - - - - - ERROR - - EMPTY - - TRUE - - FALSE - - NULL - - ON - - ERROR - - -
- - -
         ::= ( 'ERROR' | 'EMPTY' | 'TRUE' | 'FALSE' | 'NULL' ) 'ON' 'ERROR'
-
- Referenced by: -
- - -====================================================================================================================== -JsonTableOnEmptyClause -====================================================================================================================== - - -.. raw:: html - - - - - - ERROR - - EMPTY - - TRUE - - FALSE - - NULL - - ON - - EMPTY - - -
- - -
         ::= ( 'ERROR' | 'EMPTY' | 'TRUE' | 'FALSE' | 'NULL' ) 'ON' 'EMPTY'
-
- Referenced by: -
- - -====================================================================================================================== -JsonTableParsingTypeClause -====================================================================================================================== - - -.. raw:: html - - - - - - TYPE - - ( - - STRICT - - JsonKeyword - ) - - -
- - -
         ::= 'TYPE' '(' ( 'STRICT' | JsonKeyword ) ')'
-
- Referenced by: -
- - -====================================================================================================================== -JsonTableBody -====================================================================================================================== - - -.. raw:: html - - - - - - ( - - Expression - FORMAT - - JSON - - , - - Expression - AS - - RelObjectName - - JsonKeyword - - JsonTablePassingClause - , - - JsonTableOnErrorClause - - JsonTableParsingTypeClause - - JsonTableOnEmptyClause - - JsonTableColumnsClause - - JsonTablePlanClause - - JsonTableOnErrorClause - ) - - -
- - - -
- Referenced by: -
- - -====================================================================================================================== -TableFunction -====================================================================================================================== - - -.. raw:: html - - - - - - LATERAL - - JsonKeyword - - JsonTableBody - - Function - WITH - - OFFSET - - ORDINALITY - - -
- - -
         ::= 'LATERAL'? ( JsonKeyword JsonTableBody | Function ) ( 'WITH' ( 'OFFSET' | 'ORDINALITY' ) )?
-
- Referenced by: -
- - -====================================================================================================================== -ColumnNamesWithParamsList -====================================================================================================================== - - -.. raw:: html - - - - - - ( - - RelObjectName - - CreateParameter - , - - ) - - -
- - -
         ::= '(' RelObjectName CreateParameter? ( ',' RelObjectName CreateParameter? )* ')'
-
- - -====================================================================================================================== -IndexColumnWithParams -====================================================================================================================== - - -.. raw:: html - - - - - - RelObjectName - ( - - Expression - ) - - CreateParameter - -
- - -
         ::= ( RelObjectName | '(' Expression ')' ) CreateParameter?
-
- Referenced by: -
- - -====================================================================================================================== -IndexColumnsWithParamsList -====================================================================================================================== - - -.. raw:: html - - - - - - ( - - IndexColumnWithParams - , - - ) - - -
- - -
         ::= '(' IndexColumnWithParams ( ',' IndexColumnWithParams )* ')'
-
- - -====================================================================================================================== -Index -====================================================================================================================== - - -.. raw:: html - - - - - - RelObjectNames - -
- - -
- Referenced by: -
- - -====================================================================================================================== -CreateIndex -====================================================================================================================== - - -.. raw:: html - - - - - - CreateParameter - INDEX - - IF - - NOT - - EXISTS - - Index - ON - - Table - - UsingIndexType - - UsingIndexType - ON - - Table - - IndexColumnsWithParamsList - - CreateParameter - -
- - -
         ::= CreateParameter? 'INDEX' ( 'IF' 'NOT' 'EXISTS' )? Index ( 'ON' Table UsingIndexType? | UsingIndexType? 'ON' Table ) IndexColumnsWithParamsList CreateParameter*
-
- Referenced by: -
- - -====================================================================================================================== -ColumnDefinition -====================================================================================================================== - - -.. raw:: html - - - - - - RelObjectName - - ColDataType - - CreateParameter - -
- - - -
- - -====================================================================================================================== -CreateSchema -====================================================================================================================== - - -.. raw:: html - - - - - - SCHEMA - - IF - - NOT - - EXISTS - - S_IDENTIFIER - - S_QUOTED_IDENTIFIER - . - - S_IDENTIFIER - - S_QUOTED_IDENTIFIER - AUTHORIZATION - - S_IDENTIFIER - - S_QUOTED_IDENTIFIER - - PathSpecification - CREATE - - CreateTable - - CreateView - -
- - -
         ::= 'SCHEMA' ( 'IF' 'NOT' 'EXISTS' )? ( ( S_IDENTIFIER | S_QUOTED_IDENTIFIER ) ( '.' ( S_IDENTIFIER | S_QUOTED_IDENTIFIER ) )? )? ( 'AUTHORIZATION' ( S_IDENTIFIER | S_QUOTED_IDENTIFIER ) )? PathSpecification? ( 'CREATE' CreateTable | CreateView )*
-
- Referenced by: -
- - -====================================================================================================================== -PathSpecification -====================================================================================================================== - - -.. raw:: html - - - - - - PATH - - S_IDENTIFIER - - S_QUOTED_IDENTIFIER - , - - -
- - -
         ::= 'PATH' ( S_IDENTIFIER | S_QUOTED_IDENTIFIER ) ( ',' ( S_IDENTIFIER | S_QUOTED_IDENTIFIER ) )*
-
- Referenced by: -
- - -====================================================================================================================== -CreateTableConstraint -====================================================================================================================== - - -.. raw:: html - - - - - - INDEX - - UNIQUE - - FULLTEXT - - SPATIAL - - KEY - - RelObjectName - - IndexColumnsWithParamsList - - CreateParameter - CONSTRAINT - - RelObjectName - PRIMARY - - KEY - - UNIQUE - - KEY - - ColumnNamesWithParamsList - - CreateParameter - - ForeignKeySpec - - CheckConstraintSpec - EXCLUDE - - WHERE - - ( - - Expression - ) - - -
- - -
         ::= ( 'INDEX' | 'UNIQUE'? ( 'FULLTEXT' | 'SPATIAL' )? 'KEY' ) RelObjectName IndexColumnsWithParamsList CreateParameter*
-
           | ( 'CONSTRAINT' RelObjectName )? ( ( 'PRIMARY' 'KEY' | 'UNIQUE' 'KEY'? ) ColumnNamesWithParamsList CreateParameter* | ForeignKeySpec | CheckConstraintSpec )
-
           | 'EXCLUDE' 'WHERE' ( '(' Expression ')' )*
-
- Referenced by: -
- - -====================================================================================================================== -CreateTable -====================================================================================================================== - - -.. raw:: html - - - - - - UNLOGGED - - GLOBAL - - CreateParameter - TABLE - - IF - - NOT - - EXISTS - - Table - ( - - RelObjectName - , - - ColumnDefinition - , - - CreateTableConstraint - - ColumnDefinition - ) - - CreateParameter - - RowMovement - AS - - Select - LIKE - - ( - - Table - ) - - Table - , - - SpannerInterleaveIn - -
- - -
         ::= 'UNLOGGED'? 'GLOBAL'? CreateParameter* 'TABLE' ( 'IF' 'NOT' 'EXISTS' )? Table ( '(' ( RelObjectName ( ',' RelObjectName )* | ColumnDefinition ( ',' ( CreateTableConstraint | ColumnDefinition ) )* ) ')' )? CreateParameter* RowMovement? ( 'AS' Select )? ( 'LIKE' ( '(' Table ')' | Table ) )? ( ',' SpannerInterleaveIn )?
-
- Referenced by: -
- - -====================================================================================================================== -SpannerInterleaveIn -====================================================================================================================== - - -.. raw:: html - - - - - - INTERLEAVE - - IN - - PARENT - - Table - ON - - DELETE - - NO - - ACTION - - CASCADE - - -
- - -
         ::= 'INTERLEAVE' 'IN' 'PARENT' Table ( 'ON' 'DELETE' ( 'NO' 'ACTION' | 'CASCADE' ) )?
-
- Referenced by: -
- - -====================================================================================================================== -DataType -====================================================================================================================== - - -.. raw:: html - - - - - - K_DATETIMELITERAL - - DT_ZONE - - DATA_TYPE - SIGNED - - UNSIGNED - - CHARACTER - - BIT - - BYTES - - BINARY - - BOOLEAN - - CHAR - - JSON - - STRING - - DATA_TYPE - SIGNED - - UNSIGNED - - CHARACTER - - BIT - - BYTES - - BINARY - - BOOLEAN - - CHAR - - JSON - - STRING - - ( - - S_LONG - MAX - - BYTE - - CHAR - - , - - S_LONG - ) - - K_TEXT_LITERAL - ARRAY - - < - - ColDataType - > - - -
- - -
           | 'ARRAY' '<' ColDataType '>'
-
           | ( K_DATETIMELITERAL | DT_ZONE | DATA_TYPE | 'SIGNED' | 'UNSIGNED' | 'CHARACTER' | 'BIT' | 'BYTES' | 'BINARY' | 'BOOLEAN' | - 'CHAR' | 'JSON' | 'STRING' ) ( DATA_TYPE | 'SIGNED' | 'UNSIGNED' | 'CHARACTER' | 'BIT' | 'BYTES' | 'BINARY' | 'BOOLEAN' | - 'CHAR' | 'JSON' | 'STRING' )* ( '(' ( S_LONG | 'MAX' ) ( 'BYTE' | 'CHAR' )? ( ',' S_LONG )? ')' )?
-
- Referenced by: -
- - -====================================================================================================================== -ColDataType -====================================================================================================================== - - -.. raw:: html - - - - - - STRUCT - - ( - - RelObjectNameExt - - ColDataType - , - - ) - - DataType - - S_IDENTIFIER - - S_QUOTED_IDENTIFIER - - K_DATETIMELITERAL - - K_DATE_LITERAL - XML - - INTERVAL - - DT_ZONE - CHAR - - SET - - BINARY - - JSON - - STRING - - PUBLIC - - DATA - - NAME - - . - - ColDataType - ( - - S_LONG - MAX - - BYTE - - CHAR - - S_CHAR_LITERAL - - S_IDENTIFIER - CHAR - - , - - ) - - [ - - S_LONG - ] - - CHARACTER - - SET - - S_IDENTIFIER - BINARY - - -
- - -
         ::= ( 'STRUCT' '(' RelObjectNameExt ColDataType ( ',' RelObjectNameExt ColDataType )* ')' | DataType | ( S_IDENTIFIER | S_QUOTED_IDENTIFIER | K_DATETIMELITERAL | K_DATE_LITERAL | 'XML' | 'INTERVAL' | DT_ZONE | 'CHAR' | 'SET' | 'BINARY' | 'JSON' | 'STRING' | 'PUBLIC' | 'DATA' | 'NAME' ) ( - '.' ColDataType )? ) ( '(' ( ( ( S_LONG | 'MAX' ) ( 'BYTE' | 'CHAR' )? | S_CHAR_LITERAL | S_IDENTIFIER | 'CHAR' ) ','? )* ')' )? ( '[' S_LONG? ']' )* ( 'CHARACTER' 'SET' ( S_IDENTIFIER | 'BINARY' ) )?
-
- - -====================================================================================================================== -Analyze -====================================================================================================================== - - -.. raw:: html - - - - - - ANALYZE - - Table - -
- -
Analyze  ::= 'ANALYZE' Table
-
- Referenced by: -
- - -====================================================================================================================== -ColumnWithCommentList -====================================================================================================================== - - -.. raw:: html - - - - - - ( - - Column - , - - ) - - -
- - -
         ::= '(' Column ( ',' Column )* ')'
-
- Referenced by: -
- - -====================================================================================================================== -CreateView -====================================================================================================================== - - -.. raw:: html - - - - - - NO - - FORCE - - SECURE - - TEMP - - TEMPORARY - - VOLATILE - - MATERIALIZED - - VIEW - - Table - AUTO - - REFRESH - - YES - - NO - - IF - - NOT - - EXISTS - - ColumnWithCommentList - - CreateViewTailComment - AS - - Select - WITH - - READ - - ONLY - - -
- - -
         ::= ( 'NO'? 'FORCE' )? 'SECURE'? ( 'TEMP' | 'TEMPORARY' | 'VOLATILE' )? 'MATERIALIZED'? - 'VIEW' Table ( 'AUTO' 'REFRESH' ( 'YES' | 'NO' ) )? ( 'IF' 'NOT' 'EXISTS' )? ColumnWithCommentList? CreateViewTailComment? 'AS' Select ( 'WITH' 'READ' 'ONLY' )?
-
- Referenced by: -
- - -====================================================================================================================== -CreateViewTailComment -====================================================================================================================== - - -.. raw:: html - - - - - - COMMENT - - = - - S_CHAR_LITERAL - -
- - -
         ::= 'COMMENT' '='? S_CHAR_LITERAL
-
- Referenced by: -
- - -====================================================================================================================== -Action -====================================================================================================================== - - -.. raw:: html - - - - - - CASCADE - - RESTRICT - - NO - - ACTION - - SET - - NULL - - DEFAULT - - -
- -
Action   ::= 'CASCADE'
-
           | 'RESTRICT'
-
           | 'NO' 'ACTION'
-
           | 'SET' ( 'NULL' | 'DEFAULT' )
-
- Referenced by: -
- - -====================================================================================================================== -ReferentialActionsOnIndex -====================================================================================================================== - - -.. raw:: html - - - - - - ON - - DELETE - - UPDATE - - Action - ON - - DELETE - - UPDATE - - Action - -
- - -
         ::= ( 'ON' ( 'DELETE' | 'UPDATE' ) Action )? ( 'ON' ( 'DELETE' | 'UPDATE' ) Action )?
-
- - -====================================================================================================================== -CheckConstraintSpec -====================================================================================================================== - - -.. raw:: html - - - - - - CHECK - - ( - - Expression - ) - - -
- - -
         ::= 'CHECK' ( '(' Expression ')' )*
-
- - -====================================================================================================================== -ForeignKeySpec -====================================================================================================================== - - -.. raw:: html - - - - - - FOREIGN - - KEY - - ColumnNamesWithParamsList - REFERENCES - - Table - - ColumnsNamesList - - ReferentialActionsOnIndex - -
- - -
         ::= 'FOREIGN' 'KEY' ColumnNamesWithParamsList 'REFERENCES' Table ColumnsNamesList? ReferentialActionsOnIndex
-
- - -====================================================================================================================== -AlterExpressionUsingIndex -====================================================================================================================== - - -.. raw:: html - - - - - - USING - - INDEX - - RelObjectName - -
- - -
         ::= 'USING' 'INDEX'? RelObjectName
-
- - -====================================================================================================================== -AlterExpressionConstraintTail -====================================================================================================================== - - -.. raw:: html - - - - - - AlterExpressionConstraintState - - AlterExpressionUsingIndex - - IndexWithComment - -
- - - -
- Referenced by: -
- - -====================================================================================================================== -AlterView -====================================================================================================================== - - -.. raw:: html - - - - - - VIEW - - Table - - ColumnsNamesList - AS - - Select - -
- - -
         ::= 'VIEW' Table ColumnsNamesList? 'AS' Select
-
- Referenced by: -
- - -====================================================================================================================== -CreateParameter -====================================================================================================================== - - -.. raw:: html - - - - - - K_NEXTVAL - ( - - S_CHAR_LITERAL - :: - - ColDataType - ) - - S_IDENTIFIER - - S_QUOTED_IDENTIFIER - NAME - - . - - S_IDENTIFIER - - S_QUOTED_IDENTIFIER - NAME - - USING - - INDEX - - TABLESPACE - - RelObjectName - - S_CHAR_LITERAL - NULL - - NOT - - AUTO_INCREMENT - - PRIMARY - - FOREIGN - - REFERENCES - - KEY - - STORED - - ON - - COMMIT - - DROP - - ROWS - - UNIQUE - - CASCADE - - DELETE - - UPDATE - - CONSTRAINT - - WITH - - EXCLUDE - - WHERE - - TEMP - - TEMPORARY - - PARTITION - - BY - - IN - - TYPE - - COMMENT - - USING - - COLLATE - - ASC - - DESC - - TRUE - - FALSE - - PARALLEL - - BINARY - - START - - ORDER - - K_TIME_KEY_EXPR - RAW - - HASH - - FIRST - - LAST - - SIGNED - - UNSIGNED - - ENGINE - - IDENTITY - - MATERIALIZED - - SAMPLE - - ALWAYS - - = - - DEFAULT - - AS - - CHECK - - ( - - Expression - ) - - + - - - - - S_LONG - - S_DOUBLE - - AList - CHARACTER - - SET - - ARRAY - - ArrayConstructor - :: - - ColDataType - -
- - -
         ::= K_NEXTVAL '(' S_CHAR_LITERAL '::' ColDataType ')'
-
           | ( S_IDENTIFIER | S_QUOTED_IDENTIFIER | 'NAME' ) ( '.' ( S_IDENTIFIER | S_QUOTED_IDENTIFIER | 'NAME' ) )?
-
           | ( 'USING' 'INDEX' )? 'TABLESPACE' RelObjectName
-
           | S_CHAR_LITERAL
-
           | 'NULL'
-
           | 'NOT'
-
           | 'AUTO_INCREMENT'
-
           | 'PRIMARY'
-
           | 'FOREIGN'
-
           | 'REFERENCES'
-
           | 'KEY'
-
           | 'STORED'
-
           | 'ON'
-
           | 'COMMIT'
-
           | 'DROP'
-
           | 'ROWS'
-
           | 'UNIQUE'
-
           | 'CASCADE'
-
           | 'DELETE'
-
           | 'UPDATE'
-
           | 'CONSTRAINT'
-
           | 'WITH'
-
           | 'EXCLUDE'
-
           | 'WHERE'
-
           | 'TEMP'
-
           | 'TEMPORARY'
-
           | 'PARTITION'
-
           | 'BY'
-
           | 'IN'
-
           | 'TYPE'
-
           | 'COMMENT'
-
           | 'USING'
-
           | 'COLLATE'
-
           | 'ASC'
-
           | 'DESC'
-
           | 'TRUE'
-
           | 'FALSE'
-
           | 'PARALLEL'
-
           | 'BINARY'
-
           | 'START'
-
           | 'ORDER'
-
           | K_TIME_KEY_EXPR
-
           | 'RAW'
-
           | 'HASH'
-
           | 'FIRST'
-
           | 'LAST'
-
           | 'SIGNED'
-
           | 'UNSIGNED'
-
           | 'ENGINE'
-
           | 'IDENTITY'
-
           | 'MATERIALIZED'
-
           | 'SAMPLE'
-
           | 'ALWAYS'
-
           | '='
-
           | ( 'DEFAULT' | 'AS' | 'CHECK' ) ( '(' Expression ')' )?
-
           | ( '+' | '-' )? S_LONG
-
           | S_DOUBLE
-
           | AList
-
           | 'CHARACTER' 'SET'
-
           | 'ARRAY' ArrayConstructor
-
           | '::' ColDataType
-
- - -====================================================================================================================== -RowMovement -====================================================================================================================== - - -.. raw:: html - - - - - - ENABLE - - DISABLE - - ROW - - MOVEMENT - - -
- - -
         ::= ( 'ENABLE' | 'DISABLE' ) 'ROW' 'MOVEMENT'
-
- Referenced by: -
- - -====================================================================================================================== -AList -====================================================================================================================== - - -.. raw:: html - - - - - - ( - - S_LONG - - S_DOUBLE - - S_CHAR_LITERAL - TRUE - - FALSE - - RelObjectName - , - - = - - ) - - -
- -
AList    ::= '(' ( ( S_LONG | S_DOUBLE | S_CHAR_LITERAL | 'TRUE' | 'FALSE' | RelObjectName ) ( ',' | '=' )? )* ')'
-
- Referenced by: -
- - -====================================================================================================================== -ColumnsNamesListItem -====================================================================================================================== - - -.. raw:: html - - - - - - RelObjectName - ( - - S_LONG - ) - - ASC - - DESC - - -
- - -
         ::= RelObjectName ( '(' S_LONG ')' )? ( 'ASC' | 'DESC' )?
-
- Referenced by: -
- - -====================================================================================================================== -ColumnsNamesList -====================================================================================================================== - - -.. raw:: html - - - - - - ( - - ColumnsNamesListItem - , - - ) - - -
- - -
         ::= '(' ColumnsNamesListItem ( ',' ColumnsNamesListItem )* ')'
-
- - -====================================================================================================================== -FuncArgsListItem -====================================================================================================================== - - -.. raw:: html - - - - - - RelObjectName - - RelObjectName - ( - - S_LONG - ) - - -
- - -
         ::= RelObjectName RelObjectName? ( '(' S_LONG ')' )?
-
- Referenced by: -
- - -====================================================================================================================== -FuncArgsList -====================================================================================================================== - - -.. raw:: html - - - - - - ( - - FuncArgsListItem - , - - ) - - -
- - -
         ::= '(' ( FuncArgsListItem ( ',' FuncArgsListItem )* )? ')'
-
- Referenced by: -
- - -====================================================================================================================== -Drop -====================================================================================================================== - - -.. raw:: html - - - - - - DROP - - MATERIALIZED - - S_IDENTIFIER - TEMPORARY - - TABLE - - INDEX - - VIEW - - SCHEMA - - SEQUENCE - - FUNCTION - - IF - - EXISTS - - Table - - FuncArgsList - - S_IDENTIFIER - CASCADE - - RESTRICT - - ON - - Table - -
- -
Drop     ::= 'DROP' 'MATERIALIZED'? ( S_IDENTIFIER | 'TEMPORARY'? 'TABLE' | 'INDEX' | 'VIEW' | 'SCHEMA' | 'SEQUENCE' | 'FUNCTION' ) - ( 'IF' 'EXISTS' )? Table FuncArgsList? ( S_IDENTIFIER | 'CASCADE' | 'RESTRICT' | 'ON' Table )*
-
- Referenced by: -
- - -====================================================================================================================== -Truncate -====================================================================================================================== - - -.. raw:: html - - - - - - TRUNCATE - - TABLE - - ONLY - - Table - , - - CASCADE - - -
- -
Truncate ::= 'TRUNCATE' 'TABLE'? 'ONLY'? Table ( ',' Table )* 'CASCADE'?
-
- Referenced by: -
- - -====================================================================================================================== -AlterExpressionColumnChanges -====================================================================================================================== - - -.. raw:: html - - - - - - AlterExpressionColumnDropDefault - - AlterExpressionColumnSetDefault - - AlterExpressionColumnSetVisibility - ( - - AlterExpressionColumnDataType - , - - ) - - -
- - -
         ::= AlterExpressionColumnDropDefault
-
           | AlterExpressionColumnSetDefault
-
           | AlterExpressionColumnSetVisibility
-
           | '(' AlterExpressionColumnDataType ( ',' AlterExpressionColumnDataType )* ')'
-
- Referenced by: -
- - -====================================================================================================================== -AlterExpressionColumnDataType -====================================================================================================================== - - -.. raw:: html - - - - - - RelObjectName - TYPE - - ColDataType - - CreateParameter - -
- - -
         ::= RelObjectName 'TYPE'? ColDataType? CreateParameter*
-
- - -====================================================================================================================== -AlterExpressionColumnDropNotNull -====================================================================================================================== - - -.. raw:: html - - - - - - RelObjectName - DROP - - NOT - - NULL - - -
- - -
         ::= RelObjectName 'DROP' 'NOT'? 'NULL'
-
- Referenced by: -
- - -====================================================================================================================== -AlterExpressionColumnDropDefault -====================================================================================================================== - - -.. raw:: html - - - - - - RelObjectName - DROP - - DEFAULT - - -
- - -
         ::= RelObjectName 'DROP' 'DEFAULT'
-
- Referenced by: -
- - -====================================================================================================================== -AlterExpressionColumnSetDefault -====================================================================================================================== - - -.. raw:: html - - - - - - RelObjectName - SET - - DEFAULT - - Expression - -
- - -
         ::= RelObjectName 'SET' 'DEFAULT' Expression
-
- Referenced by: -
- - -====================================================================================================================== -AlterExpressionColumnSetVisibility -====================================================================================================================== - - -.. raw:: html - - - - - - RelObjectName - SET - - VISIBLE - - INVISIBLE - - -
- - -
         ::= RelObjectName 'SET' ( 'VISIBLE' | 'INVISIBLE' )
-
- Referenced by: -
- - -====================================================================================================================== -AlterExpressionConstraintState -====================================================================================================================== - - -.. raw:: html - - - - - - NOT - - DEFERRABLE - - VALIDATE - - NOVALIDATE - - ENABLE - - DISABLE - - -
- - -
         ::= ( 'NOT'? 'DEFERRABLE' | 'VALIDATE' | 'NOVALIDATE' | 'ENABLE' | 'DISABLE' - )*
-
- - -====================================================================================================================== -IndexWithComment -====================================================================================================================== - - -.. raw:: html - - - - - - COMMENT - - S_CHAR_LITERAL - -
- - -
         ::= 'COMMENT' S_CHAR_LITERAL
-
- - -====================================================================================================================== -IndexOptionList -====================================================================================================================== - - -.. raw:: html - - - - - - IndexOption - -
- - -
         ::= IndexOption*
-
- Referenced by: -
- - -====================================================================================================================== -UsingIndexType -====================================================================================================================== - - -.. raw:: html - - - - - - USING - - RelObjectName - -
- - -
         ::= 'USING' RelObjectName
-
- - -====================================================================================================================== -IndexOption -====================================================================================================================== - - -.. raw:: html - - - - - - KEY_BLOCK_SIZE - - = - - S_LONG - WITH - - PARSER - - S_IDENTIFIER - COMMENT - - S_CHAR_LITERAL - VISIBLE - - INVISIBLE - - UsingIndexType - -
- - -
         ::= 'KEY_BLOCK_SIZE' '='? S_LONG
-
           | 'WITH' 'PARSER' S_IDENTIFIER
-
           | 'COMMENT' S_CHAR_LITERAL
-
           | 'VISIBLE'
-
           | 'INVISIBLE'
-
           | UsingIndexType
-
- Referenced by: -
- - -====================================================================================================================== -PartitionDefinitions -====================================================================================================================== - - -.. raw:: html - - - - - - ( - - PARTITION - - RelObjectName - VALUES - - LESS - - THAN - - ( - - Expression - ) - - MAXVALUE - - ENGINE - - = - - S_IDENTIFIER - , - - ) - - -
- - -
         ::= '(' ( 'PARTITION' RelObjectName 'VALUES' 'LESS' 'THAN' ( '(' Expression ')' | 'MAXVALUE' ) ( 'ENGINE' '=' S_IDENTIFIER )? ','? )* ')'
-
- - -====================================================================================================================== -PartitionNamesList -====================================================================================================================== - - -.. raw:: html - - - - - - ALL - - S_IDENTIFIER - , - - -
- - -
         ::= 'ALL'
-
           | S_IDENTIFIER ( ',' S_IDENTIFIER )*
-
- - -====================================================================================================================== -AlterExpressionDiscardOrImport -====================================================================================================================== - - -.. raw:: html - - - - - - DISCARD - - IMPORT - - PARTITION - - PartitionNamesList - TABLESPACE - - -
- - -
         ::= ( 'DISCARD' | 'IMPORT' ) ( 'PARTITION' PartitionNamesList )? 'TABLESPACE'
-
- Referenced by: -
- - -====================================================================================================================== -AlterExpressionAddConstraint -====================================================================================================================== - - -.. raw:: html - - - - - - CONSTRAINT - - UNIQUE - - KEY - - INDEX - - RelObjectName - - ColumnsNamesList - - RelObjectName - FOREIGN - - KEY - - ColumnsNamesList - REFERENCES - - Table - - ColumnsNamesList - - ReferentialActionsOnIndex - KEY - - ColumnsNamesList - - AlterExpressionConstraintState - PRIMARY - - KEY - - UNIQUE - - KEY - - INDEX - - ColumnsNamesList - - AlterExpressionConstraintTail - NOT - - ENFORCED - - CheckConstraintSpec - -
- - -
         ::= 'CONSTRAINT' ( 'UNIQUE' ( 'KEY' | 'INDEX' )? RelObjectName ColumnsNamesList | RelObjectName ( ( 'FOREIGN' 'KEY' ColumnsNamesList 'REFERENCES' Table ColumnsNamesList? ReferentialActionsOnIndex | 'KEY' ColumnsNamesList ) AlterExpressionConstraintState | ( 'PRIMARY' 'KEY' | 'UNIQUE' ( 'KEY' | 'INDEX' )? ) ColumnsNamesList AlterExpressionConstraintTail | 'NOT'? 'ENFORCED' | CheckConstraintSpec ) )
-
- Referenced by: -
- - -====================================================================================================================== -AlterExpressionDrop -====================================================================================================================== - - -.. raw:: html - - - - - - DROP - - PARTITION - - PartitionNamesList - - ColumnsNamesList - COLUMN - - IF - - EXISTS - - KeywordOrIdentifier - INVALIDATE - - CASCADE - - CONSTRAINTS - - INDEX - - KEY - - S_IDENTIFIER - - S_QUOTED_IDENTIFIER - UNIQUE - - FOREIGN - - KEY - - ColumnsNamesList - PRIMARY - - KEY - - CONSTRAINT - - IF - - EXISTS - - S_IDENTIFIER - - S_QUOTED_IDENTIFIER - CASCADE - - RESTRICT - - -
- - -
         ::= 'DROP' ( 'PARTITION' PartitionNamesList | ( ColumnsNamesList | 'COLUMN'? ( 'IF' 'EXISTS' )? KeywordOrIdentifier ) 'INVALIDATE'? ( 'CASCADE' 'CONSTRAINTS'? )? | ( 'INDEX' | 'KEY' ) ( S_IDENTIFIER | S_QUOTED_IDENTIFIER ) | ( ( 'UNIQUE' | 'FOREIGN' 'KEY' ) ColumnsNamesList | 'PRIMARY' 'KEY' | 'CONSTRAINT' ( 'IF' 'EXISTS' )? ( S_IDENTIFIER | S_QUOTED_IDENTIFIER ) ) ( 'CASCADE' | 'RESTRICT' )? )
-
- Referenced by: -
- - -====================================================================================================================== -AlterExpressionPartitionOp -====================================================================================================================== - - -.. raw:: html - - - - - - TRUNCATE - - ANALYZE - - CHECK - - OPTIMIZE - - REBUILD - - REPAIR - - PARTITION - - PartitionNamesList - COALESCE - - PARTITION - - S_LONG - REORGANIZE - - PARTITION - - PartitionNamesList - INTO - - PARTITION - - BY - - RANGE - - ( - - Expression - ) - - COLUMNS - - ColumnsNamesList - - PartitionDefinitions - EXCHANGE - - PARTITION - - PartitionNamesList - WITH - - TABLE - - S_IDENTIFIER - WITH - - WITHOUT - - VALIDATION - - REMOVE - - PARTITIONING - - -
- - -
         ::= ( 'TRUNCATE' | 'ANALYZE' | 'CHECK' | 'OPTIMIZE' | 'REBUILD' | 'REPAIR' - ) 'PARTITION' PartitionNamesList
-
           | 'COALESCE' 'PARTITION' S_LONG
-
           | ( 'REORGANIZE' 'PARTITION' PartitionNamesList 'INTO' | 'PARTITION' 'BY' 'RANGE' ( '(' Expression ')' | 'COLUMNS' ColumnsNamesList ) ) PartitionDefinitions
-
           | 'EXCHANGE' 'PARTITION' PartitionNamesList 'WITH' 'TABLE' S_IDENTIFIER ( ( 'WITH' | 'WITHOUT' ) 'VALIDATION' )?
-
           | 'REMOVE' 'PARTITIONING'
-
- Referenced by: -
- - -====================================================================================================================== -AlterExpressionAddAlterModify -====================================================================================================================== - - -.. raw:: html - - - - - - ADD - - ALTER - - MODIFY - - PRIMARY - - KEY - - ColumnsNamesList - - AlterExpressionConstraintState - - AlterExpressionUsingIndex - KEY - - INDEX - - RelObjectName - - UsingIndexType - - IndexColumnsWithParamsList - - IndexOptionList - - AlterExpressionConstraintState - SPATIAL - - FULLTEXT - - INDEX - - KEY - - RelObjectName - - ColumnsNamesList - - IndexOptionList - - RelObjectName - COMMENT - - S_CHAR_LITERAL - PARTITION - - PartitionDefinitions - COLUMN - - COLUMNS - - IF - - NOT - - EXISTS - - AlterExpressionColumnChanges - - AlterExpressionColumnDataType - - AlterExpressionColumnDropNotNull - - AlterExpressionColumnChanges - UNIQUE - - KEY - - INDEX - - S_IDENTIFIER - - S_QUOTED_IDENTIFIER - - ColumnsNamesList - - AlterExpressionUsingIndex - - IndexWithComment - - ForeignKeySpec - CHECK - - RelObjectName - NOT - - ENFORCED - - AlterExpressionAddConstraint - -
- - -
         ::= ( 'ADD' | 'ALTER' | 'MODIFY' ) ( 'PRIMARY' 'KEY' ColumnsNamesList AlterExpressionConstraintState AlterExpressionUsingIndex? | ( 'KEY' | 'INDEX' ) RelObjectName? UsingIndexType? IndexColumnsWithParamsList? IndexOptionList AlterExpressionConstraintState | ( 'SPATIAL' | 'FULLTEXT' ) ( 'INDEX' | 'KEY' )? RelObjectName? ColumnsNamesList IndexOptionList | RelObjectName 'COMMENT' S_CHAR_LITERAL | 'PARTITION' PartitionDefinitions | ( 'COLUMN' | 'COLUMNS' )? ( 'IF' 'NOT' 'EXISTS' )? ( AlterExpressionColumnChanges | AlterExpressionColumnDataType | AlterExpressionColumnDropNotNull ) | AlterExpressionColumnChanges | 'UNIQUE' ( 'KEY' | 'INDEX' )? ( S_IDENTIFIER | S_QUOTED_IDENTIFIER )? ColumnsNamesList AlterExpressionUsingIndex? IndexWithComment? | ForeignKeySpec | 'CHECK' RelObjectName 'NOT'? 'ENFORCED' | AlterExpressionAddConstraint )
-
- Referenced by: -
- - -====================================================================================================================== -AlterExpressionRenameOp -====================================================================================================================== - - -.. raw:: html - - - - - - RENAME - - INDEX - - KEY - - CONSTRAINT - - S_IDENTIFIER - - S_QUOTED_IDENTIFIER - TO - - S_IDENTIFIER - - S_QUOTED_IDENTIFIER - COLUMN - - KeywordOrIdentifier - TO - - KeywordOrIdentifier - -
- - -
         ::= 'RENAME' ( ( ( 'INDEX' | 'KEY' | 'CONSTRAINT' ) ( S_IDENTIFIER | S_QUOTED_IDENTIFIER ) )? 'TO' ( S_IDENTIFIER | S_QUOTED_IDENTIFIER ) | 'COLUMN'? KeywordOrIdentifier 'TO' KeywordOrIdentifier )
-
- Referenced by: -
- - -====================================================================================================================== -AlterExpression -====================================================================================================================== - - -.. raw:: html - - - - - - AlterExpressionAddAlterModify - CHANGE - - COLUMN - - KeywordOrIdentifier - - AlterExpressionColumnDataType - - AlterExpressionDrop - FORCE - - ROW - - LEVEL - - SECURITY - - NO - - FORCE - - ROW - - LEVEL - - SECURITY - - ALGORITHM - - LOCK - - ENGINE - - = - - RelObjectName - KEY_BLOCK_SIZE - - AUTO_INCREMENT - - = - - S_LONG - - AlterExpressionRenameOp - CONVERT - - TO - - CHARACTER - - SET - - S_IDENTIFIER - COLLATE - - DEFAULT - - CHARACTER - - SET - - = - - S_IDENTIFIER - COLLATE - - COLLATE - - = - - S_IDENTIFIER - COMMENT - - ENCRYPTION - - = - - S_CHAR_LITERAL - - AlterExpressionDiscardOrImport - DISABLE - - ENABLE - - ROW - - LEVEL - - SECURITY - - KEYS - - AlterExpressionPartitionOp - - captureRest - -
- - -
         ::= AlterExpressionAddAlterModify
-
           | 'CHANGE' 'COLUMN'? KeywordOrIdentifier AlterExpressionColumnDataType
-
           | AlterExpressionDrop
-
           | 'FORCE' ( 'ROW' 'LEVEL' 'SECURITY' )?
-
           | 'NO' 'FORCE' 'ROW' 'LEVEL' 'SECURITY'
-
           | ( 'ALGORITHM' | 'LOCK' | 'ENGINE' ) '='? RelObjectName
-
           | ( 'KEY_BLOCK_SIZE' | 'AUTO_INCREMENT' ) '='? S_LONG
-
           | AlterExpressionRenameOp
-
           | ( 'CONVERT' 'TO' 'CHARACTER' 'SET' ( S_IDENTIFIER 'COLLATE' )? | 'DEFAULT'? ( 'CHARACTER' 'SET' ( '='? S_IDENTIFIER 'COLLATE' )? | 'COLLATE' ) '='? ) S_IDENTIFIER
-
           | ( 'COMMENT' | 'ENCRYPTION' ) '='? S_CHAR_LITERAL
-
           | AlterExpressionDiscardOrImport
-
           | ( 'DISABLE' | 'ENABLE' ) ( 'ROW' 'LEVEL' 'SECURITY' | 'KEYS' )
-
           | AlterExpressionPartitionOp
-
           | captureRest
-
- Referenced by: -
- - -====================================================================================================================== -Alter -====================================================================================================================== - - -.. raw:: html - - - - - - ALTER - - AlterTable - - AlterSession - - AlterView - - AlterSystemStatement - - AlterSequence - - captureRest - REPLACE - - AlterView - - captureRest - -
- - -
           | 'REPLACE' ( AlterView | captureRest )
-
- Referenced by: -
- - -====================================================================================================================== -AlterTable -====================================================================================================================== - - -.. raw:: html - - - - - - TABLE - - ONLY - - IF - - EXISTS - - Table - - AlterExpression - , - - -
- - -
         ::= 'TABLE' 'ONLY'? ( 'IF' 'EXISTS' )? Table AlterExpression ( ',' AlterExpression )*
-
- Referenced by: -
- - -====================================================================================================================== -AlterSession -====================================================================================================================== - - -.. raw:: html - - - - - - SESSION - - ADVISE - - COMMIT - - ROLLBACK - - NOTHING - - CLOSE - - DATABASE - - LINK - - ENABLE - - DISABLE - - COMMIT - - IN - - PROCEDURE - - GUARD - - PARALLEL - - DML - - DDL - - QUERY - - RESUMABLE - - FORCE - - PARALLEL - - DML - - DDL - - QUERY - - SET - - S_CHAR_LITERAL - - S_IDENTIFIER - = - - S_LONG - PARALLEL - - -
- - -
         ::= 'SESSION' ( 'ADVISE' ( 'COMMIT' | 'ROLLBACK' | 'NOTHING' ) | 'CLOSE' - 'DATABASE' 'LINK' | ( 'ENABLE' | 'DISABLE' ) ( 'COMMIT' 'IN' 'PROCEDURE' | 'GUARD' - | 'PARALLEL' ( 'DML' | 'DDL' | 'QUERY' ) | 'RESUMABLE' ) | 'FORCE' 'PARALLEL' ( 'DML' - | 'DDL' | 'QUERY' ) | 'SET' ) ( S_CHAR_LITERAL | S_IDENTIFIER | '=' | S_LONG | 'PARALLEL' )*
-
- Referenced by: -
- - -====================================================================================================================== -AlterSystemStatement -====================================================================================================================== - - -.. raw:: html - - - - - - SYSTEM - - ARCHIVE - - LOG - - CHECKPOINT - - DUMP - - ACTIVE - - SESSION - - HISTORY - - ENABLE - - DISABLE - - DISTRIBUTED - - RECOVERY - - RESTRICTED - - SESSION - - FLUSH - - DISCONNECT - - KILL - - SESSION - - SWITCH - - SUSPEND - - RESUME - - QUIESCE - - RESTRICTED - - UNQIESCE - - SHUTDOWN - - REGISTER - - SET - - RESET - - captureRest - -
- - -
         ::= 'SYSTEM' ( 'ARCHIVE' 'LOG' | 'CHECKPOINT' | 'DUMP' 'ACTIVE' 'SESSION' - 'HISTORY' | ( 'ENABLE' | 'DISABLE' ) ( 'DISTRIBUTED' 'RECOVERY' | 'RESTRICTED' 'SESSION' - ) | 'FLUSH' | ( 'DISCONNECT' | 'KILL' ) 'SESSION' | 'SWITCH' | 'SUSPEND' | 'RESUME' - | 'QUIESCE' 'RESTRICTED' | 'UNQIESCE' | 'SHUTDOWN' | 'REGISTER' | 'SET' | 'RESET' - ) captureRest
-
- Referenced by: -
- - -====================================================================================================================== -Wait -====================================================================================================================== - - -.. raw:: html - - - - - - WAIT - - S_LONG - -
- -
Wait     ::= 'WAIT' S_LONG
-
- Referenced by: -
- - -====================================================================================================================== -SavepointStatement -====================================================================================================================== - - -.. raw:: html - - - - - - SAVEPOINT - - S_IDENTIFIER - -
- - -
         ::= 'SAVEPOINT' S_IDENTIFIER
-
- Referenced by: -
- - -====================================================================================================================== -RollbackStatement -====================================================================================================================== - - -.. raw:: html - - - - - - ROLLBACK - - WORK - - TO - - SAVEPOINT - - S_IDENTIFIER - FORCE - - S_CHAR_LITERAL - -
- - -
         ::= 'ROLLBACK' 'WORK'? ( 'TO' 'SAVEPOINT'? S_IDENTIFIER | 'FORCE' S_CHAR_LITERAL )?
-
- Referenced by: -
- - -====================================================================================================================== -Comment -====================================================================================================================== - - -.. raw:: html - - - - - - COMMENT - - ON - - TABLE - - VIEW - - Table - COLUMN - - Column - IS - - S_CHAR_LITERAL - -
- -
Comment  ::= 'COMMENT' 'ON' ( ( 'TABLE' | 'VIEW' ) Table | 'COLUMN' Column ) 'IS' S_CHAR_LITERAL
-
- Referenced by: -
- - -====================================================================================================================== -Grant -====================================================================================================================== - - -.. raw:: html - - - - - - GRANT - - readGrantTypes - , - - ON - - RelObjectNames - - S_IDENTIFIER - TO - - UsersList - -
- -
Grant    ::= 'GRANT' ( ( readGrantTypes ( ',' readGrantTypes )* )? 'ON' RelObjectNames | S_IDENTIFIER ) 'TO' UsersList
-
- Referenced by: -
- - -====================================================================================================================== -UsersList -====================================================================================================================== - - -.. raw:: html - - - - - - RelObjectName - , - - ColumnsNamesListItem - -
- - -
         ::= RelObjectName ( ',' ColumnsNamesListItem )*
-
- Referenced by: -
- - -====================================================================================================================== -readGrantTypes -====================================================================================================================== - - -.. raw:: html - - - - - - K_SELECT - INSERT - - UPDATE - - DELETE - - EXECUTE - - ALTER - - DROP - - -
- - -
         ::= K_SELECT
-
           | 'INSERT'
-
           | 'UPDATE'
-
           | 'DELETE'
-
           | 'EXECUTE'
-
           | 'ALTER'
-
           | 'DROP'
-
- Referenced by: -
- - -====================================================================================================================== -Sequence -====================================================================================================================== - - -.. raw:: html - - - - - - RelObjectNames - -
- - -
- Referenced by: -
- - -====================================================================================================================== -SequenceParameters -====================================================================================================================== - - -.. raw:: html - - - - - - INCREMENT - - BY - - START - - WITH - - MAXVALUE - - MINVALUE - - CACHE - - S_LONG - RESTART - - WITH - - S_LONG - NOMAXVALUE - - NOMINVALUE - - NOCYCLE - - CYCLE - - NOCACHE - - ORDER - - NOORDER - - KEEP - - NOKEEP - - SESSION - - GLOBAL - - -
- - -
         ::= ( ( 'INCREMENT' 'BY'? | 'START' 'WITH'? | 'MAXVALUE' | 'MINVALUE' | 'CACHE' - ) S_LONG | 'RESTART' ( 'WITH' S_LONG )? | 'NOMAXVALUE' | 'NOMINVALUE' | 'NOCYCLE' | 'CYCLE' | 'NOCACHE' | 'ORDER' | 'NOORDER' - | 'KEEP' | 'NOKEEP' | 'SESSION' | 'GLOBAL' )*
-
- Referenced by: -
- - -====================================================================================================================== -CreateSequence -====================================================================================================================== - - -.. raw:: html - - - - - - SEQUENCE - - Sequence - AS - - S_IDENTIFIER - - DATA_TYPE - - SequenceParameters - -
- - -
         ::= 'SEQUENCE' Sequence ( 'AS' ( S_IDENTIFIER | DATA_TYPE ) )? SequenceParameters
-
- Referenced by: -
- - -====================================================================================================================== -AlterSequence -====================================================================================================================== - - -.. raw:: html - - - - - - SEQUENCE - - Sequence - - SequenceParameters - -
- - -
         ::= 'SEQUENCE' Sequence SequenceParameters
-
- Referenced by: -
- - -====================================================================================================================== -Create -====================================================================================================================== - - -.. raw:: html - - - - - - CREATE - - OR - - REPLACE - - CreateFunctionStatement - - CreateSchema - - CreateSequence - - CreateSynonym - - CreateTable - - CreateView - - CreatePolicy - TRIGGER - - DOMAIN - - captureRest - - CreateIndex - -
- -
Create   ::= 'CREATE' ( 'OR' 'REPLACE' )? ( CreateFunctionStatement | CreateSchema | CreateSequence | CreateSynonym | CreateTable | CreateView | CreatePolicy | ( 'TRIGGER' | 'DOMAIN' )? captureRest | CreateIndex )
-
- Referenced by: -
- - -====================================================================================================================== -CreateFunctionStatement -====================================================================================================================== - - -.. raw:: html - - - - - - FUNCTION - - PROCEDURE - - captureFunctionBody - -
- - -
         ::= ( 'FUNCTION' | 'PROCEDURE' ) captureFunctionBody
-
- Referenced by: -
- - -====================================================================================================================== -CreateSynonym -====================================================================================================================== - - -.. raw:: html - - - - - - PUBLIC - - SYNONYM - - Synonym - FOR - - RelObjectNames - -
- - -
         ::= 'PUBLIC'? 'SYNONYM' Synonym 'FOR' RelObjectNames
-
- Referenced by: -
- - -====================================================================================================================== -Synonym -====================================================================================================================== - - -.. raw:: html - - - - - - RelObjectNames - -
- - -
- Referenced by: -
- - -====================================================================================================================== -CreatePolicy -====================================================================================================================== - - -.. raw:: html - - - - - - POLICY - - RelObjectName - ON - - Table - FOR - - ALL - - K_SELECT - INSERT - - UPDATE - - DELETE - - TO - - RelObjectName - , - - USING - - ( - - Expression - ) - - WITH - - CHECK - - ( - - Expression - ) - - -
- - -
         ::= 'POLICY' RelObjectName 'ON' Table ( 'FOR' ( 'ALL' | K_SELECT | 'INSERT' | 'UPDATE' | 'DELETE' ) )? ( 'TO' RelObjectName ( ',' RelObjectName )* )? ( 'USING' '(' Expression ')' )? ( 'WITH' 'CHECK' '(' Expression ')' )?
-
- Referenced by: -
- - -====================================================================================================================== -UnsupportedStatement -====================================================================================================================== - - -.. raw:: html - - - - - - captureUnsupportedStatementDeclaration - -
- - - -
- Referenced by: -
- - -====================================================================================================================== -IdentifierChain -====================================================================================================================== - - -.. raw:: html - - - - - - RelObjectNameExt - . - - -
- - -
         ::= RelObjectNameExt ( '.' RelObjectNameExt )*
-
- - -====================================================================================================================== -IdentifierChain2 -====================================================================================================================== - - -.. raw:: html - - - - - - . - - RelObjectNameExt - -
- - -
         ::= ( '.' RelObjectNameExt )*
-
- Referenced by: -
- - -====================================================================================================================== -CharacterPrimary -====================================================================================================================== - - -.. raw:: html - - - - - - TranscodingFunction - - TrimFunction - -
- - -
         ::= TranscodingFunction
-
           | TrimFunction
-
- Referenced by: -
- - -====================================================================================================================== -TranscodingFunction -====================================================================================================================== - - -.. raw:: html - - - - - - TRY_CONVERT - - SAFE_CONVERT - - CONVERT - - ( - - ColDataType - , - - Expression - , - - S_LONG - - Expression - USING - - IdentifierChain - ) - - -
- - -
         ::= ( 'TRY_CONVERT' | 'SAFE_CONVERT' | 'CONVERT' ) '(' ( ColDataType ',' Expression ( ',' S_LONG )? | Expression 'USING' IdentifierChain ) ')'
-
- Referenced by: -
- - -====================================================================================================================== -TrimFunction -====================================================================================================================== - - -.. raw:: html - - - - - - TRIM - - ( - - LEADING - - TRAILING - - BOTH - - Expression - , - - FROM - - Expression - ) - - -
- - -
         ::= 'TRIM' '(' ( 'LEADING' | 'TRAILING' | 'BOTH' )? Expression? ( ( ',' | 'FROM' ) Expression )? ')'
-
- Referenced by: -
- - -====================================================================================================================== -SnowflakeTimeTravelAt -====================================================================================================================== - - -.. raw:: html - - - - - - AT - - ( - - K_DATETIMELITERAL - OFFSET - - => - - Expression - STATEMENT - - => - - S_CHAR_LITERAL - - S_IDENTIFIER - - S_QUOTED_IDENTIFIER - STREAM - - => - - S_CHAR_LITERAL - ) - - -
- - -
         ::= 'AT' '(' ( ( K_DATETIMELITERAL | 'OFFSET' ) '=>' Expression | 'STATEMENT' '=>' ( S_CHAR_LITERAL | S_IDENTIFIER | S_QUOTED_IDENTIFIER ) | 'STREAM' '=>' S_CHAR_LITERAL ) ')'
-
- - -====================================================================================================================== -SnowflakeTimeTravelBefore -====================================================================================================================== - - -.. raw:: html - - - - - - BEFORE - - ( - - STATEMENT - - => - - S_CHAR_LITERAL - - S_IDENTIFIER - - S_QUOTED_IDENTIFIER - ) - - -
- - -
         ::= 'BEFORE' '(' 'STATEMENT' '=>' ( S_CHAR_LITERAL | S_IDENTIFIER | S_QUOTED_IDENTIFIER ) ')'
-
- - -====================================================================================================================== -SnowflakeTimeTravelChange -====================================================================================================================== - - -.. raw:: html - - - - - - CHANGES - - ( - - INFORMATION - - => - - DEFAULT - - APPEND_ONLY - - ) - - SnowflakeTimeTravelAt - - SnowflakeTimeTravelBefore - END - - ( - - K_DATETIMELITERAL - OFFSET - - => - - Expression - STATEMENT - - => - - S_CHAR_LITERAL - - S_IDENTIFIER - - S_QUOTED_IDENTIFIER - ) - - -
- - -
         ::= 'CHANGES' '(' 'INFORMATION' '=>' ( 'DEFAULT' | 'APPEND_ONLY' ) ')' ( - SnowflakeTimeTravelAt | SnowflakeTimeTravelBefore ) ( 'END' '(' ( ( K_DATETIMELITERAL | 'OFFSET' ) '=>' Expression | 'STATEMENT' '=>' ( S_CHAR_LITERAL | S_IDENTIFIER | S_QUOTED_IDENTIFIER ) ) ')' )?
-
- Referenced by: -
- - -====================================================================================================================== -DataBricksTemporalSpec -====================================================================================================================== - - -.. raw:: html - - - - - - @ - - @V - - S_CHAR_LITERAL - - S_LONG - FOR - - SYSTEM_TIMESTAMP - - K_DATETIMELITERAL - AS - - OF - - Expression - SYSTEM_VERSION - - VERSION - - AS - - OF - - S_LONG - - S_CHAR_LITERAL - -
- - -
         ::= ( '@' | '@V' ) ( S_CHAR_LITERAL | S_LONG )
-
           | 'FOR'? ( 'SYSTEM_TIMESTAMP' | K_DATETIMELITERAL ) 'AS' 'OF' Expression
-
           | ( 'SYSTEM_VERSION' | 'VERSION' ) 'AS' 'OF' ( S_LONG | S_CHAR_LITERAL )
-
- Referenced by: -
- - -====================================================================================================================== -BigQueryHistoricalVersion -====================================================================================================================== - - -.. raw:: html - - - - - - FOR - - SYSTEM_TIME - - AS - - OF - - Expression - -
- - -
         ::= 'FOR' 'SYSTEM_TIME' 'AS' 'OF' Expression
-
- Referenced by: -
- - -====================================================================================================================== -TimeTravelBeforeAlias -====================================================================================================================== - - -.. raw:: html - - - - - - SnowflakeTimeTravelAt - - SnowflakeTimeTravelBefore - - SnowflakeTimeTravelChange - - DataBricksTemporalSpec - -
- - -
         ::= SnowflakeTimeTravelAt
-
           | SnowflakeTimeTravelBefore
-
           | SnowflakeTimeTravelChange
-
           | DataBricksTemporalSpec
-
- Referenced by: -
- - -====================================================================================================================== -TimeTravelAfterAlias -====================================================================================================================== - - -.. raw:: html - - - - - - BigQueryHistoricalVersion - -
- - -
         ::= BigQueryHistoricalVersion
-
- Referenced by: -
- - -====================================================================================================================== -WHITESPACE -====================================================================================================================== - - -.. raw:: html - - - - - - - - [#x9] - - [#xD] - - [#xA] - - -
- - -
         ::= [ #x9#xD#xA]
-
- - -====================================================================================================================== -K_ISOLATION -====================================================================================================================== - - -.. raw:: html - - - - - - UR - - RS - - RR - - CS - - -
- - -
         ::= 'UR'
-
           | 'RS'
-
           | 'RR'
-
           | 'CS'
-
- Referenced by: -
- - -====================================================================================================================== -K_NEXTVAL -====================================================================================================================== - - -.. raw:: html - - - - - - NEXTVAL - - - - FOR - - NEXT - - - - VALUE - - - - FOR - - -
- - -
         ::= 'NEXTVAL' ( ' '+ 'FOR' )?
-
           | 'NEXT' ' '+ 'VALUE' ' '+ 'FOR'
-
- - -====================================================================================================================== -K_TEXT_LITERAL -====================================================================================================================== - - -.. raw:: html - - - - - - TEXT - - TINYTEXT - - MEDIUMTEXT - - LONGTEXT - - -
- - -
         ::= 'TEXT'
-
           | 'TINYTEXT'
-
           | 'MEDIUMTEXT'
-
           | 'LONGTEXT'
-
- Referenced by: -
- - -====================================================================================================================== -K_TIME_KEY_EXPR -====================================================================================================================== - - -.. raw:: html - - - - - - CURRENT - - _ - - - - TIMESTAMP - - TIME - - DATE - - TIMEZONE - - () - - -
- - -
         ::= 'CURRENT' ( '_' | ' '+ ) ( 'TIMESTAMP' | 'TIME' | 'DATE' | 'TIMEZONE' - ) '()'?
-
- - -====================================================================================================================== -K_STRING_FUNCTION_NAME -====================================================================================================================== - - -.. raw:: html - - - - - - SUBSTR - - SUBSTRING - - TRIM - - POSITION - - OVERLAY - - -
- - -
         ::= 'SUBSTR'
-
           | 'SUBSTRING'
-
           | 'TRIM'
-
           | 'POSITION'
-
           | 'OVERLAY'
-
- - -====================================================================================================================== -K_DATETIMELITERAL -====================================================================================================================== - - -.. raw:: html - - - - - - DATE - - DATETIME - - TIME - - TIMESTAMP - - TIMESTAMPTZ - - -
- - -
         ::= 'DATE'
-
           | 'DATETIME'
-
           | 'TIME'
-
           | 'TIMESTAMP'
-
           | 'TIMESTAMPTZ'
-
- - -====================================================================================================================== -K_DATE_LITERAL -====================================================================================================================== - - -.. raw:: html - - - - - - YEAR - - MONTH - - DAY - - HOUR - - MINUTE - - SECOND - - -
- - -
         ::= 'YEAR'
-
           | 'MONTH'
-
           | 'DAY'
-
           | 'HOUR'
-
           | 'MINUTE'
-
           | 'SECOND'
-
- - -====================================================================================================================== -K_SELECT -====================================================================================================================== - - -.. raw:: html - - - - - - SELECT - - SEL - - -
- -
K_SELECT ::= 'SELECT'
-
           | 'SEL'
-
- - -====================================================================================================================== -K_SIMILAR_TO -====================================================================================================================== - - -.. raw:: html - - - - - - SIMILAR - - - - TO - - -
- - -
         ::= 'SIMILAR' ' '+ 'TO'
-
- Referenced by: -
- - -====================================================================================================================== -ST_SEMICOLON -====================================================================================================================== - - -.. raw:: html - - - - - - ; - - [#xA] - - / - - [#xA] - - go - - [#xA] - - -
- - -
         ::= ';'
-
           | #xA ( [/#xA] | 'go' ) #xA
-
- Referenced by: -
- - -====================================================================================================================== -OP_GREATERTHANEQUALS -====================================================================================================================== - - -.. raw:: html - - - - - - > - - WHITESPACE - = - - -
- - -
         ::= '>' WHITESPACE* '='
-
- Referenced by: -
- - -====================================================================================================================== -OP_MINORTHANEQUALS -====================================================================================================================== - - -.. raw:: html - - - - - - < - - WHITESPACE - = - - -
- - -
         ::= '<' WHITESPACE* '='
-
- Referenced by: -
- - -====================================================================================================================== -OP_NOTEQUALSSTANDARD -====================================================================================================================== - - -.. raw:: html - - - - - - < - - WHITESPACE - > - - -
- - -
         ::= '<' WHITESPACE* '>'
-
- Referenced by: -
- - -====================================================================================================================== -OP_NOTEQUALSBANG -====================================================================================================================== - - -.. raw:: html - - - - - - ! - - WHITESPACE - = - - -
- - -
         ::= '!' WHITESPACE* '='
-
- Referenced by: -
- - -====================================================================================================================== -DT_ZONE -====================================================================================================================== - - -.. raw:: html - - - - - - K_DATETIMELITERAL - - WHITESPACE - ( - - S_LONG - ) - - WHITESPACE - WITH - - WITHOUT - - WHITESPACE - LOCAL - - WHITESPACE - TIME - - WHITESPACE - ZONE - - -
- -
DT_ZONE  ::= K_DATETIMELITERAL WHITESPACE* ( '(' S_LONG ')' )? WHITESPACE* ( 'WITH' | 'WITHOUT' ) WHITESPACE+ ( 'LOCAL' WHITESPACE+ )? 'TIME' WHITESPACE+ 'ZONE'
-
- - -====================================================================================================================== -DATA_TYPE -====================================================================================================================== - - -.. raw:: html - - - - - - BISTRING - - TYPE_BLOB - - TYPE_BOOLEAN - ENUM - - TYPE_REAL - - TYPE_DOUBLE - UUID - - MAP - - TYPE_TINYINT - - TYPE_SMALLINT - - TYPE_INTEGER - - TYPE_BIGINT - HUGEINT - - UTINYINT - - USMALLINT - - UINTEGER - - UBIGINT - - UHUGEINT - - TYPE_DECIMAL - - TYPE_VARCHAR - TIMETZ - - TYPE_TIMESTAMP - -
- - -
         ::= 'BISTRING'
-
           | TYPE_BLOB
-
           | TYPE_BOOLEAN
-
           | 'ENUM'
-
           | TYPE_REAL
-
           | TYPE_DOUBLE
-
           | 'UUID'
-
           | 'MAP'
-
           | TYPE_TINYINT
-
           | TYPE_SMALLINT
-
           | TYPE_INTEGER
-
           | TYPE_BIGINT
-
           | 'HUGEINT'
-
           | 'UTINYINT'
-
           | 'USMALLINT'
-
           | 'UINTEGER'
-
           | 'UBIGINT'
-
           | 'UHUGEINT'
-
           | TYPE_DECIMAL
-
           | TYPE_VARCHAR
-
           | 'TIMETZ'
-
           | TYPE_TIMESTAMP
-
- - -====================================================================================================================== -TYPE_BLOB -====================================================================================================================== - - -.. raw:: html - - - - - - BLOB - - BYTEA - - BINARY - - VARBINARY - - BYTES - - -
- - -
         ::= 'BLOB'
-
           | 'BYTEA'
-
           | 'BINARY'
-
           | 'VARBINARY'
-
           | 'BYTES'
-
- Referenced by: -
- - -====================================================================================================================== -TYPE_BOOLEAN -====================================================================================================================== - - -.. raw:: html - - - - - - BOOLEAN - - BOOL - - -
- - -
         ::= 'BOOLEAN'
-
           | 'BOOL'
-
- Referenced by: -
- - -====================================================================================================================== -TYPE_DECIMAL -====================================================================================================================== - - -.. raw:: html - - - - - - DECIMAL - - NUMBER - - NUMERIC - - -
- - -
         ::= 'DECIMAL'
-
           | 'NUMBER'
-
           | 'NUMERIC'
-
- Referenced by: -
- - -====================================================================================================================== -TYPE_TINYINT -====================================================================================================================== - - -.. raw:: html - - - - - - TINYINT - - INT1 - - -
- - -
         ::= 'TINYINT'
-
           | 'INT1'
-
- Referenced by: -
- - -====================================================================================================================== -TYPE_SMALLINT -====================================================================================================================== - - -.. raw:: html - - - - - - SMALLINT - - INT2 - - SHORT - - -
- - -
         ::= 'SMALLINT'
-
           | 'INT2'
-
           | 'SHORT'
-
- Referenced by: -
- - -====================================================================================================================== -TYPE_INTEGER -====================================================================================================================== - - -.. raw:: html - - - - - - INTEGER - - INT - - INT4 - - SIGNED - - UNSIGNED - - -
- - -
         ::= 'INTEGER'
-
           | 'INT'
-
           | 'INT4'
-
           | 'SIGNED'
-
           | 'UNSIGNED'
-
- Referenced by: -
- - -====================================================================================================================== -TYPE_BIGINT -====================================================================================================================== - - -.. raw:: html - - - - - - BIGINT - - INT8 - - LONG - - -
- - -
         ::= 'BIGINT'
-
           | 'INT8'
-
           | 'LONG'
-
- Referenced by: -
- - -====================================================================================================================== -TYPE_REAL -====================================================================================================================== - - -.. raw:: html - - - - - - REAL - - FLOAT4 - - FLOAT - - -
- - -
         ::= 'REAL'
-
           | 'FLOAT4'
-
           | 'FLOAT'
-
- Referenced by: -
- - -====================================================================================================================== -TYPE_DOUBLE -====================================================================================================================== - - -.. raw:: html - - - - - - DOUBLE - - PRECISION - - FLOAT8 - - FLOAT64 - - -
- - -
         ::= 'DOUBLE'
-
           | 'PRECISION'
-
           | 'FLOAT8'
-
           | 'FLOAT64'
-
- Referenced by: -
- - -====================================================================================================================== -TYPE_VARCHAR -====================================================================================================================== - - -.. raw:: html - - - - - - NVARCHAR - - VARCHAR - - NCHAR - - CHAR - - BPCHAR - - TEXT - - STRING - - CHARACTER - - VARYING - - -
- - -
         ::= 'NVARCHAR'
-
           | 'VARCHAR'
-
           | 'NCHAR'
-
           | 'CHAR'
-
           | 'BPCHAR'
-
           | 'TEXT'
-
           | 'STRING'
-
           | 'CHARACTER'
-
           | 'VARYING'
-
- Referenced by: -
- - -====================================================================================================================== -TYPE_TIMESTAMP -====================================================================================================================== - - -.. raw:: html - - - - - - TIMESTAMP_NS - - TIMESTAMP_MS - - TIMESTAMP_S - - -
- - -
         ::= 'TIMESTAMP_NS'
-
           | 'TIMESTAMP_MS'
-
           | 'TIMESTAMP_S'
-
- Referenced by: -
- - -====================================================================================================================== -S_DOUBLE -====================================================================================================================== - - -.. raw:: html - - - - - - S_LONG - . - - S_LONG - e - - E - - + - - [#x2D] - - S_LONG - - S_LONG - . - - e - - E - - + - - [#x2D] - - S_LONG - e - - E - - + - - [#x2D] - - S_LONG - -
- -
S_DOUBLE ::= S_LONG? '.' S_LONG ( [eE] [+#x2D]? S_LONG )?
-
           | S_LONG ( '.' ( [eE] [+#x2D]? S_LONG )? | [eE] [+#x2D]? S_LONG )
-
- - -====================================================================================================================== -S_LONG -====================================================================================================================== - - -.. raw:: html - - - - - - DIGIT - -
- -
S_LONG   ::= DIGIT+
-
- - -====================================================================================================================== -DIGIT -====================================================================================================================== - - -.. raw:: html - - - - - - [0-9] - - -
- -
DIGIT    ::= [0-9]
-
- Referenced by: -
- - -====================================================================================================================== -S_HEX -====================================================================================================================== - - -.. raw:: html - - - - - - X - - ' - - HEX_VALUE - ' - - - - 0x - - HEX_VALUE - -
- -
S_HEX    ::= 'X' ( "'" HEX_VALUE* "'" ' '* )+
-
           | '0x' HEX_VALUE+
-
- - -====================================================================================================================== -HEX_VALUE -====================================================================================================================== - - -.. raw:: html - - - - - - [0-9] - - [A-F] - - - - -
- - -
         ::= [0-9A-F ]
-
- Referenced by: -
- - -====================================================================================================================== -LINE_COMMENT -====================================================================================================================== - - -.. raw:: html - - - - - - -- - - // - - [^#xD#xA] - - -
- - -
         ::= ( '--' | '//' ) [^#xD#xA]*
-
- Not referenced by any. -
- - -====================================================================================================================== -MULTI_LINE_COMMENT -====================================================================================================================== - - -.. raw:: html - - - - - - . - - -
- - -
         ::= .
-
- Not referenced by any. -
- - -====================================================================================================================== -S_PARAMETER -====================================================================================================================== - - -.. raw:: html - - - - - - $ - - [0-9] - - -
- - -
         ::= '$' [0-9]+
-
- Referenced by: -
- - -====================================================================================================================== -S_IDENTIFIER -====================================================================================================================== - - -.. raw:: html - - - - - - LETTER - - PART_LETTER - $ - - PART_LETTER_NO_DOLLAR - - PART_LETTER - -
- - -
         ::= LETTER PART_LETTER*
-
           | '$' ( PART_LETTER_NO_DOLLAR PART_LETTER* )?
-
- - -====================================================================================================================== -LETTER -====================================================================================================================== - - -.. raw:: html - - - - - - UnicodeIdentifierStart - - Nd - _ - - [#x23] - - -
- - -
           | Nd
-
           | [_#x23]
-
- Referenced by: -
- - -====================================================================================================================== -PART_LETTER_NO_DOLLAR -====================================================================================================================== - - -.. raw:: html - - - - - - UnicodeIdentifierStart - - UnicodeIdentifierExtend - _ - - @ - - [#x23] - - -
- - -
         ::= UnicodeIdentifierStart
-
           | UnicodeIdentifierExtend
-
           | [_@#x23]
-
- Referenced by: -
- - -====================================================================================================================== -PART_LETTER -====================================================================================================================== - - -.. raw:: html - - - - - - UnicodeIdentifierStart - - UnicodeIdentifierExtend - $ - - _ - - @ - - [#x23] - - -
- - -
         ::= UnicodeIdentifierStart
-
           | UnicodeIdentifierExtend
-
           | [$_@#x23]
-
- Referenced by: -
- - -====================================================================================================================== -S_AT_IDENTIFIER -====================================================================================================================== - - -.. raw:: html - - - - - - @ - - @ - - S_IDENTIFIER - -
- - -
         ::= '@' '@'? S_IDENTIFIER
-
- Referenced by: -
- - -====================================================================================================================== -UnicodeIdentifierStart -====================================================================================================================== - - -.. raw:: html - - - - - - [#xB7] - - Ll - - Lm - - Lo - - Lt - - Lu - - Nl - - CJK - -
- - -
         ::= #xB7
-
           | Ll
-
           | Lm
-
           | Lo
-
           | Lt
-
           | Lu
-
           | Nl
-
           | CJK
-
- - -====================================================================================================================== -Ll -====================================================================================================================== - - -.. raw:: html - - - - - - [a-z] - - [#xB5] - - [#xDF-#xF6] - - [#xF8-#xFF] - - [#x101] - - [#x103] - - [#x105] - - [#x107] - - [#x109] - - [#x10B] - - [#x10D] - - [#x10F] - - [#x111] - - [#x113] - - [#x115] - - [#x117] - - [#x119] - - [#x11B] - - [#x11D] - - [#x11F] - - [#x121] - - [#x123] - - [#x125] - - [#x127] - - [#x129] - - [#x12B] - - [#x12D] - - [#x12F] - - [#x131] - - [#x133] - - [#x135] - - [#x137-#x138] - - [#x13A] - - [#x13C] - - [#x13E] - - [#x140] - - [#x142] - - [#x144] - - [#x146] - - [#x148-#x149] - - [#x14B] - - [#x14D] - - [#x14F] - - [#x151] - - [#x153] - - [#x155] - - [#x157] - - [#x159] - - [#x15B] - - [#x15D] - - [#x15F] - - [#x161] - - [#x163] - - [#x165] - - [#x167] - - [#x169] - - [#x16B] - - [#x16D] - - [#x16F] - - [#x171] - - [#x173] - - [#x175] - - [#x177] - - [#x17A] - - [#x17C] - - [#x17E-#x180] - - [#x183] - - [#x185] - - [#x188] - - [#x18C-#x18D] - - [#x192] - - [#x195] - - [#x199-#x19B] - - [#x19E] - - [#x1A1] - - [#x1A3] - - [#x1A5] - - [#x1A8] - - [#x1AA-#x1AB] - - [#x1AD] - - [#x1B0] - - [#x1B4] - - [#x1B6] - - [#x1B9-#x1BA] - - [#x1BD-#x1BF] - - [#x1C6] - - [#x1C9] - - [#x1CC] - - [#x1CE] - - [#x1D0] - - [#x1D2] - - [#x1D4] - - [#x1D6] - - [#x1D8] - - [#x1DA] - - [#x1DC-#x1DD] - - [#x1DF] - - [#x1E1] - - [#x1E3] - - [#x1E5] - - [#x1E7] - - [#x1E9] - - [#x1EB] - - [#x1ED] - - [#x1EF-#x1F0] - - [#x1F3] - - [#x1F5] - - [#x1F9] - - [#x1FB] - - [#x1FD] - - [#x1FF] - - [#x201] - - [#x203] - - [#x205] - - [#x207] - - [#x209] - - [#x20B] - - [#x20D] - - [#x20F] - - [#x211] - - [#x213] - - [#x215] - - [#x217] - - [#x219] - - [#x21B] - - [#x21D] - - [#x21F] - - [#x221] - - [#x223] - - [#x225] - - [#x227] - - [#x229] - - [#x22B] - - [#x22D] - - [#x22F] - - [#x231] - - [#x233-#x239] - - [#x23C] - - [#x23F-#x240] - - [#x242] - - [#x247] - - [#x249] - - [#x24B] - - [#x24D] - - [#x24F-#x293] - - [#x295-#x2AF] - - [#x371] - - [#x373] - - [#x377] - - [#x37B-#x37D] - - [#x390] - - [#x3AC-#x3CE] - - [#x3D0-#x3D1] - - [#x3D5-#x3D7] - - [#x3D9] - - [#x3DB] - - [#x3DD] - - [#x3DF] - - [#x3E1] - - [#x3E3] - - [#x3E5] - - [#x3E7] - - [#x3E9] - - [#x3EB] - - [#x3ED] - - [#x3EF-#x3F3] - - [#x3F5] - - [#x3F8] - - [#x3FB-#x3FC] - - [#x430-#x45F] - - [#x461] - - [#x463] - - [#x465] - - [#x467] - - [#x469] - - [#x46B] - - [#x46D] - - [#x46F] - - [#x471] - - [#x473] - - [#x475] - - [#x477] - - [#x479] - - [#x47B] - - [#x47D] - - [#x47F] - - [#x481] - - [#x48B] - - [#x48D] - - [#x48F] - - [#x491] - - [#x493] - - [#x495] - - [#x497] - - [#x499] - - [#x49B] - - [#x49D] - - [#x49F] - - [#x4A1] - - [#x4A3] - - [#x4A5] - - [#x4A7] - - [#x4A9] - - [#x4AB] - - [#x4AD] - - [#x4AF] - - [#x4B1] - - [#x4B3] - - [#x4B5] - - [#x4B7] - - [#x4B9] - - [#x4BB] - - [#x4BD] - - [#x4BF] - - [#x4C2] - - [#x4C4] - - [#x4C6] - - [#x4C8] - - [#x4CA] - - [#x4CC] - - [#x4CE-#x4CF] - - [#x4D1] - - [#x4D3] - - [#x4D5] - - [#x4D7] - - [#x4D9] - - [#x4DB] - - [#x4DD] - - [#x4DF] - - [#x4E1] - - [#x4E3] - - [#x4E5] - - [#x4E7] - - [#x4E9] - - [#x4EB] - - [#x4ED] - - [#x4EF] - - [#x4F1] - - [#x4F3] - - [#x4F5] - - [#x4F7] - - [#x4F9] - - [#x4FB] - - [#x4FD] - - [#x4FF] - - [#x501] - - [#x503] - - [#x505] - - [#x507] - - [#x509] - - [#x50B] - - [#x50D] - - [#x50F] - - [#x511] - - [#x513] - - [#x515] - - [#x517] - - [#x519] - - [#x51B] - - [#x51D] - - [#x51F] - - [#x521] - - [#x523] - - [#x525] - - [#x527] - - [#x529] - - [#x52B] - - [#x52D] - - [#x52F] - - [#x560-#x588] - - [#x10D0-#x10FA] - - [#x10FD-#x10FF] - - [#x13F8-#x13FD] - - [#x1C80-#x1C88] - - [#x1D00-#x1D2B] - - [#x1D6B-#x1D77] - - [#x1D79-#x1D9A] - - [#x1E01] - - [#x1E03] - - [#x1E05] - - [#x1E07] - - [#x1E09] - - [#x1E0B] - - [#x1E0D] - - [#x1E0F] - - [#x1E11] - - [#x1E13] - - [#x1E15] - - [#x1E17] - - [#x1E19] - - [#x1E1B] - - [#x1E1D] - - [#x1E1F] - - [#x1E21] - - [#x1E23] - - [#x1E25] - - [#x1E27] - - [#x1E29] - - [#x1E2B] - - [#x1E2D] - - [#x1E2F] - - [#x1E31] - - [#x1E33] - - [#x1E35] - - [#x1E37] - - [#x1E39] - - [#x1E3B] - - [#x1E3D] - - [#x1E3F] - - [#x1E41] - - [#x1E43] - - [#x1E45] - - [#x1E47] - - [#x1E49] - - [#x1E4B] - - [#x1E4D] - - [#x1E4F] - - [#x1E51] - - [#x1E53] - - [#x1E55] - - [#x1E57] - - [#x1E59] - - [#x1E5B] - - [#x1E5D] - - [#x1E5F] - - [#x1E61] - - [#x1E63] - - [#x1E65] - - [#x1E67] - - [#x1E69] - - [#x1E6B] - - [#x1E6D] - - [#x1E6F] - - [#x1E71] - - [#x1E73] - - [#x1E75] - - [#x1E77] - - [#x1E79] - - [#x1E7B] - - [#x1E7D] - - [#x1E7F] - - [#x1E81] - - [#x1E83] - - [#x1E85] - - [#x1E87] - - [#x1E89] - - [#x1E8B] - - [#x1E8D] - - [#x1E8F] - - [#x1E91] - - [#x1E93] - - [#x1E95-#x1E9D] - - [#x1E9F] - - [#x1EA1] - - [#x1EA3] - - [#x1EA5] - - [#x1EA7] - - [#x1EA9] - - [#x1EAB] - - [#x1EAD] - - [#x1EAF] - - [#x1EB1] - - [#x1EB3] - - [#x1EB5] - - [#x1EB7] - - [#x1EB9] - - [#x1EBB] - - [#x1EBD] - - [#x1EBF] - - [#x1EC1] - - [#x1EC3] - - [#x1EC5] - - [#x1EC7] - - [#x1EC9] - - [#x1ECB] - - [#x1ECD] - - [#x1ECF] - - [#x1ED1] - - [#x1ED3] - - [#x1ED5] - - [#x1ED7] - - [#x1ED9] - - [#x1EDB] - - [#x1EDD] - - [#x1EDF] - - [#x1EE1] - - [#x1EE3] - - [#x1EE5] - - [#x1EE7] - - [#x1EE9] - - [#x1EEB] - - [#x1EED] - - [#x1EEF] - - [#x1EF1] - - [#x1EF3] - - [#x1EF5] - - [#x1EF7] - - [#x1EF9] - - [#x1EFB] - - [#x1EFD] - - [#x1EFF-#x1F07] - - [#x1F10-#x1F15] - - [#x1F20-#x1F27] - - [#x1F30-#x1F37] - - [#x1F40-#x1F45] - - [#x1F50-#x1F57] - - [#x1F60-#x1F67] - - [#x1F70-#x1F7D] - - [#x1F80-#x1F87] - - [#x1F90-#x1F97] - - [#x1FA0-#x1FA7] - - [#x1FB0-#x1FB4] - - [#x1FB6-#x1FB7] - - [#x1FBE] - - [#x1FC2-#x1FC4] - - [#x1FC6-#x1FC7] - - [#x1FD0-#x1FD3] - - [#x1FD6-#x1FD7] - - [#x1FE0-#x1FE7] - - [#x1FF2-#x1FF4] - - [#x1FF6-#x1FF7] - - [#x210A] - - [#x210E-#x210F] - - [#x2113] - - [#x212F] - - [#x2134] - - [#x2139] - - [#x213C-#x213D] - - [#x2146-#x2149] - - [#x214E] - - [#x2184] - - [#x2C30-#x2C5F] - - [#x2C61] - - [#x2C65-#x2C66] - - [#x2C68] - - [#x2C6A] - - [#x2C6C] - - [#x2C71] - - [#x2C73-#x2C74] - - [#x2C76-#x2C7B] - - [#x2C81] - - [#x2C83] - - [#x2C85] - - [#x2C87] - - [#x2C89] - - [#x2C8B] - - [#x2C8D] - - [#x2C8F] - - [#x2C91] - - [#x2C93] - - [#x2C95] - - [#x2C97] - - [#x2C99] - - [#x2C9B] - - [#x2C9D] - - [#x2C9F] - - [#x2CA1] - - [#x2CA3] - - [#x2CA5] - - [#x2CA7] - - [#x2CA9] - - [#x2CAB] - - [#x2CAD] - - [#x2CAF] - - [#x2CB1] - - [#x2CB3] - - [#x2CB5] - - [#x2CB7] - - [#x2CB9] - - [#x2CBB] - - [#x2CBD] - - [#x2CBF] - - [#x2CC1] - - [#x2CC3] - - [#x2CC5] - - [#x2CC7] - - [#x2CC9] - - [#x2CCB] - - [#x2CCD] - - [#x2CCF] - - [#x2CD1] - - [#x2CD3] - - [#x2CD5] - - [#x2CD7] - - [#x2CD9] - - [#x2CDB] - - [#x2CDD] - - [#x2CDF] - - [#x2CE1] - - [#x2CE3-#x2CE4] - - [#x2CEC] - - [#x2CEE] - - [#x2CF3] - - [#x2D00-#x2D25] - - [#x2D27] - - [#x2D2D] - - [#xA641] - - [#xA643] - - [#xA645] - - [#xA647] - - [#xA649] - - [#xA64B] - - [#xA64D] - - [#xA64F] - - [#xA651] - - [#xA653] - - [#xA655] - - [#xA657] - - [#xA659] - - [#xA65B] - - [#xA65D] - - [#xA65F] - - [#xA661] - - [#xA663] - - [#xA665] - - [#xA667] - - [#xA669] - - [#xA66B] - - [#xA66D] - - [#xA681] - - [#xA683] - - [#xA685] - - [#xA687] - - [#xA689] - - [#xA68B] - - [#xA68D] - - [#xA68F] - - [#xA691] - - [#xA693] - - [#xA695] - - [#xA697] - - [#xA699] - - [#xA69B] - - [#xA723] - - [#xA725] - - [#xA727] - - [#xA729] - - [#xA72B] - - [#xA72D] - - [#xA72F-#xA731] - - [#xA733] - - [#xA735] - - [#xA737] - - [#xA739] - - [#xA73B] - - [#xA73D] - - [#xA73F] - - [#xA741] - - [#xA743] - - [#xA745] - - [#xA747] - - [#xA749] - - [#xA74B] - - [#xA74D] - - [#xA74F] - - [#xA751] - - [#xA753] - - [#xA755] - - [#xA757] - - [#xA759] - - [#xA75B] - - [#xA75D] - - [#xA75F] - - [#xA761] - - [#xA763] - - [#xA765] - - [#xA767] - - [#xA769] - - [#xA76B] - - [#xA76D] - - [#xA76F] - - [#xA771-#xA778] - - [#xA77A] - - [#xA77C] - - [#xA77F] - - [#xA781] - - [#xA783] - - [#xA785] - - [#xA787] - - [#xA78C] - - [#xA78E] - - [#xA791] - - [#xA793-#xA795] - - [#xA797] - - [#xA799] - - [#xA79B] - - [#xA79D] - - [#xA79F] - - [#xA7A1] - - [#xA7A3] - - [#xA7A5] - - [#xA7A7] - - [#xA7A9] - - [#xA7AF] - - [#xA7B5] - - [#xA7B7] - - [#xA7B9] - - [#xA7BB] - - [#xA7BD] - - [#xA7BF] - - [#xA7C1] - - [#xA7C3] - - [#xA7C8] - - [#xA7CA] - - [#xA7D1] - - [#xA7D3] - - [#xA7D5] - - [#xA7D7] - - [#xA7D9] - - [#xA7F6] - - [#xA7FA] - - [#xAB30-#xAB5A] - - [#xAB60-#xAB68] - - [#xAB70-#xABBF] - - [#xFB00-#xFB06] - - [#xFB13-#xFB17] - - [#xFF41-#xFF5A] - - -
- -
Ll       ::= [a-z#xB5#xDF-#xF6#xF8-#xFF#x101#x103#x105#x107#x109#x10B#x10D#x10F#x111#x113#x115#x117#x119#x11B#x11D#x11F#x121#x123#x125#x127#x129#x12B#x12D#x12F#x131#x133#x135#x137-#x138#x13A#x13C#x13E#x140#x142#x144#x146#x148-#x149#x14B#x14D#x14F#x151#x153#x155#x157#x159#x15B#x15D#x15F#x161#x163#x165#x167#x169#x16B#x16D#x16F#x171#x173#x175#x177#x17A#x17C#x17E-#x180#x183#x185#x188#x18C-#x18D#x192#x195#x199-#x19B#x19E#x1A1#x1A3#x1A5#x1A8#x1AA-#x1AB#x1AD#x1B0#x1B4#x1B6#x1B9-#x1BA#x1BD-#x1BF#x1C6#x1C9#x1CC#x1CE#x1D0#x1D2#x1D4#x1D6#x1D8#x1DA#x1DC-#x1DD#x1DF#x1E1#x1E3#x1E5#x1E7#x1E9#x1EB#x1ED#x1EF-#x1F0#x1F3#x1F5#x1F9#x1FB#x1FD#x1FF#x201#x203#x205#x207#x209#x20B#x20D#x20F#x211#x213#x215#x217#x219#x21B#x21D#x21F#x221#x223#x225#x227#x229#x22B#x22D#x22F#x231#x233-#x239#x23C#x23F-#x240#x242#x247#x249#x24B#x24D#x24F-#x293#x295-#x2AF#x371#x373#x377#x37B-#x37D#x390#x3AC-#x3CE#x3D0-#x3D1#x3D5-#x3D7#x3D9#x3DB#x3DD#x3DF#x3E1#x3E3#x3E5#x3E7#x3E9#x3EB#x3ED#x3EF-#x3F3#x3F5#x3F8#x3FB-#x3FC#x430-#x45F#x461#x463#x465#x467#x469#x46B#x46D#x46F#x471#x473#x475#x477#x479#x47B#x47D#x47F#x481#x48B#x48D#x48F#x491#x493#x495#x497#x499#x49B#x49D#x49F#x4A1#x4A3#x4A5#x4A7#x4A9#x4AB#x4AD#x4AF#x4B1#x4B3#x4B5#x4B7#x4B9#x4BB#x4BD#x4BF#x4C2#x4C4#x4C6#x4C8#x4CA#x4CC#x4CE-#x4CF#x4D1#x4D3#x4D5#x4D7#x4D9#x4DB#x4DD#x4DF#x4E1#x4E3#x4E5#x4E7#x4E9#x4EB#x4ED#x4EF#x4F1#x4F3#x4F5#x4F7#x4F9#x4FB#x4FD#x4FF#x501#x503#x505#x507#x509#x50B#x50D#x50F#x511#x513#x515#x517#x519#x51B#x51D#x51F#x521#x523#x525#x527#x529#x52B#x52D#x52F#x560-#x588#x10D0-#x10FA#x10FD-#x10FF#x13F8-#x13FD#x1C80-#x1C88#x1D00-#x1D2B#x1D6B-#x1D77#x1D79-#x1D9A#x1E01#x1E03#x1E05#x1E07#x1E09#x1E0B#x1E0D#x1E0F#x1E11#x1E13#x1E15#x1E17#x1E19#x1E1B#x1E1D#x1E1F#x1E21#x1E23#x1E25#x1E27#x1E29#x1E2B#x1E2D#x1E2F#x1E31#x1E33#x1E35#x1E37#x1E39#x1E3B#x1E3D#x1E3F#x1E41#x1E43#x1E45#x1E47#x1E49#x1E4B#x1E4D#x1E4F#x1E51#x1E53#x1E55#x1E57#x1E59#x1E5B#x1E5D#x1E5F#x1E61#x1E63#x1E65#x1E67#x1E69#x1E6B#x1E6D#x1E6F#x1E71#x1E73#x1E75#x1E77#x1E79#x1E7B#x1E7D#x1E7F#x1E81#x1E83#x1E85#x1E87#x1E89#x1E8B#x1E8D#x1E8F#x1E91#x1E93#x1E95-#x1E9D#x1E9F#x1EA1#x1EA3#x1EA5#x1EA7#x1EA9#x1EAB#x1EAD#x1EAF#x1EB1#x1EB3#x1EB5#x1EB7#x1EB9#x1EBB#x1EBD#x1EBF#x1EC1#x1EC3#x1EC5#x1EC7#x1EC9#x1ECB#x1ECD#x1ECF#x1ED1#x1ED3#x1ED5#x1ED7#x1ED9#x1EDB#x1EDD#x1EDF#x1EE1#x1EE3#x1EE5#x1EE7#x1EE9#x1EEB#x1EED#x1EEF#x1EF1#x1EF3#x1EF5#x1EF7#x1EF9#x1EFB#x1EFD#x1EFF-#x1F07#x1F10-#x1F15#x1F20-#x1F27#x1F30-#x1F37#x1F40-#x1F45#x1F50-#x1F57#x1F60-#x1F67#x1F70-#x1F7D#x1F80-#x1F87#x1F90-#x1F97#x1FA0-#x1FA7#x1FB0-#x1FB4#x1FB6-#x1FB7#x1FBE#x1FC2-#x1FC4#x1FC6-#x1FC7#x1FD0-#x1FD3#x1FD6-#x1FD7#x1FE0-#x1FE7#x1FF2-#x1FF4#x1FF6-#x1FF7#x210A#x210E-#x210F#x2113#x212F#x2134#x2139#x213C-#x213D#x2146-#x2149#x214E#x2184#x2C30-#x2C5F#x2C61#x2C65-#x2C66#x2C68#x2C6A#x2C6C#x2C71#x2C73-#x2C74#x2C76-#x2C7B#x2C81#x2C83#x2C85#x2C87#x2C89#x2C8B#x2C8D#x2C8F#x2C91#x2C93#x2C95#x2C97#x2C99#x2C9B#x2C9D#x2C9F#x2CA1#x2CA3#x2CA5#x2CA7#x2CA9#x2CAB#x2CAD#x2CAF#x2CB1#x2CB3#x2CB5#x2CB7#x2CB9#x2CBB#x2CBD#x2CBF#x2CC1#x2CC3#x2CC5#x2CC7#x2CC9#x2CCB#x2CCD#x2CCF#x2CD1#x2CD3#x2CD5#x2CD7#x2CD9#x2CDB#x2CDD#x2CDF#x2CE1#x2CE3-#x2CE4#x2CEC#x2CEE#x2CF3#x2D00-#x2D25#x2D27#x2D2D#xA641#xA643#xA645#xA647#xA649#xA64B#xA64D#xA64F#xA651#xA653#xA655#xA657#xA659#xA65B#xA65D#xA65F#xA661#xA663#xA665#xA667#xA669#xA66B#xA66D#xA681#xA683#xA685#xA687#xA689#xA68B#xA68D#xA68F#xA691#xA693#xA695#xA697#xA699#xA69B#xA723#xA725#xA727#xA729#xA72B#xA72D#xA72F-#xA731#xA733#xA735#xA737#xA739#xA73B#xA73D#xA73F#xA741#xA743#xA745#xA747#xA749#xA74B#xA74D#xA74F#xA751#xA753#xA755#xA757#xA759#xA75B#xA75D#xA75F#xA761#xA763#xA765#xA767#xA769#xA76B#xA76D#xA76F#xA771-#xA778#xA77A#xA77C#xA77F#xA781#xA783#xA785#xA787#xA78C#xA78E#xA791#xA793-#xA795#xA797#xA799#xA79B#xA79D#xA79F#xA7A1#xA7A3#xA7A5#xA7A7#xA7A9#xA7AF#xA7B5#xA7B7#xA7B9#xA7BB#xA7BD#xA7BF#xA7C1#xA7C3#xA7C8#xA7CA#xA7D1#xA7D3#xA7D5#xA7D7#xA7D9#xA7F6#xA7FA#xAB30-#xAB5A#xAB60-#xAB68#xAB70-#xABBF#xFB00-#xFB06#xFB13-#xFB17#xFF41-#xFF5A]
-
- Referenced by: -
- - -====================================================================================================================== -Lm -====================================================================================================================== - - -.. raw:: html - - - - - - [#x2B0-#x2C1] - - [#x2C6-#x2D1] - - [#x2E0-#x2E4] - - [#x2EC] - - [#x2EE] - - [#x374] - - [#x37A] - - [#x559] - - [#x640] - - [#x6E5-#x6E6] - - [#x7F4-#x7F5] - - [#x7FA] - - [#x81A] - - [#x824] - - [#x828] - - [#x8C9] - - [#x971] - - [#xE46] - - [#xEC6] - - [#x10FC] - - [#x17D7] - - [#x1843] - - [#x1AA7] - - [#x1C78-#x1C7D] - - [#x1D2C-#x1D6A] - - [#x1D78] - - [#x1D9B-#x1DBF] - - [#x2071] - - [#x207F] - - [#x2090-#x209C] - - [#x2C7C-#x2C7D] - - [#x2D6F] - - [#x2E2F] - - [#x3005] - - [#x3031-#x3035] - - [#x303B] - - [#x309D-#x309E] - - [#x30FC-#x30FE] - - [#xA015] - - [#xA4F8-#xA4FD] - - [#xA60C] - - [#xA67F] - - [#xA69C-#xA69D] - - [#xA717-#xA71F] - - [#xA770] - - [#xA788] - - [#xA7F2-#xA7F4] - - [#xA7F8-#xA7F9] - - [#xA9CF] - - [#xA9E6] - - [#xAA70] - - [#xAADD] - - [#xAAF3-#xAAF4] - - [#xAB5C-#xAB5F] - - [#xAB69] - - [#xFF70] - - [#xFF9E-#xFF9F] - - -
- -
Lm       ::= [#x2B0-#x2C1#x2C6-#x2D1#x2E0-#x2E4#x2EC#x2EE#x374#x37A#x559#x640#x6E5-#x6E6#x7F4-#x7F5#x7FA#x81A#x824#x828#x8C9#x971#xE46#xEC6#x10FC#x17D7#x1843#x1AA7#x1C78-#x1C7D#x1D2C-#x1D6A#x1D78#x1D9B-#x1DBF#x2071#x207F#x2090-#x209C#x2C7C-#x2C7D#x2D6F#x2E2F#x3005#x3031-#x3035#x303B#x309D-#x309E#x30FC-#x30FE#xA015#xA4F8-#xA4FD#xA60C#xA67F#xA69C-#xA69D#xA717-#xA71F#xA770#xA788#xA7F2-#xA7F4#xA7F8-#xA7F9#xA9CF#xA9E6#xAA70#xAADD#xAAF3-#xAAF4#xAB5C-#xAB5F#xAB69#xFF70#xFF9E-#xFF9F]
-
- Referenced by: -
- - -====================================================================================================================== -Lo -====================================================================================================================== - - -.. raw:: html - - - - - - [#xAA] - - [#xBA] - - [#x1BB] - - [#x1C0-#x1C3] - - [#x294] - - [#x5D0-#x5EA] - - [#x5EF-#x5F2] - - [#x620-#x63F] - - [#x641-#x64A] - - [#x66E-#x66F] - - [#x671-#x6D3] - - [#x6D5] - - [#x6EE-#x6EF] - - [#x6FA-#x6FC] - - [#x6FF] - - [#x710] - - [#x712-#x72F] - - [#x74D-#x7A5] - - [#x7B1] - - [#x7CA-#x7EA] - - [#x800-#x815] - - [#x840-#x858] - - [#x860-#x86A] - - [#x870-#x887] - - [#x889-#x88E] - - [#x8A0-#x8C8] - - [#x904-#x939] - - [#x93D] - - [#x950] - - [#x958-#x961] - - [#x972-#x980] - - [#x985-#x98C] - - [#x98F-#x990] - - [#x993-#x9A8] - - [#x9AA-#x9B0] - - [#x9B2] - - [#x9B6-#x9B9] - - [#x9BD] - - [#x9CE] - - [#x9DC-#x9DD] - - [#x9DF-#x9E1] - - [#x9F0-#x9F1] - - [#x9FC] - - [#xA05-#xA0A] - - [#xA0F-#xA10] - - [#xA13-#xA28] - - [#xA2A-#xA30] - - [#xA32-#xA33] - - [#xA35-#xA36] - - [#xA38-#xA39] - - [#xA59-#xA5C] - - [#xA5E] - - [#xA72-#xA74] - - [#xA85-#xA8D] - - [#xA8F-#xA91] - - [#xA93-#xAA8] - - [#xAAA-#xAB0] - - [#xAB2-#xAB3] - - [#xAB5-#xAB9] - - [#xABD] - - [#xAD0] - - [#xAE0-#xAE1] - - [#xAF9] - - [#xB05-#xB0C] - - [#xB0F-#xB10] - - [#xB13-#xB28] - - [#xB2A-#xB30] - - [#xB32-#xB33] - - [#xB35-#xB39] - - [#xB3D] - - [#xB5C-#xB5D] - - [#xB5F-#xB61] - - [#xB71] - - [#xB83] - - [#xB85-#xB8A] - - [#xB8E-#xB90] - - [#xB92-#xB95] - - [#xB99-#xB9A] - - [#xB9C] - - [#xB9E-#xB9F] - - [#xBA3-#xBA4] - - [#xBA8-#xBAA] - - [#xBAE-#xBB9] - - [#xBD0] - - [#xC05-#xC0C] - - [#xC0E-#xC10] - - [#xC12-#xC28] - - [#xC2A-#xC39] - - [#xC3D] - - [#xC58-#xC5A] - - [#xC5D] - - [#xC60-#xC61] - - [#xC80] - - [#xC85-#xC8C] - - [#xC8E-#xC90] - - [#xC92-#xCA8] - - [#xCAA-#xCB3] - - [#xCB5-#xCB9] - - [#xCBD] - - [#xCDD-#xCDE] - - [#xCE0-#xCE1] - - [#xCF1-#xCF2] - - [#xD04-#xD0C] - - [#xD0E-#xD10] - - [#xD12-#xD3A] - - [#xD3D] - - [#xD4E] - - [#xD54-#xD56] - - [#xD5F-#xD61] - - [#xD7A-#xD7F] - - [#xD85-#xD96] - - [#xD9A-#xDB1] - - [#xDB3-#xDBB] - - [#xDBD] - - [#xDC0-#xDC6] - - [#xE01-#xE30] - - [#xE32-#xE33] - - [#xE40-#xE45] - - [#xE81-#xE82] - - [#xE84] - - [#xE86-#xE8A] - - [#xE8C-#xEA3] - - [#xEA5] - - [#xEA7-#xEB0] - - [#xEB2-#xEB3] - - [#xEBD] - - [#xEC0-#xEC4] - - [#xEDC-#xEDF] - - [#xF00] - - [#xF40-#xF47] - - [#xF49-#xF6C] - - [#xF88-#xF8C] - - [#x1000-#x102A] - - [#x103F] - - [#x1050-#x1055] - - [#x105A-#x105D] - - [#x1061] - - [#x1065-#x1066] - - [#x106E-#x1070] - - [#x1075-#x1081] - - [#x108E] - - [#x1100-#x1248] - - [#x124A-#x124D] - - [#x1250-#x1256] - - [#x1258] - - [#x125A-#x125D] - - [#x1260-#x1288] - - [#x128A-#x128D] - - [#x1290-#x12B0] - - [#x12B2-#x12B5] - - [#x12B8-#x12BE] - - [#x12C0] - - [#x12C2-#x12C5] - - [#x12C8-#x12D6] - - [#x12D8-#x1310] - - [#x1312-#x1315] - - [#x1318-#x135A] - - [#x1380-#x138F] - - [#x1401-#x166C] - - [#x166F-#x167F] - - [#x1681-#x169A] - - [#x16A0-#x16EA] - - [#x16F1-#x16F8] - - [#x1700-#x1711] - - [#x171F-#x1731] - - [#x1740-#x1751] - - [#x1760-#x176C] - - [#x176E-#x1770] - - [#x1780-#x17B3] - - [#x17DC] - - [#x1820-#x1842] - - [#x1844-#x1878] - - [#x1880-#x1884] - - [#x1887-#x18A8] - - [#x18AA] - - [#x18B0-#x18F5] - - [#x1900-#x191E] - - [#x1950-#x196D] - - [#x1970-#x1974] - - [#x1980-#x19AB] - - [#x19B0-#x19C9] - - [#x1A00-#x1A16] - - [#x1A20-#x1A54] - - [#x1B05-#x1B33] - - [#x1B45-#x1B4C] - - [#x1B83-#x1BA0] - - [#x1BAE-#x1BAF] - - [#x1BBA-#x1BE5] - - [#x1C00-#x1C23] - - [#x1C4D-#x1C4F] - - [#x1C5A-#x1C77] - - [#x1CE9-#x1CEC] - - [#x1CEE-#x1CF3] - - [#x1CF5-#x1CF6] - - [#x1CFA] - - [#x2135-#x2138] - - [#x2D30-#x2D67] - - [#x2D80-#x2D96] - - [#x2DA0-#x2DA6] - - [#x2DA8-#x2DAE] - - [#x2DB0-#x2DB6] - - [#x2DB8-#x2DBE] - - [#x2DC0-#x2DC6] - - [#x2DC8-#x2DCE] - - [#x2DD0-#x2DD6] - - [#x2DD8-#x2DDE] - - [#x3006] - - [#x303C] - - [#x3041-#x3096] - - [#x309F] - - [#x30A1-#x30FA] - - [#x30FF] - - [#x3105-#x312F] - - [#x3131-#x318E] - - [#x31A0-#x31BF] - - [#x31F0-#x31FF] - - [#x4DBF] - - [#x9FFF-#xA014] - - [#xA016-#xA48C] - - [#xA4D0-#xA4F7] - - [#xA500-#xA60B] - - [#xA610-#xA61F] - - [#xA62A-#xA62B] - - [#xA66E] - - [#xA6A0-#xA6E5] - - [#xA78F] - - [#xA7F7] - - [#xA7FB-#xA801] - - [#xA803-#xA805] - - [#xA807-#xA80A] - - [#xA80C-#xA822] - - [#xA840-#xA873] - - [#xA882-#xA8B3] - - [#xA8F2-#xA8F7] - - [#xA8FB] - - [#xA8FD-#xA8FE] - - [#xA90A-#xA925] - - [#xA930-#xA946] - - [#xA960-#xA97C] - - [#xA984-#xA9B2] - - [#xA9E0-#xA9E4] - - [#xA9E7-#xA9EF] - - [#xA9FA-#xA9FE] - - [#xAA00-#xAA28] - - [#xAA40-#xAA42] - - [#xAA44-#xAA4B] - - [#xAA60-#xAA6F] - - [#xAA71-#xAA76] - - [#xAA7A] - - [#xAA7E-#xAAAF] - - [#xAAB1] - - [#xAAB5-#xAAB6] - - [#xAAB9-#xAABD] - - [#xAAC0] - - [#xAAC2] - - [#xAADB-#xAADC] - - [#xAAE0-#xAAEA] - - [#xAAF2] - - [#xAB01-#xAB06] - - [#xAB09-#xAB0E] - - [#xAB11-#xAB16] - - [#xAB20-#xAB26] - - [#xAB28-#xAB2E] - - [#xABC0-#xABE2] - - [#xD7A3] - - [#xD7B0-#xD7C6] - - [#xD7CB-#xD7FB] - - [#xF900-#xFA6D] - - [#xFA70-#xFAD9] - - [#xFB1D] - - [#xFB1F-#xFB28] - - [#xFB2A-#xFB36] - - [#xFB38-#xFB3C] - - [#xFB3E] - - [#xFB40-#xFB41] - - [#xFB43-#xFB44] - - [#xFB46-#xFBB1] - - [#xFBD3-#xFD3D] - - [#xFD50-#xFD8F] - - [#xFD92-#xFDC7] - - [#xFDF0-#xFDFB] - - [#xFE70-#xFE74] - - [#xFE76-#xFEFC] - - [#xFF66-#xFF6F] - - [#xFF71-#xFF9D] - - [#xFFA0-#xFFBE] - - [#xFFC2-#xFFC7] - - [#xFFCA-#xFFCF] - - [#xFFD2-#xFFD7] - - [#xFFDA-#xFFDC] - - -
- -
Lo       ::= [#xAA#xBA#x1BB#x1C0-#x1C3#x294#x5D0-#x5EA#x5EF-#x5F2#x620-#x63F#x641-#x64A#x66E-#x66F#x671-#x6D3#x6D5#x6EE-#x6EF#x6FA-#x6FC#x6FF#x710#x712-#x72F#x74D-#x7A5#x7B1#x7CA-#x7EA#x800-#x815#x840-#x858#x860-#x86A#x870-#x887#x889-#x88E#x8A0-#x8C8#x904-#x939#x93D#x950#x958-#x961#x972-#x980#x985-#x98C#x98F-#x990#x993-#x9A8#x9AA-#x9B0#x9B2#x9B6-#x9B9#x9BD#x9CE#x9DC-#x9DD#x9DF-#x9E1#x9F0-#x9F1#x9FC#xA05-#xA0A#xA0F-#xA10#xA13-#xA28#xA2A-#xA30#xA32-#xA33#xA35-#xA36#xA38-#xA39#xA59-#xA5C#xA5E#xA72-#xA74#xA85-#xA8D#xA8F-#xA91#xA93-#xAA8#xAAA-#xAB0#xAB2-#xAB3#xAB5-#xAB9#xABD#xAD0#xAE0-#xAE1#xAF9#xB05-#xB0C#xB0F-#xB10#xB13-#xB28#xB2A-#xB30#xB32-#xB33#xB35-#xB39#xB3D#xB5C-#xB5D#xB5F-#xB61#xB71#xB83#xB85-#xB8A#xB8E-#xB90#xB92-#xB95#xB99-#xB9A#xB9C#xB9E-#xB9F#xBA3-#xBA4#xBA8-#xBAA#xBAE-#xBB9#xBD0#xC05-#xC0C#xC0E-#xC10#xC12-#xC28#xC2A-#xC39#xC3D#xC58-#xC5A#xC5D#xC60-#xC61#xC80#xC85-#xC8C#xC8E-#xC90#xC92-#xCA8#xCAA-#xCB3#xCB5-#xCB9#xCBD#xCDD-#xCDE#xCE0-#xCE1#xCF1-#xCF2#xD04-#xD0C#xD0E-#xD10#xD12-#xD3A#xD3D#xD4E#xD54-#xD56#xD5F-#xD61#xD7A-#xD7F#xD85-#xD96#xD9A-#xDB1#xDB3-#xDBB#xDBD#xDC0-#xDC6#xE01-#xE30#xE32-#xE33#xE40-#xE45#xE81-#xE82#xE84#xE86-#xE8A#xE8C-#xEA3#xEA5#xEA7-#xEB0#xEB2-#xEB3#xEBD#xEC0-#xEC4#xEDC-#xEDF#xF00#xF40-#xF47#xF49-#xF6C#xF88-#xF8C#x1000-#x102A#x103F#x1050-#x1055#x105A-#x105D#x1061#x1065-#x1066#x106E-#x1070#x1075-#x1081#x108E#x1100-#x1248#x124A-#x124D#x1250-#x1256#x1258#x125A-#x125D#x1260-#x1288#x128A-#x128D#x1290-#x12B0#x12B2-#x12B5#x12B8-#x12BE#x12C0#x12C2-#x12C5#x12C8-#x12D6#x12D8-#x1310#x1312-#x1315#x1318-#x135A#x1380-#x138F#x1401-#x166C#x166F-#x167F#x1681-#x169A#x16A0-#x16EA#x16F1-#x16F8#x1700-#x1711#x171F-#x1731#x1740-#x1751#x1760-#x176C#x176E-#x1770#x1780-#x17B3#x17DC#x1820-#x1842#x1844-#x1878#x1880-#x1884#x1887-#x18A8#x18AA#x18B0-#x18F5#x1900-#x191E#x1950-#x196D#x1970-#x1974#x1980-#x19AB#x19B0-#x19C9#x1A00-#x1A16#x1A20-#x1A54#x1B05-#x1B33#x1B45-#x1B4C#x1B83-#x1BA0#x1BAE-#x1BAF#x1BBA-#x1BE5#x1C00-#x1C23#x1C4D-#x1C4F#x1C5A-#x1C77#x1CE9-#x1CEC#x1CEE-#x1CF3#x1CF5-#x1CF6#x1CFA#x2135-#x2138#x2D30-#x2D67#x2D80-#x2D96#x2DA0-#x2DA6#x2DA8-#x2DAE#x2DB0-#x2DB6#x2DB8-#x2DBE#x2DC0-#x2DC6#x2DC8-#x2DCE#x2DD0-#x2DD6#x2DD8-#x2DDE#x3006#x303C#x3041-#x3096#x309F#x30A1-#x30FA#x30FF#x3105-#x312F#x3131-#x318E#x31A0-#x31BF#x31F0-#x31FF#x4DBF#x9FFF-#xA014#xA016-#xA48C#xA4D0-#xA4F7#xA500-#xA60B#xA610-#xA61F#xA62A-#xA62B#xA66E#xA6A0-#xA6E5#xA78F#xA7F7#xA7FB-#xA801#xA803-#xA805#xA807-#xA80A#xA80C-#xA822#xA840-#xA873#xA882-#xA8B3#xA8F2-#xA8F7#xA8FB#xA8FD-#xA8FE#xA90A-#xA925#xA930-#xA946#xA960-#xA97C#xA984-#xA9B2#xA9E0-#xA9E4#xA9E7-#xA9EF#xA9FA-#xA9FE#xAA00-#xAA28#xAA40-#xAA42#xAA44-#xAA4B#xAA60-#xAA6F#xAA71-#xAA76#xAA7A#xAA7E-#xAAAF#xAAB1#xAAB5-#xAAB6#xAAB9-#xAABD#xAAC0#xAAC2#xAADB-#xAADC#xAAE0-#xAAEA#xAAF2#xAB01-#xAB06#xAB09-#xAB0E#xAB11-#xAB16#xAB20-#xAB26#xAB28-#xAB2E#xABC0-#xABE2#xD7A3#xD7B0-#xD7C6#xD7CB-#xD7FB#xF900-#xFA6D#xFA70-#xFAD9#xFB1D#xFB1F-#xFB28#xFB2A-#xFB36#xFB38-#xFB3C#xFB3E#xFB40-#xFB41#xFB43-#xFB44#xFB46-#xFBB1#xFBD3-#xFD3D#xFD50-#xFD8F#xFD92-#xFDC7#xFDF0-#xFDFB#xFE70-#xFE74#xFE76-#xFEFC#xFF66-#xFF6F#xFF71-#xFF9D#xFFA0-#xFFBE#xFFC2-#xFFC7#xFFCA-#xFFCF#xFFD2-#xFFD7#xFFDA-#xFFDC]
-
- Referenced by: -
- - -====================================================================================================================== -Lt -====================================================================================================================== - - -.. raw:: html - - - - - - [#x1C5] - - [#x1C8] - - [#x1CB] - - [#x1F2] - - [#x1F88-#x1F8F] - - [#x1F98-#x1F9F] - - [#x1FA8-#x1FAF] - - [#x1FBC] - - [#x1FCC] - - [#x1FFC] - - -
- -
Lt       ::= [#x1C5#x1C8#x1CB#x1F2#x1F88-#x1F8F#x1F98-#x1F9F#x1FA8-#x1FAF#x1FBC#x1FCC#x1FFC]
-
- Referenced by: -
- - -====================================================================================================================== -Lu -====================================================================================================================== - - -.. raw:: html - - - - - - [A-Z] - - [#xC0-#xD6] - - [#xD8-#xDE] - - [#x100] - - [#x102] - - [#x104] - - [#x106] - - [#x108] - - [#x10A] - - [#x10C] - - [#x10E] - - [#x110] - - [#x112] - - [#x114] - - [#x116] - - [#x118] - - [#x11A] - - [#x11C] - - [#x11E] - - [#x120] - - [#x122] - - [#x124] - - [#x126] - - [#x128] - - [#x12A] - - [#x12C] - - [#x12E] - - [#x130] - - [#x132] - - [#x134] - - [#x136] - - [#x139] - - [#x13B] - - [#x13D] - - [#x13F] - - [#x141] - - [#x143] - - [#x145] - - [#x147] - - [#x14A] - - [#x14C] - - [#x14E] - - [#x150] - - [#x152] - - [#x154] - - [#x156] - - [#x158] - - [#x15A] - - [#x15C] - - [#x15E] - - [#x160] - - [#x162] - - [#x164] - - [#x166] - - [#x168] - - [#x16A] - - [#x16C] - - [#x16E] - - [#x170] - - [#x172] - - [#x174] - - [#x176] - - [#x178-#x179] - - [#x17B] - - [#x17D] - - [#x181-#x182] - - [#x184] - - [#x186-#x187] - - [#x189-#x18B] - - [#x18E-#x191] - - [#x193-#x194] - - [#x196-#x198] - - [#x19C-#x19D] - - [#x19F-#x1A0] - - [#x1A2] - - [#x1A4] - - [#x1A6-#x1A7] - - [#x1A9] - - [#x1AC] - - [#x1AE-#x1AF] - - [#x1B1-#x1B3] - - [#x1B5] - - [#x1B7-#x1B8] - - [#x1BC] - - [#x1C4] - - [#x1C7] - - [#x1CA] - - [#x1CD] - - [#x1CF] - - [#x1D1] - - [#x1D3] - - [#x1D5] - - [#x1D7] - - [#x1D9] - - [#x1DB] - - [#x1DE] - - [#x1E0] - - [#x1E2] - - [#x1E4] - - [#x1E6] - - [#x1E8] - - [#x1EA] - - [#x1EC] - - [#x1EE] - - [#x1F1] - - [#x1F4] - - [#x1F6-#x1F8] - - [#x1FA] - - [#x1FC] - - [#x1FE] - - [#x200] - - [#x202] - - [#x204] - - [#x206] - - [#x208] - - [#x20A] - - [#x20C] - - [#x20E] - - [#x210] - - [#x212] - - [#x214] - - [#x216] - - [#x218] - - [#x21A] - - [#x21C] - - [#x21E] - - [#x220] - - [#x222] - - [#x224] - - [#x226] - - [#x228] - - [#x22A] - - [#x22C] - - [#x22E] - - [#x230] - - [#x232] - - [#x23A-#x23B] - - [#x23D-#x23E] - - [#x241] - - [#x243-#x246] - - [#x248] - - [#x24A] - - [#x24C] - - [#x24E] - - [#x370] - - [#x372] - - [#x376] - - [#x37F] - - [#x386] - - [#x388-#x38A] - - [#x38C] - - [#x38E-#x38F] - - [#x391-#x3A1] - - [#x3A3-#x3AB] - - [#x3CF] - - [#x3D2-#x3D4] - - [#x3D8] - - [#x3DA] - - [#x3DC] - - [#x3DE] - - [#x3E0] - - [#x3E2] - - [#x3E4] - - [#x3E6] - - [#x3E8] - - [#x3EA] - - [#x3EC] - - [#x3EE] - - [#x3F4] - - [#x3F7] - - [#x3F9-#x3FA] - - [#x3FD-#x42F] - - [#x460] - - [#x462] - - [#x464] - - [#x466] - - [#x468] - - [#x46A] - - [#x46C] - - [#x46E] - - [#x470] - - [#x472] - - [#x474] - - [#x476] - - [#x478] - - [#x47A] - - [#x47C] - - [#x47E] - - [#x480] - - [#x48A] - - [#x48C] - - [#x48E] - - [#x490] - - [#x492] - - [#x494] - - [#x496] - - [#x498] - - [#x49A] - - [#x49C] - - [#x49E] - - [#x4A0] - - [#x4A2] - - [#x4A4] - - [#x4A6] - - [#x4A8] - - [#x4AA] - - [#x4AC] - - [#x4AE] - - [#x4B0] - - [#x4B2] - - [#x4B4] - - [#x4B6] - - [#x4B8] - - [#x4BA] - - [#x4BC] - - [#x4BE] - - [#x4C0-#x4C1] - - [#x4C3] - - [#x4C5] - - [#x4C7] - - [#x4C9] - - [#x4CB] - - [#x4CD] - - [#x4D0] - - [#x4D2] - - [#x4D4] - - [#x4D6] - - [#x4D8] - - [#x4DA] - - [#x4DC] - - [#x4DE] - - [#x4E0] - - [#x4E2] - - [#x4E4] - - [#x4E6] - - [#x4E8] - - [#x4EA] - - [#x4EC] - - [#x4EE] - - [#x4F0] - - [#x4F2] - - [#x4F4] - - [#x4F6] - - [#x4F8] - - [#x4FA] - - [#x4FC] - - [#x4FE] - - [#x500] - - [#x502] - - [#x504] - - [#x506] - - [#x508] - - [#x50A] - - [#x50C] - - [#x50E] - - [#x510] - - [#x512] - - [#x514] - - [#x516] - - [#x518] - - [#x51A] - - [#x51C] - - [#x51E] - - [#x520] - - [#x522] - - [#x524] - - [#x526] - - [#x528] - - [#x52A] - - [#x52C] - - [#x52E] - - [#x531-#x556] - - [#x10A0-#x10C5] - - [#x10C7] - - [#x10CD] - - [#x13A0-#x13F5] - - [#x1C90-#x1CBA] - - [#x1CBD-#x1CBF] - - [#x1E00] - - [#x1E02] - - [#x1E04] - - [#x1E06] - - [#x1E08] - - [#x1E0A] - - [#x1E0C] - - [#x1E0E] - - [#x1E10] - - [#x1E12] - - [#x1E14] - - [#x1E16] - - [#x1E18] - - [#x1E1A] - - [#x1E1C] - - [#x1E1E] - - [#x1E20] - - [#x1E22] - - [#x1E24] - - [#x1E26] - - [#x1E28] - - [#x1E2A] - - [#x1E2C] - - [#x1E2E] - - [#x1E30] - - [#x1E32] - - [#x1E34] - - [#x1E36] - - [#x1E38] - - [#x1E3A] - - [#x1E3C] - - [#x1E3E] - - [#x1E40] - - [#x1E42] - - [#x1E44] - - [#x1E46] - - [#x1E48] - - [#x1E4A] - - [#x1E4C] - - [#x1E4E] - - [#x1E50] - - [#x1E52] - - [#x1E54] - - [#x1E56] - - [#x1E58] - - [#x1E5A] - - [#x1E5C] - - [#x1E5E] - - [#x1E60] - - [#x1E62] - - [#x1E64] - - [#x1E66] - - [#x1E68] - - [#x1E6A] - - [#x1E6C] - - [#x1E6E] - - [#x1E70] - - [#x1E72] - - [#x1E74] - - [#x1E76] - - [#x1E78] - - [#x1E7A] - - [#x1E7C] - - [#x1E7E] - - [#x1E80] - - [#x1E82] - - [#x1E84] - - [#x1E86] - - [#x1E88] - - [#x1E8A] - - [#x1E8C] - - [#x1E8E] - - [#x1E90] - - [#x1E92] - - [#x1E94] - - [#x1E9E] - - [#x1EA0] - - [#x1EA2] - - [#x1EA4] - - [#x1EA6] - - [#x1EA8] - - [#x1EAA] - - [#x1EAC] - - [#x1EAE] - - [#x1EB0] - - [#x1EB2] - - [#x1EB4] - - [#x1EB6] - - [#x1EB8] - - [#x1EBA] - - [#x1EBC] - - [#x1EBE] - - [#x1EC0] - - [#x1EC2] - - [#x1EC4] - - [#x1EC6] - - [#x1EC8] - - [#x1ECA] - - [#x1ECC] - - [#x1ECE] - - [#x1ED0] - - [#x1ED2] - - [#x1ED4] - - [#x1ED6] - - [#x1ED8] - - [#x1EDA] - - [#x1EDC] - - [#x1EDE] - - [#x1EE0] - - [#x1EE2] - - [#x1EE4] - - [#x1EE6] - - [#x1EE8] - - [#x1EEA] - - [#x1EEC] - - [#x1EEE] - - [#x1EF0] - - [#x1EF2] - - [#x1EF4] - - [#x1EF6] - - [#x1EF8] - - [#x1EFA] - - [#x1EFC] - - [#x1EFE] - - [#x1F08-#x1F0F] - - [#x1F18-#x1F1D] - - [#x1F28-#x1F2F] - - [#x1F38-#x1F3F] - - [#x1F48-#x1F4D] - - [#x1F59] - - [#x1F5B] - - [#x1F5D] - - [#x1F5F] - - [#x1F68-#x1F6F] - - [#x1FB8-#x1FBB] - - [#x1FC8-#x1FCB] - - [#x1FD8-#x1FDB] - - [#x1FE8-#x1FEC] - - [#x1FF8-#x1FFB] - - [#x2102] - - [#x2107] - - [#x210B-#x210D] - - [#x2110-#x2112] - - [#x2115] - - [#x2119-#x211D] - - [#x2124] - - [#x2126] - - [#x2128] - - [#x212A-#x212D] - - [#x2130-#x2133] - - [#x213E-#x213F] - - [#x2145] - - [#x2183] - - [#x2C00-#x2C2F] - - [#x2C60] - - [#x2C62-#x2C64] - - [#x2C67] - - [#x2C69] - - [#x2C6B] - - [#x2C6D-#x2C70] - - [#x2C72] - - [#x2C75] - - [#x2C7E-#x2C80] - - [#x2C82] - - [#x2C84] - - [#x2C86] - - [#x2C88] - - [#x2C8A] - - [#x2C8C] - - [#x2C8E] - - [#x2C90] - - [#x2C92] - - [#x2C94] - - [#x2C96] - - [#x2C98] - - [#x2C9A] - - [#x2C9C] - - [#x2C9E] - - [#x2CA0] - - [#x2CA2] - - [#x2CA4] - - [#x2CA6] - - [#x2CA8] - - [#x2CAA] - - [#x2CAC] - - [#x2CAE] - - [#x2CB0] - - [#x2CB2] - - [#x2CB4] - - [#x2CB6] - - [#x2CB8] - - [#x2CBA] - - [#x2CBC] - - [#x2CBE] - - [#x2CC0] - - [#x2CC2] - - [#x2CC4] - - [#x2CC6] - - [#x2CC8] - - [#x2CCA] - - [#x2CCC] - - [#x2CCE] - - [#x2CD0] - - [#x2CD2] - - [#x2CD4] - - [#x2CD6] - - [#x2CD8] - - [#x2CDA] - - [#x2CDC] - - [#x2CDE] - - [#x2CE0] - - [#x2CE2] - - [#x2CEB] - - [#x2CED] - - [#x2CF2] - - [#xA640] - - [#xA642] - - [#xA644] - - [#xA646] - - [#xA648] - - [#xA64A] - - [#xA64C] - - [#xA64E] - - [#xA650] - - [#xA652] - - [#xA654] - - [#xA656] - - [#xA658] - - [#xA65A] - - [#xA65C] - - [#xA65E] - - [#xA660] - - [#xA662] - - [#xA664] - - [#xA666] - - [#xA668] - - [#xA66A] - - [#xA66C] - - [#xA680] - - [#xA682] - - [#xA684] - - [#xA686] - - [#xA688] - - [#xA68A] - - [#xA68C] - - [#xA68E] - - [#xA690] - - [#xA692] - - [#xA694] - - [#xA696] - - [#xA698] - - [#xA69A] - - [#xA722] - - [#xA724] - - [#xA726] - - [#xA728] - - [#xA72A] - - [#xA72C] - - [#xA72E] - - [#xA732] - - [#xA734] - - [#xA736] - - [#xA738] - - [#xA73A] - - [#xA73C] - - [#xA73E] - - [#xA740] - - [#xA742] - - [#xA744] - - [#xA746] - - [#xA748] - - [#xA74A] - - [#xA74C] - - [#xA74E] - - [#xA750] - - [#xA752] - - [#xA754] - - [#xA756] - - [#xA758] - - [#xA75A] - - [#xA75C] - - [#xA75E] - - [#xA760] - - [#xA762] - - [#xA764] - - [#xA766] - - [#xA768] - - [#xA76A] - - [#xA76C] - - [#xA76E] - - [#xA779] - - [#xA77B] - - [#xA77D-#xA77E] - - [#xA780] - - [#xA782] - - [#xA784] - - [#xA786] - - [#xA78B] - - [#xA78D] - - [#xA790] - - [#xA792] - - [#xA796] - - [#xA798] - - [#xA79A] - - [#xA79C] - - [#xA79E] - - [#xA7A0] - - [#xA7A2] - - [#xA7A4] - - [#xA7A6] - - [#xA7A8] - - [#xA7AA-#xA7AE] - - [#xA7B0-#xA7B4] - - [#xA7B6] - - [#xA7B8] - - [#xA7BA] - - [#xA7BC] - - [#xA7BE] - - [#xA7C0] - - [#xA7C2] - - [#xA7C4-#xA7C7] - - [#xA7C9] - - [#xA7D0] - - [#xA7D6] - - [#xA7D8] - - [#xA7F5] - - [#xFF21-#xFF3A] - - -
- -
Lu       ::= [A-Z#xC0-#xD6#xD8-#xDE#x100#x102#x104#x106#x108#x10A#x10C#x10E#x110#x112#x114#x116#x118#x11A#x11C#x11E#x120#x122#x124#x126#x128#x12A#x12C#x12E#x130#x132#x134#x136#x139#x13B#x13D#x13F#x141#x143#x145#x147#x14A#x14C#x14E#x150#x152#x154#x156#x158#x15A#x15C#x15E#x160#x162#x164#x166#x168#x16A#x16C#x16E#x170#x172#x174#x176#x178-#x179#x17B#x17D#x181-#x182#x184#x186-#x187#x189-#x18B#x18E-#x191#x193-#x194#x196-#x198#x19C-#x19D#x19F-#x1A0#x1A2#x1A4#x1A6-#x1A7#x1A9#x1AC#x1AE-#x1AF#x1B1-#x1B3#x1B5#x1B7-#x1B8#x1BC#x1C4#x1C7#x1CA#x1CD#x1CF#x1D1#x1D3#x1D5#x1D7#x1D9#x1DB#x1DE#x1E0#x1E2#x1E4#x1E6#x1E8#x1EA#x1EC#x1EE#x1F1#x1F4#x1F6-#x1F8#x1FA#x1FC#x1FE#x200#x202#x204#x206#x208#x20A#x20C#x20E#x210#x212#x214#x216#x218#x21A#x21C#x21E#x220#x222#x224#x226#x228#x22A#x22C#x22E#x230#x232#x23A-#x23B#x23D-#x23E#x241#x243-#x246#x248#x24A#x24C#x24E#x370#x372#x376#x37F#x386#x388-#x38A#x38C#x38E-#x38F#x391-#x3A1#x3A3-#x3AB#x3CF#x3D2-#x3D4#x3D8#x3DA#x3DC#x3DE#x3E0#x3E2#x3E4#x3E6#x3E8#x3EA#x3EC#x3EE#x3F4#x3F7#x3F9-#x3FA#x3FD-#x42F#x460#x462#x464#x466#x468#x46A#x46C#x46E#x470#x472#x474#x476#x478#x47A#x47C#x47E#x480#x48A#x48C#x48E#x490#x492#x494#x496#x498#x49A#x49C#x49E#x4A0#x4A2#x4A4#x4A6#x4A8#x4AA#x4AC#x4AE#x4B0#x4B2#x4B4#x4B6#x4B8#x4BA#x4BC#x4BE#x4C0-#x4C1#x4C3#x4C5#x4C7#x4C9#x4CB#x4CD#x4D0#x4D2#x4D4#x4D6#x4D8#x4DA#x4DC#x4DE#x4E0#x4E2#x4E4#x4E6#x4E8#x4EA#x4EC#x4EE#x4F0#x4F2#x4F4#x4F6#x4F8#x4FA#x4FC#x4FE#x500#x502#x504#x506#x508#x50A#x50C#x50E#x510#x512#x514#x516#x518#x51A#x51C#x51E#x520#x522#x524#x526#x528#x52A#x52C#x52E#x531-#x556#x10A0-#x10C5#x10C7#x10CD#x13A0-#x13F5#x1C90-#x1CBA#x1CBD-#x1CBF#x1E00#x1E02#x1E04#x1E06#x1E08#x1E0A#x1E0C#x1E0E#x1E10#x1E12#x1E14#x1E16#x1E18#x1E1A#x1E1C#x1E1E#x1E20#x1E22#x1E24#x1E26#x1E28#x1E2A#x1E2C#x1E2E#x1E30#x1E32#x1E34#x1E36#x1E38#x1E3A#x1E3C#x1E3E#x1E40#x1E42#x1E44#x1E46#x1E48#x1E4A#x1E4C#x1E4E#x1E50#x1E52#x1E54#x1E56#x1E58#x1E5A#x1E5C#x1E5E#x1E60#x1E62#x1E64#x1E66#x1E68#x1E6A#x1E6C#x1E6E#x1E70#x1E72#x1E74#x1E76#x1E78#x1E7A#x1E7C#x1E7E#x1E80#x1E82#x1E84#x1E86#x1E88#x1E8A#x1E8C#x1E8E#x1E90#x1E92#x1E94#x1E9E#x1EA0#x1EA2#x1EA4#x1EA6#x1EA8#x1EAA#x1EAC#x1EAE#x1EB0#x1EB2#x1EB4#x1EB6#x1EB8#x1EBA#x1EBC#x1EBE#x1EC0#x1EC2#x1EC4#x1EC6#x1EC8#x1ECA#x1ECC#x1ECE#x1ED0#x1ED2#x1ED4#x1ED6#x1ED8#x1EDA#x1EDC#x1EDE#x1EE0#x1EE2#x1EE4#x1EE6#x1EE8#x1EEA#x1EEC#x1EEE#x1EF0#x1EF2#x1EF4#x1EF6#x1EF8#x1EFA#x1EFC#x1EFE#x1F08-#x1F0F#x1F18-#x1F1D#x1F28-#x1F2F#x1F38-#x1F3F#x1F48-#x1F4D#x1F59#x1F5B#x1F5D#x1F5F#x1F68-#x1F6F#x1FB8-#x1FBB#x1FC8-#x1FCB#x1FD8-#x1FDB#x1FE8-#x1FEC#x1FF8-#x1FFB#x2102#x2107#x210B-#x210D#x2110-#x2112#x2115#x2119-#x211D#x2124#x2126#x2128#x212A-#x212D#x2130-#x2133#x213E-#x213F#x2145#x2183#x2C00-#x2C2F#x2C60#x2C62-#x2C64#x2C67#x2C69#x2C6B#x2C6D-#x2C70#x2C72#x2C75#x2C7E-#x2C80#x2C82#x2C84#x2C86#x2C88#x2C8A#x2C8C#x2C8E#x2C90#x2C92#x2C94#x2C96#x2C98#x2C9A#x2C9C#x2C9E#x2CA0#x2CA2#x2CA4#x2CA6#x2CA8#x2CAA#x2CAC#x2CAE#x2CB0#x2CB2#x2CB4#x2CB6#x2CB8#x2CBA#x2CBC#x2CBE#x2CC0#x2CC2#x2CC4#x2CC6#x2CC8#x2CCA#x2CCC#x2CCE#x2CD0#x2CD2#x2CD4#x2CD6#x2CD8#x2CDA#x2CDC#x2CDE#x2CE0#x2CE2#x2CEB#x2CED#x2CF2#xA640#xA642#xA644#xA646#xA648#xA64A#xA64C#xA64E#xA650#xA652#xA654#xA656#xA658#xA65A#xA65C#xA65E#xA660#xA662#xA664#xA666#xA668#xA66A#xA66C#xA680#xA682#xA684#xA686#xA688#xA68A#xA68C#xA68E#xA690#xA692#xA694#xA696#xA698#xA69A#xA722#xA724#xA726#xA728#xA72A#xA72C#xA72E#xA732#xA734#xA736#xA738#xA73A#xA73C#xA73E#xA740#xA742#xA744#xA746#xA748#xA74A#xA74C#xA74E#xA750#xA752#xA754#xA756#xA758#xA75A#xA75C#xA75E#xA760#xA762#xA764#xA766#xA768#xA76A#xA76C#xA76E#xA779#xA77B#xA77D-#xA77E#xA780#xA782#xA784#xA786#xA78B#xA78D#xA790#xA792#xA796#xA798#xA79A#xA79C#xA79E#xA7A0#xA7A2#xA7A4#xA7A6#xA7A8#xA7AA-#xA7AE#xA7B0-#xA7B4#xA7B6#xA7B8#xA7BA#xA7BC#xA7BE#xA7C0#xA7C2#xA7C4-#xA7C7#xA7C9#xA7D0#xA7D6#xA7D8#xA7F5#xFF21-#xFF3A]
-
- Referenced by: -
- - -====================================================================================================================== -Nl -====================================================================================================================== - - -.. raw:: html - - - - - - [#x16EE-#x16F0] - - [#x2160-#x2182] - - [#x2185-#x2188] - - [#x3007] - - [#x3021-#x3029] - - [#x3038-#x303A] - - [#xA6E6-#xA6EF] - - -
- -
Nl       ::= [#x16EE-#x16F0#x2160-#x2182#x2185-#x2188#x3007#x3021-#x3029#x3038-#x303A#xA6E6-#xA6EF]
-
- Referenced by: -
- - -====================================================================================================================== -UnicodeIdentifierExtend -====================================================================================================================== - - -.. raw:: html - - - - - - Mn - - Mc - - Nd - - Pc - - Cf - - CJK - -
- - -
         ::= Mn
-
           | Mc
-
           | Nd
-
           | Pc
-
           | Cf
-
           | CJK
-
- - -====================================================================================================================== -Cf -====================================================================================================================== - - -.. raw:: html - - - - - - [#xAD] - - [#x600-#x605] - - [#x61C] - - [#x6DD] - - [#x70F] - - [#x890-#x891] - - [#x8E2] - - [#x180E] - - [#x200B-#x200F] - - [#x202A-#x202E] - - [#x2060-#x2064] - - [#x2066-#x206F] - - [#xFEFF] - - [#xFFF9-#xFFFB] - - -
- -
Cf       ::= [#xAD#x600-#x605#x61C#x6DD#x70F#x890-#x891#x8E2#x180E#x200B-#x200F#x202A-#x202E#x2060-#x2064#x2066-#x206F#xFEFF#xFFF9-#xFFFB]
-
- Referenced by: -
- - -====================================================================================================================== -Mc -====================================================================================================================== - - -.. raw:: html - - - - - - [#x903] - - [#x93B] - - [#x93E-#x940] - - [#x949-#x94C] - - [#x94E-#x94F] - - [#x982-#x983] - - [#x9BE-#x9C0] - - [#x9C7-#x9C8] - - [#x9CB-#x9CC] - - [#x9D7] - - [#xA03] - - [#xA3E-#xA40] - - [#xA83] - - [#xABE-#xAC0] - - [#xAC9] - - [#xACB-#xACC] - - [#xB02-#xB03] - - [#xB3E] - - [#xB40] - - [#xB47-#xB48] - - [#xB4B-#xB4C] - - [#xB57] - - [#xBBE-#xBBF] - - [#xBC1-#xBC2] - - [#xBC6-#xBC8] - - [#xBCA-#xBCC] - - [#xBD7] - - [#xC01-#xC03] - - [#xC41-#xC44] - - [#xC82-#xC83] - - [#xCBE] - - [#xCC0-#xCC4] - - [#xCC7-#xCC8] - - [#xCCA-#xCCB] - - [#xCD5-#xCD6] - - [#xCF3] - - [#xD02-#xD03] - - [#xD3E-#xD40] - - [#xD46-#xD48] - - [#xD4A-#xD4C] - - [#xD57] - - [#xD82-#xD83] - - [#xDCF-#xDD1] - - [#xDD8-#xDDF] - - [#xDF2-#xDF3] - - [#xF3E-#xF3F] - - [#xF7F] - - [#x102B-#x102C] - - [#x1031] - - [#x1038] - - [#x103B-#x103C] - - [#x1056-#x1057] - - [#x1062-#x1064] - - [#x1067-#x106D] - - [#x1083-#x1084] - - [#x1087-#x108C] - - [#x108F] - - [#x109A-#x109C] - - [#x1715] - - [#x1734] - - [#x17B6] - - [#x17BE-#x17C5] - - [#x17C7-#x17C8] - - [#x1923-#x1926] - - [#x1929-#x192B] - - [#x1930-#x1931] - - [#x1933-#x1938] - - [#x1A19-#x1A1A] - - [#x1A55] - - [#x1A57] - - [#x1A61] - - [#x1A63-#x1A64] - - [#x1A6D-#x1A72] - - [#x1B04] - - [#x1B35] - - [#x1B3B] - - [#x1B3D-#x1B41] - - [#x1B43-#x1B44] - - [#x1B82] - - [#x1BA1] - - [#x1BA6-#x1BA7] - - [#x1BAA] - - [#x1BE7] - - [#x1BEA-#x1BEC] - - [#x1BEE] - - [#x1BF2-#x1BF3] - - [#x1C24-#x1C2B] - - [#x1C34-#x1C35] - - [#x1CE1] - - [#x1CF7] - - [#x302E-#x302F] - - [#xA823-#xA824] - - [#xA827] - - [#xA880-#xA881] - - [#xA8B4-#xA8C3] - - [#xA952-#xA953] - - [#xA983] - - [#xA9B4-#xA9B5] - - [#xA9BA-#xA9BB] - - [#xA9BE-#xA9C0] - - [#xAA2F-#xAA30] - - [#xAA33-#xAA34] - - [#xAA4D] - - [#xAA7B] - - [#xAA7D] - - [#xAAEB] - - [#xAAEE-#xAAEF] - - [#xAAF5] - - [#xABE3-#xABE4] - - [#xABE6-#xABE7] - - [#xABE9-#xABEA] - - [#xABEC] - - -
- -
Mc       ::= [#x903#x93B#x93E-#x940#x949-#x94C#x94E-#x94F#x982-#x983#x9BE-#x9C0#x9C7-#x9C8#x9CB-#x9CC#x9D7#xA03#xA3E-#xA40#xA83#xABE-#xAC0#xAC9#xACB-#xACC#xB02-#xB03#xB3E#xB40#xB47-#xB48#xB4B-#xB4C#xB57#xBBE-#xBBF#xBC1-#xBC2#xBC6-#xBC8#xBCA-#xBCC#xBD7#xC01-#xC03#xC41-#xC44#xC82-#xC83#xCBE#xCC0-#xCC4#xCC7-#xCC8#xCCA-#xCCB#xCD5-#xCD6#xCF3#xD02-#xD03#xD3E-#xD40#xD46-#xD48#xD4A-#xD4C#xD57#xD82-#xD83#xDCF-#xDD1#xDD8-#xDDF#xDF2-#xDF3#xF3E-#xF3F#xF7F#x102B-#x102C#x1031#x1038#x103B-#x103C#x1056-#x1057#x1062-#x1064#x1067-#x106D#x1083-#x1084#x1087-#x108C#x108F#x109A-#x109C#x1715#x1734#x17B6#x17BE-#x17C5#x17C7-#x17C8#x1923-#x1926#x1929-#x192B#x1930-#x1931#x1933-#x1938#x1A19-#x1A1A#x1A55#x1A57#x1A61#x1A63-#x1A64#x1A6D-#x1A72#x1B04#x1B35#x1B3B#x1B3D-#x1B41#x1B43-#x1B44#x1B82#x1BA1#x1BA6-#x1BA7#x1BAA#x1BE7#x1BEA-#x1BEC#x1BEE#x1BF2-#x1BF3#x1C24-#x1C2B#x1C34-#x1C35#x1CE1#x1CF7#x302E-#x302F#xA823-#xA824#xA827#xA880-#xA881#xA8B4-#xA8C3#xA952-#xA953#xA983#xA9B4-#xA9B5#xA9BA-#xA9BB#xA9BE-#xA9C0#xAA2F-#xAA30#xAA33-#xAA34#xAA4D#xAA7B#xAA7D#xAAEB#xAAEE-#xAAEF#xAAF5#xABE3-#xABE4#xABE6-#xABE7#xABE9-#xABEA#xABEC]
-
- Referenced by: -
- - -====================================================================================================================== -Mn -====================================================================================================================== - - -.. raw:: html - - - - - - [#x300-#x36F] - - [#x483-#x487] - - [#x591-#x5BD] - - [#x5BF] - - [#x5C1-#x5C2] - - [#x5C4-#x5C5] - - [#x5C7] - - [#x610-#x61A] - - [#x64B-#x65F] - - [#x670] - - [#x6D6-#x6DC] - - [#x6DF-#x6E4] - - [#x6E7-#x6E8] - - [#x6EA-#x6ED] - - [#x711] - - [#x730-#x74A] - - [#x7A6-#x7B0] - - [#x7EB-#x7F3] - - [#x7FD] - - [#x816-#x819] - - [#x81B-#x823] - - [#x825-#x827] - - [#x829-#x82D] - - [#x859-#x85B] - - [#x898-#x89F] - - [#x8CA-#x8E1] - - [#x8E3-#x902] - - [#x93A] - - [#x93C] - - [#x941-#x948] - - [#x94D] - - [#x951-#x957] - - [#x962-#x963] - - [#x981] - - [#x9BC] - - [#x9C1-#x9C4] - - [#x9CD] - - [#x9E2-#x9E3] - - [#x9FE] - - [#xA01-#xA02] - - [#xA3C] - - [#xA41-#xA42] - - [#xA47-#xA48] - - [#xA4B-#xA4D] - - [#xA51] - - [#xA70-#xA71] - - [#xA75] - - [#xA81-#xA82] - - [#xABC] - - [#xAC1-#xAC5] - - [#xAC7-#xAC8] - - [#xACD] - - [#xAE2-#xAE3] - - [#xAFA-#xAFF] - - [#xB01] - - [#xB3C] - - [#xB3F] - - [#xB41-#xB44] - - [#xB4D] - - [#xB55-#xB56] - - [#xB62-#xB63] - - [#xB82] - - [#xBC0] - - [#xBCD] - - [#xC00] - - [#xC04] - - [#xC3C] - - [#xC3E-#xC40] - - [#xC46-#xC48] - - [#xC4A-#xC4D] - - [#xC55-#xC56] - - [#xC62-#xC63] - - [#xC81] - - [#xCBC] - - [#xCBF] - - [#xCC6] - - [#xCCC-#xCCD] - - [#xCE2-#xCE3] - - [#xD00-#xD01] - - [#xD3B-#xD3C] - - [#xD41-#xD44] - - [#xD4D] - - [#xD62-#xD63] - - [#xD81] - - [#xDCA] - - [#xDD2-#xDD4] - - [#xDD6] - - [#xE31] - - [#xE34-#xE3A] - - [#xE47-#xE4E] - - [#xEB1] - - [#xEB4-#xEBC] - - [#xEC8-#xECE] - - [#xF18-#xF19] - - [#xF35] - - [#xF37] - - [#xF39] - - [#xF71-#xF7E] - - [#xF80-#xF84] - - [#xF86-#xF87] - - [#xF8D-#xF97] - - [#xF99-#xFBC] - - [#xFC6] - - [#x102D-#x1030] - - [#x1032-#x1037] - - [#x1039-#x103A] - - [#x103D-#x103E] - - [#x1058-#x1059] - - [#x105E-#x1060] - - [#x1071-#x1074] - - [#x1082] - - [#x1085-#x1086] - - [#x108D] - - [#x109D] - - [#x135D-#x135F] - - [#x1712-#x1714] - - [#x1732-#x1733] - - [#x1752-#x1753] - - [#x1772-#x1773] - - [#x17B4-#x17B5] - - [#x17B7-#x17BD] - - [#x17C6] - - [#x17C9-#x17D3] - - [#x17DD] - - [#x180B-#x180D] - - [#x180F] - - [#x1885-#x1886] - - [#x18A9] - - [#x1920-#x1922] - - [#x1927-#x1928] - - [#x1932] - - [#x1939-#x193B] - - [#x1A17-#x1A18] - - [#x1A1B] - - [#x1A56] - - [#x1A58-#x1A5E] - - [#x1A60] - - [#x1A62] - - [#x1A65-#x1A6C] - - [#x1A73-#x1A7C] - - [#x1A7F] - - [#x1AB0-#x1ABD] - - [#x1ABF-#x1ACE] - - [#x1B00-#x1B03] - - [#x1B34] - - [#x1B36-#x1B3A] - - [#x1B3C] - - [#x1B42] - - [#x1B6B-#x1B73] - - [#x1B80-#x1B81] - - [#x1BA2-#x1BA5] - - [#x1BA8-#x1BA9] - - [#x1BAB-#x1BAD] - - [#x1BE6] - - [#x1BE8-#x1BE9] - - [#x1BED] - - [#x1BEF-#x1BF1] - - [#x1C2C-#x1C33] - - [#x1C36-#x1C37] - - [#x1CD0-#x1CD2] - - [#x1CD4-#x1CE0] - - [#x1CE2-#x1CE8] - - [#x1CED] - - [#x1CF4] - - [#x1CF8-#x1CF9] - - [#x1DC0-#x1DFF] - - [#x20D0-#x20DC] - - [#x20E1] - - [#x20E5-#x20F0] - - [#x2CEF-#x2CF1] - - [#x2D7F] - - [#x2DE0-#x2DFF] - - [#x302A-#x302D] - - [#x3099-#x309A] - - [#xA66F] - - [#xA674-#xA67D] - - [#xA69E-#xA69F] - - [#xA6F0-#xA6F1] - - [#xA802] - - [#xA806] - - [#xA80B] - - [#xA825-#xA826] - - [#xA82C] - - [#xA8C4-#xA8C5] - - [#xA8E0-#xA8F1] - - [#xA8FF] - - [#xA926-#xA92D] - - [#xA947-#xA951] - - [#xA980-#xA982] - - [#xA9B3] - - [#xA9B6-#xA9B9] - - [#xA9BC-#xA9BD] - - [#xA9E5] - - [#xAA29-#xAA2E] - - [#xAA31-#xAA32] - - [#xAA35-#xAA36] - - [#xAA43] - - [#xAA4C] - - [#xAA7C] - - [#xAAB0] - - [#xAAB2-#xAAB4] - - [#xAAB7-#xAAB8] - - [#xAABE-#xAABF] - - [#xAAC1] - - [#xAAEC-#xAAED] - - [#xAAF6] - - [#xABE5] - - [#xABE8] - - [#xABED] - - [#xFB1E] - - [#xFE00-#xFE0F] - - [#xFE20-#xFE2F] - - -
- -
Mn       ::= [#x300-#x36F#x483-#x487#x591-#x5BD#x5BF#x5C1-#x5C2#x5C4-#x5C5#x5C7#x610-#x61A#x64B-#x65F#x670#x6D6-#x6DC#x6DF-#x6E4#x6E7-#x6E8#x6EA-#x6ED#x711#x730-#x74A#x7A6-#x7B0#x7EB-#x7F3#x7FD#x816-#x819#x81B-#x823#x825-#x827#x829-#x82D#x859-#x85B#x898-#x89F#x8CA-#x8E1#x8E3-#x902#x93A#x93C#x941-#x948#x94D#x951-#x957#x962-#x963#x981#x9BC#x9C1-#x9C4#x9CD#x9E2-#x9E3#x9FE#xA01-#xA02#xA3C#xA41-#xA42#xA47-#xA48#xA4B-#xA4D#xA51#xA70-#xA71#xA75#xA81-#xA82#xABC#xAC1-#xAC5#xAC7-#xAC8#xACD#xAE2-#xAE3#xAFA-#xAFF#xB01#xB3C#xB3F#xB41-#xB44#xB4D#xB55-#xB56#xB62-#xB63#xB82#xBC0#xBCD#xC00#xC04#xC3C#xC3E-#xC40#xC46-#xC48#xC4A-#xC4D#xC55-#xC56#xC62-#xC63#xC81#xCBC#xCBF#xCC6#xCCC-#xCCD#xCE2-#xCE3#xD00-#xD01#xD3B-#xD3C#xD41-#xD44#xD4D#xD62-#xD63#xD81#xDCA#xDD2-#xDD4#xDD6#xE31#xE34-#xE3A#xE47-#xE4E#xEB1#xEB4-#xEBC#xEC8-#xECE#xF18-#xF19#xF35#xF37#xF39#xF71-#xF7E#xF80-#xF84#xF86-#xF87#xF8D-#xF97#xF99-#xFBC#xFC6#x102D-#x1030#x1032-#x1037#x1039-#x103A#x103D-#x103E#x1058-#x1059#x105E-#x1060#x1071-#x1074#x1082#x1085-#x1086#x108D#x109D#x135D-#x135F#x1712-#x1714#x1732-#x1733#x1752-#x1753#x1772-#x1773#x17B4-#x17B5#x17B7-#x17BD#x17C6#x17C9-#x17D3#x17DD#x180B-#x180D#x180F#x1885-#x1886#x18A9#x1920-#x1922#x1927-#x1928#x1932#x1939-#x193B#x1A17-#x1A18#x1A1B#x1A56#x1A58-#x1A5E#x1A60#x1A62#x1A65-#x1A6C#x1A73-#x1A7C#x1A7F#x1AB0-#x1ABD#x1ABF-#x1ACE#x1B00-#x1B03#x1B34#x1B36-#x1B3A#x1B3C#x1B42#x1B6B-#x1B73#x1B80-#x1B81#x1BA2-#x1BA5#x1BA8-#x1BA9#x1BAB-#x1BAD#x1BE6#x1BE8-#x1BE9#x1BED#x1BEF-#x1BF1#x1C2C-#x1C33#x1C36-#x1C37#x1CD0-#x1CD2#x1CD4-#x1CE0#x1CE2-#x1CE8#x1CED#x1CF4#x1CF8-#x1CF9#x1DC0-#x1DFF#x20D0-#x20DC#x20E1#x20E5-#x20F0#x2CEF-#x2CF1#x2D7F#x2DE0-#x2DFF#x302A-#x302D#x3099-#x309A#xA66F#xA674-#xA67D#xA69E-#xA69F#xA6F0-#xA6F1#xA802#xA806#xA80B#xA825-#xA826#xA82C#xA8C4-#xA8C5#xA8E0-#xA8F1#xA8FF#xA926-#xA92D#xA947-#xA951#xA980-#xA982#xA9B3#xA9B6-#xA9B9#xA9BC-#xA9BD#xA9E5#xAA29-#xAA2E#xAA31-#xAA32#xAA35-#xAA36#xAA43#xAA4C#xAA7C#xAAB0#xAAB2-#xAAB4#xAAB7-#xAAB8#xAABE-#xAABF#xAAC1#xAAEC-#xAAED#xAAF6#xABE5#xABE8#xABED#xFB1E#xFE00-#xFE0F#xFE20-#xFE2F]
-
- Referenced by: -
- - -====================================================================================================================== -Nd -====================================================================================================================== - - -.. raw:: html - - - - - - [0-9] - - [#x660-#x669] - - [#x6F0-#x6F9] - - [#x7C0-#x7C9] - - [#x966-#x96F] - - [#x9E6-#x9EF] - - [#xA66-#xA6F] - - [#xAE6-#xAEF] - - [#xB66-#xB6F] - - [#xBE6-#xBEF] - - [#xC66-#xC6F] - - [#xCE6-#xCEF] - - [#xD66-#xD6F] - - [#xDE6-#xDEF] - - [#xE50-#xE59] - - [#xED0-#xED9] - - [#xF20-#xF29] - - [#x1040-#x1049] - - [#x1090-#x1099] - - [#x17E0-#x17E9] - - [#x1810-#x1819] - - [#x1946-#x194F] - - [#x19D0-#x19D9] - - [#x1A80-#x1A89] - - [#x1A90-#x1A99] - - [#x1B50-#x1B59] - - [#x1BB0-#x1BB9] - - [#x1C40-#x1C49] - - [#x1C50-#x1C59] - - [#xA620-#xA629] - - [#xA8D0-#xA8D9] - - [#xA900-#xA909] - - [#xA9D0-#xA9D9] - - [#xA9F0-#xA9F9] - - [#xAA50-#xAA59] - - [#xABF0-#xABF9] - - [#xFF10-#xFF19] - - -
- -
Nd       ::= [0-9#x660-#x669#x6F0-#x6F9#x7C0-#x7C9#x966-#x96F#x9E6-#x9EF#xA66-#xA6F#xAE6-#xAEF#xB66-#xB6F#xBE6-#xBEF#xC66-#xC6F#xCE6-#xCEF#xD66-#xD6F#xDE6-#xDEF#xE50-#xE59#xED0-#xED9#xF20-#xF29#x1040-#x1049#x1090-#x1099#x17E0-#x17E9#x1810-#x1819#x1946-#x194F#x19D0-#x19D9#x1A80-#x1A89#x1A90-#x1A99#x1B50-#x1B59#x1BB0-#x1BB9#x1C40-#x1C49#x1C50-#x1C59#xA620-#xA629#xA8D0-#xA8D9#xA900-#xA909#xA9D0-#xA9D9#xA9F0-#xA9F9#xAA50-#xAA59#xABF0-#xABF9#xFF10-#xFF19]
-
- Referenced by: -
- - -====================================================================================================================== -Pc -====================================================================================================================== - - -.. raw:: html - - - - - - [#x203F-#x2040] - - [#x2054] - - [#xFE33-#xFE34] - - [#xFE4D-#xFE4F] - - [#xFF3F] - - -
- -
Pc       ::= [#x203F-#x2040#x2054#xFE33-#xFE34#xFE4D-#xFE4F#xFF3F]
-
- Referenced by: -
- - -====================================================================================================================== -CJK -====================================================================================================================== - - -.. raw:: html - - - - - - [#xAC00-#xD7A3] - - [#x4E00-#x9FFF] - - -
- -
CJK      ::= [#xAC00-#xD7A3#x4E00-#x9FFF]
-
- - -====================================================================================================================== -ESC -====================================================================================================================== - - -.. raw:: html - - - - - - \ - - n - - t - - b - - r - - f - - \ - - " - - -
- -
ESC      ::= '\' [ntbrf\"]
-
- Referenced by: -
- - -====================================================================================================================== -S_CHAR_LITERAL -====================================================================================================================== - - -.. raw:: html - - - - - - U - - E - - N - - R - - B - - RB - - _utf8 - - q'{ - - . - - }' - - ' - - ESC - \' - - [^'\] - - '' - - [^'] - - ' - - q'( - - . - - )' - - q'[ - - . - - ]' - - q'' - - . - - '' - - -
- - -
         ::= ( [UENRB] | 'RB' | '_utf8' )? ( "'" ( ( ESC | "\'" | [^'\] )* | ( "''" | [^'] )+ ) "'" | "q'{" .* "}'" | "q'(" .* ")'" | "q'[" .* "]'" | "q''" .* "''" )
-
- - -====================================================================================================================== -S_QUOTED_IDENTIFIER -====================================================================================================================== - - -.. raw:: html - - - - - - " - - "" - - [^"#xA#xD] - - " - - ` - - [^`#xA#xD] - - ` - - [ - - [^#x5D#xA#xD] - - ] - - -
- - -
         ::= '"' ( '""' | [^"#xA#xD] )* '"'
-
           | '`' [^`#xA#xD]+ '`'
-
           | '[' [^#x5D#xA#xD]* ']'
-
- - -====================================================================================================================== -EOF -====================================================================================================================== - - -.. raw:: html - - - - - - $ - - -
- -
EOF      ::= $
-
- Referenced by: -
- - \ No newline at end of file diff --git a/src/site/sphinx/unsupported.rst b/src/site/sphinx/unsupported.rst index c231dbe814..f81ab87d3e 100644 --- a/src/site/sphinx/unsupported.rst +++ b/src/site/sphinx/unsupported.rst @@ -3,30 +3,86 @@ Unsupported Grammar of various RDBMS *************************************** *JSQLParser* is a RDBMS agnostic parser with a certain focus on SQL:2016 Standard compliant Queries and the "Big Four" (Oracle, MS SQL Server, Postgres, MySQL/MariaDB). -We would like to recommend writing portable, standard compliant SQL in general. +We would like to recommend writing portable, standard compliant SQL in general. -- Oracle PL/SQL blocks are not support. - - .. code-block:: sql +Missing syntax is added on demand — please `open an issue `_ with a minimal example. - DECLARE - num NUMBER; - BEGIN - num := 10; - dbms_output.put_line('The number is ' || num); - END; -- DDL statements +Procedural SQL +======================================= - While *JSQLParser* provides a lot of generic support for DDL statements, it is possible that certain RDBMS specific syntax (especially about indices, encodings, compression) won't be supported. +Procedural-language support is partial. Accepting a block or routine definition does not necessarily mean that its body is parsed into a statement tree. Select the relevant dialect as described in :ref:`Choose a Dialect`. -- Interval Operators +.. code-block:: sql + :caption: Oracle anonymous block — supported with Dialect.ORACLE - Anything like `DAY HOUR MINUTE SECOND [TO HOUR MINUTE SECOND]` is not supported.: + DECLARE + num NUMBER; + BEGIN + num := 10; + dbms_output.put_line('The number is ' || num); + END; - .. code-block:: sql +.. code-block:: postgresql + :caption: PostgreSQL DO — supported with Dialect.POSTGRESQL; body remains opaque - values cast ((time '12:03:34' - time '11:57:23') minute to second as varchar(8)); + DO $$ + BEGIN + RAISE NOTICE 'hello'; + END + $$; +The PostgreSQL example produces a ``DoStatement`` whose ``getCode()`` is a ``StringValue``. The body, quotes and dollar tag are preserved, and statements following the block are parsed separately. PL/pgSQL declarations, conditions and statements inside that literal are not exposed as child AST nodes. Table discovery therefore rejects the opaque body rather than reporting an incomplete table list. +Full procedural-language coverage, including cursors, loops and ``ELSIF``, remains outside the supported subset. +What *is* supported: + +- ``BEGIN .. END`` blocks and ``IF .. ELSE`` around ordinary statements (``Block``, ``IfElseStatement``) +- ``DECLARE @variable`` in the T-SQL sense (``DeclareStatement``) +- PostgreSQL ``DO`` wrappers with the body preserved as a string literal (``DoStatement``) +- Selected Oracle anonymous blocks, including variable declarations, assignments and exception handlers (``OracleBlock``); see :ref:`Oracle anonymous blocks` + +Routine and trigger definitions +--------------------------------------- + +``CREATE FUNCTION`` and ``CREATE PROCEDURE`` **are** accepted, but the body is captured as an opaque token list rather than parsed into a tree. You get a ``CreateFunction`` / ``CreateProcedure`` that round-trips to the original text — you cannot traverse or rewrite what is inside it. + +``CREATE TRIGGER`` follows the MySQL shape (``FOR EACH ROW`` with a single body statement). + +.. note:: + + Both are reported as ``OPAQUE`` by :ref:`Classify a Statement` — the analysis will not claim a routine body is side-effect free, because nothing about it is knowable. + + +Query features not implemented +======================================= + +.. list-table:: + :header-rows: 1 + :widths: 30 70 + + * - Feature + - Notes + * - ``MATCH_RECOGNIZE`` + - SQL:2016 row pattern recognition (Oracle, Snowflake, Flink, Trino) + * - Oracle ``MODEL`` clause + - spreadsheet-style inter-row calculation + + + +Dialect-specific syntax +======================================= + +The remaining gaps are narrow: one product's spelling of one construct. The `open issues `_ are the accurate list at any moment — recurring themes are Informix and Teradata extensions, T-SQL ``FOR XML PATH`` and table variables, and assorted vendor-specific DDL options. + +If you hit one, you do not have to abandon the parse: + +.. code-block:: java + + Statements statements = CCJSqlParserUtil.parseStatements( + sqlStr, parser -> parser.withUnsupportedStatements() ); + +The offending statement comes back as an ``UnsupportedStatement`` holding its original text, and the rest of the script parses normally. See :ref:`Handle Parse Errors`. + +Before concluding something is unsupported, check the parser features: square brackets, backslash escapes, double-quoted strings and hash comments are all **off by default** and are the most common cause of a "not supported" report that is really a dialect setting. See :ref:`Choose a Dialect`. diff --git a/src/site/sphinx/usage.rst b/src/site/sphinx/usage.rst index c0bcdac88a..2e70489dec 100644 --- a/src/site/sphinx/usage.rst +++ b/src/site/sphinx/usage.rst @@ -2,44 +2,105 @@ How to use it ****************************** -.. hint:: +JSQLParser turns SQL text into a tree of Java objects, lets you inspect or rewrite that tree, and prints it back out as SQL. Everything on this page is built on those three moves. - 1) **Quoting:** Double Quotes ``".."`` are used for quoting identifiers. Parsing T-SQL on **MS SQL Server** or **Sybase** with Squared Brackets ``[..]`` depends on ``Squared Bracket Quotation`` as shown in section :ref:`Define the Parser Features` below. +.. code-block:: java + :caption: The whole idea in five lines + + Statement statement = CCJSqlParserUtil.parse("SELECT a FROM my_table WHERE id = 42"); + + // inspect it + PlainSelect select = (PlainSelect) statement; + Table table = (Table) select.getFromItem(); // my_table + + // print it back + String sql = statement.toString(); + +.. tip:: + + **New here?** Read :ref:`Add JSQLParser to your Project`, then :ref:`Parse a SQL Statement`, then :ref:`Explore the Parsed Tree`. Everything after that is optional and can be read in any order. + +.. list-table:: What is on this page + :header-rows: 1 + :widths: 30 70 + + * - Section + - Use it when you want to … + * - :ref:`Add JSQLParser to your Project` + - pull in the dependency, and pick between the Manticore and upstream builds + * - :ref:`Parse a SQL Statement` + - turn SQL text into Java objects + * - :ref:`Explore the Parsed Tree` + - find your way around the object model + * - :ref:`Classify a Statement` + - know whether SQL reads, writes or returns rows — before you run it + * - :ref:`Find Table Names` + - list every table a statement touches + * - :ref:`Use the Visitor Patterns` + - walk the whole tree and react to specific nodes + * - :ref:`Build a SQL Statement` + - construct SQL from Java instead of from text + * - :ref:`Handle Parse Errors` + - keep going when one statement in a script is broken + * - :ref:`Choose a Dialect` + - parse T-SQL brackets, MySQL escapes, BigQuery quoting … + * - :ref:`Compile from Source Code` + - build JSQLParser yourself or contribute + + +Add JSQLParser to your Project +============================== - 2) JSQLParser uses a more restrictive list of ``Reserved Keywords`` and such keywords will **need to be quoted**. +There are two sets of artifacts on Maven Central, built from the same source under the same dual licence: - 3) **Escaping:** JSQLParser pre-defines standard compliant **Single Quote** ``'..`` **Escape Character**. Additional Back-slash ``\..`` Escaping needs to be activated by setting the ``BackSlashEscapeCharacter`` parser feature. See section :ref:`Define the Parser Features` below for details. +.. list-table:: + :header-rows: 1 + :widths: 30 30 40 - 4) Oracle Alternative Quoting is partially supported for common brackets such as ``q'{...}'``, ``q'[...]'``, ``q'(...)'`` and ``q''...''``. + * - Artifact + - ``groupId`` + - Cut from + * - **Manticore build** (recommended) + - ``com.manticore-projects.jsqlformatter`` + - the current development line, released continuously + * - Upstream release + - ``com.github.jsqlparser`` + - the official release cadence + * - Upstream snapshot + - ``com.github.jsqlparser`` + - the latest commit, overwritten in place - 5) Supported Statement Separators are Semicolon ``;``, ``GO``, Slash ``/`` or two empty lines ``\n\n\n``. +Upstream releases are cut infrequently. Between two of them a lot of grammar and performance work lands — the 11× parse speed-up, JavaCC 8 support, new dialect syntax — and waiting for the next official version to catch up can mean months on a build that already has the fix you need. +Snapshots are not the answer either: a ``-SNAPSHOT`` coordinate is mutable, so the same version string can resolve to different bytes tomorrow. That is fine for trying something out and wrong for a reproducible build. -Compile from Source Code -============================== +The **Manticore builds** fill that gap. Each one is an immutable, versioned release published to Maven Central from the current development line, so you get the fixes early *and* a build that stays reproducible. Use them unless you have a reason to pin to the official release — and note the different ``groupId``, the artifact name is the same. -You will need to have ``JDK 8`` or ``JDK 11`` installed. Please note that JSQLParser-4.9 is the last ``JDK 8`` compatible release and all development after will depend on ``JDK 11``. Building JSQLParser-5.1 and newer with Gradle will depend on a JDK17 toolchain due to the used plugins. +.. tab:: Maven — Manticore -.. tab:: Maven - - .. code-block:: shell + .. code-block:: xml - git clone --depth 1 https://github.com/JSQLParser/JSqlParser.git - cd JSqlParser - mvn install + + com.manticore-projects.jsqlformatter + jsqlparser + [5.3.218,) + -.. tab:: Gradle + The range ``[5.3.218,)`` takes the newest available build. Pin an exact version instead once you ship. - .. code-block:: shell +.. tab:: Gradle — Manticore - git clone --depth 1 https://github.com/JSQLParser/JSqlParser.git - cd JSqlParser - gradle publishToMavenLocal + .. code-block:: groovy + repositories { + mavenCentral() + } + dependencies { + implementation 'com.manticore-projects.jsqlformatter:jsqlparser:+' + } -Build Dependencies -============================== + ``+`` takes the newest available build. Pin an exact version instead once you ship. .. tab:: Maven Release @@ -100,11 +161,15 @@ Build Dependencies implementation 'com.github.jsqlparser:jsqlparser:|JSQLPARSER_SNAPSHOT_VERSION|' } +.. note:: + + Features documented here may reach the Manticore builds before the next upstream release. If a class or method on this page is missing, check which of the two you are resolving. + Parse a SQL Statement -============================== +============================== -Parse the SQL Text into Java Objects: +``CCJSqlParserUtil.parse()`` is the entry point. It returns a ``Statement``, which you cast to the concrete type you expect. .. code-block:: java @@ -127,8 +192,26 @@ Parse the SQL Text into Java Objects: Assertions.assertEquals("a", a.getColumnName()); Assertions.assertEquals("b", b.getColumnName()); +For several statements at once, use ``CCJSqlParserUtil.parseStatements()``, which returns a ``Statements`` — an ``ArrayList``. + +.. code-block:: java + + Statements script = CCJSqlParserUtil.parseStatements( + "UPDATE t SET a = 1; SELECT a FROM t;"); -For guidance with the API, use `JSQLFormatter `_ to visualize the Traversable Tree of Java Objects: + assertEquals(2, script.size()); + +.. note:: + + Supported statement separators are semicolon ``;``, ``GO``, slash ``/`` and two empty lines ``\n\n\n``. + +If parsing fails on syntax JSQLParser does not know, see :ref:`Handle Parse Errors` — and please `open an issue `_, missing syntax gets added on demand. + + +Explore the Parsed Tree +============================== + +The fastest way to learn the object model is to look at it. Paste your SQL into `JSQLFormatter `_ and it will draw the tree, with the Java class of every node: .. raw:: html @@ -136,7 +219,7 @@ For guidance with the API, use `JSQLFormatter SQL Text └─Statements: net.sf.jsqlparser.statement.select.Select - ├─selectItems -> Collection + ├─selectItems -> Collection<SelectItem> │ └─LongValue: 1 ├─Table: dual └─where: net.sf.jsqlparser.expression.operators.relational.EqualsTo @@ -145,48 +228,395 @@ For guidance with the API, use `JSQLFormatter -Error Handling +Read that as a map: each line is a getter away. ``select.getSelectItems()``, ``select.getFromItem()``, ``select.getWhere()``. Once the tree gets deeper than a couple of levels, stop casting by hand and use :ref:`Use the Visitor Patterns`. + + +DROP INDEX owners +----------------- + +``DROP INDEX ix ON app.t`` exposes ``app.t`` through ``Drop.getTable()``; +``getName()`` continues to identify the index. The owner is populated for the +single-index ON form used by MySQL and SQL Server. A DROP INDEX without ON, +as in PostgreSQL, has a null owner: resolving the index's table requires a catalog. +SQL Server's multi-owner list and WITH options are outside this extension. + +``getParameters()`` includes ON and the rendered table as a legacy token snapshot +when a structured owner exists. Mutate ``getTable()`` or use ``setTable()`` to +update the owner; ``addParameters()`` appends options without losing it. +``setTable(null)`` removes ON, while ``setParameters()`` replaces the entire +parameter clause with raw tokens and clears the structured owner. Raw parameter +setters do not parse SQL or infer a table. + +Table discovery and statement visitors traverse DROP TABLE/VIEW targets and +explicit index owners, without reporting catalog-only object names as tables. +The statement deparser delegates real tables to its configured select deparser. +MySQL ALGORITHM/LOCK tokens retain their order and optional equals signs. + +Inspect PostgreSQL schema statements +------------------------------------ + +PostgreSQL schema clauses extend the existing ``CreateView``, ``CreateTable``, ``Alter`` and ``Sequence`` models. Use their typed properties to inspect the clauses and preserve distinctions between omitted options and explicit values. + +.. code-block:: java + + CreateView view = (CreateView) CCJSqlParserUtil.parse( + "CREATE MATERIALIZED VIEW IF NOT EXISTS account_totals " + + "AS SELECT id FROM accounts WITH NO DATA"); + view.isMaterialized(); // true + view.isIfNotExists(); // true + view.getWithData(); // Boolean.FALSE; null means the clause was omitted + +Ordinary views expose ordered ``ViewOption`` values for ``security_barrier``, ``security_invoker`` and ``check_option``. ``getCheckOption()`` describes the trailing ``WITH CHECK OPTION`` clause: ``null`` means absent, ``DEFAULT`` preserves the bare clause, and ``LOCAL`` and ``CASCADED`` preserve explicit keywords. ``getEffectiveCheckOption()`` resolves the bare clause to ``CASCADED``. Materialized views expose their access method, storage parameters and tablespace separately. See the PostgreSQL documentation for `CREATE VIEW `_ and `CREATE MATERIALIZED VIEW `_. + +.. code-block:: java + + CreateTable table = (CreateTable) CCJSqlParserUtil.parse( + "CREATE TABLE accounts_copy (LIKE accounts INCLUDING ALL EXCLUDING INDEXES)"); + LikeClause like = table.getTableElements(LikeClause.class).get(0); + like.getOptions(); // ordered INCLUDING/EXCLUDING clauses + like.isIncluding(LikeClause.OptionKind.DEFAULTS); // true, inherited from ALL + like.isIncluding(LikeClause.OptionKind.INDEXES); // false, overridden by EXCLUDING + +``getTableElements()`` preserves the order of columns, constraints and ``LIKE`` clauses, including multiple source tables. The three legacy nullable ``LikeClause`` getters report explicit options of that kind; ``isIncluding(OptionKind)`` also resolves ``ALL`` in declaration order. A typed table's ``OF`` type is available through ``CreateTable.getOfType()``. + +Table constraints expose ``Index.getNullsDistinct()``, ``getIncludeColumns()``, storage parameters and ``ConstraintAttributes``. ``ExcludeConstraint`` reuses ``Index.ColumnParams`` for its keys; each key exposes its expression and exclusion operator. Column identity clauses are represented by ``ColumnOption.Kind.IDENTITY`` and ``IdentityDefinition``, with a generation mode and ordered ``Sequence.Parameter`` values. These APIs cover the schema clauses described in `CREATE TABLE `_. + +.. code-block:: java + + Alter alter = (Alter) CCJSqlParserUtil.parse( + "ALTER TABLE accounts ALTER COLUMN id TYPE bigint USING id + 1"); + AlterExpression.ColumnDataType column = alter.getAlterExpressions().get(0) + .getColDataTypeList().get(0); + Expression conversion = column.getUsingExpression(); + column.setUsingExpression(CCJSqlParserUtil.parseExpression("id * 10")); + String updatedSql = alter.toString(); + +Identity alterations are available as ``ColumnDataType.getIdentityAlterations()``. Sequence ownership is shared by ``CreateSequence`` and ``AlterSequence`` through ``Sequence.getOwnership()``: ``null`` means omitted, ``isNone()`` means explicit ``OWNED BY NONE``, and ``getColumn()`` identifies an owner. ``TablesNamesFinder`` includes ``LIKE`` sources and sequence owners without treating sequence or type names as tables. See `ALTER TABLE `_ and `ALTER SEQUENCE `_. + + +Structured column attributes +============================ + +``ColumnDefinition.getColumnOptions()`` exposes column nullability, ``COLLATE``, +``COMMENT``, ``ON UPDATE``, ``AUTO_INCREMENT``, visibility, inline ``PRIMARY KEY`` +and parenthesized generated expressions alongside existing defaults, references +and identity declarations. CREATE and ALTER column definitions use the same model. + +``ColumnOption.Kind`` selects the relevant accessor: ``getNullable()``, +``getCollation()``, ``getComment()``, ``getOnUpdateExpression()``, +``getVisible()``, ``getConstraint()`` or ``getGeneratedDefinition()``. +The AUTO_INCREMENT kind has no additional payload. A missing option does not +imply a server default. Option order and unknown raw extensions are preserved. + +``GeneratedColumnDefinition`` contains an ``Expression``, an explicit +``GENERATED ALWAYS`` flag, and nullable ``Storage`` (STORED/VIRTUAL). Identity +columns continue to use ``IdentityDefinition``. Visitors, validators and custom +expression deparsers traverse generation and ON UPDATE expressions and comment +literals. This is syntax modeling; server restrictions on permissible generation +expressions are not evaluated. + +Charset remains in ``ColDataType.getCharacterSet()``; column collation is in its +COLLATE option. Consumers can map both directly without reconstructing tokens. +StringValue comment bodies retain their SQL escape representation. + +The legacy ``getColumnSpecs()`` returns a token snapshot when options are +structured; expression fragments may occupy one token and structured keywords +use canonical capitalization. Mutate the option objects to change the AST. +``addColumnSpecs`` preserves existing options; ``setColumnSpecs`` explicitly +replaces them with raw specifications. + +Inspect logical replication statements +====================================== + +PostgreSQL publications and subscriptions have separate statement and option models. No database connection is opened when these statements are parsed. + +.. code-block:: java + + CreatePublication publication = (CreatePublication) CCJSqlParserUtil.parse( + "CREATE PUBLICATION changes FOR TABLE accounts (id) WHERE (active = true)"); + PublicationTable target = publication.getTargets().get(0).getTables().get(0); + Table table = target.getTable(); + List columns = target.getColumns(); + Expression filter = target.getWhere(); + +A ``PublicationTarget`` distinguishes a group of explicit tables from ``TABLES IN SCHEMA``. Target order, repeated ``TABLE`` groups, ``ONLY`` and an explicit descendant ``*`` are preserved. ``CreatePublication.isAllTables()`` represents ``FOR ALL TABLES``; an empty target list without that flag means no target clause was specified. ``AlterPublication.getAction()`` distinguishes adding, replacing or removing targets from option, owner and name changes. + +``PublicationOption`` exposes typed operation sets, partition-root booleans and generated-column modes. ``SubscriptionOption`` has a separate key enum and typed streaming, origin, synchronous-commit and boolean accessors. The ordered option lists contain only explicitly written options; server defaults, which can differ across PostgreSQL versions, are not injected into the AST. A missing value on a boolean option represents its explicit short form, equivalent to ``= true``. + +.. code-block:: java + + CreateSubscription subscription = (CreateSubscription) CCJSqlParserUtil.parse( + "CREATE SUBSCRIPTION changes_sub CONNECTION 'dbname=app' " + + "PUBLICATION changes WITH (connect = false)"); + StringValue connection = subscription.getConnection(); + List publications = subscription.getPublications(); + Boolean connect = subscription.getOptions().get(0).getBooleanValue(); + +Connection strings remain string literals for lossless SQL regeneration. They can contain credentials and should not be logged without redaction. ``SubscriptionOption.isSlotNameNone()`` distinguishes the unquoted ``NONE`` keyword from a literal slot named ``'NONE'``. ``AlterSubscription`` covers connection changes, publication lists and refresh, enable/disable, options, skip LSN, ownership and renaming. + +Publication table columns and row filters participate in visitors and custom expression deparsers. ``TablesNamesFinder`` reports explicitly named publication tables, but cannot enumerate ``ALL TABLES`` or schema-wide targets without a catalog. Publication and subscription names are not table names. Feature classification reports schema modification; a subscription that may start asynchronous replication can additionally report possible data modification. + +See `CREATE PUBLICATION `_, `ALTER PUBLICATION `_, `CREATE SUBSCRIPTION `_ and `ALTER SUBSCRIPTION `_. + +Inspect type, domain and extension statements +============================================= + +PostgreSQL type DDL uses the existing column data-type model. A ``CreateType`` exposes its qualified name and a ``TypeDefinition``: ``EnumTypeDefinition``, ``CompositeTypeDefinition`` or ``RangeTypeDefinition``. A null definition denotes a shell type. Enum labels are ordered ``StringValue`` nodes, composite attributes carry a name, ``ColDataType`` and optional collation, and range options distinguish the subtype from names of support functions, collations and operator classes. + +.. code-block:: java + + CreateType type = (CreateType) CCJSqlParserUtil.parse( + "CREATE TYPE mood AS ENUM ('sad', 'ok', 'happy')"); + EnumTypeDefinition definition = (EnumTypeDefinition) type.getDefinition(); + String firstLabel = definition.getLabels().get(0).getValue(); + + AlterType alteration = (AlterType) CCJSqlParserUtil.parse( + "ALTER TYPE mood ADD VALUE IF NOT EXISTS 'fine' AFTER 'ok'"); + alteration.getPosition(); // AlterType.Position.AFTER + alteration.getNeighborValue(); // StringValue containing 'ok' + +``AlterType`` also models type ownership, renaming, schema moves and ordered composite-attribute changes. Base-type I/O definitions and base-type ``SET (...)`` alterations are not part of this support. + +``CreateDomain`` stores a ``ColDataType``, default expression and ordered ``DomainConstraint`` nodes. Each constraint distinguishes nullability from a check expression and preserves its optional name. ``AlterDomain`` models default/nullability changes, constraint actions, ownership, renaming and schema moves. Its ``isNotValid()`` flag describes the newly added check constraint. Domain expressions participate in statement visitors, expression deparsers and validation. + +``CreateExtension`` preserves ``IF NOT EXISTS``, the optional ``WITH``, and ordered schema/version/cascade options. Version identifiers and quoted versions are represented as ``Column`` and ``StringValue``, respectively. ``AlterExtension`` distinguishes update, schema change, and member addition/removal. An ``ExtensionObject`` identifies the object kind and target; function/procedure/aggregate members expose a ``RoutineReference`` with typed signature arguments, not call expressions. A null argument list means the signature was omitted, whereas an empty list means explicit ``()``. + +Type names, domain names and routine signatures are not reported as tables by ``TablesNamesFinder``; relation members of an extension are included. Extension scripts themselves are not inspected or executed. The obsolete ``CREATE EXTENSION ... FROM`` form is not supported. + +See PostgreSQL's documentation for `CREATE TYPE `_, `ALTER TYPE `_, `CREATE DOMAIN `_ and `ALTER EXTENSION `_. + +Classify a Statement ============================== -There are two features for handling errors +Every ``Statement`` can tell you **what it does** — whether it reads, writes, changes the schema, or sends rows back — without a second parse and without writing a visitor: + +.. code-block:: java -- ``parser.withErrorRecovery(true)`` will continue to the next statement separator and return an empty statement. -- ``parser.withUnsupportedStatements(true)`` will return an instance of the `UnsupportedStatement` class, although the first statement **must** be a regular statement + StatementFeatures features = CCJSqlParserUtil.parse(sqlStr).getFeatures(); + + if (features.returnsResultSet()) { + statement.executeQuery(sqlStr); + } else { + statement.executeUpdate(sqlStr); + } + +Why bother +------------------------------ + +Two jobs come up constantly, and both are traps if you approach them with string matching: + +**Safeguarding a read-only client.** Reporting tools, BI front-ends, LLM-generated SQL, user-supplied filters — plenty of code paths need to reject anything that writes, *before* the statement reaches the database. Checking whether the text starts with ``SELECT`` is not a safeguard. + +**Dispatching correctly.** JDBC wants ``executeQuery()`` for row-returning statements and ``executeUpdate()`` for the rest. Get it backwards and you get an exception, not a wrong answer, but you still have to decide. + +The reason a keyword check fails is that SQL is not organised into tidy Query/DML/DDL buckets. ``RETURNING`` turns a ``DELETE`` into a row source. A data-modifying CTE hides that ``DELETE`` inside something that begins with ``WITH``. An ``INSERT`` can contain a whole ``SELECT`` and still return nothing: + +.. list-table:: + :header-rows: 1 + :widths: 55 15 15 15 + + * - SQL + - returns rows + - reads + - writes + * - ``SELECT * FROM t`` + - yes + - yes + - no + * - ``INSERT INTO x SELECT * FROM t`` + - **no** + - yes + - yes + * - ``DELETE FROM t RETURNING *`` + - **yes** + - no + - yes + * - ``WITH c AS (DELETE FROM t RETURNING *) SELECT * FROM c`` + - yes + - no + - **yes** + * - ``INSERT INTO x WITH c AS (DELETE FROM t RETURNING *) SELECT * FROM c`` + - **no** + - no + - yes + * - ``CREATE TABLE t AS SELECT a FROM u`` + - no + - yes + - no (schema) + * - ``SELECT a INTO new_table FROM t`` + - **no** + - yes + - **yes** + +Note the last two rows of the fourth and fifth entries: ``RETURNING`` appearing *somewhere* in the statement is not the question. What matters is whether rows reach the client, and that is a property of the statement's own result position, not of any nested one. + +The features +------------------------------ + +.. list-table:: + :header-rows: 1 + :widths: 25 40 35 + + * - ``StmtFeature`` + - Meaning + - Typical statements + * - ``READS_DATA`` + - reads persistent rows + - ``SELECT .. FROM t``, ``MERGE``, ``CREATE TABLE .. AS SELECT`` + * - ``RETURNS_RESULT_SET`` + - rows are sent back to the client + - ``SELECT``, ``DELETE .. RETURNING``, ``SHOW``, ``DESCRIBE``, ``EXPLAIN`` + * - ``MODIFIES_DATA`` + - rows are written or destroyed + - ``INSERT``, ``UPDATE``, ``DELETE``, ``MERGE``, ``UPSERT``, ``TRUNCATE``, ``DROP`` + * - ``MODIFIES_SCHEMA`` + - the catalogue changes + - ``CREATE``, ``ALTER``, ``DROP``, ``TRUNCATE``, ``GRANT``, ``COMMENT`` + * - ``MODIFIES_SESSION`` + - session state changes + - ``SET``, ``RESET``, ``USE``, ``DECLARE``, ``ALTER SESSION`` + * - ``MODIFIES_TRANSACTION`` + - transaction state or locks change + - ``COMMIT``, ``ROLLBACK``, ``SAVEPOINT``, ``LOCK``, ``SELECT .. FOR UPDATE`` + * - ``OPAQUE`` + - nothing further can be known statically + - ``CALL``, ``EXECUTE``, dynamic SQL, unsupported statements + +They are **not mutually exclusive**. ``INSERT .. RETURNING *`` carries ``MODIFIES_DATA`` *and* ``RETURNS_RESULT_SET``; ``TRUNCATE`` carries ``MODIFIES_SCHEMA`` *and* ``MODIFIES_DATA``, so that a guard looking only for data changes still stops it. + +Proven, possible, excluded +------------------------------ + +Each feature is three-valued, because some questions cannot be answered from syntax alone. ``SELECT nextval('s')`` writes; ``SELECT upper(name)`` does not; the parser cannot tell them apart, because volatility lives in the database catalogue, not in the SQL text. + +So a feature is either **proven**, **not excludable**, or **ruled out**, and you pick which side you want to be wrong on: .. code-block:: java - :caption: Error Recovery - CCJSqlParser parser = new CCJSqlParser( - "select * from mytable; select from; select * from mytable2" ); - Statements statements = parser.withErrorRecovery().Statements(); + StatementFeatures features = statement.getFeatures(); - // 3 statements, the failing one set to NULL - assertEquals(3, statements.size()); - assertNull(statements.get(1)); + features.is(StmtFeature.MODIFIES_DATA); // the grammar proves it + features.may(StmtFeature.MODIFIES_DATA); // proven, or could not be excluded - // errors are recorded - assertEquals(1, parser.getParseErrors().size()); +.. list-table:: + :header-rows: 1 + :widths: 25 20 55 + + * - Caller + - Uses + - Because + * - read-only guard + - ``may(..)`` + - a false negative lets a write through + * - JDBC dispatcher + - ``is(..)`` + - a false positive picks ``executeQuery`` for ``CREATE INDEX`` + +Convenience methods wrap the common combinations: .. code-block:: java - :caption: Unsupported Statement - Statements statements = CCJSqlParserUtil.parseStatements( - "select * from mytable; select from; select * from mytable2; select 4;" - , parser -> parser.withUnsupportedStatements() ); + features.returnsResultSet(); // is(RETURNS_RESULT_SET) + features.modifiesData(); // is(MODIFIES_DATA) + features.mayModifyData(); // may(MODIFIES_DATA) + features.modifiesSchema(); // is(MODIFIES_SCHEMA) + features.isOpaque(); // CALL, EXECUTE, dynamic SQL - // 4 statements with one Unsupported Statement holding the content - assertEquals(4, statements.size()); - assertInstanceOf(UnsupportedStatement.class, statements.get(1)); - assertEquals("select from", statements.get(1).toString()); +When something is merely *possible*, the analysis tells you **why**, so you can resolve it against your own catalogue or allow-list rather than guessing: - // no errors records, because a statement has been returned - assertEquals(0, parser.getParseErrors().size()); +.. code-block:: java + :caption: Safeguarding a read-only connection + + StatementFeatures features = CCJSqlParserUtil.parse(sqlStr).getFeatures(); + + if (connection.isReadOnly() && features.mayModifyData()) { + throw new SQLException( + "rejected, unresolved: " + features.getUnresolvedReferences()); + // e.g. [nextval] + } + +If you can prove some functions side-effect free, hand in a predicate and the uncertainty collapses: + +.. code-block:: java + + Set pure = Set.of("upper", "lower", "coalesce"); + + StatementFeatures features = statement.getFeatures(pure::contains); + + // SELECT upper(name) FROM t + features.mayModifyData(); // false + features.getUnresolvedReferences(); // empty + +.. warning:: + + The verdict is a **syntactic claim, not a semantic guarantee**. A user-defined function, a trigger on the target table or a ``CALL`` can do anything. Use this to reject obviously dangerous SQL early; it does not replace database-side permissions. + +Scripts +------------------------------ + +``Statements`` is an ``ArrayList`` and not a ``Statement``, so it has no ``getFeatures()`` of its own. Two entry points, for two different questions: + +.. code-block:: java + + Statements script = CCJSqlParserUtil.parseStatements( + "UPDATE t SET a = 1; SELECT a FROM t;"); + + // one union verdict — for guards + StatementFeatures all = StatementFeatureVisitor.analyse(script); + all.modifiesData(); // true + all.returnsResultSet(); // true + + // one verdict per statement, in order — for dispatchers + List each = StatementFeatureVisitor.analyseEach(script); + each.get(0).returnsResultSet(); // false, the UPDATE + each.get(1).returnsResultSet(); // true, the SELECT + +The union answers *"may this script write anything?"*. It cannot answer *"executeQuery or executeUpdate?"*, because it never says which statement returns the rows. + +.. note:: + + Nothing is cached. The tree is mutable and you may build statements by hand, so the verdict is recomputed on every call — microseconds against a millisecond-scale parse. + + +Find Table Names +============================== + +``net.sf.jsqlparser.util.TablesNamesFinder`` returns every table name in a statement or an expression, including the ones buried in sub-selects. + +.. code-block:: java + + // find in Statements + String sqlStr = "select * from A left join B on A.id=B.id and A.age = (select age from C)"; + Set tableNames = TablesNamesFinder.findTables(sqlStr); + assertThat( tableNames ).containsExactlyInAnyOrder("A", "B", "C"); + + // find in Expressions + String exprStr = "A.id=B.id and A.age = (select age from C)"; + tableNames = TablesNamesFinder.findTablesInExpression(exprStr); + assertThat( tableNames ).containsExactlyInAnyOrder("A", "B", "C"); Use the Visitor Patterns ============================== -Traverse the Java Object Tree using the Visitor Patterns: +Casting your way down the tree works for one known shape. For anything general — every column in a query, every table in a script — use a visitor: you override only the node types you care about and the adapters walk the rest. + +There is one visitor interface per layer of the model, and an ``..Adapter`` base class for each that already implements the full traversal: + +.. list-table:: + :header-rows: 1 + :widths: 35 65 + + * - Adapter + - Reacts to + * - ``StatementVisitorAdapter`` + - statements: ``Select``, ``Insert``, ``CreateTable``, … + * - ``SelectVisitorAdapter`` + - query bodies: ``PlainSelect``, ``SetOperationList``, ``WithItem``, … + * - ``ExpressionVisitorAdapter`` + - expressions: ``Column``, ``Function``, ``EqualsTo``, … + * - ``FromItemVisitorAdapter`` + - FROM items: ``Table``, ``ParenthesedSelect``, ``TableFunction``, … .. code-block:: java @@ -225,28 +655,15 @@ Traverse the Java Object Tree using the Visitor Patterns: // Invoke the Statement Visitor without a context stmt.accept(statementVisitor, null); -Find Table Names -============================== +.. tip:: -The class ``net.sf.jsqlparser.util.TablesNamesFinder`` can be used to return all Table Names from a Query or an Expression. - -.. code-block:: java - - // find in Statements - String sqlStr = "select * from A left join B on A.id=B.id and A.age = (select age from C)"; - Set tableNames = TablesNamesFinder.findTables(sqlStr); - assertThat( tableNames ).containsExactlyInAnyOrder("A", "B", "C"); - - // find in Expressions - String exprStr = "A.id=B.id and A.age = (select age from C)"; - tableNames = TablesNamesFinder.findTablesInExpression(exprStr); - assertThat( tableNames ).containsExactlyInAnyOrder("A", "B", "C"); + The second parameter of every ``visit()`` is a free-form **context** object of your choosing, threaded through the traversal. Pass ``null`` when you do not need it. Build a SQL Statement ============================== -Build any SQL Statement from Java Code using a fluent API: +The object model works in both directions. Build the tree from Java and print it as SQL: .. code-block:: java @@ -274,14 +691,309 @@ Build any SQL Statement from Java Code using a fluent API: Assertions.assertEquals(expectedSQLStr, builder.toString()); -Define the Parser Features +ODBC timestamp intervals +============================== + +In ODBC escapes such as ``{fn TIMESTAMPADD(SQL_TSI_YEAR, 2, travel_date)}`` and +``{fn TIMESTAMPDIFF(SQL_TSI_DAY, start_date, end_date)}``, the first argument is a +``DateUnitExpression`` for the nine standard ``SQL_TSI_*`` interval keywords. +The original ODBC keyword is preserved on output and is not visited as a column. +This applies only to unqualified, escaped calls with three arguments and a bare +interval keyword. Ordinary calls, qualified names, quoted identifiers and other +arguments keep their existing expression interpretation. + +Handle Parse Errors +============================== + +``CCJSqlParserUtil.parse(String, ...)`` requires a statement: null and empty string +inputs throw ``JSQLParserException``, matching the default behavior for whitespace-only +and comment-only input. ``CCJSqlParserUtil.parseStatements(String, ...)`` returns a new, +mutable empty ``Statements`` list for null or empty input, as it already does for +whitespace-only and comment-only input. This applies to the overloads with parser +configuration callbacks and caller-provided executors; caller-provided executors remain +open. These empty-input results replace the previous null returns of these methods. + +By default a syntax error aborts the whole parse. Two features let a script survive one bad statement: + +- ``parser.withErrorRecovery(true)`` skips to the next statement separator and returns an empty statement. +- ``parser.withUnsupportedStatements(true)`` returns an ``UnsupportedStatement`` holding the raw text instead — though the **first** statement must be a regular one. + +.. code-block:: java + :caption: Error Recovery + + CCJSqlParser parser = new CCJSqlParser( + "select * from mytable; select from; select * from mytable2" ); + Statements statements = parser.withErrorRecovery().Statements(); + + // 3 statements, the failing one set to NULL + assertEquals(3, statements.size()); + assertNull(statements.get(1)); + + // errors are recorded + assertEquals(1, parser.getParseErrors().size()); + +.. code-block:: java + :caption: Unsupported Statement + + Statements statements = CCJSqlParserUtil.parseStatements( + "select * from mytable; select from; select * from mytable2; select 4;" + , parser -> parser.withUnsupportedStatements() ); + + // 4 statements with one Unsupported Statement holding the content + assertEquals(4, statements.size()); + assertInstanceOf(UnsupportedStatement.class, statements.get(1)); + assertEquals("select from", statements.get(1).toString()); + + // no errors records, because a statement has been returned + assertEquals(0, parser.getParseErrors().size()); + +.. note:: + + An ``UnsupportedStatement`` is reported as ``OPAQUE`` by :ref:`Classify a Statement` — nothing about its effects is knowable. + + +Choose a Dialect ============================== -JSQLParser interprets Squared Brackets ``[..]`` as Arrays, which does not work with MS SQL Server and T-SQL. Please use the Parser Features to instruct JSQLParser to read Squared Brackets as Quotes instead. +One grammar covers every supported RDBMS, but a few pieces of syntax mean different things in different products. Those are switched with parser features, and a ``Dialect`` preset turns on the right set for you. -JSQLParser allows for standard compliant Single Quote ``'..`` Escaping. Additional Back-slash ``\..`` Escaping needs to be activated by setting the ``BackSlashEscapeCharacter`` parser feature. +.. code-block:: java -Additionally there are Features to control the Parser's effort at the cost of the performance. + // MySQL: backslash escapes, hash line comments, double-quoted strings + Statement stmt = CCJSqlParserUtil.parse( + "SELECT `col` FROM t WHERE a = 'x\\'yz' AND b = 42#24" + , parser -> parser.withDialect(Dialect.MYSQL) ); + +.. list-table:: + :header-rows: 1 + :widths: 30 70 + + * - ``Dialect`` + - Turns on + * - ``MYSQL`` + - ``withBackslashEscapeCharacter``, ``withHashLineComments``, ``withDoubleQuotedStrings`` (MySQL and MariaDB, the last for the default ``sql_mode``) + * - ``SQLSERVER`` + - ``withSquareBracketQuotation`` and ``CLUSTERED`` / ``NONCLUSTERED`` options on table-level primary key and unique constraints and ``CREATE INDEX`` + * - ``POSTGRESQL``, ``ANSI_SQL`` + - the newline rule for adjacent string literals + * - ``BIGQUERY`` + - ``withDoubleQuotedStrings``, ``withBackslashEscapeCharacter``, ``withHashLineComments``, any-whitespace rule for adjacent string literals + * - ``DATABRICKS`` + - ``withDoubleQuotedStrings``, ``withBackslashEscapeCharacter``, any-whitespace rule for adjacent string literals + * - ``SNOWFLAKE`` + - ``withBackslashEscapeCharacter`` only, double quotes stay quoted identifiers + * - ``INFORMIX`` + - Informix ``ALTER TABLE ... ADD CONSTRAINT`` definitions with optional trailing constraint names + * - ``SPANNER`` + - GoogleSQL ``CREATE [UNIQUE] NULL_FILTERED INDEX`` with a separate null-filtering flag + * - ``DORIS`` + - ``JOIN [shuffle]`` and ``JOIN [broadcast]`` distribution hints + * - ``COCKROACHDB`` + - ``ALTER TABLE ... ALTER PRIMARY KEY USING COLUMNS (...)`` with optional hash sharding and storage parameters + * - ``TERADATA`` + - ``UPDATE target FROM sources SET ...`` with the FROM clause before SET + +Features set explicitly *after* the preset win over it. + +MySQL user-variable targets in ``SELECT ... INTO @variable`` require +``Dialect.MYSQL`` or ``Dialect.MARIADB``. They are stored in +``PlainSelect.getMySqlSelectIntoClause().getVariables()`` as ``UserVariable`` +expressions, with the clause position preserved before ``FROM`` or at the end +of the query. They are not table targets in ``getIntoTables()``. + +Doris distribution hints require ``parser.withDialect(Dialect.DORIS)``. +``Join.getJoinHint()`` exposes the keyword and ``Position.AFTER_JOIN``; +the existing SQL Server hints use ``Position.BEFORE_JOIN``. Rendering preserves +both the position and the brackets around a Doris hint. + +CockroachDB primary-key changes require ``parser.withDialect(Dialect.COCKROACHDB)``. +Their action is an ``AlterExpressionPrimaryKey`` with key elements and storage +parameters in ``getIndex()``. ``isUsingHash()`` preserves ``USING HASH``, while +``getBucketCount()`` holds the legacy ``WITH BUCKET_COUNT = expression`` value. +The newer ``WITH (bucket_count = expression)`` form uses the index storage parameters. + +With ``Dialect.TERADATA``, ``UPDATE a FROM target a, source b SET a.id = b.id`` +uses the existing ``Update`` model's ``fromItem`` and ``joins`` properties. +``isFromBeforeSet()`` preserves the clause position in both SQL renderers. +Table discovery and metadata validation recognize a target alias declared in +that FROM clause. Other dialects retain the existing FROM-after-SET syntax. + +``Dialect.SQLSERVER`` supports methods on expression results, including +``(SELECT ... FOR XML PATH(''), TYPE).value('.', 'varchar(max)')``. +``MethodCallExpression`` exposes the receiver expression and a ``Function`` +containing the method name and arguments. Field access and method calls share +the navigation grammar; expression visitors and deparsers traverse both the +receiver and method arguments. XQuery strings remain string literals. + +``Dialect.POSTGRESQL`` enables ``DO [LANGUAGE name] code [LANGUAGE name]``, +with the language clause allowed once, before or after the body. +``DoStatement.getCode()`` is a ``StringValue`` that preserves the literal's +quotes, dollar tag and body text. The optional language and its position have +separate properties; an omitted language remains unspecified in the AST. +The body is language-specific source, not a parsed PL/pgSQL statement tree. +Expression visitors can inspect or replace the body literal. Feature analysis +reports ``OPAQUE``; table discovery rejects this statement because the body's +table accesses are unknown. Validation checks the ``doStatement`` capability, +without validating the procedural language inside the literal. + +Enable the PostgreSQL dialect when parsing a script containing a ``DO`` block: + +.. code-block:: java + + Statements statements = CCJSqlParserUtil.parseStatements( + "DO $$BEGIN RAISE NOTICE 'hello'; END$$; SELECT 1;", + parser -> parser.withDialect(Dialect.POSTGRESQL)); + DoStatement block = (DoStatement) statements.get(0); + String body = block.getCode().getValue(); + // body: BEGIN RAISE NOTICE 'hello'; END + // statements.get(1) is the following SELECT. + +Semicolons and SQL statements inside the body remain part of its string literal; +they do not split the surrounding script into additional statements. + +With ``Dialect.POSTGRESQL``, ``#`` terminates an unquoted identifier, so JSON +operators such as ``js#>>'{a}'`` and ``js#>'{a}'`` work without surrounding +spaces. Quote identifiers containing ``#``, for example ``"js#"``. Other +dialects retain their existing identifier and hash-comment rules. + +With ``Dialect.SQLSERVER``, ``SET NOCOUNT ON`` and grouped boolean options such as +``SET QUOTED_IDENTIFIER, ANSI_NULLS OFF`` use ``SetStatement.getOnOffOptions()``. +The ordered ``OnOffOption`` list and shared ``isOn()`` value are editable; +``setOnOffOptions()`` replaces generic assignments and their scope. Both SQL +renderers share statement punctuation while generic assignments retain expression +visitor support. Parsing a SET directive records it without changing lexer settings. +``Dialect.SQLSERVER`` enables ``INSERT BULK table (name type, ...) WITH (...)``. +``InsertBulk`` exposes the target table, existing ``ColumnDefinition`` models, +and ordered typed options, including ``ROWS_PER_BATCH`` and ``ORDER`` keys. +The SQL declaration is preserved; the following binary bulk-load data stream +is outside the SQL parser. Visitors and deparsers traverse option values and +ordering expressions. Validation uses the ``insertBulk`` capability. + +With ``Dialect.SQLSERVER``, ``PRIMARY KEY NONCLUSTERED (id)`` and +``UNIQUE CLUSTERED (id)`` store their clustering option in ``Index.getClustering()`` +for both ``CREATE TABLE`` and ``ALTER TABLE``. Without that dialect, these words +retain their existing interpretation as optional index names. +SQL Server ``CREATE TABLE`` also accepts a trailing comma after the final column +or table constraint. SQL output normalizes the definition by omitting that comma. + +``CREATE UNIQUE NONCLUSTERED INDEX ix ON t (id)`` also requires +``Dialect.SQLSERVER``. Uniqueness remains in ``Index.getType()`` and clustering +is stored separately in ``Index.getClustering()``. With ``Dialect.SPANNER``, +``CREATE UNIQUE NULL_FILTERED INDEX ix ON t (id)`` stores null filtering in +``CreateIndex.isNullFiltered()``. An omitted clustering or null-filtering option +is not supplied from database defaults. The Spanner preset currently selects +this index syntax; it does not configure GoogleSQL string-literal rules. + +With ``Dialect.POSTGRESQL``, index keys accept schema-qualified collation and +operator-class names, for example ``name COLLATE pg_catalog."C" +pg_catalog.text_ops ASC NULLS LAST``. Function keys such as ``lower(name)`` are +stored as expressions. Key attributes are available through ``getCollation()``, +``getOperatorClass()``, ``getOperatorClassParameters()``, ``getSortOrder()`` and +``getNullOrdering()`` on ``Index.ColumnParams``. Under this dialect these +attributes are not duplicated in the legacy ``getParams()`` list, so changing +or removing them is reflected when rendering SQL. Other dialects retain the +legacy parameter representation, including MySQL prefix lengths. + +``CreateIndexDeParser`` and ``StatementDeParser`` pass key expressions, +structured option values and the partial-index predicate to their expression +visitor. ``StatementVisitorAdapter``, ``TablesNamesFinder`` and index validation +traverse the same structured expressions. + +Informix's constraint form requires an explicit dialect selection: + +.. code-block:: java + + Statement stmt = CCJSqlParserUtil.parse( + "ALTER TABLE child ADD CONSTRAINT FOREIGN KEY (id) " + + "REFERENCES parent(id) CONSTRAINT fk_child", + parser -> parser.withDialect(Dialect.INFORMIX)); + +Row pattern matching (MATCH_RECOGNIZE) +-------------------------------------- + +``MatchRecognize`` is a ``FromItem`` wrapping the input relation. Its input +alias and output alias are independent. ``getMeasures()`` and +``getDefinitions()`` expose ordinary SQL expressions; ``getPattern()`` exposes +an editable ``RowPattern`` tree with variables, groups, ordered alternatives, +sequences, anchors and quantifiers. Oracle and Snowflake additionally support +``PERMUTE`` and exclusion nodes. Permutations remain compact in the AST. + +.. code-block:: java + + PlainSelect select = (PlainSelect) CCJSqlParserUtil.parse( + "SELECT * FROM events MATCH_RECOGNIZE (" + + "ORDER BY seq MEASURES SUM(A.price) AS total " + + "PATTERN (A+) DEFINE A AS price > 0 " + + "OPTIONS (use_longest_match = TRUE))", + parser -> parser.withDialect(Dialect.BIGQUERY)); + MatchRecognize match = (MatchRecognize) select.getFromItem(); + RowPattern.Quantified repetition = (RowPattern.Quantified) match.getPattern(); + repetition.setReluctant(true); + System.out.println(select); // PATTERN (A+?) + +``FromItemVisitorAdapter`` traverses the input and clause expressions. +``RowPatternVisitorAdapter`` traverses pattern nodes and optionally visits +expressions in quantifier bounds. ``RowPatternFunction`` represents an explicit +``RUNNING`` or ``FINAL`` prefix while exposing the underlying ``Function`` to +existing expression visitors. Both ``toString()`` and the deparsers render the +current AST, including edited nested ``STRUCT`` arguments. + +Select the source dialect explicitly: + +* ``BIGQUERY`` requires ``ORDER BY``, ``MEASURES``, ``PATTERN`` and ``DEFINE``. + It supports empty alternatives, literal or query-parameter bounds, adjacent + anchors such as ``^A+$``, and ``OPTIONS (use_longest_match = TRUE/FALSE)``. +* ``ORACLE`` supports optional ordering/measures, row output modes, targeted + ``AFTER MATCH SKIP``, ``SUBSET``, permutations, exclusions and function modes. +* ``SNOWFLAKE`` supports the corresponding documented forms except ``SUBSET`` + and BigQuery options. Its documented alternation-before-concatenation + precedence is used for the pattern AST. Other presets use + concatenation-before-alternation. Rendering adds explicit parentheses at + mixed operator boundaries; it does not translate expression syntax between DBs. + +Validation checks the ``matchRecognize`` and ``matchRecognizeOptions`` features. +With an explicit dialect, it also checks selected static rules: pattern variable +names, bounds, subset/skip targets and incompatible clause options. Table +finding does not treat pattern qualifiers such as ``A.price`` as table names. +Metadata validation does not bind these columns to the input relation. +Function eligibility, aggregation/type rules, data-dependent skip failures and +query execution remain the database's responsibility. + +The test fixtures were compared before and after deparsing using GoogleSQL's +reference evaluator and Oracle 26ai Free. The reference evaluator is not the +BigQuery service. Snowflake coverage follows its documentation and parser +round-trip tests; it has not been executed against a Snowflake account. + +See the official `BigQuery MATCH_RECOGNIZE syntax +`_, +`Oracle row pattern matching guide +`_ +and `Snowflake MATCH_RECOGNIZE reference +`_. + +The individual features +------------------------------ + +.. list-table:: + :header-rows: 1 + :widths: 35 65 + + * - Feature + - What it changes + * - ``withSquareBracketQuotation`` + - ``[..]`` reads as a quoted identifier instead of an array — needed for T-SQL on MS SQL Server and Sybase + * - ``withBackslashEscapeCharacter`` + - ``\\..`` escaping inside string literals, in addition to the standard ``'..`` doubling + * - ``withDoubleQuotedStrings`` + - ``".."`` reads as a string literal instead of a quoted identifier (BigQuery, Spark/Databricks, MySQL default ``sql_mode``) + * - ``withHashLineComments`` + - ``#`` starts a line comment + * - ``withAdjacentStringLiterals`` + - adjacent string literals concatenate: ``NEWLINE`` (SQL standard, PostgreSQL) or ``WHITESPACE`` (GoogleSQL, Spark/Databricks); ``true`` selects ``NEWLINE``, ``false`` switches it off + * - ``withAllowComplexParsing`` + - permits deeply nested expressions, at a significant performance cost + * - ``withTimeOut`` + - aborts parsing after N milliseconds .. code-block:: java @@ -317,4 +1029,186 @@ Additionally there are Features to control the Parser's effort at the cost of th sqlStr , parser -> parser .withBackslashEscapeCharacter(true) - ); \ No newline at end of file + ); + +Things that trip people up +------------------------------ + +.. hint:: + + 1) **Quoting:** Double quotes ``".."`` quote identifiers. Square brackets ``[..]`` are arrays unless you turn on ``withSquareBracketQuotation``. + + 2) **Reserved keywords:** JSQLParser uses a more restrictive list than most databases, and such keywords **need to be quoted**. + + 3) **Escaping:** standard single-quote ``'..`` escaping is always on. Backslash escaping is not — set ``withBackslashEscapeCharacter``. + + 4) **Oracle alternative quoting** is partially supported, for common brackets: ``q'{...}'``, ``q'[...]'``, ``q'(...)'`` and ``q''...''``. + + +Compile from Source Code +============================== + +You need ``JDK 8`` or ``JDK 11``. JSQLParser-4.9 is the last ``JDK 8`` compatible release; everything after depends on ``JDK 11``. Building JSQLParser-5.1 and newer with Gradle needs a JDK 17 toolchain, because of the plugins used. + +.. tab:: Maven + + .. code-block:: shell + + git clone --depth 1 https://github.com/JSQLParser/JSqlParser.git + cd JSqlParser + mvn install + +.. tab:: Gradle + + .. code-block:: shell + + git clone --depth 1 https://github.com/JSQLParser/JSqlParser.git + cd JSqlParser + gradle publishToMavenLocal + +PostgreSQL roles, privileges and triggers +----------------------------------------- + +``CreateRole`` and ``AlterRole`` model role attributes and configuration changes, +including PostgreSQL's USER/GROUP aliases. Role options are ordered and typed; +passwords and configuration values are expressions. Omitted options remain +omitted. ``CREATE USER name`` without attributes retains the existing MySQL +``CreateUser`` AST by default. Select ``Dialect.POSTGRESQL`` explicitly to obtain +the PostgreSQL ``CreateRole`` AST for this ambiguous form:: + + CreateRole user = (CreateRole) CCJSqlParserUtil.parse( + "CREATE USER app", + parser -> parser.withDialect(AbstractJSqlParser.Dialect.POSTGRESQL)); + +``Grant`` retains its string-based getters, setters and fluent methods. Its +``PrivilegeClause`` exposes typed ``Privilege`` items, column lists, +``PrivilegeTarget`` kinds, multiple role memberships and grantor information. +``getPrivileges()`` is a mutable string view of the typed privilege list. +``getRole()`` and ``getObjectName()`` expose the first role or named target for +legacy callers; use ``getRoles()`` and ``getTarget()`` for the complete lists. +Names in ``PrivilegeTarget`` are multipart identifiers, not SQL clause text. +Routine targets use ``RoutineReference`` data-type signatures, with null +arguments for an omitted signature and an empty list for explicit ``()``. + +``Revoke`` shares the privilege payload and adds the revoked option and +CASCADE/RESTRICT behavior. ``AlterDefaultPrivileges`` has separate role/schema +scope and one nested GRANT or REVOKE. Table-name discovery reports explicit +table targets, not schema-wide targets, routine/type names or role names. + +``CreateTrigger`` supports both its existing MySQL statement body and a distinct +PostgreSQL routine invocation. PostgreSQL fields include multiple events, +UPDATE OF columns, constraint attributes, transition relations, row/statement +orientation and a WHEN expression. The absence of FOR ROW/STATEMENT is retained. +Expression visitors and ``StatementDeParser`` traverse privilege columns, +role values, trigger conditions and invocation arguments. Declaring a trigger +is classified as a schema change, not execution of its body. + +Capability validation covers these statement families, not every server-version +or catalog-dependent restriction. EVENT TRIGGER is outside this support. +Serialized role SQL can contain passwords; avoid logging real credentials. + +References: `CREATE ROLE `_, +`ALTER ROLE `_, +`GRANT `_, +`REVOKE `_, +`ALTER DEFAULT PRIVILEGES `_, +`CREATE TRIGGER `_. + +Oracle anonymous blocks +----------------------- + +With ``Dialect.ORACLE``, ``OracleBlock`` extends ``Block`` with variable declarations +and exception handlers. Initializers and ``OracleAssignment`` values are expressions; +nested blocks and handler bodies contain statements. Calls without ``CALL`` use +``Execute.ExecType.IMPLICIT``, preserving qualified names, parentheses and bind arguments. +``OracleNullStatement`` represents the PL/SQL ``NULL`` statement. Implicit calls are +recognized inside Oracle blocks, so application procedure names need no keyword registration. + +Shared traversal and rendering include declaration initializers, assignments and exception +handler bodies. Procedure side effects remain unknown; table discovery reports unsupported +procedure calls, and feature analysis remains conservative. This covers anonymous blocks +with variable declarations, SQL statements, assignments, calls, nesting and handlers, not +all PL/SQL declarations, loops, packages or procedure definitions. + +SQL Server routine declarations +------------------------------- + +``Dialect.SQLSERVER`` uses a shared declaration path for ``CREATE``, ``ALTER`` and +``CREATE OR ALTER FUNCTION/PROCEDURE``. ``CreateFunctionalStatement.getOperation()`` +identifies the operation. For functions, ``getReturnType()`` exposes scalar types, +inline ``RETURNS TABLE``, and a return variable with ordered ``TableElement`` column +and constraint definitions. Table elements reuse the existing definition traversal +and deparser, including custom expression visitors. + +With a structured return type, ``getFunctionDeclarationParts()`` contains the name +and parameter tokens; ``getRoutineBodyParts()`` contains the following options and +body. These remain opaque tokens, so this does not implement a T-SQL body AST or +resolve tables used inside a routine. Other dialects retain the existing token-list +representation. New operations have separate validation capabilities. + +Parse procedure definitions one SQL Server batch at a time: a procedure consumes the +remaining batch, including SQL after an ``END``. Client-side ``GO`` batch splitting is +not performed by this routine declaration parser. + +SQL Server identity inserts +--------------------------- + +With ``Dialect.SQLSERVER``, ``SET IDENTITY_INSERT dbo.actor ON`` uses +``SetIdentityInsertStatement``. ``getTable()`` reuses the qualified ``Table`` AST; +``isOn()`` and ``setOn()`` expose the session setting. Table names may include a +database and schema, including SQL Server bracket-quoted identifiers. Table +visitors, both SQL renderers and metadata validation use this structured target. +Feature analysis reports ``MODIFIES_SESSION``; the directive itself inserts no rows. +The dedicated validation capability is ``setIdentityInsert``. + +Legacy MySQL GROUP BY ordering +============================== + +MySQL before 8.0.13 accepted ``ASC`` and ``DESC`` on individual ``GROUP BY`` items. +Select the existing ``MYSQL`` dialect and explicitly enable this legacy syntax: + +.. code-block:: java + + Statement statement = CCJSqlParserUtil.parse( + "SELECT a FROM t GROUP BY a DESC", + parser -> parser.withDialect(Dialect.MYSQL).withLegacyMySqlGroupBy(true)); + +The option is disabled by default and does not enable this syntax in other dialects. +``GroupByElement`` keeps its existing expression list; ``getGroupBySortDirection(index)`` +returns each explicit direction, or null when omitted. Directions follow list positions; +replacing the expression list clears them. Validators report the separate +``selectGroupByOrdering`` feature, which is not enabled in the MySQL 8.0 capability. + +PostgreSQL COMMENT targets +========================= + +``COMMENT ON`` supports ``INDEX``, ``SCHEMA``, ``SEQUENCE``, ``DOMAIN``, ``TYPE``, +``MATERIALIZED VIEW``, ``FUNCTION`` and ``CONSTRAINT`` in addition to the existing +``TABLE``, ``COLUMN`` and ``VIEW`` forms. These unambiguous target forms also parse +without a dialect preset. Tagged dollar strings still require +``Dialect.POSTGRESQL`` or ``withDollarQuotedStringTags(true)``. + +The additional targets are exposed through ``Comment.getTarget()`` as a +``CommentTarget``. Its ``Kind`` identifies the object, and ``getName()`` preserves +the individual identifier components. ``Table`` is used as the name container; +an index or type name does not thereby represent a table dependency. +The original ``Comment.getTable()``, ``getColumn()`` and ``getView()`` accessors +continue to describe their respective existing forms. + +``COMMENT ON FUNCTION app.f(IN value integer) IS 'description'`` uses the shared +``RoutineReference`` in ``getTarget().getRoutine()``. Argument mode, optional name +and ``ColDataType`` are preserved. An omitted signature has null arguments; +``f()`` has an empty argument list. The signature identifies a function and is not +a function call. + +For ``COMMENT ON CONSTRAINT ck ON app.t IS NULL``, ``getName()`` is the constraint +name and ``getRelation()`` is its owning table. ``ON DOMAIN app.d`` sets +``isOnDomain()`` and stores the domain as the owner instead. ``NULL`` removes the +comment and remains represented by a null ``Comment.getComment()``. + +Table discovery visits tables, column owners, views, materialized views and +table-owned constraints. It does not infer an index's table or treat domains, +sequences, types or functions as tables. Statement visitors visit comment +literals, and custom SQL deparsers can replace the explicit relation or literal. +Feature analysis reports a schema modification. Validation exposes a separate +``commentOn...`` capability for each additional target kind. diff --git a/src/test/java/net/sf/jsqlparser/benchmark/DynamicParserRunner.java b/src/test/java/net/sf/jsqlparser/benchmark/DynamicParserRunner.java index f87acd1195..a9915f994c 100644 --- a/src/test/java/net/sf/jsqlparser/benchmark/DynamicParserRunner.java +++ b/src/test/java/net/sf/jsqlparser/benchmark/DynamicParserRunner.java @@ -9,32 +9,56 @@ */ package net.sf.jsqlparser.benchmark; -import net.sf.jsqlparser.parser.CCJSqlParser; -import net.sf.jsqlparser.statement.Statements; - +import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.net.URLClassLoader; +import java.util.EnumMap; +import java.util.Map; import java.util.concurrent.ExecutorService; import java.util.function.Consumer; +/** Owns the isolated class loader and keeps its AST types behind an Object boundary. */ public class DynamicParserRunner implements SqlParserRunner { + private final URLClassLoader loader; private final Method parseStatementsMethod; + private final Map> configurations = + new EnumMap<>(Configuration.class); public DynamicParserRunner(URLClassLoader loader) throws Exception { + this.loader = loader; Class utilClass = loader.loadClass("net.sf.jsqlparser.parser.CCJSqlParserUtil"); - Class ccjClass = loader.loadClass("net.sf.jsqlparser.parser.CCJSqlParser"); - Class consumerClass = Class.forName("java.util.function.Consumer"); // interface OK - parseStatementsMethod = utilClass.getMethod( - "parseStatements", - String.class, - ExecutorService.class, - consumerClass); + Class parserClass = loader.loadClass("net.sf.jsqlparser.parser.CCJSqlParser"); + parseStatementsMethod = utilClass.getMethod("parseStatements", String.class, + ExecutorService.class, Consumer.class); + for (Configuration configuration : Configuration.values()) { + Method method = parserClass.getMethod(configuration.methodName, boolean.class); + configurations.put(configuration, parser -> { + try { + method.invoke(parser, configuration.value); + } catch (ReflectiveOperationException ex) { + throw new IllegalStateException( + "Cannot apply parser configuration " + configuration, ex); + } + }); + } + } + + @Override + public Object parseStatements(String sql, ExecutorService executorService, + Configuration configuration) throws Exception { + try { + return parseStatementsMethod.invoke(null, sql, executorService, + configuration == null ? null : configurations.get(configuration)); + } catch (InvocationTargetException ex) { + if (ex.getCause() instanceof Exception) { + throw (Exception) ex.getCause(); + } + throw ex; + } } @Override - public Statements parseStatements(String sql, - ExecutorService executorService, - Consumer consumer) throws Exception { - return (Statements) parseStatementsMethod.invoke(null, sql, executorService, null); + public void close() throws java.io.IOException { + loader.close(); } } diff --git a/src/test/java/net/sf/jsqlparser/benchmark/JSQLParserBenchmark.java b/src/test/java/net/sf/jsqlparser/benchmark/JSQLParserBenchmark.java index abe61a8048..3786eace5f 100644 --- a/src/test/java/net/sf/jsqlparser/benchmark/JSQLParserBenchmark.java +++ b/src/test/java/net/sf/jsqlparser/benchmark/JSQLParserBenchmark.java @@ -9,8 +9,6 @@ */ package net.sf.jsqlparser.benchmark; -import net.sf.jsqlparser.parser.CCJSqlParser; -import net.sf.jsqlparser.statement.Statements; import org.openjdk.jmh.annotations.*; import org.openjdk.jmh.infra.Blackhole; @@ -21,7 +19,6 @@ import java.nio.charset.StandardCharsets; import java.nio.file.*; import java.util.concurrent.*; -import java.util.function.Consumer; @BenchmarkMode(Mode.AverageTime) @OutputTimeUnit(TimeUnit.MILLISECONDS) @@ -34,7 +31,7 @@ public class JSQLParserBenchmark { SqlParserRunner runner; // @Param({ "latest", "5.2", "5.1", "5.0", "4.9", "4.8", "4.7", "4.6", "4.5" }) - @Param({"latest", "5.3", "5.1"}) + @Param({"latest", "5.4"}) public String version; @Setup(Level.Trial) @@ -44,13 +41,33 @@ public void setup() throws Exception { } else { Path jarPath = downloadJsqlparserJar(version); URLClassLoader loader = new URLClassLoader(new URL[] {jarPath.toUri().toURL()}, null); - runner = new DynamicParserRunner(loader); + try { + runner = new DynamicParserRunner(loader); + } catch (Exception ex) { + loader.close(); + throw ex; + } } - // Adjust path as necessary based on where source root is during test execution - Path path = Paths.get("src/test/resources/net/sf/jsqlparser/performance.sql"); - sqlContent = Files.readString(path, StandardCharsets.UTF_8); - executorService = Executors.newSingleThreadExecutor(); + // Reject incompatible SQL/configurations before collecting measurements. + try { + Path path = Paths.get("src/test/resources/net/sf/jsqlparser/performance.sql"); + sqlContent = Files.readString(path, StandardCharsets.UTF_8); + executorService = Executors.newSingleThreadExecutor(); + Object statements = runner.parseStatements(sqlContent, executorService, + SqlParserRunner.Configuration.SIMPLE); + if (statements == null) { + throw new IllegalStateException("Parser " + version + + " returned no statements for the benchmark corpus with SIMPLE configuration"); + } + } catch (Exception ex) { + try { + tearDown(); + } catch (Exception cleanup) { + ex.addSuppressed(cleanup); + } + throw ex; + } } private Path downloadJsqlparserJar(String version) throws IOException { @@ -74,10 +91,10 @@ private Path downloadJsqlparserJar(String version) throws IOException { @Benchmark public void parseSQLStatements(Blackhole blackhole) throws Exception { - final Statements statements = runner.parseStatements( + final Object statements = runner.parseStatements( sqlContent, executorService, - (Consumer) parser -> parser.withAllowComplexParsing(false)); + SqlParserRunner.Configuration.SIMPLE); blackhole.consume(statements); } @@ -87,15 +104,23 @@ public void parseQuotedText(Blackhole blackhole) throws Exception { + "INSERT INTO recycle_record (a,f) VALUES ('\\'anything', 'abc');\n" + "INSERT INTO recycle_record (a,f) VALUES ('\\'','83653692186728700711687663398101');\n"; - final Statements statements = runner.parseStatements( + final Object statements = runner.parseStatements( sqlStr, executorService, - (Consumer) parser -> parser.withBackslashEscapeCharacter(true)); + SqlParserRunner.Configuration.BACKSLASH_ESCAPES); blackhole.consume(statements); } @TearDown(Level.Trial) - public void tearDown() { - executorService.shutdown(); + public void tearDown() throws Exception { + try { + if (executorService != null) { + executorService.shutdownNow(); + } + } finally { + if (runner != null) { + runner.close(); + } + } } } diff --git a/src/test/java/net/sf/jsqlparser/benchmark/LatestClasspathRunner.java b/src/test/java/net/sf/jsqlparser/benchmark/LatestClasspathRunner.java index 5f70cf878f..5083716f3c 100644 --- a/src/test/java/net/sf/jsqlparser/benchmark/LatestClasspathRunner.java +++ b/src/test/java/net/sf/jsqlparser/benchmark/LatestClasspathRunner.java @@ -9,20 +9,17 @@ */ package net.sf.jsqlparser.benchmark; -import net.sf.jsqlparser.parser.CCJSqlParser; -import net.sf.jsqlparser.statement.Statements; import java.util.concurrent.ExecutorService; -import java.util.function.Consumer; public class LatestClasspathRunner implements SqlParserRunner { @Override - public Statements parseStatements(String sql, + public Object parseStatements(String sql, ExecutorService executorService, - Consumer consumer) throws Exception { + Configuration configuration) throws Exception { return net.sf.jsqlparser.parser.CCJSqlParserUtil.parseStatements(sql, executorService, - consumer); + configuration == null ? null : configuration.currentParserConfiguration); } } diff --git a/src/test/java/net/sf/jsqlparser/benchmark/ParserRunnerTest.java b/src/test/java/net/sf/jsqlparser/benchmark/ParserRunnerTest.java new file mode 100644 index 0000000000..f0b04f2015 --- /dev/null +++ b/src/test/java/net/sf/jsqlparser/benchmark/ParserRunnerTest.java @@ -0,0 +1,47 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2025 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.benchmark; + +import static org.junit.jupiter.api.Assertions.*; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.concurrent.Executors; +import net.sf.jsqlparser.parser.CCJSqlParserUtil; +import org.junit.jupiter.api.Test; +import net.sf.jsqlparser.benchmark.SqlParserRunner.Configuration; + +class ParserRunnerTest { + @Test + void isolatedParserReturnsItsOwnAstAndAppliesTheSameOptions() throws Exception { + URL classes = CCJSqlParserUtil.class.getProtectionDomain().getCodeSource().getLocation(); + var executor = Executors.newSingleThreadExecutor(); + try (var current = new LatestClasspathRunner(); + var isolated = + new DynamicParserRunner(new URLClassLoader(new URL[] {classes}, null))) { + String sql = "SELECT 'it\\'s' AS value"; + Object expected = + current.parseStatements(sql, executor, Configuration.BACKSLASH_ESCAPES); + Object actual = + isolated.parseStatements(sql, executor, Configuration.BACKSLASH_ESCAPES); + assertEquals(expected.toString(), actual.toString()); + assertNotSame(expected.getClass(), actual.getClass()); + assertEquals(expected.getClass().getName(), actual.getClass().getName()); + assertEquals( + current.parseStatements("SELECT 1", executor, Configuration.SIMPLE).toString(), + isolated.parseStatements("SELECT 1", executor, Configuration.SIMPLE) + .toString()); + assertThrows(Exception.class, + () -> isolated.parseStatements("SELECT FROM", executor, Configuration.SIMPLE)); + assertFalse(executor.isShutdown()); + } finally { + executor.shutdownNow(); + } + } +} diff --git a/src/test/java/net/sf/jsqlparser/benchmark/SqlParserRunner.java b/src/test/java/net/sf/jsqlparser/benchmark/SqlParserRunner.java index 00496ad685..1481d040c9 100644 --- a/src/test/java/net/sf/jsqlparser/benchmark/SqlParserRunner.java +++ b/src/test/java/net/sf/jsqlparser/benchmark/SqlParserRunner.java @@ -9,13 +9,31 @@ */ package net.sf.jsqlparser.benchmark; -import net.sf.jsqlparser.parser.CCJSqlParser; -import net.sf.jsqlparser.statement.Statements; - import java.util.concurrent.ExecutorService; import java.util.function.Consumer; +import net.sf.jsqlparser.parser.CCJSqlParser; + +public interface SqlParserRunner extends AutoCloseable { + enum Configuration { + SIMPLE("withAllowComplexParsing", false, + parser -> parser.withAllowComplexParsing(false)), BACKSLASH_ESCAPES( + "withBackslashEscapeCharacter", true, + parser -> parser.withBackslashEscapeCharacter(true)); + + final String methodName; + final boolean value; + final Consumer currentParserConfiguration; + + Configuration(String methodName, boolean value, Consumer configuration) { + this.methodName = methodName; + this.value = value; + this.currentParserConfiguration = configuration; + } + } + + Object parseStatements(String sql, ExecutorService executorService, + Configuration configuration) throws Exception; -public interface SqlParserRunner { - Statements parseStatements(String sql, ExecutorService executorService, - Consumer consumer) throws Exception; + @Override + default void close() throws Exception {} } diff --git a/src/test/java/net/sf/jsqlparser/expression/ArrayExpressionTest.java b/src/test/java/net/sf/jsqlparser/expression/ArrayExpressionTest.java index dd96441004..4e114c90f0 100644 --- a/src/test/java/net/sf/jsqlparser/expression/ArrayExpressionTest.java +++ b/src/test/java/net/sf/jsqlparser/expression/ArrayExpressionTest.java @@ -30,4 +30,116 @@ void testColumnArrayExpression() throws JSQLParserException { assertInstanceOf(ArrayConstructor.class, column.getArrayConstructor()); } + @Test + void testNavigationAfterSubscript() throws JSQLParserException { + // Redshift SUPER navigation: subscript then field access + String sqlStr = "SELECT recommendations[0].language_id FROM recs"; + PlainSelect select = (PlainSelect) TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true); + SelectItem selectItem = select.getSelectItem(0); + + RowGetExpression rowGet = selectItem.getExpression(RowGetExpression.class); + assertNotNull(rowGet); + assertEquals("language_id", rowGet.getColumnName()); + Column column = assertInstanceOf(Column.class, rowGet.getExpression()); + assertEquals("recommendations", column.getColumnName()); + assertInstanceOf(ArrayConstructor.class, column.getArrayConstructor()); + } + + @Test + void testAlternatingNavigationChain() throws JSQLParserException { + // field accesses and subscripts may alternate freely after the primary expression + String sqlStr = "SELECT a.b[0].c[1].d FROM t"; + PlainSelect select = (PlainSelect) TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true); + SelectItem selectItem = select.getSelectItem(0); + + RowGetExpression outer = selectItem.getExpression(RowGetExpression.class); + assertNotNull(outer); + assertEquals("d", outer.getColumnName()); + ArrayExpression subscript = assertInstanceOf(ArrayExpression.class, outer.getExpression()); + assertInstanceOf(LongValue.class, subscript.getIndexExpression()); + RowGetExpression inner = + assertInstanceOf(RowGetExpression.class, subscript.getObjExpression()); + assertEquals("c", inner.getColumnName()); + Column column = assertInstanceOf(Column.class, inner.getExpression()); + assertEquals("a", column.getTable().getName()); + assertEquals("b", column.getColumnName()); + assertInstanceOf(ArrayConstructor.class, column.getArrayConstructor()); + } + + @Test + void testNavigationFieldNames() throws JSQLParserException { + // dotted continuations accept FROM/SELECT/CURRENT and quoted names, + // same as ColumnIdentifier's dotted continuation (RelObjectNameExt) + PlainSelect select = (PlainSelect) TestUtils.assertSqlCanBeParsedAndDeparsed( + "SELECT a[0].from FROM t", true); + RowGetExpression rowGet = select.getSelectItem(0).getExpression(RowGetExpression.class); + assertEquals("from", rowGet.getColumnName()); + + select = (PlainSelect) TestUtils.assertSqlCanBeParsedAndDeparsed( + "SELECT a[0].\"quoted field\" FROM t", true); + rowGet = select.getSelectItem(0).getExpression(RowGetExpression.class); + assertEquals("\"quoted field\"", rowGet.getColumnName()); + } + + @Test + void testNavigationInClauses() throws JSQLParserException { + TestUtils.assertSqlCanBeParsedAndDeparsed( + "SELECT * FROM t WHERE a[0].b = 1", true); + TestUtils.assertSqlCanBeParsedAndDeparsed( + "SELECT * FROM t ORDER BY a[0].b", true); + } + + @Test + void testNavigationThenCastAndJson() throws JSQLParserException { + PlainSelect select = (PlainSelect) TestUtils.assertSqlCanBeParsedAndDeparsed( + "SELECT a[0].b::text FROM t", true); + CastExpression cast = select.getSelectItem(0).getExpression(CastExpression.class); + assertInstanceOf(RowGetExpression.class, cast.getLeftExpression()); + + TestUtils.assertSqlCanBeParsedAndDeparsed( + "SELECT a[0].b -> 'x' FROM t", true); + TestUtils.assertSqlCanBeParsedAndDeparsed( + "SELECT a.b[1 : 2].c FROM t", true); + TestUtils.assertSqlCanBeParsedAndDeparsed( + "SELECT a[0].b[1][2].c FROM t", true); + } + + @Test + void testNavigationDoesNotSwallowQualifiedColumns() throws JSQLParserException { + // the navigation loop must not change how plain qualified columns parse + PlainSelect select = (PlainSelect) TestUtils.assertSqlCanBeParsedAndDeparsed( + "SELECT a.b FROM t", true); + Column column = select.getSelectItem(0).getExpression(Column.class); + assertEquals("a", column.getTable().getName()); + assertEquals("b", column.getColumnName()); + assertNull(column.getArrayConstructor()); + + select = (PlainSelect) TestUtils.assertSqlCanBeParsedAndDeparsed( + "SELECT a.b.c FROM t", true); + column = select.getSelectItem(0).getExpression(Column.class); + assertEquals("a.b.c", column.toString()); + + select = (PlainSelect) TestUtils.assertSqlCanBeParsedAndDeparsed( + "SELECT a.b[0] FROM t", true); + column = select.getSelectItem(0).getExpression(Column.class); + assertEquals("a", column.getTable().getName()); + assertEquals("b", column.getColumnName()); + assertInstanceOf(ArrayConstructor.class, column.getArrayConstructor()); + + // a bracket group after a subscript stays on the ArrayExpression slot, + // not on the navigation loop + select = (PlainSelect) TestUtils.assertSqlCanBeParsedAndDeparsed( + "SELECT a[0]['k'] FROM t", true); + ArrayExpression array = select.getSelectItem(0).getExpression(ArrayExpression.class); + assertNotNull(array); + column = assertInstanceOf(Column.class, array.getObjExpression()); + assertInstanceOf(ArrayConstructor.class, column.getArrayConstructor()); + } + + @Test + void testMalformedNavigationRejected() { + assertThrows(JSQLParserException.class, + () -> TestUtils.assertSqlCanBeParsedAndDeparsed("SELECT a[0].* FROM t", true)); + } + } diff --git a/src/test/java/net/sf/jsqlparser/expression/CastExpressionTest.java b/src/test/java/net/sf/jsqlparser/expression/CastExpressionTest.java index a14d5c9a00..6e0ff80a96 100644 --- a/src/test/java/net/sf/jsqlparser/expression/CastExpressionTest.java +++ b/src/test/java/net/sf/jsqlparser/expression/CastExpressionTest.java @@ -13,6 +13,7 @@ import net.sf.jsqlparser.JSQLParserException; import net.sf.jsqlparser.expression.operators.relational.ParenthesedExpressionList; +import net.sf.jsqlparser.parser.CCJSqlParserUtil; import net.sf.jsqlparser.statement.select.PlainSelect; import net.sf.jsqlparser.test.TestUtils; import org.junit.jupiter.api.Assertions; @@ -80,6 +81,21 @@ void testImplicitCastDoublePrecisionIssue1344() throws JSQLParserException { Assertions.assertTrue(select.getSelectItem(0).getExpression() instanceof CastExpression); } + @Test + void testCastCharWithCharacterSet() throws JSQLParserException { + String sqlStr = "SELECT CAST('abc' AS CHAR CHARACTER SET utf8mb4)"; + PlainSelect select = (PlainSelect) assertSqlCanBeParsedAndDeparsed(sqlStr, true); + CastExpression castExpression = Assertions.assertInstanceOf(CastExpression.class, + select.getSelectItem(0).getExpression()); + + Assertions.assertEquals("CHAR", castExpression.getColDataType().getDataType()); + Assertions.assertEquals("utf8mb4", castExpression.getColDataType().getCharacterSet()); + + assertSqlCanBeParsedAndDeparsed( + "SELECT CAST('abc' AS CHAR(10) CHARACTER SET utf8mb4)", true); + assertSqlCanBeParsedAndDeparsed("SELECT CAST('abc' AS CHARACTER VARYING)", true); + } + @Test public void testCastToSigned() throws JSQLParserException { @@ -152,4 +168,18 @@ void testNestedCompositeTypeCastIssue2341() throws JSQLParserException { Assertions.assertEquals(1, parenthesedCast.size()); Assertions.assertInstanceOf(CastExpression.class, parenthesedCast.get(0)); } + + @Test + public void testCastToArrayIssue2490() throws JSQLParserException { + // MySQL casts to an array of the given type when a multi-valued index key part is + // defined, and accepts the same cast anywhere else an expression is allowed. + assertSqlCanBeParsedAndDeparsed("SELECT CAST(data -> '$.zips' AS UNSIGNED ARRAY) FROM t"); + assertSqlCanBeParsedAndDeparsed("SELECT CAST(x AS CHAR (10) ARRAY) FROM t"); + + PlainSelect select = (PlainSelect) CCJSqlParserUtil + .parse("SELECT CAST(data -> '$.zips' AS UNSIGNED ARRAY) FROM t"); + CastExpression cast = Assertions.assertInstanceOf(CastExpression.class, + select.getSelectItem(0).getExpression()); + Assertions.assertEquals("UNSIGNED ARRAY", cast.getColDataType().getDataType()); + } } diff --git a/src/test/java/net/sf/jsqlparser/expression/ColumnsExpressionTest.java b/src/test/java/net/sf/jsqlparser/expression/ColumnsExpressionTest.java new file mode 100644 index 0000000000..bdfa302ef6 --- /dev/null +++ b/src/test/java/net/sf/jsqlparser/expression/ColumnsExpressionTest.java @@ -0,0 +1,201 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.expression; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertSame; +import static net.sf.jsqlparser.test.TestUtils.assertSqlCanBeParsedAndDeparsed; +import static org.mockito.Mockito.CALLS_REAL_METHODS; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Set; +import net.sf.jsqlparser.JSQLParserException; +import net.sf.jsqlparser.expression.operators.arithmetic.Addition; +import net.sf.jsqlparser.parser.CCJSqlParserUtil; +import net.sf.jsqlparser.schema.Column; +import net.sf.jsqlparser.statement.select.PlainSelect; +import net.sf.jsqlparser.util.TablesNamesFinder; +import net.sf.jsqlparser.util.deparser.ExpressionDeParser; +import net.sf.jsqlparser.util.deparser.SelectDeParser; +import net.sf.jsqlparser.util.deparser.StatementDeParser; +import net.sf.jsqlparser.util.validation.ValidationContext; +import net.sf.jsqlparser.util.validation.validator.ExpressionValidator; +import org.junit.jupiter.api.Test; + +class ColumnsExpressionTest { + + private static final String SQL = "SELECT COLUMNS('m') APPLY(x -> x + 1)" + + " EXCEPT (skipped) REPLACE(value + 2 AS value) FROM metrics"; + + @Test + void testDeparserVisitsTransformerChildren() throws JSQLParserException { + PlainSelect select = (PlainSelect) CCJSqlParserUtil.parse(SQL); + StringBuilder buffer = new StringBuilder(); + List columns = new ArrayList<>(); + ExpressionDeParser expressions = new ExpressionDeParser() { + @Override + public StringBuilder visit(StringValue value, S context) { + return getBuilder().append("'renamed'"); + } + + @Override + public StringBuilder visit(LongValue value, S context) { + return getBuilder().append('?'); + } + + @Override + public StringBuilder visit(Column column, S context) { + columns.add(column.getColumnName()); + return super.visit(column, context); + } + }; + select.accept(new StatementDeParser(expressions, new SelectDeParser(), buffer), null); + + assertEquals("SELECT COLUMNS('renamed') APPLY(x -> x + ?)" + + " EXCEPT (skipped) REPLACE(value + ? AS value) FROM metrics", buffer.toString()); + assertEquals(List.of("x", "skipped", "value"), columns); + assertEquals(SQL, select.toString()); + assertSqlCanBeParsedAndDeparsed(SQL, true); + } + + @Test + void testAdapterVisitsEveryTransformerInOrder() throws JSQLParserException { + PlainSelect select = (PlainSelect) CCJSqlParserUtil.parse(SQL); + ColumnsExpression expression = assertInstanceOf(ColumnsExpression.class, + select.getSelectItems().get(0).getExpression()); + List visited = new ArrayList<>(); + Object marker = new Object(); + expression.accept(new ExpressionVisitorAdapter() { + @Override + public Void visit(StringValue value, S context) { + assertSame(marker, context); + visited.add(value.getValue()); + return null; + } + + @Override + public Void visit(Column column, S context) { + assertSame(marker, context); + visited.add(column.getColumnName()); + return null; + } + }, marker); + assertEquals(List.of("m", "x", "skipped", "value"), visited); + } + + @Test + void testAdapterPreservesCollectedResult() throws JSQLParserException { + PlainSelect select = (PlainSelect) CCJSqlParserUtil.parse( + "SELECT COLUMNS('m') EXCEPT (skipped) FROM metrics"); + Expression expression = select.getSelectItems().get(0).getExpression(); + ExpressionVisitorAdapter> collector = new ExpressionVisitorAdapter<>() { + @Override + protected List applyExpression(Expression value, S context) { + return List.of(value.toString()); + } + + @Override + protected List visitExpressions(Expression value, S context, + Collection children) { + List result = new ArrayList<>(); + for (Expression child : children) { + if (child != null) { + result.addAll(child.accept(this, context)); + } + } + return result; + } + }; + assertEquals(List.of("'m'", "skipped"), expression.accept(collector, null)); + } + + @Test + void testVisitorDefaultDispatchesChildren() throws JSQLParserException { + PlainSelect select = (PlainSelect) CCJSqlParserUtil.parse(SQL); + ColumnsExpression expression = assertInstanceOf(ColumnsExpression.class, + select.getSelectItems().get(0).getExpression()); + ExpressionVisitor visitor = mock(ExpressionVisitor.class, CALLS_REAL_METHODS); + Object context = new Object(); + expression.accept(visitor, context); + + verify(visitor).visit((Function) expression.getColumns(), context); + verify(visitor).visit((LambdaExpression) expression.getTransformers().get(0) + .getApplyExpression(), context); + verify(visitor).visit(expression.getTransformers().get(1).getExceptColumns().get(0), + context); + verify(visitor).visit( + (Addition) expression + .getTransformers().get(2).getReplaceItems().get(0).getExpression(), + context); + } + + @Test + void testValidatorVisitsExceptColumns() throws JSQLParserException { + PlainSelect select = (PlainSelect) CCJSqlParserUtil.parse(SQL); + ColumnsExpression expression = assertInstanceOf(ColumnsExpression.class, + select.getSelectItems().get(0).getExpression()); + List visited = new ArrayList<>(); + ExpressionValidator validator = new ExpressionValidator() { + @Override + public Void visit(Column column, S context) { + visited.add(column.getColumnName()); + return null; + } + }; + validator.setContext(new ValidationContext().setCapabilities(Collections.emptyList())); + expression.accept(validator, null); + assertEquals(List.of("x", "skipped", "value"), visited); + } + + @Test + void testFinderVisitsExceptColumns() throws JSQLParserException { + List visited = new ArrayList<>(); + TablesNamesFinder finder = new TablesNamesFinder<>() { + @Override + public Void visit(Column column, S context) { + visited.add(column.getColumnName()); + return super.visit(column, context); + } + }; + assertEquals(Set.of("metrics"), finder.getTables(CCJSqlParserUtil.parse(SQL))); + assertEquals(List.of("x", "skipped", "value"), visited); + } + + @Test + void testAstTokenRangeIncludesMatcher() throws JSQLParserException { + PlainSelect select = (PlainSelect) CCJSqlParserUtil.parse( + "SELECT COLUMNS('m') APPLY(sum) AS result FROM metrics"); + ColumnsExpression expression = assertInstanceOf(ColumnsExpression.class, + select.getSelectItem(0).getExpression()); + assertEquals("COLUMNS", expression.getASTNode().jjtGetFirstToken().image); + assertEquals(")", expression.getASTNode().jjtGetLastToken().image); + assertEquals("AS", expression.getASTNode().jjtGetLastToken().next.image); + assertEquals("COLUMNS", expression.getColumns().getASTNode().jjtGetFirstToken().image); + assertEquals("APPLY", expression.getColumns().getASTNode().jjtGetLastToken().next.image); + assertSame(expression, ((Function) expression.getColumns()).getParent()); + assertSame(expression, ((Function) expression.getColumns()) + .getParent(ColumnsExpression.class)); + } + + @Test + void testFinderVisitsTransformerSubqueries() throws JSQLParserException { + String sql = "SELECT COLUMNS('m') APPLY(x -> (SELECT max(v) FROM applied))" + + " REPLACE((SELECT v FROM replacement) AS c) FROM metrics"; + assertEquals(Set.of("metrics", "applied", "replacement"), + TablesNamesFinder.findTables(sql)); + assertSqlCanBeParsedAndDeparsed(sql, true); + } +} diff --git a/src/test/java/net/sf/jsqlparser/expression/ExpressionPrecedenceTest.java b/src/test/java/net/sf/jsqlparser/expression/ExpressionPrecedenceTest.java index a601f6e0ac..a0a4973718 100644 --- a/src/test/java/net/sf/jsqlparser/expression/ExpressionPrecedenceTest.java +++ b/src/test/java/net/sf/jsqlparser/expression/ExpressionPrecedenceTest.java @@ -10,9 +10,20 @@ package net.sf.jsqlparser.expression; import net.sf.jsqlparser.JSQLParserException; +import net.sf.jsqlparser.expression.operators.arithmetic.Addition; import net.sf.jsqlparser.expression.operators.arithmetic.BitwiseAnd; import net.sf.jsqlparser.expression.operators.arithmetic.Concat; +import net.sf.jsqlparser.expression.operators.conditional.AndExpression; +import net.sf.jsqlparser.expression.operators.relational.Between; +import net.sf.jsqlparser.expression.operators.relational.EqualsTo; +import net.sf.jsqlparser.expression.operators.relational.ExistsExpression; +import net.sf.jsqlparser.expression.operators.relational.InExpression; +import net.sf.jsqlparser.expression.operators.relational.IsNullExpression; +import net.sf.jsqlparser.expression.operators.relational.LikeExpression; +import net.sf.jsqlparser.expression.operators.relational.ParenthesedExpressionList; import net.sf.jsqlparser.parser.CCJSqlParserUtil; +import net.sf.jsqlparser.schema.Column; +import net.sf.jsqlparser.test.TestUtils; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; @@ -30,4 +41,180 @@ public void testGetSign() throws JSQLParserException { Assertions.assertInstanceOf(BitwiseAnd.class, ((Concat) expr).getLeftExpression()); Assertions.assertInstanceOf(LongValue.class, ((Concat) expr).getRightExpression()); } + + @Test + public void testNotNotConditionIssue1170() throws JSQLParserException { + Expression expr = CCJSqlParserUtil.parseCondExpression("not not 1 = 1"); + NotExpression outer = Assertions.assertInstanceOf(NotExpression.class, expr); + NotExpression inner = Assertions.assertInstanceOf(NotExpression.class, + outer.getExpression()); + Assertions.assertInstanceOf(EqualsTo.class, inner.getExpression()); + Assertions.assertEquals("NOT NOT 1 = 1", expr.toString()); + } + + @Test + public void testNotNotNotConditionIssue1170() throws JSQLParserException { + Expression expr = CCJSqlParserUtil.parseCondExpression("not not not 1 = 1"); + NotExpression first = Assertions.assertInstanceOf(NotExpression.class, expr); + NotExpression second = Assertions.assertInstanceOf(NotExpression.class, + first.getExpression()); + NotExpression third = Assertions.assertInstanceOf(NotExpression.class, + second.getExpression()); + Assertions.assertInstanceOf(EqualsTo.class, third.getExpression()); + Assertions.assertEquals("NOT NOT NOT 1 = 1", expr.toString()); + } + + @Test + public void testNotNotExistsIssue1170() throws JSQLParserException { + Expression expr = + CCJSqlParserUtil.parseCondExpression("not not exists (select 1 from mytable)"); + NotExpression outer = Assertions.assertInstanceOf(NotExpression.class, expr); + NotExpression inner = Assertions.assertInstanceOf(NotExpression.class, + outer.getExpression()); + Assertions.assertInstanceOf(ExistsExpression.class, inner.getExpression()); + } + + @Test + public void testNotNotInIssue1170() throws JSQLParserException { + Expression expr = CCJSqlParserUtil.parseCondExpression("not not a in (1, 2)"); + NotExpression outer = Assertions.assertInstanceOf(NotExpression.class, expr); + NotExpression inner = Assertions.assertInstanceOf(NotExpression.class, + outer.getExpression()); + Assertions.assertInstanceOf(InExpression.class, inner.getExpression()); + } + + @Test + public void testNotNotNotBetweenIssue1170() throws JSQLParserException { + Expression expr = CCJSqlParserUtil.parseCondExpression("not not not a between 1 and 2"); + NotExpression first = Assertions.assertInstanceOf(NotExpression.class, expr); + NotExpression second = Assertions.assertInstanceOf(NotExpression.class, + first.getExpression()); + NotExpression third = Assertions.assertInstanceOf(NotExpression.class, + second.getExpression()); + Assertions.assertInstanceOf(Between.class, third.getExpression()); + } + + @Test + public void testNotNotNotWhereClauseIssue1170() throws JSQLParserException { + TestUtils.assertSqlCanBeParsedAndDeparsed("SELECT * FROM mytable WHERE NOT NOT NOT a = 1"); + } + + @Test + public void testMixedNotAndExclamationMarkIssue1170() throws JSQLParserException { + Expression expr = CCJSqlParserUtil.parseCondExpression("not !1 = 1"); + NotExpression outer = Assertions.assertInstanceOf(NotExpression.class, expr); + Assertions.assertFalse(outer.isExclamationMark()); + NotExpression inner = Assertions.assertInstanceOf(NotExpression.class, + outer.getExpression()); + Assertions.assertTrue(inner.isExclamationMark()); + Assertions.assertInstanceOf(EqualsTo.class, inner.getExpression()); + Assertions.assertEquals("NOT ! 1 = 1", expr.toString()); + } + + @Test + public void testSingleNotConditionStaysFlat() throws JSQLParserException { + Expression expr = CCJSqlParserUtil.parseCondExpression("not 1 = 1"); + NotExpression not = Assertions.assertInstanceOf(NotExpression.class, expr); + Assertions.assertInstanceOf(EqualsTo.class, not.getExpression()); + } + + @Test + public void testNotNotLikeIssue1170() throws JSQLParserException { + Expression expr = CCJSqlParserUtil.parseCondExpression("not not a like 'x'"); + NotExpression outer = Assertions.assertInstanceOf(NotExpression.class, expr); + NotExpression inner = Assertions.assertInstanceOf(NotExpression.class, + outer.getExpression()); + LikeExpression like = Assertions.assertInstanceOf(LikeExpression.class, + inner.getExpression()); + Assertions.assertFalse(like.isNot()); + Assertions.assertInstanceOf(Column.class, like.getLeftExpression()); + } + + @Test + public void testNotNotIsNullIssue1170() throws JSQLParserException { + Expression expr = CCJSqlParserUtil.parseCondExpression("not not a is null"); + NotExpression outer = Assertions.assertInstanceOf(NotExpression.class, expr); + NotExpression inner = Assertions.assertInstanceOf(NotExpression.class, + outer.getExpression()); + IsNullExpression isNull = Assertions.assertInstanceOf(IsNullExpression.class, + inner.getExpression()); + Assertions.assertFalse(isNull.isNot()); + Assertions.assertInstanceOf(Column.class, isNull.getLeftExpression()); + } + + @Test + public void testNotNotSimilarToIssue1170() throws JSQLParserException { + Expression expr = CCJSqlParserUtil.parseCondExpression("not not a similar to 'x'"); + NotExpression outer = Assertions.assertInstanceOf(NotExpression.class, expr); + NotExpression inner = Assertions.assertInstanceOf(NotExpression.class, + outer.getExpression()); + LikeExpression similar = Assertions.assertInstanceOf(LikeExpression.class, + inner.getExpression()); + Assertions.assertFalse(similar.isNot()); + Assertions.assertInstanceOf(Column.class, similar.getLeftExpression()); + } + + @Test + public void testNotNotInsideAndChainIssue1170() throws JSQLParserException { + Expression expr = CCJSqlParserUtil.parseCondExpression("a and not not b = 1"); + AndExpression and = Assertions.assertInstanceOf(AndExpression.class, expr); + Assertions.assertInstanceOf(Column.class, and.getLeftExpression()); + NotExpression outer = Assertions.assertInstanceOf(NotExpression.class, + and.getRightExpression()); + NotExpression inner = Assertions.assertInstanceOf(NotExpression.class, + outer.getExpression()); + EqualsTo equalsTo = Assertions.assertInstanceOf(EqualsTo.class, inner.getExpression()); + Assertions.assertInstanceOf(Column.class, equalsTo.getLeftExpression()); + } + + @Test + public void testNotNotCaseInsensitiveIssue1170() throws JSQLParserException { + Expression expr = CCJSqlParserUtil.parseCondExpression("NoT nOt 1 = 1"); + NotExpression outer = Assertions.assertInstanceOf(NotExpression.class, expr); + NotExpression inner = Assertions.assertInstanceOf(NotExpression.class, + outer.getExpression()); + Assertions.assertInstanceOf(EqualsTo.class, inner.getExpression()); + } + + @Test + public void testEightNotIssue1170() throws JSQLParserException { + Expression expr = CCJSqlParserUtil + .parseCondExpression("not not not not not not not not 1 = 1"); + int depth = 0; + while (expr instanceof NotExpression) { + expr = ((NotExpression) expr).getExpression(); + depth++; + } + Assertions.assertEquals(8, depth); + Assertions.assertInstanceOf(EqualsTo.class, expr); + } + + @Test + public void testNotNotParenthesizedConditionIssue1170() throws JSQLParserException { + Expression expr = CCJSqlParserUtil.parseCondExpression("not not (a = 1 and b = 2)"); + NotExpression outer = Assertions.assertInstanceOf(NotExpression.class, expr); + NotExpression inner = Assertions.assertInstanceOf(NotExpression.class, + outer.getExpression()); + Assertions.assertInstanceOf(ParenthesedExpressionList.class, inner.getExpression()); + } + + @Test + public void testNotNotInStatementPositionsIssue1170() throws JSQLParserException { + TestUtils.assertSqlCanBeParsedAndDeparsed( + "SELECT * FROM t WHERE a IN (SELECT id FROM u WHERE NOT NOT x = 1)"); + TestUtils.assertSqlCanBeParsedAndDeparsed("UPDATE t SET a = 1 WHERE NOT NOT b = 2"); + TestUtils.assertSqlCanBeParsedAndDeparsed("DELETE FROM t WHERE NOT NOT b = 2"); + } + + @Test + public void testValuePositionNotStaysOnOperand() throws JSQLParserException { + Expression equalsTo = CCJSqlParserUtil.parseCondExpression("a = not 1"); + EqualsTo eq = Assertions.assertInstanceOf(EqualsTo.class, equalsTo); + Assertions.assertInstanceOf(Column.class, eq.getLeftExpression()); + Assertions.assertInstanceOf(NotExpression.class, eq.getRightExpression()); + + Expression addition = CCJSqlParserUtil.parseCondExpression("1 + not 2"); + Addition add = Assertions.assertInstanceOf(Addition.class, addition); + Assertions.assertInstanceOf(NotExpression.class, add.getRightExpression()); + } } diff --git a/src/test/java/net/sf/jsqlparser/expression/FunctionFeatureAnalysisTest.java b/src/test/java/net/sf/jsqlparser/expression/FunctionFeatureAnalysisTest.java new file mode 100644 index 0000000000..f14bf4b17d --- /dev/null +++ b/src/test/java/net/sf/jsqlparser/expression/FunctionFeatureAnalysisTest.java @@ -0,0 +1,142 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.expression; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Set; +import java.util.stream.Stream; +import net.sf.jsqlparser.JSQLParserException; +import net.sf.jsqlparser.parser.CCJSqlParserUtil; +import net.sf.jsqlparser.statement.Statement; +import net.sf.jsqlparser.statement.StatementFeatures; +import net.sf.jsqlparser.statement.StmtFeature; +import net.sf.jsqlparser.statement.select.Limit; +import net.sf.jsqlparser.statement.select.PlainSelect; +import net.sf.jsqlparser.statement.select.SelectItem; +import net.sf.jsqlparser.statement.select.SelectVisitorAdapter; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.junit.jupiter.params.provider.ValueSource; + +class FunctionFeatureAnalysisTest { + static Stream functionForms() { + return Stream.of( + Arguments.of("SELECT pg_sleep(1) FROM t", "pg_sleep"), + Arguments.of("SELECT pg_sleep(1) OVER () FROM t", "pg_sleep"), + Arguments.of("SELECT SuM(v) OVER () FROM t", "sum"), + Arguments.of("SELECT analytics.sum(v) OVER () FROM t", "analytics.sum"), + Arguments.of("SELECT CONVERT(v USING utf8) FROM t", "convert"), + Arguments.of("SELECT CONVERT(INT, v) FROM t", "convert"), + Arguments.of("SELECT TRY_CONVERT(INT, v) FROM t", "try_convert")); + } + + @ParameterizedTest + @MethodSource("functionForms") + void appliesTheSamePurityContractToEachFunctionForm(String sql, String name) + throws JSQLParserException { + Statement statement = CCJSqlParserUtil.parse(sql); + String before = statement.toString(); + StatementFeatures unknown = statement.getFeatures(n -> false); + assertThat(unknown.getUnresolvedReferences()).containsExactly(name); + assertThat(unknown.getUncertain()) + .contains(StmtFeature.MODIFIES_DATA, StmtFeature.MODIFIES_SCHEMA); + assertThat(unknown.modifiesData()).isFalse(); + assertThat(unknown.mayModifyData()).isTrue(); + + StatementFeatures pure = statement.getFeatures(name::equals); + assertThat(pure.getUnresolvedReferences()).isEmpty(); + assertThat(pure.mayModifyData()).isFalse(); + assertThat(statement.toString()).isEqualTo(before); + assertThat(CCJSqlParserUtil.parse(before).toString()).isEqualTo(before); + } + + @ParameterizedTest + @ValueSource(strings = { + "SELECT sum(danger(v)) OVER () FROM t", + "SELECT sum(v) OVER (PARTITION BY danger(k)) FROM t", + "SELECT sum(v) OVER (ORDER BY danger(k)) FROM t", + "SELECT sum(v) FILTER (WHERE danger(k) > 0) OVER () FROM t", + "SELECT array_agg(v ORDER BY danger(k)) OVER () FROM t", + "SELECT lag(v, danger(k), 0) OVER () FROM t", + "SELECT lag(v, 1, danger(k)) OVER () FROM t", + "SELECT sum(v) OVER (ORDER BY k ROWS danger(1) PRECEDING) FROM t", + "SELECT sum(v) OVER (ORDER BY k ROWS BETWEEN danger(1) PRECEDING AND CURRENT ROW) FROM t", + "SELECT sum(v) OVER (ORDER BY k ROWS BETWEEN CURRENT ROW AND danger(1) FOLLOWING) FROM t", + "SELECT sum(v) OVER w FROM t WINDOW w AS (PARTITION BY danger(k))", + "SELECT sum(v) OVER w FROM t WINDOW w AS (ORDER BY danger(k))", + "SELECT sum(v) OVER w FROM t WINDOW w AS (ORDER BY k ROWS danger(1) PRECEDING)", + "SELECT CONVERT(INT, danger(v)) FROM t"}) + void pureOuterFunctionsDoNotHideUnprovenChildren(String sql) throws JSQLParserException { + StatementFeatures features = CCJSqlParserUtil.parse(sql) + .getFeatures(Set.of("sum", "array_agg", "lag", "convert")::contains); + assertThat(features.getUnresolvedReferences()).containsExactly("danger"); + assertThat(features.mayModifyData()).isTrue(); + assertThat(features.modifiesData()).isFalse(); + } + + @Test + void inlineWindowVisitsBothOrderListsOnceAndKeepsContext() throws JSQLParserException { + String sql = "SELECT array_agg(arg_fn(v) ORDER BY inner_fn(k)) " + + "FILTER (WHERE filter_fn(v) > 0) OVER (PARTITION BY part_fn(k) " + + "ORDER BY outer_fn(k) ROWS BETWEEN start_fn(1) PRECEDING " + + "AND end_fn(1) FOLLOWING) FROM t"; + PlainSelect select = (PlainSelect) CCJSqlParserUtil.parse(sql); + List seen = new ArrayList<>(); + Object marker = new Object(); + ExpressionVisitorAdapter expressions = new ExpressionVisitorAdapter() { + @Override + public Void visit(Function function, S context) { + assertThat(context).isSameAs(marker); + seen.add(function.getName()); + return super.visit(function, context); + } + + @Override + protected Void visitExpressions(Expression expression, S context, + Collection children) { + assertThat(context).isSameAs(marker); + return super.visitExpressions(expression, context, children); + } + }; + select.accept(new SelectVisitorAdapter<>(expressions), marker); + assertThat(seen).containsExactly("arg_fn", "filter_fn", "inner_fn", "part_fn", + "outer_fn", "start_fn", "end_fn"); + } + + @Test + void functionModifiersRemainVisibleAfterAnalyticConversion() { + Function function = new Function().withName("parent").withParameters(new LongValue(1)); + function.setHavingClause(new Function.HavingClause(Function.HavingClause.HavingType.MAX, + new Function().withName("having_fn"))); + function.setKeywordArguments(List.of(new Function.KeywordArgument("SEPARATOR", + new Function().withName("keyword_fn")))); + function.setLimit(new Limit().withRowCount(new Function().withName("limit_fn"))); + for (Expression expression : List.of(function, new AnalyticExpression(function))) { + PlainSelect select = new PlainSelect(); + select.setSelectItems(List.of(new SelectItem<>(expression))); + assertThat(select.getFeatures("parent"::equals).getUnresolvedReferences()) + .containsExactly("having_fn", "keyword_fn", "limit_fn"); + } + } + + @Test + void emptyAndUnboundedWindowsContainNoSpuriousFunctions() throws JSQLParserException { + assertThat(new WindowDefinition().getAllExpressions()).isEmpty(); + Statement statement = CCJSqlParserUtil.parse("SELECT sum(v) OVER " + + "(ORDER BY k ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) FROM t"); + assertThat(statement.getFeatures("sum"::equals).mayModifyData()).isFalse(); + } +} diff --git a/src/test/java/net/sf/jsqlparser/expression/JsonExpressionTest.java b/src/test/java/net/sf/jsqlparser/expression/JsonExpressionTest.java index 5fc72bea8e..b31efae37c 100644 --- a/src/test/java/net/sf/jsqlparser/expression/JsonExpressionTest.java +++ b/src/test/java/net/sf/jsqlparser/expression/JsonExpressionTest.java @@ -10,6 +10,7 @@ package net.sf.jsqlparser.expression; import net.sf.jsqlparser.JSQLParserException; +import net.sf.jsqlparser.parser.CCJSqlParserUtil; import net.sf.jsqlparser.statement.select.PlainSelect; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; @@ -39,6 +40,21 @@ void testIssue1792() throws JSQLParserException { assertSqlCanBeParsedAndDeparsed(sqlStr, true); } + @Test + void testHashArrowOperatorsRoundTrip() throws JSQLParserException { + // #> and #>> must survive deparse+reparse: without spaces the operator glues + // onto the object (a#>'{b}') and re-parses as the comparison a# > '{b}'. + for (String sqlStr : new String[] {"SELECT a #> '{b}' FROM t", + "SELECT a #>> '{b}' FROM t"}) { + PlainSelect st = (PlainSelect) CCJSqlParserUtil.parse(sqlStr); + Assertions.assertInstanceOf(JsonExpression.class, st.getSelectItem(0).getExpression()); + PlainSelect reparsed = (PlainSelect) CCJSqlParserUtil.parse(st.toString()); + Assertions.assertInstanceOf(JsonExpression.class, + reparsed.getSelectItem(0).getExpression(), + sqlStr + " deparsed to " + st); + } + } + @Test void testSnowflakeGetOperator() throws JSQLParserException { // https://docs.snowflake.com/en/user-guide/querying-semistructured diff --git a/src/test/java/net/sf/jsqlparser/expression/LambdaExpressionTest.java b/src/test/java/net/sf/jsqlparser/expression/LambdaExpressionTest.java index 4dcc4c6e16..bd6a0fcd8a 100644 --- a/src/test/java/net/sf/jsqlparser/expression/LambdaExpressionTest.java +++ b/src/test/java/net/sf/jsqlparser/expression/LambdaExpressionTest.java @@ -10,6 +10,7 @@ package net.sf.jsqlparser.expression; import net.sf.jsqlparser.JSQLParserException; +import net.sf.jsqlparser.statement.select.Select; import net.sf.jsqlparser.test.TestUtils; import org.junit.jupiter.api.Test; @@ -20,7 +21,10 @@ class LambdaExpressionTest { @Test void testLambdaFunctionSingleParameter() throws JSQLParserException { String sqlStr = "select list_transform( split('test', ''), x -> unicode(x) )"; - TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true); + Select select = assertInstanceOf( + Select.class, TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true)); + Function function = select.getPlainSelect().getSelectItem(0).getExpression(Function.class); + assertInstanceOf(LambdaExpression.class, function.getParameters().get(1)); } @Test @@ -52,4 +56,15 @@ void testLambdaFirstArgumentIssue2195() throws JSQLParserException { TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true); } + @Test + void testLambdaFunctionFourParameters() throws JSQLParserException { + String sqlStr = "SELECT list_transform([1], (a, b, c, d) -> a)"; + Select select = assertInstanceOf( + Select.class, TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true)); + Function function = select.getPlainSelect().getSelectItem(0).getExpression(Function.class); + LambdaExpression lambda = + assertInstanceOf(LambdaExpression.class, function.getParameters().get(1)); + assertEquals(4, lambda.getIdentifiers().size()); + } + } diff --git a/src/test/java/net/sf/jsqlparser/expression/LimitExpressionTest.java b/src/test/java/net/sf/jsqlparser/expression/LimitExpressionTest.java index 70dbd21038..3139530c6d 100644 --- a/src/test/java/net/sf/jsqlparser/expression/LimitExpressionTest.java +++ b/src/test/java/net/sf/jsqlparser/expression/LimitExpressionTest.java @@ -11,6 +11,7 @@ import net.sf.jsqlparser.JSQLParserException; import net.sf.jsqlparser.parser.CCJSqlParserUtil; +import net.sf.jsqlparser.statement.select.ParenthesedSelect; import net.sf.jsqlparser.statement.select.PlainSelect; import net.sf.jsqlparser.test.TestUtils; import org.junit.jupiter.api.Assertions; @@ -31,6 +32,38 @@ public void testIssue933() throws JSQLParserException { "SELECT * FROM tmp3 LIMIT (SELECT 2)", true); } + @Test + public void testIssue2359() throws JSQLParserException { + // PostgreSQL allows any expression, including a scalar subquery, as the LIMIT row + // count. A long parenthesized subquery used to fail because the ClickHouse + // "LIMIT ... BY ..." branch was chosen by a numeric LOOKAHEAD that cannot see past + // the subquery. + String sql = "WITH some_table AS (SELECT 1 AS some_column), " + + "another_table AS (SELECT 'some_value' AS condition_column) " + + "SELECT some_column FROM some_table ORDER BY some_column " + + "LIMIT (SELECT COUNT(*) FROM another_table WHERE condition_column = 'some_value')"; + + PlainSelect plainSelect = (PlainSelect) CCJSqlParserUtil.parse(sql); + Assertions.assertTrue( + plainSelect.getLimit().getRowCount() instanceof ParenthesedSelect); + Assertions.assertNull(plainSelect.getLimitBy()); + + TestUtils.assertSqlCanBeParsedAndDeparsed(sql, true); + + // A function wrapping a scalar subquery must work as the row count too. + TestUtils.assertSqlCanBeParsedAndDeparsed( + "SELECT a FROM t LIMIT GREATEST(0, (SELECT COUNT(*) FROM u WHERE c = 'x'))", + true); + } + + @Test + public void testLimitByClickHouseUnchanged() throws JSQLParserException { + // ClickHouse "LIMIT ... BY ..." must keep parsing and round-tripping after the LIMIT + // row-count disambiguation was rewritten (issue #2359). + TestUtils.assertSqlCanBeParsedAndDeparsed("SELECT id FROM t LIMIT 5 BY id", true); + TestUtils.assertSqlCanBeParsedAndDeparsed("SELECT id FROM t LIMIT 2, 5 BY id", true); + } + @Test public void testIssue1373() throws JSQLParserException { TestUtils.assertSqlCanBeParsedAndDeparsed( diff --git a/src/test/java/net/sf/jsqlparser/expression/OracleHintTest.java b/src/test/java/net/sf/jsqlparser/expression/OracleHintTest.java index 2da69721c2..687c29ad57 100644 --- a/src/test/java/net/sf/jsqlparser/expression/OracleHintTest.java +++ b/src/test/java/net/sf/jsqlparser/expression/OracleHintTest.java @@ -9,6 +9,10 @@ */ package net.sf.jsqlparser.expression; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTimeoutPreemptively; + +import java.time.Duration; import net.sf.jsqlparser.JSQLParserException; import net.sf.jsqlparser.test.TestUtils; import org.junit.jupiter.api.Test; @@ -46,4 +50,18 @@ void testMerge() throws JSQLParserException { TestUtils.assertSqlCanBeParsedAndDeparsed(sqlString, true); } + @Test + void testCraftedHintCommentDoesNotBacktrack() { + final StringBuilder sb = new StringBuilder("-- /*+ "); + for (int i = 0; i < 100000; i++) { + sb.append('*'); + } + final String crafted = sb.toString(); + + // a line comment carrying an unterminated /*+ marker (no closing */) used to make the + // block hint pattern backtrack quadratically, and it is not an oracle hint anyway + assertTimeoutPreemptively(Duration.ofSeconds(2), + () -> assertFalse(OracleHint.isHintMatch(crafted))); + } + } diff --git a/src/test/java/net/sf/jsqlparser/expression/TaggedDollarStringTest.java b/src/test/java/net/sf/jsqlparser/expression/TaggedDollarStringTest.java new file mode 100644 index 0000000000..4dd3fa9398 --- /dev/null +++ b/src/test/java/net/sf/jsqlparser/expression/TaggedDollarStringTest.java @@ -0,0 +1,190 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2019 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.expression; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertThrows; +import java.io.StringReader; +import java.nio.charset.StandardCharsets; +import java.util.List; +import java.util.function.Consumer; +import java.util.stream.Stream; +import net.sf.jsqlparser.JSQLParserException; +import net.sf.jsqlparser.parser.AbstractJSqlParser.Dialect; +import net.sf.jsqlparser.parser.CCJSqlParser; +import net.sf.jsqlparser.parser.CCJSqlParserConstants; +import net.sf.jsqlparser.parser.CCJSqlParserUtil; +import net.sf.jsqlparser.parser.StreamProvider; +import net.sf.jsqlparser.parser.Token; +import net.sf.jsqlparser.schema.Column; +import net.sf.jsqlparser.statement.Statements; +import net.sf.jsqlparser.statement.select.PlainSelect; +import net.sf.jsqlparser.test.TestUtils; +import net.sf.jsqlparser.util.deparser.StatementDeParser; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Timeout; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; +import org.junit.jupiter.params.provider.ValueSource; + +class TaggedDollarStringTest { + static Stream tags() { + return Stream.of("", "tag", "Tag_123", "_", "한글", "étiquette"); + } + + @ParameterizedTest + @MethodSource("tags") + void preservesLiteralBodiesAndDelimiters(String tag) throws Exception { + String delimiter = "$" + tag + "$"; + for (String body : List.of("", "abc", "a\nb\r\nc\t ", "x 'one' ''two'' \\ end", + "/* comment */ -- more\n#hash", "[ {\"some\":\"json\",\"with\":\"properties$\"} ]", + "$1 $other$ こんにちは")) { + String literal = delimiter + body + delimiter; + String sql = "SELECT " + literal + " AS value, 2 FROM t"; + PlainSelect select = (PlainSelect) TestUtils.assertSqlCanBeParsedAndDeparsed(sql, true, + parser -> parser.withDialect(Dialect.POSTGRESQL)); + StringValue value = + assertInstanceOf(StringValue.class, select.getSelectItem(0).getExpression()); + assertEquals(body, value.getValue()); + assertEquals(body, value.getNotExcapedValue()); + assertEquals(delimiter, value.getQuoteStr()); + assertEquals(literal, value.toString()); + StringBuilder builder = new StringBuilder(); + select.accept(new StatementDeParser(builder), null); + PlainSelect again = (PlainSelect) CCJSqlParserUtil.parse(builder.toString(), + parser -> parser.withDialect(Dialect.POSTGRESQL)); + assertEquals(body, again.getSelectItem(0).getExpression(StringValue.class).getValue()); + assertEquals(select.toString(), builder.toString()); + } + } + + @Test + void keepsDifferentTagsAndDollarSignsInsideBody() throws Exception { + String body = "$other$ text $Tag$ $$ $1 $t"; + PlainSelect select = + (PlainSelect) CCJSqlParserUtil.parse("SELECT $tag$" + body + "$tag$::text, $1", + parser -> parser.withDialect(Dialect.POSTGRESQL)); + CastExpression cast = select.getSelectItem(0).getExpression(CastExpression.class); + assertEquals(body, ((StringValue) cast.getLeftExpression()).getValue()); + assertInstanceOf(JdbcParameter.class, select.getSelectItem(1).getExpression()); + } + + @Test + void retainsIdentifiersWithPostgreSqlDialect() throws Exception { + PlainSelect select = (PlainSelect) CCJSqlParserUtil + .parse("SELECT $parameter, foo$bar, \"$tag$abc$tag$\", $1 FROM t", + parser -> parser.withDialect(Dialect.POSTGRESQL)); + for (int i = 0; i < 3; i++) { + assertInstanceOf(Column.class, select.getSelectItem(i).getExpression()); + } + assertInstanceOf(JdbcParameter.class, select.getSelectItem(3).getExpression()); + } + + static Stream> identifierConfigurations() { + return Stream.concat( + Stream.>of(parser -> { + }, + parser -> parser.withDialect(Dialect.POSTGRESQL) + .withDollarQuotedStringTags(false)), + Stream.of(Dialect.values()).filter(dialect -> dialect != Dialect.POSTGRESQL) + .map(dialect -> parser -> parser.withDialect(dialect))); + } + + @ParameterizedTest + @MethodSource("identifierConfigurations") + void retainsIdentifiersAndUntaggedLiterals(Consumer configuration) + throws Exception { + for (String identifier : List.of("$tag$abc$tag$", "$tag$identifier")) { + PlainSelect select = + (PlainSelect) CCJSqlParserUtil.parse("SELECT " + identifier, configuration); + assertEquals(identifier, + select.getSelectItem(0).getExpression(Column.class).getColumnName()); + } + PlainSelect untagged = (PlainSelect) CCJSqlParserUtil.parse("SELECT $$text$$", + configuration); + assertEquals("text", untagged.getSelectItem(0).getExpression(StringValue.class).getValue()); + } + + @Test + void supportsExplicitOptInWithoutDialect() throws Exception { + PlainSelect select = (PlainSelect) CCJSqlParserUtil.parse("SELECT $tag$abc$tag$", + parser -> parser.withDollarQuotedStringTags(true)); + assertEquals("abc", select.getSelectItem(0).getExpression(StringValue.class).getValue()); + } + + @Test + void retainsBodyWithOtherLexerOptions() throws Exception { + PlainSelect select = (PlainSelect) CCJSqlParserUtil.parse( + "SELECT $t$#hash\n\\text't$tag$ \"q\"$t$", + parser -> parser.withDialect(Dialect.MYSQL).withDollarQuotedStringTags(true)); + assertEquals("#hash\n\\text't$tag$ \"q\"", + select.getSelectItem(0).getExpression(StringValue.class).getValue()); + } + + @Test + void keepsLineColumnAndAbsoluteTokenPositions() { + String literal = "$tag$a\nb$tag$"; + CCJSqlParser parser = CCJSqlParserUtil.newParser("SELECT " + literal + ", 2") + .withDialect(Dialect.POSTGRESQL); + parser.getNextToken(); + Token value = parser.getNextToken(); + Token comma = parser.getNextToken(); + assertEquals(CCJSqlParserConstants.S_CHAR_LITERAL, value.kind); + assertEquals(literal, value.image); + assertEquals(1, value.beginLine); + assertEquals(8, value.beginColumn); + assertEquals(2, value.endLine); + assertEquals(6, value.endColumn); + assertEquals(8, value.absoluteBegin); + assertEquals(8 + literal.length(), value.absoluteEnd); + assertEquals(value.absoluteEnd, comma.absoluteBegin); + assertEquals(7, comma.beginColumn); + } + + @Test + void recognizesFunctionBodyAndFollowingStatement() throws Exception { + String body = "SELECT 'a;''b'::text;\n"; + String sql = + "CREATE FUNCTION f() RETURNS text AS $fn$" + body + "$fn$ LANGUAGE SQL; SELECT 42;"; + Statements statements = CCJSqlParserUtil.parseStatements(sql, + parser -> parser.withDialect(Dialect.POSTGRESQL)); + assertEquals(2, statements.size()); + assertEquals("SELECT 42", statements.get(1).toString()); + org.junit.jupiter.api.Assertions + .assertTrue(statements.get(0).toString().contains("$fn$" + body + "$fn$")); + assertEquals(2, CCJSqlParserUtil.parseStatements(statements.toString(), + parser -> parser.withDialect(Dialect.POSTGRESQL)).size()); + } + + @Test + @Timeout(10) + void handlesLongBodiesAndOverlappingDelimiterPrefixes() throws Exception { + String body = "$ta$tagX $tagtagX\n".repeat(12000); + String sql = "SELECT $tagtag$" + body + "$tagtag$"; + PlainSelect select = + (PlainSelect) new CCJSqlParser(new StreamProvider(new StringReader(sql))) + .withDialect(Dialect.POSTGRESQL).Statement(); + assertEquals(body, select.getSelectItem(0).getExpression(StringValue.class).getValue()); + PlainSelect streamed = (PlainSelect) CCJSqlParserUtil.newParser( + new java.io.ByteArrayInputStream(sql.getBytes(StandardCharsets.UTF_8)), "UTF-8") + .withDialect(Dialect.POSTGRESQL).Statement(); + assertEquals(body, streamed.getSelectItem(0).getExpression(StringValue.class).getValue()); + } + + @ParameterizedTest + @ValueSource(strings = {"SELECT $tag$missing", "SELECT $Tag$wrong$tag$", "SELECT $t$ends$t", + "SELECT $a$text$b$", "SELECT $$missing"}) + void rejectsUnterminatedOrMismatchedTags(String sql) { + assertThrows(JSQLParserException.class, + () -> CCJSqlParserUtil.parse(sql, + parser -> parser.withDialect(Dialect.POSTGRESQL).withTimeOut(1000))); + } +} diff --git a/src/test/java/net/sf/jsqlparser/expression/TernaryExpressionTest.java b/src/test/java/net/sf/jsqlparser/expression/TernaryExpressionTest.java new file mode 100644 index 0000000000..38f20111df --- /dev/null +++ b/src/test/java/net/sf/jsqlparser/expression/TernaryExpressionTest.java @@ -0,0 +1,193 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2025 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.expression; + +import net.sf.jsqlparser.JSQLParserException; +import net.sf.jsqlparser.expression.operators.arithmetic.Addition; +import net.sf.jsqlparser.parser.CCJSqlParserUtil; +import net.sf.jsqlparser.expression.operators.conditional.AndExpression; +import net.sf.jsqlparser.expression.operators.conditional.OrExpression; +import net.sf.jsqlparser.expression.operators.relational.ComparisonOperator; +import net.sf.jsqlparser.expression.operators.relational.JsonOperator; +import net.sf.jsqlparser.schema.Column; +import net.sf.jsqlparser.statement.select.PlainSelect; +import net.sf.jsqlparser.statement.select.Select; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import static net.sf.jsqlparser.test.TestUtils.assertSqlCanBeParsedAndDeparsed; + +/** + * Tests for the ClickHouse-style ternary conditional operator {@code cond ? then : else}. + */ +class TernaryExpressionTest { + + @Test + void testIssue2436() throws JSQLParserException { + assertSqlCanBeParsedAndDeparsed("SELECT x > 0 ? 'y' : 'n' FROM t", true); + + Select select = (Select) CCJSqlParserUtil.parse("SELECT x > 0 ? 'y' : 'n' FROM t"); + TernaryExpression ternary = (TernaryExpression) ((PlainSelect) select).getSelectItem(0) + .getExpression(); + Assertions.assertTrue(ternary.getCondition() instanceof ComparisonOperator); + Assertions.assertTrue(ternary.getThenExpression() instanceof StringValue); + Assertions.assertTrue(ternary.getElseExpression() instanceof StringValue); + } + + @ParameterizedTest + @ValueSource(strings = { + // select items, where, order by, group by, having + "SELECT a ? b : c FROM t", + "SELECT * FROM t WHERE a ? b : c", + "SELECT * FROM t WHERE a ? b : c ORDER BY x ? y : z", + "SELECT id FROM t GROUP BY v ? 1 : 0 HAVING count(*) > 1", + // function arguments, nested in parentheses and subqueries + "SELECT abs(a ? b : c) FROM t", + "SELECT (a ? b : c) FROM t", + "SELECT * FROM (SELECT a ? b : c AS v FROM t) x", + // dml statements + "UPDATE t SET v = a ? b : c", + "INSERT INTO t VALUES (a ? b : c)", + "DELETE FROM t WHERE a ? b : c", + // joins + "SELECT * FROM t1 JOIN t2 ON t1.a ? t1.b : t2.c", + // branches with arithmetic or boolean operators + "SELECT x > 0 ? 1 + 2 : 3 * 4 FROM t", + "SELECT a ? b OR c : d AND e FROM t", + "SELECT a IS NULL ? 'x' : y FROM t", + // jdbc parameters as branches + "SELECT a ? ? : c FROM t", + "SELECT a ? b + ? : c FROM t", + "SELECT a ? ? : ? FROM t", + "SELECT a ? b : ? FROM t", + "SELECT * FROM t WHERE x = ? AND y ? z : w", + // case expression inside a branch + "SELECT a ? CASE WHEN b THEN c ELSE d END : e FROM t", + // cast target type ends the then-branch (a bare ":" after a type + // keyword is the ternary separator, not a named parameter) + "SELECT a ? b :: int : c FROM t", + "SELECT x > 0 ? CAST(b AS int) : c FROM t" + }) + void testTernaryInVariousContexts(String sqlStr) throws JSQLParserException { + assertSqlCanBeParsedAndDeparsed(sqlStr, true); + } + + @Test + void testPrecedenceBindsLooserThanBooleanOperators() throws JSQLParserException { + Select select = (Select) CCJSqlParserUtil + .parse("SELECT * FROM t WHERE a OR b ? c : d"); + Expression where = ((PlainSelect) select).getWhere(); + Assertions.assertTrue(where instanceof TernaryExpression); + Assertions.assertTrue(((TernaryExpression) where).getCondition() instanceof OrExpression); + + select = (Select) CCJSqlParserUtil.parse("SELECT * FROM t WHERE a AND b ? c : d"); + where = ((PlainSelect) select).getWhere(); + Assertions.assertTrue(where instanceof TernaryExpression); + Assertions.assertTrue(((TernaryExpression) where).getCondition() instanceof AndExpression); + + select = (Select) CCJSqlParserUtil.parse("SELECT * FROM t WHERE a ? b : c OR d"); + where = ((PlainSelect) select).getWhere(); + Assertions.assertTrue(where instanceof TernaryExpression); + Assertions.assertTrue( + ((TernaryExpression) where).getElseExpression() instanceof OrExpression); + } + + @Test + void testRightAssociativeNesting() throws JSQLParserException { + // a ? b : c ? d : e ==> a ? b : (c ? d : e) + Select select = (Select) CCJSqlParserUtil.parse("SELECT * FROM t WHERE a ? b : c ? d : e"); + TernaryExpression ternary = (TernaryExpression) ((PlainSelect) select).getWhere(); + Assertions.assertTrue(ternary.getThenExpression() instanceof Column); + Assertions.assertTrue(ternary.getElseExpression() instanceof TernaryExpression); + + // a ? b ? c : d : e ==> a ? (b ? c : d) : e + select = (Select) CCJSqlParserUtil.parse("SELECT * FROM t WHERE a ? b ? c : d : e"); + ternary = (TernaryExpression) ((PlainSelect) select).getWhere(); + Assertions.assertTrue(ternary.getThenExpression() instanceof TernaryExpression); + Assertions.assertTrue(ternary.getElseExpression() instanceof Column); + } + + @ParameterizedTest + @ValueSource(strings = { + // PostgreSQL JSON operators must keep working + "SELECT col ? 'key' FROM t", + "SELECT col ?| 'key' FROM t", + "SELECT col ?& 'key' FROM t", + "SELECT * FROM t WHERE col ? 'key' AND x = 1", + // JSON path operator must keep working + "SELECT col -> 'key' FROM t", + // JDBC parameters must keep working + "SELECT * FROM t WHERE x = ?", + "SELECT * FROM t WHERE x = ?5", + "SELECT * FROM t WHERE x = ? AND y = ?", + "SELECT * FROM t LIMIT ?", + // array ranges and casts are unaffected + "SELECT ARRAY[1:3]", + "SELECT CAST(a AS CHAR)" + }) + void testUnrelatedSyntaxUnaffected(String sqlStr) throws JSQLParserException { + assertSqlCanBeParsedAndDeparsed(sqlStr, true); + } + + @Test + void testJsonbOperatorWithNamedParameter() throws JSQLParserException { + // regression guard (ternary support, #2466): a ":" in operand position + // starts a JDBC named parameter and must not close a ternary then-branch + Select select = (Select) CCJSqlParserUtil.parse("SELECT * FROM t WHERE j ? :key"); + Expression where = ((PlainSelect) select).getWhere(); + Assertions.assertTrue(where instanceof JsonOperator); + Assertions.assertTrue( + ((JsonOperator) where).getRightExpression() instanceof JdbcNamedParameter); + } + + @Test + void testTernaryWithCastThenBranch() throws JSQLParserException { + // regression guard: a DATA_TYPE token can end the then-branch, so the + // ":" after the cast target type must close the ternary + Select select = (Select) CCJSqlParserUtil.parse("SELECT a ? b :: int : c FROM t"); + TernaryExpression ternary = (TernaryExpression) ((PlainSelect) select).getSelectItem(0) + .getExpression(); + Assertions.assertTrue(ternary.getThenExpression() instanceof CastExpression); + } + + @Test + void testTernaryWithPositionalParameterThenBranch() throws JSQLParserException { + // regression guard: a bare "?" positional parameter is a complete + // expression, so the ":" after it closes the ternary instead of being + // skipped in favor of the jsonb reading + Select select = (Select) CCJSqlParserUtil.parse("SELECT a ? ? : c FROM t"); + TernaryExpression ternary = (TernaryExpression) ((PlainSelect) select).getSelectItem(0) + .getExpression(); + Assertions.assertTrue(ternary.getThenExpression() instanceof JdbcParameter); + Assertions.assertTrue(ternary.getElseExpression() instanceof Column); + + // a positional parameter as the LAST token of the then-branch, too + select = (Select) CCJSqlParserUtil.parse("SELECT a ? b + ? : c FROM t"); + ternary = (TernaryExpression) ((PlainSelect) select).getSelectItem(0).getExpression(); + Assertions.assertTrue(ternary.getThenExpression() instanceof Addition); + } + + @ParameterizedTest + @ValueSource(strings = { + // PostgreSQL jsonb operator combined with JDBC named parameters + "SELECT * FROM t WHERE j ? :key", + "SELECT * FROM t WHERE j ? 'k' AND x = :p", + "SELECT * FROM t WHERE j ? 'k' OR x = :p", + "SELECT * FROM t WHERE j ? :key AND x = 1", + // named parameters keep working inside ternary branches, too + "SELECT a ? :t : :e FROM t", + "SELECT * FROM t WHERE c = :c AND a ? :t : :e" + }) + void testTernaryAndJsonbWithJdbcNamedParameters(String sqlStr) throws JSQLParserException { + assertSqlCanBeParsedAndDeparsed(sqlStr, true); + } +} diff --git a/src/test/java/net/sf/jsqlparser/expression/XMLSerializeExprTest.java b/src/test/java/net/sf/jsqlparser/expression/XMLSerializeExprTest.java new file mode 100644 index 0000000000..f3c6ea36c2 --- /dev/null +++ b/src/test/java/net/sf/jsqlparser/expression/XMLSerializeExprTest.java @@ -0,0 +1,151 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.expression; + +import static org.junit.jupiter.api.Assertions.*; + +import java.util.ArrayList; +import java.util.List; +import net.sf.jsqlparser.JSQLParserException; +import net.sf.jsqlparser.parser.CCJSqlParserUtil; +import net.sf.jsqlparser.schema.Column; +import net.sf.jsqlparser.statement.select.PlainSelect; +import net.sf.jsqlparser.statement.select.OrderByElement; +import net.sf.jsqlparser.test.TestUtils; +import net.sf.jsqlparser.util.TablesNamesFinder; +import net.sf.jsqlparser.util.deparser.ExpressionDeParser; +import net.sf.jsqlparser.util.deparser.OrderByDeParser; +import net.sf.jsqlparser.util.validation.validator.ExpressionValidator; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +class XMLSerializeExprTest { + @ParameterizedTest + @ValueSource(strings = { + "SELECT XMLSERIALIZE(CONTENT XMLTYPE('Grandco')) AS xmlserialize_doc FROM DUAL", + "SELECT XMLSERIALIZE(DOCUMENT payload AS CLOB) FROM docs", + "SELECT XMLSERIALIZE(CONTENT payload AS VARCHAR2(4000)) FROM docs", + "SELECT XMLSERIALIZE(CONTENT payload AS BLOB ENCODING 'UTF-8' VERSION '1.0' INDENT SIZE = 2 SHOW DEFAULTS) FROM docs", + "SELECT XMLSERIALIZE(DOCUMENT payload NO INDENT HIDE DEFAULTS) FROM docs", + "SELECT XMLSERIALIZE(CONTENT payload INDENT) FROM docs", + "SELECT XMLSERIALIZE(CONTENT payload INDENT SIZE = 0) FROM docs", + "SELECT XMLSERIALIZE(CONTENT XMLAGG(XMLELEMENT(NAME e, payload) ORDER BY id) AS CLOB) FROM docs", + "SELECT XMLSERIALIZE(CONTENT (SELECT payload FROM docs)) FROM DUAL", + "SELECT XMLSERIALIZE(CONTENT XMLTYPE('it''s XML') VERSION '1.1') FROM DUAL" + }) + void roundTripOracleSyntax(String sql) throws JSQLParserException { + TestUtils.assertSqlCanBeParsedAndDeparsed(sql); + } + + @ParameterizedTest + @ValueSource(strings = { + "XMLSERIALIZE(CONTENT)", + "XMLSERIALIZE(DOCUMENT x AS)", + "XMLSERIALIZE(CONTENT x NO INDENT SIZE = 2)", + "XMLSERIALIZE(CONTENT x INDENT SIZE = -1)", + "XMLSERIALIZE(CONTENT x ENCODING UTF8)", + "XMLSERIALIZE(CONTENT x VERSION 1)", + "XMLSERIALIZE(CONTENT x SHOW)", + "XMLSERIALIZE(CONTENT x NO SOMETHING)", + "XMLSERIALIZE(CONTENT x INDENT INDENT)" + }) + void rejectMalformedSyntax(String expression) { + assertThrows(JSQLParserException.class, () -> CCJSqlParserUtil.parseExpression(expression)); + } + + @Test + void inspectOptionsAndOmission() throws JSQLParserException { + XMLSerializeExpr xml = (XMLSerializeExpr) CCJSqlParserUtil.parseExpression( + "XMLSERIALIZE(CONTENT payload ENCODING 'UTF-8' VERSION '1.0' INDENT SIZE = 4 SHOW DEFAULTS)"); + assertEquals(XMLSerializeExpr.SerializationMode.CONTENT, xml.getSerializationMode()); + assertNull(xml.getDataType()); + assertEquals("UTF-8", xml.getEncoding().getValue()); + assertEquals("1.0", xml.getVersion().getValue()); + assertEquals(Boolean.TRUE, xml.getIndent()); + assertEquals(4, xml.getIndentSize().getValue()); + assertEquals(Boolean.TRUE, xml.getShowDefaults()); + assertEquals(4, xml.getExpressions().size()); + XMLSerializeExpr omitted = (XMLSerializeExpr) CCJSqlParserUtil.parseExpression( + "XMLSERIALIZE(DOCUMENT payload)"); + assertNull(omitted.getIndent()); + assertNull(omitted.getShowDefaults()); + omitted.setIndentSize(new LongValue(2)); + assertThrows(IllegalArgumentException.class, omitted::validateOptions); + } + + @Test + void legacyVisitorsHandleAbsentOrderBy() throws JSQLParserException { + XMLSerializeExpr xml = (XMLSerializeExpr) CCJSqlParserUtil.parseExpression( + "XMLSERIALIZE(XMLAGG(XMLTEXT(payload)) AS VARCHAR(100))"); + assertNull(xml.getSerializationMode()); + assertNull(xml.getOrderByElements()); + List seen = new ArrayList<>(); + xml.accept(new ExpressionVisitorAdapter() { + @Override + public Void visit(Column column, S context) { + seen.add(column.getColumnName() + ":" + context); + return null; + } + }, "context"); + assertEquals(List.of("payload:context"), seen); + } + + @Test + void visitAndRewriteLegacyOrderByWithContext() throws JSQLParserException { + XMLSerializeExpr xml = (XMLSerializeExpr) CCJSqlParserUtil.parseExpression( + "XMLSERIALIZE(XMLAGG(XMLTEXT(payload) ORDER BY id DESC NULLS LAST) AS VARCHAR(100))"); + StringBuilder sql = new StringBuilder(); + ExpressionDeParser writer = new ExpressionDeParser() { + @Override + public StringBuilder visit(Column column, S context) { + assertEquals("context", context); + return getBuilder().append("new_").append(column.getColumnName()); + } + }; + writer.setBuilder(sql); + xml.accept(writer, "context"); + assertEquals( + "xmlserialize(xmlagg(xmltext(new_payload) ORDER BY new_id DESC NULLS LAST) AS VARCHAR (100))", + sql.toString()); + List seen = new ArrayList<>(); + xml.accept(new ExpressionValidator() { + @Override + public Void visit(Column column, S context) { + seen.add(column.getColumnName()); + assertEquals("context", context); + return null; + } + }, "context"); + assertEquals(List.of("payload", "id"), seen); + } + + @Test + void discoverTablesInSerializedSubquery() throws JSQLParserException { + PlainSelect select = (PlainSelect) CCJSqlParserUtil.parse( + "SELECT XMLSERIALIZE(CONTENT (SELECT payload FROM docs)) FROM outer_source"); + assertTrue( + new TablesNamesFinder().getTableList((net.sf.jsqlparser.statement.Statement) select) + .containsAll(List.of("docs", "outer_source"))); + } + + @Test + void preserveLegacyOrderByCustomization() { + StringBuilder sql = new StringBuilder(); + OrderByDeParser writer = new OrderByDeParser(new ExpressionDeParser(), sql) { + @Override + public void deParseElement(OrderByElement element) { + getBuilder().append("custom_order"); + } + }; + writer.deParse(false, List.of(new OrderByElement().withExpression(new Column("id")))); + assertEquals(" ORDER BY custom_order", sql.toString()); + } +} diff --git a/src/test/java/net/sf/jsqlparser/expression/operators/relational/BetweenTest.java b/src/test/java/net/sf/jsqlparser/expression/operators/relational/BetweenTest.java index 4fd8fd706a..ee04111bc4 100644 --- a/src/test/java/net/sf/jsqlparser/expression/operators/relational/BetweenTest.java +++ b/src/test/java/net/sf/jsqlparser/expression/operators/relational/BetweenTest.java @@ -10,6 +10,7 @@ package net.sf.jsqlparser.expression.operators.relational; import net.sf.jsqlparser.JSQLParserException; +import net.sf.jsqlparser.schema.Column; import net.sf.jsqlparser.statement.select.PlainSelect; import net.sf.jsqlparser.test.TestUtils; import org.junit.jupiter.api.Assertions; @@ -50,4 +51,78 @@ void testBetweenASymmetricIssue2250() throws JSQLParserException { Assertions.assertFalse(between.isUsingSymmetric()); Assertions.assertTrue(between.isUsingAsymmetric()); } + + @Test + void testBetweenWithOldOracleJoinSyntaxOnBothOperandsIssue672() throws JSQLParserException { + String sqlStr = + "SELECT * FROM table1 t1, table2 t2 WHERE t1.col1 BETWEEN t2.col2(+) AND t2.col3(+)"; + PlainSelect select = (PlainSelect) TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true); + Between between = (Between) select.getWhere(); + + assertEquals(EqualsTo.ORACLE_JOIN_RIGHT, + ((Column) between.getBetweenExpressionStart()).getOldOracleJoinSyntax()); + assertEquals(EqualsTo.ORACLE_JOIN_RIGHT, + ((Column) between.getBetweenExpressionEnd()).getOldOracleJoinSyntax()); + } + + @Test + void testBetweenWithOldOracleJoinSyntaxOnStartOperandIssue672() throws JSQLParserException { + TestUtils.assertSqlCanBeParsedAndDeparsed( + "SELECT * FROM table1 t1, table2 t2 WHERE t1.col1 BETWEEN t2.col2(+) AND 5", true); + } + + @Test + void testBetweenWithOldOracleJoinSyntaxOnEndOperandIssue672() throws JSQLParserException { + TestUtils.assertSqlCanBeParsedAndDeparsed( + "SELECT * FROM table1 t1, table2 t2 WHERE t1.col1 BETWEEN 1 AND t2.col3(+)", true); + } + + @Test + void testNotBetweenWithOldOracleJoinSyntaxIssue672() throws JSQLParserException { + String sqlStr = + "SELECT * FROM table1 t1, table2 t2 WHERE t1.col1 NOT BETWEEN t2.col2(+) AND t2.col3(+)"; + PlainSelect select = (PlainSelect) TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true); + Between between = (Between) select.getWhere(); + + assertTrue(between.isNot()); + assertEquals(EqualsTo.ORACLE_JOIN_RIGHT, + ((Column) between.getBetweenExpressionStart()).getOldOracleJoinSyntax()); + assertEquals(EqualsTo.ORACLE_JOIN_RIGHT, + ((Column) between.getBetweenExpressionEnd()).getOldOracleJoinSyntax()); + } + + @Test + void testBetweenSymmetricWithOldOracleJoinSyntaxIssue672() throws JSQLParserException { + TestUtils.assertSqlCanBeParsedAndDeparsed( + "SELECT * FROM t1, t2 WHERE t1.c BETWEEN SYMMETRIC t2.c(+) AND t2.d(+)", true); + } + + @Test + void testBetweenWithOldOracleJoinSyntaxInJoinOnClauseIssue672() throws JSQLParserException { + TestUtils.assertSqlCanBeParsedAndDeparsed( + "SELECT * FROM t1 JOIN t2 ON t1.c BETWEEN t2.c(+) AND t2.d(+)", true); + } + + @Test + void testBetweenWithOldOracleJoinSyntaxOnLeftOperand() throws JSQLParserException { + TestUtils.assertSqlCanBeParsedAndDeparsed( + "SELECT * FROM table1 t1, table2 t2 WHERE t2.col2(+) BETWEEN t1.col1 AND t1.col3", + true); + } + + @Test + void testBetweenWithOldOracleJoinSyntaxBeforeComparisonSuffixOnOperand() + throws JSQLParserException { + // (+) directly followed by a comparison operator keeps the pre-existing + // RegularConditionRHS path, where the marker sits on the comparison itself + String sqlStr = + "SELECT * FROM t1, t2 WHERE t1.c BETWEEN t2.c(+) = 5 AND 1"; + PlainSelect select = (PlainSelect) TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true); + Between between = (Between) select.getWhere(); + EqualsTo comparison = (EqualsTo) between.getBetweenExpressionStart(); + + assertEquals(EqualsTo.ORACLE_JOIN_RIGHT, comparison.getOldOracleJoinSyntax()); + assertEquals(EqualsTo.NO_ORACLE_JOIN, + ((Column) comparison.getLeftExpression()).getOldOracleJoinSyntax()); + } } diff --git a/src/test/java/net/sf/jsqlparser/expression/operators/relational/LikeExpressionTest.java b/src/test/java/net/sf/jsqlparser/expression/operators/relational/LikeExpressionTest.java index eb11e86e20..f06dc9e328 100644 --- a/src/test/java/net/sf/jsqlparser/expression/operators/relational/LikeExpressionTest.java +++ b/src/test/java/net/sf/jsqlparser/expression/operators/relational/LikeExpressionTest.java @@ -12,9 +12,13 @@ import static org.junit.jupiter.api.Assertions.*; import net.sf.jsqlparser.JSQLParserException; +import net.sf.jsqlparser.expression.BinaryExpression; import net.sf.jsqlparser.expression.Expression; import net.sf.jsqlparser.expression.StringValue; +import net.sf.jsqlparser.expression.operators.conditional.AndExpression; import net.sf.jsqlparser.parser.CCJSqlParserUtil; +import net.sf.jsqlparser.schema.Column; +import net.sf.jsqlparser.statement.select.PlainSelect; import net.sf.jsqlparser.test.TestUtils; import org.junit.jupiter.api.Test; @@ -98,4 +102,87 @@ public void testMatchRegexp() throws JSQLParserException { TestUtils.assertSqlCanBeParsedAndDeparsed( "select * from dual where v NOT MATCH_REGEXP 'keyword1 keyword2'", true); } + + @Test + public void testLikeWithOldOracleJoinSyntaxOnRightOperand() throws JSQLParserException { + String sqlStr = "SELECT * FROM table1 t1, table2 t2 WHERE t1.col1 LIKE t2.col2(+)"; + PlainSelect select = (PlainSelect) TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true); + LikeExpression like = (LikeExpression) select.getWhere(); + + assertEquals(EqualsTo.ORACLE_JOIN_RIGHT, + ((Column) like.getRightExpression()).getOldOracleJoinSyntax()); + assertEquals(EqualsTo.NO_ORACLE_JOIN, + ((Column) like.getLeftExpression()).getOldOracleJoinSyntax()); + } + + @Test + public void testNotLikeWithOldOracleJoinSyntaxOnRightOperand() throws JSQLParserException { + TestUtils.assertSqlCanBeParsedAndDeparsed( + "SELECT * FROM table1 t1, table2 t2 WHERE t1.col1 NOT LIKE t2.col2(+)", true); + } + + @Test + public void testSimilarToWithOldOracleJoinSyntaxOnRightOperand() throws JSQLParserException { + String sqlStr = "SELECT * FROM table1 t1, table2 t2 WHERE t1.col1 SIMILAR TO t2.col2(+)"; + PlainSelect select = (PlainSelect) TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true); + BinaryExpression similarTo = (BinaryExpression) select.getWhere(); + + assertEquals(EqualsTo.ORACLE_JOIN_RIGHT, + ((Column) similarTo.getRightExpression()).getOldOracleJoinSyntax()); + } + + @Test + public void testSimilarToOnSeparateTokensWithOldOracleJoinSyntaxOnRightOperand() + throws JSQLParserException { + String sqlStr = "SELECT * FROM table1 t1, table2 t2 WHERE t1.col1 SIMILAR\nTO t2.col2(+)"; + PlainSelect select = (PlainSelect) TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true); + + assertTrue(select.getWhere() instanceof SimilarToExpression); + assertEquals(EqualsTo.ORACLE_JOIN_RIGHT, + ((Column) ((SimilarToExpression) select.getWhere()).getRightExpression()) + .getOldOracleJoinSyntax()); + } + + @Test + public void testILikeWithOldOracleJoinSyntaxOnRightOperand() throws JSQLParserException { + String sqlStr = "SELECT * FROM table1 t1, table2 t2 WHERE t1.col1 ILIKE t2.col2(+)"; + PlainSelect select = (PlainSelect) TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true); + LikeExpression like = (LikeExpression) select.getWhere(); + + assertEquals(LikeExpression.KeyWord.ILIKE, like.getLikeKeyWord()); + assertEquals(EqualsTo.ORACLE_JOIN_RIGHT, + ((Column) like.getRightExpression()).getOldOracleJoinSyntax()); + } + + @Test + public void testLikeWithOldOracleJoinSyntaxInJoinOnClause() throws JSQLParserException { + String sqlStr = + "SELECT * FROM table1 t1 LEFT JOIN table2 t2 ON t1.a = t2.b AND t1.col1 LIKE t2.col2(+)"; + PlainSelect select = (PlainSelect) TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true); + LikeExpression like = (LikeExpression) ((AndExpression) select.getJoins().get(0) + .getOnExpression()).getRightExpression(); + + assertEquals(EqualsTo.ORACLE_JOIN_RIGHT, + ((Column) like.getRightExpression()).getOldOracleJoinSyntax()); + } + + @Test + public void testSimilarToWithOldOracleJoinSyntaxOnRightOperandAndEscape() + throws JSQLParserException { + TestUtils.assertSqlCanBeParsedAndDeparsed( + "SELECT * FROM table1 t1, table2 t2 WHERE t1.col1 SIMILAR TO t2.col2(+) ESCAPE '\\'", + true); + } + + @Test + public void testLikeWithOldOracleJoinSyntaxOnLeftOperand() throws JSQLParserException { + String sqlStr = "SELECT * FROM table1 t1, table2 t2 WHERE t1.col1(+) LIKE t2.col2"; + PlainSelect select = (PlainSelect) TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true); + LikeExpression like = (LikeExpression) select.getWhere(); + + assertEquals(EqualsTo.ORACLE_JOIN_RIGHT, + ((Column) like.getLeftExpression()).getOldOracleJoinSyntax()); + assertEquals(EqualsTo.NO_ORACLE_JOIN, + ((Column) like.getRightExpression()).getOldOracleJoinSyntax()); + } } diff --git a/src/test/java/net/sf/jsqlparser/expression/operators/relational/OraclePriorPositionTest.java b/src/test/java/net/sf/jsqlparser/expression/operators/relational/OraclePriorPositionTest.java new file mode 100644 index 0000000000..643342b71b --- /dev/null +++ b/src/test/java/net/sf/jsqlparser/expression/operators/relational/OraclePriorPositionTest.java @@ -0,0 +1,211 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.expression.operators.relational; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import net.sf.jsqlparser.JSQLParserException; +import net.sf.jsqlparser.expression.Expression; +import net.sf.jsqlparser.statement.select.PlainSelect; +import net.sf.jsqlparser.test.TestUtils; +import org.junit.jupiter.api.Test; + +public class OraclePriorPositionTest { + + private static Expression whereOf(String sqlStr) throws JSQLParserException { + PlainSelect select = + (PlainSelect) TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true); + return select.getWhere(); + } + + @Test + public void testPriorBetween() throws JSQLParserException { + Expression where = whereOf("SELECT * FROM t WHERE PRIOR a BETWEEN 1 AND 2"); + Between between = (Between) where; + assertEquals(SupportsOldOracleJoinSyntax.ORACLE_PRIOR_START, + between.getOraclePriorPosition()); + } + + @Test + public void testPriorLike() throws JSQLParserException { + Expression where = whereOf("SELECT * FROM t WHERE PRIOR a LIKE 'x%'"); + LikeExpression like = (LikeExpression) where; + assertEquals(SupportsOldOracleJoinSyntax.ORACLE_PRIOR_START, + like.getOraclePriorPosition()); + } + + @Test + public void testPriorNotLike() throws JSQLParserException { + Expression where = whereOf("SELECT * FROM t WHERE PRIOR a NOT LIKE 'x%'"); + LikeExpression like = (LikeExpression) where; + assertEquals(SupportsOldOracleJoinSyntax.ORACLE_PRIOR_START, + like.getOraclePriorPosition()); + } + + @Test + public void testPriorSimilarTo() throws JSQLParserException { + Expression where = whereOf("SELECT * FROM t WHERE PRIOR a SIMILAR TO 'x'"); + LikeExpression similarTo = (LikeExpression) where; + assertEquals(SupportsOldOracleJoinSyntax.ORACLE_PRIOR_START, + similarTo.getOraclePriorPosition()); + } + + @Test + public void testPriorSimilarToOnSeparateTokens() throws JSQLParserException { + Expression where = whereOf("SELECT * FROM t WHERE PRIOR a SIMILAR\nTO 'x'"); + SimilarToExpression similarTo = (SimilarToExpression) where; + assertEquals(SupportsOldOracleJoinSyntax.ORACLE_PRIOR_START, + similarTo.getOraclePriorPosition()); + } + + @Test + public void testPriorIsNull() throws JSQLParserException { + Expression where = whereOf("SELECT * FROM t WHERE PRIOR a IS NULL"); + IsNullExpression isNull = (IsNullExpression) where; + assertEquals(SupportsOldOracleJoinSyntax.ORACLE_PRIOR_START, + isNull.getOraclePriorPosition()); + } + + @Test + public void testPriorIsNotNull() throws JSQLParserException { + Expression where = whereOf("SELECT * FROM t WHERE PRIOR a IS NOT NULL"); + IsNullExpression isNull = (IsNullExpression) where; + assertEquals(SupportsOldOracleJoinSyntax.ORACLE_PRIOR_START, + isNull.getOraclePriorPosition()); + } + + @Test + public void testPriorIsTrue() throws JSQLParserException { + Expression where = whereOf("SELECT * FROM t WHERE PRIOR a IS TRUE"); + IsBooleanExpression isBoolean = (IsBooleanExpression) where; + assertEquals(SupportsOldOracleJoinSyntax.ORACLE_PRIOR_START, + isBoolean.getOraclePriorPosition()); + } + + @Test + public void testPriorIsUnknown() throws JSQLParserException { + Expression where = whereOf("SELECT * FROM t WHERE PRIOR a IS UNKNOWN"); + IsUnknownExpression isUnknown = (IsUnknownExpression) where; + assertEquals(SupportsOldOracleJoinSyntax.ORACLE_PRIOR_START, + isUnknown.getOraclePriorPosition()); + } + + @Test + public void testPriorIsDistinctFrom() throws JSQLParserException { + Expression where = whereOf("SELECT * FROM t WHERE PRIOR a IS DISTINCT FROM b"); + IsDistinctExpression isDistinct = (IsDistinctExpression) where; + assertEquals(SupportsOldOracleJoinSyntax.ORACLE_PRIOR_START, + isDistinct.getOraclePriorPosition()); + } + + @Test + public void testPriorMemberOf() throws JSQLParserException { + Expression where = whereOf("SELECT * FROM t WHERE PRIOR a MEMBER OF (1)"); + MemberOfExpression memberOf = (MemberOfExpression) where; + assertEquals(SupportsOldOracleJoinSyntax.ORACLE_PRIOR_START, + memberOf.getOraclePriorPosition()); + } + + @Test + public void testPriorIn() throws JSQLParserException { + Expression where = whereOf("SELECT * FROM t WHERE PRIOR a IN (1, 2)"); + InExpression in = (InExpression) where; + assertEquals(SupportsOldOracleJoinSyntax.ORACLE_PRIOR_START, + in.getOraclePriorPosition()); + } + + @Test + public void testPriorNotIn() throws JSQLParserException { + TestUtils.assertSqlCanBeParsedAndDeparsed("SELECT * FROM t WHERE PRIOR a NOT IN (1, 2)", + true); + } + + @Test + public void testPriorOnComparisonStartIsDeparsed() throws JSQLParserException { + TestUtils.assertSqlCanBeParsedAndDeparsed("SELECT * FROM t WHERE PRIOR a = b", true); + } + + @Test + public void testPriorOnComparisonEndIsDeparsed() throws JSQLParserException { + TestUtils.assertSqlCanBeParsedAndDeparsed("SELECT * FROM t WHERE a = PRIOR b", true); + } + + @Test + public void testPriorIsNullShorthand() throws JSQLParserException { + Expression where = whereOf("SELECT * FROM t WHERE PRIOR a ISNULL"); + IsNullExpression isNull = (IsNullExpression) where; + assertEquals(SupportsOldOracleJoinSyntax.ORACLE_PRIOR_START, + isNull.getOraclePriorPosition()); + } + + @Test + public void testPriorNotNullShorthand() throws JSQLParserException { + TestUtils.assertSqlCanBeParsedAndDeparsed("SELECT * FROM t WHERE PRIOR a NOTNULL", true); + } + + @Test + public void testPriorIsFalse() throws JSQLParserException { + Expression where = whereOf("SELECT * FROM t WHERE PRIOR a IS FALSE"); + IsBooleanExpression isBoolean = (IsBooleanExpression) where; + assertEquals(SupportsOldOracleJoinSyntax.ORACLE_PRIOR_START, + isBoolean.getOraclePriorPosition()); + } + + @Test + public void testPriorIsNotUnknown() throws JSQLParserException { + TestUtils.assertSqlCanBeParsedAndDeparsed("SELECT * FROM t WHERE PRIOR a IS NOT UNKNOWN", + true); + } + + @Test + public void testPriorNotSimilarTo() throws JSQLParserException { + TestUtils.assertSqlCanBeParsedAndDeparsed( + "SELECT * FROM t WHERE PRIOR a NOT SIMILAR TO 'x'", + true); + } + + @Test + public void testPriorInJoinOnClause() throws JSQLParserException { + TestUtils.assertSqlCanBeParsedAndDeparsed( + "SELECT * FROM t JOIN t2 ON PRIOR t.c = t2.c", true); + } + + @Test + public void testPriorBetweenEndOperandStaysIntact() throws JSQLParserException { + TestUtils.assertSqlCanBeParsedAndDeparsed("SELECT * FROM t WHERE a BETWEEN 1 AND PRIOR 2", + true); + } + + @Test + public void testPlainConditionsWithoutPriorAreUnchanged() throws JSQLParserException { + TestUtils.assertSqlCanBeParsedAndDeparsed("SELECT * FROM t WHERE a = b", true); + TestUtils.assertSqlCanBeParsedAndDeparsed("SELECT * FROM t WHERE a BETWEEN 1 AND 2", true); + TestUtils.assertSqlCanBeParsedAndDeparsed("SELECT * FROM t WHERE a LIKE 'x%'", true); + TestUtils.assertSqlCanBeParsedAndDeparsed("SELECT * FROM t WHERE a IS NULL", true); + } + + @Test + public void testNotPriorBetween() throws JSQLParserException { + TestUtils.assertSqlCanBeParsedAndDeparsed( + "SELECT * FROM t WHERE NOT PRIOR a BETWEEN 1 AND 2", true); + } + + @Test + public void testPriorOnSeveralConditions() throws JSQLParserException { + TestUtils.assertSqlCanBeParsedAndDeparsed( + "SELECT * FROM t WHERE PRIOR a = b AND PRIOR c BETWEEN 1 AND 2", true); + } + + @Test + public void testPriorCombinedWithOracleJoinOnBetween() throws JSQLParserException { + TestUtils.assertSqlCanBeParsedAndDeparsed( + "SELECT * FROM t1, t2 WHERE PRIOR t2.c(+) BETWEEN 1 AND 2", true); + } +} diff --git a/src/test/java/net/sf/jsqlparser/parser/CCJSqlParserTest.java b/src/test/java/net/sf/jsqlparser/parser/CCJSqlParserTest.java index f0093e11f8..d408fc68fb 100644 --- a/src/test/java/net/sf/jsqlparser/parser/CCJSqlParserTest.java +++ b/src/test/java/net/sf/jsqlparser/parser/CCJSqlParserTest.java @@ -10,10 +10,14 @@ package net.sf.jsqlparser.parser; import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import net.sf.jsqlparser.JSQLParserException; import org.junit.jupiter.api.Test; import net.sf.jsqlparser.parser.feature.Feature; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; public class CCJSqlParserTest { @Test @@ -24,4 +28,21 @@ public void parserWithTimeout() throws Exception { assertThat(timeOut).isEqualTo(123L); } + + @ParameterizedTest + @CsvSource(delimiter = '|', value = { + "SELECT /* c */1 | SELECT /* c */ 1", + "SELECT 1 /* c */+2 | SELECT 1 /* c */ +2", + "SELECT 1/*x*/+2 | SELECT 1 /*x*/ +2", + "SELECT /* a /* b */ c */1 | SELECT /* a /* b */ c */ 1", + "SELECT * FROM t WHERE/*c*/x=1 | SELECT * FROM t WHERE /*c*/ x=1", + "SELECT/*+ FULL(t) */*FROM t | SELECT /*+ FULL(t) */ * FROM t" + }) + void testBlockCommentFollowedByNonWhitespace(String tight, String spaced) + throws JSQLParserException { + // Issue #2509: the first character after */ was swallowed into the comment token, + // so the tight form either failed to parse or silently lost that character. + assertEquals(CCJSqlParserUtil.parse(spaced).toString(), + CCJSqlParserUtil.parse(tight).toString()); + } } diff --git a/src/test/java/net/sf/jsqlparser/parser/CCJSqlParserUtilTest.java b/src/test/java/net/sf/jsqlparser/parser/CCJSqlParserUtilTest.java index dd9b557325..6de42eb6a6 100644 --- a/src/test/java/net/sf/jsqlparser/parser/CCJSqlParserUtilTest.java +++ b/src/test/java/net/sf/jsqlparser/parser/CCJSqlParserUtilTest.java @@ -12,6 +12,7 @@ import net.sf.jsqlparser.JSQLParserException; import net.sf.jsqlparser.expression.Expression; import net.sf.jsqlparser.expression.LongValue; +import net.sf.jsqlparser.expression.StringValue; import net.sf.jsqlparser.expression.operators.arithmetic.Addition; import net.sf.jsqlparser.expression.operators.arithmetic.Multiplication; import net.sf.jsqlparser.expression.operators.relational.ParenthesedExpressionList; @@ -19,7 +20,9 @@ import net.sf.jsqlparser.statement.Statement; import net.sf.jsqlparser.statement.Statements; import net.sf.jsqlparser.statement.UnsupportedStatement; +import net.sf.jsqlparser.statement.create.table.ColDataType; import net.sf.jsqlparser.statement.select.PlainSelect; +import net.sf.jsqlparser.statement.select.Select; import net.sf.jsqlparser.statement.select.TableStatement; import net.sf.jsqlparser.test.MemoryLeakVerifier; import net.sf.jsqlparser.test.TestUtils; @@ -37,6 +40,7 @@ import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicBoolean; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -44,6 +48,7 @@ import static org.junit.jupiter.api.Assertions.assertInstanceOf; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; public class CCJSqlParserUtilTest { @@ -104,6 +109,34 @@ public void testParseExpression2() throws Exception { assertInstanceOf(ParenthesedExpressionList.class, mult.getRightExpression()); } + @Test + public void testParseColDataType() throws Exception { + assertNull(CCJSqlParserUtil.parseColDataType(null)); + assertNull(CCJSqlParserUtil.parseColDataType("")); + + ColDataType arrayType = CCJSqlParserUtil.parseColDataType("integer[][]"); + assertEquals("integer", arrayType.getDataType()); + assertEquals(2, arrayType.getArrayData().size()); + assertNull(arrayType.getArrayData().get(0)); + assertNull(arrayType.getArrayData().get(1)); + + AtomicBoolean configured = new AtomicBoolean(); + ColDataType enumType = + CCJSqlParserUtil.parseColDataType("enum('small','medium')", parser -> { + configured.set(true); + parser.withDialect(AbstractJSqlParser.Dialect.MYSQL); + }); + assertTrue(configured.get()); + assertEquals("enum", enumType.getDataType()); + assertEquals(List.of("'small'", "'medium'"), enumType.getArgumentsStringList()); + } + + @Test + public void testParseColDataTypeRejectsTrailingTokens() { + assertThrows(JSQLParserException.class, + () -> CCJSqlParserUtil.parseColDataType("varchar(255) NOT NULL")); + } + @Test public void testParseExpressionNonPartial() throws Exception { assertThrows(JSQLParserException.class, @@ -487,9 +520,9 @@ void testUnbalancedPosition() { } @Test - void testParseEmpty() throws JSQLParserException { - assertNull(CCJSqlParserUtil.parse("")); - assertNull(CCJSqlParserUtil.parse((String) null)); + void testParseEmpty() { + assertThrows(JSQLParserException.class, () -> CCJSqlParserUtil.parse("")); + assertThrows(JSQLParserException.class, () -> CCJSqlParserUtil.parse((String) null)); } @Test @@ -514,4 +547,261 @@ void testSingleStatementWithEmptyLines() throws JSQLParserException { + "def'\n" + "where id=?", true); } + + @Test + public void testHashLineCommentsFeature() throws Exception { + // with the flag: MySQL line comments, `#` to end of line, + // unconditional like MySQL itself (no blank needed, `42#24` is a + // comment there too); the statement continues on the next line + assertEquals("SELECT 42", CCJSqlParserUtil + .parse("SELECT 42 # 24", p -> p.withHashLineComments(true)).toString()); + assertEquals("SELECT 1", CCJSqlParserUtil + .parse("SELECT 1 # comment, 2", p -> p.withHashLineComments(true)).toString()); + assertEquals("SELECT 1", CCJSqlParserUtil + .parse("SELECT 1 #comment", p -> p.withHashLineComments(true)).toString()); + assertEquals("SELECT 1", CCJSqlParserUtil + .parse("SELECT 1 #!bang", p -> p.withHashLineComments(true)).toString()); + assertEquals("SELECT 42", CCJSqlParserUtil + .parse("SELECT 42#24", p -> p.withHashLineComments(true)).toString()); + assertEquals("SELECT a", CCJSqlParserUtil + .parse("SELECT a#b FROM t", p -> p.withHashLineComments(true)).toString()); + assertEquals("SELECT 1 FROM t", CCJSqlParserUtil + .parse("SELECT 1 # c\nFROM t", p -> p.withHashLineComments(true)).toString()); + assertEquals("SELECT 1", CCJSqlParserUtil + .parse("# leading\nSELECT 1", p -> p.withHashLineComments(true)).toString()); + assertEquals("SELECT 1", CCJSqlParserUtil + .parse("SELECT 1 #", p -> p.withHashLineComments(true)).toString()); + assertEquals("SELECT 1", CCJSqlParserUtil + .parse("SELECT 1 #\n", p -> p.withHashLineComments(true)).toString()); + // the `#>` family has no meaning in MySQL and comments out like any + // other `#`; quoted forms keep their `#` + assertEquals("SELECT data", CCJSqlParserUtil + .parse("SELECT data #> '{a}' FROM t", p -> p.withHashLineComments(true)) + .toString()); + TestUtils.assertSqlCanBeParsedAndDeparsed("SELECT '#' FROM t", true, + p -> p.withHashLineComments(true)); + TestUtils.assertSqlCanBeParsedAndDeparsed("SELECT \"a#b\" FROM t", true, + p -> p.withHashLineComments(true)); + // default (flag off): the #2507 binary operator, unchanged + assertEquals("SELECT 42 # 24", CCJSqlParserUtil.parse("SELECT 42 # 24").toString()); + assertEquals("SELECT 42#24", CCJSqlParserUtil.parse("SELECT 42#24").toString()); + } + + @Test + public void testHashLineCommentsMySQLStatementSemantics() throws Exception { + // `#temp` is a comment under the flag, so `SELECT #temp FROM t` + // loses the rest of the line and fails, exactly like MySQL; with the + // flag off the same input parses as the #2507 operator expression + assertThrows(JSQLParserException.class, () -> CCJSqlParserUtil + .parse("SELECT #temp FROM t", p -> p.withHashLineComments(true))); + assertEquals("SELECT 1 # comment", + CCJSqlParserUtil.parse("SELECT 1 # comment").toString()); + } + + @Test + public void testDialectPresets() throws Exception { + // MYSQL and MARIADB: backslash escapes and # line comments (MySQL 8 + // manual "Comments" and "String Literals", MariaDB KB "Comment Syntax" + // and "String Literals"); backticks are always on, brackets are not + // MySQL syntax + for (AbstractJSqlParser.Dialect dialect : new AbstractJSqlParser.Dialect[] { + AbstractJSqlParser.Dialect.MYSQL, AbstractJSqlParser.Dialect.MARIADB}) { + assertEquals("SELECT `col` FROM t WHERE a = 'x\\'yz' AND b = 42", CCJSqlParserUtil + .parse("SELECT `col` FROM t WHERE a = 'x\\'yz' AND b = 42#24", + p -> p.withDialect(dialect)) + .toString()); + } + // SQLSERVER: bracket quotation only (Microsoft Learn "Database + // Identifiers"), `#` stays available for identifiers (temp tables) + assertEquals("SELECT [my column] FROM t", + CCJSqlParserUtil.parse("SELECT [my column] FROM t", + p -> p.withDialect(AbstractJSqlParser.Dialect.SQLSERVER)).toString()); + // empty presets keep the defaults: the #2507 operator, no backslash + // escapes + for (AbstractJSqlParser.Dialect dialect : new AbstractJSqlParser.Dialect[] { + AbstractJSqlParser.Dialect.ANSI_SQL, AbstractJSqlParser.Dialect.ORACLE, + AbstractJSqlParser.Dialect.POSTGRESQL, AbstractJSqlParser.Dialect.H2, + AbstractJSqlParser.Dialect.EXASOL}) { + assertEquals("SELECT 42 # 24", + CCJSqlParserUtil.parse("SELECT 42 # 24", p -> p.withDialect(dialect)) + .toString()); + assertThrows(JSQLParserException.class, () -> CCJSqlParserUtil + .parse("SELECT 'a\\'b'", p -> p.withDialect(dialect))); + } + } + + @Test + public void testDialectPresetSwitchOverrides() throws Exception { + // an explicit switch after the preset wins: `#` is the #2507 + // operator again + assertEquals("SELECT 1 # comment", CCJSqlParserUtil.parse("SELECT 1 # comment", + p -> p.withDialect(AbstractJSqlParser.Dialect.MYSQL).withHashLineComments(false)) + .toString()); + // the preset never turns a previously enabled switch off + assertEquals("SELECT [my column] FROM mytable", + CCJSqlParserUtil.parse("SELECT [my column] FROM mytable", + p -> p.withSquareBracketQuotation(true) + .withDialect(AbstractJSqlParser.Dialect.MYSQL)) + .toString()); + } + + @Test + public void testDoubleQuotedStringsFeature() throws Exception { + // default (flag off): double quotes are quoted identifiers, unchanged + PlainSelect select = (PlainSelect) ((Select) CCJSqlParserUtil + .parse("SELECT \"not an identifier\"")).getSelectBody(); + assertTrue(select.getSelectItems().get(0).getExpression() instanceof Column); + // on: the BigQuery/Spark/MySQL-default reading, double quotes are + // string literals, the quote kept on round-trip + select = (PlainSelect) ((Select) CCJSqlParserUtil.parse("SELECT \"not an identifier\"", + p -> p.withDoubleQuotedStrings(true))).getSelectBody(); + Expression expression = select.getSelectItems().get(0).getExpression(); + assertTrue(expression instanceof StringValue); + assertEquals("not an identifier", ((StringValue) expression).getValue()); + assertEquals("SELECT \"not an identifier\"", select.toString()); + // empty string and doubled quotes take the single-quote treatment + assertEquals("SELECT \"\"", CCJSqlParserUtil + .parse("SELECT \"\"", p -> p.withDoubleQuotedStrings(true)).toString()); + assertEquals("SELECT \"a\"\"b\"", CCJSqlParserUtil + .parse("SELECT \"a\"\"b\"", p -> p.withDoubleQuotedStrings(true)).toString()); + // identifier positions: a `"..."` token follows the existing + // string-as-table branch (`FROM 'file.csv'`), the same leniency + // single quotes already have; MySQL itself would error here + assertEquals("SELECT * FROM \"t\"", CCJSqlParserUtil + .parse("SELECT * FROM \"t\"", p -> p.withDoubleQuotedStrings(true)).toString()); + } + + @Test + public void testDialectPresetsWarehouses() throws Exception { + // BIGQUERY: double quoted strings, # line comments and backslash + // escapes (GoogleSQL "Lexical structure and syntax", identifiers are + // backticked), backticks stay unconditional + assertEquals("SELECT `col` FROM t WHERE a = 'x\\'yz' AND b = 42", CCJSqlParserUtil + .parse("SELECT `col` FROM t WHERE a = 'x\\'yz' AND b = 42#24", + p -> p.withDialect(AbstractJSqlParser.Dialect.BIGQUERY)) + .toString()); + // DATABRICKS: double quoted strings and backslash escapes (Spark + // "Literals"), `#` stays the #2507 operator + assertEquals("SELECT \"a\" FROM t WHERE x = 'it\\'s'", CCJSqlParserUtil + .parse("SELECT \"a\" FROM t WHERE x = 'it\\'s'", + p -> p.withDialect(AbstractJSqlParser.Dialect.DATABRICKS)) + .toString()); + assertEquals("SELECT 42 # 24", CCJSqlParserUtil.parse("SELECT 42 # 24", + p -> p.withDialect(AbstractJSqlParser.Dialect.DATABRICKS)).toString()); + // SNOWFLAKE: backslash escape sequences ("String & binary data + // types"), `#` stays the operator + assertEquals("SELECT col FROM t WHERE a = 'x\\'yz'", CCJSqlParserUtil + .parse("SELECT col FROM t WHERE a = 'x\\'yz'", + p -> p.withDialect(AbstractJSqlParser.Dialect.SNOWFLAKE)) + .toString()); + assertEquals("SELECT 42 # 24", CCJSqlParserUtil.parse("SELECT 42 # 24", + p -> p.withDialect(AbstractJSqlParser.Dialect.SNOWFLAKE)).toString()); + } + + @Test + public void testDoubleQuotedStringsPreset() throws Exception { + // the string-default dialects: MYSQL and MARIADB (default sql_mode), + // BIGQUERY and DATABRICKS (string literals, identifiers are backticked) + for (AbstractJSqlParser.Dialect dialect : new AbstractJSqlParser.Dialect[] { + AbstractJSqlParser.Dialect.MYSQL, AbstractJSqlParser.Dialect.MARIADB, + AbstractJSqlParser.Dialect.BIGQUERY, AbstractJSqlParser.Dialect.DATABRICKS}) { + PlainSelect select = (PlainSelect) ((Select) CCJSqlParserUtil + .parse("SELECT \"abc\"", p -> p.withDialect(dialect))).getSelectBody(); + assertTrue(select.getSelectItems().get(0).getExpression() instanceof StringValue); + } + // the identifier-default dialects keep the quoted identifier reading + for (AbstractJSqlParser.Dialect dialect : new AbstractJSqlParser.Dialect[] { + AbstractJSqlParser.Dialect.SQLSERVER, AbstractJSqlParser.Dialect.SNOWFLAKE}) { + PlainSelect select = (PlainSelect) ((Select) CCJSqlParserUtil.parse("SELECT \"abc\"", + p -> p.withDialect(dialect))).getSelectBody(); + assertTrue(select.getSelectItems().get(0).getExpression() instanceof Column); + } + } + + @Test + public void testAdjacentStringLiteralsNewline() throws Exception { + // default (off): the MySQL / SQL Server alias reading, unchanged + assertEquals("SELECT 'a' 'b'", CCJSqlParserUtil.parse("SELECT 'a' 'b'").toString()); + assertEquals("SELECT 'a' 'b'", CCJSqlParserUtil.parse("SELECT 'a'\n'b'").toString()); + assertThrows(JSQLParserException.class, + () -> CCJSqlParserUtil.parse("SELECT * FROM t WHERE x = 'a'\n'b'")); + // newline mode: the standard / Postgres reading, "Two string constants + // that are only separated by whitespace with at least one newline are + // concatenated" + PlainSelect select = (PlainSelect) ((Select) CCJSqlParserUtil.parse("SELECT 'a'\n'b'", + p -> p.withAdjacentStringLiterals( + AbstractJSqlParser.AdjacentStringLiterals.NEWLINE))) + .getSelectBody(); + Expression expression = select.getSelectItems().get(0).getExpression(); + assertTrue(expression instanceof StringValue); + assertEquals("ab", ((StringValue) expression).getValue()); + assertEquals("SELECT 'ab'", select.toString()); + // same line keeps the alias reading under newline mode + assertEquals("SELECT 'a' 'b'", + CCJSqlParserUtil.parse("SELECT 'a' 'b'", + p -> p.withAdjacentStringLiterals( + AbstractJSqlParser.AdjacentStringLiterals.NEWLINE)) + .toString()); + // expression positions concatenate too + assertEquals("SELECT * FROM t WHERE x = 'ab'", + CCJSqlParserUtil.parse("SELECT * FROM t WHERE x = 'a'\n'b'", + p -> p.withAdjacentStringLiterals( + AbstractJSqlParser.AdjacentStringLiterals.NEWLINE)) + .toString()); + // three parts merge into one literal + assertEquals("SELECT 'abc'", + CCJSqlParserUtil.parse("SELECT 'a'\n'b'\n'c'", + p -> p.withAdjacentStringLiterals( + AbstractJSqlParser.AdjacentStringLiterals.NEWLINE)) + .toString()); + // the ANSI SQL and Postgres presets carry it, the MySQL preset does not + for (AbstractJSqlParser.Dialect dialect : new AbstractJSqlParser.Dialect[] { + AbstractJSqlParser.Dialect.ANSI_SQL, AbstractJSqlParser.Dialect.POSTGRESQL}) { + assertEquals("SELECT 'ab'", CCJSqlParserUtil.parse("SELECT 'a'\n'b'", + p -> p.withDialect(dialect)).toString()); + } + assertEquals("SELECT 'a' 'b'", CCJSqlParserUtil.parse("SELECT 'a'\n'b'", + p -> p.withDialect(AbstractJSqlParser.Dialect.MYSQL)).toString()); + } + + @Test + public void testAdjacentStringLiteralsWhitespace() throws Exception { + // whitespace mode: the GoogleSQL / Spark reading, same line + // concatenates (their literal chunking) + assertEquals("SELECT 'ab'", + CCJSqlParserUtil.parse("SELECT 'a' 'b'", + p -> p.withAdjacentStringLiterals( + AbstractJSqlParser.AdjacentStringLiterals.WHITESPACE)) + .toString()); + // combined with allowDoubleQuotedStrings: the BigQuery chunking shape + assertEquals("SELECT \"12\"", + CCJSqlParserUtil.parse("SELECT \"1\" \"2\"", + p -> p.withDoubleQuotedStrings(true).withAdjacentStringLiterals( + AbstractJSqlParser.AdjacentStringLiterals.WHITESPACE)) + .toString()); + // the BigQuery and Databricks presets carry it, the Snowflake preset + // does not (no adjacent-literal concatenation documented) + for (AbstractJSqlParser.Dialect dialect : new AbstractJSqlParser.Dialect[] { + AbstractJSqlParser.Dialect.BIGQUERY, AbstractJSqlParser.Dialect.DATABRICKS}) { + assertEquals("SELECT 'ab'", CCJSqlParserUtil.parse("SELECT 'a' 'b'", + p -> p.withDialect(dialect)).toString()); + } + assertEquals("SELECT 'a' 'b'", CCJSqlParserUtil.parse("SELECT 'a' 'b'", + p -> p.withDialect(AbstractJSqlParser.Dialect.SNOWFLAKE)).toString()); + } + + @Test + public void testAdjacentStringLiteralsBoolean() throws Exception { + // true: the standard (newline) mode, the same line keeps the alias reading + assertEquals("SELECT 'ab'", CCJSqlParserUtil.parse("SELECT 'a'\n'b'", + p -> p.withAdjacentStringLiterals(true)).toString()); + assertEquals("SELECT 'a' 'b'", CCJSqlParserUtil.parse("SELECT 'a' 'b'", + p -> p.withAdjacentStringLiterals(true)).toString()); + // false: off, the alias reading also across newlines + assertEquals("SELECT 'a' 'b'", CCJSqlParserUtil.parse("SELECT 'a'\n'b'", + p -> p.withAdjacentStringLiterals(false)).toString()); + // the no-arg variant enables the standard mode + assertEquals("SELECT 'ab'", CCJSqlParserUtil.parse("SELECT 'a'\n'b'", + p -> p.withAdjacentStringLiterals()).toString()); + } } diff --git a/src/test/java/net/sf/jsqlparser/parser/ContextualTokenTest.java b/src/test/java/net/sf/jsqlparser/parser/ContextualTokenTest.java new file mode 100644 index 0000000000..61dc9aa3e3 --- /dev/null +++ b/src/test/java/net/sf/jsqlparser/parser/ContextualTokenTest.java @@ -0,0 +1,78 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.parser; + +import net.sf.jsqlparser.JSQLParserException; +import net.sf.jsqlparser.statement.alter.AlterSystemStatement; +import net.sf.jsqlparser.statement.alter.AlterSystemOperation; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import static net.sf.jsqlparser.test.TestUtils.assertSqlCanBeParsedAndDeparsed; +import static org.junit.jupiter.api.Assertions.*; + +class ContextualTokenTest { + @ParameterizedTest + @ValueSource(strings = { + "SELECT a FROM t UNION ALL BY NAME MATCHING (a) SELECT a FROM u", + "SELECT a FROM t UNION DISTINCT BY NAME MATCHING (a, b) SELECT a FROM u", + "ALTER SYSTEM ENABLE DISTRIBUTED RECOVERY", + "ALTER SYSTEM DISABLE DISTRIBUTED RECOVERY" + }) + void parsesPreviouslyShadowedContextualKeywords(String sql) throws Exception { + for (boolean complex : new boolean[] {false, true}) { + String parsed = assertSqlCanBeParsedAndDeparsed(sql, true, + parser -> parser.withAllowComplexParsing(complex)).toString(); + assertEquals(parsed, CCJSqlParserUtil.parse(parsed).toString()); + } + } + + @Test + void preservesAlterSystemOperation() throws Exception { + assertEquals(AlterSystemOperation.ENABLE_DISTRIBUTED_RECOVERY, + ((AlterSystemStatement) CCJSqlParserUtil.parse( + "alter system enable distributed recovery")).getOperation()); + assertEquals(AlterSystemOperation.DISABLE_DISTRIBUTED_RECOVERY, + ((AlterSystemStatement) CCJSqlParserUtil.parse( + "alter system disable distributed recovery")).getOperation()); + } + + @ParameterizedTest + @ValueSource(strings = {"MATCHING", "DISTRIBUTED", "RECOVERY"}) + void keepsContextualWordsUsableAsIdentifiers(String word) throws Exception { + assertEquals(CCJSqlParserConstants.S_IDENTIFIER, + CCJSqlParserUtil.newParser(word).getNextToken().kind); + assertSqlCanBeParsedAndDeparsed("SELECT " + word + ", " + word + + "(a) FROM " + word + " AS x", true); + } + + @ParameterizedTest + @ValueSource( + strings = {"BYTES", "BINARY", "BOOLEAN", "UNSIGNED", "SIGNED", "CHARACTER", "CHAR"}) + void retainsDedicatedTypeTokensAndTypeParsing(String type) throws Exception { + assertNotEquals(CCJSqlParserConstants.DATA_TYPE, + CCJSqlParserUtil.newParser(type).getNextToken().kind); + assertSqlCanBeParsedAndDeparsed("CREATE TABLE t (c " + type + ")", true); + assertSqlCanBeParsedAndDeparsed("SELECT CAST(c AS " + type + ") FROM t", true); + } + + @ParameterizedTest + @ValueSource(strings = { + "ALTER SYSTEM ENABLE other RECOVERY", + "ALTER SYSTEM DISABLE DISTRIBUTED other", + "ALTER SYSTEM ENABLE DISTRIBUTED", + "SELECT a FROM t UNION ALL BY NAME other (a) SELECT a FROM u", + "SELECT a FROM t UNION ALL BY NAME MATCHING () SELECT a FROM u" + }) + void rejectsWrongContextualKeywords(String sql) { + assertThrows(JSQLParserException.class, () -> CCJSqlParserUtil.parse(sql)); + } +} diff --git a/src/test/java/net/sf/jsqlparser/parser/DialectPresetIsolationTest.java b/src/test/java/net/sf/jsqlparser/parser/DialectPresetIsolationTest.java new file mode 100644 index 0000000000..e1c1bd56dd --- /dev/null +++ b/src/test/java/net/sf/jsqlparser/parser/DialectPresetIsolationTest.java @@ -0,0 +1,48 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2019 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.parser; + +import static org.junit.jupiter.api.Assertions.*; +import net.sf.jsqlparser.parser.AbstractJSqlParser.Dialect; +import net.sf.jsqlparser.parser.feature.Feature; +import org.junit.jupiter.api.Test; + +class DialectPresetIsolationTest { + @Test + void callersCannotMutateSharedPresets() { + for (Dialect dialect : Dialect.values()) { + assertThrows(UnsupportedOperationException.class, + () -> dialect.getLexerFeatures().add(Feature.allowHashLineComments)); + assertThrows(UnsupportedOperationException.class, + () -> dialect.getLexerFeatures().clear()); + } + } + + @Test + void explicitParserOverridesAreLocalAndDoNotModifyPresetDefaults() { + CCJSqlParser first = CCJSqlParserUtil.newParser("SELECT 1").withDialect(Dialect.MYSQL) + .withHashLineComments(false); + CCJSqlParser second = CCJSqlParserUtil.newParser("SELECT 1").withDialect(Dialect.MYSQL); + assertFalse(first.getAsBoolean(Feature.allowHashLineComments)); + assertTrue(second.getAsBoolean(Feature.allowHashLineComments)); + assertTrue(Dialect.MYSQL.getLexerFeatures().contains(Feature.allowHashLineComments)); + } + + @Test + void preservesAdditivePresetApplicationAndExplicitOverrideOrder() { + CCJSqlParser parser = CCJSqlParserUtil.newParser("SELECT 1").withDialect(Dialect.SQLSERVER) + .withDialect(Dialect.POSTGRESQL); + assertTrue(parser.getAsBoolean(Feature.allowSquareBracketQuotation)); + parser.withSquareBracketQuotation(false); + assertFalse(parser.getAsBoolean(Feature.allowSquareBracketQuotation)); + assertFalse(CCJSqlParserUtil.newParser("SELECT 1").withDialect(Dialect.POSTGRESQL) + .getAsBoolean(Feature.allowSquareBracketQuotation)); + } +} diff --git a/src/test/java/net/sf/jsqlparser/parser/EmptyStatementInputTest.java b/src/test/java/net/sf/jsqlparser/parser/EmptyStatementInputTest.java new file mode 100644 index 0000000000..9d7f87eb72 --- /dev/null +++ b/src/test/java/net/sf/jsqlparser/parser/EmptyStatementInputTest.java @@ -0,0 +1,89 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.parser; + +import java.io.ByteArrayInputStream; +import java.io.StringReader; +import java.nio.charset.StandardCharsets; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import net.sf.jsqlparser.JSQLParserException; +import net.sf.jsqlparser.statement.Statements; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.NullAndEmptySource; +import org.junit.jupiter.params.provider.ValueSource; + +import static org.junit.jupiter.api.Assertions.*; + +class EmptyStatementInputTest { + @ParameterizedTest + @NullAndEmptySource + @ValueSource(strings = {" \t\r\n", "/* nothing */", "-- nothing\n"}) + void rejectsInputWithoutASingleStatement(String sql) { + assertThrows(JSQLParserException.class, () -> CCJSqlParserUtil.parse(sql)); + for (boolean allowComplex : new boolean[] {false, true}) { + assertThrows(JSQLParserException.class, () -> CCJSqlParserUtil.parse(sql, + parser -> parser.withAllowComplexParsing(allowComplex))); + } + } + + @ParameterizedTest + @NullAndEmptySource + @ValueSource(strings = {" \t\r\n", "/* nothing */", "-- nothing\n"}) + void returnsAnEmptyStatementList(String sql) throws Exception { + assertTrue(CCJSqlParserUtil.parseStatements(sql).isEmpty()); + for (boolean allowComplex : new boolean[] {false, true}) { + assertTrue(CCJSqlParserUtil.parseStatements(sql, + parser -> parser.withAllowComplexParsing(allowComplex)).isEmpty()); + } + } + + @ParameterizedTest + @NullAndEmptySource + @ValueSource(strings = {" \t\r\n", "/* nothing */", "-- nothing\n"}) + void preservesTheContractWithACallerExecutor(String sql) throws Exception { + ExecutorService executor = Executors.newSingleThreadExecutor(); + try { + for (boolean allowComplex : new boolean[] {false, true}) { + assertThrows(JSQLParserException.class, () -> CCJSqlParserUtil.parse(sql, executor, + parser -> parser.withAllowComplexParsing(allowComplex))); + assertTrue(CCJSqlParserUtil.parseStatements(sql, executor, + parser -> parser.withAllowComplexParsing(allowComplex)).isEmpty()); + } + assertFalse(executor.isShutdown()); + assertEquals("SELECT 1", CCJSqlParserUtil.parse("SELECT 1", executor, null).toString()); + assertEquals(2, + CCJSqlParserUtil.parseStatements("SELECT 1; SELECT 2", executor, null).size()); + } finally { + executor.shutdownNow(); + } + } + + @ParameterizedTest + @ValueSource(strings = {"", " \t\r\n", "/* nothing */", "-- nothing\n"}) + void agreesWithReaderAndStreamParsing(String sql) { + assertThrows(JSQLParserException.class, + () -> CCJSqlParserUtil.parse(new StringReader(sql))); + assertThrows(JSQLParserException.class, () -> CCJSqlParserUtil.parse( + new ByteArrayInputStream(sql.getBytes(StandardCharsets.UTF_8)))); + assertThrows(JSQLParserException.class, () -> CCJSqlParserUtil.parse( + new ByteArrayInputStream(sql.getBytes(StandardCharsets.UTF_8)), "UTF-8")); + } + + @Test + void emptyResultsRemainIndependentAndMutable() throws Exception { + Statements first = CCJSqlParserUtil.parseStatements(""); + first.add(CCJSqlParserUtil.parse("SELECT 1")); + assertTrue(CCJSqlParserUtil.parseStatements("").isEmpty()); + assertTrue(CCJSqlParserUtil.parseStatements((String) null).isEmpty()); + assertEquals(1, first.size()); + } +} diff --git a/src/test/java/net/sf/jsqlparser/parser/LargeObjectTypeTest.java b/src/test/java/net/sf/jsqlparser/parser/LargeObjectTypeTest.java new file mode 100644 index 0000000000..1c7a1a0ce9 --- /dev/null +++ b/src/test/java/net/sf/jsqlparser/parser/LargeObjectTypeTest.java @@ -0,0 +1,89 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.parser; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static net.sf.jsqlparser.test.TestUtils.assertSqlCanBeParsedAndDeparsed; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import net.sf.jsqlparser.JSQLParserException; +import net.sf.jsqlparser.expression.CastExpression; +import net.sf.jsqlparser.expression.ExpressionVisitorAdapter; +import net.sf.jsqlparser.expression.StringValue; +import net.sf.jsqlparser.statement.create.table.ColDataType; +import net.sf.jsqlparser.statement.select.PlainSelect; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +class LargeObjectTypeTest { + @ParameterizedTest + @ValueSource(strings = {"CHARACTER LARGE OBJECT", "CHAR LARGE OBJECT", "NCHAR LARGE OBJECT", + "NATIONAL CHARACTER LARGE OBJECT", "BINARY LARGE OBJECT", "character large object"}) + void sharesTypeNameAndLengthAcrossTypeFragmentsCastsAndColumns(String name) throws Exception { + ColDataType type = CCJSqlParserUtil.parseColDataType(name + "(3)"); + assertEquals(name, type.getDataType()); + assertEquals(3, type.getPrecision()); + assertEquals(Arrays.asList("3"), type.getArgumentsStringList()); + assertEquals(name, CCJSqlParserUtil.parseColDataType(name).getDataType()); + PlainSelect select = (PlainSelect) assertSqlCanBeParsedAndDeparsed( + "SELECT CAST('abc' AS " + name + "(3)) FROM t"); + CastExpression cast = (CastExpression) select.getSelectItem(0).getExpression(); + assertEquals(name, cast.getColDataType().getDataType()); + assertEquals(3, cast.getColDataType().getPrecision()); + assertSqlCanBeParsedAndDeparsed("CREATE TABLE t (payload " + name + "(3) NOT NULL)"); + assertSqlCanBeParsedAndDeparsed("ALTER TABLE t ADD payload " + name + "(3)"); + assertEquals(type.toString(), + CCJSqlParserUtil.parseColDataType(type.toString()).toString()); + } + + @Test + void parsesTheDownstreamConditionWithoutRewritingLiteralContents() throws Exception { + String sql = "(status IN (CAST('hi' AS CHARACTER LARGE OBJECT(2)), " + + "CAST('low' AS CHARACTER LARGE OBJECT(3))))"; + var condition = CCJSqlParserUtil.parseCondExpression(sql, false); + List literals = new ArrayList<>(); + condition.accept(new ExpressionVisitorAdapter() { + @Override + public Void visit(StringValue value, S context) { + literals.add(value.getValue()); + return null; + } + }, null); + assertEquals(Arrays.asList("hi", "low"), literals); + assertEquals(condition.toString(), + CCJSqlParserUtil.parseCondExpression(condition.toString(), false).toString()); + assertSqlCanBeParsedAndDeparsed("SELECT CAST('CHARACTER LARGE OBJECT' " + + "AS CHARACTER LARGE OBJECT(22)) FROM t"); + } + + @Test + void acceptsTokenWhitespaceWithoutChangingIdentifiersOrExistingTypes() throws Exception { + assertEquals("CHARACTER LARGE OBJECT", + CCJSqlParserUtil.parseColDataType("CHARACTER /* comment */ LARGE\nOBJECT") + .getDataType()); + for (String type : Arrays.asList("CHARACTER VARYING(3)", "NATIONAL CHARACTER(3)", + "CLOB", "BLOB", "\"CHARACTER LARGE OBJECT\"", "app.large")) { + ColDataType parsed = CCJSqlParserUtil.parseColDataType(type); + assertEquals(parsed.toString(), + CCJSqlParserUtil.parseColDataType(parsed.toString()).toString()); + } + assertSqlCanBeParsedAndDeparsed("SELECT large, object FROM t"); + } + + @ParameterizedTest + @ValueSource(strings = {"CHARACTER LARGE", "CHARACTER LARGE OTHER", "INT LARGE OBJECT"}) + void rejectsIncompleteOrNonCharacterTypeNames(String type) { + assertThrows(JSQLParserException.class, () -> CCJSqlParserUtil.parseColDataType(type)); + } +} diff --git a/src/test/java/net/sf/jsqlparser/parser/LongChainPredicateTest.java b/src/test/java/net/sf/jsqlparser/parser/LongChainPredicateTest.java new file mode 100644 index 0000000000..7c92090a8b --- /dev/null +++ b/src/test/java/net/sf/jsqlparser/parser/LongChainPredicateTest.java @@ -0,0 +1,103 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.parser; + +import static net.sf.jsqlparser.test.TestUtils.assertSqlCanBeParsedAndDeparsed; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import net.sf.jsqlparser.expression.Function; +import net.sf.jsqlparser.schema.Column; +import net.sf.jsqlparser.statement.select.AllTableColumns; +import net.sf.jsqlparser.statement.select.PlainSelect; +import net.sf.jsqlparser.statement.select.Select; +import org.junit.jupiter.api.Test; + +/** + * Guards the threshold-gated chain-walk memoization in isFunctionAhead() / + * isAllTableColumnsAhead(): chains on both sides of CHAIN_CACHE_THRESHOLD and inside the memoized + * path must keep the exact same AST shapes as the plain walk. The speed-up itself is + * constant-factor only and is carried by the measured numbers, not by a CI timing assertion. + */ +public class LongChainPredicateTest { + + private static String chain(int innerDelimiters, String last) { + // innerDelimiters = dots between s0..sN (chain length before the last part) + StringBuilder sb = new StringBuilder("s0"); + for (int i = 1; i <= innerDelimiters; i++) { + sb.append(".s").append(i); + } + return sb.append(".").append(last).toString(); + } + + private Object firstExpression(String sql) throws Exception { + Select select = (Select) CCJSqlParserUtil.parse(sql); + return ((PlainSelect) select).getSelectItems().get(0).getExpression(); + } + + @Test + void functionOnShortChainFastPath() throws Exception { + // total 8 delimiters: the walk stays inside the plain non-cached path + String sql = "SELECT " + chain(7, "f(1)") + " FROM t"; + assertSqlCanBeParsedAndDeparsed(sql); + assertTrue(firstExpression(sql) instanceof Function, + "8-delimiter chain ending in ( must stay a Function"); + } + + @Test + void functionOnLongChainMemoPath() throws Exception { + // total 9 delimiters: the walk aborts past the threshold and goes through the cache + String sql = "SELECT " + chain(8, "f(1)") + " FROM t"; + assertSqlCanBeParsedAndDeparsed(sql); + assertTrue(firstExpression(sql) instanceof Function, + "9-delimiter chain ending in ( must stay a Function on the memoized path"); + } + + @Test + void columnOnShortChainFastPath() throws Exception { + String sql = "SELECT " + chain(7, "col") + " FROM t"; + assertSqlCanBeParsedAndDeparsed(sql); + assertTrue(firstExpression(sql) instanceof Column); + assertFalse(firstExpression(sql) instanceof Function); + } + + @Test + void columnOnLongChainMemoPath() throws Exception { + String sql = "SELECT " + chain(8, "col") + " FROM t"; + assertSqlCanBeParsedAndDeparsed(sql); + assertTrue(firstExpression(sql) instanceof Column); + assertFalse(firstExpression(sql) instanceof Function); + } + + @Test + void columnOnVeryLongChainMemoPath() throws Exception { + String sql = "SELECT " + chain(40, "col") + " FROM t"; + assertSqlCanBeParsedAndDeparsed(sql); + assertTrue(firstExpression(sql) instanceof Column); + } + + @Test + void allTableColumnsAcrossThreshold() throws Exception { + String shortChain = "SELECT " + chain(7, "*") + " FROM t"; + assertSqlCanBeParsedAndDeparsed(shortChain); + assertTrue(firstExpression(shortChain) instanceof AllTableColumns); + + String longChain = "SELECT " + chain(8, "*") + " FROM t"; + assertSqlCanBeParsedAndDeparsed(longChain); + assertTrue(firstExpression(longChain) instanceof AllTableColumns); + } + + @Test + void oracleOuterJoinColumnPlusOnMemoPath() throws Exception { + // the column(+) exclusion must survive the memoized walk + assertSqlCanBeParsedAndDeparsed( + "SELECT * FROM a, b WHERE " + chain(8, "x(+)") + " = b.x"); + } +} diff --git a/src/test/java/net/sf/jsqlparser/parser/OdbcTimestampIntervalTest.java b/src/test/java/net/sf/jsqlparser/parser/OdbcTimestampIntervalTest.java new file mode 100644 index 0000000000..381bbccdac --- /dev/null +++ b/src/test/java/net/sf/jsqlparser/parser/OdbcTimestampIntervalTest.java @@ -0,0 +1,88 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.parser; + +import java.util.ArrayList; +import java.util.List; +import net.sf.jsqlparser.expression.DateUnitExpression; +import net.sf.jsqlparser.expression.DateUnitExpression.DateUnit; +import net.sf.jsqlparser.expression.ExpressionVisitorAdapter; +import net.sf.jsqlparser.expression.Function; +import net.sf.jsqlparser.schema.Column; +import net.sf.jsqlparser.statement.select.PlainSelect; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import static net.sf.jsqlparser.test.TestUtils.assertSqlCanBeParsedAndDeparsed; +import static org.junit.jupiter.api.Assertions.*; + +class OdbcTimestampIntervalTest { + @ParameterizedTest + @ValueSource(strings = {"SQL_TSI_FRAC_SECOND", "SQL_TSI_SECOND", "SQL_TSI_MINUTE", + "SQL_TSI_HOUR", "SQL_TSI_DAY", "SQL_TSI_WEEK", "SQL_TSI_MONTH", + "SQL_TSI_QUARTER", "SQL_TSI_YEAR"}) + void recognizesStandardIntervalsForBothFunctions(String unit) throws Exception { + for (String name : List.of("TIMESTAMPADD", "TIMESTAMPDIFF")) { + String sql = "SELECT {fn " + name + "(" + unit + ", 2, travel_date)} FROM t"; + for (boolean complex : new boolean[] {false, true}) { + PlainSelect select = (PlainSelect) assertSqlCanBeParsedAndDeparsed(sql, true, + parser -> parser.withAllowComplexParsing(complex)); + Function function = (Function) select.getSelectItem(0).getExpression(); + DateUnitExpression interval = assertInstanceOf(DateUnitExpression.class, + function.getParameters().get(0)); + assertEquals(DateUnit.from(unit), interval.getType()); + assertEquals(unit, interval.toString()); + assertNotNull(interval.getASTNode()); + assertEquals(select.toString(), + CCJSqlParserUtil.parse(select.toString()).toString()); + List columns = new ArrayList<>(); + function.accept(new ExpressionVisitorAdapter() { + @Override + public Void visit(Column column, S context) { + columns.add(column.getColumnName()); + return null; + } + }, null); + assertEquals(List.of("travel_date"), columns); + } + } + } + + @ParameterizedTest + @ValueSource(strings = { + "TIMESTAMPADD(SQL_TSI_YEAR, 2, travel_date)", + "{fn other(SQL_TSI_YEAR, 2, travel_date)}", + "{fn schema.TIMESTAMPADD(SQL_TSI_YEAR, 2, travel_date)}", + "{fn TIMESTAMPADD(t.SQL_TSI_YEAR, 2, travel_date)}", + "{fn TIMESTAMPADD(\"SQL_TSI_YEAR\", 2, travel_date)}", + "{fn TIMESTAMPADD(SQL_TSI_UNKNOWN, 2, travel_date)}", + "{fn TIMESTAMPADD(SQL_TSI_YEAR, travel_date)}" + }) + void preservesIdentifiersOutsideTheOdbcIntervalPosition(String expression) throws Exception { + PlainSelect select = + (PlainSelect) CCJSqlParserUtil.parse("SELECT " + expression + " FROM t"); + Function function = (Function) select.getSelectItem(0).getExpression(); + assertInstanceOf(Column.class, function.getParameters().get(0)); + } + + @Test + void handlesCaseAndNestedEscapesWithoutChangingOtherArguments() throws Exception { + String sql = "SELECT {fn timestampdiff(sql_tsi_year, {fn CURDATE()}, SQL_TSI_YEAR)} FROM t"; + PlainSelect select = (PlainSelect) assertSqlCanBeParsedAndDeparsed(sql, true, null); + Function function = (Function) select.getSelectItem(0).getExpression(); + assertInstanceOf(DateUnitExpression.class, function.getParameters().get(0)); + assertInstanceOf(Function.class, function.getParameters().get(1)); + assertInstanceOf(Column.class, function.getParameters().get(2)); + assertNull(DateUnit.fromOdbcInterval(null)); + assertNull(DateUnit.fromOdbcInterval("YEAR")); + assertNull(DateUnit.fromOdbcInterval("SQL_TSI_UNKNOWN")); + } +} diff --git a/src/test/java/net/sf/jsqlparser/parser/ParseStatementsFailureTest.java b/src/test/java/net/sf/jsqlparser/parser/ParseStatementsFailureTest.java new file mode 100644 index 0000000000..376d2ad03b --- /dev/null +++ b/src/test/java/net/sf/jsqlparser/parser/ParseStatementsFailureTest.java @@ -0,0 +1,81 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.parser; + +import net.sf.jsqlparser.JSQLParserException; +import net.sf.jsqlparser.statement.UnsupportedStatement; +import org.junit.jupiter.api.Test; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +import static org.junit.jupiter.api.Assertions.*; + +class ParseStatementsFailureTest { + @Test + void reportsFailureWhenComplexParsingIsDisabled() { + JSQLParserException exception = assertThrows(JSQLParserException.class, + () -> CCJSqlParserUtil.parseStatements("SELECT FROM", + parser -> parser.withAllowComplexParsing(false))); + assertNotNull(exception.getCause()); + } + + @Test + void reportsFailureWhenNestingPreventsRetry() { + assertThrows(JSQLParserException.class, + () -> CCJSqlParserUtil.parseStatements("SELECT (( FROM", + parser -> parser.withAllowComplexParsing(true).withAllowedNestingDepth(0))); + } + + @Test + void leavesCallerExecutorUsableAfterFailure() throws Exception { + ExecutorService executor = Executors.newSingleThreadExecutor(); + try { + assertThrows(JSQLParserException.class, + () -> CCJSqlParserUtil.parseStatements("SELECT FROM", executor, + parser -> parser.withAllowComplexParsing(false))); + assertFalse(executor.isShutdown()); + assertEquals(2, CCJSqlParserUtil.parseStatements("SELECT 1; SELECT 2", executor, + parser -> parser.withAllowComplexParsing(false)).size()); + } finally { + executor.shutdownNow(); + } + } + + @Test + void reportsTimeoutWhenComplexParsingIsDisabled() { + ExecutorService executor = Executors.newSingleThreadExecutor(); + CountDownLatch release = new CountDownLatch(1); + executor.submit(() -> release.await(30, TimeUnit.SECONDS)); + try { + JSQLParserException exception = assertThrows(JSQLParserException.class, + () -> CCJSqlParserUtil.parseStatements("SELECT 1", executor, + parser -> parser.withAllowComplexParsing(false).withTimeOut(50))); + assertInstanceOf(TimeoutException.class, exception.getCause()); + } finally { + release.countDown(); + executor.shutdownNow(); + } + } + + @Test + void returnsEmptyListsAndPreservesUnsupportedStatements() throws Exception { + assertTrue(CCJSqlParserUtil.parseStatements((String) null).isEmpty()); + assertTrue(CCJSqlParserUtil.parseStatements("").isEmpty()); + assertInstanceOf(UnsupportedStatement.class, + CCJSqlParserUtil.parseStatements("SELECT 1; WHATEVER !", + parser -> parser.withAllowComplexParsing(false) + .withUnsupportedStatements(true)) + .get(1)); + } +} diff --git a/src/test/java/net/sf/jsqlparser/parser/ParserExecutionTest.java b/src/test/java/net/sf/jsqlparser/parser/ParserExecutionTest.java new file mode 100644 index 0000000000..09ad06b042 --- /dev/null +++ b/src/test/java/net/sf/jsqlparser/parser/ParserExecutionTest.java @@ -0,0 +1,142 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2019 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.parser; + +import static org.junit.jupiter.api.Assertions.*; +import java.util.List; +import java.util.concurrent.AbstractExecutorService; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.Executors; +import net.sf.jsqlparser.JSQLParserException; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +class ParserExecutionTest { + private static class QueuedExecutor extends AbstractExecutorService { + Future task; + int submissions; + boolean shutdown; + + @Override + public void execute(Runnable command) { + task = (Future) command; + submissions++; + } + + @Override + public void shutdown() { + shutdown = true; + } + + @Override + public List shutdownNow() { + shutdown(); + return List.of(); + } + + @Override + public boolean isShutdown() { + return shutdown; + } + + @Override + public boolean isTerminated() { + return shutdown; + } + + @Override + public boolean awaitTermination(long timeout, TimeUnit unit) { + return shutdown; + } + } + + @ParameterizedTest + @ValueSource(booleans = {false, true}) + void cancelsTheSubmittedTaskAndPreservesCallerInterruption(boolean multiple) { + QueuedExecutor executor = new QueuedExecutor(); + CCJSqlParser parser = CCJSqlParserUtil.newParser("SELECT 1"); + Thread.currentThread().interrupt(); + try { + JSQLParserException error = assertThrows(JSQLParserException.class, () -> { + if (multiple) { + CCJSqlParserUtil.parseStatements(parser, executor); + } else { + CCJSqlParserUtil.parseStatement(parser, executor); + } + }); + assertInstanceOf(InterruptedException.class, error.getCause()); + assertTrue(Thread.currentThread().isInterrupted()); + assertTrue(parser.interrupted); + assertTrue(executor.task.isCancelled()); + assertFalse(executor.isShutdown()); + } finally { + Thread.interrupted(); + } + } + + @ParameterizedTest + @ValueSource(booleans = {false, true}) + void doesNotRetryInterruptedConvenienceCalls(boolean multiple) { + QueuedExecutor executor = new QueuedExecutor(); + Thread.currentThread().interrupt(); + try { + JSQLParserException error = assertThrows(JSQLParserException.class, () -> { + if (multiple) { + CCJSqlParserUtil.parseStatements("SELECT 1", executor, null); + } else { + CCJSqlParserUtil.parse("SELECT 1", executor, null); + } + }); + assertInstanceOf(InterruptedException.class, error.getCause()); + assertEquals(1, executor.submissions); + assertTrue(executor.task.isCancelled()); + assertTrue(Thread.currentThread().isInterrupted()); + } finally { + Thread.interrupted(); + } + } + + @ParameterizedTest + @ValueSource(booleans = {false, true}) + void timeoutCancelsOneTaskWithoutRetryingOrInterruptingTheCaller(boolean multiple) { + QueuedExecutor executor = new QueuedExecutor(); + JSQLParserException error = assertThrows(JSQLParserException.class, () -> { + if (multiple) { + CCJSqlParserUtil.parseStatements("SELECT 1", executor, + parser -> parser.withTimeOut(0)); + } else { + CCJSqlParserUtil.parse("SELECT 1", executor, parser -> parser.withTimeOut(0)); + } + }); + assertInstanceOf(TimeoutException.class, error.getCause()); + assertEquals(1, executor.submissions); + assertTrue(executor.task.isCancelled()); + assertFalse(Thread.currentThread().isInterrupted()); + assertFalse(executor.isShutdown()); + } + + @Test + void successfulAndInvalidParsingKeepTheCallerExecutorUsable() throws Exception { + var executor = Executors.newSingleThreadExecutor(); + try { + assertEquals("SELECT 1", CCJSqlParserUtil.parse("SELECT 1", executor, null).toString()); + assertThrows(JSQLParserException.class, + () -> CCJSqlParserUtil.parse("SELECT FROM", executor, null)); + assertEquals("SELECT 2;\n", + CCJSqlParserUtil.parseStatements("SELECT 2", executor, null).toString()); + assertFalse(executor.isShutdown()); + } finally { + executor.shutdownNow(); + } + } +} diff --git a/src/test/java/net/sf/jsqlparser/parser/PostgreSqlHashOperatorTest.java b/src/test/java/net/sf/jsqlparser/parser/PostgreSqlHashOperatorTest.java new file mode 100644 index 0000000000..5644161928 --- /dev/null +++ b/src/test/java/net/sf/jsqlparser/parser/PostgreSqlHashOperatorTest.java @@ -0,0 +1,99 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.parser; + +import static org.junit.jupiter.api.Assertions.*; + +import net.sf.jsqlparser.expression.JsonExpression; +import net.sf.jsqlparser.expression.operators.arithmetic.BitwiseRightShift; +import net.sf.jsqlparser.expression.operators.relational.Intersects; +import net.sf.jsqlparser.expression.operators.relational.NotEqualsTo; +import net.sf.jsqlparser.parser.AbstractJSqlParser.Dialect; +import net.sf.jsqlparser.schema.Column; +import net.sf.jsqlparser.statement.select.PlainSelect; +import net.sf.jsqlparser.util.deparser.StatementDeParser; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +class PostgreSqlHashOperatorTest { + private static PlainSelect parse(String sql) throws Exception { + return (PlainSelect) CCJSqlParserUtil.parse(sql, p -> p.withDialect(Dialect.POSTGRESQL)); + } + + @ParameterizedTest + @ValueSource(strings = {"js#>>'{a,b,1}'", "js #>> '{a,b,1}'", "js#>> '{a,b,1}'", + "t.js#>>'{a,b,1}'", "\"js#\"#>>'{a,b,1}'", "js/*comment*/#>>'{a,b,1}'"}) + void preservesJsonPrecedenceWithOrWithoutSpacesIssue2163(String expression) throws Exception { + PlainSelect select = parse("SELECT * FROM t WHERE " + expression + " <> 'bar'"); + NotEqualsTo condition = (NotEqualsTo) select.getWhere(); + assertInstanceOf(JsonExpression.class, condition.getLeftExpression()); + assertTrue(select.toString().contains(" #>> ")); + StringBuilder output = new StringBuilder(); + select.accept(new StatementDeParser(output)); + assertEquals(select.toString(), output.toString()); + assertEquals(select.toString(), parse(output.toString()).toString()); + } + + @ParameterizedTest + @ValueSource(strings = {"js#>'{a}'", "js#>'{a}'#>>'{b}'"}) + void recognizesOtherHashJsonOperators(String expression) throws Exception { + PlainSelect select = parse("SELECT " + expression + " FROM t"); + assertInstanceOf(JsonExpression.class, select.getSelectItem(0).getExpression()); + assertEquals(select.toString(), parse(select.toString()).toString()); + } + + @Test + void splitsHashXorAndPreservesQuotedIdentifiersAndLiterals() throws Exception { + PlainSelect select = parse("SELECT a#b, a#2, \"a#b\", '#>>', $$a#b$$ FROM t"); + assertInstanceOf(Intersects.class, select.getSelectItem(0).getExpression()); + assertInstanceOf(Intersects.class, select.getSelectItem(1).getExpression()); + assertEquals("\"a#b\"", ((Column) select.getSelectItem(2).getExpression()).getColumnName()); + assertEquals(select.toString(), parse(select.toString()).toString()); + } + + @Test + void leavesLegacyAndSqlServerNamesAndMysqlCommentsIntact() throws Exception { + for (Dialect dialect : Dialect.values()) { + if (dialect != Dialect.POSTGRESQL && dialect != Dialect.MYSQL + && dialect != Dialect.MARIADB && dialect != Dialect.BIGQUERY) { + PlainSelect select = (PlainSelect) CCJSqlParserUtil.parse("SELECT a#b FROM #temp", + p -> p.withDialect(dialect)); + assertEquals("a#b", + ((Column) select.getSelectItem(0).getExpression()).getColumnName()); + } + } + PlainSelect legacy = (PlainSelect) CCJSqlParserUtil.parse("SELECT js#>>'{a}' FROM t"); + assertInstanceOf(BitwiseRightShift.class, legacy.getSelectItem(0).getExpression()); + PlainSelect mysql = (PlainSelect) CCJSqlParserUtil.parse("SELECT a#comment\nFROM t", + p -> p.withDialect(Dialect.MYSQL)); + assertEquals("SELECT a FROM t", mysql.toString()); + } + + @Test + void keepsTokenOffsetsAfterSplittingAndAcrossStatements() throws Exception { + CCJSqlParser parser = CCJSqlParserUtil.newParser("js#>>'{}'") + .withDialect(Dialect.POSTGRESQL); + Token name = parser.getNextToken(); + Token operator = parser.getNextToken(); + assertEquals("js", name.image); + assertEquals(1, name.beginColumn); + assertEquals(2, name.endColumn); + assertEquals(1, name.absoluteBegin); + assertEquals(3, name.absoluteEnd); + assertEquals("#>>", operator.image); + assertEquals(3, operator.beginColumn); + assertEquals(5, operator.endColumn); + assertEquals(3, operator.absoluteBegin); + assertEquals(6, operator.absoluteEnd); + assertEquals(2, CCJSqlParserUtil.parseStatements("SELECT js#>>'{}' FROM t; SELECT 1;", + p -> p.withDialect(Dialect.POSTGRESQL)).size()); + } +} diff --git a/src/test/java/net/sf/jsqlparser/schema/ServerTest.java b/src/test/java/net/sf/jsqlparser/schema/ServerTest.java index 216248be90..ed24ea52d4 100644 --- a/src/test/java/net/sf/jsqlparser/schema/ServerTest.java +++ b/src/test/java/net/sf/jsqlparser/schema/ServerTest.java @@ -10,10 +10,27 @@ package net.sf.jsqlparser.schema; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTimeoutPreemptively; + +import java.time.Duration; import org.junit.jupiter.api.Test; public class ServerTest { + @Test + public void testCraftedServerNameDoesNotBacktrack() throws Exception { + final StringBuilder sb = new StringBuilder("["); + for (int i = 0; i < 40000; i++) { + sb.append("a\\"); + } + final String crafted = sb.toString(); + + // an unterminated bracketed name full of backslashes used to backtrack quadratically + final Server server = assertTimeoutPreemptively(Duration.ofSeconds(2), + () -> new Server(crafted)); + assertEquals(crafted, server.getFullyQualifiedName()); + } + @Test public void testServerNameParsing() throws Exception { final String serverName = "LOCALHOST"; diff --git a/src/test/java/net/sf/jsqlparser/schema/TableVariableTest.java b/src/test/java/net/sf/jsqlparser/schema/TableVariableTest.java new file mode 100644 index 0000000000..66d6a4a7de --- /dev/null +++ b/src/test/java/net/sf/jsqlparser/schema/TableVariableTest.java @@ -0,0 +1,167 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.schema; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.util.ArrayList; +import java.util.List; +import net.sf.jsqlparser.JSQLParserException; +import net.sf.jsqlparser.expression.UserVariable; +import net.sf.jsqlparser.parser.CCJSqlParserUtil; +import net.sf.jsqlparser.parser.feature.Feature; +import net.sf.jsqlparser.statement.Statement; +import net.sf.jsqlparser.statement.Statements; +import net.sf.jsqlparser.statement.UnsupportedStatement; +import net.sf.jsqlparser.statement.delete.Delete; +import net.sf.jsqlparser.statement.insert.Insert; +import net.sf.jsqlparser.statement.select.PlainSelect; +import net.sf.jsqlparser.statement.update.Update; +import net.sf.jsqlparser.util.TablesNamesFinder; +import net.sf.jsqlparser.util.deparser.StatementDeParser; +import net.sf.jsqlparser.util.validation.Validation; +import net.sf.jsqlparser.util.validation.feature.DatabaseType; +import net.sf.jsqlparser.util.validation.feature.FeaturesAllowed; +import net.sf.jsqlparser.util.validation.metadata.DatabaseMetaDataValidation; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +class TableVariableTest { + private Statement roundtrip(String sql) throws JSQLParserException { + Statement statement = CCJSqlParserUtil.parse(sql, p -> p.withUnsupportedStatements(false)); + assertThat(statement).isNotInstanceOf(UnsupportedStatement.class); + StringBuilder text = new StringBuilder(); + statement.accept(new StatementDeParser(text), null); + assertThat(text.toString()).isEqualTo(statement.toString()); + assertThat(CCJSqlParserUtil.parse(text.toString()).toString()) + .isEqualTo(statement.toString()); + return statement; + } + + @ParameterizedTest + @ValueSource(strings = {"SELECT columnName FROM @table", "SELECT v.* FROM @table AS v", + "SELECT v.id FROM @table v JOIN dbo.source s ON v.id = s.id", + "SELECT * FROM (SELECT id FROM @table) t", + "WITH t AS (SELECT id FROM @table) SELECT * FROM t", + "INSERT INTO @table VALUES (1)", "INSERT INTO @table (id) SELECT id FROM source", + "UPDATE @table SET id = 2", "UPDATE t SET id = 3 FROM @table AS t", + "DELETE FROM @table", "DELETE @table WHERE id = 1", + "DELETE t FROM @table AS t", + "MERGE INTO @table AS t USING source AS s ON t.id = s.id " + + "WHEN MATCHED THEN UPDATE SET t.id = s.id"}) + void supportsTableVariablesInQueriesAndDml(String sql) throws JSQLParserException { + roundtrip(sql); + assertThat(Validation.validate(List.of(new FeaturesAllowed(Feature.values())), sql)) + .isEmpty(); + } + + @Test + void retainsTypedReferencesInExistingDmlModels() throws JSQLParserException { + Table from = (Table) ((PlainSelect) roundtrip("SELECT id FROM @t AS v")).getFromItem(); + Table inserted = ((Insert) roundtrip("INSERT INTO @t VALUES (1)")).getTable(); + Table updated = ((Update) roundtrip("UPDATE @t SET id = 2")).getTable(); + Table deleted = ((Delete) roundtrip("DELETE FROM @t")).getTable(); + for (Table table : List.of(from, inserted, updated, deleted)) { + assertThat(table.isTableVariable()).isTrue(); + assertThat(table.getName()).isEqualTo("@t"); + assertThat(table.getSchemaName()).isNull(); + assertThat(table.getASTNode()).isNotNull(); + Table copy = table.clone(); + assertThat(copy.isTableVariable()).isTrue(); + assertThat(copy.getName()).isEqualTo("@t"); + table.setUnsetCatalogAndSchema("catalog", "schema"); + assertThat(table.getFullyQualifiedName()).isEqualTo("@t"); + } + assertThat(from.getAlias().getName()).isEqualTo("v"); + } + + @Test + void separatesLocalSourcesFromCatalogTables() throws JSQLParserException { + String sql = "SELECT v.id FROM @t v JOIN dbo.source s ON v.id = s.id"; + assertThat(TablesNamesFinder.findTables(sql)).containsExactly("dbo.source"); + assertThat(TablesNamesFinder.findTablesOrOtherSources(sql)) + .containsExactlyInAnyOrder("@t", "dbo.source"); + assertThat(TablesNamesFinder.findTables("INSERT INTO @t SELECT id FROM source")) + .containsExactly("source"); + assertThat(TablesNamesFinder.findTables("UPDATE @t SET id = 2")).isEmpty(); + assertThat(TablesNamesFinder.findTables("DELETE FROM @t")).isEmpty(); + } + + @Test + void skipsOnlyLocalTableMetadataLookups() { + List checkedNames = new ArrayList<>(); + DatabaseMetaDataValidation metadata = named -> { + checkedNames.add(named.getFqn()); + return true; + }; + assertThat(Validation.validate(List.of(FeaturesAllowed.SELECT, metadata), + "SELECT 1 FROM @t CROSS JOIN dbo.source")).isEmpty(); + assertThat(checkedNames).containsExactly("dbo.source"); + checkedNames.clear(); + assertThat(Validation.validate(List.of(new FeaturesAllowed(Feature.values()), metadata), + "INSERT INTO @t VALUES (1)", "DELETE FROM @t")).isEmpty(); + assertThat(checkedNames).isEmpty(); + } + + @Test + void validatesTheDialectFeature() { + assertThat(Validation.validate(List.of(DatabaseType.SQLSERVER), "SELECT id FROM @t")) + .isEmpty(); + assertThat(Validation.validate(List.of(FeaturesAllowed.SELECT), "SELECT id FROM @t")) + .isEmpty(); + assertThat(Validation.validate(List.of(new FeaturesAllowed(Feature.select)), + "SELECT id FROM @t").toString()).contains("tableVariable not allowed"); + assertThat(Validation.validate(List.of(DatabaseType.POSTGRESQL), "SELECT id FROM @t") + .toString()).contains("tableVariable not supported"); + } + + @Test + void preservesScalarVariablesDeclarationsAndFollowingStatements() throws JSQLParserException { + PlainSelect select = (PlainSelect) roundtrip("SELECT @value FROM @t"); + assertThat(select.getSelectItem(0).getExpression()).isInstanceOf(UserVariable.class); + Statements statements = CCJSqlParserUtil.parseStatements( + "DECLARE @t TABLE (id INT); INSERT INTO @t VALUES (1); " + + "UPDATE @t SET id = 2; SELECT id FROM @t; DELETE FROM @t; SELECT 42;"); + assertThat(statements).hasSize(6); + assertThat(statements.get(5).toString()).isEqualTo("SELECT 42"); + assertThat(CCJSqlParserUtil.parseStatements(statements.toString())).hasSize(6); + } + + @ParameterizedTest + @ValueSource(strings = {"SELECT * FROM dbo.t", "SELECT * FROM #temp", + "SELECT * FROM \"@catalog_table\"", "UPDATE t USE INDEX (idx) SET id = 1", + "CREATE INDEX idx ON t (id)", "SELECT id INTO copy FROM source"}) + void preservesCatalogTableGrammar(String sql) throws JSQLParserException { + Statement statement = roundtrip(sql); + if (statement instanceof PlainSelect) { + assertThat(((Table) ((PlainSelect) statement).getFromItem()).isTableVariable()) + .isFalse(); + } + } + + @ParameterizedTest + @ValueSource(strings = {"SELECT * FROM @@global", "SELECT * FROM dbo.@t", + "SELECT id INTO @t FROM source", + "CREATE INDEX idx ON @t (id)", "ALTER TABLE @t ADD value INT", + "DROP TABLE @t", "TRUNCATE TABLE @t", "SELECT @t.* FROM @t"}) + void rejectsCatalogOnlyAndMalformedReferences(String sql) { + assertThrows(JSQLParserException.class, + () -> CCJSqlParserUtil.parse(sql, p -> p.withUnsupportedStatements(false))); + } + + @Test + void doesNotAddCreateTableVariableSyntax() throws JSQLParserException { + // CREATE's existing fallback returns UnsupportedStatement even in strict mode. + assertThat(CCJSqlParserUtil.parse("CREATE TABLE @t (id INT)", + p -> p.withUnsupportedStatements(false))).isInstanceOf(UnsupportedStatement.class); + } +} diff --git a/src/test/java/net/sf/jsqlparser/statement/AdapterRegressionTest.java b/src/test/java/net/sf/jsqlparser/statement/AdapterRegressionTest.java new file mode 100644 index 0000000000..3d6c93c3d3 --- /dev/null +++ b/src/test/java/net/sf/jsqlparser/statement/AdapterRegressionTest.java @@ -0,0 +1,141 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.statement; + +import net.sf.jsqlparser.JSQLParserException; +import net.sf.jsqlparser.expression.ExpressionVisitorAdapter; +import net.sf.jsqlparser.expression.Function; +import net.sf.jsqlparser.parser.CCJSqlParserUtil; +import net.sf.jsqlparser.statement.select.Select; +import net.sf.jsqlparser.statement.select.SelectVisitorAdapter; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatCode; + +/** + * Regressions for two adapter traversal defects. + */ +class AdapterRegressionTest { + + /** + * {@code SelectVisitorAdapter#visit(WithItem)} used to call {@code withItem.getSelect()}, an + * unchecked cast to {@code ParenthesedSelect}. Any data-modifying CTE reached through the + * select path threw {@link ClassCastException} - and the select path is taken for every + * sub-select, because {@code ExpressionVisitorAdapter#visit(Select)} loops the with-items into + * the select visitor. + */ + @Test + @DisplayName("a data-modifying CTE inside a sub-select no longer throws ClassCastException") + void dataModifyingCteOnTheSelectPath() throws JSQLParserException { + Statement statement = CCJSqlParserUtil.parse("SELECT * FROM u WHERE id IN (" + + "WITH c AS (DELETE FROM t RETURNING id) SELECT id FROM c)"); + + assertThatCode(() -> statement.accept(new StatementVisitorAdapter<>(), null)) + .doesNotThrowAnyException(); + } + + /** + * The CTE body must actually be traversed, not merely survived. + * + *

+ * This is the test that catches both defects, and it is the one that exposed the + * larger of the two. Reaching {@code visit(Delete)} here requires: + * + *

    + *
  1. the expression chain to be connected at all. {@code StatementVisitorAdapter()} used to + * build its {@code ExpressionVisitorAdapter} with the no-arg constructor, which leaves + * {@code selectVisitor} null - and {@code ExpressionVisitorAdapter#visit(Select)} returns + * immediately when it is. Every sub-select inside an expression (IN, EXISTS, scalar) was + * silently dropped, so no subclass could observe anything below one. Its + * {@code FromItemVisitorAdapter} had the mirror problem: the no-arg constructor builds a + * throwaway select/expression chain of its own.
  2. + *
  3. {@code SelectVisitorAdapter#visit(WithItem)} to dispatch the CTE body through + * {@code getParenthesedStatement()} rather than casting it to ParenthesedSelect, and to have a + * statement visitor to hand a DELETE body to.
  4. + *
+ * + *

+ * Defect (1) is also why (2) never surfaced as a ClassCastException in practice: the traversal + * never got that far. Fixing the wiring is what makes the cast reachable, so the two fixes have + * to land together. + * + *

+ * Note this can only be asserted through its effects - {@code StatementVisitorAdapter} exposes + * no getters for its sub-visitors, so the wiring cannot be inspected directly. Adding them + * would be a reasonable follow-up. + */ + @Test + @DisplayName("the CTE body is traversed, not skipped") + void dataModifyingCteBodyIsVisited() throws JSQLParserException { + List visited = new ArrayList<>(); + + StatementVisitorAdapter visitor = new StatementVisitorAdapter() { + @Override + public Void visit(net.sf.jsqlparser.statement.delete.Delete delete, S context) { + visited.add("delete"); + return super.visit(delete, context); + } + }; + + CCJSqlParserUtil + .parse("SELECT * FROM u WHERE id IN (" + + "WITH c AS (DELETE FROM t RETURNING id) SELECT id FROM c)") + .accept(visitor, null); + + assertThat(visited).containsExactly("delete"); + } + + /** + * A bare SelectVisitorAdapter has no statement visitor, so it cannot accept a DELETE body. It + * must skip it rather than fail - the old cast made this case fatal. + */ + @Test + @DisplayName("a standalone SelectVisitorAdapter skips a DML CTE instead of failing") + void standaloneSelectVisitorSkipsDmlCte() throws JSQLParserException { + Select select = (Select) CCJSqlParserUtil + .parse("WITH c AS (DELETE FROM t RETURNING id) SELECT id FROM c"); + + SelectVisitorAdapter selects = new SelectVisitorAdapter<>(); + assertThat(selects.getStatementVisitor()).isNull(); + assertThatCode(() -> select.accept(selects, null)).doesNotThrowAnyException(); + } + + /** + * {@code StatementVisitorAdapter#visit(Delete)} never called {@code visitReturningClause}, + * unlike its Insert and Update counterparts, so expressions in the RETURNING list were never + * handed to the expression visitor. + */ + @Test + @DisplayName("DELETE .. RETURNING expressions are traversed") + void deleteReturningExpressionsAreVisited() throws JSQLParserException { + List functions = new ArrayList<>(); + + ExpressionVisitorAdapter expressions = new ExpressionVisitorAdapter() { + @Override + public Void visit(Function function, S context) { + functions.add(function.getName().toLowerCase()); + return super.visit(function, context); + } + }; + + StatementVisitorAdapter visitor = + new StatementVisitorAdapter<>(new SelectVisitorAdapter<>(expressions)); + + CCJSqlParserUtil.parse("DELETE FROM t WHERE a = 1 RETURNING upper(name)") + .accept(visitor, null); + + assertThat(functions).contains("upper"); + } +} diff --git a/src/test/java/net/sf/jsqlparser/statement/DeclareStatementTest.java b/src/test/java/net/sf/jsqlparser/statement/DeclareStatementTest.java index c8b5ca1ac6..7ec516f7f5 100644 --- a/src/test/java/net/sf/jsqlparser/statement/DeclareStatementTest.java +++ b/src/test/java/net/sf/jsqlparser/statement/DeclareStatementTest.java @@ -16,6 +16,7 @@ import net.sf.jsqlparser.statement.DeclareStatement.TypeDefExpr; import net.sf.jsqlparser.statement.create.table.ColDataType; import net.sf.jsqlparser.statement.create.table.ColumnDefinition; +import net.sf.jsqlparser.statement.create.table.ColumnOption; import static net.sf.jsqlparser.test.TestUtils.asList; import static net.sf.jsqlparser.test.TestUtils.assertDeparse; import static net.sf.jsqlparser.test.TestUtils.assertEqualsObjectTree; @@ -37,7 +38,8 @@ public void testDeclareType() throws JSQLParserException { DeclareStatement created = new DeclareStatement() .addTypeDefExprList( new TypeDefExpr(new UserVariable().withName("find"), - new ColDataType().withDataType("nvarchar (30)"), null)) + new ColDataType().withDataType("nvarchar (30)").withPrecision(30), + null)) .withDeclareType(DeclareType.TYPE); assertDeparse(created, statement); assertEqualsObjectTree(parsed, created); @@ -49,7 +51,7 @@ public void testDeclareTypeWithDefault() throws JSQLParserException { Statement parsed = assertSqlCanBeParsedAndDeparsed(statement); DeclareStatement created = new DeclareStatement() .addTypeDefExprList(new TypeDefExpr(new UserVariable().withName("find"), - new ColDataType().withDataType("varchar (30)"), + new ColDataType().withDataType("varchar (30)").withPrecision(30), new StringValue().withValue("Man%"))) .withDeclareType(DeclareType.TYPE); assertDeparse(created, statement); @@ -63,7 +65,7 @@ public void testDeclareTypeList() throws JSQLParserException { DeclareStatement created = new DeclareStatement().addTypeDefExprList(asList( // new TypeDefExpr( new UserVariable().withName("group"), - new ColDataType().withDataType("nvarchar (50)"), + new ColDataType().withDataType("nvarchar (50)").withPrecision(50), null), new TypeDefExpr(new UserVariable().withName("sales"), new ColDataType().withDataType("money"), null))) @@ -86,8 +88,8 @@ public void testDeclareTable() throws JSQLParserException { .withUserVariable(new UserVariable("MyTableVar")) .withColumnDefinitions(new ArrayList<>()) .addColumnDefinitions( - new ColumnDefinition("EmpID", new ColDataType().withDataType("int"), - asList("NOT", "NULL")), + new ColumnDefinition("EmpID", new ColDataType().withDataType("int")) + .addColumnOptions(ColumnOption.nullability(false)), new ColumnDefinition("OldVacationHours", new ColDataType("int"))) .addColumnDefinitions( asList( diff --git a/src/test/java/net/sf/jsqlparser/statement/DistinctVisitorTest.java b/src/test/java/net/sf/jsqlparser/statement/DistinctVisitorTest.java new file mode 100644 index 0000000000..6a4f7b40c0 --- /dev/null +++ b/src/test/java/net/sf/jsqlparser/statement/DistinctVisitorTest.java @@ -0,0 +1,99 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.statement; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.ArrayList; +import java.util.List; +import net.sf.jsqlparser.JSQLParserException; +import net.sf.jsqlparser.expression.Expression; +import net.sf.jsqlparser.expression.ExpressionVisitorAdapter; +import net.sf.jsqlparser.parser.CCJSqlParserUtil; +import net.sf.jsqlparser.statement.select.FromItemVisitorAdapter; +import net.sf.jsqlparser.statement.select.PivotVisitorAdapter; +import net.sf.jsqlparser.statement.select.PlainSelect; +import net.sf.jsqlparser.statement.select.Select; +import net.sf.jsqlparser.statement.select.SelectItem; +import net.sf.jsqlparser.statement.select.SelectItemVisitorAdapter; +import net.sf.jsqlparser.statement.select.SelectVisitorAdapter; +import net.sf.jsqlparser.util.deparser.StatementDeParser; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +class DistinctVisitorTest { + @ParameterizedTest + @ValueSource(strings = {"SELECT DISTINCT id FROM foo", + "SELECT * FROM (SELECT DISTINCT id FROM foo) x", + "WITH x AS (SELECT DISTINCT id FROM foo) SELECT * FROM x", + "SELECT DISTINCT ON (id) id FROM foo", "SELECT ALL id FROM foo"}) + void analysisPreservesSelectSyntax(String sql) throws JSQLParserException { + Statement statement = CCJSqlParserUtil.parse(sql); + String before = statement.toString(); + StatementFeatures features = statement.getFeatures(); + assertThat(features.returnsResultSet()).isTrue(); + assertThat(features.mayModifyData()).isFalse(); + assertThat(features.getUnresolvedReferences()).isEmpty(); + statement.accept(new StatementVisitorAdapter<>(), null); + assertThat(statement.toString()).isEqualTo(before); + StringBuilder deparsed = new StringBuilder(); + statement.accept(new StatementDeParser(deparsed), null); + assertThat(CCJSqlParserUtil.parse(deparsed.toString()).toString()).isEqualTo(before); + } + + @Test + void plainDistinctKeepsItsAbsentOnList() throws JSQLParserException { + PlainSelect select = (PlainSelect) CCJSqlParserUtil.parse("SELECT DISTINCT id FROM foo"); + select.accept(new SelectVisitorAdapter<>(), null); + assertThat(select.getDistinct().getOnSelectItems()).isNull(); + assertThat(select.toString()).isEqualTo("SELECT DISTINCT id FROM foo"); + } + + @ParameterizedTest + @ValueSource(strings = {"SELECT DISTINCT unproven(id) FROM foo", + "SELECT * FROM (SELECT DISTINCT unproven(id) FROM foo) x", + "WITH x AS (SELECT DISTINCT unproven(id) FROM foo) SELECT * FROM x"}) + void plainDistinctStillReportsUnprovenFunctions(String sql) throws JSQLParserException { + StatementFeatures features = CCJSqlParserUtil.parse(sql).getFeatures(n -> false); + assertThat(features.getUnresolvedReferences()).containsExactly("unproven"); + assertThat(features.mayModifyData()).isTrue(); + assertThat(features.modifiesData()).isFalse(); + } + + @Test + void selectAndOutputItemsKeepCallbackOrderAndContext() throws JSQLParserException { + List seen = new ArrayList<>(); + Object marker = new Object(); + SelectItemVisitorAdapter items = new SelectItemVisitorAdapter() { + @Override + public Void visit(SelectItem item, S context) { + assertThat(context).isSameAs(marker); + seen.add(item.getExpression().toString()); + return null; + } + }; + SelectVisitorAdapter visitor = new SelectVisitorAdapter<>( + new ExpressionVisitorAdapter<>(), new PivotVisitorAdapter<>(), items, + new FromItemVisitorAdapter<>()); + Select select = (Select) CCJSqlParserUtil + .parse("SELECT DISTINCT ON (key_fn(id)) value_fn(id), id FROM foo"); + select.accept(visitor, marker); + assertThat(seen).containsExactly("key_fn(id)", "value_fn(id)", "id"); + assertThat(select.getFeatures().getUnresolvedReferences()) + .containsExactly("key_fn", "value_fn"); + + seen.clear(); + PlainSelect output = (PlainSelect) CCJSqlParserUtil.parse("SELECT a, b"); + visitor.visitOutputClause(new OutputClause(output.getSelectItems(), null, null, null), + marker); + assertThat(seen).containsExactly("a", "b"); + } +} diff --git a/src/test/java/net/sf/jsqlparser/statement/DoStatementTest.java b/src/test/java/net/sf/jsqlparser/statement/DoStatementTest.java new file mode 100644 index 0000000000..6c5b495ec4 --- /dev/null +++ b/src/test/java/net/sf/jsqlparser/statement/DoStatementTest.java @@ -0,0 +1,150 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.statement; + +import static net.sf.jsqlparser.test.TestUtils.assertSqlCanBeParsedAndDeparsed; +import static org.junit.jupiter.api.Assertions.*; + +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; +import net.sf.jsqlparser.JSQLParserException; +import net.sf.jsqlparser.expression.ExpressionVisitorAdapter; +import net.sf.jsqlparser.expression.StringValue; +import net.sf.jsqlparser.parser.AbstractJSqlParser.Dialect; +import net.sf.jsqlparser.parser.CCJSqlParserUtil; +import net.sf.jsqlparser.parser.feature.Feature; +import net.sf.jsqlparser.parser.feature.FeatureConfiguration; +import net.sf.jsqlparser.statement.select.SelectVisitorAdapter; +import net.sf.jsqlparser.util.TablesNamesFinder; +import net.sf.jsqlparser.util.deparser.ExpressionDeParser; +import net.sf.jsqlparser.util.deparser.SelectDeParser; +import net.sf.jsqlparser.util.deparser.StatementDeParser; +import net.sf.jsqlparser.util.validation.Validation; +import net.sf.jsqlparser.util.validation.feature.FeaturesAllowed; +import net.sf.jsqlparser.util.validation.feature.PostgresqlVersion; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +class DoStatementTest { + @ParameterizedTest + @ValueSource(strings = {"DO $$BEGIN NULL; END$$", "DO LANGUAGE plpgsql $$BEGIN NULL; END$$", + "DO $body$BEGIN NULL; END$body$ LANGUAGE plpgsql", "DO 'BEGIN NULL; END'", + "DO LANGUAGE plpython3u 'print(1)'"}) + void roundTripsBodyAndLanguagePosition(String sql) throws Exception { + DoStatement statement = (DoStatement) assertSqlCanBeParsedAndDeparsed(sql, false, + p -> p.withDialect(Dialect.POSTGRESQL)); + assertEquals(sql, CCJSqlParserUtil.parse(statement.toString(), + p -> p.withDialect(Dialect.POSTGRESQL)).toString()); + assertEquals(sql.startsWith("DO LANGUAGE"), statement.isLanguageBeforeCode()); + assertEquals( + sql.contains("LANGUAGE") ? sql.contains("plpython") ? "plpython3u" : "plpgsql" + : null, + statement.getLanguage()); + assertTrue(statement.getFeatures().isOpaque()); + assertTrue(statement.getFeatures().mayModifyData()); + assertThrows(UnsupportedOperationException.class, + () -> new TablesNamesFinder().getTables(statement)); + } + + @Test + void preservesProceduralBodyAndFollowingStatementsIssue1946() throws Exception { + String sql; + try (InputStream input = getClass().getResourceAsStream("/postgresql/do-issue1946.sql")) { + assertNotNull(input); + sql = new String(input.readAllBytes(), StandardCharsets.UTF_8).strip(); + } + String body = sql.substring(sql.indexOf("$$"), sql.lastIndexOf("$$") + 2); + Statements statements = + CCJSqlParserUtil.parseStatements("SELECT 0;\n" + sql + "\nSELECT 1;", + p -> p.withDialect(Dialect.POSTGRESQL).withUnsupportedStatements(false)); + assertEquals(3, statements.size()); + assertEquals("SELECT 0", statements.get(0).toString()); + DoStatement block = assertInstanceOf(DoStatement.class, statements.get(1)); + assertEquals(body, block.getCode().toString()); + assertEquals(body.substring(2, body.length() - 2), block.getCode().getValue()); + assertEquals("SELECT 1", statements.get(2).toString()); + + StringBuilder output = new StringBuilder(); + for (Statement statement : statements) { + statement.accept(new StatementDeParser(output), null); + output.append(";\n"); + } + assertEquals("SELECT 0;\nDO " + body + ";\nSELECT 1;\n", output.toString()); + Statements reparsed = CCJSqlParserUtil.parseStatements(output.toString(), + p -> p.withDialect(Dialect.POSTGRESQL).withUnsupportedStatements(false)); + assertEquals(3, reparsed.size()); + assertEquals(body, + assertInstanceOf(DoStatement.class, reparsed.get(1)).getCode().toString()); + assertEquals("SELECT 0", reparsed.get(0).toString()); + assertEquals("SELECT 1", reparsed.get(2).toString()); + } + + @Test + void supportsBodyVisitorsAndAstEdits() throws Exception { + DoStatement statement = (DoStatement) CCJSqlParserUtil.parse("DO $$BEGIN NULL; END$$", + p -> p.withDialect(Dialect.POSTGRESQL)); + List bodies = new ArrayList<>(); + statement.accept(new StatementVisitorAdapter<>(new SelectVisitorAdapter<>( + new ExpressionVisitorAdapter() { + @Override + public Void visit(StringValue value, S context) { + assertEquals("context", context); + bodies.add(value.toString()); + return null; + } + })), "context"); + assertEquals(List.of("$$BEGIN NULL; END$$"), bodies); + statement.withCode(new StringValue("$new$BEGIN PERFORM 1; END$new$")) + .withLanguage("plpgsql"); + statement.setLanguageBeforeCode(true); + assertEquals("DO LANGUAGE plpgsql $new$BEGIN PERFORM 1; END$new$", statement.toString()); + StringBuilder output = new StringBuilder(); + ExpressionDeParser expressions = new ExpressionDeParser() { + @Override + public StringBuilder visit(StringValue value, S context) { + return getBuilder().append("$$BEGIN NULL; END$$"); + } + }; + statement.accept(new StatementDeParser(expressions, new SelectDeParser(), output)); + assertEquals("DO LANGUAGE plpgsql $$BEGIN NULL; END$$", output.toString()); + } + + @Test + void gatesDialectAndRejectsMalformedWrappers() throws Exception { + String sql = "DO $$BEGIN NULL; END$$"; + assertThrows(JSQLParserException.class, () -> CCJSqlParserUtil.parse(sql)); + for (Dialect dialect : Dialect.values()) { + if (dialect != Dialect.POSTGRESQL) { + assertThrows(JSQLParserException.class, + () -> CCJSqlParserUtil.parse(sql, p -> p.withDialect(dialect))); + } + } + for (String malformed : List.of("DO", "DO LANGUAGE plpgsql", "DO 123", + "DO LANGUAGE plpgsql $$x$$ LANGUAGE plpgsql", "DO $tag$unterminated")) { + assertThrows(JSQLParserException.class, + () -> CCJSqlParserUtil.parse(malformed, + p -> p.withDialect(Dialect.POSTGRESQL))); + } + } + + @Test + void validatesWrapperCapabilityWithoutClaimingBodyValidation() { + FeatureConfiguration config = new FeatureConfiguration() + .setValue(Feature.dialect, Dialect.POSTGRESQL.name()); + assertTrue(new Validation(config, List.of(PostgresqlVersion.V10), + "DO $$arbitrary language body$$") + .validate().isEmpty()); + assertFalse(new Validation(config, List.of(new FeaturesAllowed(Feature.select)), + "DO $$arbitrary language body$$").validate().isEmpty()); + } +} diff --git a/src/test/java/net/sf/jsqlparser/statement/ForPortionClauseTest.java b/src/test/java/net/sf/jsqlparser/statement/ForPortionClauseTest.java new file mode 100644 index 0000000000..012ac9df97 --- /dev/null +++ b/src/test/java/net/sf/jsqlparser/statement/ForPortionClauseTest.java @@ -0,0 +1,171 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.statement; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertThrows; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import net.sf.jsqlparser.JSQLParserException; +import net.sf.jsqlparser.expression.ExpressionVisitorAdapter; +import net.sf.jsqlparser.expression.JdbcParameter; +import net.sf.jsqlparser.expression.LongValue; +import net.sf.jsqlparser.expression.StringValue; +import net.sf.jsqlparser.parser.CCJSqlParserUtil; +import net.sf.jsqlparser.parser.feature.Feature; +import net.sf.jsqlparser.statement.delete.Delete; +import net.sf.jsqlparser.statement.update.Update; +import net.sf.jsqlparser.test.TestUtils; +import net.sf.jsqlparser.util.TablesNamesFinder; +import net.sf.jsqlparser.util.deparser.ExpressionDeParser; +import net.sf.jsqlparser.util.deparser.SelectDeParser; +import net.sf.jsqlparser.util.deparser.StatementDeParser; +import net.sf.jsqlparser.util.validation.ValidationTestAsserts; +import net.sf.jsqlparser.util.validation.feature.FeaturesAllowed; +import net.sf.jsqlparser.util.validation.feature.MariaDbVersion; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +class ForPortionClauseTest extends ValidationTestAsserts { + @ParameterizedTest + @ValueSource(strings = { + "UPDATE prices FOR PORTION OF VALID_TIME FROM '2020-01-01' TO '2021-01-01' SET price = 10 WHERE id = 1", + "DELETE FROM prices FOR PORTION OF VALID_TIME FROM '2020-01-01' TO '2021-01-01' WHERE id = 1", + "UPDATE prices p FOR PORTION OF valid_time FROM ? TO ? SET price = ? WHERE id = ?", + "DELETE FROM prices AS p FOR PORTION OF \"valid time\" FROM :start_date TO :end_date WHERE id = :id", + "UPDATE prices FOR PORTION OF p FROM DATE '2020-01-01' TO DATE '2021-01-01' SET price = 10", + "DELETE FROM prices FOR PORTION OF p FROM CAST(? AS DATE) TO CAST(? AS DATE) ORDER BY id LIMIT 1", + "UPDATE prices FOR PORTION OF p FROM (1 + 2) TO COALESCE(3, 4) SET price = 10 RETURNING id", + "DELETE FROM prices FOR PORTION OF p FROM 1 TO 2 RETURNING id", + "WITH ids AS (SELECT id FROM selected) UPDATE prices FOR PORTION OF p FROM 1 TO 2 SET price = 10 WHERE id IN (SELECT id FROM ids)"}) + void preservesTheIntervalThroughBothStatementRenderers(String sql) throws Exception { + Statement statement = TestUtils.assertSqlCanBeParsedAndDeparsed(sql); + ForPortionClause clause = portion(statement); + assertEquals(clause.toString(), + portion(CCJSqlParserUtil.parse(statement.toString())).toString()); + } + + @Test + void exposesTypedBoundsAndPreservesGlobalParameterOrder() throws Exception { + Update update = (Update) CCJSqlParserUtil.parse( + "UPDATE prices FOR PORTION OF p FROM ? TO ? SET price = ? WHERE id = ?"); + ForPortionClause clause = update.getForPortionClause(); + assertEquals("p", clause.getPeriodName()); + assertEquals(1, ((JdbcParameter) clause.getFromExpression()).getIndex()); + assertEquals(2, ((JdbcParameter) clause.getToExpression()).getIndex()); + assertEquals(3, + ((JdbcParameter) update.getUpdateSets().get(0).getValues().get(0)).getIndex()); + clause.setPeriodName("valid_time"); + clause.setFromExpression(new StringValue("2020-01-01")); + clause.setToExpression(new StringValue("2021-01-01")); + assertInstanceOf(StringValue.class, clause.getFromExpression()); + assertEquals( + "UPDATE prices FOR PORTION OF valid_time FROM '2020-01-01' TO '2021-01-01' SET price = ? WHERE id = ?", + update.toString()); + Delete delete = (Delete) CCJSqlParserUtil.parse("DELETE FROM prices"); + assertNull(delete.getForPortionClause()); + assertSame(delete, delete.withForPortionClause(clause)); + assertEquals("DELETE FROM prices " + clause, delete.toString()); + update.setForPortionClause(null); + assertEquals("UPDATE prices SET price = ? WHERE id = ?", update.toString()); + } + + @ParameterizedTest + @ValueSource(strings = {"UPDATE prices FOR PORTION OF p FROM 1 TO 2 SET price = 3 WHERE id = 4", + "DELETE FROM prices FOR PORTION OF p FROM 1 TO 2 WHERE id = 4"}) + void customVisitorsSeeEachBoundExactlyOnce(String sql) throws Exception { + Statement statement = CCJSqlParserUtil.parse(sql); + Object context = new Object(); + List bounds = new ArrayList<>(); + portion(statement).accept(new ExpressionVisitorAdapter() { + @Override + public Void visit(LongValue value, S actualContext) { + assertSame(context, actualContext); + bounds.add(value.getValue()); + return null; + } + }, context); + assertEquals(List.of(1L, 2L), bounds); + List rendered = new ArrayList<>(); + StringBuilder output = new StringBuilder(); + ExpressionDeParser expressions = new ExpressionDeParser() { + @Override + public StringBuilder visit(LongValue value, S actualContext) { + rendered.add(value.getValue()); + return getBuilder().append(value.getValue() + 100); + } + }; + statement.accept(new StatementDeParser(expressions, new SelectDeParser(), output), null); + assertEquals(statement instanceof Update ? List.of(1L, 2L, 3L, 4L) : List.of(1L, 2L, 4L), + rendered); + String expected = sql.replace("FROM 1 TO 2", "FROM 101 TO 102") + .replace("price = 3", "price = 103").replace("id = 4", "id = 104"); + assertEquals(expected, output.toString()); + assertEquals(sql, statement.toString()); + TestUtils.assertSqlCanBeParsedAndDeparsed(output.toString()); + } + + @ParameterizedTest + @ValueSource(strings = { + "UPDATE prices FOR PORTION OF p FROM (SELECT MIN(ts) FROM starts) TO (SELECT MAX(ts) FROM ends) SET price = 10", + "DELETE FROM prices FOR PORTION OF p FROM (SELECT MIN(ts) FROM starts) TO (SELECT MAX(ts) FROM ends)"}) + void tableDiscoveryTraversesBothBoundExpressions(String sql) throws Exception { + assertEquals(Set.of("prices", "starts", "ends"), TablesNamesFinder.findTables(sql)); + } + + @ParameterizedTest + @ValueSource(strings = {"UPDATE prices FOR PORTION OF p FROM ? TO ? SET price = 10", + "DELETE FROM prices FOR PORTION OF p FROM ? TO ?", + "UPDATE prices FOR PORTION OF p FROM ? TO 2 SET price = 10", + "UPDATE prices FOR PORTION OF p FROM 1 TO ? SET price = 10", + "DELETE FROM prices FOR PORTION OF p FROM ? TO 2", + "DELETE FROM prices FOR PORTION OF p FROM 1 TO ?"}) + void validatesTheClauseFeatureAndBothBoundExpressions(String sql) throws Exception { + validateNoErrors(sql, 1, + new FeaturesAllowed().add(FeaturesAllowed.DML).add(Feature.jdbcParameter)); + validateNoErrors(sql, 1, MariaDbVersion.V10_5_4); + validateNotAllowed(sql, 1, 1, + new FeaturesAllowed().add(FeaturesAllowed.DML).add(Feature.jdbcParameter) + .remove(Feature.forPortion), + Feature.forPortion); + validateNotAllowed(sql, 1, 1, + new FeaturesAllowed().add(FeaturesAllowed.DML).remove(Feature.jdbcParameter), + Feature.jdbcParameter); + } + + @ParameterizedTest + @ValueSource(strings = {"UPDATE prices FOR PORTION p FROM 1 TO 2 SET price = 10", + "DELETE FROM prices FOR PORTION OF p FROM 1", + "DELETE FROM prices FOR PORTION OF p FROM 1 TO", + "DELETE FROM prices FOR PORTION OF p TO 2", + "UPDATE prices FOR PORTION OF p FROM 1 TO 2", + "DELETE FROM prices FOR PORTION OF p FROM 1 TO 2 garbage"}) + void rejectsIncompleteClausesAndTrailingInput(String sql) { + assertThrows(JSQLParserException.class, () -> CCJSqlParserUtil.parse(sql)); + } + + @ParameterizedTest + @ValueSource(strings = {"UPDATE portion SET valid_time = 1", + "DELETE FROM portion WHERE valid_time = 1", + "SELECT portion, valid_time FROM prices FOR UPDATE"}) + void preservesOrdinaryIdentifiersAndForUpdate(String sql) throws Exception { + TestUtils.assertSqlCanBeParsedAndDeparsed(sql); + } + + private static ForPortionClause portion(Statement statement) { + return statement instanceof Update ? ((Update) statement).getForPortionClause() + : ((Delete) statement).getForPortionClause(); + } +} diff --git a/src/test/java/net/sf/jsqlparser/statement/KeywordsTest.java b/src/test/java/net/sf/jsqlparser/statement/KeywordsTest.java index b595ab096f..9e2c14ce7e 100644 --- a/src/test/java/net/sf/jsqlparser/statement/KeywordsTest.java +++ b/src/test/java/net/sf/jsqlparser/statement/KeywordsTest.java @@ -51,4 +51,15 @@ public void testCombinedTokenKeywords() throws JSQLParserException { String sqlStr = "SELECT current_date(3)"; assertSqlCanBeParsedAndDeparsed(sqlStr, true); } + + // #2473: GROUPS is a non-reserved keyword in PostgreSQL and must stay usable as an + // identifier, although it also introduces the GROUPS window frame unit. + @Test + public void testGroupsAsIdentifier() throws JSQLParserException { + assertSqlCanBeParsedAndDeparsed("SELECT groups FROM mytable", true); + assertSqlCanBeParsedAndDeparsed("SELECT * FROM groups", true); + assertSqlCanBeParsedAndDeparsed("SELECT a AS groups FROM mytable", true); + assertSqlCanBeParsedAndDeparsed("SELECT a groups FROM mytable", true); + assertSqlCanBeParsedAndDeparsed("CREATE TABLE mytable (groups INT)", true); + } } diff --git a/src/test/java/net/sf/jsqlparser/statement/OracleBlockTest.java b/src/test/java/net/sf/jsqlparser/statement/OracleBlockTest.java new file mode 100644 index 0000000000..07ddbd5924 --- /dev/null +++ b/src/test/java/net/sf/jsqlparser/statement/OracleBlockTest.java @@ -0,0 +1,182 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.statement; + +import static org.junit.jupiter.api.Assertions.*; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import net.sf.jsqlparser.JSQLParserException; +import net.sf.jsqlparser.expression.ExpressionVisitorAdapter; +import net.sf.jsqlparser.expression.JdbcNamedParameter; +import net.sf.jsqlparser.expression.LongValue; +import net.sf.jsqlparser.parser.AbstractJSqlParser.Dialect; +import net.sf.jsqlparser.parser.CCJSqlParserUtil; +import net.sf.jsqlparser.parser.feature.Feature; +import net.sf.jsqlparser.statement.execute.Execute; +import net.sf.jsqlparser.statement.oracle.OracleAssignment; +import net.sf.jsqlparser.statement.oracle.OracleBlock; +import net.sf.jsqlparser.statement.select.SelectVisitorAdapter; +import net.sf.jsqlparser.util.TablesNamesFinder; +import net.sf.jsqlparser.util.deparser.ExpressionDeParser; +import net.sf.jsqlparser.util.deparser.SelectDeParser; +import net.sf.jsqlparser.util.deparser.StatementDeParser; +import net.sf.jsqlparser.util.validation.Validation; +import net.sf.jsqlparser.util.validation.feature.FeaturesAllowed; +import net.sf.jsqlparser.util.validation.feature.OracleVersion; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +class OracleBlockTest { + private static OracleBlock parse(String sql) throws Exception { + OracleBlock block = + (OracleBlock) CCJSqlParserUtil.parse(sql, p -> p.withDialect(Dialect.ORACLE)); + StringBuilder output = new StringBuilder(); + block.accept(new StatementDeParser(output)); + assertEquals(block.toString(), output.toString()); + assertEquals(block.toString(), CCJSqlParserUtil + .parse(output.toString(), p -> p.withDialect(Dialect.ORACLE)).toString()); + return block; + } + + @Test + void supportsIssue2007BindCalls() throws Exception { + OracleBlock block = + parse("BEGIN modifyParams(:BP_00000_v, :BP_00001_v, :BP_00012_v); END;"); + Execute call = assertInstanceOf(Execute.class, block.getStatements().get(0)); + assertEquals(Execute.ExecType.IMPLICIT, call.getExecType()); + assertEquals("modifyParams", call.getName()); + assertEquals(3, call.getExprList().size()); + List names = new ArrayList<>(); + ExpressionVisitorAdapter expressions = new ExpressionVisitorAdapter() { + @Override + public Void visit(JdbcNamedParameter parameter, S context) { + assertEquals("ctx", context); + names.add(parameter.getName()); + return null; + } + }; + block.accept(new StatementVisitorAdapter<>(new SelectVisitorAdapter<>(expressions)), "ctx"); + assertEquals(List.of("BP_00000_v", "BP_00001_v", "BP_00012_v"), names); + assertTrue(block.getFeatures().isOpaque()); + assertTrue(block.getFeatures().getUnresolvedReferences().contains("modifyparams")); + assertThrows(UnsupportedOperationException.class, + () -> new TablesNamesFinder().getTables(block)); + } + + @Test + void supportsIssue1786AndMaintainerNestedExample() throws Exception { + OracleBlock simple = parse( + "DECLARE num NUMBER; BEGIN num := 10; dbms_output.put_line('The number is ' || num); END;"); + assertEquals("num", simple.getDeclarations().get(0).getName()); + assertEquals("NUMBER", simple.getDeclarations().get(0).getDataType().toString()); + OracleAssignment assignment = + assertInstanceOf(OracleAssignment.class, simple.getStatements().get(0)); + assertEquals("num", assignment.getTarget().toString()); + assertEquals("10", assignment.getValue().toString()); + OracleBlock nested = parse( + "DECLARE l_message VARCHAR2(100) := 'Hello'; BEGIN DECLARE l_message2 VARCHAR2(100) := l_message || ' World!'; BEGIN DBMS_OUTPUT.put_line(l_message2); END; EXCEPTION WHEN OTHERS THEN DBMS_OUTPUT.put_line(DBMS_UTILITY.format_error_stack); END;"); + assertInstanceOf(OracleBlock.class, nested.getStatements().get(0)); + assertEquals(List.of("OTHERS"), nested.getExceptionHandlers().get(0).getExceptions()); + assertEquals("DBMS_OUTPUT.put_line", + ((Execute) nested.getExceptionHandlers().get(0).getStatements().get(0)).getName()); + } + + @ParameterizedTest + @ValueSource(strings = { + "BEGIN p; p(); schema.pkg.p(1, 'text'); p(arg => 1); NULL; END;", + "DECLARE n CONSTANT NUMBER := 1; s VARCHAR2(10) NOT NULL DEFAULT 'a'; BEGIN n2 := n; END;", + "BEGIN :out_value := 1; rec.field := 2; END;", + "BEGIN NULL; EXCEPTION WHEN NO_DATA_FOUND OR TOO_MANY_ROWS THEN NULL; WHEN OTHERS THEN p; END;", + "BEGIN BEGIN NULL; END; BEGIN NULL; END; END;" + }) + void supportsBlockForms(String sql) throws Exception { + parse(sql); + } + + @Test + void visitsMutationsAcrossDeclarationsBodyAndHandlers() throws Exception { + OracleBlock block = parse( + "DECLARE n NUMBER := 1; BEGIN n := 2; EXCEPTION WHEN OTHERS THEN n := 3; END;"); + List values = new ArrayList<>(); + ExpressionVisitorAdapter expressions = new ExpressionVisitorAdapter() { + @Override + public Void visit(LongValue value, S context) { + assertEquals("ctx", context); + values.add(value.getValue()); + return null; + } + }; + block.accept(new StatementVisitorAdapter<>(new SelectVisitorAdapter<>(expressions)), "ctx"); + assertEquals(List.of(1L, 2L, 3L), values); + assertFalse(block.getFeatures().modifiesData()); + block.getDeclarations().get(0).setInitializer(new LongValue(10)); + ((OracleAssignment) block.getExceptionHandlers().get(0).getStatements().get(0)) + .setValue(new LongValue(30)); + StringBuilder output = new StringBuilder(); + ExpressionDeParser deparser = new ExpressionDeParser() { + @Override + public StringBuilder visit(LongValue value, S context) { + assertEquals("ctx", context); + return getBuilder().append(value.getValue() + 1); + } + }; + block.accept(new StatementDeParser(deparser, new SelectDeParser(), output), "ctx"); + assertTrue(output.toString().contains("NUMBER := 11")); + assertTrue(output.toString().contains("n := 31")); + parse(output.toString()); + } + + @Test + void findsSqlTablesInBothMainBodyAndHandlers() throws Exception { + OracleBlock block = parse( + "DECLARE n NUMBER := (SELECT max(id) FROM source); BEGIN INSERT INTO target SELECT id FROM source; EXCEPTION WHEN OTHERS THEN INSERT INTO audit VALUES (1); END;"); + assertEquals(Set.of("source", "target", "audit"), new TablesNamesFinder().getTables(block)); + assertTrue(block.getFeatures().modifiesData()); + assertFalse(block.getFeatures().returnsResultSet()); + } + + @Test + void retainsScriptBoundariesAndExistingDialects() throws Exception { + assertEquals(3, CCJSqlParserUtil.parseStatements("BEGIN p; END; BEGIN q; END; SELECT 1;", + p -> p.withDialect(Dialect.ORACLE)).size()); + assertThrows(JSQLParserException.class, () -> CCJSqlParserUtil.parse("BEGIN p(1); END;")); + assertThrows(JSQLParserException.class, + () -> CCJSqlParserUtil.parse("DECLARE n NUMBER; BEGIN n := 1; END;", + p -> p.withDialect(Dialect.SQLSERVER))); + assertInstanceOf(DeclareStatement.class, CCJSqlParserUtil.parse("DECLARE @n int = 1", + p -> p.withDialect(Dialect.SQLSERVER))); + assertInstanceOf(Block.class, CCJSqlParserUtil.parse("BEGIN SELECT 1; END;")); + } + + @ParameterizedTest + @ValueSource(strings = {"BEGIN RAISE; END;", "BEGIN LOOP; END;", "DECLARE BEGIN NULL; END;", + "DECLARE n NUMBER BEGIN NULL; END;", + "BEGIN p(1) END;", "BEGIN NULL;", "BEGIN END;", "BEGIN p(1,); END;", + "DECLARE n CONSTANT NUMBER; BEGIN NULL; END;", + "DECLARE n NUMBER NOT NULL; BEGIN NULL; END;", + "BEGIN NULL; EXCEPTION WHEN OTHERS THEN END;"}) + void rejectsIncompleteBlocks(String sql) { + assertThrows(JSQLParserException.class, () -> parse(sql)); + } + + @Test + void validatesOracleBlockCapability() { + String sql = "DECLARE n NUMBER := 1; BEGIN n := 2; p(n); END;"; + assertTrue(new Validation(CCJSqlParserUtil.newParser("SELECT 1").withDialect(Dialect.ORACLE) + .getConfiguration(), List.of(OracleVersion.values()[0]), sql).validate().isEmpty()); + assertFalse( + new Validation( + CCJSqlParserUtil.newParser("SELECT 1").withDialect(Dialect.ORACLE) + .getConfiguration(), + List.of(new FeaturesAllowed(Feature.block)), sql).validate().isEmpty()); + } +} diff --git a/src/test/java/net/sf/jsqlparser/statement/PostgresqlExplainTest.java b/src/test/java/net/sf/jsqlparser/statement/PostgresqlExplainTest.java new file mode 100644 index 0000000000..f0cef624dd --- /dev/null +++ b/src/test/java/net/sf/jsqlparser/statement/PostgresqlExplainTest.java @@ -0,0 +1,112 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.statement; + +import static org.junit.jupiter.api.Assertions.*; + +import java.util.List; +import net.sf.jsqlparser.JSQLParserException; +import net.sf.jsqlparser.parser.AbstractJSqlParser.Dialect; +import net.sf.jsqlparser.parser.CCJSqlParserUtil; +import net.sf.jsqlparser.statement.ExplainStatement.Option; +import net.sf.jsqlparser.statement.ExplainStatement.OptionType; +import net.sf.jsqlparser.test.TestUtils; +import net.sf.jsqlparser.util.TablesNamesFinder; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +class PostgresqlExplainTest { + @ParameterizedTest + @ValueSource(strings = { + "EXPLAIN SELECT 1", + "EXPLAIN ANALYZE VERBOSE SELECT 1", + "EXPLAIN (ANALYZE, VERBOSE, COSTS, BUFFERS) SELECT 1", + "EXPLAIN (ANALYZE TRUE, VERBOSE ON, COSTS OFF, BUFFERS FALSE) SELECT 1", + "EXPLAIN (ANALYZE 1, COSTS 0, FORMAT TEXT) SELECT 1", + "EXPLAIN (FORMAT XML) SELECT 1", + "EXPLAIN (FORMAT JSON) SELECT 1", + "EXPLAIN (FORMAT YAML) SELECT 1", + "EXPLAIN (ANALYZE, TIMING, SUMMARY, SETTINGS, WAL) SELECT 1", + "EXPLAIN (GENERIC_PLAN, SERIALIZE NONE, MEMORY) SELECT 1", + "EXPLAIN (SERIALIZE TEXT, GENERIC_PLAN FALSE) SELECT 1", + "EXPLAIN (ANALYZE, SERIALIZE BINARY) SELECT 1", + "EXPLAIN (SERIALIZE) SELECT 1", + "EXPLAIN (ANALYZE OFF, ANALYZE ON) SELECT 1", + "EXPLAIN (ANALYZE) WITH t AS (SELECT * FROM source) SELECT * FROM t", + "EXPLAIN (ANALYZE FALSE) INSERT INTO t VALUES (1)", + "EXPLAIN (COSTS OFF) UPDATE t SET n = 2", + "EXPLAIN (FORMAT JSON) DELETE FROM t WHERE n = 1", + "EXPLAIN (COSTS) VALUES (1), (2)", + "EXPLAIN (SELECT 1)" + }) + void roundTrip(String sql) throws JSQLParserException { + TestUtils.assertSqlCanBeParsedAndDeparsed(sql, true, + parser -> parser.withDialect(Dialect.POSTGRESQL)); + } + + @ParameterizedTest + @ValueSource(strings = { + "EXPLAIN () SELECT 1", + "EXPLAIN ANALYZE TRUE SELECT 1", + "EXPLAIN VERBOSE TRUE SELECT 1", + "EXPLAIN BUFFERS SELECT 1", + "EXPLAIN COSTS SELECT 1", + "EXPLAIN FORMAT XML SELECT 1", + "EXPLAIN VERBOSE ANALYZE SELECT 1", + "EXPLAIN ANALYZE ANALYZE SELECT 1", + "EXPLAIN PLAN FOR SELECT 1", + "EXPLAIN (FORMAT) SELECT 1", + "EXPLAIN (FORMAT LONGTEXT) SELECT 1", + "EXPLAIN (ANALYZE 2) SELECT 1", + "EXPLAIN (ANALYZE MAYBE) SELECT 1", + "EXPLAIN (SERIALIZE JSON) SELECT 1", + "EXPLAIN (UNKNOWN_OPTION) SELECT 1", + "EXPLAIN (ANALYZE,) SELECT 1", + "EXPLAIN (ANALYZE VERBOSE) SELECT 1", + "EXPLAIN some_table" + }) + void rejectInvalidPostgresqlOptions(String sql) { + assertThrows(JSQLParserException.class, () -> CCJSqlParserUtil.parse(sql, + parser -> parser.withDialect(Dialect.POSTGRESQL).withUnsupportedStatements(false))); + } + + @Test + void preserveRepetitionsAndLegacyMapAccess() throws JSQLParserException { + ExplainStatement explain = (ExplainStatement) TestUtils.assertSqlCanBeParsedAndDeparsed( + "EXPLAIN (ANALYZE OFF, COSTS, ANALYZE ON) SELECT * FROM accounts"); + assertTrue(explain.isParenthesizedOptions()); + assertEquals(3, explain.getOptionList().size()); + assertEquals(2, explain.getOptions().size()); + assertEquals("ON", explain.getOption(OptionType.ANALYZE).getValue()); + assertEquals("ON", explain.getOptions().get(OptionType.ANALYZE).getValue()); + explain.getOptionList().clear(); + explain.getOptions().clear(); + assertEquals(3, explain.getOptionList().size()); + explain.addOption(new Option(OptionType.ANALYZE).withValue("FALSE")); + assertEquals(3, explain.getOptionList().size()); + assertEquals("FALSE", explain.getOption(OptionType.ANALYZE).getValue()); + assertEquals(List.of("accounts"), new TablesNamesFinder().getTableList(explain)); + } + + @Test + void constructOptionsAndKeepOtherDialects() throws JSQLParserException { + ExplainStatement explain = new ExplainStatement(CCJSqlParserUtil.parse("SELECT 1")); + assertNull(explain.getOptions()); + explain.setParenthesizedOptions(true); + explain.addOption(new Option(OptionType.GENERIC_PLAN)); + explain.addOption(new Option(OptionType.FORMAT).withValue("TEXT")); + TestUtils.assertStatementCanBeDeparsedAs(explain, + "EXPLAIN (GENERIC_PLAN, FORMAT TEXT) SELECT 1"); + TestUtils.assertSqlCanBeParsedAndDeparsed("EXPLAIN PLAN FOR SELECT 1"); + TestUtils.assertSqlCanBeParsedAndDeparsed("EXPLAIN BUFFERS FALSE SELECT 1"); + TestUtils.assertSqlCanBeParsedAndDeparsed("SUMMARIZE accounts"); + } +} diff --git a/src/test/java/net/sf/jsqlparser/statement/SetIdentityInsertTest.java b/src/test/java/net/sf/jsqlparser/statement/SetIdentityInsertTest.java new file mode 100644 index 0000000000..b83de91b51 --- /dev/null +++ b/src/test/java/net/sf/jsqlparser/statement/SetIdentityInsertTest.java @@ -0,0 +1,170 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.statement; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import net.sf.jsqlparser.JSQLParserException; +import net.sf.jsqlparser.parser.AbstractJSqlParser.Dialect; +import net.sf.jsqlparser.parser.CCJSqlParserUtil; +import net.sf.jsqlparser.parser.feature.Feature; +import net.sf.jsqlparser.schema.Table; +import net.sf.jsqlparser.statement.select.FromItemVisitorAdapter; +import net.sf.jsqlparser.util.TablesNamesFinder; +import net.sf.jsqlparser.util.deparser.ExpressionDeParser; +import net.sf.jsqlparser.util.deparser.SelectDeParser; +import net.sf.jsqlparser.util.deparser.StatementDeParser; +import net.sf.jsqlparser.util.validation.ValidationContext; +import net.sf.jsqlparser.util.validation.ValidationTestAsserts; +import net.sf.jsqlparser.util.validation.feature.PostgresqlVersion; +import net.sf.jsqlparser.util.validation.feature.SqlServerVersion; +import net.sf.jsqlparser.util.validation.metadata.DatabaseMetaDataValidation; +import net.sf.jsqlparser.util.validation.metadata.Named; +import net.sf.jsqlparser.util.validation.metadata.NamedObject; +import net.sf.jsqlparser.util.validation.validator.StatementValidator; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import static org.junit.jupiter.api.Assertions.*; + +class SetIdentityInsertTest { + private SetIdentityInsertStatement parse(String sql) throws JSQLParserException { + return (SetIdentityInsertStatement) CCJSqlParserUtil.parse(sql, + p -> p.withDialect(Dialect.SQLSERVER).withUnsupportedStatements(false)); + } + + private void assertRoundTrip(SetIdentityInsertStatement set, String sql) throws Exception { + assertEquals(sql, set.toString()); + StringBuilder buffer = new StringBuilder(); + set.accept(new StatementDeParser(buffer), null); + assertEquals(sql, buffer.toString()); + SetIdentityInsertStatement reparsed = parse(buffer.toString()); + assertEquals(set.isOn(), reparsed.isOn()); + assertEquals(set.getTable().getFullyQualifiedName(), + reparsed.getTable().getFullyQualifiedName()); + } + + @ParameterizedTest + @ValueSource(strings = {"language", "country", "city", "address", "actor", "staff", + "store", "category", "film", "inventory", "customer", "rental", "payment"}) + void parsesSakilaIdentityDirectives(String table) throws Exception { + for (boolean on : new boolean[] {true, false}) { + String sql = "SET IDENTITY_INSERT " + table + (on ? " ON" : " OFF"); + SetIdentityInsertStatement set = parse(sql); + assertEquals(table, set.getTable().getName()); + assertEquals(on, set.isOn()); + assertRoundTrip(set, sql); + } + } + + @ParameterizedTest + @ValueSource(strings = {"dbo.actor", "sakila.dbo.actor", "sakila..actor", + "[sakila].[dbo].[order]", "[table with spaces]", "#staging"}) + void reusesQualifiedTableNames(String table) throws Exception { + SetIdentityInsertStatement set = parse("set identity_insert " + table + " on"); + assertEquals(table, set.getTable().getFullyQualifiedName()); + assertEquals(Collections.singleton(table), new TablesNamesFinder().getTables(set)); + assertRoundTrip(set, "SET IDENTITY_INSERT " + table + " ON"); + } + + @Test + void exposesEditableTargetAndStateWithoutClaimingDataChanges() throws Exception { + SetIdentityInsertStatement set = parse("SET IDENTITY_INSERT dbo.actor ON"); + set.getTable().setName("customer"); + set.setOn(false); + assertRoundTrip(set, "SET IDENTITY_INSERT dbo.customer OFF"); + set.setTable(new Table("replacement")); + assertRoundTrip(set, "SET IDENTITY_INSERT replacement OFF"); + assertEquals(Collections.singleton(StmtFeature.MODIFIES_SESSION), + set.getFeatures().getCertain()); + assertTrue(set.getFeatures().getUncertain().isEmpty()); + assertEquals(3, CCJSqlParserUtil.parseStatements( + "SET IDENTITY_INSERT dbo.actor ON; INSERT INTO dbo.actor VALUES (1); " + + "SET IDENTITY_INSERT dbo.actor OFF;", + p -> p.withDialect(Dialect.SQLSERVER)).size()); + } + + @Test + void traversesTheTargetWithVisitorContext() throws Exception { + SetIdentityInsertStatement set = parse("SET IDENTITY_INSERT dbo.actor ON"); + Object context = new Object(); + List visited = new ArrayList<>(); + FromItemVisitorAdapter tables = new FromItemVisitorAdapter() { + @Override + public Void visit(Table table, S value) { + assertSame(context, value); + visited.add(table); + return null; + } + }; + set.accept(new StatementVisitorAdapter(null, null, null, tables, null, null), + context); + assertEquals(Collections.singletonList(set.getTable()), visited); + + StringBuilder buffer = new StringBuilder(); + SelectDeParser selects = new SelectDeParser() { + @Override + public StringBuilder visit(Table table, S value) { + assertSame(context, value); + return getBuilder().append("mapped_table"); + } + }; + set.accept(new StatementDeParser(new ExpressionDeParser(), selects, buffer), context); + assertEquals("SET IDENTITY_INSERT mapped_table ON", buffer.toString()); + } + + @ParameterizedTest + @ValueSource(strings = {"SET IDENTITY_INSERT", "SET IDENTITY_INSERT ON", + "SET IDENTITY_INSERT dbo.actor", "SET IDENTITY_INSERT dbo.actor TRUE", + "SET IDENTITY_INSERT dbo.actor = ON", "SET IDENTITY_INSERT dbo.actor ON OFF", + "SET IDENTITY_INSERT server.sakila.dbo.actor ON", + "SET IDENTITY_INSERT dbo.actor AS a ON", "SET IDENTITY_INSERT @actor ON"}) + void rejectsMalformedDirectives(String sql) { + assertThrows(JSQLParserException.class, () -> parse(sql)); + } + + @Test + void preservesGenericSetAndRequiresSqlServerDialect() throws Exception { + String sql = "SET IDENTITY_INSERT dbo.actor ON"; + assertThrows(JSQLParserException.class, () -> CCJSqlParserUtil.parse(sql)); + assertThrows(JSQLParserException.class, + () -> CCJSqlParserUtil.parse(sql, p -> p.withDialect(Dialect.POSTGRESQL))); + for (String generic : new String[] {"SET IDENTITY_INSERT = 1", + "SET IDENTITY_INSERT.value = 1", "SET @flag = 1"}) { + assertInstanceOf(SetStatement.class, CCJSqlParserUtil.parse(generic, + p -> p.withDialect(Dialect.SQLSERVER))); + } + } + + @Test + void validatesTheCapabilityAndExistingTargetTable() throws Exception { + SetIdentityInsertStatement set = parse("SET IDENTITY_INSERT dbo.actor OFF"); + List visited = new ArrayList<>(); + DatabaseMetaDataValidation metadata = name -> { + visited.add(name); + return true; + }; + StatementValidator validator = new StatementValidator(); + validator.setContext(new ValidationContext().setCapabilities( + Arrays.asList(SqlServerVersion.V2019, PostgresqlVersion.V10, metadata))); + set.accept(validator, null); + assertFalse(validator.getValidationErrors().containsKey(SqlServerVersion.V2019)); + assertFalse(validator.getValidationErrors().containsKey(metadata)); + ValidationTestAsserts.assertNotSupported( + validator.getValidationErrors().get(PostgresqlVersion.V10), + Feature.setIdentityInsert); + assertEquals(1, visited.size()); + assertEquals(NamedObject.table, visited.get(0).getNamedObject()); + assertEquals("dbo.actor", visited.get(0).getFqn()); + } +} diff --git a/src/test/java/net/sf/jsqlparser/statement/SqlServerRoutineTest.java b/src/test/java/net/sf/jsqlparser/statement/SqlServerRoutineTest.java new file mode 100644 index 0000000000..f1644d0e3a --- /dev/null +++ b/src/test/java/net/sf/jsqlparser/statement/SqlServerRoutineTest.java @@ -0,0 +1,177 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.statement; + +import static org.junit.jupiter.api.Assertions.*; +import java.util.ArrayList; +import java.util.List; +import net.sf.jsqlparser.JSQLParserException; +import net.sf.jsqlparser.expression.ExpressionVisitorAdapter; +import net.sf.jsqlparser.expression.LongValue; +import net.sf.jsqlparser.parser.AbstractJSqlParser.Dialect; +import net.sf.jsqlparser.parser.CCJSqlParserUtil; +import net.sf.jsqlparser.parser.feature.Feature; +import net.sf.jsqlparser.statement.create.function.CreateFunction; +import net.sf.jsqlparser.statement.create.table.ColumnDefinition; +import net.sf.jsqlparser.statement.create.table.CheckConstraint; +import net.sf.jsqlparser.statement.create.table.Index; +import net.sf.jsqlparser.statement.select.SelectVisitorAdapter; +import net.sf.jsqlparser.util.TablesNamesFinder; +import net.sf.jsqlparser.util.deparser.ExpressionDeParser; +import net.sf.jsqlparser.util.deparser.SelectDeParser; +import net.sf.jsqlparser.util.deparser.StatementDeParser; +import net.sf.jsqlparser.util.validation.Validation; +import net.sf.jsqlparser.util.validation.feature.FeaturesAllowed; +import net.sf.jsqlparser.util.validation.feature.SqlServerVersion; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +class SqlServerRoutineTest { + private static CreateFunctionalStatement parse(String sql) throws Exception { + CreateFunctionalStatement statement = + (CreateFunctionalStatement) CCJSqlParserUtil.parse(sql, + p -> p.withDialect(Dialect.SQLSERVER)); + StringBuilder output = new StringBuilder(); + statement.accept(new StatementDeParser(output)); + assertEquals(statement.toString(), output.toString()); + assertEquals(statement.toString(), CCJSqlParserUtil.parse(output.toString(), + p -> p.withDialect(Dialect.SQLSERVER)).toString()); + return statement; + } + + @ParameterizedTest + @ValueSource(strings = {"CREATE", "ALTER", "CREATE OR ALTER"}) + void supportsIssue1978AndSharedOperations(String prefix) throws Exception { + CreateFunctionalStatement function = parse( + prefix + " FUNCTION getPayments() RETURNS TABLE AS RETURN SELECT * from Payments;"); + assertEquals(prefix.replace(' ', '_'), function.getOperation().name()); + assertTrue(function.getReturnType().isTable()); + assertNull(function.getReturnType().getTableElements()); + CreateFunctionalStatement procedure = parse(prefix + + " PROCEDURE SPPayment AS SET NOCOUNT ON; BEGIN SELECT * FROM Payments; END"); + assertEquals("PROCEDURE", procedure.getKind()); + assertNull(procedure.getReturnType()); + assertTrue(procedure.formatDeclaration().contains("NOCOUNT ON; BEGIN")); + assertTrue(procedure.getFeatures().modifiesSchema()); + assertThrows(UnsupportedOperationException.class, + () -> new TablesNamesFinder().getTables(procedure)); + } + + @Test + void exposesIssue715ReturnTableAndConstraintExpressions() throws Exception { + CreateFunctionalStatement statement = parse( + "CREATE OR ALTER FUNCTION dbo.f(@id int = 1) RETURNS @result TABLE (id int NOT NULL, amount decimal(10, 2), PRIMARY KEY (id), CHECK (amount > 0)) AS BEGIN INSERT INTO @result SELECT id, amount FROM payments; RETURN; END;"); + assertEquals("@result", statement.getReturnType().getTableVariable()); + assertEquals(4, statement.getReturnType().getTableElements().size()); + List columns = + statement.getReturnType().getTableElements(ColumnDefinition.class); + assertEquals(2, columns.size()); + assertEquals(2, statement.getReturnType().getTableElements(Index.class).size()); + assertEquals("id", columns.get(0).getColumnName()); + List visited = new ArrayList<>(); + ExpressionVisitorAdapter expressions = new ExpressionVisitorAdapter() { + @Override + public Void visit(LongValue value, S context) { + assertEquals("ctx", context); + visited.add(value.getValue()); + return null; + } + }; + statement.accept(new StatementVisitorAdapter<>(new SelectVisitorAdapter<>(expressions)), + "ctx"); + assertEquals(List.of(0L), visited); + columns.get(0).setColumnName("payment_id"); + statement.getReturnType().setTableVariable("@rows"); + assertTrue(statement.toString().contains("RETURNS @rows TABLE (payment_id int NOT NULL")); + CheckConstraint check = + statement.getReturnType().getTableElements(CheckConstraint.class).get(0); + check.setExpression(CCJSqlParserUtil.parseCondExpression("amount > 10")); + StringBuilder output = new StringBuilder(); + ExpressionDeParser deparser = new ExpressionDeParser() { + @Override + public StringBuilder visit(LongValue value, S context) { + return getBuilder().append(value.getValue() + 1); + } + }; + statement.accept(new StatementDeParser(deparser, new SelectDeParser(), output)); + assertTrue(output.toString().contains("CHECK (amount > 11)")); + parse(output.toString()); + } + + @ParameterizedTest + @ValueSource(strings = { + "CREATE FUNCTION dbo.f() RETURNS int AS BEGIN RETURN CASE WHEN 1 = 1 THEN 1 ELSE 0 END; END;", + "ALTER FUNCTION dbo.f() RETURNS @r TABLE (id int) AS BEGIN BEGIN INSERT INTO @r SELECT 1; END; RETURN; END;", + "CREATE FUNCTION dbo.f() RETURNS TABLE (id int) AS EXTERNAL NAME assembly.class.method;" + }) + void preservesScalarClrAndNestedBodies(String sql) throws Exception { + parse(sql); + } + + @Test + void retainsFollowingStatements() throws Exception { + for (String sql : List.of( + "ALTER FUNCTION f() RETURNS TABLE AS RETURN SELECT 1 AS id; SELECT 2;", + "ALTER FUNCTION f() RETURNS int AS BEGIN RETURN CASE WHEN 1=1 THEN 1 END; END; SELECT 2;")) { + assertEquals(2, CCJSqlParserUtil + .parseStatements(sql, p -> p.withDialect(Dialect.SQLSERVER)).size()); + } + } + + @Test + void preservesTheEntireProcedureBatch() throws Exception { + String sql = "ALTER PROCEDURE p AS BEGIN TRY BEGIN TRANSACTION; SELECT 1; COMMIT; END TRY; " + + "BEGIN CATCH ROLLBACK; END CATCH; SELECT 2;"; + CreateFunctionalStatement statement = parse(sql); + assertTrue(statement.formatDeclaration().endsWith("END CATCH; SELECT 2;")); + assertEquals(1, CCJSqlParserUtil.parseStatements(sql, p -> p.withDialect(Dialect.SQLSERVER)) + .size()); + } + + @Test + void keepsDefaultAndOtherDialectsUnchanged() throws Exception { + CreateFunction old = (CreateFunction) CCJSqlParserUtil + .parse("CREATE FUNCTION f() RETURNS @r TABLE (id int) AS BEGIN RETURN; END;"); + assertNull(old.getReturnType()); + String pg = "CREATE OR REPLACE FUNCTION f() RETURNS int AS $$ SELECT 1; $$ LANGUAGE sql;"; + assertTrue(CCJSqlParserUtil.parse(pg, p -> p.withDialect(Dialect.POSTGRESQL)).toString() + .startsWith("CREATE OR REPLACE FUNCTION")); + assertThrows(JSQLParserException.class, () -> CCJSqlParserUtil + .parse("CREATE OR ALTER FUNCTION f() RETURNS TABLE AS RETURN SELECT 1")); + } + + @ParameterizedTest + @ValueSource(strings = {"CREATE FUNCTION f() AS RETURN 1", + "ALTER FUNCTION f() RETURNS @r TABLE AS BEGIN RETURN; END", + "CREATE FUNCTION f() RETURNS @r TABLE () AS BEGIN RETURN; END", + "ALTER FUNCTION f() RETURNS int AS BEGIN RETURN 1;", + "ALTER FUNCTION f() RETURNS TABLE"}) + void rejectsIncompleteDeclarations(String sql) { + assertThrows(JSQLParserException.class, () -> parse(sql)); + } + + @Test + void validatesOperationCapabilities() { + for (String prefix : List.of("CREATE", "ALTER", "CREATE OR ALTER")) { + assertTrue(new Validation( + CCJSqlParserUtil.newParser("SELECT 1").withDialect(Dialect.SQLSERVER) + .getConfiguration(), + List.of(SqlServerVersion.V2019), + prefix + " FUNCTION f() RETURNS TABLE AS RETURN SELECT 1").validate() + .isEmpty()); + } + assertFalse(new Validation( + CCJSqlParserUtil.newParser("SELECT 1").withDialect(Dialect.SQLSERVER) + .getConfiguration(), + List.of(new FeaturesAllowed(Feature.functionalStatement, Feature.createFunction)), + "ALTER FUNCTION f() RETURNS TABLE AS RETURN SELECT 1").validate().isEmpty()); + } +} diff --git a/src/test/java/net/sf/jsqlparser/statement/SqlServerSetOptionsTest.java b/src/test/java/net/sf/jsqlparser/statement/SqlServerSetOptionsTest.java new file mode 100644 index 0000000000..4b7b3f62fa --- /dev/null +++ b/src/test/java/net/sf/jsqlparser/statement/SqlServerSetOptionsTest.java @@ -0,0 +1,157 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.statement; + +import java.util.Arrays; +import java.util.Collections; +import net.sf.jsqlparser.JSQLParserException; +import net.sf.jsqlparser.expression.LongValue; +import net.sf.jsqlparser.expression.operators.relational.ExpressionList; +import net.sf.jsqlparser.parser.AbstractJSqlParser.Dialect; +import net.sf.jsqlparser.parser.CCJSqlParserUtil; +import net.sf.jsqlparser.parser.feature.Feature; +import net.sf.jsqlparser.statement.SetStatement.OnOffOption; +import net.sf.jsqlparser.statement.SetStatement.OnOffOptions; +import net.sf.jsqlparser.util.TablesNamesFinder; +import net.sf.jsqlparser.util.deparser.ExpressionDeParser; +import net.sf.jsqlparser.util.deparser.SetStatementDeParser; +import net.sf.jsqlparser.util.deparser.StatementDeParser; +import net.sf.jsqlparser.util.validation.ValidationContext; +import net.sf.jsqlparser.util.validation.ValidationTestAsserts; +import net.sf.jsqlparser.util.validation.feature.PostgresqlVersion; +import net.sf.jsqlparser.util.validation.feature.SqlServerVersion; +import net.sf.jsqlparser.util.validation.validator.SetStatementValidator; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; +import org.junit.jupiter.params.provider.ValueSource; + +import static org.junit.jupiter.api.Assertions.*; + +class SqlServerSetOptionsTest { + private SetStatement parse(String sql) throws JSQLParserException { + return (SetStatement) CCJSqlParserUtil.parse(sql, + p -> p.withDialect(Dialect.SQLSERVER).withUnsupportedStatements(false)); + } + + private void assertRoundTrip(SetStatement set, String sql) throws Exception { + assertEquals(sql, set.toString()); + StringBuilder buffer = new StringBuilder(); + set.accept(new StatementDeParser(buffer), null); + assertEquals(sql, buffer.toString()); + assertEquals(sql, parse(buffer.toString()).toString()); + } + + @ParameterizedTest + @EnumSource(OnOffOption.class) + void parsesBothStatesAsOptions(OnOffOption option) throws Exception { + for (boolean on : new boolean[] {true, false}) { + String sql = "SET " + option + (on ? " ON" : " OFF"); + SetStatement set = parse(sql); + assertEquals(Collections.singletonList(option), set.getOnOffOptions().getOptions()); + assertEquals(on, set.getOnOffOptions().isOn()); + assertEquals(0, set.getCount()); + assertRoundTrip(set, sql); + } + } + + @Test + void retainsGroupedOptionsAndEditsTheirSharedValue() throws Exception { + SetStatement set = parse("set quoted_identifier, /* group */ ansi_nulls on"); + assertEquals(Arrays.asList(OnOffOption.QUOTED_IDENTIFIER, OnOffOption.ANSI_NULLS), + set.getOnOffOptions().getOptions()); + set.getOnOffOptions().getOptions().set(0, OnOffOption.NOCOUNT); + set.getOnOffOptions().setOn(false); + assertRoundTrip(set, "SET NOCOUNT, ANSI_NULLS OFF"); + assertTrue(new TablesNamesFinder().getTables(set).isEmpty()); + assertEquals(3, CCJSqlParserUtil.parseStatements( + "SET NOCOUNT ON; SET XACT_ABORT OFF; SELECT 1;", + p -> p.withDialect(Dialect.SQLSERVER)).size()); + Object context = new Object(); + assertSame(context, set.accept(new StatementVisitorAdapter() { + @Override + public Object visit(SetStatement statement, S value) { + assertSame(set, statement); + return value; + } + }, context)); + } + + @ParameterizedTest + @ValueSource(strings = {"SET NOCOUNT", "SET NOCOUNT TRUE", "SET NOCOUNT 1", + "SET NOCOUNT, ON", "SET NOCOUNT,, ANSI_NULLS ON", "SET NOCOUNT, unknown ON", + "SET NOCOUNT ON OFF", "SET NOCOUNT ON, ANSI_NULLS OFF"}) + void rejectsIncompleteOrMixedOptionGroups(String sql) { + assertThrows(JSQLParserException.class, () -> parse(sql)); + } + + @Test + void retainsGenericAssignmentsAndDialectIsolation() throws Exception { + for (String sql : new String[] {"SET NOCOUNT = 1", "SET NOCOUNT.value = 1", + "SET v = 1, c = 3", "SET v = 1, 3", "SET @Flag = 1", + "SET LOCAL Time Zone 'UTC'", "SET standard_conforming_strings = on"}) { + SetStatement set = parse(sql); + assertNull(set.getOnOffOptions()); + assertEquals(CCJSqlParserUtil.parse(sql).toString(), set.toString()); + assertRoundTrip(set, set.toString()); + } + assertThrows(JSQLParserException.class, () -> CCJSqlParserUtil.parse("SET NOCOUNT ON")); + assertThrows(JSQLParserException.class, () -> CCJSqlParserUtil.parse("SET NOCOUNT ON", + p -> p.withDialect(Dialect.POSTGRESQL))); + SetStatement generic = (SetStatement) CCJSqlParserUtil.parse("SET NOCOUNT OFF"); + assertNull(generic.getOnOffOptions()); + assertEquals(1, generic.getCount()); + } + + @Test + void sharesPunctuationWithoutBypassingCustomExpressionDeparsers() throws Exception { + SetStatement set = parse("SET v = 1, 3, c = 5"); + StringBuilder buffer = new StringBuilder(); + ExpressionDeParser expressions = new ExpressionDeParser() { + @Override + public StringBuilder visit(LongValue value, S context) { + return getBuilder().append(value.getValue() + 10); + } + }; + expressions.setBuilder(buffer); + new SetStatementDeParser(expressions, buffer).deParse(set); + assertEquals("SET v = 11, 13, c = 15", buffer.toString()); + } + + @Test + void switchesBetweenAssignmentAndOptionForms() throws Exception { + SetStatement set = parse("SET LOCAL x = 1"); + set.setOnOffOptions(new OnOffOptions(Collections.singleton(OnOffOption.NOCOUNT), true)); + assertNull(set.getEffectParameter()); + assertRoundTrip(set, "SET NOCOUNT ON"); + set.add("x", new ExpressionList<>(new LongValue(2)), true); + assertNull(set.getOnOffOptions()); + assertRoundTrip(set, "SET x = 2"); + set.setOnOffOptions(new OnOffOptions(Collections.singleton(OnOffOption.XACT_ABORT), false)); + set.clear(); + assertNull(set.getOnOffOptions()); + assertEquals(0, set.getCount()); + assertThrows(IllegalArgumentException.class, + () -> new OnOffOptions(Collections.emptyList(), true)); + } + + @Test + void validatesSqlServerCapabilitySeparatelyFromGenericSet() throws Exception { + SetStatement set = parse("SET ANSI_NULLS, QUOTED_IDENTIFIER ON"); + SetStatementValidator validator = new SetStatementValidator(); + validator.setContext(new ValidationContext().setCapabilities( + Arrays.asList(SqlServerVersion.V2019, PostgresqlVersion.V10))); + validator.validate(set); + assertFalse(validator.getValidationErrors().containsKey(SqlServerVersion.V2019)); + ValidationTestAsserts.assertNotSupported( + validator.getValidationErrors().get(PostgresqlVersion.V10), + Feature.sqlServerSetOptions); + } +} diff --git a/src/test/java/net/sf/jsqlparser/statement/StatementFeatureVisitorTest.java b/src/test/java/net/sf/jsqlparser/statement/StatementFeatureVisitorTest.java new file mode 100644 index 0000000000..95490e7532 --- /dev/null +++ b/src/test/java/net/sf/jsqlparser/statement/StatementFeatureVisitorTest.java @@ -0,0 +1,501 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +/*- + * Copyright (C) 2004 - 2026 JSQLParser + * Licensed under the Apache License, Version 2.0. + */ +package net.sf.jsqlparser.statement; + +import net.sf.jsqlparser.JSQLParserException; +import net.sf.jsqlparser.parser.CCJSqlParserUtil; +import net.sf.jsqlparser.statement.delete.Delete; +import net.sf.jsqlparser.schema.Column; +import net.sf.jsqlparser.statement.select.SelectItem; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import java.util.Collections; +import java.util.EnumSet; +import java.util.List; +import java.util.Set; +import java.util.function.Predicate; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * The interesting assertions here are the negative ones. Any naive implementation gets + * {@code SELECT} and {@code CREATE TABLE} right; what separates a correct analysis from a flag + * union is that it says no to a result set for {@code INSERT .. SELECT} and to a mutation + * for a pure read. + */ +class StatementFeatureVisitorTest { + + private static StatementFeatures analyse(String sql) throws JSQLParserException { + return CCJSqlParserUtil.parse(sql).getFeatures(); + } + + private static StatementFeatures analyse(String sql, String... pureFunctions) + throws JSQLParserException { + Set pure = Set.of(pureFunctions); + return StatementFeatureVisitor.analyse(CCJSqlParserUtil.parse(sql), pure::contains); + } + + // ---- invariants ------------------------------------------------------------------------- + + /** + *

What this pins down

+ * + * {@link StatementFeatures} splits its verdict into two sets: + * + *
    + *
  • {@code getCertain()} - the grammar proves the statement has this feature;
  • + *
  • {@code getUncertain()} - the grammar could not exclude it, but could not prove + * it either.
  • + *
+ * + * For the second set to be readable at all, it has to mean "unproven and nothing more". + * If a feature could appear in both sets, then {@code getUncertain()} would answer the question + * "is anything doubtful here?" with a yes even when the feature is settled, and printing it in + * a rejection message - which is exactly what the read-only guard in the README does - would + * report a proven mutation as a doubt. So the constructor normalises: + * {@code uncertain.removeAll(certain)}. This test is that normalisation's only guard. + * + *

Why the overlap is not hypothetical

+ * + * {@code UPDATE t SET a = f(b)} produces it on every run: + * + *
    + *
  • {@code visit(Update)} adds {@code MODIFIES_DATA} to certain;
  • + *
  • {@code FeatureExpressionVisitor#visit(Function)} cannot prove {@code f} pure, so it adds + * {@code MODIFIES_DATA} and {@code MODIFIES_SCHEMA} to uncertain.
  • + *
+ * + * Raw, that is {@code certain={MODIFIES_DATA}}, {@code uncertain={MODIFIES_DATA, + * MODIFIES_SCHEMA}}. Normalised, {@code uncertain} must come out as {@code {MODIFIES_SCHEMA}} + * alone - the schema change is the only open question; the data change is settled. + * + *

Why the previous version of this test was broken

+ * + * It looped over four statements asserting + * {@code assertThat(getCertain()).doesNotContainAnyElementsOf(getUncertain())}. Two problems: + * + *
    + *
  1. It threw. AssertJ rejects an empty argument to {@code doesNotContainAnyElementsOf} + * with "The iterable of values to look for should not be empty" - an + * {@code IllegalArgumentException} about the assertion's own inputs, not a failed expectation. + * {@code SELECT a FROM t} has nothing uncertain, so the first iteration blew up before testing + * anything.
  2. + *
  3. Three of the four inputs were dead weight. Only the {@code f(b)} case can ever + * produce an overlap; for the others the assertion held vacuously. A loop over inputs that + * cannot exercise the invariant hides the one input that can.
  4. + *
+ * + * Both are fixed below by intersecting explicitly (an empty intersection is a legitimate + * result, not a malformed assertion) and by testing the case that actually overlaps. + */ + @Test + @DisplayName("a proven feature is never also reported as merely possible") + void certainAndUncertainAreDisjoint() throws JSQLParserException { + StatementFeatures f = analyse("UPDATE t SET a = f(b)"); + + // the overlap the constructor has to resolve + assertThat(f.getCertain()).contains(StmtFeature.MODIFIES_DATA); + assertThat(f.getUncertain()) + .as("MODIFIES_DATA is proven, so it must not also be listed as doubtful") + .doesNotContain(StmtFeature.MODIFIES_DATA) + .as("the unproven function leaves the schema question genuinely open") + .contains(StmtFeature.MODIFIES_SCHEMA); + + assertDisjoint(f, "UPDATE t SET a = f(b)"); + } + + @Test + @DisplayName("the invariant holds for statements with nothing uncertain too") + void disjointWhenNothingIsUncertain() throws JSQLParserException { + for (String sql : new String[] {"SELECT a FROM t", "DELETE FROM t RETURNING *", + "CREATE TABLE t (a INT)", "UPDATE t SET a = f(b)"}) { + assertDisjoint(analyse(sql), sql); + } + } + + @Test + @DisplayName("normalisation happens in the constructor, not by luck of traversal order") + void constructorNormalisesOverlappingSets() { + // hand-built overlap, bypassing the visitor entirely: whatever order the visitor happens + // to add features in, the value object must still come out normalised + StatementFeatures f = new StatementFeatures( + EnumSet.of(StmtFeature.MODIFIES_DATA, StmtFeature.READS_DATA), + EnumSet.of(StmtFeature.MODIFIES_DATA, StmtFeature.MODIFIES_SCHEMA), + Set.of("f")); + + assertThat(f.getCertain()) + .containsExactlyInAnyOrder(StmtFeature.MODIFIES_DATA, StmtFeature.READS_DATA); + assertThat(f.getUncertain()).containsExactly(StmtFeature.MODIFIES_SCHEMA); + + // and the two accessors stay consistent with the predicates + assertThat(f.is(StmtFeature.MODIFIES_DATA)).isTrue(); + assertThat(f.may(StmtFeature.MODIFIES_DATA)).isTrue(); + assertThat(f.is(StmtFeature.MODIFIES_SCHEMA)).isFalse(); + assertThat(f.may(StmtFeature.MODIFIES_SCHEMA)).isTrue(); + } + + /** + * Intersects the two sets and asserts the result is empty. + * + *

+ * Deliberately not {@code doesNotContainAnyElementsOf}: that assertion treats an empty argument + * as caller error and throws {@code IllegalArgumentException}, which turns the perfectly valid + * case "this statement has no uncertainty at all" into a test error. An explicit intersection + * has no such precondition - empty in, empty out, assertion passes. + */ + private static void assertDisjoint(StatementFeatures f, String description) { + EnumSet overlap = EnumSet.noneOf(StmtFeature.class); + overlap.addAll(f.getCertain()); + overlap.retainAll(f.getUncertain()); + + assertThat(overlap).as("features reported as both proven and doubtful for: %s", description) + .isEmpty(); + } + + @Test + @DisplayName("is() implies may(), never the reverse") + void certainImpliesPossible() throws JSQLParserException { + StatementFeatures f = analyse("DELETE FROM t WHERE a = f(1)"); + for (StmtFeature feature : StmtFeature.values()) { + assertThat(!f.is(feature) || f.may(feature)).as(feature.name()).isTrue(); + } + } + + @Test + @DisplayName("not cached: mutating the AST changes the verdict") + void notCached() throws JSQLParserException { + Statement statement = CCJSqlParserUtil.parse("DELETE FROM t WHERE a = 1"); + assertThat(statement.getFeatures().returnsResultSet()).isFalse(); + + ReturningClause returning = new ReturningClause(ReturningClause.Keyword.RETURNING, + List.of(new SelectItem<>(new Column("id")))); + ((Delete) statement).setReturningClause(returning); + + assertThat(statement.getFeatures().returnsResultSet()).isTrue(); + } + + // ---- plain cases ------------------------------------------------------------------------ + + @Test + void plainSelectReadsAndReturns() throws JSQLParserException { + StatementFeatures f = analyse("SELECT a FROM t"); + assertThat(f.returnsResultSet()).isTrue(); + assertThat(f.is(StmtFeature.READS_DATA)).isTrue(); + assertThat(f.mayModifyData()).isFalse(); + assertThat(f.modifiesSchema()).isFalse(); + } + + @Test + void insertValuesMutatesOnly() throws JSQLParserException { + StatementFeatures f = analyse("INSERT INTO t (a) VALUES (1)"); + assertThat(f.modifiesData()).isTrue(); + assertThat(f.returnsResultSet()).isFalse(); + } + + /** + * DDL that only touches the catalogue. No rows are read, written or returned. + */ + @Test + void nonDestructiveDdlTouchesTheSchemaOnly() throws JSQLParserException { + for (String sql : new String[] {"CREATE TABLE t (a INT)", "ALTER TABLE t ADD COLUMN b INT", + "GRANT SELECT ON t TO u", "CREATE INDEX i ON t (a)", "COMMENT ON TABLE t IS 'x'"}) { + StatementFeatures f = analyse(sql); + assertThat(f.modifiesSchema()).as(sql).isTrue(); + assertThat(f.returnsResultSet()).as(sql).isFalse(); + assertThat(f.mayModifyData()).as(sql).isFalse(); + } + } + + /** + * DROP and TRUNCATE are DDL by every other measure - implicit commit, no row triggers, + * catalogue change - but they destroy rows, so they carry {@code MODIFIES_DATA} as well. + * + *

+ * This is a deliberate call, not an oversight. The failure mode it prevents: + * + *

+     * if (connection.isReadOnly() && features.mayModifyData()) {
+     *     throw new SQLException("read-only");
+     * }
+     * 
+ * + * A guard written like that - and plenty are, because "modifies data" reads as the obvious + * check - would wave {@code DROP TABLE t} straight through if DROP were classified as schema + * only. Making the destructive DDL admit to destroying data means the naive guard is still + * correct. + * + *

+ * The cost is on the other side: a caller counting "did this write rows?" for auditing gets a + * yes for DROP, which is arguably wrong - no rows were written, they were removed along with + * the table. If that matters more to you than the guard case, move DROP (not TRUNCATE, which + * really is a bulk delete) back to schema-only and require guards to check both predicates. + */ + @Test + void destructiveDdlAlsoModifiesData() throws JSQLParserException { + for (String sql : new String[] {"DROP TABLE t", "TRUNCATE TABLE t"}) { + StatementFeatures f = analyse(sql); + assertThat(f.modifiesSchema()).as(sql).isTrue(); + assertThat(f.modifiesData()).as(sql).isTrue(); + assertThat(f.returnsResultSet()).as(sql).isFalse(); + } + } + + // ---- the cases a flag union gets wrong -------------------------------------------------- + + @Test + @DisplayName("INSERT .. SELECT contains a Select but returns no result set") + void insertSelectDoesNotReturnResultSet() throws JSQLParserException { + StatementFeatures f = analyse("INSERT INTO x (a) SELECT a FROM t"); + assertThat(f.modifiesData()).isTrue(); + assertThat(f.is(StmtFeature.READS_DATA)).isTrue(); + assertThat(f.returnsResultSet()).isFalse(); + assertThat(f.may(StmtFeature.RETURNS_RESULT_SET)).isFalse(); + } + + @Test + @DisplayName("data-modifying CTE: top-level SELECT returns rows, DELETE still mutates") + void dataModifyingCteAtTopLevel() throws JSQLParserException { + StatementFeatures f = + analyse("WITH c AS (DELETE FROM t RETURNING *) SELECT * FROM c"); + assertThat(f.returnsResultSet()).isTrue(); + assertThat(f.modifiesData()).isTrue(); + } + + @Test + @DisplayName("RETURNING inside a nested CTE of an INSERT returns nothing to the client") + void nestedReturningIsNotAResultSet() throws JSQLParserException { + StatementFeatures f = analyse( + "INSERT INTO x WITH c AS (DELETE FROM t RETURNING *) SELECT * FROM c"); + assertThat(f.modifiesData()).isTrue(); + assertThat(f.returnsResultSet()).isFalse(); + } + + @Test + void dmlWithReturningIsBoth() throws JSQLParserException { + StatementFeatures f = analyse("DELETE FROM t WHERE a = 1 RETURNING id, name"); + assertThat(f.modifiesData()).isTrue(); + assertThat(f.returnsResultSet()).isTrue(); + } + + // ---- pending: requires the expression / from-item traversal to be wired ----------------- + + @Nested + @DisplayName("subqueries and function purity") + class PendingExpressionWiring { + + @Test + void subqueryInWhereDoesNotAddAResultSet() throws JSQLParserException { + StatementFeatures f = analyse("UPDATE t SET a = 1 WHERE b IN (SELECT b FROM u)"); + assertThat(f.modifiesData()).isTrue(); + assertThat(f.is(StmtFeature.READS_DATA)).isTrue(); + assertThat(f.returnsResultSet()).isFalse(); + } + + @Test + void scalarSubqueryInSelectListStaysOneResultSet() throws JSQLParserException { + StatementFeatures f = analyse("SELECT (SELECT max(x) FROM u) AS m FROM t"); + assertThat(f.returnsResultSet()).isTrue(); + assertThat(f.mayModifyData()).isTrue(); // max() unproven + } + + @Test + void unprovenFunctionIsPossibleNotCertain() throws JSQLParserException { + StatementFeatures f = analyse("SELECT nextval('s')"); + assertThat(f.modifiesData()).isFalse(); + assertThat(f.mayModifyData()).isTrue(); + assertThat(f.getUnresolvedReferences()).contains("nextval"); + } + + @Test + void allowListCollapsesTheUncertainty() throws JSQLParserException { + StatementFeatures f = analyse("SELECT upper(name) FROM t", "upper"); + assertThat(f.mayModifyData()).isFalse(); + assertThat(f.getUnresolvedReferences()).isEmpty(); + } + } + + @Nested + @DisplayName("DDL and opaque statements") + class PendingStatementOverrides { + + @Test + void ctasIsSchemaAndReadButNotAResultSet() throws JSQLParserException { + StatementFeatures f = analyse("CREATE TABLE t AS SELECT a FROM u"); + assertThat(f.modifiesSchema()).isTrue(); + assertThat(f.is(StmtFeature.READS_DATA)).isTrue(); + assertThat(f.returnsResultSet()).isFalse(); + } + + @Test + void callIsOpaqueAndFailsSafeForGuardsOnly() throws JSQLParserException { + StatementFeatures f = analyse("CALL do_something(1)"); + assertThat(f.isOpaque()).isTrue(); + assertThat(f.mayModifyData()).isTrue(); + assertThat(f.modifiesData()).isFalse(); // guards block it, dispatchers are not misled + assertThat(f.getUnresolvedReferences()).isNotEmpty(); + } + } + + // ---- the failure mode that must stay loud ---------------------------------------------- + + @Test + @DisplayName("an unclassified statement type degrades to OPAQUE, never to harmless") + void unknownStatementTypeIsOpaque() { + Statement unknown = new Statement() { + @Override + public T accept(net.sf.jsqlparser.statement.StatementVisitor visitor, + S context) { + return null; + } + }; + StatementFeatures f = StatementFeatureVisitor.analyse(unknown, + Collections.emptySet()::contains); + assertThat(f.isOpaque()).isTrue(); + assertThat(f.mayModifyData()).isTrue(); + assertThat(f.modifiesData()).isFalse(); + } + + @Test + @DisplayName("purity predicate is consulted, not bypassed") + void purityPredicateIsUsed() throws JSQLParserException { + Predicate nothingIsPure = name -> false; + StatementFeatures f = StatementFeatureVisitor + .analyse(CCJSqlParserUtil.parse("SELECT a FROM t"), nothingIsPure); + assertThat(f.mayModifyData()).isFalse(); // no function call at all -> still clean + } + + // ---- cases the adapter wiring makes reachable ------------------------------------------- + + @Test + @DisplayName("data-modifying CTE inside a subquery does not blow up the select path") + void dataModifyingCteInsideASubquery() throws JSQLParserException { + // SelectVisitorAdapter#visit(WithItem) would ClassCastException here via getSelect() + StatementFeatures f = analyse( + "SELECT * FROM u WHERE id IN (" + + "WITH c AS (DELETE FROM t RETURNING id) SELECT id FROM c)"); + assertThat(f.returnsResultSet()).isTrue(); + assertThat(f.modifiesData()).isTrue(); + } + + @Test + @DisplayName("SELECT .. INTO materialises rows instead of returning them") + void selectIntoIsNotAResultSet() throws JSQLParserException { + StatementFeatures f = analyse("SELECT a INTO newtable FROM t"); + assertThat(f.returnsResultSet()).isFalse(); + assertThat(f.modifiesData()).isTrue(); + assertThat(f.modifiesSchema()).isTrue(); + } + + /** + *

Why the SQL looks the way it does

+ * + * {@code DELETE FROM t OUTPUT DELETED.id} does not parse. In {@code JSqlParserCC.jjt} + * the DELETE production only offers the OUTPUT clause inside the optional + * {@code (tables [OUTPUT ..] | )} group - i.e. the T-SQL + * {@code DELETE OUTPUT .. FROM } shape, where the target precedes OUTPUT and + * FROM follows it. Put OUTPUT after {@code FROM t} and there is no production for it. + * + *

+ * The strings below are lifted from the upstream {@code DeleteTest#testDeleteOutputClause}, so + * they are known to parse. + * + *

Why the INTO case uses a table variable

+ * + * The grammar is {@code ( UserVariable() | Table() )}. The {@code @MyTableVar} form + * populates {@code getTableVariable()} and leaves {@code getOutputTable()} null - which is + * exactly the case an implementation checking only {@code getOutputTable()} gets wrong. + */ + @Test + @DisplayName("OUTPUT returns rows; OUTPUT .. INTO redirects them") + void outputClauseDistinction() throws JSQLParserException { + StatementFeatures toClient = + analyse("DELETE Sales.ShoppingCartItem OUTPUT DELETED.* FROM Sales"); + assertThat(toClient.returnsResultSet()).isTrue(); + + StatementFeatures toTableVariable = analyse("DELETE Production.ProductProductPhoto " + + "OUTPUT DELETED.ProductID INTO @MyTableVar " + + "FROM Production.ProductProductPhoto AS ph"); + assertThat(toTableVariable.returnsResultSet()) + .as("rows go into @MyTableVar, not down the wire") + .isFalse(); + assertThat(toTableVariable.modifiesData()).isTrue(); + } + + @Test + @DisplayName("CREATE VIEW references tables without reading them") + void createViewDoesNotRead() throws JSQLParserException { + StatementFeatures f = analyse("CREATE VIEW v AS SELECT a FROM t"); + assertThat(f.modifiesSchema()).isTrue(); + assertThat(f.is(StmtFeature.READS_DATA)).isFalse(); + assertThat(f.returnsResultSet()).isFalse(); + } + + @Test + @DisplayName("script: the aggregate verdict is a union over all statements") + void scriptAggregateIsAUnion() throws JSQLParserException { + Statements script = + CCJSqlParserUtil.parseStatements("UPDATE t SET a = 1; SELECT a FROM t;"); + + StatementFeatures f = StatementFeatureVisitor.analyse(script); + + // useful for "may this script write?" - useless for executeQuery/executeUpdate, because + // the union does not say which statement returns the rows + assertThat(f.modifiesData()).isTrue(); + assertThat(f.returnsResultSet()).isTrue(); + } + + @Test + @DisplayName("script: each statement keeps its own result position") + void scriptStatementsAreIndependentlyTopLevel() throws JSQLParserException { + Statements script = + CCJSqlParserUtil.parseStatements("UPDATE t SET a = 1; SELECT a FROM t;"); + + List each = StatementFeatureVisitor.analyseEach(script); + + assertThat(each).hasSize(2); + + // the UPDATE writes and returns nothing + assertThat(each.get(0).modifiesData()).isTrue(); + assertThat(each.get(0).returnsResultSet()).isFalse(); + + // the SELECT returns rows and writes nothing - no leakage from the statement before it + assertThat(each.get(1).returnsResultSet()).isTrue(); + assertThat(each.get(1).mayModifyData()).isFalse(); + } + + @Test + @DisplayName("SELECT 1 reads nothing") + void selectWithoutFromReadsNothing() throws JSQLParserException { + StatementFeatures f = analyse("SELECT 1"); + assertThat(f.returnsResultSet()).isTrue(); + assertThat(f.is(StmtFeature.READS_DATA)).isFalse(); + } + + @Test + @DisplayName("SELECT .. FOR UPDATE takes locks") + void selectForUpdateTouchesTheTransaction() throws JSQLParserException { + StatementFeatures f = analyse("SELECT a FROM t FOR UPDATE"); + assertThat(f.is(StmtFeature.MODIFIES_TRANSACTION)).isTrue(); + } + + @Test + @DisplayName("EXPLAIN returns rows and may execute (EXPLAIN ANALYZE)") + void explainIsUncertain() throws JSQLParserException { + StatementFeatures f = analyse("EXPLAIN DELETE FROM t"); + assertThat(f.returnsResultSet()).isTrue(); + assertThat(f.mayModifyData()).isTrue(); + } +} diff --git a/src/test/java/net/sf/jsqlparser/statement/UnsupportedStatementTest.java b/src/test/java/net/sf/jsqlparser/statement/UnsupportedStatementTest.java index 4ff4c83336..a7a7b6d4d0 100644 --- a/src/test/java/net/sf/jsqlparser/statement/UnsupportedStatementTest.java +++ b/src/test/java/net/sf/jsqlparser/statement/UnsupportedStatementTest.java @@ -9,12 +9,15 @@ */ package net.sf.jsqlparser.statement; +import net.sf.jsqlparser.statement.create.domain.CreateDomain; + import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertInstanceOf; import static org.junit.jupiter.api.Assertions.assertTrue; import net.sf.jsqlparser.JSQLParserException; import net.sf.jsqlparser.parser.CCJSqlParserUtil; +import net.sf.jsqlparser.statement.create.trigger.CreateTrigger; import net.sf.jsqlparser.statement.select.Select; import net.sf.jsqlparser.test.TestUtils; import org.junit.jupiter.api.Assertions; @@ -134,12 +137,12 @@ void testCreate() throws JSQLParserException { String sqlStr = "create trigger stud_marks before INSERT on Student for each row set Student.total = Student.subj1 + Student.subj2, Student.per = Student.total * 60 / 100"; Statement statement = TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true); - assertTrue(statement instanceof UnsupportedStatement); + assertInstanceOf(CreateTrigger.class, statement); sqlStr = "create domain TNOTIFICATION_ACTION as ENUM ('ADD', 'CHANGE', 'DEL')"; statement = TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true); - assertTrue(statement instanceof UnsupportedStatement); + assertInstanceOf(CreateDomain.class, statement); } @Test diff --git a/src/test/java/net/sf/jsqlparser/statement/alter/AlterColumnDefaultExpressionTest.java b/src/test/java/net/sf/jsqlparser/statement/alter/AlterColumnDefaultExpressionTest.java new file mode 100644 index 0000000000..07cbcc8972 --- /dev/null +++ b/src/test/java/net/sf/jsqlparser/statement/alter/AlterColumnDefaultExpressionTest.java @@ -0,0 +1,101 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.statement.alter; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; + +import java.util.ArrayList; +import java.util.List; +import net.sf.jsqlparser.JSQLParserException; +import net.sf.jsqlparser.expression.ExpressionVisitorAdapter; +import net.sf.jsqlparser.expression.LongValue; +import net.sf.jsqlparser.parser.CCJSqlParserUtil; +import net.sf.jsqlparser.statement.StatementVisitorAdapter; +import net.sf.jsqlparser.statement.alter.AlterExpression.ColumnSetDefault; +import net.sf.jsqlparser.statement.select.SelectVisitorAdapter; +import net.sf.jsqlparser.util.deparser.ExpressionDeParser; +import net.sf.jsqlparser.util.deparser.SelectDeParser; +import net.sf.jsqlparser.util.deparser.StatementDeParser; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +class AlterColumnDefaultExpressionTest { + @ParameterizedTest + @ValueSource(strings = {"(1 + 2)", "NULL", "CURRENT_TIMESTAMP", + "nextval('app.counter'::regclass)", "'value'::text", "-1"}) + void parsedDefaultRetainsExpressionAndLegacyText(String value) throws JSQLParserException { + Alter alter = parse(value); + ColumnSetDefault column = + alter.getAlterExpressions().get(0).getColumnSetDefaultList().get(0); + assertNotNull(column.getDefaultExpression()); + assertEquals(column.getDefaultExpression().toString(), column.getDefaultValue()); + StringBuilder buffer = new StringBuilder(); + alter.accept(new StatementDeParser(buffer), null); + assertEquals(alter.toString(), buffer.toString()); + assertEquals(alter.toString(), CCJSqlParserUtil.parse(buffer.toString()).toString()); + } + + @Test + void visitorReachesDefaultsInEachAlterActionWithContext() throws JSQLParserException { + List values = new ArrayList<>(); + ExpressionVisitorAdapter expressions = new ExpressionVisitorAdapter() { + @Override + public Void visit(LongValue value, S context) { + assertEquals("context", context); + values.add(value.getValue()); + return null; + } + }; + CCJSqlParserUtil.parse("ALTER TABLE t ALTER COLUMN a SET DEFAULT (1 + 2), " + + "ALTER COLUMN b SET DEFAULT 3") + .accept(new StatementVisitorAdapter<>(new SelectVisitorAdapter<>(expressions)), + "context"); + assertThat(values).containsExactly(1L, 2L, 3L); + } + + @Test + void customDeparserAndAstEditsUseTheStructuredDefault() throws JSQLParserException { + Alter alter = parse("(1 + 2)"); + StringBuilder output = new StringBuilder(); + ExpressionDeParser expressions = new ExpressionDeParser() { + @Override + public StringBuilder visit(LongValue value, S context) { + return getBuilder().append(value.getValue() + 100); + } + }; + alter.accept(new StatementDeParser(expressions, new SelectDeParser(), output), null); + assertEquals("ALTER TABLE t ALTER COLUMN a SET DEFAULT (101 + 102)", output.toString()); + ColumnSetDefault column = + alter.getAlterExpressions().get(0).getColumnSetDefaultList().get(0); + column.setDefaultExpression(new LongValue(42)); + assertEquals("42", column.getDefaultValue()); + assertEquals("ALTER TABLE t ALTER COLUMN a SET DEFAULT 42", alter.toString()); + } + + @Test + void legacyStringConstructorRemainsOpaqueAndAcceptsNull() { + ColumnSetDefault column = new ColumnSetDefault("a", "vendor_default()"); + assertNull(column.getDefaultExpression()); + assertEquals("a SET DEFAULT vendor_default()", column.toString()); + assertEquals("a SET DEFAULT null", new ColumnSetDefault("a", null).toString()); + column.setDefaultExpression(new LongValue(1)); + assertEquals("1", column.getDefaultValue()); + column.setDefaultExpression(null); + assertNull(column.getDefaultValue()); + } + + private static Alter parse(String value) throws JSQLParserException { + return (Alter) CCJSqlParserUtil.parse("ALTER TABLE t ALTER COLUMN a SET DEFAULT " + value); + } +} diff --git a/src/test/java/net/sf/jsqlparser/statement/alter/AlterKeyAccessorsTest.java b/src/test/java/net/sf/jsqlparser/statement/alter/AlterKeyAccessorsTest.java new file mode 100644 index 0000000000..ddda3cc7eb --- /dev/null +++ b/src/test/java/net/sf/jsqlparser/statement/alter/AlterKeyAccessorsTest.java @@ -0,0 +1,166 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2019 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.statement.alter; + +import static org.junit.jupiter.api.Assertions.*; +import java.util.ArrayList; +import java.util.List; +import net.sf.jsqlparser.parser.CCJSqlParserUtil; +import net.sf.jsqlparser.schema.Column; +import net.sf.jsqlparser.statement.create.table.Index; +import net.sf.jsqlparser.util.deparser.StatementDeParser; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +class AlterKeyAccessorsTest { + private List columns(AlterExpression action, boolean primary) { + return primary ? action.getPkColumns() : action.getUkColumns(); + } + + private void setColumns(AlterExpression action, boolean primary, List columns) { + if (primary) { + action.setPkColumns(columns); + } else { + action.setUkColumns(columns); + } + } + + private void assertDeparsed(Alter statement, String expected) { + assertEquals(expected, statement.toString()); + StringBuilder output = new StringBuilder(); + statement.accept(new StatementDeParser(output), null); + assertEquals(expected, output.toString()); + } + + @ParameterizedTest + @ValueSource(booleans = {false, true}) + void readsCurrentIndexAndAppliesSettersAndFluentAdditions(boolean primary) throws Exception { + String prefix = "ALTER TABLE t ADD " + (primary ? "PRIMARY KEY" : "UNIQUE"); + Alter statement = (Alter) CCJSqlParserUtil.parse(prefix + " (a)"); + AlterExpression action = statement.getAlterExpressions().get(0); + Index originalIndex = action.getIndex(); + originalIndex.setColumnsNames(List.of("b")); + List snapshot = columns(action, primary); + assertEquals(List.of("b"), snapshot); + snapshot.set(0, "c"); + assertEquals(List.of("b"), columns(action, primary)); + assertDeparsed(statement, prefix + " (b)"); + setColumns(action, primary, List.of("c")); + assertDeparsed(statement, prefix + " (c)"); + if (primary) { + action.addPkColumns("d").addPkColumns(List.of("e")); + } else { + action.addUkColumns("d").addUkColumns(List.of("e")); + } + assertEquals(List.of("c", "d", "e"), columns(action, primary)); + assertSame(originalIndex, action.getIndex()); + assertDeparsed(statement, prefix + " (c, d, e)"); + setColumns(action, primary, null); + assertTrue(columns(action, primary).isEmpty()); + assertTrue(originalIndex.getColumns().isEmpty()); + } + + @ParameterizedTest + @ValueSource(booleans = {false, true}) + void replacingWithBareNamesDropsElementOptionsButKeepsIndexMetadata(boolean primary) + throws Exception { + String prefix = "ALTER TABLE t ADD CONSTRAINT key_t " + + (primary ? "PRIMARY KEY" : "UNIQUE"); + Alter statement = (Alter) CCJSqlParserUtil.parse(prefix + " (a DESC, b) DEFERRABLE"); + AlterExpression action = statement.getAlterExpressions().get(0); + Index index = action.getIndex(); + Index.ColumnParams first = index.getColumns().get(0); + assertEquals("a DESC", first.toString()); + setColumns(action, primary, List.of("b", "a")); + assertEquals(List.of("b", "a"), columns(action, primary)); + assertNull(index.getColumns().get(1).getSortOrder()); + assertNull(index.getColumns().get(1).getParams()); + assertNotSame(first, index.getColumns().get(1)); + assertSame(index, action.getIndex()); + assertEquals("key_t", index.getName()); + assertDeparsed(statement, prefix + " (b, a) DEFERRABLE"); + } + + @ParameterizedTest + @ValueSource(booleans = {false, true}) + void settingAnUnchangedSnapshotPreservesSqlButRebuildsPlainElements(boolean primary) + throws Exception { + String prefix = "ALTER TABLE t ADD " + (primary ? "PRIMARY KEY" : "UNIQUE"); + Alter statement = (Alter) CCJSqlParserUtil.parse(prefix + " (a DESC, b)"); + AlterExpression action = statement.getAlterExpressions().get(0); + Index index = action.getIndex(); + Index.ColumnParams expression = new Index.ColumnParams(new Column("a")) + .withExpressionParenthesized(false) + .withSortOrder(Index.ColumnParams.SortOrder.DESC); + index.getColumns().set(0, expression); + setColumns(action, primary, new ArrayList<>(columns(action, primary))); + Index.ColumnParams replacement = index.getColumns().get(0); + assertNotSame(expression, replacement); + assertEquals("a DESC", replacement.getColumnName()); + assertNull(replacement.getExpression()); + assertNull(replacement.getSortOrder()); + assertDeparsed(statement, prefix + " (a DESC, b)"); + } + + @ParameterizedTest + @ValueSource(booleans = {false, true}) + void structuredReorderingRetainsElementIdentityAndOptions(boolean primary) throws Exception { + String prefix = "ALTER TABLE t ADD " + (primary ? "PRIMARY KEY" : "UNIQUE"); + Alter statement = (Alter) CCJSqlParserUtil.parse(prefix + " (a DESC, b)"); + AlterExpression action = statement.getAlterExpressions().get(0); + Index index = action.getIndex(); + Index.ColumnParams first = index.getColumns().get(0); + Index.ColumnParams second = index.getColumns().get(1); + index.setColumns(List.of(second, first)); + assertSame(first, index.getColumns().get(1)); + assertSame(second, index.getColumns().get(0)); + assertEquals(Index.ColumnParams.SortOrder.DESC, first.getSortOrder()); + assertEquals(List.of("b", "a DESC"), columns(action, primary)); + assertDeparsed(statement, prefix + " (b, a DESC)"); + } + + @ParameterizedTest + @ValueSource(booleans = {false, true}) + void supportsAnIndexWithoutColumnsAndFluentPopulation(boolean primary) { + Index index = new Index().withType(primary ? "PRIMARY KEY" : "UNIQUE"); + AlterExpression action = new AlterExpression().withOperation(AlterOperation.ADD) + .withIndex(index); + assertTrue(index.getColumnsNames().isEmpty()); + assertTrue(columns(action, primary).isEmpty()); + index.getColumnsNames().add("detached"); + assertNull(index.getColumns()); + if (primary) { + action.addPkColumns("a"); + } else { + action.addUkColumns("a"); + } + assertEquals(List.of("a"), columns(action, primary)); + index.setColumns(null); + assertTrue(columns(action, primary).isEmpty()); + setColumns(action, primary, List.of("b")); + assertEquals(List.of("b"), index.getColumnsNames()); + } + + @Test + void retainsLegacyOnlyConstructionAndIgnoresUnrelatedIndexes() { + AlterExpression action = new AlterExpression().withOperation(AlterOperation.ADD) + .withPkColumns(new ArrayList<>(List.of("a"))); + action.getPkColumns().add("b"); + assertEquals("ADD PRIMARY KEY (a, b)", action.toString()); + Index unrelated = new Index().withType("INDEX").withColumnsNames(List.of("other")); + action.setIndex(unrelated); + action.setPkColumns(List.of("pk")); + action.setUkColumns(List.of("uk")); + assertEquals(List.of("pk"), action.getPkColumns()); + assertEquals(List.of("uk"), action.getUkColumns()); + assertEquals(List.of("other"), unrelated.getColumnsNames()); + } +} diff --git a/src/test/java/net/sf/jsqlparser/statement/alter/AlterTest.java b/src/test/java/net/sf/jsqlparser/statement/alter/AlterTest.java index 5ba1a87625..fa82730d41 100644 --- a/src/test/java/net/sf/jsqlparser/statement/alter/AlterTest.java +++ b/src/test/java/net/sf/jsqlparser/statement/alter/AlterTest.java @@ -28,6 +28,7 @@ import net.sf.jsqlparser.expression.StringValue; import net.sf.jsqlparser.expression.operators.relational.NotEqualsTo; import net.sf.jsqlparser.parser.CCJSqlParserUtil; +import net.sf.jsqlparser.parser.AbstractJSqlParser.Dialect; import net.sf.jsqlparser.schema.Column; import net.sf.jsqlparser.schema.Table; import net.sf.jsqlparser.statement.ReferentialAction; @@ -42,6 +43,7 @@ import net.sf.jsqlparser.statement.create.table.Index.ColumnParams; import net.sf.jsqlparser.statement.create.table.NamedConstraint; import net.sf.jsqlparser.statement.create.table.PartitionDefinition; +import net.sf.jsqlparser.statement.create.table.PartitionBound; import net.sf.jsqlparser.test.TestUtils; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; @@ -368,12 +370,12 @@ public void testAlterTableAddColumn5() throws JSQLParserException { public void testAlterTableAddColumn6() throws JSQLParserException { final String sql = "ALTER TABLE mytable ADD COLUMN col1 timestamp (3) not null"; Statement stmt = CCJSqlParserUtil.parse(sql); - assertStatementCanBeDeparsedAs(stmt, sql); + assertStatementCanBeDeparsedAs(stmt, sql.replace("not null", "NOT NULL")); Alter alter = (Alter) stmt; List alterExps = alter.getAlterExpressions(); AlterExpression col1Exp = alterExps.get(0); - assertEquals("not", col1Exp.getColDataTypeList().get(0).getColumnSpecs().get(0)); - assertEquals("null", col1Exp.getColDataTypeList().get(0).getColumnSpecs().get(1)); + assertEquals("NOT", col1Exp.getColDataTypeList().get(0).getColumnSpecs().get(0)); + assertEquals("NULL", col1Exp.getColDataTypeList().get(0).getColumnSpecs().get(1)); assertTrue(col1Exp.hasColumn()); } @@ -598,6 +600,28 @@ public void testAlterTableDropAndAddUniqueIndexWithAscendingColumns() throws Exc result.toString()); } + @Test + public void testAlterTableAddUniqueIndexWithCommentIssue2503() throws Exception { + String sql = "ALTER TABLE `wxp_dm`.`xqgl_req_report` " + + "ADD UNIQUE INDEX `index2` (`report_name` ASC) USING BTREE COMMENT '唯一索引'"; + + Alter alter = (Alter) assertSqlCanBeParsedAndDeparsed(sql); + AlterExpression alterExpression = alter.getAlterExpressions().get(0); + Index index = alterExpression.getIndex(); + + assertNotNull(index); + assertEquals("UNIQUE", index.getType()); + assertEquals("INDEX", index.getIndexKeyword()); + assertEquals("`index2`", index.getName()); + assertEquals(List.of("`report_name` ASC"), index.getColumnsNames()); + assertEquals("'唯一索引'", index.getCommentText()); + + // Keep the legacy fields populated for existing consumers. + assertEquals("`index2`", alterExpression.getUkName()); + assertEquals(List.of("`report_name` ASC"), alterExpression.getUkColumns()); + assertEquals(List.of("USING", "BTREE"), alterExpression.getParameters()); + } + @Test public void testIssue259() throws JSQLParserException { assertSqlCanBeParsedAndDeparsed( @@ -644,8 +668,32 @@ public void testAlterConstraintWithoutFKSourceColumnsIssue929() throws JSQLParse @Test public void testAlterTableAlterColumnDropNotNullIssue918() throws JSQLParserException { - assertSqlCanBeParsedAndDeparsed( + Alter alter = (Alter) assertSqlCanBeParsedAndDeparsed( "ALTER TABLE \"user_table_t\" ALTER COLUMN name DROP NOT NULL"); + AlterExpression expression = alter.getAlterExpressions().get(0); + + assertNull(expression.getColDataTypeList()); + assertEquals("name", expression.getColumnDropNotNullList().get(0).getColumnName()); + assertTrue(expression.getColumnDropNotNullList().get(0).isWithNot()); + } + + @Test + public void testAlterTableAlterColumnSetNotNull() throws JSQLParserException { + Alter alter = (Alter) assertSqlCanBeParsedAndDeparsed( + "ALTER TABLE user_table ALTER COLUMN name SET NOT NULL"); + AlterExpression expression = alter.getAlterExpressions().get(0); + + assertNull(expression.getColDataTypeList()); + assertEquals("name", expression.getColumnSetNotNullList().get(0).getColumnName()); + } + + @Test + public void testAlterTableVersionColumn() throws JSQLParserException { + assertSqlCanBeParsedAndDeparsed( + "ALTER TABLE test_table DROP COLUMN version, DROP COLUMN other_column"); + assertSqlCanBeParsedAndDeparsed( + "ALTER TABLE test_table ALTER COLUMN version TYPE VARCHAR(32) " + + "USING version::VARCHAR(32)"); } @Test @@ -690,6 +738,24 @@ public void testAlterTableRenameColumnIssue2447() throws JSQLParserException { assertEquals(expression.getColumnName(), "type2"); } + @Test + public void testMySqlAlterTableRenameVariants() throws JSQLParserException { + Alter withoutKeyword = (Alter) assertSqlCanBeParsedAndDeparsed( + "ALTER TABLE t1 RENAME t2"); + AlterExpression rename = withoutKeyword.getAlterExpressions().get(0); + assertEquals(AlterOperation.RENAME_TABLE, rename.getOperation()); + assertEquals("t2", rename.getNewTableName()); + assertEquals(AlterExpression.TableRenameKeyword.NONE, rename.getTableRenameKeyword()); + + Alter withAs = (Alter) assertSqlCanBeParsedAndDeparsed("ALTER TABLE t1 RENAME AS t2"); + assertEquals(AlterExpression.TableRenameKeyword.AS, + withAs.getAlterExpressions().get(0).getTableRenameKeyword()); + + Alter withTo = (Alter) assertSqlCanBeParsedAndDeparsed("ALTER TABLE t1 RENAME TO t2"); + assertEquals(AlterExpression.TableRenameKeyword.TO, + withTo.getAlterExpressions().get(0).getTableRenameKeyword()); + } + @Test public void testAlterTableForeignKeyIssue981() throws JSQLParserException { assertSqlCanBeParsedAndDeparsed( @@ -905,7 +971,7 @@ public void testAlterTableDefaultValueTrueIssue926() throws JSQLParserException // There shall be no COLUMN where there is no COLUMN assertStatementCanBeDeparsedAs(parsed, - "ALTER TABLE my_table ADD some_column BOOLEAN DEFAULT FALSE"); + "ALTER TABLE my_table ADD some_column BOOLEAN DEFAULT false"); } private void assertReferentialActionOnConstraint(Alter parsed, Action onUpdate, @@ -1109,6 +1175,25 @@ public void testIssue1875() throws JSQLParserException { assertSqlCanBeParsedAndDeparsed(stmt); } + @ParameterizedTest + @MethodSource("provideModifyColumnExistenceClauses") + public void testModifyColumnExistenceClauses(String sql, boolean usingIfExists, + boolean useIfNotExists) throws JSQLParserException { + Alter alter = (Alter) assertSqlCanBeParsedAndDeparsed(sql); + AlterExpression expression = alter.getAlterExpressions().get(0); + + assertEquals(usingIfExists, expression.isUsingIfExists()); + assertEquals(useIfNotExists, expression.isUseIfNotExists()); + } + + private static Stream provideModifyColumnExistenceClauses() { + return Stream.of( + Arguments.of("ALTER TABLE t MODIFY COLUMN IF EXISTS c INT", true, false), + Arguments.of("ALTER TABLE t MODIFY IF EXISTS c INT", true, false), + Arguments.of("ALTER TABLE t MODIFY COLUMN IF NOT EXISTS c INT", false, true), + Arguments.of("ALTER TABLE t MODIFY IF NOT EXISTS c INT", false, true)); + } + @Test public void testIssue2027() throws JSQLParserException { String sql = "ALTER TABLE `foo_bar` ADD COLUMN `baz` text"; @@ -2345,4 +2430,100 @@ public void testAlterTableAddConstraintPrimaryKeyUsingIndexName() throws JSQLPar assertSqlCanBeParsedAndDeparsed(sql); } + + @Test + public void testAlterTableAddConstraintPrimaryKeyUsingIndexTablespace() + throws JSQLParserException { + String sql = + "ALTER TABLE bfmcs.your_table ADD CONSTRAINT your_table_pk PRIMARY KEY (ID) USING INDEX TABLESPACE your_tablespace"; + Statement stmt = CCJSqlParserUtil.parse(sql); + assertInstanceOf(Alter.class, stmt); + + Alter alter = (Alter) stmt; + List alterExpressions = alter.getAlterExpressions(); + assertNotNull(alterExpressions); + assertEquals(1, alterExpressions.size()); + + AlterExpression alterExp = alterExpressions.get(0); + assertEquals(AlterOperation.ADD, alterExp.getOperation()); + assertEquals(Arrays.asList("USING", "INDEX", "TABLESPACE", "your_tablespace"), + alterExp.getParameters()); + + assertSqlCanBeParsedAndDeparsed(sql); + } + + @Test + public void testAlterTableAddConstraintPrimaryKeyUsingIndexNameAndTablespace() + throws JSQLParserException { + String sql = + "ALTER TABLE your_table ADD CONSTRAINT pk_test PRIMARY KEY (ID) USING INDEX idx_test TABLESPACE ts_test"; + Statement stmt = CCJSqlParserUtil.parse(sql); + assertInstanceOf(Alter.class, stmt); + + Alter alter = (Alter) stmt; + List alterExpressions = alter.getAlterExpressions(); + assertNotNull(alterExpressions); + assertEquals(1, alterExpressions.size()); + + AlterExpression alterExp = alterExpressions.get(0); + assertEquals(AlterOperation.ADD, alterExp.getOperation()); + assertEquals(Arrays.asList("USING", "INDEX", "idx_test", "TABLESPACE", "ts_test"), + alterExp.getParameters()); + + assertSqlCanBeParsedAndDeparsed(sql); + } + + @Test + public void testAlterTableAddIndexKeyPartWithPrefixLengthAndDirectionIssue2490() + throws JSQLParserException { + assertSqlCanBeParsedAndDeparsed("ALTER TABLE t ADD INDEX i05 (c1 (20) DESC)"); + assertSqlCanBeParsedAndDeparsed("ALTER TABLE t ADD INDEX i33 (c1 (20) ASC)"); + assertSqlCanBeParsedAndDeparsed("ALTER TABLE t ADD UNIQUE INDEX i34 (c1 (10) DESC)"); + } + + @Test + public void testAlterTableAddMultiValuedIndexIssue2490() throws JSQLParserException { + assertSqlCanBeParsedAndDeparsed( + "ALTER TABLE t ADD INDEX i31 ((CAST(data -> '$.zips' AS UNSIGNED ARRAY)))"); + } + + @Test + public void testPostgreSqlAttachPartitionIssue2523() throws JSQLParserException { + String sql = "ALTER TABLE measurement ATTACH PARTITION measurement_y2026m01 " + + "FOR VALUES FROM ('2026-01-01') TO ('2026-02-01')"; + Alter alter = (Alter) assertSqlCanBeParsedAndDeparsed( + sql, true, parser -> parser.withDialect(Dialect.POSTGRESQL)); + AlterExpressionPartition expression = + (AlterExpressionPartition) alter.getAlterExpressions().get(0); + + assertEquals(AlterOperation.ATTACH_PARTITION, expression.getOperation()); + assertEquals("measurement_y2026m01", expression.getPartitionTable().getName()); + assertEquals(PartitionBound.Type.RANGE, expression.getPartitionBound().getType()); + } + + @Test + public void testPostgreSqlDetachPartitionModesIssue2523() throws JSQLParserException { + String concurrently = "ALTER TABLE measurement DETACH PARTITION " + + "measurement_y2026m01 CONCURRENTLY"; + Alter concurrentAlter = (Alter) assertSqlCanBeParsedAndDeparsed( + concurrently, true, parser -> parser.withDialect(Dialect.POSTGRESQL)); + AlterExpressionPartition concurrentExpression = + (AlterExpressionPartition) concurrentAlter.getAlterExpressions().get(0); + assertEquals(AlterOperation.DETACH_PARTITION, concurrentExpression.getOperation()); + assertEquals(AlterExpressionPartition.DetachMode.CONCURRENTLY, + concurrentExpression.getDetachMode()); + + String finalizeSql = + "ALTER TABLE measurement DETACH PARTITION measurement_y2026m01 FINALIZE"; + Alter finalizeAlter = (Alter) assertSqlCanBeParsedAndDeparsed( + finalizeSql, true, parser -> parser.withDialect(Dialect.POSTGRESQL)); + AlterExpressionPartition finalizeExpression = + (AlterExpressionPartition) finalizeAlter.getAlterExpressions().get(0); + assertEquals(AlterExpressionPartition.DetachMode.FINALIZE, + finalizeExpression.getDetachMode()); + + assertSqlCanBeParsedAndDeparsed( + "ALTER TABLE measurement DETACH PARTITION measurement_y2026m01", + true, parser -> parser.withDialect(Dialect.POSTGRESQL)); + } } diff --git a/src/test/java/net/sf/jsqlparser/statement/alter/CockroachAlterPrimaryKeyTest.java b/src/test/java/net/sf/jsqlparser/statement/alter/CockroachAlterPrimaryKeyTest.java new file mode 100644 index 0000000000..580c5febec --- /dev/null +++ b/src/test/java/net/sf/jsqlparser/statement/alter/CockroachAlterPrimaryKeyTest.java @@ -0,0 +1,186 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.statement.alter; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import net.sf.jsqlparser.JSQLParserException; +import net.sf.jsqlparser.expression.ExpressionVisitorAdapter; +import net.sf.jsqlparser.expression.Function; +import net.sf.jsqlparser.expression.LongValue; +import net.sf.jsqlparser.parser.AbstractJSqlParser.Dialect; +import net.sf.jsqlparser.parser.CCJSqlParserUtil; +import net.sf.jsqlparser.statement.StatementVisitorAdapter; +import net.sf.jsqlparser.statement.Statements; +import net.sf.jsqlparser.statement.create.table.Index; +import net.sf.jsqlparser.statement.select.SelectVisitorAdapter; +import net.sf.jsqlparser.test.TestUtils; +import net.sf.jsqlparser.util.TablesNamesFinder; +import net.sf.jsqlparser.util.deparser.AlterDeParser; +import net.sf.jsqlparser.util.deparser.ExpressionDeParser; +import net.sf.jsqlparser.util.deparser.StatementDeParser; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +class CockroachAlterPrimaryKeyTest { + private static Alter parse(String sql) throws JSQLParserException { + return (Alter) CCJSqlParserUtil.parse(sql, + parser -> parser.withDialect(Dialect.COCKROACHDB)); + } + + private static AlterExpressionPrimaryKey primaryKey(Alter alter) { + return (AlterExpressionPrimaryKey) alter.getAlterExpressions().get(0); + } + + @Test + void parsesOriginalReproducerIssue1743() throws Exception { + String sql = "ALTER TABLE FEATURE_SWITCH_CONFIG ALTER PRIMARY KEY " + + "USING COLUMNS (FEATURE_NAME) USING HASH"; + Alter alter = (Alter) TestUtils.assertSqlCanBeParsedAndDeparsed(sql, false, + parser -> parser.withDialect(Dialect.COCKROACHDB)); + AlterExpressionPrimaryKey action = primaryKey(alter); + assertEquals(AlterOperation.ALTER_PRIMARY_KEY, action.getOperation()); + assertEquals(Index.Kind.PRIMARY_KEY, action.getIndex().getKind()); + assertEquals(List.of("FEATURE_NAME"), action.getIndex().getColumnsNames()); + assertTrue(action.isUsingHash()); + assertNull(action.getBucketCount()); + assertEquals(Set.of("FEATURE_SWITCH_CONFIG"), new TablesNamesFinder<>().getTables(alter)); + } + + @ParameterizedTest + @ValueSource(strings = {"", " USING HASH", " USING HASH WITH BUCKET_COUNT = 8", + " USING HASH WITH (bucket_count = 8)", " WITH (fillfactor = 70)"}) + void preservesKeyOrderOptionsAndRoundTrips(String suffix) throws Exception { + String sql = "ALTER TABLE t ALTER PRIMARY KEY USING COLUMNS (tenant_id ASC, \"key\" DESC)" + + suffix; + Alter alter = (Alter) TestUtils.assertSqlCanBeParsedAndDeparsed(sql, false, + parser -> parser.withDialect(Dialect.COCKROACHDB)); + StringBuilder output = new StringBuilder(); + alter.accept(new StatementDeParser(output), null); + for (String rendered : List.of(alter.toString(), output.toString())) { + AlterExpressionPrimaryKey action = primaryKey(parse(rendered)); + assertEquals(List.of("tenant_id ASC", "\"key\" DESC"), + action.getIndex().getColumnsNames()); + assertEquals(suffix.contains("USING HASH"), action.isUsingHash()); + if (suffix.contains("BUCKET_COUNT")) { + assertEquals(8, ((LongValue) action.getBucketCount()).getValue()); + } else { + assertNull(action.getBucketCount()); + } + if (suffix.contains("WITH (")) { + Index.Option option = action.getIndex().getStorageParameters().get(0); + assertEquals(suffix.contains("bucket_count") ? "bucket_count" : "fillfactor", + option.getName()); + assertEquals(suffix.contains("bucket_count") ? 8 : 70, + ((LongValue) option.getValue()).getValue()); + } + } + } + + @Test + void exposesStructuredExpressionKeysAndSupportsMutation() throws Exception { + Alter alter = parse("ALTER TABLE t ALTER PRIMARY KEY USING COLUMNS " + + "(lower(name) text_ops DESC, (id + 1) ASC)"); + AlterExpressionPrimaryKey action = primaryKey(alter); + Index.ColumnParams first = action.getIndex().getColumns().get(0); + assertTrue(first.getExpression() instanceof Function); + assertFalse(first.isExpressionParenthesized()); + assertEquals("text_ops", first.getOperatorClass()); + assertNull(first.getParams()); + first.setSortOrder(Index.ColumnParams.SortOrder.ASC); + action.setUsingHash(true); + action.setBucketCount(new LongValue(16)); + String expected = "ALTER TABLE t ALTER PRIMARY KEY USING COLUMNS " + + "(lower(name) text_ops ASC, (id + 1) ASC) USING HASH WITH BUCKET_COUNT = 16"; + TestUtils.assertDeparse(alter, expected); + assertEquals(expected, parse(expected).toString()); + } + + @Test + void requiresCockroachDialectAndKeepsExistingAlterForms() throws Exception { + String sql = "ALTER TABLE t ALTER PRIMARY KEY USING COLUMNS (id) USING HASH"; + assertThrows(JSQLParserException.class, () -> CCJSqlParserUtil.parse(sql)); + for (Dialect dialect : Dialect.values()) { + if (dialect != Dialect.COCKROACHDB) { + assertThrows(JSQLParserException.class, + () -> CCJSqlParserUtil.parse(sql, parser -> parser.withDialect(dialect)), + dialect.name()); + } + } + for (String existing : List.of("ALTER TABLE t ADD PRIMARY KEY (id)", + "ALTER TABLE t ALTER COLUMN id TYPE BIGINT", "ALTER TABLE t DROP PRIMARY KEY")) { + String originalOutput = CCJSqlParserUtil.parse(existing).toString(); + assertEquals(existing, originalOutput.trim()); + TestUtils.assertStatementCanBeDeparsedAs(parse(existing), originalOutput); + } + } + + @Test + void visitsAndRewritesHashAndStorageExpressions() throws Exception { + Alter alter = parse("ALTER TABLE t ALTER PRIMARY KEY USING COLUMNS (id) " + + "USING HASH WITH BUCKET_COUNT = 4 WITH (fillfactor = 70)"); + List values = new ArrayList<>(); + ExpressionVisitorAdapter visitor = new ExpressionVisitorAdapter() { + @Override + public Void visit(LongValue value, S context) { + values.add(value.getValue()); + return null; + } + }; + alter.accept(new StatementVisitorAdapter(new SelectVisitorAdapter(visitor)), + null); + assertEquals(2, values.size()); + assertEquals(Set.of(4L, 70L), Set.copyOf(values)); + StringBuilder output = new StringBuilder(); + ExpressionDeParser expressions = new ExpressionDeParser() { + @Override + public StringBuilder visit(LongValue value, S context) { + return getBuilder().append(value.getValue() + 1); + } + }; + expressions.setBuilder(output); + new AlterDeParser(output, expressions).deParse(alter); + assertEquals("ALTER TABLE t ALTER PRIMARY KEY USING COLUMNS (id) " + + "USING HASH WITH BUCKET_COUNT = 5 WITH (fillfactor = 71)", output.toString()); + assertEquals(output.toString(), parse(output.toString()).toString()); + } + + @Test + void keepsFollowingActionsAndStatements() throws Exception { + Statements statements = CCJSqlParserUtil.parseStatements("ALTER TABLE t ALTER PRIMARY KEY " + + "USING COLUMNS (id) USING HASH WITH BUCKET_COUNT = 4, ADD COLUMN note INT; SELECT 1;", + parser -> parser.withDialect(Dialect.COCKROACHDB)); + assertEquals(2, statements.size()); + Alter alter = (Alter) statements.get(0); + assertEquals(2, alter.getAlterExpressions().size()); + assertEquals(AlterOperation.ADD, alter.getAlterExpressions().get(1).getOperation()); + assertEquals(alter.toString(), parse(alter.toString()).toString()); + } + + @Test + void rejectsIncompleteKeysAndHashOptions() { + for (String sql : List.of("ALTER TABLE t ALTER PRIMARY KEY USING COLUMNS ()", + "ALTER TABLE t ALTER PRIMARY KEY USING (id)", + "ALTER TABLE t ALTER PRIMARY KEY USING COLUMNS (id,)", + "ALTER TABLE t ALTER PRIMARY KEY USING COLUMNS (id) USING BTREE", + "ALTER TABLE t ALTER PRIMARY KEY USING COLUMNS (id) USING HASH WITH BUCKET_COUNT =", + "ALTER TABLE t ALTER PRIMARY KEY USING COLUMNS (id) WITH BUCKET_COUNT = 8", + "ALTER TABLE t ALTER PRIMARY KEY USING COLUMNS (id) USING HASH WITH wrong = 8")) { + assertThrows(JSQLParserException.class, () -> parse(sql), sql); + } + } +} diff --git a/src/test/java/net/sf/jsqlparser/statement/alter/InformixConstraintTest.java b/src/test/java/net/sf/jsqlparser/statement/alter/InformixConstraintTest.java new file mode 100644 index 0000000000..cc27254d46 --- /dev/null +++ b/src/test/java/net/sf/jsqlparser/statement/alter/InformixConstraintTest.java @@ -0,0 +1,189 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2019 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.statement.alter; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import net.sf.jsqlparser.statement.create.table.CheckConstraint; +import net.sf.jsqlparser.util.deparser.ExpressionDeParser; +import net.sf.jsqlparser.schema.Column; +import net.sf.jsqlparser.util.deparser.TableElementDeParser; +import net.sf.jsqlparser.util.TablesNamesFinder; +import net.sf.jsqlparser.test.TestUtils; +import java.util.Set; +import java.util.List; +import java.util.stream.Stream; +import net.sf.jsqlparser.JSQLParserException; +import net.sf.jsqlparser.parser.AbstractJSqlParser.Dialect; +import net.sf.jsqlparser.parser.CCJSqlParserUtil; +import net.sf.jsqlparser.statement.Statements; +import net.sf.jsqlparser.statement.create.table.ForeignKeyIndex; +import net.sf.jsqlparser.statement.create.table.NamedConstraint; +import net.sf.jsqlparser.statement.create.table.NamedConstraint.ConstraintNamePosition; +import net.sf.jsqlparser.util.deparser.StatementDeParser; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +class InformixConstraintTest { + static Stream definitions() { + return Stream.of("PRIMARY KEY (column)", "UNIQUE (column)", + "FOREIGN KEY (column) REFERENCES referenced_table(referenced_column)", + "CHECK (id > 0)", "PRIMARY KEY (id, tenant_id)", + "FOREIGN KEY (id, tenant_id) REFERENCES parent(id, tenant_id) ON DELETE CASCADE"); + } + + @ParameterizedTest + @MethodSource("definitions") + void preservesNamePlacement(String definition) throws Exception { + for (String suffix : List.of("", " CONSTRAINT constraint_name", + " CONSTRAINT \"constraint name\"")) { + String sql = "ALTER TABLE table_name ADD CONSTRAINT " + definition + suffix; + Alter statement = (Alter) TestUtils.assertSqlCanBeParsedAndDeparsed(sql, true, + parser -> parser.withDialect(Dialect.INFORMIX)); + NamedConstraint constraint = + (NamedConstraint) statement.getAlterExpressions().get(0).getIndex(); + assertEquals(ConstraintNamePosition.AFTER, constraint.getConstraintNamePosition()); + assertTrue(constraint.isUseConstraintKeyword()); + assertEquals(suffix.isEmpty() ? null : suffix.substring(" CONSTRAINT ".length()), + constraint.getName()); + StringBuilder deparsed = new StringBuilder(); + statement.accept(new StatementDeParser(deparsed), null); + assertEquals(statement.toString(), deparsed.toString()); + assertEquals(statement.toString(), + CCJSqlParserUtil.parse(deparsed.toString(), + parser -> parser.withDialect(Dialect.INFORMIX)).toString()); + assertTrue(statement.toString().contains("ADD CONSTRAINT " + definition)); + if (!suffix.isEmpty()) { + assertTrue(statement.toString().endsWith(suffix)); + } + } + } + + @ParameterizedTest + @MethodSource("definitions") + void requiresInformixDialectForTrailingNames(String definition) { + for (String suffix : List.of(" CONSTRAINT constraint_name", + " CONSTRAINT \"constraint name\"")) { + String sql = "ALTER TABLE table_name ADD CONSTRAINT " + definition + suffix; + assertThrows(JSQLParserException.class, () -> CCJSqlParserUtil.parse(sql)); + for (Dialect dialect : Dialect.values()) { + if (dialect != Dialect.INFORMIX) { + assertThrows(JSQLParserException.class, + () -> CCJSqlParserUtil.parse(sql, + parser -> parser.withDialect(dialect)), + dialect.name()); + } + } + } + } + + @Test + void retainsDefaultInterpretationOfPrimaryAsConstraintName() throws Exception { + String sql = "ALTER TABLE t ADD CONSTRAINT PRIMARY KEY (id)"; + Alter statement = (Alter) CCJSqlParserUtil.parse(sql); + NamedConstraint constraint = + (NamedConstraint) statement.getAlterExpressions().get(0).getIndex(); + assertEquals("PRIMARY", constraint.getName()); + assertEquals("KEY", constraint.getType()); + assertEquals(ConstraintNamePosition.BEFORE, constraint.getConstraintNamePosition()); + assertEquals(sql, statement.toString()); + } + + @Test + void exposesForeignKeyAndMutableName() throws Exception { + Alter statement = (Alter) CCJSqlParserUtil.parse( + "ALTER TABLE child ADD CONSTRAINT FOREIGN KEY (id) REFERENCES parent(id) CONSTRAINT fk_child", + parser -> parser.withDialect(Dialect.INFORMIX)); + ForeignKeyIndex key = (ForeignKeyIndex) statement.getAlterExpressions().get(0).getIndex(); + assertEquals(List.of("id"), key.getColumnsNames()); + assertEquals("parent", key.getTable().getName()); + assertEquals(List.of("id"), key.getReferencedColumnNames()); + key.setName("renamed_fk"); + assertTrue(statement.toString().endsWith("REFERENCES parent(id) CONSTRAINT renamed_fk")); + key.setName((String) null); + assertFalse(statement.toString().contains("fk_child")); + assertEquals(statement.toString(), CCJSqlParserUtil.parse(statement.toString(), + parser -> parser.withDialect(Dialect.INFORMIX)).toString()); + } + + @Test + void keepsFollowingAlterActionsAndStatements() throws Exception { + String sql = + "ALTER TABLE t ADD CONSTRAINT PRIMARY KEY (id) CONSTRAINT pk_t, ADD COLUMN note INT; SELECT 1;"; + Statements statements = CCJSqlParserUtil.parseStatements(sql, + parser -> parser.withDialect(Dialect.INFORMIX)); + assertEquals(2, statements.size()); + assertEquals(2, ((Alter) statements.get(0)).getAlterExpressions().size()); + } + + @Test + void retainsLeadingNamesAndBuilders() throws Exception { + NamedConstraint built = new NamedConstraint().withType("PRIMARY KEY").withName("pk_t") + .withColumnsNames(List.of("id")); + assertEquals(ConstraintNamePosition.BEFORE, built.getConstraintNamePosition()); + assertEquals("CONSTRAINT pk_t PRIMARY KEY (id)", built.toString()); + for (String definition : List.of("PRIMARY KEY (id)", "UNIQUE (id)", + "FOREIGN KEY (id) REFERENCES parent(id)", "CHECK (id > 0)")) { + String sql = "ALTER TABLE t ADD CONSTRAINT c " + definition; + for (Alter statement : List.of((Alter) CCJSqlParserUtil.parse(sql), + (Alter) CCJSqlParserUtil.parse(sql, + parser -> parser.withDialect(Dialect.INFORMIX)))) { + assertEquals(ConstraintNamePosition.BEFORE, + ((NamedConstraint) statement.getAlterExpressions().get(0).getIndex()) + .getConstraintNamePosition()); + assertTrue(statement.toString().contains("CONSTRAINT c " + definition)); + } + } + } + + @Test + void keepsCheckExpressionVisitorAndForeignTableTraversal() throws Exception { + Alter statement = (Alter) CCJSqlParserUtil + .parse("ALTER TABLE child ADD CONSTRAINT CHECK (id > 0) CONSTRAINT positive_id", + parser -> parser.withDialect(Dialect.INFORMIX)); + CheckConstraint check = + (CheckConstraint) statement + .getAlterExpressions().get(0).getIndex(); + StringBuilder builder = new StringBuilder(); + ExpressionDeParser visitor = + new ExpressionDeParser() { + @Override + public StringBuilder visit(Column column, + S context) { + getBuilder().append("renamed_id"); + return getBuilder(); + } + }; + visitor.setBuilder(builder); + new TableElementDeParser(builder, visitor).deParse(check); + assertEquals("CONSTRAINT CHECK (renamed_id > 0) CONSTRAINT positive_id", + builder.toString()); + assertEquals(Set.of("child", "parent"), + new TablesNamesFinder<>().getTables(CCJSqlParserUtil.parse( + "ALTER TABLE child ADD CONSTRAINT FOREIGN KEY (id) REFERENCES parent(id) CONSTRAINT fk", + parser -> parser.withDialect(Dialect.INFORMIX)))); + } + + @Test + void rejectsMissingOrDuplicateNames() { + for (String sql : List.of( + "ALTER TABLE t ADD CONSTRAINT PRIMARY KEY (id) CONSTRAINT", + "ALTER TABLE t ADD CONSTRAINT UNIQUE (id) CONSTRAINT a CONSTRAINT b", + "ALTER TABLE t ADD CONSTRAINT FOREIGN KEY (id) CONSTRAINT fk", + "ALTER TABLE t MODIFY CONSTRAINT UNIQUE (id) CONSTRAINT uk")) { + assertThrows(JSQLParserException.class, + () -> CCJSqlParserUtil.parse(sql, + parser -> parser.withDialect(Dialect.INFORMIX))); + } + } +} diff --git a/src/test/java/net/sf/jsqlparser/statement/alter/MySQLAlterTablePartitionTest.java b/src/test/java/net/sf/jsqlparser/statement/alter/MySQLAlterTablePartitionTest.java new file mode 100644 index 0000000000..75dbb7aedc --- /dev/null +++ b/src/test/java/net/sf/jsqlparser/statement/alter/MySQLAlterTablePartitionTest.java @@ -0,0 +1,183 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.statement.alter; + +import static net.sf.jsqlparser.test.TestUtils.assertSqlCanBeParsedAndDeparsed; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.List; +import java.util.stream.Stream; +import net.sf.jsqlparser.JSQLParserException; +import net.sf.jsqlparser.parser.CCJSqlParserUtil; +import net.sf.jsqlparser.schema.Table; +import net.sf.jsqlparser.statement.create.table.PartitionDefinition; +import net.sf.jsqlparser.statement.create.table.TablePartitioning; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +/** MySQL 8.4 {@code ALTER TABLE} partition syntax verified against the MySQL manual. */ +public class MySQLAlterTablePartitionTest { + + private static final List MYSQL84_PARTITION_OPERATIONS = List.of( + "ALTER TABLE sales ADD PARTITION " + + "(PARTITION p2027 VALUES LESS THAN (2028) ENGINE = InnoDB)", + "ALTER TABLE sales DROP PARTITION p2023, `p2024`", + "ALTER TABLE sales DISCARD PARTITION p2024 TABLESPACE", + "ALTER TABLE sales IMPORT PARTITION ALL TABLESPACE", + "ALTER TABLE sales TRUNCATE PARTITION ALL", + "ALTER TABLE sales COALESCE PARTITION 2", + "ALTER TABLE sales REORGANIZE PARTITION p0, p1 INTO " + + "(PARTITION p_low VALUES LESS THAN (100), " + + "PARTITION p_high VALUES LESS THAN MAXVALUE)", + "ALTER TABLE sales EXCHANGE PARTITION p0 WITH TABLE archive.sales_2024", + "ALTER TABLE sales EXCHANGE PARTITION p0 WITH TABLE archive.sales_2024 " + + "WITH VALIDATION", + "ALTER TABLE sales EXCHANGE PARTITION p0 WITH TABLE archive.sales_2024 " + + "WITHOUT VALIDATION", + "ALTER TABLE sales ANALYZE PARTITION p0, p1", + "ALTER TABLE sales CHECK PARTITION ALL", + "ALTER TABLE sales OPTIMIZE PARTITION p0", + "ALTER TABLE sales REBUILD PARTITION p0", + "ALTER TABLE sales REPAIR PARTITION ALL", + "ALTER TABLE sales REMOVE PARTITIONING"); + + private static final List MYSQL84_PARTITIONING_CLAUSES = List.of( + "ALTER TABLE sales PARTITION BY HASH (id) PARTITIONS 8", + "ALTER TABLE sales PARTITION BY LINEAR KEY ALGORITHM = 1 (id) PARTITIONS 4", + "ALTER TABLE sales PARTITION BY RANGE (YEAR(created_at)) " + + "SUBPARTITION BY HASH (id) SUBPARTITIONS 2 " + + "(PARTITION p2026 VALUES LESS THAN (2027), " + + "PARTITION pmax VALUES LESS THAN MAXVALUE)", + "ALTER TABLE sales PARTITION BY LIST COLUMNS (region, tier) " + + "(PARTITION p_eu VALUES IN (('eu', 1), ('eu', 2)), " + + "PARTITION p_us VALUES IN (('us', 1)))"); + + private static Stream mysql84PartitionOperations() { + return MYSQL84_PARTITION_OPERATIONS.stream(); + } + + private static Stream mysql84PartitioningClauses() { + return MYSQL84_PARTITIONING_CLAUSES.stream(); + } + + @ParameterizedTest + @MethodSource("mysql84PartitionOperations") + public void testPartitionOperationsUseStructuredSubtype(String sql) + throws JSQLParserException { + assertInstanceOf(AlterExpressionPartition.class, parseExpression(sql)); + assertSqlCanBeParsedAndDeparsed(sql); + } + + @ParameterizedTest + @MethodSource("mysql84PartitioningClauses") + public void testPartitionByUsesCreateTablePartitioningModel(String sql) + throws JSQLParserException { + AlterExpressionPartition expression = parseExpression(sql); + assertEquals(AlterOperation.PARTITION_BY, expression.getOperation()); + assertNotNull(expression.getPartitioning()); + assertSqlCanBeParsedAndDeparsed(sql); + } + + @Test + public void testAddPartitionDefinitionAst() throws JSQLParserException { + AlterExpressionPartition expression = parseExpression( + "ALTER TABLE sales ADD PARTITION " + + "(PARTITION p2027 VALUES LESS THAN (2028) ENGINE = InnoDB " + + "COMMENT = 'archive')"); + + assertEquals(AlterOperation.ADD_PARTITION, expression.getOperation()); + PartitionDefinition definition = expression.getPartitionDefinitions().get(0); + assertEquals("p2027", definition.getPartitionName()); + assertEquals(PartitionDefinition.ValueOperator.LESS_THAN, + definition.getValueOperator()); + assertEquals(List.of("2028"), definition.getValues()); + assertEquals("InnoDB", definition.getStorageEngine()); + assertEquals("'archive'", definition.getComment()); + } + + @Test + public void testAllPartitionsAndCoalesceCountAst() throws JSQLParserException { + AlterExpressionPartition all = + parseExpression("ALTER TABLE sales TRUNCATE PARTITION ALL"); + assertTrue(all.isAllPartitions()); + assertEquals(List.of("ALL"), all.getPartitionNames()); + assertNull(all.getCoalescePartitionCount()); + + AlterExpressionPartition coalesce = + parseExpression("ALTER TABLE sales COALESCE PARTITION 2"); + assertFalse(coalesce.isAllPartitions()); + assertEquals(2, coalesce.getCoalescePartitionCount()); + assertEquals(2, coalesce.getCoalescePartitionNumber()); + } + + @Test + public void testExchangePartitionAst() throws JSQLParserException { + AlterExpressionPartition expression = parseExpression( + "ALTER TABLE sales EXCHANGE PARTITION `p0` WITH TABLE " + + "`archive`.`sales_2024` WITHOUT VALIDATION"); + + assertEquals(List.of("`p0`"), expression.getPartitionNames()); + assertEquals("`archive`.`sales_2024`", + expression.getExchangeTable().getFullyQualifiedName()); + assertEquals(AlterExpressionPartition.ExchangeValidationMode.WITHOUT, + expression.getExchangeValidationMode()); + assertTrue(expression.isExchangePartitionWithoutValidation()); + assertFalse(expression.isExchangePartitionWithValidation()); + assertEquals("`archive`.`sales_2024`", expression.getExchangePartitionTableName()); + assertSqlCanBeParsedAndDeparsed("ALTER TABLE sales " + expression); + } + + @Test + public void testPartitionByAst() throws JSQLParserException { + AlterExpressionPartition expression = parseExpression( + "ALTER TABLE sales PARTITION BY LIST COLUMNS (region, tier) " + + "(PARTITION p_eu VALUES IN (('eu', 1), ('eu', 2)), " + + "PARTITION p_us VALUES IN (('us', 1)))"); + + TablePartitioning partitioning = expression.getPartitioning(); + assertEquals(TablePartitioning.Type.LIST, partitioning.getType()); + assertTrue(partitioning.isColumnsSyntax()); + assertEquals("region", partitioning.getColumns().get(0).getColumnName()); + assertEquals("tier", partitioning.getColumns().get(1).getColumnName()); + assertEquals(PartitionDefinition.ValueOperator.IN, + partitioning.getPartitionDefinitions().get(0).getValueOperator()); + + // Legacy accessors remain populated for source compatibility. + assertEquals("LIST", expression.getPartitionType()); + assertEquals(List.of("region", "tier"), expression.getPartitionColumns()); + assertEquals(partitioning.getPartitionDefinitions(), expression.getPartitionDefinitions()); + } + + @Test + public void testProgrammaticConstruction() { + AlterExpressionPartition expression = new AlterExpressionPartition() + .withOperation(AlterOperation.EXCHANGE_PARTITION) + .addPartitionNames("p0") + .withExchangeTable(new Table("archive", "sales_2024")) + .withExchangeValidationMode( + AlterExpressionPartition.ExchangeValidationMode.WITHOUT); + + assertEquals("EXCHANGE PARTITION p0 WITH TABLE archive.sales_2024 WITHOUT VALIDATION", + expression.toString()); + } + + private static AlterExpressionPartition parseExpression(String sql) + throws JSQLParserException { + Alter alter = (Alter) CCJSqlParserUtil.parse(sql); + return assertInstanceOf(AlterExpressionPartition.class, + alter.getAlterExpressions().get(0)); + } +} diff --git a/src/test/java/net/sf/jsqlparser/statement/alter/PostgreSqlAlterConstraintTest.java b/src/test/java/net/sf/jsqlparser/statement/alter/PostgreSqlAlterConstraintTest.java new file mode 100644 index 0000000000..70e48887fb --- /dev/null +++ b/src/test/java/net/sf/jsqlparser/statement/alter/PostgreSqlAlterConstraintTest.java @@ -0,0 +1,104 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.statement.alter; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import net.sf.jsqlparser.JSQLParserException; +import net.sf.jsqlparser.parser.AbstractJSqlParser.Dialect; +import net.sf.jsqlparser.parser.CCJSqlParserUtil; +import net.sf.jsqlparser.statement.create.table.CheckConstraint; +import net.sf.jsqlparser.statement.create.table.ConstraintAttributes; +import net.sf.jsqlparser.statement.create.table.ExcludeConstraint; +import net.sf.jsqlparser.statement.create.table.Index; +import net.sf.jsqlparser.util.TablesNamesFinder; +import net.sf.jsqlparser.util.deparser.StatementDeParser; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +class PostgreSqlAlterConstraintTest { + @ParameterizedTest + @ValueSource(strings = { + "CONSTRAINT c CHECK (id > 0) NOT VALID", + "CHECK (id > 0) NOT VALID", + "CONSTRAINT c UNIQUE NULLS NOT DISTINCT (id) INCLUDE (other) WITH (fillfactor = 70) " + + "USING INDEX TABLESPACE fast DEFERRABLE INITIALLY DEFERRED", + "UNIQUE (id) NOT DEFERRABLE INITIALLY IMMEDIATE", + "CONSTRAINT c PRIMARY KEY (id) DEFERRABLE INITIALLY DEFERRED", + "CONSTRAINT c FOREIGN KEY (id) REFERENCES parent(id) ON DELETE CASCADE " + + "DEFERRABLE INITIALLY DEFERRED NOT VALID", + "FOREIGN KEY (id) REFERENCES parent(id) NOT VALID", + "CONSTRAINT c EXCLUDE USING gist ((id + 1) WITH =) WHERE (id > 0) DEFERRABLE", + "EXCLUDE USING gist (id WITH =)" + }) + void sharedConstraintGrammarParsesAndRoundTrips(String definition) + throws JSQLParserException { + Alter alter = parse("ALTER TABLE t ADD " + definition); + StringBuilder buffer = new StringBuilder(); + alter.accept(new StatementDeParser(buffer), null); + assertEquals(alter.toString(), buffer.toString()); + assertEquals(alter.toString(), parse(buffer.toString()).toString()); + assertEquals(1, alter.getAlterExpressions().size()); + } + + @Test + void constraintAttributesAndPredicatesRemainStructured() throws JSQLParserException { + Alter alter = parse("ALTER TABLE t ADD CONSTRAINT c CHECK (id > 0) NOT VALID, " + + "ADD CONSTRAINT u UNIQUE (id) DEFERRABLE INITIALLY DEFERRED"); + CheckConstraint check = assertInstanceOf(CheckConstraint.class, + alter.getAlterExpressions().get(0).getIndex()); + assertTrue(check.getConstraintAttributes().isNotValid()); + Index unique = alter.getAlterExpressions().get(1).getIndex(); + assertEquals(ConstraintAttributes.Initially.DEFERRED, + unique.getConstraintAttributes().getInitially()); + assertEquals(Boolean.TRUE, unique.getConstraintAttributes().getDeferrable()); + ExcludeConstraint exclude = assertInstanceOf(ExcludeConstraint.class, + parse("ALTER TABLE t ADD EXCLUDE USING gist ((id + 1) WITH =) WHERE (id > 0)") + .getAlterExpressions().get(0).getIndex()); + assertEquals("id > 0", exclude.getExpression().toString()); + } + + @Test + void commonProjectionPreservesLegacyKeyAccessors() throws JSQLParserException { + AlterExpression primary = parse("ALTER TABLE t ADD PRIMARY KEY (id)") + .getAlterExpressions().get(0); + assertThat(primary.getPkColumns()).containsExactly("id"); + AlterExpression foreign = + parse("ALTER TABLE t ADD FOREIGN KEY (id) REFERENCES app.parent(id)") + .getAlterExpressions().get(0); + assertThat(foreign.getFkColumns()).containsExactly("id"); + assertEquals("app", foreign.getFkSourceSchema()); + assertEquals("parent", foreign.getFkSourceTable()); + assertThat(new TablesNamesFinder().getTables( + parse("ALTER TABLE t ADD FOREIGN KEY (id) REFERENCES app.parent(id) NOT VALID"))) + .containsExactlyInAnyOrder("t", "app.parent"); + } + + @ParameterizedTest + @ValueSource(strings = { + "ALTER TABLE t ADD CONSTRAINT c CHECK (id > 0) NOT INVALID", + "ALTER TABLE t ADD UNIQUE (id) INITIALLY UNKNOWN", + "ALTER TABLE t ADD EXCLUDE USING gist (id)", + "ALTER TABLE t ADD CONSTRAINT c CHECK (id > 0), ADD" + }) + void invalidConstraintTailsAreRejected(String sql) { + assertThrows(JSQLParserException.class, () -> parse(sql)); + } + + private static Alter parse(String sql) throws JSQLParserException { + return (Alter) CCJSqlParserUtil.parse(sql, + parser -> parser.withDialect(Dialect.POSTGRESQL)); + } +} diff --git a/src/test/java/net/sf/jsqlparser/statement/alter/SqlServerDefaultConstraintTest.java b/src/test/java/net/sf/jsqlparser/statement/alter/SqlServerDefaultConstraintTest.java new file mode 100644 index 0000000000..3516b92f32 --- /dev/null +++ b/src/test/java/net/sf/jsqlparser/statement/alter/SqlServerDefaultConstraintTest.java @@ -0,0 +1,164 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.statement.alter; + +import static org.junit.jupiter.api.Assertions.*; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import net.sf.jsqlparser.JSQLParserException; +import net.sf.jsqlparser.expression.Expression; +import net.sf.jsqlparser.expression.LongValue; +import net.sf.jsqlparser.parser.AbstractJSqlParser.Dialect; +import net.sf.jsqlparser.parser.CCJSqlParserUtil; +import net.sf.jsqlparser.schema.Column; +import net.sf.jsqlparser.statement.create.table.DefaultConstraint; +import net.sf.jsqlparser.statement.create.table.Index; +import net.sf.jsqlparser.test.TestUtils; +import net.sf.jsqlparser.util.TablesNamesFinder; +import net.sf.jsqlparser.util.deparser.AlterDeParser; +import net.sf.jsqlparser.util.deparser.ExpressionDeParser; +import net.sf.jsqlparser.util.validation.validator.AlterValidator; +import net.sf.jsqlparser.util.validation.ValidationContext; +import net.sf.jsqlparser.util.validation.metadata.DatabaseMetaDataValidation; +import net.sf.jsqlparser.util.validation.metadata.Named; +import net.sf.jsqlparser.util.validation.metadata.NamedObject; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +class SqlServerDefaultConstraintTest { + @ParameterizedTest + @ValueSource(strings = { + "ALTER TABLE dbo.virtual_production_record ADD CONSTRAINT DF_virtual_production_record__d DEFAULT ((0)) FOR _d", + "ALTER TABLE [dbo].[t] ADD CONSTRAINT [DF_value] DEFAULT ((0)) FOR [value] WITH VALUES", + "ALTER TABLE t ADD DEFAULT 0 FOR c", + "ALTER TABLE t ADD DEFAULT NULL FOR c", + "ALTER TABLE t ADD DEFAULT -1 FOR c WITH VALUES", + "ALTER TABLE t ADD DEFAULT N'it''s fine' FOR c", + "ALTER TABLE t ADD CONSTRAINT df DEFAULT (GETDATE()) FOR created_at", + "ALTER TABLE t ADD DEFAULT (NEXT VALUE FOR dbo.seq) FOR id", + "ALTER TABLE t ADD DEFAULT (1 + 2) FOR c", + "ALTER TABLE t ADD CONSTRAINT df DEFAULT CAST(0 AS INT) FOR c", + "ALTER TABLE t ADD DEFAULT 0 FOR a, ADD DEFAULT 1 FOR b", + "ALTER TABLE t ADD DEFAULT 0 FOR c, ADD CONSTRAINT ck CHECK (c >= 0)" + }) + void roundTrip(String sql) throws JSQLParserException { + Alter alter = (Alter) TestUtils.assertSqlCanBeParsedAndDeparsed(sql, true, + parser -> parser.withSquareBracketQuotation(true)); + assertEquals(alter.toString(), parse(sql).toString()); + assertInstanceOf(DefaultConstraint.class, alter.getAlterExpressions().get(0).getIndex()); + } + + @ParameterizedTest + @ValueSource(strings = { + "ALTER TABLE t ADD DEFAULT FOR c", + "ALTER TABLE t ADD DEFAULT 0", + "ALTER TABLE t ADD CONSTRAINT df DEFAULT 0 FOR", + "ALTER TABLE t ADD CONSTRAINT DEFAULT 0 FOR c", + "ALTER TABLE t ADD DEFAULT 0 FOR c WITH", + "ALTER TABLE t ADD DEFAULT 0 FOR c WITH VALUES WITH VALUES", + "ALTER TABLE t ALTER CONSTRAINT df DEFAULT 0 FOR c", + "ALTER TABLE t MODIFY DEFAULT 0 FOR c" + }) + void rejectMalformedSyntax(String sql) { + assertThrows(JSQLParserException.class, () -> parse(sql)); + } + + @Test + void inspectAndModifyStructuredConstraint() throws JSQLParserException { + Alter alter = parse("ALTER TABLE t ADD CONSTRAINT df DEFAULT 0 FOR c WITH VALUES"); + AlterExpression action = alter.getAlterExpressions().get(0); + DefaultConstraint constraint = (DefaultConstraint) action.getIndex(); + assertEquals(AlterOperation.ADD, action.getOperation()); + assertEquals(Index.Kind.DEFAULT, constraint.getKind()); + assertEquals("DEFAULT", constraint.getType()); + assertEquals("df", constraint.getName()); + assertEquals("c", constraint.getColumn().getColumnName()); + assertEquals(0, ((LongValue) constraint.getExpression()).getValue()); + assertTrue(constraint.isWithValues()); + constraint.withName((String) null).withExpression(new LongValue(2)) + .withColumn(new Column("other_column")).withWithValues(false); + assertEquals("ALTER TABLE t ADD DEFAULT 2 FOR other_column", alter.toString()); + TestUtils.assertSqlCanBeParsedAndDeparsed(alter.toString()); + } + + @Test + void deparserVisitsDefaultAndTargetColumn() throws JSQLParserException { + Alter alter = parse("ALTER TABLE t ADD CONSTRAINT df DEFAULT ((0)) FOR c WITH VALUES"); + StringBuilder sql = new StringBuilder(); + ExpressionDeParser expressions = new ExpressionDeParser() { + @Override + public StringBuilder visit(LongValue value, S context) { + return getBuilder().append('1'); + } + + @Override + public StringBuilder visit(Column column, S context) { + return getBuilder().append("new_").append(column.getColumnName()); + } + }; + expressions.setBuilder(sql); + new AlterDeParser(sql, expressions).deParse(alter); + assertEquals("ALTER TABLE t ADD CONSTRAINT df DEFAULT ((1)) FOR new_c WITH VALUES", + sql.toString()); + } + + @Test + void validationTraversesStructuredValueAndColumn() throws JSQLParserException { + Alter alter = parse("ALTER TABLE dbo.t ADD DEFAULT (GETDATE()) FOR created_at"); + List visited = new ArrayList<>(); + AlterValidator validator = new AlterValidator() { + @Override + public void validateOptionalExpression(Expression expression) { + visited.add(expression.toString()); + } + }; + validator.setContext(new ValidationContext().setCapabilities(List.of())); + validator.validate(alter); + assertEquals(List.of("(GETDATE())", "created_at"), visited); + assertEquals(Set.of("dbo.t"), new TablesNamesFinder().getTables(alter)); + } + + @Test + void existingColumnDefaultsAndCreateTableRemainSupported() throws JSQLParserException { + TestUtils.assertSqlCanBeParsedAndDeparsed( + "CREATE TABLE t (c INT DEFAULT ((0)), CONSTRAINT pk PRIMARY KEY CLUSTERED (c))"); + TestUtils.assertSqlCanBeParsedAndDeparsed("ALTER TABLE t ALTER COLUMN c SET DEFAULT 0"); + TestUtils.assertSqlCanBeParsedAndDeparsed("ALTER TABLE t ALTER COLUMN c DROP DEFAULT"); + assertEquals(Index.Kind.DEFAULT, new Index().withType("DEFAULT").getKind()); + } + + @ParameterizedTest + @ValueSource(strings = {"", "CONSTRAINT df "}) + void validateOptionalConstraintNameAsNewConstraint(String prefix) throws JSQLParserException { + List visited = new ArrayList<>(); + DatabaseMetaDataValidation metadata = named -> { + visited.add(named); + return named.getNamedObject() != NamedObject.constraint; + }; + AlterValidator validator = new AlterValidator(); + validator.setContext(new ValidationContext().setCapabilities(List.of(metadata))); + validator.validate(parse("ALTER TABLE t ADD " + prefix + "DEFAULT 0 FOR c")); + assertTrue(validator.getValidationErrors().isEmpty()); + assertTrue(visited.stream().anyMatch(n -> n.getNamedObject() == NamedObject.column + && n.getFqn().equals("c"))); + assertEquals(prefix.isEmpty() ? 0 : 1, + visited.stream().filter(n -> n.getNamedObject() == NamedObject.constraint).count()); + assertTrue(visited.stream().noneMatch(n -> n.getNamedObject() == NamedObject.index)); + } + + private Alter parse(String sql) throws JSQLParserException { + return (Alter) CCJSqlParserUtil.parse(sql, + parser -> parser.withDialect(Dialect.SQLSERVER).withSquareBracketQuotation(true) + .withUnsupportedStatements(false)); + } +} diff --git a/src/test/java/net/sf/jsqlparser/statement/analyze/AnalyzeTest.java b/src/test/java/net/sf/jsqlparser/statement/analyze/AnalyzeTest.java index 7f19370f45..578e68fd78 100644 --- a/src/test/java/net/sf/jsqlparser/statement/analyze/AnalyzeTest.java +++ b/src/test/java/net/sf/jsqlparser/statement/analyze/AnalyzeTest.java @@ -9,17 +9,19 @@ */ package net.sf.jsqlparser.statement.analyze; +import static net.sf.jsqlparser.test.TestUtils.assertDeparse; +import static net.sf.jsqlparser.test.TestUtils.assertSqlCanBeParsedAndDeparsed; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.io.StringReader; +import java.util.List; +import java.util.stream.Collectors; import net.sf.jsqlparser.JSQLParserException; import net.sf.jsqlparser.parser.CCJSqlParserManager; +import net.sf.jsqlparser.schema.Column; import net.sf.jsqlparser.schema.Table; import org.junit.jupiter.api.Test; -import java.io.StringReader; - -import static net.sf.jsqlparser.test.TestUtils.assertDeparse; -import static net.sf.jsqlparser.test.TestUtils.assertSqlCanBeParsedAndDeparsed; -import static org.junit.jupiter.api.Assertions.assertEquals; - public class AnalyzeTest { private final CCJSqlParserManager parserManager = new CCJSqlParserManager(); @@ -38,4 +40,47 @@ public void testAnalyze() throws JSQLParserException { public void testAnalyze2() throws JSQLParserException { assertSqlCanBeParsedAndDeparsed("ANALYZE mytable"); } + + @Test + public void testMySqlAnalyzeTableList() throws JSQLParserException { + String sql = "ANALYZE TABLE t2, t3"; + Analyze analyze = (Analyze) parserManager.parse(new StringReader(sql)); + + assertEquals(List.of("t2", "t3"), analyze.getTables().stream() + .map(Table::getFullyQualifiedName).collect(Collectors.toList())); + assertEquals("t2", analyze.getTable().getFullyQualifiedName()); + assertEquals(sql, analyze.toString()); + + assertDeparse(new Analyze().withTableKeyword(true) + .withTables(List.of(new Table("t2"), new Table("t3"))), sql); + } + + @Test + public void testMySqlAnalyzeUpdateHistogram() throws JSQLParserException { + String sql = "ANALYZE TABLE t1 UPDATE HISTOGRAM ON c1, c2 WITH 64 BUCKETS"; + Analyze analyze = (Analyze) assertSqlCanBeParsedAndDeparsed(sql); + + assertEquals(Analyze.HistogramAction.UPDATE, analyze.getHistogramAction()); + assertEquals(List.of("c1", "c2"), analyze.getHistogramColumns().stream() + .map(Column::getFullyQualifiedName).collect(Collectors.toList())); + assertEquals(64, analyze.getHistogramBucketCount()); + } + + @Test + public void testMySqlAnalyzeDropHistogram() throws JSQLParserException { + String sql = "ANALYZE LOCAL TABLE t2 DROP HISTOGRAM ON c1"; + Analyze analyze = (Analyze) assertSqlCanBeParsedAndDeparsed(sql); + + assertEquals(Analyze.Modifier.LOCAL, analyze.getModifier()); + assertEquals(Analyze.HistogramAction.DROP, analyze.getHistogramAction()); + assertEquals("c1", analyze.getHistogramColumns().get(0).getFullyQualifiedName()); + } + + @Test + public void testMySqlAnalyzeNoWriteToBinlog() throws JSQLParserException { + String sql = "ANALYZE NO_WRITE_TO_BINLOG TABLE t1"; + Analyze analyze = (Analyze) assertSqlCanBeParsedAndDeparsed(sql); + + assertEquals(Analyze.Modifier.NO_WRITE_TO_BINLOG, analyze.getModifier()); + } } diff --git a/src/test/java/net/sf/jsqlparser/statement/builder/ReflectionModelTest.java b/src/test/java/net/sf/jsqlparser/statement/builder/ReflectionModelTest.java index 3fbfe8d962..64d7d83242 100644 --- a/src/test/java/net/sf/jsqlparser/statement/builder/ReflectionModelTest.java +++ b/src/test/java/net/sf/jsqlparser/statement/builder/ReflectionModelTest.java @@ -141,6 +141,7 @@ public class ReflectionModelTest { new net.sf.jsqlparser.statement.alter.AlterExpression(), new net.sf.jsqlparser.statement.alter.AlterExpression.ColumnDataType(false), new net.sf.jsqlparser.statement.alter.AlterExpression.ColumnDropNotNull("name"), + new net.sf.jsqlparser.statement.alter.AlterExpression.ColumnSetNotNull("name"), new net.sf.jsqlparser.statement.merge.MergeInsert(), new net.sf.jsqlparser.statement.alter.DeferrableConstraint(), new net.sf.jsqlparser.statement.alter.EnableConstraint(), diff --git a/src/test/java/net/sf/jsqlparser/statement/comment/CommentTargetTest.java b/src/test/java/net/sf/jsqlparser/statement/comment/CommentTargetTest.java new file mode 100644 index 0000000000..67f2dddf3f --- /dev/null +++ b/src/test/java/net/sf/jsqlparser/statement/comment/CommentTargetTest.java @@ -0,0 +1,260 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.statement.comment; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import java.util.stream.Stream; +import net.sf.jsqlparser.JSQLParserException; +import net.sf.jsqlparser.expression.ExpressionVisitorAdapter; +import net.sf.jsqlparser.expression.StringValue; +import net.sf.jsqlparser.parser.AbstractJSqlParser.Dialect; +import net.sf.jsqlparser.parser.CCJSqlParserUtil; +import net.sf.jsqlparser.schema.Column; +import net.sf.jsqlparser.schema.Table; +import net.sf.jsqlparser.statement.RoutineReference; +import net.sf.jsqlparser.statement.StatementVisitorAdapter; +import net.sf.jsqlparser.statement.Statements; +import net.sf.jsqlparser.statement.StmtFeature; +import net.sf.jsqlparser.statement.select.SelectVisitorAdapter; +import net.sf.jsqlparser.util.TablesNamesFinder; +import net.sf.jsqlparser.util.deparser.ExpressionDeParser; +import net.sf.jsqlparser.util.deparser.SelectDeParser; +import net.sf.jsqlparser.util.deparser.StatementDeParser; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.junit.jupiter.params.provider.ValueSource; + +class CommentTargetTest { + static Stream names() { + List cases = new ArrayList<>(); + for (CommentTarget.Kind kind : List.of(CommentTarget.Kind.INDEX, CommentTarget.Kind.SCHEMA, + CommentTarget.Kind.SEQUENCE, CommentTarget.Kind.DOMAIN, CommentTarget.Kind.TYPE, + CommentTarget.Kind.MATERIALIZED_VIEW)) { + for (String name : List.of("obj", "\"a.b\"", "\"a\"\"b.c\"", "\"한.글\"", "\".\"")) { + cases.add(Arguments.of(kind, name, null, name)); + } + if (kind != CommentTarget.Kind.SCHEMA) { + cases.add(Arguments.of(kind, "public.\"a.b\"", "public", "\"a.b\"")); + cases.add(Arguments.of(kind, "\"s.p\".\"a.b\"", "\"s.p\"", "\"a.b\"")); + } + } + return cases.stream(); + } + + @ParameterizedTest + @MethodSource("names") + void preservesNamedTargetsAndTheirComponents(CommentTarget.Kind kind, String name, + String schema, String leaf) throws Exception { + for (String literal : List.of("'body'", "$tag$body;\nquote's \\path$tag$", "NULL", "''")) { + String sql = + "COMMENT ON " + kind.name().replace('_', ' ') + " " + name + " IS " + literal; + for (Comment comment : roundTrip(sql)) { + assertNull(comment.getTable()); + assertNull(comment.getColumn()); + assertNull(comment.getView()); + CommentTarget target = comment.getTarget(); + assertEquals(kind, target.getKind()); + assertEquals(leaf, target.getName().getName()); + assertEquals(schema, target.getName().getSchemaName()); + assertEquals(name, target.getName().getFullyQualifiedName()); + if (literal.equals("NULL")) { + assertNull(comment.getComment()); + } else { + assertEquals(literal, comment.getComment().toString()); + } + } + } + } + + @ParameterizedTest + @ValueSource(strings = {"f", "f()", "f(integer)", "f(IN arg integer)", + "f(OUT result text)", "f(INOUT arg integer)", "f(VARIADIC args text[])", + "\"s.p\".\"f.x\"(IN \"arg.x\" pg_catalog.int4[])", + "f(timestamp with time zone, double precision)"}) + void preservesRoutineSignaturesWithoutTreatingThemAsCalls(String signature) throws Exception { + for (Comment comment : roundTrip("COMMENT ON FUNCTION " + signature + " IS 'body'")) { + assertEquals(CommentTarget.Kind.FUNCTION, comment.getTarget().getKind()); + RoutineReference routine = comment.getTarget().getRoutine(); + assertEquals( + signature.contains("(") ? signature.substring(0, signature.indexOf('(')) + : signature, + routine.getName()); + assertEquals(!signature.contains("("), routine.getArguments() == null); + assertTrue(new TablesNamesFinder().getTables(comment).isEmpty()); + assertTrue(comment.getFeatures().modifiesSchema()); + assertFalse(comment.getFeatures().may(StmtFeature.READS_DATA)); + assertFalse(comment.getFeatures().isOpaque()); + assertTrue(comment.getFeatures().getUnresolvedReferences().isEmpty()); + } + } + + @Test + void exposesArgumentModeNameAndDataType() throws Exception { + Comment comment = parse( + "COMMENT ON FUNCTION f(INOUT \"arg.x\" app.custom_type, VARIADIC rest text[]) IS NULL"); + List arguments = comment.getTarget().getRoutine().getArguments(); + assertEquals(RoutineReference.Argument.Mode.INOUT, arguments.get(0).getMode()); + assertEquals("\"arg.x\"", arguments.get(0).getName()); + assertEquals("app.custom_type", arguments.get(0).getDataType().toString()); + assertEquals(RoutineReference.Argument.Mode.VARIADIC, arguments.get(1).getMode()); + assertEquals("rest", arguments.get(1).getName()); + assertEquals("text[]", arguments.get(1).getDataType().toString()); + } + + @ParameterizedTest + @ValueSource(strings = {"t", "\"t.x\"", "public.\"t.x\"", "\"s.p\".\"t.x\""}) + void distinguishesConstraintTablesFromDomains(String owner) throws Exception { + for (boolean domain : List.of(false, true)) { + String sql = "COMMENT ON CONSTRAINT \"c.k\" ON " + (domain ? "DOMAIN " : "") + owner + + " IS $$body$$"; + for (Comment comment : roundTrip(sql)) { + CommentTarget target = comment.getTarget(); + assertEquals(CommentTarget.Kind.CONSTRAINT, target.getKind()); + assertEquals("\"c.k\"", target.getName().getName()); + assertNull(target.getName().getSchemaName()); + assertEquals(domain, target.isOnDomain()); + assertEquals(owner, target.getRelation().getFullyQualifiedName()); + assertEquals(domain ? Set.of() : Set.of(owner), + new TablesNamesFinder().getTables(comment)); + } + } + } + + @ParameterizedTest + @ValueSource(strings = {"domain", "domain.t", "\"DOMAIN\""}) + void domainKeywordCanNameTheConstraintTable(String name) throws Exception { + for (Comment comment : roundTrip("COMMENT ON CONSTRAINT ck ON " + name + " IS NULL")) { + assertFalse(comment.getTarget().isOnDomain()); + assertEquals(name, comment.getTarget().getRelation().getFullyQualifiedName()); + assertEquals(Set.of(name), new TablesNamesFinder().getTables(comment)); + } + } + + @ParameterizedTest + @ValueSource(strings = {"INDEX", "MATERIALIZED obj", "MATERIALIZED VIEW", "FUNCTION f(*)", + "FUNCTION f(integer ORDER BY integer)", "FUNCTION f(IN)", + "FUNCTION f(x integer DEFAULT 1)", + "CONSTRAINT ck", "CONSTRAINT ck ON", + "CONSTRAINT public.ck ON t", + "UNKNOWN obj"}) + void rejectsIncompleteOrInvalidTargets(String target) { + assertThrows(JSQLParserException.class, () -> parse("COMMENT ON " + target + " IS NULL")); + } + + @Test + void findsOnlyRelationsExplicitlyNamedByComments() throws Exception { + for (String target : List.of("INDEX idx", "SCHEMA app", "SEQUENCE seq", "DOMAIN d", + "TYPE ty", "FUNCTION f(int)")) { + assertTrue(new TablesNamesFinder().getTables(parse("COMMENT ON " + target + " IS NULL")) + .isEmpty()); + } + for (String target : List.of("TABLE app.t", "COLUMN app.t.c", "VIEW app.t", + "MATERIALIZED VIEW app.t", "CONSTRAINT ck ON app.t")) { + assertEquals(Set.of("app.t"), + new TablesNamesFinder().getTables(parse("COMMENT ON " + target + " IS NULL"))); + } + } + + @Test + void defaultConfigurationAcceptsUnambiguousTargetsButTagsRemainOptIn() throws Exception { + Comment plain = (Comment) CCJSqlParserUtil.parse("COMMENT ON INDEX idx IS 'body'"); + assertEquals(CommentTarget.Kind.INDEX, plain.getTarget().getKind()); + assertThrows(JSQLParserException.class, + () -> CCJSqlParserUtil.parse("COMMENT ON INDEX idx IS $tag$body$tag$")); + Comment tagged = (Comment) CCJSqlParserUtil.parse("COMMENT ON INDEX idx IS $tag$body$tag$", + p -> p.withDollarQuotedStringTags(true)); + assertEquals("body", tagged.getComment().getValue()); + } + + @Test + void keepsFollowingStatementsOutsideCommentBodies() throws Exception { + Statements statements = CCJSqlParserUtil.parseStatements( + "COMMENT ON FUNCTION f() IS $tag$body; SELECT 0$tag$; COMMENT ON INDEX idx IS NULL; SELECT 42", + p -> p.withDialect(Dialect.POSTGRESQL)); + assertEquals(3, statements.size()); + assertEquals("body; SELECT 0", ((Comment) statements.get(0)).getComment().getValue()); + assertEquals("SELECT 42", statements.get(2).toString()); + } + + @Test + void retainsLegacyAccessorsAndAllowsSwitchingTargets() throws Exception { + Comment comment = parse("COMMENT ON INDEX idx IS 'body'"); + CommentTarget target = comment.getTarget(); + comment.setTable(new Table("t")); + assertNull(comment.getTarget()); + assertEquals("COMMENT ON TABLE t IS 'body'", comment.toString()); + comment.withTarget(target); + assertNull(comment.getTable()); + assertEquals("COMMENT ON INDEX idx IS 'body'", comment.toString()); + comment.setColumn(new Column(new Table("t"), "c")); + assertNull(comment.getTarget()); + assertEquals("COMMENT ON COLUMN t.c IS 'body'", comment.toString()); + } + + @Test + void expressionVisitorsReceiveCommentLiteralAndContext() throws Exception { + List values = new ArrayList<>(); + ExpressionVisitorAdapter expressions = new ExpressionVisitorAdapter() { + @Override + public Void visit(StringValue value, S context) { + assertEquals("context", context); + values.add(value.getValue()); + return null; + } + }; + parse("COMMENT ON INDEX idx IS 'body'").accept( + new StatementVisitorAdapter<>(new SelectVisitorAdapter<>(expressions)), "context"); + assertEquals(List.of("body"), values); + } + + @Test + void customDeparsersCanRewriteRelationsAndLiterals() throws Exception { + Comment comment = parse("COMMENT ON CONSTRAINT ck ON app.t IS 'body'"); + StringBuilder builder = new StringBuilder(); + ExpressionDeParser expressions = new ExpressionDeParser() { + @Override + public StringBuilder visit(StringValue value, S context) { + assertEquals("context", context); + return getBuilder().append("'changed'"); + } + }; + SelectDeParser selects = new SelectDeParser() { + @Override + public StringBuilder visit(Table table, S context) { + assertEquals("context", context); + return getBuilder().append("app.new_t"); + } + }; + comment.accept(new StatementDeParser(expressions, selects, builder), "context"); + assertEquals("COMMENT ON CONSTRAINT ck ON app.new_t IS 'changed'", builder.toString()); + assertEquals("COMMENT ON CONSTRAINT ck ON app.t IS 'body'", comment.toString()); + } + + private static Comment parse(String sql) throws JSQLParserException { + return (Comment) CCJSqlParserUtil.parse(sql, p -> p.withDialect(Dialect.POSTGRESQL)); + } + + private static List roundTrip(String sql) throws JSQLParserException { + Comment comment = parse(sql); + StringBuilder builder = new StringBuilder(); + comment.accept(new StatementDeParser(builder), null); + assertEquals(comment.toString(), builder.toString()); + return List.of(comment, parse(comment.toString()), parse(builder.toString())); + } +} diff --git a/src/test/java/net/sf/jsqlparser/statement/comment/CommentTest.java b/src/test/java/net/sf/jsqlparser/statement/comment/CommentTest.java index 840905bdd2..35bdd8a7b6 100755 --- a/src/test/java/net/sf/jsqlparser/statement/comment/CommentTest.java +++ b/src/test/java/net/sf/jsqlparser/statement/comment/CommentTest.java @@ -78,7 +78,36 @@ public void testCommentColumnDeparse() throws JSQLParserException { @Test public void testToString() { Comment comment = new Comment(); - assertEquals("COMMENT ON IS null", comment.toString()); + assertEquals("COMMENT ON IS NULL", comment.toString()); + } + + @Test + public void testCommentTableIsNull() throws JSQLParserException { + String statement = "COMMENT ON TABLE schema1.table1 IS NULL"; + Comment comment = (Comment) CCJSqlParserUtil.parse(statement); + assertEquals("table1", comment.getTable().getName()); + assertThat(comment.getComment()).isNull(); + assertSqlCanBeParsedAndDeparsed(statement); + } + + @Test + public void testCommentColumnIsNull() throws JSQLParserException { + String statement = "COMMENT ON COLUMN table1.column1 IS NULL"; + Comment comment = (Comment) CCJSqlParserUtil.parse(statement); + assertEquals("column1", comment.getColumn().getColumnName()); + assertThat(comment.getComment()).isNull(); + assertSqlCanBeParsedAndDeparsed(statement); + } + + @Test + public void testCommentViewIsNull() throws JSQLParserException { + assertSqlCanBeParsedAndDeparsed("COMMENT ON VIEW myschema.myView IS NULL"); + } + + @Test + public void testCommentIsNullLowercase() throws JSQLParserException { + Comment comment = (Comment) CCJSqlParserUtil.parse("comment on table table1 is null"); + assertThat(comment.getComment()).isNull(); } @Test diff --git a/src/test/java/net/sf/jsqlparser/statement/create/ColumnAttributeTest.java b/src/test/java/net/sf/jsqlparser/statement/create/ColumnAttributeTest.java new file mode 100644 index 0000000000..faf18fdbfc --- /dev/null +++ b/src/test/java/net/sf/jsqlparser/statement/create/ColumnAttributeTest.java @@ -0,0 +1,239 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.statement.create; + +import static org.junit.jupiter.api.Assertions.*; +import static net.sf.jsqlparser.util.validation.ValidationTestAsserts.validateNotAllowed; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Stream; +import java.util.stream.Collectors; +import net.sf.jsqlparser.JSQLParserException; +import net.sf.jsqlparser.expression.ExpressionVisitorAdapter; +import net.sf.jsqlparser.expression.LongValue; +import net.sf.jsqlparser.expression.StringValue; +import net.sf.jsqlparser.parser.AbstractJSqlParser.Dialect; +import net.sf.jsqlparser.parser.CCJSqlParserUtil; +import net.sf.jsqlparser.parser.feature.Feature; +import net.sf.jsqlparser.statement.Statement; +import net.sf.jsqlparser.statement.StatementVisitorAdapter; +import net.sf.jsqlparser.statement.alter.Alter; +import net.sf.jsqlparser.statement.create.table.ColumnDefinition; +import net.sf.jsqlparser.statement.create.table.ColumnOption; +import net.sf.jsqlparser.statement.create.table.CreateTable; +import net.sf.jsqlparser.statement.create.table.GeneratedColumnDefinition; +import net.sf.jsqlparser.statement.select.SelectVisitorAdapter; +import net.sf.jsqlparser.util.deparser.ExpressionDeParser; +import net.sf.jsqlparser.util.deparser.SelectDeParser; +import net.sf.jsqlparser.util.deparser.StatementDeParser; +import net.sf.jsqlparser.util.validation.feature.FeaturesAllowed; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.junit.jupiter.params.provider.ValueSource; + +class ColumnAttributeTest { + static Stream attributes() { + return Stream.of( + Arguments.of("NOT NULL", ColumnOption.Kind.NULLABILITY), + Arguments.of("NULL", ColumnOption.Kind.NULLABILITY), + Arguments.of("AUTO_INCREMENT", ColumnOption.Kind.AUTO_INCREMENT), + Arguments.of("VISIBLE", ColumnOption.Kind.VISIBILITY), + Arguments.of("INVISIBLE", ColumnOption.Kind.VISIBILITY), + Arguments.of("COLLATE utf8mb4_bin", ColumnOption.Kind.COLLATE), + Arguments.of("COLLATE `a.b`", ColumnOption.Kind.COLLATE), + Arguments.of("COMMENT 'quote''s \\n body'", ColumnOption.Kind.COMMENT), + Arguments.of("COMMENT ''", ColumnOption.Kind.COMMENT), + Arguments.of("ON UPDATE CURRENT_TIMESTAMP", ColumnOption.Kind.ON_UPDATE), + Arguments.of("ON UPDATE CURRENT_TIMESTAMP(6)", ColumnOption.Kind.ON_UPDATE), + Arguments.of("ON UPDATE NOW()", ColumnOption.Kind.ON_UPDATE), + Arguments.of("PRIMARY KEY", ColumnOption.Kind.CONSTRAINT)); + } + + @ParameterizedTest + @MethodSource("attributes") + void createAndAlterStructureAttributes(String attribute, ColumnOption.Kind kind) + throws Exception { + for (String sql : statements("VARCHAR(20) " + attribute)) { + for (Statement statement : roundTrip(sql, Dialect.MYSQL)) { + ColumnOption option = column(statement).getColumnOptions().get(0); + assertEquals(kind, option.getKind()); + assertEquals(attribute, option.toString()); + assertNull(option.getDefaultExpression()); + } + } + } + + @ParameterizedTest + @ValueSource(strings = {"x + 1", "COALESCE(x, 0) * 2", "CASE WHEN x > 0 THEN x ELSE 0 END", + "CAST(x AS CHAR(10))", "CONCAT('prefix,', x)", "((x + 1) * 2)"}) + void generatedExpressionsKeepPrefixStorageAndFollowingOptions(String expression) + throws Exception { + for (String prefix : List.of("", "GENERATED ALWAYS ")) { + for (String storage : List.of("", " STORED", " VIRTUAL")) { + for (String sql : statements("INT " + prefix + "AS (" + expression + ")" + storage + + " NOT NULL COMMENT 'body'")) { + for (Statement statement : roundTrip(sql, Dialect.MYSQL)) { + List options = column(statement).getColumnOptions(); + assertEquals( + List.of(ColumnOption.Kind.GENERATED, ColumnOption.Kind.NULLABILITY, + ColumnOption.Kind.COMMENT), + options.stream().map(ColumnOption::getKind) + .collect(Collectors.toList())); + GeneratedColumnDefinition generated = + options.get(0).getGeneratedDefinition(); + assertEquals(!prefix.isEmpty(), generated.isGeneratedAlways()); + assertEquals( + storage.isEmpty() ? null + : GeneratedColumnDefinition.Storage.valueOf(storage.trim()), + generated.getStorage()); + assertEquals(CCJSqlParserUtil.parseExpression(expression).toString(), + generated.getExpression().toString()); + } + } + } + } + } + + @ParameterizedTest + @ValueSource(strings = {"\"C\"", "pg_catalog.\"C\"", "\"s.p\".\"a.b\""}) + void postgresCollationAndCharsetAreSeparateAttributes(String name) throws Exception { + for (Statement statement : roundTrip( + "CREATE TABLE t (c TEXT COLLATE " + name + " NOT NULL)", Dialect.POSTGRESQL)) { + assertEquals(name, column(statement).getColumnOptions().get(0).getCollation()); + } + Statement mysql = + parse("CREATE TABLE t (c VARCHAR(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin)", + Dialect.MYSQL); + assertEquals("utf8mb4", column(mysql).getColDataType().getCharacterSet()); + assertEquals("utf8mb4_bin", column(mysql).getColumnOptions().get(0).getCollation()); + } + + @Test + void keepsIdentityReferencesDefaultsAndRawExtensionsDistinct() throws Exception { + Statement statement = parse( + "CREATE TABLE t (c INT NOT NULL DEFAULT 0 ON UPDATE CURRENT_TIMESTAMP(6) " + + "REFERENCES parent(id) ON UPDATE CASCADE COMMENT 'body' COLUMN_FORMAT FIXED)", + Dialect.MYSQL); + List options = column(statement).getColumnOptions(); + assertEquals(List.of(ColumnOption.Kind.NULLABILITY, ColumnOption.Kind.DEFAULT, + ColumnOption.Kind.ON_UPDATE, ColumnOption.Kind.REFERENCE, + ColumnOption.Kind.COMMENT), + options.subList(0, 5).stream().map(ColumnOption::getKind) + .collect(Collectors.toList())); + assertTrue(options.stream().skip(5).allMatch(o -> o.getKind() == ColumnOption.Kind.OTHER)); + for (String definition : List.of("INT GENERATED ALWAYS AS IDENTITY", + "INT GENERATED BY DEFAULT AS IDENTITY")) { + assertEquals(ColumnOption.Kind.IDENTITY, + column(parse("CREATE TABLE t (c " + definition + ")", Dialect.POSTGRESQL)) + .getColumnOptions().get(0).getKind()); + } + } + + @Test + void mutationsUpdateLegacyTokensAndBothRenderers() throws Exception { + Statement statement = + parse("CREATE TABLE t (c INT NOT NULL INVISIBLE COMMENT 'old' AS (x + 1) STORED)", + Dialect.MYSQL); + List options = column(statement).getColumnOptions(); + options.get(0).setNullable(true); + options.get(1).setVisible(true); + options.get(2).setComment(new StringValue("'new'")); + options.get(3).getGeneratedDefinition().setExpression(new LongValue(42)); + options.get(3).getGeneratedDefinition() + .setStorage(GeneratedColumnDefinition.Storage.VIRTUAL); + assertEquals(List.of("NULL", "VISIBLE", "COMMENT", "'new'", "AS", "(42)", "VIRTUAL"), + column(statement).getColumnSpecs()); + roundTrip(statement.toString(), Dialect.MYSQL); + column(statement).addColumnSpecs("CUSTOM"); + assertEquals(ColumnOption.Kind.OTHER, options.get(options.size() - 1).getKind()); + column(statement).setColumnSpecs(List.of("NOT", "NULL")); + assertNull(column(statement).getColumnOptions()); + assertEquals("CREATE TABLE t (c INT NOT NULL)", statement.toString()); + } + + @ParameterizedTest + @ValueSource(strings = {"CREATE TABLE t (c INT AS (1 + 2) COMMENT 'body')", + "ALTER TABLE t ADD c INT AS (1 + 2) COMMENT 'body'"}) + void visitorsAndCustomDeparsersReachExpressionsWithContext(String sql) throws Exception { + Statement statement = parse(sql, Dialect.MYSQL); + List visited = new ArrayList<>(); + ExpressionVisitorAdapter expressions = new ExpressionVisitorAdapter() { + @Override + public Void visit(LongValue value, S context) { + assertEquals("ctx", context); + visited.add(value.toString()); + return null; + } + + @Override + public Void visit(StringValue value, S context) { + assertEquals("ctx", context); + visited.add(value.getValue()); + return null; + } + }; + statement.accept(new StatementVisitorAdapter<>(new SelectVisitorAdapter<>(expressions)), + "ctx"); + assertEquals(List.of("1", "2", "body"), visited); + StringBuilder builder = new StringBuilder(); + ExpressionDeParser printer = new ExpressionDeParser() { + @Override + public StringBuilder visit(LongValue value, S context) { + return getBuilder().append(value.getValue() + 10); + } + }; + statement.accept(new StatementDeParser(printer, new SelectDeParser(), builder), null); + assertEquals(statement.toString().replace("1 + 2", "11 + 12"), builder.toString()); + } + + @ParameterizedTest + @ValueSource(strings = {"AS (?)", "ON UPDATE ?"}) + void validationReachesNewExpressions(String attribute) { + validateNotAllowed("CREATE TABLE t (c INT " + attribute + ")", 1, 1, FeaturesAllowed.DDL, + Feature.jdbcParameter); + } + + @ParameterizedTest + @ValueSource( + strings = {"COMMENT", "COLLATE", "ON UPDATE", "AS (1 +)", "GENERATED ALWAYS AS (1 +)"}) + void rejectsIncompleteStructuredAttributes(String attribute) { + assertThrows(JSQLParserException.class, + () -> parse("CREATE TABLE t (c INT " + attribute + ")", Dialect.MYSQL)); + } + + private static List statements(String definition) { + return List.of("CREATE TABLE t (c " + definition + ")", + "ALTER TABLE t ADD COLUMN c " + definition, + "ALTER TABLE t MODIFY COLUMN c " + definition, + "ALTER TABLE t CHANGE COLUMN old_c c " + definition); + } + + private static Statement parse(String sql, Dialect dialect) throws JSQLParserException { + return CCJSqlParserUtil.parse(sql, p -> p.withDialect(dialect)); + } + + private static ColumnDefinition column(Statement statement) { + return statement instanceof CreateTable + ? ((CreateTable) statement).getColumnDefinitions().get(0) + : ((Alter) statement).getAlterExpressions().get(0).getColDataTypeList().get(0); + } + + private static List roundTrip(String sql, Dialect dialect) + throws JSQLParserException { + Statement statement = parse(sql, dialect); + StringBuilder builder = new StringBuilder(); + statement.accept(new StatementDeParser(builder), null); + assertEquals(statement.toString(), builder.toString()); + return List.of(statement, parse(statement.toString(), dialect), + parse(builder.toString(), dialect)); + } +} diff --git a/src/test/java/net/sf/jsqlparser/statement/create/ColumnDefaultExpressionTest.java b/src/test/java/net/sf/jsqlparser/statement/create/ColumnDefaultExpressionTest.java new file mode 100644 index 0000000000..c99e6a11e1 --- /dev/null +++ b/src/test/java/net/sf/jsqlparser/statement/create/ColumnDefaultExpressionTest.java @@ -0,0 +1,203 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.statement.create; + +import static net.sf.jsqlparser.util.validation.ValidationTestAsserts.validateNotAllowed; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import net.sf.jsqlparser.JSQLParserException; +import net.sf.jsqlparser.expression.Expression; +import net.sf.jsqlparser.expression.ExpressionVisitorAdapter; +import net.sf.jsqlparser.expression.LongValue; +import net.sf.jsqlparser.parser.CCJSqlParserUtil; +import net.sf.jsqlparser.parser.feature.Feature; +import net.sf.jsqlparser.util.validation.feature.FeaturesAllowed; +import net.sf.jsqlparser.parser.AbstractJSqlParser.Dialect; +import net.sf.jsqlparser.statement.Statement; +import net.sf.jsqlparser.statement.StatementVisitorAdapter; +import net.sf.jsqlparser.statement.alter.Alter; +import net.sf.jsqlparser.statement.create.table.ColumnDefinition; +import net.sf.jsqlparser.statement.create.table.ColumnOption; +import net.sf.jsqlparser.statement.create.table.CreateTable; +import net.sf.jsqlparser.statement.select.SelectVisitorAdapter; +import net.sf.jsqlparser.util.deparser.ExpressionDeParser; +import net.sf.jsqlparser.util.deparser.SelectDeParser; +import net.sf.jsqlparser.util.deparser.StatementDeParser; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +class ColumnDefaultExpressionTest { + @ParameterizedTest + @ValueSource(strings = {"1", "-1", "NULL", "true", "CURRENT_TIMESTAMP(6)", + "nextval('app.counter'::regclass)", "'value'::character varying", "ARRAY[1, 2]::int[]", + "(1 + 2 * 3)", "(UUID_TO_BIN(UUID()))", "(JSON_ARRAY())", + "(CURRENT_DATE + INTERVAL 1 YEAR)", "CASE WHEN 1 = 1 THEN 2 ELSE 3 END", + "1 > 0", "1 NOT IN (2, 3)"}) + void createAndAlterKeepDefaultsSeparateFromFollowingConstraints(String value) + throws JSQLParserException { + Expression expected = CCJSqlParserUtil.parseExpression(value); + for (String prefix : Arrays.asList("CREATE TABLE t (a INT DEFAULT ", + "ALTER TABLE t ADD COLUMN a INT DEFAULT ", + "ALTER TABLE t MODIFY COLUMN a INT DEFAULT ")) { + Statement statement = CCJSqlParserUtil.parse(prefix + value + " NOT NULL" + + (prefix.startsWith("CREATE") ? ", b INT)" : ", ADD COLUMN b INT")); + ColumnDefinition column = firstColumn(statement); + ColumnOption option = defaultOption(column); + assertEquals(expected.getClass(), option.getDefaultExpression().getClass()); + assertEquals(expected.toString(), option.getDefaultExpression().toString()); + assertThat(column.getColumnSpecs()).containsExactly("DEFAULT", expected.toString(), + "NOT", "NULL"); + assertRoundTrip(statement); + } + } + + @ParameterizedTest + @ValueSource(strings = { + "CREATE TABLE t (a INT DEFAULT (1 + 2), b INT DEFAULT 3)", + "ALTER TABLE t ADD (a INT DEFAULT (1 + 2), b INT DEFAULT 3)", + "ALTER TABLE t MODIFY (a INT DEFAULT (1 + 2), b INT DEFAULT 3)", + "ALTER TABLE t ADD a INT DEFAULT (1 + 2), MODIFY b INT DEFAULT 3"}) + void visitorReachesEachDefaultOnceWithContext(String sql) throws JSQLParserException { + List values = new ArrayList<>(); + ExpressionVisitorAdapter expressions = new ExpressionVisitorAdapter() { + @Override + public Void visit(LongValue value, S context) { + assertEquals("context", context); + values.add(value.getValue()); + return null; + } + }; + CCJSqlParserUtil.parse(sql) + .accept(new StatementVisitorAdapter<>(new SelectVisitorAdapter<>(expressions)), + "context"); + assertThat(values).containsExactly(1L, 2L, 3L); + } + + @ParameterizedTest + @ValueSource(strings = { + "CREATE TABLE t (a INT DEFAULT (1 + 2) NOT NULL)", + "CREATE TABLE t OF row_type (PRIMARY KEY (a), a WITH OPTIONS DEFAULT (1 + 2))", + "ALTER TABLE t ADD COLUMN IF NOT EXISTS a INT DEFAULT (1 + 2)", + "ALTER TABLE t ADD (a INT DEFAULT 1, b INT DEFAULT 2)", + "ALTER TABLE t MODIFY (a INT DEFAULT 1, b INT DEFAULT 2)", + "ALTER TABLE t MODIFY (a INT DEFAULT (1 + 2))", + "ALTER TABLE t CHANGE COLUMN old_a a INT DEFAULT (1 + 2) AFTER id", + "ALTER TABLE t MODIFY COLUMN a INT DEFAULT (1 + 2) FIRST", + "ALTER TABLE t ALTER COLUMN a TYPE INT USING (1 + 2)"}) + void sharedRenderingUsesCustomExpressionPrinterAndPreservesAlterLayout(String sql) + throws JSQLParserException { + Statement statement = parse(sql); + StringBuilder output = new StringBuilder(); + ExpressionDeParser expressions = new ExpressionDeParser() { + @Override + public StringBuilder visit(LongValue value, S context) { + return getBuilder().append(value.getValue() + 100); + } + }; + statement.accept(new StatementDeParser(expressions, new SelectDeParser(), output), null); + assertEquals(statement.toString().replace("1", "101").replace("2", "102"), + output.toString()); + assertRoundTrip(statement); + } + + @ParameterizedTest + @ValueSource(strings = {"CREATE TABLE t (a INT DEFAULT (1 + 2) NOT NULL)", + "ALTER TABLE t ADD COLUMN a INT DEFAULT (1 + 2) NOT NULL"}) + void replacingDefaultUpdatesLegacyTokensAndBothRenderers(String sql) + throws JSQLParserException { + Statement statement = parse(sql); + ColumnDefinition column = firstColumn(statement); + defaultOption(column).setDefaultExpression(new LongValue(42)); + assertThat(column.getColumnSpecs()).containsExactly("DEFAULT", "42", "NOT", "NULL"); + assertEquals(sql.replace("(1 + 2)", "42"), statement.toString()); + assertRoundTrip(statement); + column.addColumnSpecs("UNIQUE"); + assertNotNull(defaultOption(column).getDefaultExpression()); + assertRoundTrip(statement); + column.setColumnSpecs(Arrays.asList("DEFAULT", "9")); + assertNull(column.getColumnOptions()); + assertEquals(sql.replace("(1 + 2) NOT NULL", "9"), statement.toString()); + assertRoundTrip(statement); + } + + @Test + void identityAndSerialDefaultsKeepTheirOwnOptionKinds() throws JSQLParserException { + ColumnDefinition identity = firstColumn(CCJSqlParserUtil.parse( + "CREATE TABLE t (a INT GENERATED BY DEFAULT AS IDENTITY)")); + assertEquals(ColumnOption.Kind.IDENTITY, identity.getColumnOptions().get(0).getKind()); + assertNull(identity.getColumnOptions().get(0).getDefaultExpression()); + ColumnDefinition serial = firstColumn(CCJSqlParserUtil.parse( + "CREATE TABLE t (a INT SERIAL DEFAULT VALUE)")); + assertEquals(ColumnOption.Kind.SERIAL_DEFAULT_VALUE, + serial.getColumnOptions().get(0).getKind()); + assertNull(serial.getColumnOptions().get(0).getDefaultExpression()); + } + + @ParameterizedTest + @ValueSource(strings = { + "CREATE TABLE t (a TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP)", + "CREATE TABLE t (a INT DEFAULT ON NULL 1 NOT NULL)", + "CREATE TABLE t (a INT WITH DEFAULT)", + "CREATE TABLE t (a INT DEFAULT NOT NULL)", + "CREATE TABLE t (a INT NOT NULL WITH DEFAULT, b INT DEFAULT)", + "CREATE TABLE t (a INT CONSTRAINT df DEFAULT 1 REFERENCES parent(id))", + "ALTER TABLE t MODIFY (COLUMN a DROP DEFAULT, COLUMN b DROP DEFAULT)"}) + void neighboringAndLegacyColumnOptionsStillRoundTrip(String sql) throws JSQLParserException { + assertRoundTrip(CCJSqlParserUtil.parse(sql)); + } + + @ParameterizedTest + @ValueSource(strings = {"CREATE TABLE t (a INT DEFAULT (1 + 2", + "CREATE TABLE t (a INT DEFAULT (1 +))", + "ALTER TABLE t ADD a INT DEFAULT (1 +), ADD b INT"}) + void rejectsIncompleteDefaultExpressions(String sql) { + assertThrows(JSQLParserException.class, () -> CCJSqlParserUtil.parse(sql)); + } + + @ParameterizedTest + @ValueSource(strings = {"CREATE TABLE t (a INT DEFAULT ?)", + "ALTER TABLE t ADD COLUMN a INT DEFAULT ?"}) + void validationChecksStructuredDefaultExpressions(String sql) { + validateNotAllowed(sql, 1, 1, FeaturesAllowed.DDL, Feature.jdbcParameter); + } + + private static Statement parse(String sql) throws JSQLParserException { + return sql.contains(" OF ") + ? CCJSqlParserUtil.parse(sql, parser -> parser.withDialect(Dialect.POSTGRESQL)) + : CCJSqlParserUtil.parse(sql); + } + + private static ColumnDefinition firstColumn(Statement statement) { + return statement instanceof CreateTable + ? ((CreateTable) statement).getColumnDefinitions().get(0) + : ((Alter) statement).getAlterExpressions().get(0).getColDataTypeList().get(0); + } + + private static ColumnOption defaultOption(ColumnDefinition column) { + return column.getColumnOptions().stream() + .filter(option -> option.getKind() == ColumnOption.Kind.DEFAULT) + .findFirst().orElseThrow(AssertionError::new); + } + + private static void assertRoundTrip(Statement statement) throws JSQLParserException { + StringBuilder output = new StringBuilder(); + statement.accept(new StatementDeParser(output), null); + assertEquals(statement.toString(), output.toString()); + assertEquals(statement.toString(), parse(output.toString()).toString()); + } +} diff --git a/src/test/java/net/sf/jsqlparser/statement/create/ColumnOptionMutationTest.java b/src/test/java/net/sf/jsqlparser/statement/create/ColumnOptionMutationTest.java new file mode 100644 index 0000000000..bceb012c4d --- /dev/null +++ b/src/test/java/net/sf/jsqlparser/statement/create/ColumnOptionMutationTest.java @@ -0,0 +1,92 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.statement.create; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.Arrays; +import net.sf.jsqlparser.JSQLParserException; +import net.sf.jsqlparser.parser.CCJSqlParserUtil; +import net.sf.jsqlparser.statement.create.table.ColumnDefinition; +import net.sf.jsqlparser.statement.create.table.ColumnOption; +import net.sf.jsqlparser.statement.create.table.CreateTable; +import net.sf.jsqlparser.statement.create.table.ForeignKeyReference; +import net.sf.jsqlparser.util.TablesNamesFinder; +import net.sf.jsqlparser.util.deparser.StatementDeParser; +import org.junit.jupiter.api.Test; + +class ColumnOptionMutationTest { + @Test + void clearingIdentityDoesNotRestoreRawSpecifications() throws JSQLParserException { + CreateTable table = parse("id INT GENERATED ALWAYS AS IDENTITY"); + ColumnDefinition column = table.getColumnDefinitions().get(0); + column.getColumnOptions().clear(); + assertTrue(column.getColumnSpecs().isEmpty()); + assertSql(table, "CREATE TABLE t (id INT)"); + } + + @Test + void legacyAppendPreservesReferenceAndTraversal() throws JSQLParserException { + CreateTable table = parse("id INT REFERENCES parent(id)"); + ColumnDefinition column = table.getColumnDefinitions().get(0); + ForeignKeyReference reference = column.getForeignKeyReference(); + column.addColumnSpecs("NOT", "NULL"); + column.addColumnSpecs(Arrays.asList("DEFAULT", "1")); + assertSame(reference, column.getForeignKeyReference()); + assertTrue(new TablesNamesFinder().getTables(table).contains("parent")); + assertSql(table, "CREATE TABLE t (id INT REFERENCES parent(id) NOT NULL DEFAULT 1)"); + } + + @Test + void addingStructuredOptionPreservesExistingRawSpecifications() throws JSQLParserException { + CreateTable table = parse("id INT"); + ColumnDefinition column = table.getColumnDefinitions().get(0); + column.setColumnSpecs(Arrays.asList("NOT", "NULL")); + assertNull(column.getColumnOptions()); + ForeignKeyReference reference = parse("id INT REFERENCES parent(id)") + .getColumnDefinitions().get(0).getForeignKeyReference(); + column.addColumnOptions(ColumnOption.reference(reference)); + assertSql(table, "CREATE TABLE t (id INT NOT NULL REFERENCES parent(id))"); + assertTrue(new TablesNamesFinder().getTables(table).contains("parent")); + } + + @Test + void replacingOptionsWithNullClearsPreviousSpecifications() throws JSQLParserException { + CreateTable table = parse("id INT GENERATED ALWAYS AS IDENTITY"); + ColumnDefinition column = table.getColumnDefinitions().get(0); + column.setColumnOptions(null); + assertNull(column.getColumnSpecs()); + assertSql(table, "CREATE TABLE t (id INT)"); + } + + @Test + void legacySetterStillReplacesStructuredOptions() throws JSQLParserException { + CreateTable table = parse("id INT REFERENCES parent(id)"); + ColumnDefinition column = table.getColumnDefinitions().get(0); + column.setColumnSpecs(Arrays.asList("NOT", "NULL")); + assertNull(column.getForeignKeyReference()); + assertSql(table, "CREATE TABLE t (id INT NOT NULL)"); + } + + private static CreateTable parse(String definition) throws JSQLParserException { + return (CreateTable) CCJSqlParserUtil.parse("CREATE TABLE t (" + definition + ")"); + } + + private static void assertSql(CreateTable table, String expected) throws JSQLParserException { + assertEquals(expected, table.toString()); + StringBuilder buffer = new StringBuilder(); + table.accept(new StatementDeParser(buffer), null); + assertEquals(expected, buffer.toString()); + assertEquals(expected, CCJSqlParserUtil.parse(expected).toString()); + } +} diff --git a/src/test/java/net/sf/jsqlparser/statement/create/CreateIndexTest.java b/src/test/java/net/sf/jsqlparser/statement/create/CreateIndexTest.java index 2096b97301..32baade27c 100644 --- a/src/test/java/net/sf/jsqlparser/statement/create/CreateIndexTest.java +++ b/src/test/java/net/sf/jsqlparser/statement/create/CreateIndexTest.java @@ -19,7 +19,9 @@ import java.util.List; import net.sf.jsqlparser.JSQLParserException; import net.sf.jsqlparser.parser.CCJSqlParserManager; +import net.sf.jsqlparser.parser.AbstractJSqlParser.Dialect; import net.sf.jsqlparser.statement.create.index.CreateIndex; +import net.sf.jsqlparser.statement.create.table.Index; import org.junit.jupiter.api.Test; public class CreateIndexTest { @@ -174,4 +176,123 @@ public void testCreateIndexVisibility() throws JSQLParserException { assertSqlCanBeParsedAndDeparsed("CREATE INDEX idx_a ON t1 (a) INVISIBLE", true); assertSqlCanBeParsedAndDeparsed("CREATE INDEX idx_a ON t1 (a) VISIBLE", true); } + + @Test + public void testCreateIndexIncludeIssue2459() throws JSQLParserException { + assertSqlCanBeParsedAndDeparsed("CREATE INDEX idx_a ON t1 (a) INCLUDE (b, c)"); + } + + @Test + public void testCreateIndexKeyPartWithPrefixLengthAndDirectionIssue2490() + throws JSQLParserException { + // MySQL writes the prefix length without a space, JSqlParser deparses it with one. + String statement = "CREATE INDEX i03 ON t (c1(20) DESC)"; + CreateIndex createIndex = (CreateIndex) parserManager.parse(new StringReader(statement)); + + List params = createIndex.getIndex().getColumns().get(0).getParams(); + assertEquals(2, params.size()); + assertEquals("(20)", params.get(0)); + assertEquals("DESC", params.get(1)); + assertEquals(Index.ColumnParams.SortOrder.DESC, + createIndex.getIndex().getColumns().get(0).getSortOrder()); + + assertSqlCanBeParsedAndDeparsed("CREATE INDEX i03 ON t (c1 (20) DESC)"); + assertSqlCanBeParsedAndDeparsed("CREATE INDEX i04 ON t (c1 (20) ASC, c2 (10) DESC)"); + assertSqlCanBeParsedAndDeparsed("CREATE UNIQUE INDEX i25 ON t (c1 (10) DESC)"); + } + + @Test + public void testCreateIndexKeyBlockSizeIssue2490() throws JSQLParserException { + assertSqlCanBeParsedAndDeparsed("CREATE INDEX i08 ON t (c1) KEY_BLOCK_SIZE = 8"); + assertSqlCanBeParsedAndDeparsed("CREATE INDEX i09 ON t (c1) KEY_BLOCK_SIZE 8"); + assertSqlCanBeParsedAndDeparsed( + "CREATE INDEX i14 ON t (c1) USING BTREE KEY_BLOCK_SIZE = 8 COMMENT 'combo' INVISIBLE"); + } + + @Test + public void testCreateIndexAlgorithmAndLockOptionsIssue2490() throws JSQLParserException { + assertSqlCanBeParsedAndDeparsed( + "CREATE INDEX i10 ON t (c1) ALGORITHM = INPLACE LOCK = NONE"); + assertSqlCanBeParsedAndDeparsed("CREATE INDEX i11 ON t (c1) ALGORITHM INPLACE LOCK NONE"); + assertSqlCanBeParsedAndDeparsed("CREATE INDEX i12 ON t (c1) ALGORITHM = INPLACE"); + assertSqlCanBeParsedAndDeparsed("CREATE INDEX i13 ON t (c1) LOCK = NONE"); + + CreateIndex createIndex = (CreateIndex) parserManager + .parse(new StringReader("CREATE INDEX i10 ON t (c1) ALGORITHM=INPLACE LOCK=NONE")); + assertEquals(List.of("ALGORITHM", "=", "INPLACE", "LOCK", "=", "NONE"), + createIndex.getTailParameters()); + } + + @Test + public void testCreateFullTextAndSpatialIndexIssue2490() throws JSQLParserException { + // These used to fall back to UnsupportedStatement instead of producing a CreateIndex. + CreateIndex fullText = (CreateIndex) parserManager + .parse(new StringReader("CREATE FULLTEXT INDEX i17 ON t (body)")); + assertEquals("FULLTEXT", fullText.getIndex().getType()); + + CreateIndex spatial = (CreateIndex) parserManager + .parse(new StringReader("CREATE SPATIAL INDEX i19 ON t (g)")); + assertEquals("SPATIAL", spatial.getIndex().getType()); + + assertSqlCanBeParsedAndDeparsed("CREATE FULLTEXT INDEX i17 ON t (body)"); + assertSqlCanBeParsedAndDeparsed("CREATE FULLTEXT INDEX i18 ON t (body) WITH PARSER ngram"); + assertSqlCanBeParsedAndDeparsed("CREATE SPATIAL INDEX i19 ON t (g)"); + } + + @Test + public void testCreateMultiValuedIndexIssue2490() throws JSQLParserException { + assertSqlCanBeParsedAndDeparsed( + "CREATE INDEX i20 ON t ((CAST(data -> '$.zips' AS UNSIGNED ARRAY)))"); + } + + @Test + public void testPostgreSqlIndexKeyAttributesIssue2525() throws JSQLParserException { + String orderSql = "CREATE INDEX pg_idx_sort ON pg_index_test " + + "(id DESC NULLS FIRST)"; + CreateIndex ordered = (CreateIndex) assertSqlCanBeParsedAndDeparsed( + orderSql, true, parser -> parser.withDialect(Dialect.POSTGRESQL)); + Index.ColumnParams orderedKey = ordered.getIndex().getColumns().get(0); + assertEquals(Index.ColumnParams.SortOrder.DESC, orderedKey.getSortOrder()); + assertEquals(Index.ColumnParams.NullOrdering.FIRST, orderedKey.getNullOrdering()); + + String operatorClassSql = "CREATE INDEX pg_idx_collate ON pg_index_test " + + "(email COLLATE \"C\" text_pattern_ops)"; + CreateIndex operatorClassIndex = (CreateIndex) assertSqlCanBeParsedAndDeparsed( + operatorClassSql, true, parser -> parser.withDialect(Dialect.POSTGRESQL)); + Index.ColumnParams operatorClassKey = + operatorClassIndex.getIndex().getColumns().get(0); + assertEquals("\"C\"", operatorClassKey.getCollation()); + assertEquals("text_pattern_ops", operatorClassKey.getOperatorClass()); + + String optionSql = "CREATE INDEX pg_idx_opclass ON pg_index_test " + + "(email text_pattern_ops (example = 1))"; + CreateIndex optionIndex = (CreateIndex) assertSqlCanBeParsedAndDeparsed( + optionSql, true, parser -> parser.withDialect(Dialect.POSTGRESQL)); + Index.Option option = optionIndex.getIndex().getColumns().get(0) + .getOperatorClassParameters().get(0); + assertEquals("example", option.getName()); + assertEquals("1", option.getValue().toString()); + assertTrue(option.isUseEquals()); + } + + @Test + public void testPostgreSqlIndexTrailingClausesIssue2525() throws JSQLParserException { + String includeSql = "CREATE UNIQUE INDEX pg_idx_nulls ON pg_index_test (email) " + + "INCLUDE (payload) NULLS NOT DISTINCT"; + CreateIndex include = (CreateIndex) assertSqlCanBeParsedAndDeparsed( + includeSql, true, parser -> parser.withDialect(Dialect.POSTGRESQL)); + assertEquals(List.of("payload"), include.getIncludeColumns()); + assertEquals(Boolean.FALSE, include.getNullsDistinct()); + + String trailingSql = "CREATE INDEX pg_idx_with ON pg_index_test (id) " + + "WITH (fillfactor = 80) TABLESPACE pg_default WHERE active"; + CreateIndex trailing = (CreateIndex) assertSqlCanBeParsedAndDeparsed( + trailingSql, true, parser -> parser.withDialect(Dialect.POSTGRESQL)); + Index.Option fillfactor = trailing.getStorageParameters().get(0); + assertEquals("fillfactor", fillfactor.getName()); + assertEquals("80", fillfactor.getValue().toString()); + assertTrue(fillfactor.isUseEquals()); + assertEquals("pg_default", trailing.getTableSpace()); + assertEquals("active", trailing.getWhere().toString()); + } } diff --git a/src/test/java/net/sf/jsqlparser/statement/create/CreateIndexTraversalTest.java b/src/test/java/net/sf/jsqlparser/statement/create/CreateIndexTraversalTest.java new file mode 100644 index 0000000000..5c9f640265 --- /dev/null +++ b/src/test/java/net/sf/jsqlparser/statement/create/CreateIndexTraversalTest.java @@ -0,0 +1,72 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.statement.create; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import net.sf.jsqlparser.expression.ExpressionVisitorAdapter; +import net.sf.jsqlparser.expression.LongValue; +import net.sf.jsqlparser.parser.AbstractJSqlParser.Dialect; +import net.sf.jsqlparser.parser.CCJSqlParserUtil; +import net.sf.jsqlparser.statement.StatementVisitorAdapter; +import net.sf.jsqlparser.statement.create.index.CreateIndex; +import net.sf.jsqlparser.statement.select.SelectVisitorAdapter; +import net.sf.jsqlparser.util.TablesNamesFinder; +import net.sf.jsqlparser.util.deparser.ExpressionDeParser; +import net.sf.jsqlparser.util.deparser.SelectDeParser; +import net.sf.jsqlparser.util.deparser.StatementDeParser; +import org.junit.jupiter.api.Test; + +class CreateIndexTraversalTest { + private static final String SQL = "CREATE INDEX ix ON public.t " + + "((id + 1) public.test_ops (option = 2)) WITH (fillfactor = 80) WHERE id > 3"; + + @Test + void visitsKeyOptionStorageAndPredicateExpressionsOnceWithContext() throws Exception { + CreateIndex statement = (CreateIndex) CCJSqlParserUtil.parse(SQL, + parser -> parser.withDialect(Dialect.POSTGRESQL)); + List values = new ArrayList<>(); + ExpressionVisitorAdapter expressions = new ExpressionVisitorAdapter() { + @Override + public Void visit(LongValue value, S context) { + assertEquals("context", context); + values.add(value.getValue()); + return null; + } + }; + statement.accept(new StatementVisitorAdapter<>(new SelectVisitorAdapter<>(expressions)), + "context"); + assertEquals(List.of(1L, 2L, 80L, 3L), values); + assertEquals(Set.of("public.t"), new TablesNamesFinder().getTables(statement)); + } + + @Test + void deparsesAllExpressionsThroughTheCustomVisitor() throws Exception { + CreateIndex statement = (CreateIndex) CCJSqlParserUtil.parse(SQL, + parser -> parser.withDialect(Dialect.POSTGRESQL)); + StringBuilder output = new StringBuilder(); + ExpressionDeParser expressions = new ExpressionDeParser() { + @Override + public StringBuilder visit(LongValue value, S context) { + return getBuilder().append(value.getValue() + 10); + } + }; + statement.accept(new StatementDeParser(expressions, new SelectDeParser(), output)); + assertEquals("CREATE INDEX ix ON public.t " + + "((id + 11) public.test_ops (option = 12)) WITH (fillfactor = 90) WHERE id > 13", + output.toString()); + assertEquals(SQL, statement.toString()); + assertEquals(output.toString(), CCJSqlParserUtil.parse(output.toString(), + parser -> parser.withDialect(Dialect.POSTGRESQL)).toString()); + } +} diff --git a/src/test/java/net/sf/jsqlparser/statement/create/CreatePolicyTest.java b/src/test/java/net/sf/jsqlparser/statement/create/CreatePolicyTest.java index 829efd2c7d..90fed32e17 100644 --- a/src/test/java/net/sf/jsqlparser/statement/create/CreatePolicyTest.java +++ b/src/test/java/net/sf/jsqlparser/statement/create/CreatePolicyTest.java @@ -13,6 +13,8 @@ import net.sf.jsqlparser.parser.CCJSqlParserUtil; import net.sf.jsqlparser.statement.Statement; import net.sf.jsqlparser.statement.create.policy.CreatePolicy; +import net.sf.jsqlparser.statement.create.policy.PolicyCommand; +import net.sf.jsqlparser.statement.create.policy.PolicyMode; import org.junit.jupiter.api.Test; import static net.sf.jsqlparser.test.TestUtils.assertSqlCanBeParsedAndDeparsed; @@ -33,6 +35,10 @@ public void testCreatePolicyBasic() throws JSQLParserException { CreatePolicy policy = (CreatePolicy) stmt; assertEquals("policy_name", policy.getPolicyName()); assertEquals("table_name", policy.getTable().getName()); + assertNull(policy.getPolicyMode()); + assertEquals(PolicyMode.PERMISSIVE, policy.getEffectivePolicyMode()); + assertNull(policy.getPolicyCommand()); + assertEquals(PolicyCommand.ALL, policy.getEffectivePolicyCommand()); } @Test @@ -55,6 +61,7 @@ public void testCreatePolicyWithForClause() throws JSQLParserException { CreatePolicy policy = (CreatePolicy) CCJSqlParserUtil.parse(sql); assertEquals("SELECT", policy.getCommand()); + assertEquals(PolicyCommand.SELECT, policy.getPolicyCommand()); } @Test @@ -65,9 +72,40 @@ public void testCreatePolicyWithAllCommands() throws JSQLParserException { assertSqlCanBeParsedAndDeparsed(sql, true); CreatePolicy policy = (CreatePolicy) CCJSqlParserUtil.parse(sql); assertEquals(cmd, policy.getCommand()); + assertEquals(PolicyCommand.from(cmd), policy.getPolicyCommand()); } } + @Test + public void testCreateRestrictivePolicy() throws JSQLParserException { + String sql = "CREATE POLICY tenant_policy ON users AS RESTRICTIVE FOR SELECT TO app " + + "USING (tenant_id = current_user)"; + assertSqlCanBeParsedAndDeparsed(sql, true); + + CreatePolicy policy = (CreatePolicy) CCJSqlParserUtil.parse(sql); + assertEquals(PolicyMode.RESTRICTIVE, policy.getPolicyMode()); + assertEquals(PolicyMode.RESTRICTIVE, policy.getEffectivePolicyMode()); + assertEquals(PolicyCommand.SELECT, policy.getPolicyCommand()); + assertEquals("app", policy.getRoles().get(0)); + assertNotNull(policy.getUsingExpression()); + } + + @Test + public void testCreateExplicitPermissivePolicy() throws JSQLParserException { + String sql = "CREATE POLICY tenant_policy ON users AS PERMISSIVE"; + assertSqlCanBeParsedAndDeparsed(sql, true); + + CreatePolicy policy = (CreatePolicy) CCJSqlParserUtil.parse(sql); + assertEquals(PolicyMode.PERMISSIVE, policy.getPolicyMode()); + assertEquals(PolicyMode.PERMISSIVE, policy.getEffectivePolicyMode()); + } + + @Test + public void testCreatePolicyRejectsUnknownMode() { + assertThrows(JSQLParserException.class, + () -> CCJSqlParserUtil.parse("CREATE POLICY p ON t AS UNKNOWN")); + } + @Test public void testCreatePolicyWithSingleRole() throws JSQLParserException { String sql = "CREATE POLICY policy1 ON table1 TO role1"; diff --git a/src/test/java/net/sf/jsqlparser/statement/create/CreateTableTest.java b/src/test/java/net/sf/jsqlparser/statement/create/CreateTableTest.java index aa7574c0aa..9cd27a797d 100644 --- a/src/test/java/net/sf/jsqlparser/statement/create/CreateTableTest.java +++ b/src/test/java/net/sf/jsqlparser/statement/create/CreateTableTest.java @@ -14,6 +14,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; import java.io.BufferedReader; @@ -24,19 +25,26 @@ import java.util.Iterator; import java.util.List; import java.util.StringTokenizer; +import java.util.stream.Collectors; import net.sf.jsqlparser.JSQLParserException; import net.sf.jsqlparser.expression.LongValue; import net.sf.jsqlparser.expression.operators.relational.GreaterThan; import net.sf.jsqlparser.parser.CCJSqlParserManager; import net.sf.jsqlparser.parser.CCJSqlParserUtil; +import net.sf.jsqlparser.parser.AbstractJSqlParser.Dialect; import net.sf.jsqlparser.schema.Column; import net.sf.jsqlparser.schema.Table; import net.sf.jsqlparser.statement.create.table.ColDataType; +import net.sf.jsqlparser.statement.create.table.ColDataType.Signedness; import net.sf.jsqlparser.statement.create.table.ColumnDefinition; import net.sf.jsqlparser.statement.create.table.CreateTable; import net.sf.jsqlparser.statement.create.table.ExcludeConstraint; +import net.sf.jsqlparser.statement.create.table.ForeignKeyIndex; import net.sf.jsqlparser.statement.create.table.Index; +import net.sf.jsqlparser.statement.create.table.NamedConstraint; +import net.sf.jsqlparser.statement.create.table.PartitionBound; import net.sf.jsqlparser.statement.create.table.RowMovementMode; +import net.sf.jsqlparser.statement.create.table.TablePartitioning; import net.sf.jsqlparser.test.TestException; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.Test; @@ -1191,6 +1199,36 @@ void testCreateTableColumnVisibility() throws JSQLParserException { createTable.getColumnDefinitions().get(1).getColumnSpecs()); } + @Test + void testOracleInlineConstraintState() throws JSQLParserException { + String sqlStr = + "CREATE TABLE DIAM.IAM_DENEME1 (" + + "ID VARCHAR2(36 BYTE) NOT NULL ENABLE," + + "REQUEST_ID VARCHAR2(50 BYTE)," + + "CREATE_DATE TIMESTAMP(9)," + + "USER_ID VARCHAR2(50 BYTE)," + + "CODE VARCHAR2(100 BYTE) NOT NULL ENABLE," + + "IS_OUTSOURCE_ENABLED CHAR(1 BYTE) DEFAULT 1 NOT NULL ENABLE," + + "IS_PASSWORD_NUMERIC CHAR(1 BYTE) DEFAULT 0 NOT NULL ENABLE" + + ")"; + + CreateTable createTable = (CreateTable) CCJSqlParserUtil.parse(sqlStr); + + assertSqlCanBeParsedAndDeparsed( + "CREATE TABLE test (" + + "a NUMBER NOT NULL ENABLE," + + "b NUMBER NOT NULL DISABLE," + + "c NUMBER NOT NULL ENABLE NOVALIDATE," + + "d NUMBER CONSTRAINT d_nn NOT NULL DEFERRABLE VALIDATE" + + ")", + true); + + assertEquals(Arrays.asList("NOT", "NULL", "ENABLE"), + createTable.getColumnDefinitions().get(0).getColumnSpecs()); + assertEquals(Arrays.asList("DEFAULT", "1", "NOT", "NULL", "ENABLE"), + createTable.getColumnDefinitions().get(5).getColumnSpecs()); + } + @Test void testCreateTableIndexVisibilityWithOtherIndexOptions() throws JSQLParserException { assertSqlCanBeParsedAndDeparsed( @@ -1217,4 +1255,120 @@ void testUniqueIndexIssue1893() throws JSQLParserException { // A plain INDEX must still parse unchanged. assertSqlCanBeParsedAndDeparsed("CREATE TABLE t (a int, INDEX idx (a))", true); } + + @Test + void testMySqlColumnTypeModifiers() throws JSQLParserException { + String sql = "CREATE TABLE t (a INT UNSIGNED, b INT (11) UNSIGNED ZEROFILL, " + + "c DECIMAL (10, 2) SIGNED NOT NULL)"; + CreateTable createTable = + (CreateTable) assertSqlCanBeParsedAndDeparsed(sql, true); + + ColDataType first = createTable.getColumnDefinitions().get(0).getColDataType(); + assertEquals("INT", first.getDataType()); + assertEquals(Signedness.UNSIGNED, first.getSignedness()); + assertFalse(first.isZerofill()); + + ColDataType second = createTable.getColumnDefinitions().get(1).getColDataType(); + assertEquals(Signedness.UNSIGNED, second.getSignedness()); + assertTrue(second.isZerofill()); + + ColDataType third = createTable.getColumnDefinitions().get(2).getColDataType(); + assertEquals(Signedness.SIGNED, third.getSignedness()); + assertEquals(List.of("NOT", "NULL"), + createTable.getColumnDefinitions().get(2).getColumnSpecs()); + } + + @Test + void testMySqlConstraintSymbolAndIndexNameIssue1570() throws JSQLParserException { + String uniqueSql = "CREATE TABLE table1 (col1 INT, col2 INT UNIQUE, " + + "CONSTRAINT my_constraint UNIQUE KEY index_name (col1))"; + CreateTable uniqueTable = (CreateTable) assertSqlCanBeParsedAndDeparsed(uniqueSql, true); + NamedConstraint unique = (NamedConstraint) uniqueTable.getIndexes().get(0); + + assertEquals("my_constraint", unique.getName()); + assertEquals("index_name", unique.getIndexName()); + assertEquals("UNIQUE KEY", unique.getType()); + + String foreignKeySql = "CREATE TABLE table2 (id INT, parent_id INT, " + + "CONSTRAINT fk_parent FOREIGN KEY fk_parent_idx (parent_id) " + + "REFERENCES parent (id))"; + CreateTable foreignKeyTable = + (CreateTable) assertSqlCanBeParsedAndDeparsed(foreignKeySql, true); + ForeignKeyIndex foreignKey = (ForeignKeyIndex) foreignKeyTable.getIndexes().get(0); + + assertEquals("fk_parent", foreignKey.getName()); + assertEquals("fk_parent_idx", foreignKey.getIndexName()); + } + + @Test + void testMySqlUnnamedConstraintAndUniqueIndexOptionsIssues1570And538() + throws JSQLParserException { + assertSqlCanBeParsedAndDeparsed( + "CREATE TABLE table1 (col1 INT, CONSTRAINT UNIQUE KEY (col1))", true); + + String sql = "CREATE TABLE g_platform_payway (id INT, platform_code VARCHAR (45), " + + "pay_way VARCHAR (32), UNIQUE uniq_platform_payway USING BTREE " + + "(platform_code, pay_way) COMMENT 'should be unique')"; + CreateTable createTable = (CreateTable) assertSqlCanBeParsedAndDeparsed(sql, true); + NamedConstraint unique = (NamedConstraint) createTable.getIndexes().get(0); + + assertNull(unique.getName()); + assertEquals("uniq_platform_payway", unique.getIndexName()); + assertEquals("BTREE", unique.getUsing()); + assertEquals(List.of("COMMENT", "'should be unique'"), unique.getIndexSpec()); + } + + @Test + void testPostgreSqlDeclarativePartitionParentsIssue2522() throws JSQLParserException { + String sql = "CREATE TABLE pg_range_parent (tenant_id int, " + + "happened_at timestamptz) PARTITION BY RANGE (tenant_id, happened_at)"; + CreateTable createTable = (CreateTable) assertSqlCanBeParsedAndDeparsed( + sql, true, parser -> parser.withDialect(Dialect.POSTGRESQL)); + TablePartitioning partitioning = createTable.getPartitioning(); + + assertEquals(TablePartitioning.Type.RANGE, partitioning.getType()); + assertEquals(List.of("tenant_id", "happened_at"), partitioning.getExpressionList() + .stream().map(Object::toString).collect(Collectors.toList())); + + assertSqlCanBeParsedAndDeparsed( + "CREATE TABLE pg_list_parent (region text) PARTITION BY LIST (region)", + true, parser -> parser.withDialect(Dialect.POSTGRESQL)); + assertSqlCanBeParsedAndDeparsed( + "CREATE TABLE pg_hash_parent (tenant_id int) PARTITION BY HASH (tenant_id)", + true, parser -> parser.withDialect(Dialect.POSTGRESQL)); + } + + @Test + void testPostgreSqlDeclarativePartitionChildrenIssue2522() throws JSQLParserException { + String rangeSql = "CREATE TABLE pg_range_p1 PARTITION OF pg_range_parent " + + "FOR VALUES FROM (1, MINVALUE) TO (2, MAXVALUE)"; + CreateTable range = (CreateTable) assertSqlCanBeParsedAndDeparsed( + rangeSql, true, parser -> parser.withDialect(Dialect.POSTGRESQL)); + assertEquals("pg_range_parent", range.getPartitionOf().getName()); + assertEquals(PartitionBound.Type.RANGE, range.getPartitionBound().getType()); + assertEquals(List.of("1", "MINVALUE"), range.getPartitionBound().getFromExpressions() + .stream().map(Object::toString).collect(Collectors.toList())); + assertEquals(List.of("2", "MAXVALUE"), range.getPartitionBound().getToExpressions() + .stream().map(Object::toString).collect(Collectors.toList())); + + String listSql = "CREATE TABLE pg_list_eu_us PARTITION OF pg_list_parent " + + "FOR VALUES IN ('eu', 'us')"; + CreateTable list = (CreateTable) assertSqlCanBeParsedAndDeparsed( + listSql, true, parser -> parser.withDialect(Dialect.POSTGRESQL)); + assertEquals(PartitionBound.Type.LIST, list.getPartitionBound().getType()); + + String hashSql = "CREATE TABLE pg_hash_p0 PARTITION OF pg_hash_parent " + + "FOR VALUES WITH (MODULUS 4, REMAINDER 0)"; + CreateTable hash = (CreateTable) assertSqlCanBeParsedAndDeparsed( + hashSql, true, parser -> parser.withDialect(Dialect.POSTGRESQL)); + assertEquals(PartitionBound.Type.HASH, hash.getPartitionBound().getType()); + assertEquals("4", hash.getPartitionBound().getModulus().toString()); + assertEquals("0", hash.getPartitionBound().getRemainder().toString()); + + String defaultSql = + "CREATE TABLE pg_list_default PARTITION OF pg_list_parent DEFAULT"; + CreateTable defaultPartition = (CreateTable) assertSqlCanBeParsedAndDeparsed( + defaultSql, true, parser -> parser.withDialect(Dialect.POSTGRESQL)); + assertEquals(PartitionBound.Type.DEFAULT, defaultPartition.getPartitionBound().getType()); + } } diff --git a/src/test/java/net/sf/jsqlparser/statement/create/DialectIndexDefinitionTest.java b/src/test/java/net/sf/jsqlparser/statement/create/DialectIndexDefinitionTest.java new file mode 100644 index 0000000000..69de8156da --- /dev/null +++ b/src/test/java/net/sf/jsqlparser/statement/create/DialectIndexDefinitionTest.java @@ -0,0 +1,214 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.statement.create; + +import static net.sf.jsqlparser.test.TestUtils.assertDeparse; +import static net.sf.jsqlparser.test.TestUtils.assertSqlCanBeParsedAndDeparsed; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.List; +import net.sf.jsqlparser.JSQLParserException; +import net.sf.jsqlparser.parser.AbstractJSqlParser.Dialect; +import net.sf.jsqlparser.parser.CCJSqlParserUtil; +import net.sf.jsqlparser.statement.Statements; +import net.sf.jsqlparser.statement.create.index.CreateIndex; +import net.sf.jsqlparser.statement.create.table.Index; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +class DialectIndexDefinitionTest { + private static CreateIndex parse(String sql, Dialect dialect) throws JSQLParserException { + return (CreateIndex) CCJSqlParserUtil.parse(sql, parser -> parser.withDialect(dialect)); + } + + @ParameterizedTest + @ValueSource(strings = {"", "UNIQUE ", "CLUSTERED ", "NONCLUSTERED ", + "UNIQUE CLUSTERED ", "UNIQUE NONCLUSTERED "}) + void parsesSqlServerIndexModifiers(String modifiers) throws Exception { + String sql = "CREATE " + modifiers + + "INDEX [index name] ON [dbo].[store] ([manager_staff_id] DESC, [id] ASC)"; + CreateIndex statement = (CreateIndex) assertSqlCanBeParsedAndDeparsed(sql, true, + parser -> parser.withDialect(Dialect.SQLSERVER)); + Index index = statement.getIndex(); + assertEquals(modifiers.contains("UNIQUE") ? "UNIQUE" : null, index.getType()); + assertEquals(modifiers.contains("NONCLUSTERED") ? Index.Clustering.NONCLUSTERED + : modifiers.contains("CLUSTERED") ? Index.Clustering.CLUSTERED : null, + index.getClustering()); + assertFalse(statement.isNullFiltered()); + assertEquals(index.getClustering(), parse(statement.toString(), Dialect.SQLSERVER) + .getIndex().getClustering()); + } + + @ParameterizedTest + @ValueSource(strings = {"", "UNIQUE ", "NULL_FILTERED ", "UNIQUE NULL_FILTERED "}) + void parsesSpannerIndexModifiersIssue652(String modifiers) throws Exception { + String sql = "CREATE " + modifiers + + "INDEX IF NOT EXISTS `index name` ON `Users` (`name` DESC, `id` ASC)"; + CreateIndex statement = (CreateIndex) assertSqlCanBeParsedAndDeparsed(sql, true, + parser -> parser.withDialect(Dialect.SPANNER)); + assertEquals(modifiers.contains("UNIQUE") ? "UNIQUE" : null, + statement.getIndex().getType()); + assertEquals(modifiers.contains("NULL_FILTERED"), statement.isNullFiltered()); + assertNull(statement.getIndex().getClustering()); + assertEquals(statement.isNullFiltered(), + parse(statement.toString(), Dialect.SPANNER).isNullFiltered()); + } + + @Test + void requiresDialectForCombinedModifiersAndPreservesLegacySingleTypes() throws Exception { + for (String type : List.of("CLUSTERED", "NONCLUSTERED", "NULL_FILTERED", "mytype")) { + String sql = "CREATE " + type + " INDEX ix ON t (id)"; + CreateIndex legacy = (CreateIndex) assertSqlCanBeParsedAndDeparsed(sql); + assertEquals(type, legacy.getIndex().getType()); + assertNull(legacy.getIndex().getClustering()); + assertFalse(legacy.isNullFiltered()); + } + for (Dialect target : List.of(Dialect.SQLSERVER, Dialect.SPANNER)) { + String sql = "CREATE UNIQUE " + + (target == Dialect.SQLSERVER ? "NONCLUSTERED" : "NULL_FILTERED") + + " INDEX ix ON t (id)"; + assertThrows(JSQLParserException.class, () -> CCJSqlParserUtil.parse(sql)); + for (Dialect dialect : Dialect.values()) { + if (dialect != target) { + assertThrows(JSQLParserException.class, () -> parse(sql, dialect), + dialect.name()); + } + } + } + } + + @Test + void rejectsDuplicateOrReversedModifiers() { + for (String modifiers : List.of("UNIQUE CLUSTERED NONCLUSTERED", "NONCLUSTERED UNIQUE", + "UNIQUE UNIQUE", "UNIQUE NULL_FILTERED")) { + assertThrows(JSQLParserException.class, + () -> parse("CREATE " + modifiers + " INDEX ix ON t (id)", Dialect.SQLSERVER)); + } + for (String modifiers : List.of("NULL_FILTERED UNIQUE", + "UNIQUE NULL_FILTERED NULL_FILTERED", + "UNIQUE UNIQUE", "UNIQUE NONCLUSTERED")) { + assertThrows(JSQLParserException.class, + () -> parse("CREATE " + modifiers + " INDEX ix ON t (id)", Dialect.SPANNER)); + } + } + + @Test + void rendersModifiedClusteringAndNullFiltering() throws Exception { + CreateIndex sqlServer = parse("CREATE UNIQUE NONCLUSTERED INDEX ix ON t (id)", + Dialect.SQLSERVER); + sqlServer.getIndex().setClustering(Index.Clustering.CLUSTERED); + assertDeparse(sqlServer, "CREATE UNIQUE CLUSTERED INDEX ix ON t (id)"); + sqlServer.getIndex().setClustering(null); + assertDeparse(sqlServer, "CREATE UNIQUE INDEX ix ON t (id)"); + + CreateIndex spanner = parse("CREATE UNIQUE NULL_FILTERED INDEX ix ON t (id)", + Dialect.SPANNER); + spanner.setNullFiltered(false); + assertDeparse(spanner, "CREATE UNIQUE INDEX ix ON t (id)"); + assertDeparse(spanner.withNullFiltered(true), + "CREATE UNIQUE NULL_FILTERED INDEX ix ON t (id)"); + } + + @Test + void parsesSakilaIndexAndFollowingStatementsIssue1563() throws Exception { + String sql = + "CREATE UNIQUE NONCLUSTERED INDEX idx_fk_address_id ON store(manager_staff_id);"; + Statements statements = CCJSqlParserUtil.parseStatements(sql + "\nGO\nSELECT 1;", + parser -> parser.withDialect(Dialect.SQLSERVER)); + assertEquals(2, statements.size()); + assertEquals(Index.Clustering.NONCLUSTERED, + ((CreateIndex) statements.get(0)).getIndex().getClustering()); + assertEquals("SELECT 1", statements.get(1).toString()); + } + + @Test + void parsesQualifiedPostgreSqlAttributesIssue1994() throws Exception { + String sql = "CREATE INDEX \"index_keyword\" ON \"inter\".\"inter_ti_rec\" USING btree " + + "(\"keyword\" COLLATE \"pg_catalog\".\"default\" \"pg_catalog\".\"text_ops\" ASC NULLS LAST)"; + CreateIndex statement = (CreateIndex) assertSqlCanBeParsedAndDeparsed(sql, true, + parser -> parser.withDialect(Dialect.POSTGRESQL)); + Index.ColumnParams key = statement.getIndex().getColumns().get(0); + assertEquals("\"pg_catalog\".\"default\"", key.getCollation()); + assertEquals("\"pg_catalog\".\"text_ops\"", key.getOperatorClass()); + assertEquals(Index.ColumnParams.SortOrder.ASC, key.getSortOrder()); + assertEquals(Index.ColumnParams.NullOrdering.LAST, key.getNullOrdering()); + assertNull(key.getParams()); + + key.setCollation("public.\"C\""); + key.setOperatorClass("public.text_pattern_ops"); + key.setSortOrder(Index.ColumnParams.SortOrder.DESC); + key.setNullOrdering(Index.ColumnParams.NullOrdering.FIRST); + assertDeparse(statement, + "CREATE INDEX \"index_keyword\" ON \"inter\".\"inter_ti_rec\" USING btree " + + "(\"keyword\" COLLATE public.\"C\" public.text_pattern_ops DESC NULLS FIRST)"); + assertEquals(key.getOperatorClass(), parse(statement.toString(), Dialect.POSTGRESQL) + .getIndex().getColumns().get(0).getOperatorClass()); + key.setCollation(null); + key.setOperatorClass(null); + key.setSortOrder(null); + key.setNullOrdering(null); + assertDeparse(statement, + "CREATE INDEX \"index_keyword\" ON \"inter\".\"inter_ti_rec\" USING btree (\"keyword\")"); + } + + @Test + void preservesQuotedNamePartsAndBareFunctionKeys() throws Exception { + String sql = "CREATE INDEX ix ON t (lower(name) COLLATE \"schema.with.dot\".\"C\" " + + "\"schema\".\"op\"\"class\" DESC NULLS FIRST)"; + CreateIndex statement = (CreateIndex) assertSqlCanBeParsedAndDeparsed(sql, true, + parser -> parser.withDialect(Dialect.POSTGRESQL)); + Index.ColumnParams key = statement.getIndex().getColumns().get(0); + assertTrue(key.isExpression()); + assertFalse(key.isExpressionParenthesized()); + assertEquals("\"schema.with.dot\".\"C\"", key.getCollation()); + assertEquals("\"schema\".\"op\"\"class\"", key.getOperatorClass()); + assertNull(key.getParams()); + } + + @Test + void keepsPostgreSqlFunctionAndFollowingIndexSeparateIssue1994() throws Exception { + String sql = "DROP FUNCTION IF EXISTS fin.restore_fund_data(in_fund_id int8); " + + "CREATE OR REPLACE FUNCTION fin.restore_fund_data(in_fund_id int8) " + + "RETURNS pg_catalog.varchar AS $BODY$ BEGIN RETURN 'success'; END $BODY$ " + + "LANGUAGE plpgsql VOLATILE COST 100; " + + "CREATE INDEX ix ON inter.inter_ti_rec " + + "(keyword COLLATE pg_catalog.\"default\" pg_catalog.text_ops ASC NULLS LAST); " + + "SELECT 1;"; + Statements statements = CCJSqlParserUtil.parseStatements(sql, + parser -> parser.withDialect(Dialect.POSTGRESQL)); + assertEquals(4, statements.size()); + assertEquals("pg_catalog.text_ops", ((CreateIndex) statements.get(2)) + .getIndex().getColumns().get(0).getOperatorClass()); + assertEquals("SELECT 1", statements.get(3).toString()); + } + + @Test + void gatesQualifiedAttributesAndRejectsRepeatedPostgreSqlAttributes() { + for (String key : List.of("name COLLATE public.\"C\"", "name public.text_ops")) { + String sql = "CREATE INDEX ix ON t (" + key + ")"; + assertThrows(JSQLParserException.class, () -> CCJSqlParserUtil.parse(sql)); + for (Dialect dialect : Dialect.values()) { + if (dialect != Dialect.POSTGRESQL) { + assertThrows(JSQLParserException.class, () -> parse(sql, dialect)); + } + } + } + for (String key : List.of("name ASC DESC", "name NULLS FIRST NULLS LAST", + "name COLLATE public.\"C\" COLLATE public.\"C\"")) { + assertThrows(JSQLParserException.class, + () -> parse("CREATE INDEX ix ON t (" + key + ")", Dialect.POSTGRESQL)); + } + } +} diff --git a/src/test/java/net/sf/jsqlparser/statement/create/MySQLCreateTableConstraintTest.java b/src/test/java/net/sf/jsqlparser/statement/create/MySQLCreateTableConstraintTest.java new file mode 100644 index 0000000000..dc5b70911f --- /dev/null +++ b/src/test/java/net/sf/jsqlparser/statement/create/MySQLCreateTableConstraintTest.java @@ -0,0 +1,39 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.statement.create; + +import static net.sf.jsqlparser.test.TestUtils.assertSqlCanBeParsedAndDeparsed; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import net.sf.jsqlparser.JSQLParserException; +import net.sf.jsqlparser.statement.create.table.CheckConstraint; +import net.sf.jsqlparser.statement.create.table.CreateTable; +import org.junit.jupiter.api.Test; + +/** MySQL {@code CREATE TABLE} constraint syntax verified against MySQL 8.4.11. */ +public class MySQLCreateTableConstraintTest { + + @Test + public void testCheckConstraintEnforcement() throws JSQLParserException { + String sql = "CREATE TABLE mysql_check_enforcement (score INT, " + + "CONSTRAINT chk_positive CHECK (score >= 0) ENFORCED, " + + "CONSTRAINT chk_cap CHECK (score < 100) NOT ENFORCED)"; + + CreateTable createTable = (CreateTable) assertSqlCanBeParsedAndDeparsed(sql); + + CheckConstraint positive = (CheckConstraint) createTable.getIndexes().get(0); + assertEquals(Boolean.TRUE, positive.getEnforced()); + assertEquals("chk_positive", positive.getName()); + + CheckConstraint cap = (CheckConstraint) createTable.getIndexes().get(1); + assertEquals(Boolean.FALSE, cap.getEnforced()); + assertEquals("chk_cap", cap.getName()); + } +} diff --git a/src/test/java/net/sf/jsqlparser/statement/create/MySQLCreateTablePartitionTest.java b/src/test/java/net/sf/jsqlparser/statement/create/MySQLCreateTablePartitionTest.java new file mode 100644 index 0000000000..ce1a0f84cc --- /dev/null +++ b/src/test/java/net/sf/jsqlparser/statement/create/MySQLCreateTablePartitionTest.java @@ -0,0 +1,175 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.statement.create; + +import static net.sf.jsqlparser.test.TestUtils.assertSqlCanBeParsedAndDeparsed; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.List; +import java.util.stream.Stream; +import net.sf.jsqlparser.JSQLParserException; +import net.sf.jsqlparser.parser.CCJSqlParserUtil; +import net.sf.jsqlparser.statement.create.table.CreateTable; +import net.sf.jsqlparser.statement.create.table.PartitionDefinition; +import net.sf.jsqlparser.statement.create.table.TablePartitioning; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +/** MySQL 8.4 {@code CREATE TABLE} partition syntax verified against MySQL 8.4.11. */ +public class MySQLCreateTablePartitionTest { + + private static final List MYSQL84_PARTITION_STATEMENTS = List.of( + "CREATE TABLE mysql_partition_hash (id INT) " + + "PARTITION BY HASH (id) PARTITIONS 4", + "CREATE TABLE mysql_partition_linear_hash (id INT) " + + "PARTITION BY LINEAR HASH (id) PARTITIONS 4", + "CREATE TABLE mysql_partition_key (id INT) " + + "PARTITION BY LINEAR KEY ALGORITHM = 1 (id) PARTITIONS 4", + "CREATE TABLE mysql_partition_range (id INT) PARTITION BY RANGE (id) " + + "(PARTITION p0 VALUES LESS THAN (10), " + + "PARTITION p1 VALUES LESS THAN (20), " + + "PARTITION pmax VALUES LESS THAN MAXVALUE)", + "CREATE TABLE mysql_partition_range_columns (a INT, b INT) " + + "PARTITION BY RANGE COLUMNS (a, b) " + + "(PARTITION p0 VALUES LESS THAN (5, 12), " + + "PARTITION pmax VALUES LESS THAN (MAXVALUE, MAXVALUE))", + "CREATE TABLE mysql_partition_list (id INT) PARTITION BY LIST (id) " + + "(PARTITION p0 VALUES IN (1, 2), " + + "PARTITION p1 VALUES IN (3, 4))", + "CREATE TABLE mysql_partition_list_columns_multi " + + "(region VARCHAR (16), tier INT) " + + "PARTITION BY LIST COLUMNS (region, tier) " + + "(PARTITION p0 VALUES IN (('eu', 1), ('us', 2)), " + + "PARTITION p1 VALUES IN (('apac', 3)))", + "CREATE TABLE mysql_partition_sub_count (id INT, d DATE) " + + "PARTITION BY RANGE (YEAR(d)) " + + "SUBPARTITION BY HASH (id) SUBPARTITIONS 2 " + + "(PARTITION p0 VALUES LESS THAN (2025), " + + "PARTITION pmax VALUES LESS THAN MAXVALUE)", + "CREATE TABLE mysql_partition_sub_explicit (id INT, d DATE) " + + "PARTITION BY RANGE (YEAR(d)) " + + "SUBPARTITION BY LINEAR KEY ALGORITHM = 2 (id) " + + "(PARTITION p0 VALUES LESS THAN (2025) " + + "(SUBPARTITION p0s0 ENGINE = InnoDB, " + + "SUBPARTITION p0s1 ENGINE = InnoDB), " + + "PARTITION pmax VALUES LESS THAN MAXVALUE " + + "(SUBPARTITION pmaxs0 ENGINE = InnoDB, " + + "SUBPARTITION pmaxs1 ENGINE = InnoDB))", + "CREATE TABLE mysql_partition_options (id INT) ENGINE = InnoDB " + + "PARTITION BY RANGE (id) " + + "(PARTITION p0 VALUES LESS THAN (10) ENGINE = InnoDB " + + "COMMENT = 'hot' MAX_ROWS = 100 MIN_ROWS = 1, " + + "PARTITION pmax VALUES LESS THAN MAXVALUE " + + "ENGINE InnoDB COMMENT 'cold')", + "CREATE TABLE mysql_partition_key_empty " + + "(id INT NOT NULL PRIMARY KEY, name VARCHAR (20)) " + + "PARTITION BY KEY () PARTITIONS 2", + "CREATE TABLE mysql_partition_hash_named (id INT) " + + "PARTITION BY HASH (id) PARTITIONS 2 " + + "(PARTITION p0 STORAGE ENGINE = InnoDB, " + + "PARTITION p1 ENGINE InnoDB)"); + + private static Stream mysql84PartitionStatements() { + return MYSQL84_PARTITION_STATEMENTS.stream(); + } + + @ParameterizedTest + @MethodSource("mysql84PartitionStatements") + public void testMySQL84CreateTablePartitions(String sql) throws JSQLParserException { + assertInstanceOf(CreateTable.class, assertSqlCanBeParsedAndDeparsed(sql)); + } + + @Test + public void testHashAndKeyPartitioningAst() throws JSQLParserException { + CreateTable hash = parse(MYSQL84_PARTITION_STATEMENTS.get(1)); + TablePartitioning hashPartitioning = hash.getPartitioning(); + assertEquals(TablePartitioning.Type.HASH, hashPartitioning.getType()); + assertTrue(hashPartitioning.isLinear()); + assertEquals("id", hashPartitioning.getExpression().toString()); + assertEquals(4L, hashPartitioning.getPartitions()); + + CreateTable key = parse(MYSQL84_PARTITION_STATEMENTS.get(2)); + TablePartitioning keyPartitioning = key.getPartitioning(); + assertEquals(TablePartitioning.Type.KEY, keyPartitioning.getType()); + assertTrue(keyPartitioning.isLinear()); + assertEquals(1, keyPartitioning.getAlgorithm()); + assertTrue(keyPartitioning.isAlgorithmUseEquals()); + assertEquals("id", keyPartitioning.getColumns().get(0).getColumnName()); + + CreateTable emptyKey = parse(MYSQL84_PARTITION_STATEMENTS.get(10)); + assertTrue(emptyKey.getPartitioning().getColumns().isEmpty()); + } + + @Test + public void testRangeAndListPartitionDefinitionsAst() throws JSQLParserException { + CreateTable range = parse(MYSQL84_PARTITION_STATEMENTS.get(4)); + TablePartitioning rangePartitioning = range.getPartitioning(); + assertEquals(TablePartitioning.Type.RANGE, rangePartitioning.getType()); + assertTrue(rangePartitioning.isColumnsSyntax()); + assertEquals("a", rangePartitioning.getColumns().get(0).getColumnName()); + assertEquals("b", rangePartitioning.getColumns().get(1).getColumnName()); + PartitionDefinition maxPartition = rangePartitioning.getPartitionDefinitions().get(1); + assertEquals(PartitionDefinition.ValueOperator.LESS_THAN, + maxPartition.getValueOperator()); + assertEquals(List.of("MAXVALUE", "MAXVALUE"), maxPartition.getValues()); + assertFalse(maxPartition.isMaxValue()); + + CreateTable list = parse(MYSQL84_PARTITION_STATEMENTS.get(5)); + PartitionDefinition listPartition = list.getPartitioning().getPartitionDefinitions().get(0); + assertEquals(PartitionDefinition.ValueOperator.IN, listPartition.getValueOperator()); + assertEquals(List.of("1", "2"), listPartition.getValues()); + } + + @Test + public void testSubPartitioningAst() throws JSQLParserException { + CreateTable createTable = parse(MYSQL84_PARTITION_STATEMENTS.get(8)); + TablePartitioning subPartitioning = createTable.getPartitioning().getSubPartitioning(); + assertNotNull(subPartitioning); + assertEquals(TablePartitioning.Type.KEY, subPartitioning.getType()); + assertTrue(subPartitioning.isLinear()); + assertEquals(2, subPartitioning.getAlgorithm()); + assertEquals("id", subPartitioning.getColumns().get(0).getColumnName()); + + PartitionDefinition first = createTable.getPartitioning().getPartitionDefinitions().get(0); + assertEquals(2, first.getSubPartitionDefinitions().size()); + assertTrue(first.getSubPartitionDefinitions().get(0).isSubPartition()); + assertEquals("InnoDB", first.getSubPartitionDefinitions().get(0).getStorageEngine()); + } + + @Test + public void testPartitionOptionsAst() throws JSQLParserException { + CreateTable createTable = parse(MYSQL84_PARTITION_STATEMENTS.get(9)); + PartitionDefinition first = createTable.getPartitioning().getPartitionDefinitions().get(0); + assertEquals("InnoDB", first.getStorageEngine()); + assertTrue(first.isStorageEngineUseEquals()); + assertEquals("'hot'", first.getComment()); + assertTrue(first.isCommentUseEquals()); + assertEquals("100", first.getMaxRows()); + assertEquals("1", first.getMinRows()); + + PartitionDefinition max = createTable.getPartitioning().getPartitionDefinitions().get(1); + assertTrue(max.isMaxValue()); + assertFalse(max.isStorageEngineUseEquals()); + assertFalse(max.isCommentUseEquals()); + + PartitionDefinition namedHash = parse(MYSQL84_PARTITION_STATEMENTS.get(11)) + .getPartitioning().getPartitionDefinitions().get(0); + assertTrue(namedHash.isStorageKeyword()); + } + + private static CreateTable parse(String sql) throws JSQLParserException { + return (CreateTable) CCJSqlParserUtil.parse(sql); + } +} diff --git a/src/test/java/net/sf/jsqlparser/statement/create/MySQLIndexOptionsTest.java b/src/test/java/net/sf/jsqlparser/statement/create/MySQLIndexOptionsTest.java new file mode 100644 index 0000000000..efb30097f5 --- /dev/null +++ b/src/test/java/net/sf/jsqlparser/statement/create/MySQLIndexOptionsTest.java @@ -0,0 +1,98 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.statement.create; + +import net.sf.jsqlparser.JSQLParserException; +import net.sf.jsqlparser.parser.CCJSqlParserUtil; +import net.sf.jsqlparser.statement.Statements; +import net.sf.jsqlparser.statement.create.index.CreateIndex; +import net.sf.jsqlparser.statement.drop.Drop; +import net.sf.jsqlparser.statement.lock.LockStatement; +import net.sf.jsqlparser.test.TestUtils; +import net.sf.jsqlparser.util.deparser.CreateIndexDeParser; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +class MySQLIndexOptionsTest { + @Test + void parsesAlgorithmAndLockOptionsInEitherOrder() throws Exception { + for (String algorithm : List.of("DEFAULT", "INPLACE", "COPY")) { + for (String lock : List.of("DEFAULT", "NONE", "SHARED", "EXCLUSIVE")) { + for (String equals : List.of(" ", " = ")) { + String algorithmOption = "ALGORITHM" + equals + algorithm; + String lockOption = "LOCK" + equals + lock; + for (String options : List.of(algorithmOption + " " + lockOption, + lockOption + " " + algorithmOption)) { + for (String statement : List.of("CREATE INDEX idx ON t (id) ", + "DROP INDEX idx ON t ")) { + TestUtils.assertSqlCanBeParsedAndDeparsed(statement + options, true); + } + } + } + } + } + } + + @Test + void preservesLegacyOptionTokensAndTheirOrder() throws Exception { + CreateIndex create = (CreateIndex) CCJSqlParserUtil.parse( + "CREATE INDEX idx ON t (id) LOCK EXCLUSIVE ALGORITHM = DEFAULT"); + assertEquals(List.of("LOCK", "EXCLUSIVE", "ALGORITHM", "=", "DEFAULT"), + create.getTailParameters()); + Drop drop = (Drop) CCJSqlParserUtil.parse( + "DROP INDEX idx ON t LOCK = DEFAULT ALGORITHM COPY"); + assertEquals(List.of("ON", "t", "LOCK", "=", "DEFAULT", "ALGORITHM", "COPY"), + drop.getParameters()); + assertEquals(create.toString(), CCJSqlParserUtil.parse(create.toString()).toString()); + assertEquals(drop.toString(), CCJSqlParserUtil.parse(drop.toString()).toString()); + } + + @ParameterizedTest + @ValueSource(strings = {"CREATE INDEX idx ON t (id)", "DROP INDEX idx ON t"}) + void keepsFollowingLockStatementSeparate(String index) throws Exception { + Statements statements = CCJSqlParserUtil.parseStatements( + index + "; LOCK TABLE t IN SHARE MODE"); + assertEquals(2, statements.size()); + assertInstanceOf(LockStatement.class, statements.get(1)); + net.sf.jsqlparser.parser.CCJSqlParser parser = CCJSqlParserUtil.newParser( + index + " LOCK TABLE t IN SHARE MODE"); + parser.SingleStatement(); + assertEquals(net.sf.jsqlparser.parser.CCJSqlParserConstants.K_LOCK, + parser.getNextToken().kind); + } + + @ParameterizedTest + @ValueSource(strings = {"CREATE INDEX idx ON t (id) LOCK", "DROP INDEX idx ON t LOCK =", + "CREATE INDEX idx ON t (id) ALGORITHM =", "DROP INDEX idx ON t ALGORITHM"}) + void rejectsMissingOptionValues(String sql) { + assertThrows(JSQLParserException.class, () -> CCJSqlParserUtil.parse(sql)); + } + + @ParameterizedTest + @ValueSource(strings = { + "CREATE INDEX idx ON t (id) LOCK EXCLUSIVE ALGORITHM DEFAULT", + "CREATE INDEX idx USING BTREE ON t (id) KEY_BLOCK_SIZE = 8 COMMENT 'test' INVISIBLE", + "CREATE INDEX idx ON t (id) parallel compress nologging", + "CREATE UNIQUE INDEX CONCURRENTLY IF NOT EXISTS idx ON ONLY t USING btree (id DESC) INCLUDE (value) NULLS NOT DISTINCT WITH (fillfactor = 80) TABLESPACE fast WHERE active" + }) + void sharesRenderingForMySqlPostgreSqlAndLegacyTails(String sql) throws Exception { + CreateIndex create = (CreateIndex) TestUtils.assertSqlCanBeParsedAndDeparsed(sql, true); + create.getIndex().setName("renamed"); + StringBuilder deparsed = new StringBuilder("prefix "); + new CreateIndexDeParser(deparsed).deParse(create); + assertEquals("prefix " + create, deparsed.toString()); + assertEquals(create.toString(), CCJSqlParserUtil.parse(create.toString()).toString()); + } +} diff --git a/src/test/java/net/sf/jsqlparser/statement/create/MySqlTableDefinitionTest.java b/src/test/java/net/sf/jsqlparser/statement/create/MySqlTableDefinitionTest.java new file mode 100644 index 0000000000..cc0b580d13 --- /dev/null +++ b/src/test/java/net/sf/jsqlparser/statement/create/MySqlTableDefinitionTest.java @@ -0,0 +1,220 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.statement.create; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.Arrays; +import net.sf.jsqlparser.JSQLParserException; +import net.sf.jsqlparser.parser.AbstractJSqlParser.Dialect; +import net.sf.jsqlparser.parser.CCJSqlParserUtil; +import net.sf.jsqlparser.statement.alter.Alter; +import net.sf.jsqlparser.statement.alter.AlterExpression; +import net.sf.jsqlparser.statement.create.table.ColDataType; +import net.sf.jsqlparser.statement.create.table.ColumnDefinition; +import net.sf.jsqlparser.statement.create.table.CreateTable; +import net.sf.jsqlparser.statement.create.table.ForeignKeyReference; +import net.sf.jsqlparser.statement.create.table.ForeignKeyIndex; +import net.sf.jsqlparser.statement.create.table.Index; +import net.sf.jsqlparser.statement.create.table.NamedConstraint; +import net.sf.jsqlparser.statement.create.table.TableOption; +import org.junit.jupiter.api.Test; + +/** Regression tests for MySQL table definitions and their structured AST representation. */ +public class MySqlTableDefinitionTest { + + @Test + public void testConstraintBeforeColumnDefinition() throws JSQLParserException { + CreateTable table = + parseMySql("CREATE TABLE inventory (PRIMARY KEY (id), id INT NOT NULL)"); + + assertEquals(2, table.getTableElements().size()); + assertInstanceOf(Index.class, table.getTableElements().get(0)); + assertInstanceOf(ColumnDefinition.class, table.getTableElements().get(1)); + assertEquals("PRIMARY KEY", table.getIndexes().get(0).getType()); + assertReparse(table); + } + + @Test + public void testNamedPrimaryAndSpecializedIndexes() throws JSQLParserException { + CreateTable table = parseMySql("CREATE TABLE documents (id INT, body TEXT, data JSON, " + + "PRIMARY KEY pk_documents (id), FULLTEXT INDEX ft_body (body), " + + "INDEX ((CAST(data AS CHAR(30)))))"); + + NamedConstraint primary = (NamedConstraint) table.getIndexes().get(0); + assertEquals("pk_documents", primary.getIndexName()); + assertEquals(Index.Kind.PRIMARY_KEY, primary.getKind()); + assertEquals(Index.Kind.FULLTEXT, table.getIndexes().get(1).getKind()); + assertTrue(table.getIndexes().get(2).getColumns().get(0).isExpression()); + assertReparse(table); + } + + @Test + public void testFulltextAndSpatialIndexes() throws JSQLParserException { + CreateTable table = parseMySql("CREATE TABLE map_entry (body TEXT, coordinates POINT, " + + "FULLTEXT KEY ft_body (body), SPATIAL INDEX sp_coordinates (coordinates))"); + + assertEquals(Index.Kind.FULLTEXT, table.getIndexes().get(0).getKind()); + assertEquals("KEY", table.getIndexes().get(0).getType().split(" ")[1]); + assertEquals(Index.Kind.SPATIAL, table.getIndexes().get(1).getKind()); + assertReparse(table); + } + + @Test + public void testStructuredColumnOptions() throws JSQLParserException { + CreateTable table = parseMySql("CREATE TABLE child_record (id SMALLINT UNSIGNED " + + "SERIAL DEFAULT VALUE NOT NULL, parent_id INT REFERENCES parent_record(id) " + + "MATCH FULL ON UPDATE CASCADE ON DELETE SET NULL)"); + + ColumnDefinition id = table.getColumnDefinitions().get(0); + assertTrue(id.isSerialDefaultValue()); + + ForeignKeyReference reference = table.getColumnDefinitions().get(1) + .getForeignKeyReference(); + assertNotNull(reference); + assertEquals("parent_record", reference.getTable().getName()); + assertEquals(Arrays.asList("id"), reference.getReferencedColumnNames()); + assertEquals(ForeignKeyReference.MatchType.FULL, reference.getMatchType()); + assertNotNull(reference.getReferentialAction( + net.sf.jsqlparser.statement.ReferentialAction.Type.UPDATE)); + assertReparse(table); + } + + @Test + public void testNationalCharacterTypesAndOrderedModifiers() throws JSQLParserException { + CreateTable table = parseMySql("CREATE TABLE type_samples (label NATIONAL CHARACTER " + + "VARYING(64), code NATIONAL CHAR(8), flags BIGINT ZEROFILL SIGNED UNSIGNED " + + "SIGNED ZEROFILL)"); + + assertEquals(ColDataType.NationalCharacterType.VARCHAR, + table.getColumnDefinitions().get(0).getColDataType().getNationalCharacterType()); + assertEquals(ColDataType.NationalCharacterType.CHAR, + table.getColumnDefinitions().get(1).getColDataType().getNationalCharacterType()); + assertEquals(Arrays.asList(ColDataType.TypeModifier.ZEROFILL, + ColDataType.TypeModifier.SIGNED, ColDataType.TypeModifier.UNSIGNED, + ColDataType.TypeModifier.SIGNED, ColDataType.TypeModifier.ZEROFILL), + table.getColumnDefinitions().get(2).getColDataType().getTypeModifiers()); + assertReparse(table); + } + + @Test + public void testNationalCharacterAliases() throws JSQLParserException { + CreateTable table = parseMySql("CREATE TABLE localized_text (short_name NCHAR(16), " + + "long_name NVARCHAR(255), legacy_name NCHAR VARCHAR(64))"); + + assertEquals(ColDataType.NationalCharacterType.CHAR, + table.getColumnDefinitions().get(0).getColDataType().getNationalCharacterType()); + assertEquals(ColDataType.NationalCharacterType.VARCHAR, + table.getColumnDefinitions().get(1).getColDataType().getNationalCharacterType()); + assertEquals(ColDataType.NationalCharacterType.VARCHAR, + table.getColumnDefinitions().get(2).getColDataType().getNationalCharacterType()); + assertReparse(table); + } + + @Test + public void testStructuredMySqlTableOptionsAndKeywordColumn() throws JSQLParserException { + CreateTable table = + parseMySql("CREATE TABLE log_entry (Position BIGINT, File VARCHAR(255)) " + + "ENGINE=CSV DEFAULT CHAR SET=utf8mb4"); + + assertEquals("File", table.getColumnDefinitions().get(1).getColumnName()); + assertEquals(TableOption.Kind.ENGINE, table.getTableOptions().get(0).getKind()); + assertEquals("CSV", table.getTableOptions().get(0).getValue()); + assertEquals(TableOption.Kind.CHARACTER_SET, table.getTableOptions().get(1).getKind()); + assertEquals("utf8mb4", table.getTableOptions().get(1).getValue()); + assertReparse(table); + + CreateTable quotedEngine = parseMySql("CREATE TABLE archive_entry (id INT) " + + "ENGINE='InnoDB'"); + assertEquals("'InnoDB'", quotedEngine.getTableOption(TableOption.Kind.ENGINE) + .orElseThrow().getValue()); + assertReparse(quotedEngine); + + CreateTable commonOptions = parseMySql("CREATE TABLE table_options " + + "(id BIGINT AUTO_INCREMENT PRIMARY KEY) ENGINE=InnoDB " + + "DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin AUTO_INCREMENT=7 " + + "COMMENT='archive'"); + assertEquals("utf8mb4_bin", commonOptions.getTableOption(TableOption.Kind.COLLATE) + .orElseThrow().getValue()); + assertEquals("7", commonOptions.getTableOption(TableOption.Kind.AUTO_INCREMENT) + .orElseThrow().getValue()); + assertEquals("'archive'", commonOptions.getTableOption(TableOption.Kind.COMMENT) + .orElseThrow().getValue()); + assertReparse(commonOptions); + } + + @Test + public void testEscapedBacktickIdentifier() throws JSQLParserException { + CreateTable table = parseMySql("CREATE TABLE `odd``name` (`value``part` INT)"); + + assertEquals("`odd``name`", table.getTable().getName()); + assertEquals("`value``part`", table.getColumnDefinitions().get(0).getColumnName()); + assertReparse(table); + } + + @Test + public void testTableForeignKeyUsesStructuredReference() throws JSQLParserException { + CreateTable table = parseMySql("CREATE TABLE order_line (customer_id BIGINT, " + + "CONSTRAINT fk_customer FOREIGN KEY (customer_id) REFERENCES customer(id) " + + "MATCH FULL ON DELETE CASCADE)"); + + ForeignKeyIndex foreignKey = (ForeignKeyIndex) table.getIndexes().get(0); + assertEquals(Index.Kind.FOREIGN_KEY, foreignKey.getKind()); + assertEquals("customer", foreignKey.getReference().getTable().getName()); + assertEquals(ForeignKeyReference.MatchType.FULL, foreignKey.getMatchType()); + assertReparse(table); + } + + @Test + public void testAlterColumnUsesStructuredOptions() throws JSQLParserException { + Alter alter = (Alter) CCJSqlParserUtil.parse("ALTER TABLE child_record ADD COLUMN " + + "parent_id INT REFERENCES parent_record(id) MATCH FULL ON DELETE CASCADE", + parser -> parser.withDialect(Dialect.MYSQL)); + + AlterExpression.ColumnDataType column = + alter.getAlterExpressions().get(0).getColDataTypeList().get(0); + assertEquals("parent_record", column.getForeignKeyReference().getTable().getName()); + assertEquals(ForeignKeyReference.MatchType.FULL, + column.getForeignKeyReference().getMatchType()); + CCJSqlParserUtil.parse(alter.toString(), parser -> parser.withDialect(Dialect.MYSQL)); + } + + @Test + public void testCreateAndAlterTableIndexParity() throws JSQLParserException { + CreateTable create = parseMySql("CREATE TABLE search_item (id INT, body TEXT, " + + "PRIMARY KEY pk_search (id), FULLTEXT INDEX ft_body (body))"); + Alter alter = (Alter) CCJSqlParserUtil.parse( + "ALTER TABLE search_item ADD PRIMARY KEY pk_search (id), " + + "ADD FULLTEXT INDEX ft_body (body)", + parser -> parser.withDialect(Dialect.MYSQL)); + + assertEquals(create.getIndexes().get(0).getClass(), + alter.getAlterExpressions().get(0).getIndex().getClass()); + assertEquals(create.getIndexes().get(0).getKind(), + alter.getAlterExpressions().get(0).getIndex().getKind()); + assertEquals(create.getIndexes().get(0).toString(), + alter.getAlterExpressions().get(0).getIndex().toString()); + assertEquals(create.getIndexes().get(1).toString(), + alter.getAlterExpressions().get(1).getIndex().toString()); + CCJSqlParserUtil.parse(alter.toString(), parser -> parser.withDialect(Dialect.MYSQL)); + } + + private static CreateTable parseMySql(String sql) throws JSQLParserException { + return (CreateTable) CCJSqlParserUtil.parse(sql, + parser -> parser.withDialect(Dialect.MYSQL)); + } + + private static void assertReparse(CreateTable table) throws JSQLParserException { + parseMySql(table.toString()); + } +} diff --git a/src/test/java/net/sf/jsqlparser/statement/create/PartitionTraversalTest.java b/src/test/java/net/sf/jsqlparser/statement/create/PartitionTraversalTest.java new file mode 100644 index 0000000000..065ec7f495 --- /dev/null +++ b/src/test/java/net/sf/jsqlparser/statement/create/PartitionTraversalTest.java @@ -0,0 +1,128 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.statement.create; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.ArrayList; +import java.util.List; +import net.sf.jsqlparser.JSQLParserException; +import net.sf.jsqlparser.expression.ExpressionVisitorAdapter; +import net.sf.jsqlparser.expression.LongValue; +import net.sf.jsqlparser.parser.CCJSqlParserUtil; +import net.sf.jsqlparser.schema.Column; +import net.sf.jsqlparser.statement.Statement; +import net.sf.jsqlparser.statement.StatementVisitorAdapter; +import net.sf.jsqlparser.statement.select.SelectVisitorAdapter; +import net.sf.jsqlparser.util.TablesNamesFinder; +import net.sf.jsqlparser.util.deparser.ExpressionDeParser; +import net.sf.jsqlparser.util.deparser.SelectDeParser; +import net.sf.jsqlparser.util.deparser.StatementDeParser; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +class PartitionTraversalTest { + @ParameterizedTest + @ValueSource(strings = { + "ALTER TABLE parent ATTACH PARTITION child FOR VALUES FROM (0) TO (10)", + "ALTER TABLE parent ATTACH PARTITION child DEFAULT", + "ALTER TABLE parent DETACH PARTITION child CONCURRENTLY", + "ALTER TABLE parent EXCHANGE PARTITION p0 WITH TABLE child WITHOUT VALIDATION" + }) + void finderIncludesPartitionTables(String sql) throws JSQLParserException { + assertThat(TablesNamesFinder.findTables(sql)).containsExactlyInAnyOrder("parent", "child"); + } + + @Test + void visitorReceivesPartitionKeysBoundsAndContext() throws JSQLParserException { + List visited = new ArrayList<>(); + ExpressionVisitorAdapter expressions = new ExpressionVisitorAdapter() { + @Override + public Void visit(Column column, S context) { + assertEquals("context", context); + visited.add(column.getColumnName()); + return null; + } + + @Override + public Void visit(LongValue value, S context) { + assertEquals("context", context); + visited.add(value.toString()); + return null; + } + }; + StatementVisitorAdapter visitor = + new StatementVisitorAdapter<>(new SelectVisitorAdapter<>(expressions)); + CCJSqlParserUtil.parse("CREATE TABLE t (id INT) PARTITION BY RANGE (id) " + + "SUBPARTITION BY HASH (id + 1) SUBPARTITIONS 2") + .accept(visitor, "context"); + CCJSqlParserUtil + .parse("ALTER TABLE parent ATTACH PARTITION child FOR VALUES FROM (0) TO (10)") + .accept(visitor, "context"); + assertThat(visited).containsExactly("id", "id", "1", "0", "10"); + } + + @Test + void rangeMarkersAreNotVisitedAsColumnReferences() throws JSQLParserException { + List columns = new ArrayList<>(); + ExpressionVisitorAdapter expressions = new ExpressionVisitorAdapter() { + @Override + public Void visit(Column column, S context) { + columns.add(column.getColumnName()); + return null; + } + }; + CCJSqlParserUtil.parse("ALTER TABLE parent ATTACH PARTITION child " + + "FOR VALUES FROM (MINVALUE) TO (MAXVALUE)") + .accept(new StatementVisitorAdapter<>(new SelectVisitorAdapter<>(expressions)), + null); + assertThat(columns).isEmpty(); + } + + @ParameterizedTest + @ValueSource(strings = { + "CREATE TABLE t (id INT) PARTITION BY HASH (id + 1) PARTITIONS 2", + "ALTER TABLE t PARTITION BY RANGE COLUMNS (id)", + "CREATE TABLE child PARTITION OF parent FOR VALUES FROM (0) TO (10)", + "CREATE TABLE child PARTITION OF parent FOR VALUES FROM (MINVALUE) TO (MAXVALUE)", + "ALTER TABLE parent ATTACH PARTITION child FOR VALUES IN (1, 2)", + "ALTER TABLE parent ATTACH PARTITION child FOR VALUES WITH (MODULUS 4, REMAINDER 1)", + "ALTER TABLE parent ATTACH PARTITION child DEFAULT" + }) + void deparserUsesPartitionExpressionVisitor(String sql) throws JSQLParserException { + Statement statement = CCJSqlParserUtil.parse(sql); + StringBuilder output = new StringBuilder(); + ExpressionDeParser expressions = new ExpressionDeParser() { + @Override + public StringBuilder visit(Column column, S context) { + return getBuilder().append("mapped_").append(column.getColumnName()); + } + + @Override + public StringBuilder visit(LongValue value, S context) { + return getBuilder().append(value.getValue() + 100); + } + }; + statement.accept(new StatementDeParser(expressions, new SelectDeParser(), output), null); + String expected = statement.toString() + .replace("HASH (id + 1)", "HASH (mapped_id + 101)") + .replace("COLUMNS (id)", "COLUMNS (mapped_id)") + .replace("FROM (0) TO (10)", "FROM (100) TO (110)") + .replace("IN (1, 2)", "IN (101, 102)") + .replace("MODULUS 4, REMAINDER 1", "MODULUS 104, REMAINDER 101"); + assertEquals(expected, output.toString()); + StringBuilder plain = new StringBuilder(); + statement.accept(new StatementDeParser(plain), null); + assertEquals(statement.toString(), plain.toString()); + assertEquals(expected, CCJSqlParserUtil.parse(expected).toString()); + } +} diff --git a/src/test/java/net/sf/jsqlparser/statement/create/PostgreSQLCreateIndexTest.java b/src/test/java/net/sf/jsqlparser/statement/create/PostgreSQLCreateIndexTest.java new file mode 100644 index 0000000000..23d8442f92 --- /dev/null +++ b/src/test/java/net/sf/jsqlparser/statement/create/PostgreSQLCreateIndexTest.java @@ -0,0 +1,58 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.statement.create; + +import static net.sf.jsqlparser.test.TestUtils.assertSqlCanBeParsedAndDeparsed; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import net.sf.jsqlparser.JSQLParserException; +import net.sf.jsqlparser.statement.create.index.CreateIndex; +import org.junit.jupiter.api.Test; + +/** PostgreSQL {@code CREATE INDEX} syntax verified against PostgreSQL 18.6. */ +public class PostgreSQLCreateIndexTest { + + @Test + public void testCreateIndexConcurrently() throws JSQLParserException { + String sql = "CREATE UNIQUE INDEX CONCURRENTLY IF NOT EXISTS pg_idx_email " + + "ON pg_index_test USING btree (email)"; + + CreateIndex createIndex = (CreateIndex) assertSqlCanBeParsedAndDeparsed(sql); + + assertTrue(createIndex.isConcurrently()); + assertTrue(createIndex.isUsingIfNotExists()); + assertFalse(createIndex.isOnly()); + assertEquals("pg_idx_email", createIndex.getIndex().getName()); + } + + @Test + public void testCreateIndexWithOmittedNameAndOnly() throws JSQLParserException { + String sql = "CREATE INDEX ON ONLY pg_index_test USING btree (id DESC)"; + + CreateIndex createIndex = (CreateIndex) assertSqlCanBeParsedAndDeparsed(sql); + + assertNull(createIndex.getIndex().getName()); + assertTrue(createIndex.isOnly()); + assertFalse(createIndex.isConcurrently()); + } + + @Test + public void testCreateNamedIndexOnOnlyTable() throws JSQLParserException { + String sql = "CREATE INDEX pg_idx_only ON ONLY pg_index_test (id)"; + + CreateIndex createIndex = (CreateIndex) assertSqlCanBeParsedAndDeparsed(sql); + + assertEquals("pg_idx_only", createIndex.getIndex().getName()); + assertTrue(createIndex.isOnly()); + } +} diff --git a/src/test/java/net/sf/jsqlparser/statement/create/PostgreSqlAccessDdlTest.java b/src/test/java/net/sf/jsqlparser/statement/create/PostgreSqlAccessDdlTest.java new file mode 100644 index 0000000000..6b8b1e8fb0 --- /dev/null +++ b/src/test/java/net/sf/jsqlparser/statement/create/PostgreSqlAccessDdlTest.java @@ -0,0 +1,289 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.statement.create; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static net.sf.jsqlparser.test.TestUtils.assertSqlCanBeParsedAndDeparsed; +import static net.sf.jsqlparser.util.validation.ValidationTestAsserts.validateNoErrors; +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import net.sf.jsqlparser.JSQLParserException; +import net.sf.jsqlparser.expression.ExpressionVisitorAdapter; +import net.sf.jsqlparser.expression.Function; +import net.sf.jsqlparser.expression.LongValue; +import net.sf.jsqlparser.expression.StringValue; +import net.sf.jsqlparser.expression.operators.relational.ExpressionList; +import net.sf.jsqlparser.parser.AbstractJSqlParser.Dialect; +import net.sf.jsqlparser.parser.CCJSqlParserUtil; +import net.sf.jsqlparser.schema.Column; +import net.sf.jsqlparser.schema.Table; +import net.sf.jsqlparser.statement.RoutineReference; +import net.sf.jsqlparser.statement.Statement; +import net.sf.jsqlparser.statement.StatementVisitorAdapter; +import net.sf.jsqlparser.statement.StmtFeature; +import net.sf.jsqlparser.statement.create.table.ConstraintAttributes; +import net.sf.jsqlparser.statement.create.trigger.CreateTrigger; +import net.sf.jsqlparser.statement.create.trigger.TransitionRelation; +import net.sf.jsqlparser.statement.create.trigger.TriggerEvent; +import net.sf.jsqlparser.statement.create.user.CreateUser; +import net.sf.jsqlparser.statement.grant.AlterDefaultPrivileges; +import net.sf.jsqlparser.statement.grant.Grant; +import net.sf.jsqlparser.statement.grant.GrantOption; +import net.sf.jsqlparser.statement.grant.Privilege; +import net.sf.jsqlparser.statement.grant.PrivilegeTarget; +import net.sf.jsqlparser.statement.grant.Revoke; +import net.sf.jsqlparser.statement.role.AlterRole; +import net.sf.jsqlparser.statement.role.CreateRole; +import net.sf.jsqlparser.statement.role.RoleOption; +import net.sf.jsqlparser.statement.select.SelectVisitorAdapter; +import net.sf.jsqlparser.util.TablesNamesFinder; +import net.sf.jsqlparser.util.deparser.ExpressionDeParser; +import net.sf.jsqlparser.util.deparser.SelectDeParser; +import net.sf.jsqlparser.util.deparser.StatementDeParser; +import net.sf.jsqlparser.util.validation.feature.DatabaseType; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; +import org.junit.jupiter.params.provider.ValueSource; + +class PostgreSqlAccessDdlTest { + static Stream statements() throws Exception { + try (BufferedReader reader = new BufferedReader(new InputStreamReader( + PostgreSqlAccessDdlTest.class.getResourceAsStream("/postgresql/access-ddl.sql"), + StandardCharsets.UTF_8))) { + return reader.lines() + .filter(line -> !line.trim().isEmpty() && !line.trim().startsWith("--")) + .collect(Collectors.toList()).stream(); + } + } + + @ParameterizedTest + @MethodSource("statements") + void testRoundTripAndValidation(String sql) throws Exception { + Statement statement = assertSqlCanBeParsedAndDeparsed(sql); + assertThat(statement).isInstanceOfAny(CreateRole.class, AlterRole.class, Grant.class, + Revoke.class, AlterDefaultPrivileges.class, CreateTrigger.class); + assertThat(CCJSqlParserUtil.parse(statement.toString()).toString()) + .isEqualTo(statement.toString()); + assertThat(statement.getFeatures().getCertain()) + .containsExactly(StmtFeature.MODIFIES_SCHEMA); + assertThat(statement.getFeatures().getUncertain()) + .doesNotContain(StmtFeature.MODIFIES_DATA); + validateNoErrors(sql, 1, DatabaseType.POSTGRESQL); + } + + @Test + void testRoleOptionsAndAliases() throws Exception { + CreateRole role = (CreateRole) CCJSqlParserUtil + .parse("CREATE ROLE app WITH REPLICATION LOGIN CONNECTION LIMIT -1 PASSWORD NULL"); + assertThat(role.getCommand()).isEqualTo(CreateRole.Command.ROLE); + assertThat(role.isUseWith()).isTrue(); + assertThat(role.getOptions()).extracting(RoleOption::getKind).containsExactly( + RoleOption.Kind.REPLICATION, RoleOption.Kind.LOGIN, + RoleOption.Kind.CONNECTION_LIMIT, RoleOption.Kind.PASSWORD); + assertThat(((LongValue) role.getOptions().get(2).getValue()).getValue()).isEqualTo(-1); + assertThat(((CreateRole) CCJSqlParserUtil.parse("CREATE ROLE app")).getOptions()).isEmpty(); + assertThat(CCJSqlParserUtil.parse("CREATE USER app")).isInstanceOf(CreateUser.class); + CreateRole user = (CreateRole) CCJSqlParserUtil.parse("CREATE USER app", + p -> p.withDialect(Dialect.POSTGRESQL)); + assertThat(user.getCommand()).isEqualTo(CreateRole.Command.USER); + assertThat(user.getOptions()).isEmpty(); + assertThat(CCJSqlParserUtil + .parse("CREATE USER 'app'@'localhost' IDENTIFIED BY 'example-only'")) + .isInstanceOf(CreateUser.class); + assertThat(TablesNamesFinder.findTables(role.toString())).isEmpty(); + } + + @Test + void testAlterRoleSettings() throws Exception { + AlterRole role = (AlterRole) CCJSqlParserUtil + .parse("ALTER ROLE ALL IN DATABASE appdb SET search_path TO app, public"); + assertThat(role.getAction()).isEqualTo(AlterRole.Action.SET); + assertThat(role.getName()).isEqualTo("ALL"); + assertThat(role.getDatabase()).isEqualTo("appdb"); + assertThat(role.getValues()).hasSize(2); + role.getValues().set(0, new Column("new_schema")); + assertThat(role.toString()).endsWith("TO new_schema, public"); + assertThat(TablesNamesFinder.findTables(role.toString())).isEmpty(); + } + + @Test + void testTypedPrivilegesAndLegacyMutation() throws Exception { + Grant grant = (Grant) CCJSqlParserUtil.parse( + "GRANT SELECT (id), UPDATE (name) ON TABLE app.users, app.orders TO app WITH GRANT OPTION"); + assertThat(grant.getPrivilegeItems().get(0).getKind()).isEqualTo(Privilege.Kind.SELECT); + assertThat(grant.getPrivilegeItems().get(0).getColumns().get(0).getColumnName()) + .isEqualTo("id"); + assertThat(grant.getTarget().getKind()).isEqualTo(PrivilegeTarget.Kind.TABLE); + assertThat(grant.getTarget().getNames()).hasSize(2); + assertThat(grant.getOption().getKind()).isEqualTo(GrantOption.Kind.GRANT); + grant.addPrivileges("INSERT"); + assertThat(grant.getPrivilegeItems().get(0).getColumns()).hasSize(1); + grant.getPrivileges().set(1, "DELETE"); + grant.getUsers().add("reader"); + grant.getObjectNameParts().set(1, "new_users"); + assertThat(grant.getPrivilegeItems().get(1).getKind()).isEqualTo(Privilege.Kind.DELETE); + assertThat(grant.toString()).contains("app.new_users, app.orders TO app, reader"); + assertThat(TablesNamesFinder.findTables(grant.toString())) + .containsExactlyInAnyOrder("app.new_users", "app.orders"); + Grant created = new Grant().addPrivileges("SELECT") + .withObjectName(Arrays.asList("app", "users")).addUsers("app"); + assertThat(created.toString()).isEqualTo("GRANT SELECT ON app.users TO app"); + assertThat(created.getTarget().getNames()).containsExactly(Arrays.asList("app", "users")); + created.setObjectName(Arrays.asList("catalog", null, "users")); + assertThat(created.toString()).isEqualTo("GRANT SELECT ON catalog..users TO app"); + } + + @Test + void testRoutineSignaturesAndNonRelationTargets() throws Exception { + Grant grant = (Grant) CCJSqlParserUtil + .parse("GRANT EXECUTE ON FUNCTION app.f(IN arg integer), app.g(), app.h TO app"); + List routines = grant.getTarget().getRoutines(); + assertThat(routines).hasSize(3); + assertThat(routines.get(0).getArguments().get(0).getMode()) + .isEqualTo(RoutineReference.Argument.Mode.IN); + assertThat(routines.get(0).getArguments().get(0).getName()).isEqualTo("arg"); + assertThat(routines.get(1).getArguments()).isEmpty(); + assertThat(routines.get(2).getArguments()).isNull(); + assertThat(TablesNamesFinder.findTables(grant.toString())).isEmpty(); + assertThat( + TablesNamesFinder.findTables("GRANT SELECT ON ALL TABLES IN SCHEMA app TO reader")) + .isEmpty(); + assertThat(TablesNamesFinder.findTables("GRANT USAGE ON SCHEMA app TO reader")).isEmpty(); + } + + @Test + void testRevokeAndDefaults() throws Exception { + Revoke revoke = (Revoke) CCJSqlParserUtil + .parse("REVOKE ADMIN OPTION FOR reader, writer FROM app GRANTED BY owner CASCADE"); + assertThat(revoke.getOptionFor()).isEqualTo(GrantOption.Kind.ADMIN); + assertThat(revoke.getClause().getRoles()).containsExactly("reader", "writer"); + assertThat(revoke.getClause().getGrantedBy()).isEqualTo("owner"); + assertThat(revoke.getBehavior()).isEqualTo(Revoke.Behavior.CASCADE); + AlterDefaultPrivileges defaults = (AlterDefaultPrivileges) CCJSqlParserUtil.parse( + "ALTER DEFAULT PRIVILEGES FOR USER owner IN SCHEMA app GRANT SELECT ON TABLES TO reader"); + assertThat(defaults.getRoleKeyword()).isEqualTo(AlterDefaultPrivileges.RoleKeyword.USER); + assertThat(defaults.getGrant().getTarget().isDefaultPrivileges()).isTrue(); + assertThat(defaults.getSchemas()).containsExactly("app"); + assertThat(TablesNamesFinder.findTables(defaults.toString())).isEmpty(); + } + + @Test + void testTriggerFields() throws Exception { + CreateTrigger trigger = (CreateTrigger) CCJSqlParserUtil.parse( + "CREATE CONSTRAINT TRIGGER check_parent AFTER INSERT OR UPDATE ON child FROM parent DEFERRABLE INITIALLY DEFERRED FOR EACH ROW WHEN (NEW.id > 0) EXECUTE PROCEDURE app.check_parent('id')"); + assertThat(trigger.getEvent()).isEqualTo(CreateTrigger.Event.INSERT); + assertThat(trigger.getEvents()).extracting(TriggerEvent::getEvent) + .containsExactly(CreateTrigger.Event.INSERT, CreateTrigger.Event.UPDATE); + assertThat(trigger.getConstraintAttributes().getInitially()) + .isEqualTo(ConstraintAttributes.Initially.DEFERRED); + assertThat(trigger.getBody()).isNull(); + assertThat(trigger.getRoutine().getName()).isEqualTo("app.check_parent"); + assertThat(trigger.getExecuteKeyword()).isEqualTo(CreateTrigger.ExecuteKeyword.PROCEDURE); + assertThat(TablesNamesFinder.findTables(trigger.toString())) + .containsExactlyInAnyOrder("child", "parent"); + CreateTrigger transition = (CreateTrigger) CCJSqlParserUtil.parse( + "CREATE TRIGGER audit AFTER INSERT ON users REFERENCING NEW TABLE AS inserted EXECUTE FUNCTION audit_all()"); + assertThat(transition.getOrientation()).isNull(); + assertThat(transition.getTransitionRelations().get(0).getImage()) + .isEqualTo(TransitionRelation.Image.NEW); + assertThat(transition.getTransitionRelations().get(0).isUseAs()).isTrue(); + assertThat(TablesNamesFinder.findTables(transition.toString())).containsExactly("users"); + } + + @Test + void testVisitorsAndDeparser() throws Exception { + Statement trigger = CCJSqlParserUtil.parse( + "CREATE TRIGGER audit BEFORE UPDATE OF id ON users FOR EACH ROW WHEN (NEW.id > 0) EXECUTE FUNCTION audit_row('example')"); + List columns = new ArrayList<>(); + List strings = new ArrayList<>(); + ExpressionVisitorAdapter expressions = new ExpressionVisitorAdapter() { + @Override + public Void visit(Column column, S context) { + assertThat(context).isEqualTo("access"); + columns.add(column.getColumnName()); + return null; + } + + @Override + public Void visit(StringValue value, S context) { + assertThat(context).isEqualTo("access"); + strings.add(value.getValue()); + return null; + } + }; + StatementVisitorAdapter visitor = + new StatementVisitorAdapter<>(new SelectVisitorAdapter<>(expressions)); + trigger.accept(visitor, "access"); + CCJSqlParserUtil.parse("GRANT SELECT (name) ON users TO app").accept(visitor, "access"); + CCJSqlParserUtil.parse("CREATE ROLE app PASSWORD 'example-only'").accept(visitor, "access"); + assertThat(columns).containsExactly("id", "id", "name"); + assertThat(strings).containsExactly("example", "example-only"); + StringBuilder output = new StringBuilder(); + ExpressionDeParser deparser = new ExpressionDeParser() { + @Override + public StringBuilder visit(StringValue value, S context) { + return getBuilder().append("'changed'"); + } + }; + trigger.accept(new StatementDeParser(deparser, new SelectDeParser(), output)); + assertThat(output.toString()).endsWith("audit_row('changed')"); + assertThat(trigger.toString()).endsWith("audit_row('example')"); + } + + @Test + void testProgrammaticTriggerAndRole() throws Exception { + CreateTrigger trigger = new CreateTrigger(); + trigger.setTrigger(new Table("audit")); + trigger.setTable(new Table("users")); + trigger.setTiming(CreateTrigger.Timing.AFTER); + trigger.setEvent(CreateTrigger.Event.INSERT); + Function routine = new Function(); + routine.setName("audit_row"); + routine.setParameters(new ExpressionList<>()); + trigger.setRoutine(routine); + assertThat(CCJSqlParserUtil.parse(trigger.toString()).toString()) + .isEqualTo(trigger.toString()); + CreateRole role = new CreateRole(); + role.setName("reader"); + role.getOptions().add(new RoleOption(RoleOption.Kind.NOLOGIN)); + assertThat(CCJSqlParserUtil.parse(role.toString()).toString()) + .isEqualTo("CREATE ROLE reader NOLOGIN"); + } + + @ParameterizedTest + @ValueSource(strings = { + "CREATE OR REPLACE ROLE app", "CREATE ROLE app WITH", "CREATE ROLE app CONNECTION 10", + "CREATE ROLE app PASSWORD", "ALTER ROLE ALL LOGIN", + "ALTER ROLE app IN DATABASE appdb LOGIN", + "ALTER ROLE app IN ROLE reader", "ALTER GROUP staff LOGIN", + "GRANT SELECT ON users FROM app", "REVOKE SELECT ON users TO app", + "GRANT SELECT () ON users TO app", "GRANT SELECT ON users TO app WITH ADMIN OPTION", + "GRANT reader TO app WITH GRANT OPTION", + "REVOKE ADMIN OPTION FOR SELECT ON users FROM app", + "ALTER DEFAULT PRIVILEGES GRANT SELECT ON TABLE users TO app", + "ALTER DEFAULT PRIVILEGES GRANT SELECT ON TABLES TO app GRANTED BY owner", + "CREATE TRIGGER audit BEFORE INSERT ON users FOR EACH ROW EXECUTE FUNCTION audit_row(id + 1)", + "CREATE CONSTRAINT TRIGGER audit BEFORE INSERT ON users FOR EACH ROW EXECUTE FUNCTION audit_row()", + "CREATE TRIGGER audit AFTER INSERT ON users DEFERRABLE EXECUTE FUNCTION audit_row()", + "CREATE OR REPLACE CONSTRAINT TRIGGER audit AFTER INSERT ON users FOR EACH ROW EXECUTE FUNCTION audit_row()" + }) + void testInvalidSyntax(String sql) { + assertThatThrownBy(() -> CCJSqlParserUtil.parse(sql)) + .isInstanceOf(JSQLParserException.class); + } +} diff --git a/src/test/java/net/sf/jsqlparser/statement/create/PostgreSqlCreateViewTest.java b/src/test/java/net/sf/jsqlparser/statement/create/PostgreSqlCreateViewTest.java new file mode 100644 index 0000000000..524c3d61fc --- /dev/null +++ b/src/test/java/net/sf/jsqlparser/statement/create/PostgreSqlCreateViewTest.java @@ -0,0 +1,112 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.statement.create; + +import static net.sf.jsqlparser.test.TestUtils.assertSqlCanBeParsedAndDeparsed; +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.Collections; +import net.sf.jsqlparser.JSQLParserException; +import net.sf.jsqlparser.expression.BooleanValue; +import net.sf.jsqlparser.parser.CCJSqlParserUtil; +import net.sf.jsqlparser.schema.Table; +import net.sf.jsqlparser.statement.create.view.CreateView; +import net.sf.jsqlparser.statement.create.view.ViewOption; +import net.sf.jsqlparser.statement.select.Select; +import net.sf.jsqlparser.util.TablesNamesFinder; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +/** + * @see PostgreSQL CREATE VIEW + */ +class PostgreSqlCreateViewTest { + @Test + void testSecurityOptions() throws JSQLParserException { + CreateView view = (CreateView) assertSqlCanBeParsedAndDeparsed( + "CREATE VIEW visible_accounts WITH (security_barrier = true, security_invoker = false)" + + " AS SELECT * FROM accounts WHERE enabled WITH LOCAL CHECK OPTION"); + assertThat(view.getOptions()).hasSize(2); + assertThat(view.getOptions().get(0).getKind()).isEqualTo(ViewOption.Kind.SECURITY_BARRIER); + assertThat(view.getOptions().get(0).getBooleanValue()).isTrue(); + assertThat(view.getOptions().get(1).getBooleanValue()).isFalse(); + assertThat(view.getCheckOption()).isEqualTo(CreateView.CheckOption.LOCAL); + assertThat(TablesNamesFinder.findTables(view.toString())) + .containsExactlyInAnyOrder("visible_accounts", "accounts"); + } + + @Test + void testImplicitOptionValueAndCheckOptionParameter() throws JSQLParserException { + CreateView view = (CreateView) assertSqlCanBeParsedAndDeparsed( + "CREATE VIEW visible_accounts WITH (security_barrier, check_option = 'local')" + + " AS SELECT * FROM accounts"); + assertThat(view.getOptions().get(0).getValue()).isNull(); + assertThat(view.getOptions().get(0).getBooleanValue()).isTrue(); + assertThat(view.getOptions().get(1).getCheckOption()) + .isEqualTo(CreateView.CheckOption.LOCAL); + assertThat(view.getCheckOption()).isNull(); + } + + @Test + void testDefaultCheckOption() throws JSQLParserException { + CreateView view = (CreateView) assertSqlCanBeParsedAndDeparsed( + "CREATE VIEW visible_accounts AS SELECT * FROM accounts WITH CHECK OPTION"); + assertThat(view.getCheckOption()).isEqualTo(CreateView.CheckOption.DEFAULT); + assertThat(view.getEffectiveCheckOption()).isEqualTo(CreateView.CheckOption.CASCADED); + } + + @Test + void testRecursiveView() throws JSQLParserException { + CreateView view = (CreateView) assertSqlCanBeParsedAndDeparsed( + "CREATE RECURSIVE VIEW numbers(n) AS VALUES (1)" + + " UNION ALL SELECT n + 1 FROM numbers WHERE n < 5"); + assertThat(view.isRecursive()).isTrue(); + assertThat(view.getColumnNames()).hasSize(1); + } + + @Test + void testMaterializedViewParameters() throws JSQLParserException { + CreateView view = (CreateView) assertSqlCanBeParsedAndDeparsed( + "CREATE MATERIALIZED VIEW IF NOT EXISTS account_totals(id) USING heap" + + " WITH (fillfactor = 70) TABLESPACE archive" + + " AS SELECT id FROM accounts WITH NO DATA"); + assertThat(view.isMaterialized()).isTrue(); + assertThat(view.isIfNotExists()).isTrue(); + assertThat(view.isIfNotExistsAfterViewName()).isFalse(); + assertThat(view.getAccessMethod()).isEqualTo("heap"); + assertThat(view.getStorageParameters().get(0).getName()).isEqualTo("fillfactor"); + assertThat(view.getTableSpace()).isEqualTo("archive"); + assertThat(view.getWithData()).isFalse(); + assertThat(view.getOptions()).isNull(); + } + + @ParameterizedTest + @ValueSource(strings = {"", " WITH DATA", " WITH NO DATA"}) + void testMaterializedViewDataMode(String suffix) throws JSQLParserException { + CreateView view = (CreateView) assertSqlCanBeParsedAndDeparsed( + "CREATE MATERIALIZED VIEW account_totals AS SELECT * FROM accounts" + suffix); + assertThat(view.getWithData()).isEqualTo(suffix.isEmpty() ? null : !suffix.contains("NO")); + CreateView reparsed = (CreateView) CCJSqlParserUtil.parse(view.toString()); + assertThat(reparsed.getWithData()).isEqualTo(view.getWithData()); + } + + @Test + void testProgrammaticConstruction() throws JSQLParserException { + CreateView view = new CreateView().withView(new Table("visible_accounts")) + .withSelect((Select) CCJSqlParserUtil.parse("SELECT * FROM accounts")); + view.setOptions(Collections.singletonList( + new ViewOption("security_barrier", new BooleanValue(true), true))); + view.setCheckOption(CreateView.CheckOption.CASCADED); + CreateView parsed = (CreateView) assertSqlCanBeParsedAndDeparsed(view.toString()); + assertThat(parsed.getOptions().get(0).getBooleanValue()).isTrue(); + assertThat(parsed.getCheckOption()).isEqualTo(CreateView.CheckOption.CASCADED); + } +} diff --git a/src/test/java/net/sf/jsqlparser/statement/create/PostgreSqlReplicationDdlTest.java b/src/test/java/net/sf/jsqlparser/statement/create/PostgreSqlReplicationDdlTest.java new file mode 100644 index 0000000000..02e6c5566e --- /dev/null +++ b/src/test/java/net/sf/jsqlparser/statement/create/PostgreSqlReplicationDdlTest.java @@ -0,0 +1,209 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.statement.create; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static net.sf.jsqlparser.test.TestUtils.assertSqlCanBeParsedAndDeparsed; +import static net.sf.jsqlparser.util.validation.ValidationTestAsserts.validateNoErrors; + +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import net.sf.jsqlparser.JSQLParserException; +import net.sf.jsqlparser.expression.ExpressionVisitorAdapter; +import net.sf.jsqlparser.expression.StringValue; +import net.sf.jsqlparser.parser.CCJSqlParserUtil; +import net.sf.jsqlparser.schema.Column; +import net.sf.jsqlparser.statement.Statement; +import net.sf.jsqlparser.statement.StatementVisitorAdapter; +import net.sf.jsqlparser.statement.StmtFeature; +import net.sf.jsqlparser.statement.alter.AlterPublication; +import net.sf.jsqlparser.statement.alter.AlterSubscription; +import net.sf.jsqlparser.statement.create.publication.CreatePublication; +import net.sf.jsqlparser.statement.create.publication.PublicationOption; +import net.sf.jsqlparser.statement.create.publication.PublicationTable; +import net.sf.jsqlparser.statement.create.publication.PublicationTarget; +import net.sf.jsqlparser.statement.create.subscription.CreateSubscription; +import net.sf.jsqlparser.statement.create.subscription.SubscriptionOption; +import net.sf.jsqlparser.statement.select.SelectVisitorAdapter; +import net.sf.jsqlparser.util.TablesNamesFinder; +import net.sf.jsqlparser.util.deparser.ExpressionDeParser; +import net.sf.jsqlparser.util.deparser.SelectDeParser; +import net.sf.jsqlparser.util.deparser.StatementDeParser; +import net.sf.jsqlparser.util.validation.feature.DatabaseType; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; +import org.junit.jupiter.params.provider.ValueSource; + +class PostgreSqlReplicationDdlTest { + static Stream statements() throws Exception { + try (BufferedReader reader = new BufferedReader(new InputStreamReader( + PostgreSqlReplicationDdlTest.class + .getResourceAsStream("/postgresql/replication-ddl.sql"), + StandardCharsets.UTF_8))) { + return reader.lines() + .filter(line -> !line.trim().isEmpty() && !line.trim().startsWith("--")) + .collect(Collectors.toList()).stream(); + } + } + + @ParameterizedTest + @MethodSource("statements") + void testRoundTripAndValidation(String sql) throws Exception { + Statement statement = assertSqlCanBeParsedAndDeparsed(sql); + assertThat(statement).isInstanceOfAny(CreatePublication.class, AlterPublication.class, + CreateSubscription.class, AlterSubscription.class); + assertThat(CCJSqlParserUtil.parse(statement.toString()).toString()) + .isEqualTo(statement.toString()); + assertThat(statement.getFeatures().getCertain()).contains(StmtFeature.MODIFIES_SCHEMA) + .doesNotContain(StmtFeature.RETURNS_RESULT_SET); + validateNoErrors(sql, 1, DatabaseType.POSTGRESQL); + } + + @Test + void testPublicationTargets() throws Exception { + CreatePublication publication = (CreatePublication) CCJSqlParserUtil.parse( + "CREATE PUBLICATION mixed FOR TABLE ONLY app.users WHERE (id > 0), orders *, TABLES IN SCHEMA app, audit, TABLE events"); + assertThat(publication.isAllTables()).isFalse(); + assertThat(publication.getTargets()).extracting(PublicationTarget::getKind) + .containsExactly(PublicationTarget.Kind.TABLE, + PublicationTarget.Kind.TABLES_IN_SCHEMA, PublicationTarget.Kind.TABLE); + PublicationTable first = publication.getTargets().get(0).getTables().get(0); + assertThat(first.isOnly()).isTrue(); + assertThat(first.getWhere()).isNotNull(); + assertThat(publication.getTargets().get(0).getTables().get(1).isIncludeDescendants()) + .isTrue(); + assertThat(publication.getTargets().get(1).getSchemas()).containsExactly("app", "audit"); + assertThat(TablesNamesFinder.findTables(publication.toString())) + .containsExactlyInAnyOrder("app.users", "orders", "events"); + assertThat(TablesNamesFinder.findTables("CREATE PUBLICATION p FOR ALL TABLES")).isEmpty(); + assertThat(TablesNamesFinder.findTables("CREATE PUBLICATION p FOR TABLES IN SCHEMA app")) + .isEmpty(); + } + + @Test + void testPublicationOptions() throws Exception { + CreatePublication publication = (CreatePublication) CCJSqlParserUtil.parse( + "CREATE PUBLICATION p FOR ALL TABLES WITH (publish = 'insert, update', publish_via_partition_root, publish_generated_columns = stored)"); + assertThat(publication.getOptions().get(0).getPublishOperations()) + .containsExactlyInAnyOrder(PublicationOption.Operation.INSERT, + PublicationOption.Operation.UPDATE); + assertThat(publication.getOptions().get(1).getBooleanValue()).isTrue(); + assertThat(publication.getOptions().get(1).getValue()).isNull(); + assertThat(publication.getOptions().get(2).getGeneratedColumns()) + .isEqualTo(PublicationOption.GeneratedColumns.STORED); + assertThat( + ((CreatePublication) CCJSqlParserUtil.parse("CREATE PUBLICATION p")).getOptions()) + .isEmpty(); + publication.setName("new_publication"); + assertThat(CCJSqlParserUtil.parse(publication.toString()).toString()) + .startsWith("CREATE PUBLICATION new_publication"); + } + + @Test + void testSubscriptionOptionsAndNames() throws Exception { + CreateSubscription subscription = (CreateSubscription) CCJSqlParserUtil.parse( + "CREATE SUBSCRIPTION sub CONNECTION 'dbname=app' PUBLICATION first_pub, second_pub WITH (connect = false, streaming = parallel, slot_name = NONE)"); + assertThat(subscription.getConnection().getValue()).isEqualTo("dbname=app"); + assertThat(subscription.getPublications()).containsExactly("first_pub", "second_pub"); + assertThat(subscription.getOptions().get(0).getBooleanValue()).isFalse(); + assertThat(subscription.getOptions().get(1).getStreaming()) + .isEqualTo(SubscriptionOption.Streaming.PARALLEL); + assertThat(subscription.getOptions().get(2).isSlotNameNone()).isTrue(); + assertThat(new SubscriptionOption("slot_name", SubscriptionOption.Kind.SLOT_NAME, + new StringValue("NONE")).isSlotNameNone()).isFalse(); + assertThat(TablesNamesFinder.findTables(subscription.toString())).isEmpty(); + assertThat(subscription.getFeatures().getUncertain()) + .doesNotContain(StmtFeature.MODIFIES_DATA); + } + + @Test + void testAlterActions() throws Exception { + AlterPublication publication = + (AlterPublication) CCJSqlParserUtil.parse("ALTER PUBLICATION p DROP TABLE users"); + assertThat(publication.getAction()).isEqualTo(AlterPublication.Action.DROP); + assertThat(publication.getTargets().get(0).getTables().get(0).getColumns()).isNull(); + AlterSubscription subscription = (AlterSubscription) CCJSqlParserUtil + .parse("ALTER SUBSCRIPTION sub SET PUBLICATION p WITH (refresh = false)"); + assertThat(subscription.getAction()).isEqualTo(AlterSubscription.Action.SET_PUBLICATION); + assertThat(subscription.getOptions().get(0).getKind()) + .isEqualTo(SubscriptionOption.Kind.REFRESH); + AlterSubscription skip = (AlterSubscription) CCJSqlParserUtil + .parse("ALTER SUBSCRIPTION sub SKIP (lsn = '0/123')"); + assertThat(skip.getAction()).isEqualTo(AlterSubscription.Action.SKIP); + assertThat(skip.getOptions().get(0).getValue()).isInstanceOf(StringValue.class); + } + + @Test + void testVisitorAndCustomDeparser() throws Exception { + String sql = "CREATE PUBLICATION p FOR TABLE users (id) WHERE (id > 0)"; + Statement statement = CCJSqlParserUtil.parse(sql); + List columns = new ArrayList<>(); + ExpressionVisitorAdapter expressions = new ExpressionVisitorAdapter() { + @Override + public Void visit(Column column, S context) { + assertThat(context).isEqualTo("publication"); + columns.add(column.getColumnName()); + return null; + } + }; + statement.accept(new StatementVisitorAdapter<>(new SelectVisitorAdapter<>(expressions)), + "publication"); + assertThat(columns).containsExactly("id", "id"); + + StringBuilder output = new StringBuilder(); + ExpressionDeParser deparser = new ExpressionDeParser() { + @Override + public StringBuilder visit(Column column, S context) { + return getBuilder().append("new_").append(column.getColumnName()); + } + }; + statement.accept(new StatementDeParser(deparser, new SelectDeParser(), output)); + assertThat(output.toString()).contains("users (new_id) WHERE (new_id > 0)"); + assertThat(statement.toString()).doesNotContain("new_id"); + CCJSqlParserUtil.parse(output.toString()); + } + + @Test + void testProgrammaticConstruction() throws Exception { + CreateSubscription subscription = new CreateSubscription(); + subscription.setName("sub"); + subscription.setConnection(new StringValue("dbname=app")); + subscription.getPublications().add("pub"); + subscription.getOptions() + .add(new SubscriptionOption("connect", SubscriptionOption.Kind.CONNECT, + CCJSqlParserUtil.parseExpression("false"))); + assertThat(CCJSqlParserUtil.parse(subscription.toString()).toString()) + .isEqualTo(subscription.toString()); + } + + @ParameterizedTest + @ValueSource(strings = {"ALTER SUBSCRIPTION sub SKIP (lsn)", + "ALTER SUBSCRIPTION sub SKIP (copy_data = false)", "CREATE OR REPLACE PUBLICATION p", + "CREATE OR REPLACE SUBSCRIPTION sub CONNECTION 'dbname=app' PUBLICATION p", + "CREATE PUBLICATION p FOR TABLE", "CREATE PUBLICATION p FOR TABLE users ()", + "CREATE PUBLICATION p FOR TABLE users WHERE id > 0", + "CREATE PUBLICATION p FOR TABLE ONLY users *", + "ALTER PUBLICATION p DROP TABLE users (id)", + "ALTER PUBLICATION p DROP TABLE users WHERE (id > 0)", + "CREATE SUBSCRIPTION sub PUBLICATION p", + "CREATE SUBSCRIPTION sub CONNECTION 'dbname=app' PUBLICATION", + "ALTER SUBSCRIPTION sub SET ()", "CREATE PUBLICATION p WITH (unknown = true)"}) + void testInvalidSyntax(String sql) { + assertThatThrownBy(() -> CCJSqlParserUtil.parse(sql)) + .isInstanceOf(JSQLParserException.class); + } +} diff --git a/src/test/java/net/sf/jsqlparser/statement/create/PostgreSqlRoutineArgumentTest.java b/src/test/java/net/sf/jsqlparser/statement/create/PostgreSqlRoutineArgumentTest.java new file mode 100644 index 0000000000..64dc8078f0 --- /dev/null +++ b/src/test/java/net/sf/jsqlparser/statement/create/PostgreSqlRoutineArgumentTest.java @@ -0,0 +1,72 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.statement.create; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import net.sf.jsqlparser.JSQLParserException; +import net.sf.jsqlparser.parser.CCJSqlParserUtil; +import net.sf.jsqlparser.statement.Statement; +import net.sf.jsqlparser.statement.alter.AlterExtension; +import net.sf.jsqlparser.statement.grant.Grant; +import net.sf.jsqlparser.statement.RoutineReference; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +class PostgreSqlRoutineArgumentTest { + @ParameterizedTest + @ValueSource(strings = {"integer", "text[]", "IN value integer", "OUT result text", + "INOUT state app.custom_type", "VARIADIC items text[]", + "\"argument name\" double precision"}) + void privilegeAndExtensionArgumentsHaveEquivalentAst(String argument) + throws JSQLParserException { + Grant grant = (Grant) CCJSqlParserUtil.parse( + "GRANT EXECUTE ON FUNCTION app.f(" + argument + ") TO reader"); + AlterExtension extension = (AlterExtension) CCJSqlParserUtil.parse( + "ALTER EXTENSION example ADD FUNCTION app.f(" + argument + ")"); + RoutineReference.Argument access = + grant.getTarget().getRoutines().get(0).getArguments().get(0); + RoutineReference.Argument member = extension.getMember().getRoutine().getArguments().get(0); + assertEquals(access.getMode(), member.getMode()); + assertEquals(access.getName(), member.getName()); + assertEquals(access.getDataType().toString(), member.getDataType().toString()); + assertRoundTrip(grant); + assertRoundTrip(extension); + } + + @Test + void aggregateKeepsOrderByArgumentBoundary() throws JSQLParserException { + AlterExtension extension = (AlterExtension) CCJSqlParserUtil.parse( + "ALTER EXTENSION example ADD AGGREGATE app.percentile(double precision ORDER BY integer)"); + RoutineReference routine = extension.getMember().getRoutine(); + assertEquals(1, routine.getArguments().size()); + assertEquals(1, routine.getOrderByArguments().size()); + assertEquals("double precision", routine.getArguments().get(0).getDataType().toString()); + assertRoundTrip(extension); + } + + @ParameterizedTest + @ValueSource(strings = { + "GRANT EXECUTE ON FUNCTION app.f(integer ORDER BY integer) TO reader", + "GRANT EXECUTE ON FUNCTION app.f(*) TO reader", + "ALTER EXTENSION example ADD FUNCTION app.f(integer ORDER BY integer)", + "ALTER EXTENSION example ADD FUNCTION app.f(*)", + "GRANT EXECUTE ON FUNCTION app.f(IN) TO reader" + }) + void aggregateSyntaxDoesNotLeakIntoOtherRoutineContexts(String sql) { + assertThrows(JSQLParserException.class, () -> CCJSqlParserUtil.parse(sql)); + } + + private static void assertRoundTrip(Statement statement) throws JSQLParserException { + assertEquals(statement.toString(), CCJSqlParserUtil.parse(statement.toString()).toString()); + } +} diff --git a/src/test/java/net/sf/jsqlparser/statement/create/PostgreSqlSchemaDdlTest.java b/src/test/java/net/sf/jsqlparser/statement/create/PostgreSqlSchemaDdlTest.java new file mode 100644 index 0000000000..c3ac603f1d --- /dev/null +++ b/src/test/java/net/sf/jsqlparser/statement/create/PostgreSqlSchemaDdlTest.java @@ -0,0 +1,236 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.statement.create; + +import static net.sf.jsqlparser.test.TestUtils.assertSqlCanBeParsedAndDeparsed; +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.Arrays; +import java.util.Collections; +import net.sf.jsqlparser.JSQLParserException; +import net.sf.jsqlparser.parser.CCJSqlParserUtil; +import net.sf.jsqlparser.schema.Column; +import net.sf.jsqlparser.schema.Sequence; +import net.sf.jsqlparser.schema.SequenceOwnership; +import net.sf.jsqlparser.schema.Table; +import net.sf.jsqlparser.statement.LikeClause; +import net.sf.jsqlparser.statement.alter.Alter; +import net.sf.jsqlparser.statement.alter.AlterExpression; +import net.sf.jsqlparser.statement.alter.IdentityAlteration; +import net.sf.jsqlparser.statement.alter.sequence.AlterSequence; +import net.sf.jsqlparser.statement.create.sequence.CreateSequence; +import net.sf.jsqlparser.statement.create.table.ColumnOption; +import net.sf.jsqlparser.statement.create.table.ConstraintAttributes; +import net.sf.jsqlparser.statement.create.table.CreateTable; +import net.sf.jsqlparser.statement.create.table.ExcludeConstraint; +import net.sf.jsqlparser.statement.create.table.IdentityDefinition; +import net.sf.jsqlparser.statement.create.table.Index; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +/** + * @see PostgreSQL CREATE + * TABLE + */ +class PostgreSqlSchemaDdlTest { + @Test + void testExclusionConstraint() throws JSQLParserException { + CreateTable table = (CreateTable) assertSqlCanBeParsedAndDeparsed( + "CREATE TABLE reservations (room integer, during tsrange," + + " CONSTRAINT no_overlap EXCLUDE USING gist (room WITH =, during WITH &&)" + + " WHERE (room > 0) DEFERRABLE INITIALLY DEFERRED)"); + ExcludeConstraint constraint = (ExcludeConstraint) table.getIndexes().get(0); + assertThat(constraint.getName()).isEqualTo("no_overlap"); + assertThat(constraint.getUsing()).isEqualTo("gist"); + assertThat(constraint.getColumns().get(0).getExclusionOperator()).isEqualTo("="); + assertThat(constraint.getColumns().get(1).getExclusionOperator()).isEqualTo("&&"); + assertThat(constraint.getExpression()).isNotNull(); + assertThat(constraint.getConstraintAttributes().getDeferrable()).isTrue(); + assertThat(constraint.getConstraintAttributes().getInitially()) + .isEqualTo(ConstraintAttributes.Initially.DEFERRED); + } + + @Test + void testExclusionExpressionAndStorage() throws JSQLParserException { + CreateTable table = (CreateTable) assertSqlCanBeParsedAndDeparsed( + "CREATE TABLE reservations (room text, EXCLUDE USING gist ((lower(room)) WITH =)" + + " INCLUDE (room) WITH (fillfactor = 70) USING INDEX TABLESPACE archive)"); + Index constraint = table.getIndexes().get(0); + assertThat(constraint.getColumns().get(0).isExpression()).isTrue(); + assertThat(constraint.getIncludeColumns()).containsExactly("room"); + assertThat(constraint.getStorageParameters()).hasSize(1); + assertThat(constraint.getTableSpace()).isEqualTo("archive"); + } + + @Test + void testUniqueNullTreatment() throws JSQLParserException { + CreateTable table = (CreateTable) assertSqlCanBeParsedAndDeparsed( + "CREATE TABLE contacts (email text UNIQUE NULLS NOT DISTINCT," + + " CONSTRAINT contact_email UNIQUE NULLS DISTINCT (email))"); + ColumnOption option = table.getColumnDefinitions().get(0).getColumnOptions().get(0); + assertThat(option.getKind()).isEqualTo(ColumnOption.Kind.CONSTRAINT); + assertThat(option.getConstraint().getNullsDistinct()).isFalse(); + assertThat(table.getIndexes().get(0).getNullsDistinct()).isTrue(); + } + + @Test + void testPrimaryKeyInclude() throws JSQLParserException { + CreateTable table = (CreateTable) assertSqlCanBeParsedAndDeparsed( + "CREATE TABLE contacts (id integer, email text, PRIMARY KEY (id) INCLUDE (email))"); + assertThat(table.getIndexes().get(0).getIncludeColumns()).containsExactly("email"); + Alter alter = (Alter) assertSqlCanBeParsedAndDeparsed( + "ALTER TABLE contacts ADD PRIMARY KEY (id) INCLUDE (email)"); + assertThat(alter.getAlterExpressions().get(0).getIndex().getIncludeColumns()) + .containsExactly("email"); + } + + @Test + void testLikeOptionsInOrder() throws JSQLParserException { + CreateTable table = (CreateTable) assertSqlCanBeParsedAndDeparsed( + "CREATE TABLE contact_copy (LIKE contacts INCLUDING ALL EXCLUDING INDEXES," + + " extra integer, LIKE history EXCLUDING ALL INCLUDING DEFAULTS INCLUDING COMPRESSION)"); + assertThat(table.getTableElements()).hasSize(3); + LikeClause clause = table.getTableElements(LikeClause.class).get(0); + assertThat(table.getLikeTable().getName()).isEqualTo("contacts"); + assertThat(clause.getOptions()).hasSize(2); + assertThat(clause.isIncluding(LikeClause.OptionKind.DEFAULTS)).isTrue(); + assertThat(clause.isIncluding(LikeClause.OptionKind.INDEXES)).isFalse(); + assertThat(clause.isIncludingDefaults()).isNull(); + clause.addOption(LikeClause.OptionKind.INDEXES, true); + assertThat(clause.isIncluding(LikeClause.OptionKind.INDEXES)).isTrue(); + assertSqlCanBeParsedAndDeparsed(table.toString()); + } + + @Test + void testLegacyLikeSetters() { + LikeClause clause = new LikeClause(); + clause.setIncludingDefaults(true); + clause.setExcludingIdentity(true); + assertThat(clause.isIncludingDefaults()).isTrue(); + assertThat(clause.isIncludingIdentity()).isFalse(); + clause.setExcludingDefaults(null); + assertThat(clause.isIncludingDefaults()).isNull(); + } + + @Test + void testLegacyLikeTableSetterUpdatesFirstSource() throws JSQLParserException { + CreateTable table = (CreateTable) CCJSqlParserUtil + .parse("CREATE TABLE copy (LIKE source INCLUDING ALL, id integer)"); + table.setLikeTable(new Table("replacement"), false); + assertThat(table.getTableElements(LikeClause.class).get(0).getTable().getName()) + .isEqualTo("replacement"); + assertThat(table.getTrailingLikeTable()).isNull(); + assertSqlCanBeParsedAndDeparsed(table.toString()); + table.setLikeTable(null, false); + assertThat(table.getLikeTable()).isNull(); + assertSqlCanBeParsedAndDeparsed(table.toString()); + } + + @Test + void testTypedTable() throws JSQLParserException { + CreateTable table = (CreateTable) assertSqlCanBeParsedAndDeparsed( + "CREATE TABLE employees OF employee_type (PRIMARY KEY (name), salary WITH OPTIONS DEFAULT 1000)"); + assertThat(table.getOfType().getDataType()).isEqualTo("employee_type"); + assertThat(table.getColumnDefinitions().get(0).isWithOptions()).isTrue(); + assertThat(table.getColumnDefinitions().get(0).getColDataType()).isNull(); + assertThat(table.getTableElements().get(0)).isInstanceOf(Index.class); + } + + @Test + void testIdentityOptions() throws JSQLParserException { + CreateTable table = (CreateTable) assertSqlCanBeParsedAndDeparsed( + "CREATE TABLE counters (id bigint GENERATED BY DEFAULT AS IDENTITY" + + " (START WITH 100 INCREMENT BY -2 NO MINVALUE NO MAXVALUE NO CYCLE CACHE 5), value integer)"); + IdentityDefinition identity = table.getColumnDefinitions().get(0) + .getColumnOptions().get(0).getIdentityDefinition(); + assertThat(identity.getGenerationMode()) + .isEqualTo(IdentityDefinition.GenerationMode.BY_DEFAULT); + assertThat(identity.getParameters()).hasSize(6); + assertThat(identity.getParameters().get(1).getOption()) + .isEqualTo(Sequence.ParameterType.INCREMENT_BY); + assertThat(identity.getParameters().get(1).getValue()).isEqualTo(-2L); + } + + @Test + void testAlterColumnTypeUsing() throws JSQLParserException { + Alter alter = (Alter) assertSqlCanBeParsedAndDeparsed( + "ALTER TABLE contacts ALTER COLUMN id TYPE bigint USING (id::bigint)"); + AlterExpression.ColumnDataType column = + alter.getAlterExpressions().get(0).getColDataTypeList().get(0); + assertThat(column.isWithType()).isTrue(); + assertThat(column.getUsingExpression()).isNotNull(); + column.setUsingExpression(CCJSqlParserUtil.parseExpression("id + 1")); + assertSqlCanBeParsedAndDeparsed(alter.toString()); + } + + @ParameterizedTest + @ValueSource(strings = {"ADD GENERATED ALWAYS AS IDENTITY", + "ADD GENERATED BY DEFAULT AS IDENTITY (START WITH 50)", + "SET GENERATED ALWAYS", "SET GENERATED BY DEFAULT", "SET INCREMENT BY -1", + "SET NO CYCLE", "RESTART", "RESTART WITH 200", "DROP IDENTITY IF EXISTS", + "SET GENERATED ALWAYS SET CACHE 10", "SET CACHE 10 RESTART WITH 50", + "SET NO CYCLE RESTART"}) + void testIdentityAlterations(String action) throws JSQLParserException { + Alter alter = (Alter) assertSqlCanBeParsedAndDeparsed( + "ALTER TABLE counters ALTER COLUMN id " + action); + assertThat(alter.getAlterExpressions().get(0).getColDataTypeList().get(0) + .getIdentityAlterations()) + .isNotEmpty(); + } + + @Test + void testIdentityAlterationKind() throws JSQLParserException { + Alter alter = (Alter) CCJSqlParserUtil.parse( + "ALTER TABLE counters ALTER COLUMN id ADD GENERATED ALWAYS AS IDENTITY"); + IdentityAlteration action = alter.getAlterExpressions().get(0).getColDataTypeList().get(0) + .getIdentityAlterations().get(0); + assertThat(action.getKind()).isEqualTo(IdentityAlteration.Kind.ADD); + assertThat(action.getIdentityDefinition().getGenerationMode()) + .isEqualTo(IdentityDefinition.GenerationMode.ALWAYS); + } + + @Test + void testSequenceOwnership() throws JSQLParserException { + CreateSequence create = (CreateSequence) assertSqlCanBeParsedAndDeparsed( + "CREATE SEQUENCE counter_seq AS bigint INCREMENT BY -1 NO MINVALUE NO MAXVALUE NO CYCLE OWNED BY counters.id"); + assertThat(create.getSequence().getOwnership().getColumn().getColumnName()).isEqualTo("id"); + AlterSequence alter = (AlterSequence) assertSqlCanBeParsedAndDeparsed( + "ALTER SEQUENCE counter_seq OWNED BY NONE"); + assertThat(alter.getSequence().getOwnership().isNone()).isTrue(); + alter.getSequence().setOwnership(SequenceOwnership.ownedBy(new Column("app.counters.id"))); + assertSqlCanBeParsedAndDeparsed(alter.toString()); + } + + @Test + void testSequenceParametersDoNotReusePreviousValues() throws JSQLParserException { + AlterSequence alter = (AlterSequence) assertSqlCanBeParsedAndDeparsed( + "ALTER SEQUENCE counter_seq INCREMENT BY 2 INCREMENT 3 START WITH 7 START 8 RESTART"); + assertThat(alter.getSequence().getParameters().get(1).getOption()) + .isEqualTo(Sequence.ParameterType.INCREMENT); + assertThat(alter.getSequence().getParameters().get(3).getOption()) + .isEqualTo(Sequence.ParameterType.START); + assertThat(alter.getSequence().getParameters().get(4).getValue()).isNull(); + } + + @Test + void testProgrammaticIdentity() throws JSQLParserException { + CreateTable table = + (CreateTable) CCJSqlParserUtil.parse("CREATE TABLE counters (id bigint)"); + IdentityDefinition identity = + new IdentityDefinition(IdentityDefinition.GenerationMode.ALWAYS); + identity.setParameters(Arrays.asList( + new Sequence.Parameter(Sequence.ParameterType.START_WITH).withValue(50L), + new Sequence.Parameter(Sequence.ParameterType.NO_CYCLE))); + table.getColumnDefinitions().get(0) + .setColumnOptions(Collections.singletonList(ColumnOption.identity(identity))); + assertSqlCanBeParsedAndDeparsed(table.toString()); + } +} diff --git a/src/test/java/net/sf/jsqlparser/statement/create/PostgreSqlSchemaTraversalTest.java b/src/test/java/net/sf/jsqlparser/statement/create/PostgreSqlSchemaTraversalTest.java new file mode 100644 index 0000000000..09544ba84f --- /dev/null +++ b/src/test/java/net/sf/jsqlparser/statement/create/PostgreSqlSchemaTraversalTest.java @@ -0,0 +1,133 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.statement.create; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static net.sf.jsqlparser.util.validation.ValidationTestAsserts.validateNoErrors; + +import java.util.ArrayList; +import java.util.List; +import net.sf.jsqlparser.JSQLParserException; +import net.sf.jsqlparser.expression.ExpressionVisitorAdapter; +import net.sf.jsqlparser.parser.CCJSqlParserUtil; +import net.sf.jsqlparser.schema.Column; +import net.sf.jsqlparser.statement.Statement; +import net.sf.jsqlparser.statement.StatementVisitorAdapter; +import net.sf.jsqlparser.statement.StmtFeature; +import net.sf.jsqlparser.statement.select.SelectVisitorAdapter; +import net.sf.jsqlparser.util.TablesNamesFinder; +import net.sf.jsqlparser.util.deparser.ExpressionDeParser; +import net.sf.jsqlparser.util.deparser.SelectDeParser; +import net.sf.jsqlparser.util.deparser.StatementDeParser; +import net.sf.jsqlparser.util.validation.feature.DatabaseType; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +class PostgreSqlSchemaTraversalTest { + @Test + void testTableFinderIncludesLikeAndReferenceSources() throws JSQLParserException { + assertThat(TablesNamesFinder.findTables( + "CREATE TABLE copy (LIKE source INCLUDING ALL, parent_id integer REFERENCES parent (id))")) + .containsExactlyInAnyOrder("copy", "source", "parent"); + assertThat(TablesNamesFinder.findTables("CREATE TABLE employees OF employee_type")) + .containsExactly("employees"); + assertThat(TablesNamesFinder.findTables("ALTER SEQUENCE seq OWNED BY app.counters.id")) + .containsExactly("app.counters"); + assertThat(TablesNamesFinder.findTables("CREATE SEQUENCE seq OWNED BY NONE")).isEmpty(); + } + + @Test + void testExpressionVisitorReceivesContext() throws JSQLParserException { + List columns = new ArrayList<>(); + ExpressionVisitorAdapter expressions = new ExpressionVisitorAdapter() { + @Override + public Void visit(Column column, S context) { + assertThat(context).isEqualTo("context"); + columns.add(column.getColumnName()); + return null; + } + }; + StatementVisitorAdapter visitor = + new StatementVisitorAdapter<>(new SelectVisitorAdapter<>(expressions)); + CCJSqlParserUtil.parse( + "CREATE TABLE bookings (room text, EXCLUDE USING gist ((lower(room)) WITH =) WHERE (room <> ''))") + .accept(visitor, "context"); + CCJSqlParserUtil.parse("ALTER TABLE bookings ALTER COLUMN id TYPE bigint USING id + 1") + .accept(visitor, "context"); + CCJSqlParserUtil.parse("CREATE VIEW v AS SELECT email FROM contacts") + .accept(visitor, "context"); + CCJSqlParserUtil.parse("ALTER SEQUENCE seq OWNED BY bookings.id").accept(visitor, + "context"); + assertThat(columns).containsExactly("room", "room", "id", "email", "id"); + } + + @ParameterizedTest + @ValueSource(strings = { + "CREATE TABLE bookings (room text, EXCLUDE USING gist ((lower(room)) WITH =) WHERE (room <> ''))", + "ALTER TABLE bookings ALTER COLUMN room TYPE text USING lower(room)", + "CREATE VIEW v AS SELECT room FROM bookings", + "CREATE TABLE bookings (room text, CHECK (room <> ''))"}) + void testCustomExpressionDeparser(String sql) throws JSQLParserException { + StringBuilder output = new StringBuilder(); + ExpressionDeParser expressions = new ExpressionDeParser() { + @Override + public StringBuilder visit(Column column, S context) { + return getBuilder().append("new_").append(column.getColumnName()); + } + }; + Statement statement = CCJSqlParserUtil.parse(sql); + statement.accept(new StatementDeParser(expressions, new SelectDeParser(), output)); + assertThat(output.toString()).contains("new_room"); + assertThat(statement.toString()).doesNotContain("new_room"); + assertThat(CCJSqlParserUtil.parse(output.toString()).getClass()) + .isEqualTo(statement.getClass()); + } + + @ParameterizedTest + @ValueSource(strings = {"CREATE VIEW v AS SELECT * FROM source WITH NO DATA", + "CREATE MATERIALIZED VIEW v AS SELECT * FROM source WITH CHECK OPTION", + "CREATE RECURSIVE VIEW v AS SELECT 1", + "CREATE RECURSIVE VIEW v(n) AS SELECT 1 WITH CHECK OPTION", + "CREATE TABLE copy (LIKE source INCLUDING UNKNOWN)", + "CREATE TABLE t (id integer, UNIQUE (id) INITIALLY UNKNOWN)"}) + void testInvalidClauseCombinations(String sql) { + assertThatThrownBy(() -> CCJSqlParserUtil.parse(sql)) + .isInstanceOf(JSQLParserException.class); + } + + @Test + void testMaterializedViewReadEffects() throws JSQLParserException { + Statement populated = + CCJSqlParserUtil.parse("CREATE MATERIALIZED VIEW v AS SELECT * FROM source"); + assertThat(populated.getFeatures().getCertain()) + .contains(StmtFeature.MODIFIES_SCHEMA, StmtFeature.READS_DATA) + .doesNotContain(StmtFeature.RETURNS_RESULT_SET); + Statement empty = CCJSqlParserUtil + .parse("CREATE MATERIALIZED VIEW v AS SELECT * FROM source WITH NO DATA"); + assertThat(empty.getFeatures().getCertain()).contains(StmtFeature.MODIFIES_SCHEMA) + .doesNotContain(StmtFeature.READS_DATA); + Statement view = CCJSqlParserUtil.parse("CREATE VIEW v AS SELECT * FROM source"); + assertThat(view.getFeatures().getCertain()).doesNotContain(StmtFeature.READS_DATA); + } + + @Test + void testPostgreSqlValidation() throws JSQLParserException { + validateNoErrors("CREATE MATERIALIZED VIEW v AS SELECT * FROM source WITH NO DATA", 1, + DatabaseType.POSTGRESQL); + validateNoErrors( + "CREATE TABLE bookings (room integer, EXCLUDE USING gist ((room + 1) WITH =) WHERE (room > 0))", + 1, DatabaseType.POSTGRESQL); + validateNoErrors("ALTER TABLE bookings ALTER COLUMN room TYPE bigint USING room + 1", 1, + DatabaseType.POSTGRESQL); + validateNoErrors("CREATE SEQUENCE seq OWNED BY bookings.room", 1, DatabaseType.POSTGRESQL); + } +} diff --git a/src/test/java/net/sf/jsqlparser/statement/create/PostgreSqlTypeDdlTest.java b/src/test/java/net/sf/jsqlparser/statement/create/PostgreSqlTypeDdlTest.java new file mode 100644 index 0000000000..37e2c26676 --- /dev/null +++ b/src/test/java/net/sf/jsqlparser/statement/create/PostgreSqlTypeDdlTest.java @@ -0,0 +1,215 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.statement.create; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static net.sf.jsqlparser.test.TestUtils.assertSqlCanBeParsedAndDeparsed; +import static net.sf.jsqlparser.util.validation.ValidationTestAsserts.validateNoErrors; + +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import net.sf.jsqlparser.JSQLParserException; +import net.sf.jsqlparser.expression.ExpressionVisitorAdapter; +import net.sf.jsqlparser.expression.StringValue; +import net.sf.jsqlparser.parser.CCJSqlParserUtil; +import net.sf.jsqlparser.schema.Column; +import net.sf.jsqlparser.statement.Statement; +import net.sf.jsqlparser.statement.StatementVisitorAdapter; +import net.sf.jsqlparser.statement.StmtFeature; +import net.sf.jsqlparser.statement.alter.AlterDomain; +import net.sf.jsqlparser.statement.alter.AlterExtension; +import net.sf.jsqlparser.statement.alter.AlterType; +import net.sf.jsqlparser.statement.create.domain.CreateDomain; +import net.sf.jsqlparser.statement.create.domain.DomainConstraint; +import net.sf.jsqlparser.statement.create.extension.CreateExtension; +import net.sf.jsqlparser.statement.create.type.CreateType; +import net.sf.jsqlparser.statement.create.type.TypeAttribute; +import net.sf.jsqlparser.statement.create.type.EnumTypeDefinition; +import net.sf.jsqlparser.statement.create.type.CompositeTypeDefinition; +import net.sf.jsqlparser.statement.create.type.RangeTypeDefinition; +import net.sf.jsqlparser.statement.select.SelectVisitorAdapter; +import net.sf.jsqlparser.util.TablesNamesFinder; +import net.sf.jsqlparser.util.deparser.ExpressionDeParser; +import net.sf.jsqlparser.util.deparser.SelectDeParser; +import net.sf.jsqlparser.util.deparser.StatementDeParser; +import net.sf.jsqlparser.util.validation.feature.DatabaseType; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; +import org.junit.jupiter.params.provider.ValueSource; + +class PostgreSqlTypeDdlTest { + static Stream statements() throws Exception { + try (BufferedReader reader = new BufferedReader(new InputStreamReader( + PostgreSqlTypeDdlTest.class.getResourceAsStream("/postgresql/type-ddl.sql"), + StandardCharsets.UTF_8))) { + return reader.lines() + .filter(line -> !line.trim().isEmpty() && !line.trim().startsWith("--")) + .collect(Collectors.toList()) + .stream(); + } + } + + @ParameterizedTest + @MethodSource("statements") + void testRoundTripAndValidation(String sql) throws Exception { + Statement statement = assertSqlCanBeParsedAndDeparsed(sql); + assertThat(statement).isInstanceOfAny(CreateType.class, AlterType.class, CreateDomain.class, + AlterDomain.class, CreateExtension.class, AlterExtension.class); + assertThat(CCJSqlParserUtil.parse(statement.toString()).toString()) + .isEqualTo(statement.toString()); + assertThat(statement.getFeatures().getCertain()).contains(StmtFeature.MODIFIES_SCHEMA) + .doesNotContain(StmtFeature.READS_DATA, StmtFeature.RETURNS_RESULT_SET); + validateNoErrors(sql, 1, DatabaseType.POSTGRESQL); + } + + @Test + void testTypeDefinitionsAndConstruction() throws Exception { + CreateType enumeration = + (CreateType) CCJSqlParserUtil.parse("CREATE TYPE mood AS ENUM ('sad', 'ok')"); + EnumTypeDefinition definition = (EnumTypeDefinition) enumeration.getDefinition(); + assertThat(definition.getLabels()).extracting(StringValue::getValue).containsExactly("sad", + "ok"); + definition.getLabels().add(new StringValue("happy")); + assertThat(enumeration.toString()) + .isEqualTo("CREATE TYPE mood AS ENUM ('sad', 'ok', 'happy')"); + + CreateType composite = (CreateType) CCJSqlParserUtil + .parse("CREATE TYPE item AS (name text COLLATE \"C\")"); + TypeAttribute attribute = + ((CompositeTypeDefinition) composite.getDefinition()).getAttributes().get(0); + assertThat(attribute.getName()).isEqualTo("name"); + assertThat(attribute.getDataType().getDataType()).isEqualTo("text"); + assertThat(attribute.getCollation()).isEqualTo("\"C\""); + + CreateType range = (CreateType) CCJSqlParserUtil + .parse("CREATE TYPE r AS RANGE (SUBTYPE = numeric, SUBTYPE_DIFF = app.diff)"); + RangeTypeDefinition rangeDefinition = (RangeTypeDefinition) range.getDefinition(); + assertThat(rangeDefinition.getSubtype().getDataType()).isEqualTo("numeric"); + assertThat(rangeDefinition.getOptions().get(1).getKind()) + .isEqualTo(RangeTypeDefinition.OptionKind.SUBTYPE_DIFF); + assertThat(rangeDefinition.getOptions().get(1).getName()).isEqualTo("app.diff"); + + CreateType constructed = new CreateType(); + constructed.setName("new_mood"); + constructed.setDefinition(definition); + assertThat(CCJSqlParserUtil.parse(constructed.toString()).toString()) + .isEqualTo(constructed.toString()); + } + + @Test + void testTypeAlterations() throws Exception { + AlterType alteration = (AlterType) CCJSqlParserUtil + .parse("ALTER TYPE mood ADD VALUE IF NOT EXISTS 'fine' AFTER 'ok'"); + assertThat(alteration.getAction()).isEqualTo(AlterType.Action.ADD_VALUE); + assertThat(alteration.isIfNotExists()).isTrue(); + assertThat(alteration.getPosition()).isEqualTo(AlterType.Position.AFTER); + assertThat(alteration.getValue().getValue()).isEqualTo("fine"); + assertThat(alteration.getNeighborValue().getValue()).isEqualTo("ok"); + AlterType attributes = (AlterType) CCJSqlParserUtil.parse( + "ALTER TYPE item ADD ATTRIBUTE code text, DROP ATTRIBUTE IF EXISTS price CASCADE"); + assertThat(attributes.getAttributeChanges()).hasSize(2); + assertThat(attributes.getAttributeChanges().get(1).isIfExists()).isTrue(); + assertThat(attributes.getAttributeChanges().get(1).getBehavior()) + .isEqualTo(AlterType.Behavior.CASCADE); + } + + @Test + void testDomainConstraintsAndTraversal() throws Exception { + CreateDomain domain = (CreateDomain) CCJSqlParserUtil.parse( + "CREATE DOMAIN positive AS integer DEFAULT 1 CONSTRAINT required NOT NULL CHECK (VALUE > 0)"); + assertThat(domain.isUseAs()).isTrue(); + assertThat(domain.getConstraints()).extracting(DomainConstraint::getKind) + .containsExactly(DomainConstraint.Kind.NOT_NULL, DomainConstraint.Kind.CHECK); + assertThat(domain.getConstraints().get(0).getName()).isEqualTo("required"); + AlterDomain altered = (AlterDomain) CCJSqlParserUtil.parse( + "ALTER DOMAIN positive ADD CONSTRAINT positive_check CHECK (VALUE > 1) NOT VALID"); + assertThat(altered.isNotValid()).isTrue(); + assertThat(altered.getConstraint().getExpression()).isNotNull(); + + List names = new ArrayList<>(); + ExpressionVisitorAdapter expressions = new ExpressionVisitorAdapter() { + @Override + public Void visit(Column column, S context) { + assertThat(context).isEqualTo("domain"); + names.add(column.getColumnName()); + return null; + } + }; + StatementVisitorAdapter visitor = + new StatementVisitorAdapter<>(new SelectVisitorAdapter<>(expressions)); + domain.accept(visitor, "domain"); + altered.accept(visitor, "domain"); + assertThat(names).containsExactly("VALUE", "VALUE"); + assertThat(TablesNamesFinder.findTables(domain.toString())).isEmpty(); + } + + @Test + void testDomainExpressionDeparser() throws Exception { + for (String sql : new String[] {"CREATE DOMAIN positive AS integer CHECK (VALUE > 0)", + "ALTER DOMAIN positive ADD CHECK (VALUE > 0) NOT VALID"}) { + StringBuilder output = new StringBuilder(); + ExpressionDeParser expressions = new ExpressionDeParser() { + @Override + public StringBuilder visit(Column column, S context) { + return getBuilder().append("new_").append(column.getColumnName()); + } + }; + Statement statement = CCJSqlParserUtil.parse(sql); + statement.accept(new StatementDeParser(expressions, new SelectDeParser(), output)); + assertThat(output.toString()).contains("new_VALUE"); + assertThat(statement.toString()).doesNotContain("new_VALUE"); + CCJSqlParserUtil.parse(output.toString()); + } + } + + @Test + void testExtensionOptionsAndRoutineSignatures() throws Exception { + CreateExtension extension = (CreateExtension) CCJSqlParserUtil.parse( + "CREATE EXTENSION IF NOT EXISTS hstore WITH VERSION '1.8' SCHEMA public CASCADE"); + assertThat(extension.isIfNotExists()).isTrue(); + assertThat(extension.isUseWith()).isTrue(); + assertThat(extension.getOptions()).extracting(CreateExtension.Option::getKind) + .containsExactly(CreateExtension.OptionKind.VERSION, + CreateExtension.OptionKind.SCHEMA, CreateExtension.OptionKind.CASCADE); + assertThat(extension.getOptions().get(0).getVersion()).isInstanceOf(StringValue.class); + AlterExtension alteration = (AlterExtension) CCJSqlParserUtil + .parse("ALTER EXTENSION hstore ADD FUNCTION app.f(integer, text[])"); + assertThat(alteration.getMember().getRoutine().getArguments()).hasSize(2); + assertThat(alteration.getMember().getRoutine().getArguments().get(1).getDataType() + .getArrayData()).hasSize(1); + assertThat(TablesNamesFinder.findTables(alteration.toString())).isEmpty(); + assertThat(TablesNamesFinder.findTables("ALTER EXTENSION hstore ADD TABLE app.settings")) + .containsExactly("app.settings"); + assertThat(TablesNamesFinder.findTables("CREATE TYPE item AS (id integer)")).isEmpty(); + assertThat(TablesNamesFinder.findTables("ALTER TYPE item SET SCHEMA app")).isEmpty(); + } + + @ParameterizedTest + @ValueSource(strings = {"CREATE OR REPLACE TYPE t AS ENUM ('a')", + "CREATE OR REPLACE DOMAIN d AS integer", "CREATE OR REPLACE EXTENSION e", + "CREATE TYPE r AS RANGE (UNKNOWN = integer)", + "CREATE TYPE r AS RANGE (CANONICAL = f)", + "ALTER TYPE mood ADD VALUE ok", "ALTER TYPE item ADD ATTRIBUTE x", + "CREATE DOMAIN positive AS integer CHECK ()", + "ALTER DOMAIN positive ADD NULL", "ALTER DOMAIN positive ADD NOT NULL NOT VALID", + "CREATE EXTENSION hstore FROM '1.0'", "ALTER EXTENSION hstore ADD UNKNOWN app.x", + "ALTER EXTENSION hstore ADD FUNCTION f(*)", "ALTER EXTENSION hstore ADD AGGREGATE f"}) + void testInvalidSyntax(String sql) { + assertThatThrownBy(() -> CCJSqlParserUtil.parse(sql)) + .isInstanceOf(JSQLParserException.class); + } +} diff --git a/src/test/java/net/sf/jsqlparser/statement/create/RedshiftCreateViewTest.java b/src/test/java/net/sf/jsqlparser/statement/create/RedshiftCreateViewTest.java new file mode 100644 index 0000000000..d5346390da --- /dev/null +++ b/src/test/java/net/sf/jsqlparser/statement/create/RedshiftCreateViewTest.java @@ -0,0 +1,82 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.statement.create; + +import static org.junit.jupiter.api.Assertions.*; + +import net.sf.jsqlparser.JSQLParserException; +import net.sf.jsqlparser.parser.CCJSqlParserUtil; +import net.sf.jsqlparser.schema.Table; +import net.sf.jsqlparser.statement.create.view.AutoRefreshOption; +import net.sf.jsqlparser.statement.create.view.CreateView; +import net.sf.jsqlparser.statement.select.Select; +import net.sf.jsqlparser.test.TestUtils; +import net.sf.jsqlparser.util.TablesNamesFinder; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +class RedshiftCreateViewTest { + @ParameterizedTest + @ValueSource(strings = {"", " BACKUP YES", " BACKUP NO"}) + void preserveBackupAndOmission(String clause) throws JSQLParserException { + String sql = "CREATE MATERIALIZED VIEW myview" + clause + " AS SELECT * FROM mytab"; + CreateView view = (CreateView) TestUtils.assertSqlCanBeParsedAndDeparsed(sql); + assertEquals(clause.isEmpty() ? null : clause.endsWith("YES"), view.getBackup()); + CreateView reparsed = (CreateView) CCJSqlParserUtil.parse(view.toString()); + assertEquals(view.getBackup(), reparsed.getBackup()); + assertTrue(TablesNamesFinder.findTables(sql).contains("mytab")); + } + + @ParameterizedTest + @ValueSource(strings = {"YES", "NO"}) + void combineWithAutoRefresh(String value) throws JSQLParserException { + CreateView view = (CreateView) TestUtils.assertSqlCanBeParsedAndDeparsed( + "CREATE MATERIALIZED VIEW report.mv BACKUP NO AUTO REFRESH " + value + + " AS SELECT * FROM source"); + assertEquals(Boolean.FALSE, view.getBackup()); + assertEquals(AutoRefreshOption.from(value), view.getAutoRefresh()); + } + + @ParameterizedTest + @ValueSource(strings = { + "CREATE MATERIALIZED VIEW mv BACKUP AS SELECT 1", + "CREATE MATERIALIZED VIEW mv BACKUP TRUE AS SELECT 1", + "CREATE MATERIALIZED VIEW mv BACKUP YES BACKUP NO AS SELECT 1", + "CREATE MATERIALIZED VIEW mv AUTO REFRESH YES BACKUP NO AS SELECT 1", + "CREATE VIEW mv BACKUP NO AS SELECT 1" + }) + void rejectMalformedOptions(String sql) { + assertThrows(JSQLParserException.class, () -> CCJSqlParserUtil.parse(sql, + parser -> parser.withUnsupportedStatements(false))); + } + + @Test + void constructAndClearBackup() throws JSQLParserException { + CreateView view = new CreateView().withMaterialized(true).withBackup(false) + .withView(new Table("mv")) + .withSelect((Select) CCJSqlParserUtil.parse("SELECT * FROM source")); + TestUtils.assertStatementCanBeDeparsedAs(view, + "CREATE MATERIALIZED VIEW mv BACKUP NO AS SELECT * FROM source"); + view.setBackup(null); + TestUtils.assertStatementCanBeDeparsedAs(view, + "CREATE MATERIALIZED VIEW mv AS SELECT * FROM source"); + view.setMaterialized(false); + view.setBackup(true); + assertThrows(IllegalArgumentException.class, view::validateOptions); + } + + @Test + void backupRemainsAnIdentifier() throws JSQLParserException { + TestUtils.assertSqlCanBeParsedAndDeparsed( + "CREATE MATERIALIZED VIEW backup BACKUP YES AS SELECT backup FROM backup"); + TestUtils.assertSqlCanBeParsedAndDeparsed("SELECT backup FROM backup"); + } +} diff --git a/src/test/java/net/sf/jsqlparser/statement/create/SqlServerConstraintClusteringTest.java b/src/test/java/net/sf/jsqlparser/statement/create/SqlServerConstraintClusteringTest.java new file mode 100644 index 0000000000..ab16a8e1a3 --- /dev/null +++ b/src/test/java/net/sf/jsqlparser/statement/create/SqlServerConstraintClusteringTest.java @@ -0,0 +1,130 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.statement.create; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.util.List; +import net.sf.jsqlparser.JSQLParserException; +import net.sf.jsqlparser.parser.AbstractJSqlParser.Dialect; +import net.sf.jsqlparser.parser.CCJSqlParserUtil; +import net.sf.jsqlparser.statement.Statement; +import net.sf.jsqlparser.statement.alter.Alter; +import net.sf.jsqlparser.statement.create.table.CreateTable; +import net.sf.jsqlparser.statement.create.table.Index; +import net.sf.jsqlparser.statement.create.table.NamedConstraint; +import net.sf.jsqlparser.test.TestUtils; +import net.sf.jsqlparser.util.deparser.StatementDeParser; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; + +class SqlServerConstraintClusteringTest { + private static Index constraint(Statement statement) { + return statement instanceof CreateTable + ? ((CreateTable) statement).getIndexes().get(0) + : ((Alter) statement).getAlterExpressions().get(0).getIndex(); + } + + private static Statement parse(String sql) throws JSQLParserException { + return CCJSqlParserUtil.parse(sql, parser -> parser.withDialect(Dialect.SQLSERVER)); + } + + @Test + void parsesSakilaReproducerIssue1589() throws Exception { + String sql = "CREATE TABLE actor (actor_id INT NOT NULL IDENTITY, " + + "first_name VARCHAR (45) NOT NULL, last_name VARCHAR (45) NOT NULL, " + + "last_update DATETIME NOT NULL, PRIMARY KEY NONCLUSTERED (actor_id))"; + Index index = constraint(TestUtils.assertSqlCanBeParsedAndDeparsed(sql, true, + parser -> parser.withDialect(Dialect.SQLSERVER))); + assertEquals(Index.Clustering.NONCLUSTERED, index.getClustering()); + assertNull(((NamedConstraint) index).getIndexName()); + assertEquals(List.of("actor_id"), index.getColumnsNames()); + } + + @ParameterizedTest + @EnumSource(Index.Clustering.class) + void sharesClusteringAcrossCreateAndAlter(Index.Clustering clustering) throws Exception { + for (String type : List.of("PRIMARY KEY", "UNIQUE")) { + for (String name : List.of("", "CONSTRAINT [key name] ")) { + String definition = name + type + " " + clustering + " (id)"; + for (String sql : List.of("CREATE TABLE t (id INT, " + definition + ")", + "ALTER TABLE t ADD " + definition)) { + Statement statement = TestUtils.assertSqlCanBeParsedAndDeparsed(sql, true, + parser -> parser.withDialect(Dialect.SQLSERVER)); + Index index = constraint(statement); + assertEquals(clustering, index.getClustering(), sql); + assertEquals(type, index.getType()); + assertEquals(name.isEmpty() ? null : "[key name]", index.getName()); + if (index instanceof NamedConstraint) { + assertNull(((NamedConstraint) index).getIndexName()); + } + StringBuilder output = new StringBuilder(); + statement.accept(new StatementDeParser(output), null); + assertEquals(clustering, constraint(parse(output.toString())).getClustering()); + assertEquals(clustering, + constraint(parse(statement.toString())).getClustering()); + } + } + } + } + + @Test + void preservesNamesOutsideSqlServerAndQuotedNames() throws Exception { + for (String type : List.of("PRIMARY KEY", "UNIQUE")) { + String sql = "CREATE TABLE t (id INT, " + type + " NONCLUSTERED (id))"; + NamedConstraint defaultIndex = + (NamedConstraint) constraint(CCJSqlParserUtil.parse(sql)); + assertNull(defaultIndex.getClustering()); + assertEquals("NONCLUSTERED", defaultIndex.getIndexName()); + for (Dialect dialect : Dialect.values()) { + if (dialect != Dialect.SQLSERVER) { + NamedConstraint index = (NamedConstraint) constraint(CCJSqlParserUtil.parse(sql, + parser -> parser.withDialect(dialect))); + assertNull(index.getClustering(), dialect.name()); + assertEquals("NONCLUSTERED", index.getIndexName()); + } + } + NamedConstraint quoted = (NamedConstraint) constraint( + parse("CREATE TABLE t (id INT, " + type + " [NONCLUSTERED] (id))")); + assertNull(quoted.getClustering()); + assertEquals("[NONCLUSTERED]", quoted.getIndexName()); + } + } + + @Test + void supportsMutationAndLeavesOmittedOptionUnspecified() throws Exception { + Statement statement = parse("CREATE TABLE t (id INT, PRIMARY KEY (id))"); + Index index = constraint(statement); + assertNull(index.getClustering()); + index.setClustering(Index.Clustering.CLUSTERED); + TestUtils.assertDeparse(statement, "CREATE TABLE t (id INT, PRIMARY KEY CLUSTERED (id))"); + index.setClustering(null); + TestUtils.assertDeparse(statement, "CREATE TABLE t (id INT, PRIMARY KEY (id))"); + NamedConstraint built = new NamedConstraint().withType("UNIQUE").withName("uq_t") + .withClustering(Index.Clustering.NONCLUSTERED).withColumnsNames(List.of("id")); + assertEquals("CONSTRAINT uq_t UNIQUE NONCLUSTERED (id)", built.toString()); + } + + @Test + void keepsFollowingConstraintsAndRejectsDuplicateModifiers() throws Exception { + CreateTable table = (CreateTable) parse("CREATE TABLE t (id INT, other_id INT, " + + "PRIMARY KEY NONCLUSTERED (id), UNIQUE (other_id))"); + assertNull(table.getIndexes().get(1).getClustering()); + for (String sql : List.of( + "CREATE TABLE t (id INT, PRIMARY KEY NONCLUSTERED CLUSTERED (id))", + "ALTER TABLE t ADD CONSTRAINT pk PRIMARY KEY CLUSTERED NONCLUSTERED (id)", + "ALTER TABLE t ADD UNIQUE NONCLUSTERED")) { + assertThrows(JSQLParserException.class, () -> parse(sql)); + } + } +} diff --git a/src/test/java/net/sf/jsqlparser/statement/create/SqlServerCreateTableSeparatorTest.java b/src/test/java/net/sf/jsqlparser/statement/create/SqlServerCreateTableSeparatorTest.java new file mode 100644 index 0000000000..bf850f0209 --- /dev/null +++ b/src/test/java/net/sf/jsqlparser/statement/create/SqlServerCreateTableSeparatorTest.java @@ -0,0 +1,89 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.statement.create; + +import net.sf.jsqlparser.JSQLParserException; +import net.sf.jsqlparser.parser.AbstractJSqlParser.Dialect; +import net.sf.jsqlparser.parser.CCJSqlParserUtil; +import net.sf.jsqlparser.statement.create.table.ColumnDefinition; +import net.sf.jsqlparser.statement.create.table.CreateTable; +import net.sf.jsqlparser.statement.create.table.Index; +import net.sf.jsqlparser.util.deparser.StatementDeParser; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import static org.junit.jupiter.api.Assertions.*; + +class SqlServerCreateTableSeparatorTest { + private CreateTable parse(String sql) throws JSQLParserException { + return (CreateTable) CCJSqlParserUtil.parse(sql, + p -> p.withDialect(Dialect.SQLSERVER).withUnsupportedStatements(false)); + } + + @ParameterizedTest + @ValueSource(strings = {"CREATE TABLE t (id int,)", + "CREATE TABLE t (id int, name varchar(20),)", + "CREATE TABLE t (id int, PRIMARY KEY (id),)", + "CREATE TABLE t (id int, CHECK (id > 0),)", + "CREATE TABLE t (id int, /* last element */ )"}) + void acceptsOneTrailingSeparatorAndNormalizesBothRenderers(String sql) throws Exception { + CreateTable table = parse(sql); + String normalized = table.toString(); + assertFalse(normalized.contains(",)")); + assertEquals(normalized, parse(normalized).toString()); + StringBuilder buffer = new StringBuilder(); + table.accept(new StatementDeParser(buffer), null); + assertEquals(normalized, buffer.toString()); + assertEquals(table.getTableElements().size(), + parse(buffer.toString()).getTableElements().size()); + } + + @Test + void preservesSakilaColumnsAndNonclusteredPrimaryKey() throws Exception { + CreateTable table = parse("CREATE TABLE film_text (film_id INT NOT NULL, " + + "title VARCHAR(255) NOT NULL, description TEXT, PRIMARY KEY NONCLUSTERED (film_id),)"); + assertEquals(4, table.getTableElements().size()); + assertInstanceOf(ColumnDefinition.class, table.getTableElements().get(0)); + Index primaryKey = assertInstanceOf(Index.class, table.getTableElements().get(3)); + assertEquals("PRIMARY KEY", primaryKey.getType()); + assertEquals(Index.Clustering.NONCLUSTERED, primaryKey.getClustering()); + assertEquals(3, table.getColumnDefinitions().size()); + table.getColumnDefinitions().get(0).setColumnName("renamed_id"); + assertTrue(table.toString().contains("renamed_id INT")); + assertEquals(2, CCJSqlParserUtil.parseStatements(table + "; SELECT 1;", + p -> p.withDialect(Dialect.SQLSERVER)).size()); + } + + @ParameterizedTest + @ValueSource(strings = {"CREATE TABLE t (,)", + "CREATE TABLE t (, id int)", "CREATE TABLE t (id int,,)", + "CREATE TABLE t (id int,, name int)", "CREATE TABLE t (id int, PRIMARY KEY,)", + "CREATE TABLE t (id int, PRIMARY KEY (id,))", + "CREATE FUNCTION f() RETURNS @r TABLE (id int,) AS BEGIN RETURN; END"}) + void rejectsMissingElementsAndDoesNotWidenOtherDefinitionLists(String sql) { + assertThrows(JSQLParserException.class, () -> parse(sql)); + } + + @Test + void retainsOtherDialectsAndCreateTableForms() throws Exception { + String trailing = "CREATE TABLE t (id int,)"; + assertThrows(JSQLParserException.class, () -> CCJSqlParserUtil.parse(trailing)); + for (Dialect dialect : new Dialect[] {Dialect.POSTGRESQL, Dialect.MYSQL, Dialect.ORACLE}) { + assertThrows(JSQLParserException.class, + () -> CCJSqlParserUtil.parse(trailing, p -> p.withDialect(dialect))); + } + for (String sql : new String[] {"CREATE TABLE t (id int, PRIMARY KEY (id))", + "CREATE TABLE t (id) AS SELECT 1", "CREATE TABLE t AS SELECT 1", + "CREATE TABLE t ()"}) { + assertEquals(CCJSqlParserUtil.parse(sql).toString(), parse(sql).toString()); + } + } +} diff --git a/src/test/java/net/sf/jsqlparser/statement/create/TableElementMutationTest.java b/src/test/java/net/sf/jsqlparser/statement/create/TableElementMutationTest.java new file mode 100644 index 0000000000..08240a80c2 --- /dev/null +++ b/src/test/java/net/sf/jsqlparser/statement/create/TableElementMutationTest.java @@ -0,0 +1,114 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.statement.create; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import net.sf.jsqlparser.JSQLParserException; +import net.sf.jsqlparser.parser.CCJSqlParserUtil; +import net.sf.jsqlparser.statement.LikeClause; +import net.sf.jsqlparser.statement.create.table.ColDataType; +import net.sf.jsqlparser.statement.create.table.ColumnDefinition; +import net.sf.jsqlparser.statement.create.table.CreateTable; +import net.sf.jsqlparser.util.TablesNamesFinder; +import net.sf.jsqlparser.util.deparser.StatementDeParser; +import org.junit.jupiter.api.Test; + +class TableElementMutationTest { + private static final String SQL = "CREATE TABLE t (LIKE parent INCLUDING DEFAULTS, " + + "a INT, CONSTRAINT c CHECK (a > 0), b INT)"; + + @Test + void legacyListEditsUpdateOrderedElements() throws JSQLParserException { + CreateTable table = parse(); + table.getColumnDefinitions().remove(1); + table.getIndexes().clear(); + table.getColumnDefinitions().add(0, column("first")); + table.getColumnDefinitions().set(1, column("last")); + assertSql(table, "CREATE TABLE t (LIKE parent INCLUDING DEFAULTS, first INT, last INT)"); + assertEquals(3, table.getTableElements().size()); + assertTrue(new TablesNamesFinder().getTables(table).contains("parent")); + } + + @Test + void orderedListEditsRemainVisibleThroughExistingViews() throws JSQLParserException { + CreateTable table = parse(); + List columns = table.getColumnDefinitions(); + table.getTableElements().remove(1); + assertEquals("b", columns.get(0).getColumnName()); + table.getTableElements().add(column("extra")); + assertEquals(2, columns.size()); + columns.clear(); + assertEquals(2, table.getTableElements().size()); + assertEquals(1, table.getIndexes().size()); + assertEquals(1, table.getTableElements(LikeClause.class).size()); + } + + @Test + void replacingColumnsPreservesOtherElementsAndTheirPositions() throws JSQLParserException { + CreateTable table = parse(); + table.setColumnDefinitions(new ArrayList<>(table.getColumnDefinitions())); + assertSql(table, SQL); + table.setColumnDefinitions(Collections.singletonList(column("replacement"))); + assertSql(table, "CREATE TABLE t (LIKE parent INCLUDING DEFAULTS, replacement INT, " + + "CONSTRAINT c CHECK (a > 0))"); + table.setIndexes(null); + table.setColumnDefinitions(null); + assertSql(table, "CREATE TABLE t (LIKE parent INCLUDING DEFAULTS)"); + } + + @Test + void fluentAddersCanPassTheirOwnViewsToSetters() throws JSQLParserException { + CreateTable table = parse(); + table.addColumnDefinitions(column("extra")); + assertEquals(3, table.getColumnDefinitions().size()); + assertEquals(1, table.getTableElements(LikeClause.class).size()); + table.setIndexes(table.getIndexes()); + assertEquals(1, table.getIndexes().size()); + assertSql(table, SQL.substring(0, SQL.length() - 1) + ", extra INT)"); + } + + @Test + void filteredListHonorsIndexBounds() throws JSQLParserException { + List columns = parse().getColumnDefinitions(); + assertThrows(IndexOutOfBoundsException.class, () -> columns.get(-1)); + assertThrows(IndexOutOfBoundsException.class, () -> columns.get(2)); + assertThrows(IndexOutOfBoundsException.class, () -> columns.add(3, column("bad"))); + } + + @Test + void clearingAllElementsKeepsBothRenderersConsistent() throws JSQLParserException { + CreateTable table = parse(); + table.getTableElements().clear(); + assertSql(table, "CREATE TABLE t ()"); + assertTrue(table.getColumnDefinitions().isEmpty()); + assertTrue(table.getIndexes().isEmpty()); + } + + private static CreateTable parse() throws JSQLParserException { + return (CreateTable) CCJSqlParserUtil.parse(SQL); + } + + private static ColumnDefinition column(String name) { + return new ColumnDefinition(name, new ColDataType("INT")); + } + + private static void assertSql(CreateTable table, String expected) { + assertEquals(expected, table.toString()); + StringBuilder buffer = new StringBuilder(); + table.accept(new StatementDeParser(buffer), null); + assertEquals(expected, buffer.toString()); + } +} diff --git a/src/test/java/net/sf/jsqlparser/statement/create/TableOptionMutationTest.java b/src/test/java/net/sf/jsqlparser/statement/create/TableOptionMutationTest.java new file mode 100644 index 0000000000..31a957bf14 --- /dev/null +++ b/src/test/java/net/sf/jsqlparser/statement/create/TableOptionMutationTest.java @@ -0,0 +1,69 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.statement.create; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +import java.util.ArrayList; +import java.util.Arrays; +import net.sf.jsqlparser.JSQLParserException; +import net.sf.jsqlparser.parser.CCJSqlParserUtil; +import net.sf.jsqlparser.statement.create.table.CreateTable; +import net.sf.jsqlparser.statement.create.table.TableOption; +import net.sf.jsqlparser.util.deparser.StatementDeParser; +import org.junit.jupiter.api.Test; + +class TableOptionMutationTest { + @Test + void changingTypedOptionUpdatesLegacyTokensAndBothRenderers() throws JSQLParserException { + CreateTable table = parse(); + TableOption engine = table.getTableOption(TableOption.Kind.ENGINE).orElseThrow(); + engine.setValue("MyISAM"); + engine.setUseEquals(false); + assertEquals(Arrays.asList("ENGINE", "MyISAM"), table.getTableOptionsStrings()); + assertSql(table, "CREATE TABLE t (id INT) ENGINE MyISAM"); + } + + @Test + void addingAndClearingTypedOptionsUpdatesDeparser() throws JSQLParserException { + CreateTable table = parse(); + table.getTableOptions().add(TableOption.raw("ROW_FORMAT", "=", "DYNAMIC")); + assertSql(table, "CREATE TABLE t (id INT) ENGINE = InnoDB ROW_FORMAT = DYNAMIC"); + table.getTableOptions().remove(0); + assertSql(table, "CREATE TABLE t (id INT) ROW_FORMAT = DYNAMIC"); + table.getTableOptions().clear(); + assertSql(table, "CREATE TABLE t (id INT)"); + } + + @Test + void rawOptionsRemainMutableAndCanReplaceTypedOptions() throws JSQLParserException { + CreateTable table = parse(); + table.setTableOptionsStrings(new ArrayList<>(Arrays.asList("ENGINE", "=", "CSV"))); + assertNull(table.getTableOptions()); + table.getTableOptionsStrings().set(2, "MyISAM"); + assertSql(table, "CREATE TABLE t (id INT) ENGINE = MyISAM"); + table.setTableOptions(null); + assertNull(table.getTableOptionsStrings()); + assertSql(table, "CREATE TABLE t (id INT)"); + } + + private static CreateTable parse() throws JSQLParserException { + return (CreateTable) CCJSqlParserUtil.parse("CREATE TABLE t (id INT) ENGINE=InnoDB"); + } + + private static void assertSql(CreateTable table, String expected) throws JSQLParserException { + assertEquals(expected, table.toString()); + StringBuilder buffer = new StringBuilder(); + table.accept(new StatementDeParser(buffer), null); + assertEquals(expected, buffer.toString()); + assertEquals(expected, CCJSqlParserUtil.parse(expected).toString()); + } +} diff --git a/src/test/java/net/sf/jsqlparser/statement/create/database/CreateDatabaseTest.java b/src/test/java/net/sf/jsqlparser/statement/create/database/CreateDatabaseTest.java new file mode 100644 index 0000000000..20f7db0ee4 --- /dev/null +++ b/src/test/java/net/sf/jsqlparser/statement/create/database/CreateDatabaseTest.java @@ -0,0 +1,82 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.statement.create.database; + +import net.sf.jsqlparser.JSQLParserException; +import net.sf.jsqlparser.parser.CCJSqlParserUtil; +import net.sf.jsqlparser.statement.Statement; +import static net.sf.jsqlparser.test.TestUtils.assertDeparse; +import static net.sf.jsqlparser.test.TestUtils.assertSqlCanBeParsedAndDeparsed; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.Arrays; +import org.junit.jupiter.api.Test; + +public class CreateDatabaseTest { + + @Test + void testCreateDatabaseIssue2070() throws JSQLParserException { + String statement = "CREATE DATABASE USERS"; + assertSqlCanBeParsedAndDeparsed(statement); + assertDeparse(new CreateDatabase().withDatabaseName("USERS"), statement); + } + + @Test + void testCreateDatabaseIfNotExists() throws JSQLParserException { + String statement = "CREATE DATABASE IF NOT EXISTS mydb"; + assertSqlCanBeParsedAndDeparsed(statement); + assertDeparse(new CreateDatabase().withDatabaseName("mydb").withIfNotExists(true), + statement); + } + + @Test + void testCreateDatabaseQuotedName() throws JSQLParserException { + assertSqlCanBeParsedAndDeparsed("CREATE DATABASE `my db`"); + assertSqlCanBeParsedAndDeparsed("CREATE DATABASE \"my db\""); + } + + @Test + void testCreateDatabaseWithOptions() throws JSQLParserException { + String statement = + "CREATE DATABASE IF NOT EXISTS mydb DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci"; + assertSqlCanBeParsedAndDeparsed(statement); + + Statement parsed = CCJSqlParserUtil.parse(statement); + CreateDatabase createDatabase = assertInstanceOf(CreateDatabase.class, parsed); + assertEquals("mydb", createDatabase.getDatabaseName()); + assertTrue(createDatabase.hasIfNotExists()); + assertEquals( + Arrays.asList("DEFAULT", "CHARACTER", "SET", "utf8mb4", "COLLATE", + "utf8mb4_unicode_ci"), + createDatabase.getDatabaseOptions()); + DatabaseOption characterSet = + createDatabase.getOption(DatabaseOption.Kind.CHARACTER_SET).orElseThrow(); + assertEquals("utf8mb4", characterSet.getValue()); + assertTrue(characterSet.isUseDefault()); + assertEquals("utf8mb4_unicode_ci", + createDatabase.getOption(DatabaseOption.Kind.COLLATE).orElseThrow().getValue()); + } + + @Test + void testCreateDatabaseAST() throws JSQLParserException { + Statement statement = CCJSqlParserUtil.parse("CREATE DATABASE USERS"); + CreateDatabase createDatabase = assertInstanceOf(CreateDatabase.class, statement); + assertEquals("USERS", createDatabase.getDatabaseName()); + assertFalse(createDatabase.hasIfNotExists()); + + statement = CCJSqlParserUtil.parse("CREATE DATABASE IF NOT EXISTS mydb"); + createDatabase = assertInstanceOf(CreateDatabase.class, statement); + assertEquals("mydb", createDatabase.getDatabaseName()); + assertTrue(createDatabase.hasIfNotExists()); + } +} diff --git a/src/test/java/net/sf/jsqlparser/statement/create/event/EventStatementTest.java b/src/test/java/net/sf/jsqlparser/statement/create/event/EventStatementTest.java new file mode 100644 index 0000000000..95f406388b --- /dev/null +++ b/src/test/java/net/sf/jsqlparser/statement/create/event/EventStatementTest.java @@ -0,0 +1,95 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.statement.create.event; + +import static net.sf.jsqlparser.test.TestUtils.assertSqlCanBeParsedAndDeparsed; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNull; + +import net.sf.jsqlparser.JSQLParserException; +import net.sf.jsqlparser.expression.LongValue; +import net.sf.jsqlparser.statement.truncate.Truncate; +import net.sf.jsqlparser.statement.update.Update; +import org.junit.jupiter.api.Test; + +class EventStatementTest { + + @Test + void parsesCreateEventIntoStructuredProperties() throws JSQLParserException { + String sql = "CREATE EVENT hourly_rollup ON SCHEDULE EVERY 6 HOUR " + + "COMMENT 'Refresh aggregate' DO UPDATE app.metrics SET counter = counter + 1"; + CreateEvent event = + assertInstanceOf(CreateEvent.class, assertSqlCanBeParsedAndDeparsed(sql)); + + assertEquals("hourly_rollup", event.getEvent().getFullyQualifiedName()); + assertEquals(EventSchedule.Type.EVERY, event.getSchedule().getType()); + assertEquals(6, assertInstanceOf(LongValue.class, event.getSchedule().getInterval()) + .getValue()); + assertEquals("HOUR", event.getSchedule().getIntervalUnit()); + assertEquals("Refresh aggregate", event.getComment().getValue()); + assertInstanceOf(Update.class, event.getBody()); + } + + @Test + void parsesRecurringScheduleBounds() throws JSQLParserException { + String sql = "ALTER EVENT hourly_rollup ON SCHEDULE EVERY 12 HOUR " + + "STARTS CURRENT_TIMESTAMP + INTERVAL 4 HOUR " + + "ENDS CURRENT_TIMESTAMP + INTERVAL 2 DAY"; + AlterEvent event = + assertInstanceOf(AlterEvent.class, assertSqlCanBeParsedAndDeparsed(sql)); + + assertEquals(EventSchedule.Type.EVERY, event.getSchedule().getType()); + assertEquals("HOUR", event.getSchedule().getIntervalUnit()); + assertEquals("CURRENT_TIMESTAMP + INTERVAL 4 HOUR", + event.getSchedule().getStarts().toString()); + assertEquals("CURRENT_TIMESTAMP + INTERVAL 2 DAY", + event.getSchedule().getEnds().toString()); + } + + @Test + void parsesOneTimeScheduleAndReplacementBody() throws JSQLParserException { + String sql = "ALTER EVENT hourly_rollup ON SCHEDULE AT CURRENT_TIMESTAMP + INTERVAL 1 DAY " + + "DO TRUNCATE TABLE app.metrics"; + AlterEvent event = + assertInstanceOf(AlterEvent.class, assertSqlCanBeParsedAndDeparsed(sql)); + + assertEquals(EventSchedule.Type.AT, event.getSchedule().getType()); + assertEquals("CURRENT_TIMESTAMP + INTERVAL 1 DAY", + event.getSchedule().getExecuteAt().toString()); + assertInstanceOf(Truncate.class, event.getBody()); + } + + @Test + void parsesStatusAndRenameOperations() throws JSQLParserException { + AlterEvent disabled = assertInstanceOf(AlterEvent.class, + assertSqlCanBeParsedAndDeparsed("ALTER EVENT hourly_rollup DISABLE")); + assertEquals(EventStatus.DISABLE, disabled.getStatus()); + assertNull(disabled.getRenameTo()); + + AlterEvent renamed = assertInstanceOf(AlterEvent.class, + assertSqlCanBeParsedAndDeparsed( + "ALTER EVENT hourly_rollup RENAME TO daily_rollup")); + assertEquals("daily_rollup", renamed.getRenameTo().getFullyQualifiedName()); + } + + @Test + void parsesCompletionPolicy() throws JSQLParserException { + String sql = "CREATE EVENT IF NOT EXISTS one_time_cleanup " + + "ON SCHEDULE AT CURRENT_TIMESTAMP + INTERVAL 1 DAY " + + "ON COMPLETION NOT PRESERVE ENABLE DO TRUNCATE TABLE app.staging"; + CreateEvent event = + assertInstanceOf(CreateEvent.class, assertSqlCanBeParsedAndDeparsed(sql)); + + assertFalse(event.getOnCompletionPreserve()); + assertEquals(EventStatus.ENABLE, event.getStatus()); + } +} diff --git a/src/test/java/net/sf/jsqlparser/statement/create/schema/CreateSchemaTest.java b/src/test/java/net/sf/jsqlparser/statement/create/schema/CreateSchemaTest.java index 51aec7a84a..5d6093f186 100644 --- a/src/test/java/net/sf/jsqlparser/statement/create/schema/CreateSchemaTest.java +++ b/src/test/java/net/sf/jsqlparser/statement/create/schema/CreateSchemaTest.java @@ -9,9 +9,14 @@ */ package net.sf.jsqlparser.statement.create.schema; -import net.sf.jsqlparser.JSQLParserException; import static net.sf.jsqlparser.test.TestUtils.assertDeparse; import static net.sf.jsqlparser.test.TestUtils.assertSqlCanBeParsedAndDeparsed; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import net.sf.jsqlparser.JSQLParserException; +import net.sf.jsqlparser.parser.CCJSqlParserUtil; +import net.sf.jsqlparser.statement.create.database.DatabaseOption; import org.junit.jupiter.api.Test; /** @@ -49,4 +54,27 @@ void testIfNotExistsIssue2061() throws JSQLParserException { String sqlStr = "CREATE SCHEMA IF NOT EXISTS sales_kpi"; assertSqlCanBeParsedAndDeparsed(sqlStr); } + + @Test + void testMySqlCreateSchemaOptions() throws JSQLParserException { + String sql = "CREATE SCHEMA IF NOT EXISTS schema_4 DEFAULT CHARACTER SET = utf8 " + + "COLLATE = utf8_general_ci DEFAULT ENCRYPTION = 'N'"; + CreateSchema schema = (CreateSchema) assertSqlCanBeParsedAndDeparsed(sql); + + DatabaseOption characterSet = schema + .getDatabaseOption(DatabaseOption.Kind.CHARACTER_SET).orElseThrow(); + assertEquals("utf8", characterSet.getValue()); + assertTrue(characterSet.isUseDefault()); + assertTrue(characterSet.isUseEquals()); + + DatabaseOption collate = + schema.getDatabaseOption(DatabaseOption.Kind.COLLATE).orElseThrow(); + assertEquals("utf8_general_ci", collate.getValue()); + + DatabaseOption encryption = + schema.getDatabaseOption(DatabaseOption.Kind.ENCRYPTION).orElseThrow(); + assertEquals("'N'", encryption.getValue()); + + assertEquals(sql, CCJSqlParserUtil.parse(sql).toString()); + } } diff --git a/src/test/java/net/sf/jsqlparser/statement/create/table/ColDataTypeTest.java b/src/test/java/net/sf/jsqlparser/statement/create/table/ColDataTypeTest.java index 5d282fb5ff..9a52246864 100644 --- a/src/test/java/net/sf/jsqlparser/statement/create/table/ColDataTypeTest.java +++ b/src/test/java/net/sf/jsqlparser/statement/create/table/ColDataTypeTest.java @@ -17,6 +17,7 @@ import static net.sf.jsqlparser.test.TestUtils.assertSqlCanBeParsedAndDeparsed; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; class ColDataTypeTest { @Test @@ -97,4 +98,57 @@ void testCastAsIntervalWithQualifierRoundTrip() throws JSQLParserException { "SELECT CAST(col AS INTERVAL DAY TO SECOND)", true); assertSqlCanBeParsedAndDeparsed("SELECT CAST(col AS INTERVAL HOUR)", true); } + + @Test + void testStructuredPrecisionForKeywordTypes() throws JSQLParserException { + ColDataType varchar = parseColumnType("CREATE TABLE t (a VARCHAR(255))"); + assertEquals(255, varchar.getPrecision()); + assertNull(varchar.getScale()); + // the rendered string keeps its historical shape + assertEquals("VARCHAR (255)", varchar.getDataType()); + + ColDataType decimal = parseColumnType("CREATE TABLE t (a DECIMAL(10, 2))"); + assertEquals(10, decimal.getPrecision()); + assertEquals(2, decimal.getScale()); + assertEquals("DECIMAL (10, 2)", decimal.getDataType()); + + ColDataType max = parseColumnType("CREATE TABLE t (a VARCHAR(MAX))"); + assertEquals(Integer.MAX_VALUE, max.getPrecision()); + + ColDataType plain = parseColumnType("CREATE TABLE t (a INT)"); + assertNull(plain.getPrecision()); + assertNull(plain.getScale()); + } + + @Test + void testStructuredPrecisionForIdentifierTypes() throws JSQLParserException { + ColDataType mediumInt = parseColumnType("CREATE TABLE t (a mediumint(9))"); + assertEquals(9, mediumInt.getPrecision()); + assertNull(mediumInt.getScale()); + // the string arguments stay available as before + assertEquals(java.util.List.of("9"), mediumInt.getArgumentsStringList()); + + // non-numeric arguments are not numeric parameters + ColDataType enumType = parseColumnType("CREATE TABLE t (a ENUM('small', 'medium'))"); + assertNull(enumType.getPrecision()); + assertNull(enumType.getScale()); + } + + @Test + void testStructuredPrecisionForZonedTypes() throws JSQLParserException { + ColDataType zoned = parseColumnType("CREATE TABLE t (a TIMESTAMP(3) WITH TIME ZONE)"); + assertEquals(3, zoned.getPrecision()); + assertNull(zoned.getScale()); + // the token image keeps its historical shape + assertEquals("TIMESTAMP(3) WITH TIME ZONE", zoned.getDataType()); + + ColDataType unparameterized = + parseColumnType("CREATE TABLE t (a TIMESTAMP WITH TIME ZONE)"); + assertNull(unparameterized.getPrecision()); + } + + private ColDataType parseColumnType(String sqlStr) throws JSQLParserException { + CreateTable create = (CreateTable) assertSqlCanBeParsedAndDeparsed(sqlStr, true); + return create.getColumnDefinitions().get(0).getColDataType(); + } } diff --git a/src/test/java/net/sf/jsqlparser/statement/create/table/SqlServerXmlTypeTest.java b/src/test/java/net/sf/jsqlparser/statement/create/table/SqlServerXmlTypeTest.java new file mode 100644 index 0000000000..5c065ed802 --- /dev/null +++ b/src/test/java/net/sf/jsqlparser/statement/create/table/SqlServerXmlTypeTest.java @@ -0,0 +1,112 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.statement.create.table; + +import static org.junit.jupiter.api.Assertions.*; + +import java.util.List; +import net.sf.jsqlparser.JSQLParserException; +import net.sf.jsqlparser.parser.AbstractJSqlParser.Dialect; +import net.sf.jsqlparser.parser.CCJSqlParserUtil; +import net.sf.jsqlparser.test.TestUtils; +import net.sf.jsqlparser.util.TablesNamesFinder; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +class SqlServerXmlTypeTest { + @ParameterizedTest + @ValueSource(strings = { + "XML(CONTENT dbo.OrderSchema)", + "XML(DOCUMENT dbo.OrderSchema)", + "XML(dbo.OrderSchema)", + "XML(OrderSchema)", + "[xml](CONTENT [Person].[AdditionalContactInfoSchemaCollection])", + "\"xml\"(DOCUMENT \"Person\".\"Order Schema\")", + "XML(CONTENT)", + "XML(DOCUMENT.Collection)", + "XML([schema.with.dot].[collection.with.dot])" + }) + void roundTripTypedXml(String typeSql) throws JSQLParserException { + CreateTable table = (CreateTable) TestUtils.assertSqlCanBeParsedAndDeparsed( + "CREATE TABLE dbo.t (payload " + typeSql + " NULL)", true, + parser -> parser.withDialect(Dialect.SQLSERVER)); + ColDataType type = table.getColumnDefinitions().get(0).getColDataType(); + assertNotNull(type.getXmlTypeModifier()); + assertNull(type.getArgumentsStringList()); + assertNull(type.getPrecision()); + assertNull(type.getScale()); + assertEquals(List.of("dbo.t"), new TablesNamesFinder().getTableList(table)); + } + + @ParameterizedTest + @ValueSource(strings = { + "ALTER TABLE t ADD payload XML(CONTENT dbo.OrderSchema)", + "ALTER TABLE t ALTER COLUMN payload XML(DOCUMENT dbo.OrderSchema)", + "DECLARE @payload XML(CONTENT dbo.OrderSchema)", + "CREATE TABLE t (untyped XML, n DECIMAL(10, 2), s VARCHAR(20))" + }) + void reuseTypeParsingAcrossStatements(String sql) throws JSQLParserException { + TestUtils.assertSqlCanBeParsedAndDeparsed(sql, true, + parser -> parser.withDialect(Dialect.SQLSERVER)); + } + + @Test + void reproduceIssue1567() throws JSQLParserException { + TestUtils.assertSqlCanBeParsedAndDeparsed("CREATE TABLE [Person].[Person] (" + + "[BusinessEntityID] [int] NOT NULL, [PersonType] [nchar](2) NOT NULL, " + + "[NameStyle] [dbo].[NameStyle] NOT NULL, [Title] [nvarchar](8) NULL, " + + "[FirstName] [dbo].[Name] NOT NULL, [MiddleName] [dbo].[Name] NULL, " + + "[LastName] [dbo].[Name] NOT NULL, [Suffix] [nvarchar](10) NULL, " + + "[EmailPromotion] [int] NOT NULL, " + + "[AdditionalContactInfo] [xml](CONTENT [Person].[AdditionalContactInfoSchemaCollection]) NULL, " + + "[Demographics] [xml](CONTENT [Person].[IndividualSurveySchemaCollection]) NULL, " + + "[rowguid] [uniqueidentifier] ROWGUIDCOL NOT NULL, [ModifiedDate] [datetime] NOT NULL)", + true, parser -> parser.withDialect(Dialect.SQLSERVER)); + } + + @ParameterizedTest + @ValueSource(strings = {"XML()", "XML(10)", "XML(OTHER dbo.c)", + "XML(CONTENT dbo.c, dbo.d)", "XML(CONTENT db.dbo.c)", "XML(CONTENT dbo.c"}) + void rejectInvalidModifier(String typeSql) { + assertThrows(JSQLParserException.class, + () -> CCJSqlParserUtil.parse("CREATE TABLE t (x " + typeSql + ")", + parser -> parser.withDialect(Dialect.SQLSERVER) + .withUnsupportedStatements(false))); + } + + @Test + void structuredValuesAndEquality() { + XmlTypeModifier omitted = new XmlTypeModifier(null, List.of("dbo", "c")); + assertNull(omitted.getKind()); + assertEquals(XmlTypeModifier.Kind.CONTENT, omitted.getEffectiveKind()); + assertThrows(UnsupportedOperationException.class, + () -> omitted.getSchemaCollection().clear()); + ColDataType lower = new ColDataType("xml").withXmlTypeModifier(omitted); + ColDataType upper = new ColDataType("XML").withXmlTypeModifier( + new XmlTypeModifier(null, List.of("dbo", "c"))); + assertEquals(lower, upper); + assertEquals(lower.hashCode(), upper.hashCode()); + assertNotEquals(lower, new ColDataType("xml")); + assertNotEquals(omitted, + new XmlTypeModifier(XmlTypeModifier.Kind.CONTENT, List.of("dbo", "c"))); + assertNotEquals(omitted, new XmlTypeModifier(null, List.of("dbo", "other"))); + lower.setXmlTypeModifier(null); + assertEquals("xml", lower.toString()); + } + + @Test + void caseInsensitiveTypeHashAlsoHandlesUnicode() { + ColDataType dotted = new ColDataType("\u0130"); + ColDataType plain = new ColDataType("i"); + assertEquals(dotted, plain); + assertEquals(dotted.hashCode(), plain.hashCode()); + } +} diff --git a/src/test/java/net/sf/jsqlparser/statement/create/trigger/CreateTriggerTest.java b/src/test/java/net/sf/jsqlparser/statement/create/trigger/CreateTriggerTest.java new file mode 100644 index 0000000000..f199bc293b --- /dev/null +++ b/src/test/java/net/sf/jsqlparser/statement/create/trigger/CreateTriggerTest.java @@ -0,0 +1,74 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.statement.create.trigger; + +import static net.sf.jsqlparser.test.TestUtils.assertSqlCanBeParsedAndDeparsed; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNull; + +import net.sf.jsqlparser.JSQLParserException; +import net.sf.jsqlparser.statement.SetStatement; +import net.sf.jsqlparser.statement.insert.Insert; +import org.junit.jupiter.api.Test; + +class CreateTriggerTest { + + @Test + void parsesTriggerDefinitionAndSetBody() throws JSQLParserException { + String sql = "CREATE TRIGGER normalize_name BEFORE INSERT ON customer " + + "FOR EACH ROW SET NEW.name = UPPER(NEW.name)"; + CreateTrigger trigger = + assertInstanceOf(CreateTrigger.class, assertSqlCanBeParsedAndDeparsed(sql)); + + assertEquals("normalize_name", trigger.getTrigger().getFullyQualifiedName()); + assertEquals(CreateTrigger.Timing.BEFORE, trigger.getTiming()); + assertEquals(CreateTrigger.Event.INSERT, trigger.getEvent()); + assertEquals("customer", trigger.getTable().getFullyQualifiedName()); + assertNull(trigger.getOrder()); + SetStatement body = assertInstanceOf(SetStatement.class, trigger.getBody()); + assertEquals("NEW.name", body.getName().toString()); + assertEquals("UPPER(NEW.name)", body.getExpressions().get(0).toString()); + } + + @Test + void parsesAfterTriggerWithInsertBody() throws JSQLParserException { + String sql = "CREATE TRIGGER record_delete AFTER DELETE ON customer FOR EACH ROW " + + "INSERT INTO audit_log (customer_id) VALUES (OLD.id)"; + CreateTrigger trigger = + assertInstanceOf(CreateTrigger.class, assertSqlCanBeParsedAndDeparsed(sql)); + + assertEquals(CreateTrigger.Timing.AFTER, trigger.getTiming()); + assertEquals(CreateTrigger.Event.DELETE, trigger.getEvent()); + assertInstanceOf(Insert.class, trigger.getBody()); + } + + @Test + void parsesDefinerWithoutReparsingAccountValues() throws JSQLParserException { + String sql = "CREATE DEFINER = 'automation'@'localhost' TRIGGER normalize_name " + + "BEFORE UPDATE ON customer FOR EACH ROW SET NEW.name = UPPER(NEW.name)"; + CreateTrigger trigger = + assertInstanceOf(CreateTrigger.class, assertSqlCanBeParsedAndDeparsed(sql)); + + assertEquals("automation", trigger.getDefiner().getUser().getValue()); + assertEquals("localhost", trigger.getDefiner().getHost().getValue()); + } + + @Test + void parsesTriggerOrdering() throws JSQLParserException { + String sql = "CREATE TRIGGER normalize_name_second BEFORE INSERT ON customer " + + "FOR EACH ROW FOLLOWS normalize_name SET NEW.name = TRIM(NEW.name)"; + CreateTrigger trigger = + assertInstanceOf(CreateTrigger.class, assertSqlCanBeParsedAndDeparsed(sql)); + + assertEquals(CreateTrigger.Order.FOLLOWS, trigger.getOrder()); + assertEquals("normalize_name", trigger.getOtherTrigger().getFullyQualifiedName()); + } +} diff --git a/src/test/java/net/sf/jsqlparser/statement/create/user/CreateUserTest.java b/src/test/java/net/sf/jsqlparser/statement/create/user/CreateUserTest.java new file mode 100644 index 0000000000..d0dcbb1af9 --- /dev/null +++ b/src/test/java/net/sf/jsqlparser/statement/create/user/CreateUserTest.java @@ -0,0 +1,71 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.statement.create.user; + +import static net.sf.jsqlparser.test.TestUtils.assertSqlCanBeParsedAndDeparsed; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import net.sf.jsqlparser.JSQLParserException; +import net.sf.jsqlparser.parser.CCJSqlParserUtil; +import org.junit.jupiter.api.Test; + +class CreateUserTest { + + @Test + void parsesAccountAndPasswordAsStructuredValues() throws JSQLParserException { + String sql = "CREATE USER IF NOT EXISTS 'replicator' IDENTIFIED BY 'replpass'"; + CreateUser createUser = + assertInstanceOf(CreateUser.class, CCJSqlParserUtil.parse(sql)); + + assertTrue(createUser.isIfNotExists()); + assertEquals(1, createUser.getAccounts().size()); + UserAccount account = createUser.getAccounts().get(0); + assertEquals("replicator", account.getUser().getValue()); + assertNull(account.getHost()); + assertEquals(UserAuthentication.Mode.BY_PASSWORD, account.getAuthentication().getMode()); + assertEquals("replpass", account.getAuthentication().getCredential().getValue()); + assertSqlCanBeParsedAndDeparsed(sql); + } + + @Test + void parsesHostAndOptionalAuthentication() throws JSQLParserException { + String sql = "CREATE USER IF NOT EXISTS 'snapshot'@'%'"; + CreateUser createUser = + assertInstanceOf(CreateUser.class, assertSqlCanBeParsedAndDeparsed(sql)); + + UserAccount account = createUser.getAccounts().get(0); + assertEquals("snapshot", account.getUser().getValue()); + assertEquals("%", account.getHost().getValue()); + assertNull(account.getAuthentication()); + } + + @Test + void parsesMultipleAccountsAndAuthenticationPlugin() throws JSQLParserException { + String sql = "CREATE USER alice@localhost IDENTIFIED WITH caching_sha2_password " + + "BY 'secret', 'service'@'10.%' IDENTIFIED BY RANDOM PASSWORD"; + CreateUser createUser = + assertInstanceOf(CreateUser.class, assertSqlCanBeParsedAndDeparsed(sql)); + + assertFalse(createUser.isIfNotExists()); + assertEquals(2, createUser.getAccounts().size()); + UserAuthentication pluginAuthentication = + createUser.getAccounts().get(0).getAuthentication(); + assertEquals(UserAuthentication.Mode.WITH_PLUGIN_BY_PASSWORD, + pluginAuthentication.getMode()); + assertEquals("caching_sha2_password", pluginAuthentication.getPlugin()); + assertEquals("secret", pluginAuthentication.getCredential().getValue()); + assertEquals(UserAuthentication.Mode.BY_RANDOM_PASSWORD, + createUser.getAccounts().get(1).getAuthentication().getMode()); + } +} diff --git a/src/test/java/net/sf/jsqlparser/statement/drop/DropIndexTableTest.java b/src/test/java/net/sf/jsqlparser/statement/drop/DropIndexTableTest.java new file mode 100644 index 0000000000..0319e6de51 --- /dev/null +++ b/src/test/java/net/sf/jsqlparser/statement/drop/DropIndexTableTest.java @@ -0,0 +1,132 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.statement.drop; + +import static org.junit.jupiter.api.Assertions.*; +import java.util.List; +import java.util.Set; +import net.sf.jsqlparser.JSQLParserException; +import net.sf.jsqlparser.parser.AbstractJSqlParser.Dialect; +import net.sf.jsqlparser.parser.CCJSqlParserUtil; +import net.sf.jsqlparser.schema.Table; +import net.sf.jsqlparser.util.TablesNamesFinder; +import net.sf.jsqlparser.util.deparser.DropDeParser; +import net.sf.jsqlparser.util.deparser.ExpressionDeParser; +import net.sf.jsqlparser.util.deparser.SelectDeParser; +import net.sf.jsqlparser.util.deparser.StatementDeParser; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +class DropIndexTableTest { + @ParameterizedTest + @ValueSource(strings = {"t", "app.t", "`t.x`", "`s.p`.`t.x`", "`한글`"}) + void mysqlOwnersAndOptions(String owner) throws Exception { + for (String suffix : List.of("", " ALGORITHM = INPLACE LOCK = NONE", " LOCK SHARED", + " ALGORITHM COPY", " LOCK EXCLUSIVE ALGORITHM DEFAULT", " LOCK DEFAULT")) { + String sql = "DROP INDEX `i.x` ON " + owner + suffix; + for (Drop drop : roundTrip(sql, Dialect.MYSQL)) { + assertEquals("`i.x`", drop.getName().getName()); + assertEquals(owner, drop.getTable().getFullyQualifiedName()); + assertEquals(List.of("ON", owner), drop.getParameters().subList(0, 2)); + assertEquals(Set.of(owner), new TablesNamesFinder().getTables(drop)); + } + } + } + + @ParameterizedTest + @ValueSource(strings = {"[t]", "[dbo].[t]", "[db].[dbo].[t]"}) + void sqlServerOwners(String owner) throws Exception { + for (Drop drop : roundTrip("DROP INDEX IF EXISTS [ix] ON " + owner, Dialect.SQLSERVER)) { + assertEquals(owner, drop.getTable().getFullyQualifiedName()); + assertTrue(drop.isIfExists()); + } + } + + @Test + void mutationAndLegacyParametersStayConsistent() throws Exception { + Drop drop = parse("DROP INDEX i ON t LOCK NONE", Dialect.MYSQL); + drop.getTable().setName("renamed"); + assertEquals(List.of("ON", "renamed", "LOCK", "NONE"), drop.getParameters()); + List snapshot = drop.getParameters(); + snapshot.set(1, "ignored"); + assertEquals("renamed", drop.getTable().getName()); + drop.addParameters("ALGORITHM", "DEFAULT"); + assertEquals("DROP INDEX i ON renamed LOCK NONE ALGORITHM DEFAULT", drop.toString()); + drop.setTable(null); + assertEquals("DROP INDEX i LOCK NONE ALGORITHM DEFAULT", drop.toString()); + drop.withTable(new Table("next")); + drop.setParameters(List.of("ON", "legacy")); + assertNull(drop.getTable()); + assertEquals("DROP INDEX i ON legacy", drop.toString()); + assertThrows(IllegalArgumentException.class, () -> drop.setTable(new Table("t"), 99)); + } + + @Test + void builderAndParameterOrder() { + Drop drop = new Drop().withType("INDEX").withName(new Table("i")) + .addParameters("EXTENSION"); + drop.setTable(new Table("t"), 1); + drop.addParameters(List.of("LOCK", "NONE")); + assertEquals("DROP INDEX i EXTENSION ON t LOCK NONE", drop.toString()); + assertEquals(List.of("EXTENSION", "ON", "t", "LOCK", "NONE"), drop.getParameters()); + } + + @Test + void doesNotInventOwnersOrDiscoverCatalogNamesAsTables() throws Exception { + for (String sql : List.of("DROP INDEX public.i", "DROP SCHEMA s", "DROP SEQUENCE seq", + "DROP FUNCTION f(integer)", "DROP TRIGGER tr")) { + Drop drop = parse(sql, Dialect.POSTGRESQL); + assertNull(drop.getTable()); + assertTrue(new TablesNamesFinder().getTables(drop).isEmpty()); + } + assertEquals(Set.of("a", "b"), new TablesNamesFinder().getTables( + parse("DROP TABLE a, b", Dialect.POSTGRESQL))); + assertEquals(Set.of("v"), new TablesNamesFinder().getTables( + parse("DROP MATERIALIZED VIEW v", Dialect.POSTGRESQL))); + } + + @Test + void customDeparserRewritesOwnerOnly() throws Exception { + Drop drop = parse("DROP INDEX i ON t LOCK NONE", Dialect.MYSQL); + StringBuilder builder = new StringBuilder(); + SelectDeParser tables = new SelectDeParser() { + @Override + public StringBuilder visit(Table table, S context) { + assertEquals("ctx", context); + return getBuilder().append("new_t"); + } + }; + drop.accept(new StatementDeParser(new ExpressionDeParser(), tables, builder), "ctx"); + assertEquals("DROP INDEX i ON new_t LOCK NONE", builder.toString()); + assertEquals("DROP INDEX i ON t LOCK NONE", drop.toString()); + } + + @Test + void malformedOwnersAndStatementBoundaries() throws Exception { + for (String sql : List.of("DROP INDEX i ON", "DROP INDEX i ON t ON u")) { + assertThrows(JSQLParserException.class, () -> parse(sql, Dialect.MYSQL)); + } + assertEquals(2, CCJSqlParserUtil.parseStatements( + "DROP INDEX i ON t; LOCK TABLE u IN SHARE MODE").size()); + } + + private static Drop parse(String sql, Dialect dialect) throws JSQLParserException { + return (Drop) CCJSqlParserUtil.parse(sql, p -> p.withDialect(dialect)); + } + + private static List roundTrip(String sql, Dialect dialect) throws Exception { + Drop drop = parse(sql, dialect); + StringBuilder builder = new StringBuilder(); + new DropDeParser(builder).deParse(drop); + assertEquals(drop.toString(), builder.toString()); + return List.of(drop, parse(drop.toString(), dialect), parse(builder.toString(), dialect)); + } +} diff --git a/src/test/java/net/sf/jsqlparser/statement/drop/DropTest.java b/src/test/java/net/sf/jsqlparser/statement/drop/DropTest.java index a48b2fe7df..a2a25ab6f4 100644 --- a/src/test/java/net/sf/jsqlparser/statement/drop/DropTest.java +++ b/src/test/java/net/sf/jsqlparser/statement/drop/DropTest.java @@ -10,10 +10,14 @@ package net.sf.jsqlparser.statement.drop; import java.io.StringReader; +import java.util.List; +import java.util.stream.Collectors; import net.sf.jsqlparser.JSQLParserException; import net.sf.jsqlparser.parser.CCJSqlParserManager; +import net.sf.jsqlparser.parser.CCJSqlParserUtil; import net.sf.jsqlparser.schema.Table; import net.sf.jsqlparser.statement.Statement; +import net.sf.jsqlparser.statement.Statements; import static net.sf.jsqlparser.test.TestUtils.*; import static org.junit.jupiter.api.Assertions.assertEquals; import org.junit.jupiter.api.Test; @@ -152,4 +156,62 @@ void dropTemporaryTableTestIssue1712() throws JSQLParserException { String sqlStr = "drop temporary table if exists tmp_MwYT8N0z"; assertSqlCanBeParsedAndDeparsed(sqlStr, true); } + + @Test + public void testDropIndexAlgorithmAndLockOptionsIssue2490() throws JSQLParserException { + assertSqlCanBeParsedAndDeparsed("DROP INDEX i15 ON t ALGORITHM = INPLACE LOCK = NONE"); + assertSqlCanBeParsedAndDeparsed("DROP INDEX i16 ON t ALGORITHM INPLACE"); + assertSqlCanBeParsedAndDeparsed("DROP INDEX i17 ON t LOCK = NONE"); + } + + @Test + public void testDropTableFollowedByLockTableIssue2490() throws JSQLParserException { + // LOCK must not be swallowed as a DROP argument when it starts the next statement. + Statements statements = CCJSqlParserUtil.parseStatements( + "DROP TABLE t1; LOCK TABLE t2 IN SHARE MODE;"); + assertEquals(2, statements.size()); + } + + @Test + public void testMySqlDropObjectTypes() throws JSQLParserException { + Drop database = (Drop) assertSqlCanBeParsedAndDeparsed( + "DROP DATABASE IF EXISTS database_1"); + assertEquals(Drop.ObjectType.DATABASE, database.getObjectType()); + + Drop procedure = (Drop) assertSqlCanBeParsedAndDeparsed( + "DROP PROCEDURE IF EXISTS procedure_1"); + assertEquals(Drop.ObjectType.PROCEDURE, procedure.getObjectType()); + + Drop trigger = (Drop) assertSqlCanBeParsedAndDeparsed( + "DROP TRIGGER IF EXISTS schema_1.trigger_1"); + assertEquals(Drop.ObjectType.TRIGGER, trigger.getObjectType()); + assertEquals("schema_1.trigger_1", trigger.getName().getFullyQualifiedName()); + } + + @Test + public void testMySqlDropMultipleTables() throws JSQLParserException { + String sql = "DROP TABLE IF EXISTS table_1, table_2, table_3 RESTRICT"; + Drop drop = (Drop) assertSqlCanBeParsedAndDeparsed(sql); + + assertEquals(List.of("table_1", "table_2", "table_3"), drop.getNames().stream() + .map(Table::getFullyQualifiedName).collect(Collectors.toList())); + assertEquals("table_1", drop.getName().getFullyQualifiedName()); + assertEquals(List.of("RESTRICT"), drop.getParameters()); + + assertDeparse(new Drop().withObjectType(Drop.ObjectType.TABLE).withIfExists(true) + .withNames(List.of(new Table("table_1"), new Table("table_2"), + new Table("table_3"))) + .addParameters("RESTRICT"), sql); + } + + @Test + public void testMySqlDropMultipleViews() throws JSQLParserException { + Drop drop = (Drop) assertSqlCanBeParsedAndDeparsed( + "DROP VIEW IF EXISTS view_1, view_2 CASCADE"); + + assertEquals(Drop.ObjectType.VIEW, drop.getObjectType()); + assertEquals(List.of("view_1", "view_2"), drop.getNames().stream() + .map(Table::getFullyQualifiedName).collect(Collectors.toList())); + assertEquals(List.of("CASCADE"), drop.getParameters()); + } } diff --git a/src/test/java/net/sf/jsqlparser/statement/execute/ExecuteArgumentsTest.java b/src/test/java/net/sf/jsqlparser/statement/execute/ExecuteArgumentsTest.java new file mode 100644 index 0000000000..c303d436f9 --- /dev/null +++ b/src/test/java/net/sf/jsqlparser/statement/execute/ExecuteArgumentsTest.java @@ -0,0 +1,140 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.statement.execute; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import net.sf.jsqlparser.JSQLParserException; +import net.sf.jsqlparser.expression.ExpressionVisitorAdapter; +import net.sf.jsqlparser.expression.JdbcNamedParameter; +import net.sf.jsqlparser.expression.UserVariable; +import net.sf.jsqlparser.expression.operators.relational.ExpressionList; +import net.sf.jsqlparser.parser.CCJSqlParserUtil; +import net.sf.jsqlparser.parser.feature.Feature; +import net.sf.jsqlparser.statement.Statements; +import net.sf.jsqlparser.util.deparser.ExpressionDeParser; +import net.sf.jsqlparser.util.deparser.StatementDeParser; +import net.sf.jsqlparser.util.validation.Validation; +import net.sf.jsqlparser.util.validation.feature.FeaturesAllowed; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +class ExecuteArgumentsTest { + private Execute roundtrip(String sql) throws JSQLParserException { + Execute execute = (Execute) CCJSqlParserUtil.parse(sql, + p -> p.withSquareBracketQuotation(true)); + StringBuilder text = new StringBuilder(); + execute.accept(new StatementDeParser(text), null); + assertThat(text.toString()).isEqualTo(execute.toString()); + assertThat(CCJSqlParserUtil.parse(text.toString(), p -> p.withSquareBracketQuotation(true)) + .toString()).isEqualTo(execute.toString()); + return execute; + } + + @ParameterizedTest + @ValueSource(strings = {"EXEC", "EXECUTE", "CALL"}) + void namedBindsRemainArguments(String command) throws JSQLParserException { + for (String name : List.of("PCK_ACTION_BY", "dbo.p", "db..p", "srv.db.dbo.p", + "\"a.b\".\"p\"", "[dbo].[p]")) { + Execute execute = roundtrip(command + " " + name + " :USER_ID, :GROUP_ID"); + assertThat(execute.getName()).isEqualTo(name); + assertThat(execute.getExprList()).hasSize(2); + assertThat(execute.getExprList().get(0)).isInstanceOf(JdbcNamedParameter.class); + assertThat(((JdbcNamedParameter) execute.getExprList().get(0)).getName()) + .isEqualTo("USER_ID"); + } + } + + @ParameterizedTest + @ValueSource(strings = {"EXECUTE myProc 'foo', @outputVar OUTPUT", + "EXEC p @result = @value OUTPUT, :other", "EXEC p @value OUT", + "EXEC p :value OUTPUT", "EXEC p ? OUTPUT"}) + void outputArgumentsHaveTheirOwnModel(String sql) throws JSQLParserException { + Execute execute = roundtrip(sql); + assertThat(execute.getExprList()).anySatisfy(expression -> { + assertThat(expression).isInstanceOf(ExecuteArgument.class); + assertThat(((ExecuteArgument) expression).isOutput()).isTrue(); + }); + assertThat(Validation.validate(List.of(new FeaturesAllowed(Feature.values())), sql)) + .isEmpty(); + } + + @ParameterizedTest + @ValueSource(strings = {"CALL p ()", "CALL p (:a, :b)", "EXEC p (1 + 2) * 3", + "EXEC p @param = 'foo', @param2 = 'bar'", "CALL dbo.p", "EXEC p -1, NULL"}) + void legacyArgumentFormsStillRoundtrip(String sql) throws JSQLParserException { + roundtrip(sql); + } + + @Test + void preservesFollowingStatementsAndVisitsOutputVariables() throws JSQLParserException { + Statements statements = CCJSqlParserUtil.parseStatements( + "EXEC p @a OUTPUT, @b = @c OUTPUT; SELECT 42;"); + assertThat(statements).hasSize(2); + assertThat(statements.get(1).toString()).isEqualTo("SELECT 42"); + Execute execute = (Execute) statements.get(0); + List variables = new ArrayList<>(); + Object marker = new Object(); + execute.getExprList().accept(new ExpressionVisitorAdapter() { + @Override + public Void visit(UserVariable variable, S context) { + assertThat(context).isSameAs(marker); + variables.add(variable.getName()); + return null; + } + }, marker); + assertThat(variables).containsExactly("a", "b", "c"); + variables.clear(); + execute.getExprList().accept(new net.sf.jsqlparser.util.TablesNamesFinder() { + @Override + public Void visit(UserVariable variable, S context) { + assertThat(context).isSameAs(marker); + variables.add(variable.getName()); + return null; + } + }, marker); + assertThat(variables).containsExactly("a", "b", "c"); + } + + @Test + void customDeparserRetainsTheOutputModifierAndLegacyMutation() throws JSQLParserException { + Execute execute = roundtrip("EXEC p @a OUTPUT"); + StringBuilder text = new StringBuilder(); + ExpressionDeParser expressions = new ExpressionDeParser() { + @Override + public StringBuilder visit(UserVariable variable, S context) { + getBuilder().append('@').append(variable.getName()).append("_rewritten"); + return getBuilder(); + } + }; + expressions.setBuilder(text); + new net.sf.jsqlparser.util.deparser.ExecuteDeParser(expressions, text).deParse(execute); + assertThat(text.toString()).isEqualTo("EXEC p @a_rewritten OUTPUT"); + + ExecuteArgument argument = (ExecuteArgument) execute.getExprList().get(0); + argument.setOutput(false); + argument.setExpression(new JdbcNamedParameter().withName("replacement")); + execute.setExprList(new ExpressionList<>(argument)); + execute.setName(Arrays.asList("db", null, "renamed")); + assertThat(execute.toString()).isEqualTo("EXEC db..renamed :replacement"); + } + + @ParameterizedTest + @ValueSource(strings = {"EXEC p 1 OUTPUT", "EXEC p 'x' OUT", "EXEC p @x = 1 OUTPUT", + "EXEC p @x OUTPUT OUTPUT", "EXEC p :id,"}) + void rejectsMalformedOutputArguments(String sql) { + assertThrows(JSQLParserException.class, () -> CCJSqlParserUtil.parse(sql)); + } +} diff --git a/src/test/java/net/sf/jsqlparser/statement/insert/InsertBulkTest.java b/src/test/java/net/sf/jsqlparser/statement/insert/InsertBulkTest.java new file mode 100644 index 0000000000..bf764fd9f2 --- /dev/null +++ b/src/test/java/net/sf/jsqlparser/statement/insert/InsertBulkTest.java @@ -0,0 +1,151 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.statement.insert; + +import static net.sf.jsqlparser.test.TestUtils.assertSqlCanBeParsedAndDeparsed; +import static org.junit.jupiter.api.Assertions.*; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import net.sf.jsqlparser.JSQLParserException; +import net.sf.jsqlparser.expression.ExpressionVisitorAdapter; +import net.sf.jsqlparser.expression.LongValue; +import net.sf.jsqlparser.parser.AbstractJSqlParser.Dialect; +import net.sf.jsqlparser.parser.CCJSqlParserUtil; +import net.sf.jsqlparser.parser.feature.Feature; +import net.sf.jsqlparser.parser.feature.FeatureConfiguration; +import net.sf.jsqlparser.statement.StatementVisitorAdapter; +import net.sf.jsqlparser.statement.select.SelectVisitorAdapter; +import net.sf.jsqlparser.util.TablesNamesFinder; +import net.sf.jsqlparser.util.deparser.ExpressionDeParser; +import net.sf.jsqlparser.util.deparser.SelectDeParser; +import net.sf.jsqlparser.util.deparser.StatementDeParser; +import net.sf.jsqlparser.util.validation.Validation; +import net.sf.jsqlparser.util.validation.feature.FeaturesAllowed; +import net.sf.jsqlparser.util.validation.feature.SqlServerVersion; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +class InsertBulkTest { + private static InsertBulk parse(String sql) throws Exception { + return (InsertBulk) CCJSqlParserUtil.parse(sql, p -> p.withDialect(Dialect.SQLSERVER)); + } + + @Test + void parsesIssue2033() throws Exception { + String sql = "INSERT BULK tpch.dbo.order_line ([ol_o_id] int, [ol_d_id] tinyint, " + + "[ol_w_id] int, [ol_number] tinyint, [ol_i_id] int, [ol_delivery_d] datetime, " + + "[ol_amount] smallmoney, [ol_supply_w_id] int, [ol_quantity] smallint, " + + "[ol_dist_info] char(24) COLLATE Chinese_PRC_CI_AS) WITH (ROWS_PER_BATCH = 500000)"; + InsertBulk statement = (InsertBulk) assertSqlCanBeParsedAndDeparsed(sql, true, + p -> p.withDialect(Dialect.SQLSERVER)); + assertEquals(10, statement.getColumns().size()); + assertEquals("char(24)", + statement.getColumns().get(9).getColDataType().toString().replace(" ", "")); + assertEquals(List.of("COLLATE", "Chinese_PRC_CI_AS"), + statement.getColumns().get(9).getColumnSpecs()); + assertEquals(InsertBulk.Option.Kind.ROWS_PER_BATCH, + statement.getOptions().get(0).getKind()); + assertEquals("500000", statement.getOptions().get(0).getValue().toString()); + assertEquals(Set.of("tpch.dbo.order_line"), new TablesNamesFinder().getTables(statement)); + assertTrue(statement.getFeatures().modifiesData()); + assertFalse(statement.getFeatures().modifiesSchema()); + assertFalse(statement.getFeatures().returnsResultSet()); + assertEquals(statement.toString(), parse(statement.toString()).toString()); + } + + @ParameterizedTest + @ValueSource(strings = {"", " WITH (TABLOCK)", + " WITH (CHECK_CONSTRAINTS, FIRE_TRIGGERS, KEEP_NULLS, ALLOW_ENCRYPTED_VALUE_MODIFICATIONS)", + " WITH (ORDER ([id] ASC, [name] DESC), ROWS_PER_BATCH = 100)"}) + void supportsDriverOptionsAndColumnTypes(String options) throws Exception { + String sql = "INSERT BULK [db].[dbo].[items] ([id] decimal(18, 2), [name] nvarchar(max))" + + options; + InsertBulk statement = (InsertBulk) assertSqlCanBeParsedAndDeparsed(sql, true, + p -> p.withDialect(Dialect.SQLSERVER)); + assertEquals(statement.toString(), parse(statement.toString()).toString()); + } + + @Test + void exposesMutableOptionsToVisitorsAndDeparsers() throws Exception { + InsertBulk statement = + parse("INSERT BULK t (id int) WITH (ROWS_PER_BATCH = 100, ORDER (id ASC))"); + List values = new ArrayList<>(); + ExpressionVisitorAdapter expressions = new ExpressionVisitorAdapter() { + @Override + public Void visit(LongValue value, S context) { + assertEquals("context", context); + values.add(value.getValue()); + return null; + } + }; + statement.accept(new StatementVisitorAdapter<>(new SelectVisitorAdapter<>(expressions)), + "context"); + assertEquals(List.of(100L), values); + statement.getColumns().get(0).getColDataType().setDataType("bigint"); + statement.getOptions().get(0).setValue(new LongValue(200)); + statement.getOptions().get(1).getOrderByElements().get(0).setAsc(false); + assertEquals("INSERT BULK t (id bigint) WITH (ROWS_PER_BATCH = 200, ORDER (id DESC))", + statement.toString()); + StringBuilder output = new StringBuilder(); + ExpressionDeParser deparser = new ExpressionDeParser() { + @Override + public StringBuilder visit(LongValue value, S context) { + return getBuilder().append(value.getValue() + 1); + } + }; + statement.accept(new StatementDeParser(deparser, new SelectDeParser(), output)); + assertEquals(statement.toString().replace("200", "201"), output.toString()); + assertEquals(output.toString(), parse(output.toString()).toString()); + } + + @Test + void gatesDialectAndRetainsOrdinaryInserts() throws Exception { + String sql = "INSERT BULK t (id int)"; + assertThrows(JSQLParserException.class, () -> CCJSqlParserUtil.parse(sql)); + for (Dialect dialect : Dialect.values()) { + if (dialect != Dialect.SQLSERVER) { + assertThrows(JSQLParserException.class, + () -> CCJSqlParserUtil.parse(sql, p -> p.withDialect(dialect))); + } + } + assertSqlCanBeParsedAndDeparsed("INSERT INTO bulk (id) VALUES (1)", true, + p -> p.withDialect(Dialect.SQLSERVER)); + assertSqlCanBeParsedAndDeparsed("INSERT INTO [bulk] (id) VALUES (1)", true, + p -> p.withDialect(Dialect.SQLSERVER)); + assertEquals(2, CCJSqlParserUtil.parseStatements(sql + "; SELECT 1;", + p -> p.withDialect(Dialect.SQLSERVER)).size()); + } + + @ParameterizedTest + @ValueSource(strings = {"INSERT BULK t ()", "INSERT BULK t (id)", + "INSERT BULK t (id int) VALUES (1)", + "INSERT BULK t (id int) WITH ()", "INSERT BULK t (id int) WITH (UNKNOWN_OPTION)", + "INSERT BULK t (id int) WITH (TABLOCK = 1)", + "INSERT BULK t (id int) WITH (TABLOCK, TABLOCK)", + "INSERT BULK t (id int) WITH (ROWS_PER_BATCH)", + "INSERT BULK t (id int) WITH (ORDER ())"}) + void rejectsMalformedBulkDeclarations(String sql) { + assertThrows(JSQLParserException.class, () -> parse(sql)); + } + + @Test + void validatesBulkCapability() { + FeatureConfiguration config = CCJSqlParserUtil.newParser("SELECT 1") + .withDialect(Dialect.SQLSERVER).getConfiguration(); + assertTrue(new Validation(config, List.of(SqlServerVersion.values()[0]), + "INSERT BULK t (id int)") + .validate().isEmpty()); + assertFalse(new Validation(config, List.of(new FeaturesAllowed(Feature.insert)), + "INSERT BULK t (id int)").validate().isEmpty()); + } +} diff --git a/src/test/java/net/sf/jsqlparser/statement/insert/InsertTest.java b/src/test/java/net/sf/jsqlparser/statement/insert/InsertTest.java index 18a9019e1e..21e2200c16 100644 --- a/src/test/java/net/sf/jsqlparser/statement/insert/InsertTest.java +++ b/src/test/java/net/sf/jsqlparser/statement/insert/InsertTest.java @@ -677,6 +677,118 @@ public void testInsertOnConflictIssue1551() throws JSQLParserException { true); } + @Test + public void testInsertOnConflictAfterJoinIssue2358() throws JSQLParserException { + String sqlStr = "INSERT INTO conf.supply_info (id, deal_id)\n" + + "SELECT uuid_generate_v4(), rep.deal_id\n" + + "FROM conf.reports rep\n" + + "JOIN conf.orders o ON rep.id = o.report_id\n" + + "ON CONFLICT DO NOTHING"; + Insert insert = (Insert) assertSqlCanBeParsedAndDeparsed(sqlStr, true); + PlainSelect plainSelect = insert.getSelect().getPlainSelect(); + assertEquals(1, plainSelect.getJoins().size()); + assertEquals(1, plainSelect.getJoins().get(0).getOnExpressions().size()); + assertEquals(ConflictActionType.DO_NOTHING, + insert.getConflictAction().getConflictActionType()); + assertNull(insert.getConflictTarget()); + + assertSqlCanBeParsedAndDeparsed( + "INSERT INTO conf.supply_info (id, deal_id)\n" + + "SELECT uuid_generate_v4(), rep.deal_id\n" + + "FROM conf.reports rep\n" + + "JOIN conf.orders o ON rep.id = o.report_id\n" + + "ON CONFLICT (id) DO NOTHING", + true); + + assertSqlCanBeParsedAndDeparsed( + "INSERT INTO distributors (did, dname)\n" + + "SELECT did, dname FROM staging\n" + + "JOIN suppliers s ON staging.did = s.did\n" + + "ON CONFLICT ON CONSTRAINT distributors_pkey DO NOTHING", + true); + + assertSqlCanBeParsedAndDeparsed( + "INSERT INTO distributors (did, dname)\n" + + "SELECT did, dname FROM staging\n" + + "JOIN suppliers s ON staging.did = s.did\n" + + "ON CONFLICT (did) DO UPDATE SET dname = EXCLUDED.dname", + true); + + assertSqlCanBeParsedAndDeparsed( + "INSERT INTO distributors (did, dname)\n" + + "SELECT did, dname FROM staging\n" + + "JOIN suppliers s ON staging.did = s.did\n" + + "ON CONFLICT (did) WHERE is_active DO NOTHING", + true); + + assertSqlCanBeParsedAndDeparsed( + "INSERT INTO conf.supply_info (id, deal_id)\n" + + "SELECT uuid_generate_v4(), rep.deal_id\n" + + "FROM conf.reports rep\n" + + "JOIN conf.orders o ON rep.id = o.report_id\n" + + "ON CONFLICT DO UPDATE SET deal_id = excluded.deal_id", + true); + + assertSqlCanBeParsedAndDeparsed( + "INSERT INTO conf.supply_info (id, deal_id)\n" + + "SELECT uuid_generate_v4(), rep.deal_id\n" + + "FROM conf.reports rep\n" + + "JOIN conf.orders o ON rep.id = o.report_id\n" + + "ON CONFLICT (id, deal_id) DO NOTHING", + true); + + assertSqlCanBeParsedAndDeparsed( + "INSERT INTO supply_info (id, deal_id)\n" + + "SELECT rep.deal_id, o.total\n" + + "FROM reports rep\n" + + "JOIN orders o ON rep.id = o.report_id\n" + + "JOIN regions r ON o.region = r.id\n" + + "ON CONFLICT DO NOTHING", + true); + + assertSqlCanBeParsedAndDeparsed( + "INSERT INTO conf.supply_info (id, deal_id)\n" + + "SELECT uuid_generate_v4(), rep.deal_id\n" + + "FROM conf.reports rep\n" + + "JOIN conf.orders o ON rep.id = o.report_id\n" + + "ON CONFLICT (id) WHERE (deal_id IN (1, 2)) DO NOTHING", + true); + } + + @Test + public void testInsertSelectWithTrailingOnExpressionsIssue2358() throws JSQLParserException { + String sqlStr = "INSERT INTO supply_info (id, deal_id)\n" + + "SELECT rep.deal_id, o.total\n" + + "FROM reports rep\n" + + "JOIN orders o ON rep.id = o.report_id\n" + + "ON o.total > 100"; + Insert insert = (Insert) assertSqlCanBeParsedAndDeparsed(sqlStr, true); + PlainSelect plainSelect = insert.getSelect().getPlainSelect(); + assertEquals(1, plainSelect.getJoins().size()); + assertEquals(2, plainSelect.getJoins().get(0).getOnExpressions().size()); + assertNull(insert.getConflictAction()); + assertNull(insert.getConflictTarget()); + + insert = (Insert) assertSqlCanBeParsedAndDeparsed( + "INSERT INTO supply_info (id, deal_id)\n" + + "SELECT rep.deal_id, o.total\n" + + "FROM reports rep\n" + + "JOIN orders o ON rep.id = o.report_id\n" + + "ON conflict", + true); + assertEquals(2, + insert.getSelect().getPlainSelect().getJoins().get(0).getOnExpressions().size()); + + insert = (Insert) assertSqlCanBeParsedAndDeparsed( + "INSERT INTO t (a)\n" + + "SELECT x FROM u\n" + + "JOIN v USING (id)\n" + + "ON CONFLICT DO NOTHING", + true); + assertEquals(ConflictActionType.DO_NOTHING, + insert.getConflictAction().getConflictActionType()); + } + @Test public void insertOnConflictObjectsTest() throws JSQLParserException { String sqlStr = "WITH a ( a, b , c ) \n" + "AS (SELECT 1 , 2 , 3 )\n" @@ -966,6 +1078,58 @@ void testInsertOverwrite() throws JSQLParserException { assertFalse(insert.isOverwrite()); } + @Test + // a dynamic partition column without a value must not inherit the value of a + // preceding static partition column (#2500) + void testInsertMixedStaticAndDynamicPartitions() throws JSQLParserException { + String sqlStr = + "INSERT OVERWRITE TABLE t PARTITION (dtime = '2024-04-03', region) SELECT * FROM a"; + Insert insert = (Insert) assertSqlCanBeParsedAndDeparsed(sqlStr); + assertEquals(2, insert.getPartitions().size()); + assertEquals("dtime", insert.getPartitions().get(0).getColumn().getColumnName()); + assertEquals("'2024-04-03'", insert.getPartitions().get(0).getValue().toString()); + assertEquals("region", insert.getPartitions().get(1).getColumn().getColumnName()); + assertNull(insert.getPartitions().get(1).getValue()); + + sqlStr = + "INSERT OVERWRITE TABLE t PARTITION (region, dtime = '2024-04-03') SELECT * FROM a"; + insert = (Insert) assertSqlCanBeParsedAndDeparsed(sqlStr); + assertEquals(2, insert.getPartitions().size()); + assertEquals("region", insert.getPartitions().get(0).getColumn().getColumnName()); + assertNull(insert.getPartitions().get(0).getValue()); + assertEquals("dtime", insert.getPartitions().get(1).getColumn().getColumnName()); + assertEquals("'2024-04-03'", insert.getPartitions().get(1).getValue().toString()); + + sqlStr = "INSERT OVERWRITE TABLE t PARTITION" + + " (dtime = '2024-04-03', region, dept) SELECT * FROM a"; + insert = (Insert) assertSqlCanBeParsedAndDeparsed(sqlStr); + assertEquals(3, insert.getPartitions().size()); + assertEquals("dtime", insert.getPartitions().get(0).getColumn().getColumnName()); + assertEquals("'2024-04-03'", insert.getPartitions().get(0).getValue().toString()); + assertEquals("region", insert.getPartitions().get(1).getColumn().getColumnName()); + assertNull(insert.getPartitions().get(1).getValue()); + assertEquals("dept", insert.getPartitions().get(2).getColumn().getColumnName()); + assertNull(insert.getPartitions().get(2).getValue()); + + sqlStr = "INSERT OVERWRITE TABLE t PARTITION (a, b = '1', c) SELECT * FROM a"; + insert = (Insert) assertSqlCanBeParsedAndDeparsed(sqlStr); + assertEquals(3, insert.getPartitions().size()); + assertEquals("a", insert.getPartitions().get(0).getColumn().getColumnName()); + assertNull(insert.getPartitions().get(0).getValue()); + assertEquals("b", insert.getPartitions().get(1).getColumn().getColumnName()); + assertEquals("'1'", insert.getPartitions().get(1).getValue().toString()); + assertEquals("c", insert.getPartitions().get(2).getColumn().getColumnName()); + assertNull(insert.getPartitions().get(2).getValue()); + + sqlStr = "INSERT INTO TABLE t PARTITION (dtime = 20240403, region) SELECT * FROM a"; + insert = (Insert) assertSqlCanBeParsedAndDeparsed(sqlStr); + assertEquals(2, insert.getPartitions().size()); + assertEquals("dtime", insert.getPartitions().get(0).getColumn().getColumnName()); + assertEquals("20240403", insert.getPartitions().get(0).getValue().toString()); + assertNull(insert.getPartitions().get(1).getValue()); + assertFalse(insert.isOverwrite()); + } + @ParameterizedTest @ValueSource(strings = { "INSERT INTO mytable (foo) OVERRIDING SYSTEM VALUE VALUES (1)", diff --git a/src/test/java/net/sf/jsqlparser/statement/insert/PostgreSQLConflictTargetTest.java b/src/test/java/net/sf/jsqlparser/statement/insert/PostgreSQLConflictTargetTest.java new file mode 100644 index 0000000000..926f06da1b --- /dev/null +++ b/src/test/java/net/sf/jsqlparser/statement/insert/PostgreSQLConflictTargetTest.java @@ -0,0 +1,177 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.statement.insert; + +import java.util.ArrayList; +import net.sf.jsqlparser.expression.ExpressionVisitorAdapter; +import net.sf.jsqlparser.statement.StatementVisitorAdapter; +import net.sf.jsqlparser.statement.select.SelectVisitorAdapter; +import net.sf.jsqlparser.util.deparser.SelectDeParser; + +import net.sf.jsqlparser.expression.Function; +import net.sf.jsqlparser.parser.CCJSqlParserUtil; +import net.sf.jsqlparser.schema.Column; +import net.sf.jsqlparser.statement.create.table.Index; +import net.sf.jsqlparser.test.TestUtils; +import net.sf.jsqlparser.util.deparser.ExpressionDeParser; +import net.sf.jsqlparser.util.deparser.StatementDeParser; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import java.util.Arrays; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +class PostgreSQLConflictTargetTest { + @ParameterizedTest + @ValueSource(strings = { + "(id, (lower(email)))", + "(id, lower(email))", + "(pg_catalog.lower(email) COLLATE pg_catalog.\"C\" pg_catalog.text_pattern_ops, id)", + "((lower(email)), id, (abs(region))) WHERE active", + "(email COLLATE \"C\" text_pattern_ops, id)", + "((lower(email)) COLLATE pg_catalog.\"C\" pg_catalog.text_pattern_ops)", + "ON CONSTRAINT users_pkey" + }) + void parsesAndReparsesConflictTargets(String target) throws Exception { + String sql = "INSERT INTO users (id, email) VALUES (1, 'a') ON CONFLICT " + + target + " DO UPDATE SET email = excluded.email RETURNING id"; + Insert insert = (Insert) TestUtils.assertSqlCanBeParsedAndDeparsed(sql, true); + assertEquals(insert.toString(), CCJSqlParserUtil.parse(insert.toString()).toString()); + } + + @Test + void preservesFunctionKeyParentheses() throws Exception { + Insert insert = (Insert) TestUtils.assertSqlCanBeParsedAndDeparsed( + "INSERT INTO users VALUES (1) ON CONFLICT (lower(email), (abs(region))) DO NOTHING", + true); + List keys = insert.getConflictTarget().getIndexElements(); + assertInstanceOf(Function.class, keys.get(0).getExpression()); + assertFalse(keys.get(0).isExpressionParenthesized()); + assertTrue(keys.get(1).isExpressionParenthesized()); + } + + @ParameterizedTest + @ValueSource(strings = {"SYSTEM", "USER"}) + void acceptsOverridingWithoutColumnList(String mode) throws Exception { + Insert insert = (Insert) TestUtils.assertSqlCanBeParsedAndDeparsed( + "INSERT INTO users OVERRIDING " + mode + " VALUE VALUES (42, 'a@example.org', 'A')", + true); + assertNull(insert.getTable().getAlias()); + assertEquals(Insert.OverridingMode.valueOf(mode), insert.getOverridingMode()); + TestUtils.assertSqlCanBeParsedAndDeparsed( + "INSERT INTO users overriding VALUES (1)", true); + TestUtils.assertSqlCanBeParsedAndDeparsed( + "INSERT INTO users AS overriding OVERRIDING " + mode + + " VALUE SELECT * FROM source", + true); + } + + @Test + void exposesOrderedTypedKeys() throws Exception { + Insert insert = (Insert) CCJSqlParserUtil.parse("INSERT INTO users VALUES (1) " + + "ON CONFLICT (id, (lower(email)) COLLATE pg_catalog.\"C\" " + + "pg_catalog.text_pattern_ops, region) WHERE active DO NOTHING"); + InsertConflictTarget target = insert.getConflictTarget(); + List keys = target.getIndexElements(); + assertEquals(3, keys.size()); + assertEquals("id", keys.get(0).getColumnName()); + assertInstanceOf(Function.class, keys.get(1).getExpression()); + assertEquals("pg_catalog.\"C\"", keys.get(1).getCollation()); + assertEquals("pg_catalog.text_pattern_ops", keys.get(1).getOperatorClass()); + assertEquals(Arrays.asList("id", "region"), target.getIndexColumnNames()); + assertNotNull(target.getWhereExpression()); + } + + @Test + void retainsMutableLegacyColumnViewAndReplacementMethods() throws Exception { + InsertConflictTarget target = new InsertConflictTarget("id", null, null, null); + List names = target.getIndexColumnNames(); + names.add("region"); + names.set(0, "tenant"); + names.remove(1); + assertEquals(" (tenant)", target.toString()); + target.setIndexExpression(CCJSqlParserUtil.parseExpression("lower(email)")); + assertTrue(names.isEmpty()); + assertNotNull(target.getIndexExpression()); + target.withIndexColumnName("id").addAllIndexColumnNames(Arrays.asList("region", "tenant")); + assertNull(target.getIndexExpression()); + assertEquals(Arrays.asList("id", "region", "tenant"), names); + names.clear(); + assertTrue(target.getIndexElements().isEmpty()); + } + + @Test + void bulkColumnAdditionsSnapshotTheMutableView() { + InsertConflictTarget target = new InsertConflictTarget("id", null, null, null); + List names = target.getIndexColumnNames(); + assertTrue(target.addAllIndexColumnNames(names)); + assertEquals(List.of("id", "id"), names); + assertTrue(names.addAll(names)); + assertEquals(4, names.size()); + assertTrue(names.addAll(1, names.subList(0, 2))); + assertEquals(6, names.size()); + assertFalse(names.addAll(List.of())); + } + + @Test + void statementVisitorTraversesTargetExpressions() throws Exception { + Insert insert = (Insert) CCJSqlParserUtil.parse("INSERT INTO users VALUES (1) " + + "ON CONFLICT ((lower(email))) WHERE active DO NOTHING"); + List columns = new ArrayList<>(); + ExpressionVisitorAdapter expressions = + new ExpressionVisitorAdapter() { + @Override + public Void visit(Column column, S context) { + columns.add(column.getColumnName()); + return null; + } + }; + insert.accept(new StatementVisitorAdapter( + new SelectVisitorAdapter<>(expressions)), null); + assertEquals(List.of("email", "active"), columns); + } + + @Test + void deparserVisitsConflictExpressionsAndPredicate() throws Exception { + Insert insert = (Insert) CCJSqlParserUtil.parse("INSERT INTO users VALUES (1) " + + "ON CONFLICT ((lower(email))) WHERE active DO NOTHING"); + StringBuilder sql = new StringBuilder(); + ExpressionDeParser expressions = new ExpressionDeParser() { + @Override + public StringBuilder visit(Column column, S context) { + getBuilder().append("x_").append(column.getColumnName()); + return getBuilder(); + } + }; + insert.accept(new StatementDeParser(expressions, + new SelectDeParser(), sql), null); + assertTrue(sql.toString().contains("lower(x_email)"), sql.toString()); + assertTrue(sql.toString().contains("WHERE x_active"), sql.toString()); + } + + @ParameterizedTest + @ValueSource(strings = {"VALUES (1)", "SELECT id FROM source"}) + void distinguishesOverridingModes(String source) throws Exception { + for (Insert.OverridingMode mode : Arrays.asList(Insert.OverridingMode.SYSTEM, + Insert.OverridingMode.USER)) { + Insert insert = (Insert) TestUtils.assertSqlCanBeParsedAndDeparsed( + "INSERT INTO users (id) OVERRIDING " + mode + " VALUE " + source, true); + assertEquals(mode, insert.getOverridingMode()); + assertTrue(insert.isOverriding()); + insert.setOverriding(false); + assertEquals(Insert.OverridingMode.NONE, insert.getOverridingMode()); + insert.setOverriding(true); + assertEquals(Insert.OverridingMode.SYSTEM, insert.getOverridingMode()); + } + } +} diff --git a/src/test/java/net/sf/jsqlparser/statement/merge/MergeTest.java b/src/test/java/net/sf/jsqlparser/statement/merge/MergeTest.java index 27c8c4da0c..87d1d14489 100644 --- a/src/test/java/net/sf/jsqlparser/statement/merge/MergeTest.java +++ b/src/test/java/net/sf/jsqlparser/statement/merge/MergeTest.java @@ -25,6 +25,7 @@ import static net.sf.jsqlparser.test.TestUtils.assertOracleHintExists; import static net.sf.jsqlparser.test.TestUtils.assertSqlCanBeParsedAndDeparsed; import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.Mockito.mock; /** @@ -363,4 +364,36 @@ private static Stream deriveStandardClausesFromOperationsCases() { Arguments.of(Arrays.asList(insert1, insert2, update1, update2, delete1), update1, insert1, true)); } + + @Test + void testMergeRejectsInvalidWhenNotMatchedClausePairings() { + // WHEN MATCHED rows exist in the target: INSERT is not legal + assertThrows(JSQLParserException.class, () -> CCJSqlParserUtil.parse( + "MERGE INTO t USING s ON t.id = s.id WHEN MATCHED THEN INSERT (a) VALUES (1)")); + + // WHEN NOT MATCHED [BY TARGET] rows do not exist in the target: only INSERT is legal + assertInvalidMergePairing( + "MERGE INTO t USING s ON t.id = s.id WHEN NOT MATCHED THEN UPDATE SET a = 1"); + assertInvalidMergePairing( + "MERGE INTO t USING s ON t.id = s.id WHEN NOT MATCHED THEN DELETE"); + assertInvalidMergePairing( + "MERGE INTO t USING s ON t.id = s.id WHEN NOT MATCHED BY TARGET THEN UPDATE SET a = 1"); + assertInvalidMergePairing( + "MERGE INTO t USING s ON t.id = s.id WHEN NOT MATCHED BY TARGET THEN DELETE"); + + // WHEN NOT MATCHED BY SOURCE rows exist in the target: INSERT is not legal + assertInvalidMergePairing( + "MERGE INTO t USING s ON t.id = s.id WHEN NOT MATCHED BY SOURCE THEN INSERT (a) VALUES (1)"); + } + + private static void assertInvalidMergePairing(String sql) { + JSQLParserException exception = assertThrows(JSQLParserException.class, + () -> CCJSqlParserUtil.parse(sql)); + Throwable cause = exception; + while (cause.getCause() != null) { + cause = cause.getCause(); + } + assertThat(cause).isInstanceOf(IllegalArgumentException.class); + assertThat(cause.getMessage()).contains("WHEN NOT MATCHED"); + } } diff --git a/src/test/java/net/sf/jsqlparser/statement/merge/PostgreSQLMergeReturningTest.java b/src/test/java/net/sf/jsqlparser/statement/merge/PostgreSQLMergeReturningTest.java new file mode 100644 index 0000000000..d1937bcf74 --- /dev/null +++ b/src/test/java/net/sf/jsqlparser/statement/merge/PostgreSQLMergeReturningTest.java @@ -0,0 +1,78 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.statement.merge; + +import java.util.List; +import java.util.ArrayList; +import net.sf.jsqlparser.expression.ExpressionVisitorAdapter; +import net.sf.jsqlparser.expression.Function; +import net.sf.jsqlparser.statement.StatementVisitorAdapter; +import net.sf.jsqlparser.statement.select.SelectVisitorAdapter; + +import net.sf.jsqlparser.parser.CCJSqlParserUtil; +import net.sf.jsqlparser.schema.Column; +import net.sf.jsqlparser.statement.ReturningReferenceType; +import net.sf.jsqlparser.test.TestUtils; +import net.sf.jsqlparser.util.TablesNamesFinder; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import static org.junit.jupiter.api.Assertions.*; + +class PostgreSQLMergeReturningTest { + private static final String MERGE = "MERGE INTO products p USING incoming i ON p.id = i.id " + + "WHEN MATCHED THEN UPDATE SET price = i.price " + + "WHEN NOT MATCHED THEN INSERT (id, price) VALUES (i.id, i.price) "; + + @ParameterizedTest + @ValueSource(strings = {"RETURNING *", "RETURNING merge_action(), p.id, old.price, new.price", + "RETURNING WITH (OLD AS o, NEW AS n) o.price, n.price, n.*"}) + void parsesAndReparsesReturning(String returning) throws Exception { + Merge merge = (Merge) TestUtils.assertSqlCanBeParsedAndDeparsed(MERGE + returning, true); + assertNotNull(merge.getReturningClause()); + assertEquals(merge.toString(), CCJSqlParserUtil.parse(merge.toString()).toString()); + } + + @Test + void normalizesOldAndNewReferences() throws Exception { + Merge merge = (Merge) CCJSqlParserUtil.parse(MERGE + + "RETURNING WITH (OLD AS o, NEW AS n) o.price, n.price"); + Column oldPrice = merge.getReturningClause().get(0).getExpression(Column.class); + assertEquals(ReturningReferenceType.OLD, oldPrice.getReturningReferenceType()); + assertNull(oldPrice.getTable()); + assertEquals(ReturningReferenceType.NEW, + merge.getReturningClause().get(1).getExpression(Column.class) + .getReturningReferenceType()); + } + + @Test + void statementVisitorTraversesReturningExpressions() throws Exception { + Merge merge = (Merge) CCJSqlParserUtil.parse(MERGE + "RETURNING merge_action()"); + List functions = new ArrayList<>(); + ExpressionVisitorAdapter expressions = + new ExpressionVisitorAdapter() { + @Override + public Void visit(Function function, S context) { + functions.add(function.getName()); + return super.visit(function, context); + } + }; + merge.accept(new StatementVisitorAdapter( + new SelectVisitorAdapter<>(expressions)), null); + assertEquals(List.of("merge_action"), functions); + } + + @Test + void discoversTablesInReturningSubquery() throws Exception { + assertTrue(TablesNamesFinder.findTables(MERGE + + "RETURNING (SELECT max(price) FROM history)").contains("history")); + } +} diff --git a/src/test/java/net/sf/jsqlparser/statement/piped/PipeExpressionDeParserTest.java b/src/test/java/net/sf/jsqlparser/statement/piped/PipeExpressionDeParserTest.java new file mode 100644 index 0000000000..717a303595 --- /dev/null +++ b/src/test/java/net/sf/jsqlparser/statement/piped/PipeExpressionDeParserTest.java @@ -0,0 +1,73 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2025 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.statement.piped; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import net.sf.jsqlparser.expression.LongValue; +import net.sf.jsqlparser.expression.StringValue; +import net.sf.jsqlparser.parser.CCJSqlParserUtil; +import net.sf.jsqlparser.util.deparser.ExpressionDeParser; +import net.sf.jsqlparser.util.deparser.SelectDeParser; +import net.sf.jsqlparser.util.deparser.StatementDeParser; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +class PipeExpressionDeParserTest { + @Test + void customPrinterVisitsEveryValueOnceAcrossThePipeline() throws Exception { + String sql = "FROM t |> SELECT 1 AS a, 'secret' AS b |> EXTEND a + 2 AS c " + + "|> SET a = 3, b = 'hidden' |> LIMIT 4 OFFSET 5"; + var statement = CCJSqlParserUtil.parse(sql); + String original = statement.toString(); + List visited = new ArrayList<>(); + List strings = new ArrayList<>(); + StringBuilder output = new StringBuilder(); + ExpressionDeParser expressions = new ExpressionDeParser() { + @Override + public StringBuilder visit(LongValue value, S context) { + visited.add(value.getValue()); + return getBuilder().append(value.getValue() + 100); + } + + @Override + public StringBuilder visit(StringValue value, S context) { + strings.add(value.getValue()); + return getBuilder().append("'masked'"); + } + }; + statement.accept(new StatementDeParser(expressions, new SelectDeParser(), output), null); + assertEquals(Arrays.asList(1L, 2L, 3L, 4L, 5L), visited); + assertEquals(Arrays.asList("secret", "hidden"), strings); + assertEquals("FROM t\n|> SELECT 101 AS a, 'masked' AS b\n" + + "|> EXTEND a + 102 AS c\n|> SET a = 103, b = 'masked'\n" + + "|> LIMIT 104 OFFSET 105", output.toString()); + assertEquals(original, statement.toString()); + assertEquals(output.toString(), CCJSqlParserUtil.parse(output.toString()).toString()); + } + + @ParameterizedTest + @ValueSource(strings = {"FROM t |> SELECT DISTINCT a + 1 AS b, a * 2 AS c", + "FROM t |> EXTEND COALESCE(a, 1) AS b", + "FROM t |> SET a = 1, b = (SELECT 2)", + "FROM t |> SET (a, b) = (1, 2)", + "FROM t |> LIMIT 1", "FROM t |> LIMIT 1 OFFSET 2"}) + void defaultPrinterPreservesAliasesSubqueriesAndAssignmentBrackets(String sql) + throws Exception { + var statement = CCJSqlParserUtil.parse(sql); + StringBuilder output = new StringBuilder(); + statement.accept(new StatementDeParser(output), null); + assertEquals(statement.toString(), output.toString()); + assertEquals(statement.toString(), CCJSqlParserUtil.parse(output.toString()).toString()); + } +} diff --git a/src/test/java/net/sf/jsqlparser/statement/select/AsOfJoinTest.java b/src/test/java/net/sf/jsqlparser/statement/select/AsOfJoinTest.java new file mode 100644 index 0000000000..1d327ee0cf --- /dev/null +++ b/src/test/java/net/sf/jsqlparser/statement/select/AsOfJoinTest.java @@ -0,0 +1,114 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.statement.select; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import java.util.Set; +import net.sf.jsqlparser.expression.LongValue; +import net.sf.jsqlparser.parser.CCJSqlParserUtil; +import net.sf.jsqlparser.parser.feature.Feature; +import net.sf.jsqlparser.test.TestUtils; +import net.sf.jsqlparser.util.TablesNamesFinder; +import net.sf.jsqlparser.util.deparser.ExpressionDeParser; +import net.sf.jsqlparser.util.deparser.SelectDeParser; +import net.sf.jsqlparser.util.validation.ValidationTestAsserts; +import net.sf.jsqlparser.util.validation.feature.FeaturesAllowed; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +class AsOfJoinTest extends ValidationTestAsserts { + @ParameterizedTest + @ValueSource(strings = {"ASOF JOIN", "ASOF INNER JOIN", "ASOF LEFT JOIN", + "ASOF LEFT OUTER JOIN", "ASOF RIGHT JOIN", "ASOF FULL OUTER JOIN"}) + void preservesAsOfAndOrdinaryJoinQualifiers(String joinType) throws Exception { + String sql = "SELECT * FROM trades t " + joinType + + " prices p ON t.symbol = p.symbol AND t.ts >= p.ts"; + PlainSelect select = (PlainSelect) TestUtils.assertSqlCanBeParsedAndDeparsed(sql); + Join join = select.getJoins().get(0); + assertTrue(join.isAsOf()); + assertEquals(joinType.contains("LEFT"), join.isLeft()); + assertEquals(joinType.contains("OUTER"), join.isOuter()); + assertTrue(((PlainSelect) CCJSqlParserUtil.parse(select.toString())).getJoins().get(0) + .isAsOf()); + assertEquals(Set.of("trades", "prices"), TablesNamesFinder.findTables(sql)); + } + + @ParameterizedTest + @ValueSource(strings = { + "SELECT * FROM trades ASOF JOIN prices USING (symbol, ts)", + "SELECT * FROM trades ASOF /* nearest */ LEFT JOIN prices USING (symbol, ts)", + "SELECT * FROM (SELECT * FROM trades) ASOF JOIN prices ON trades.ts >= prices.ts", + "SELECT * FROM trades t ASOF JOIN prices p ON t.ts >= p.ts JOIN symbols s ON s.id = t.id", + "FROM trades t |> ASOF JOIN prices p ON t.ts >= p.ts |> SELECT *"}) + void worksWithoutAliasesAndWithinExistingJoinPaths(String sql) throws Exception { + TestUtils.assertSqlCanBeParsedAndDeparsed(sql, true); + } + + @Test + void keepsUsingColumnsStructuredAndAllowsChangingTheJoinKind() throws Exception { + PlainSelect select = (PlainSelect) CCJSqlParserUtil.parse( + "SELECT * FROM trades ASOF LEFT JOIN prices USING (symbol, ts)"); + Join join = select.getJoins().get(0); + assertEquals("symbol", join.getUsingColumns().get(0).getColumnName()); + assertEquals("ts", join.getUsingColumns().get(1).getColumnName()); + join.setAsOf(false); + assertEquals("SELECT * FROM trades LEFT JOIN prices USING (symbol, ts)", select.toString()); + assertTrue(join.withAsOf(true).isAsOf()); + } + + @Test + void visitsPredicatesThroughTheConfiguredDeparser() throws Exception { + PlainSelect select = (PlainSelect) CCJSqlParserUtil.parse( + "SELECT * FROM trades t ASOF LEFT JOIN prices p ON t.ts >= p.ts + 1"); + StringBuilder output = new StringBuilder(); + int[] visits = {0}; + ExpressionDeParser expressions = new ExpressionDeParser(null, output) { + @Override + public StringBuilder visit(LongValue value, S context) { + visits[0]++; + return getBuilder().append(value.getValue() + 100); + } + }; + SelectDeParser deparser = new SelectDeParser(expressions, output); + expressions.setSelectVisitor(deparser); + select.accept((SelectVisitor) deparser, null); + assertEquals("SELECT * FROM trades t ASOF LEFT JOIN prices p ON t.ts >= p.ts + 101", + output.toString()); + assertEquals(1, visits[0]); + assertTrue(select.toString().endsWith("p.ts + 1")); + } + + @ParameterizedTest + @ValueSource(strings = {"SELECT asof FROM trades", "SELECT * FROM asof", + "SELECT * FROM trades asof WHERE asof.ts > 0", + "SELECT * FROM trades AS asof JOIN prices p ON asof.ts = p.ts", + "SELECT * FROM trades \"ASOF\" JOIN prices p ON \"ASOF\".ts = p.ts"}) + void keepsAsOfUsableAsAnIdentifier(String sql) throws Exception { + PlainSelect select = (PlainSelect) TestUtils.assertSqlCanBeParsedAndDeparsed(sql); + if (select.getJoins() != null) { + assertFalse(select.getJoins().get(0).isAsOf()); + } + } + + @Test + void validatesTheAsOfFeatureAndPredicateExpressions() throws Exception { + String sql = "SELECT * FROM trades t ASOF JOIN prices p ON t.ts >= p.ts"; + validateNoErrors(sql, 1, FeaturesAllowed.SELECT); + validateNotAllowed(sql, 1, 1, + new FeaturesAllowed().add(FeaturesAllowed.SELECT).remove(Feature.joinAsOf), + Feature.joinAsOf); + validateNotAllowed(sql + " + ?", 1, 1, + new FeaturesAllowed().add(FeaturesAllowed.SELECT).remove(Feature.jdbcParameter), + Feature.jdbcParameter); + } +} diff --git a/src/test/java/net/sf/jsqlparser/statement/select/BigQueryTest.java b/src/test/java/net/sf/jsqlparser/statement/select/BigQueryTest.java index 81a3217844..33a74b2cc0 100644 --- a/src/test/java/net/sf/jsqlparser/statement/select/BigQueryTest.java +++ b/src/test/java/net/sf/jsqlparser/statement/select/BigQueryTest.java @@ -10,6 +10,12 @@ package net.sf.jsqlparser.statement.select; import net.sf.jsqlparser.JSQLParserException; +import net.sf.jsqlparser.expression.ArrayExpression; +import net.sf.jsqlparser.statement.AssertStatement; +import net.sf.jsqlparser.statement.create.table.CreateTable; +import net.sf.jsqlparser.statement.export.ExportDataStatement; +import net.sf.jsqlparser.statement.load.LoadDataStatement; +import net.sf.jsqlparser.statement.create.table.TablePartitioning; import net.sf.jsqlparser.test.TestUtils; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Disabled; @@ -110,4 +116,233 @@ void testTimeSeriesFunction() throws JSQLParserException { TableFunction function = select.getFromItem(TableFunction.class); Assertions.assertEquals("TABLE", function.getFunction().getExtraKeyword()); } + + @Test + void testArrayOffsetAccessor() throws JSQLParserException { + String sqlStr = "SELECT arr[OFFSET(0)] FROM t"; + PlainSelect select = (PlainSelect) TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true); + ArrayExpression arrayExpression = + (ArrayExpression) select.getSelectItem(0).getExpression(); + + Assertions.assertEquals("OFFSET(0)", arrayExpression.getIndexExpression().toString()); + } + + @Test + void testArraySafeOffsetAndOrdinalAccessors() throws JSQLParserException { + String sqlStr = "SELECT arr[ORDINAL(1)], arr[SAFE_OFFSET(2)], arr[SAFE_ORDINAL(3)] FROM t"; + TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true); + } + + @Test + void testOffsetClauseStillParses() throws JSQLParserException { + String sqlStr = "SELECT a FROM t LIMIT 10 OFFSET 5"; + TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true); + } + + @Test + void testUnnestWithOffsetAfterAlias() throws JSQLParserException { + String sqlStr = "SELECT x, pos FROM UNNEST([1, 2, 3]) AS x WITH OFFSET AS pos"; + PlainSelect select = (PlainSelect) TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true); + TableFunction unnest = (TableFunction) select.getFromItem(); + + Assertions.assertTrue(unnest.isWithOffset()); + Assertions.assertEquals("x", unnest.getAlias().getName()); + Assertions.assertEquals("pos", unnest.getOffsetAlias().getName()); + } + + @Test + void testUnnestWithOffsetWithoutOffsetAlias() throws JSQLParserException { + String sqlStr = "SELECT * FROM t, UNNEST(t.arr) AS c WITH OFFSET"; + TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true); + } + + @Test + void testUnnestWithOrdinalityAliasStillBindsToTableFunction() throws JSQLParserException { + String sqlStr = "SELECT * FROM UNNEST(ARRAY[1, 2, 3]) WITH ORDINALITY AS t (a, b)"; + PlainSelect select = (PlainSelect) TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true); + TableFunction unnest = (TableFunction) select.getFromItem(); + + Assertions.assertFalse(unnest.isWithOffset()); + Assertions.assertEquals("t", unnest.getAlias().getName()); + } + + @Test + void testAggregateFunctionIgnoreNullsBeforeOrderBy() throws JSQLParserException { + String sqlStr = "SELECT ARRAY_AGG(x IGNORE NULLS ORDER BY x) FROM t"; + TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true); + } + + @Test + void testAggregateFunctionIgnoreNullsOrderByLimit() throws JSQLParserException { + String sqlStr = "SELECT ARRAY_AGG(DISTINCT x IGNORE NULLS ORDER BY x DESC LIMIT 5) FROM t"; + TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true); + } + + @Test + void testAggregateFunctionOrderByWithoutNullHandling() throws JSQLParserException { + String sqlStr = "SELECT ARRAY_AGG(x ORDER BY x) FROM t"; + TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true); + } + + @Test + void testCreateTablePartitionByExpression() throws JSQLParserException { + String sqlStr = "CREATE TABLE ds.t (id INT64, ts TIMESTAMP) PARTITION BY DATE(ts)"; + CreateTable createTable = + (CreateTable) TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true); + + Assertions.assertEquals(TablePartitioning.Type.EXPRESSION, + createTable.getPartitioning().getType()); + Assertions.assertEquals("DATE(ts)", + createTable.getPartitioning().getExpression().toString()); + } + + @Test + void testCreateTablePartitionByColumnWithOptions() throws JSQLParserException { + String sqlStr = + "CREATE TABLE ds.t (id INT64, d DATE) PARTITION BY d OPTIONS (description = 'x')"; + TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true); + } + + @Test + void testCreateTableAsSelectPartitionByAndClusterBy() throws JSQLParserException { + String sqlStr = "CREATE TABLE ds.t PARTITION BY d CLUSTER BY id " + + "AS SELECT 1 AS id, CURRENT_DATE() AS d"; + TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true); + } + + @Test + void testCreateTablePartitionByHashStillParses() throws JSQLParserException { + String sqlStr = "CREATE TABLE t (id INT) PARTITION BY HASH (id)"; + CreateTable createTable = + (CreateTable) TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true); + + Assertions.assertEquals(TablePartitioning.Type.HASH, + createTable.getPartitioning().getType()); + } + + @Test + void testAssertStatementWithDescription() throws JSQLParserException { + String sqlStr = "ASSERT (SELECT COUNT(*) FROM t) > 0 AS 'table is empty'"; + AssertStatement assertStatement = + (AssertStatement) TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true); + + Assertions.assertEquals("'table is empty'", assertStatement.getDescription().toString()); + } + + @Test + void testAssertStatementWithoutDescription() throws JSQLParserException { + String sqlStr = "ASSERT EXISTS(SELECT 1 FROM t)"; + AssertStatement assertStatement = + (AssertStatement) TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true); + + Assertions.assertNull(assertStatement.getDescription()); + } + + @Test + void testAssertRemainsUsableAsIdentifier() throws JSQLParserException { + String sqlStr = "SELECT assert FROM t"; + TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true); + } + + @Test + void testExportData() throws JSQLParserException { + String sqlStr = "EXPORT DATA OPTIONS (uri = 'gs://bucket/*.csv', format = 'CSV', " + + "overwrite = true) AS SELECT * FROM t"; + ExportDataStatement exportData = + (ExportDataStatement) TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true); + + Assertions.assertEquals(3, exportData.getOptions().size()); + Assertions.assertNull(exportData.getConnectionName()); + } + + @Test + void testExportDataWithConnection() throws JSQLParserException { + String sqlStr = "EXPORT DATA WITH CONNECTION myproject.us.myconnection " + + "OPTIONS (uri = 'gs://bucket/*.csv') AS SELECT a FROM t"; + ExportDataStatement exportData = + (ExportDataStatement) TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true); + + Assertions.assertEquals("myproject.us.myconnection", exportData.getConnectionName()); + } + + @Test + void testExportDataTablesNamesFinder() throws JSQLParserException { + String sqlStr = "EXPORT DATA OPTIONS (uri = 'gs://bucket/*.csv') AS SELECT * FROM ds.t"; + + Assertions.assertEquals(java.util.Collections.singletonList("ds.t"), + new net.sf.jsqlparser.util.TablesNamesFinder() + .getTableList(net.sf.jsqlparser.parser.CCJSqlParserUtil.parse(sqlStr))); + } + + @Test + void testLoadDataIntoFromFiles() throws JSQLParserException { + String sqlStr = "LOAD DATA INTO mydataset.mytable " + + "FROM FILES (format = 'AVRO', uris = ['gs://bucket/*.avro'])"; + LoadDataStatement loadData = + (LoadDataStatement) TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true); + + Assertions.assertFalse(loadData.isOverwrite()); + Assertions.assertEquals("mydataset.mytable", loadData.getTable().getFullyQualifiedName()); + Assertions.assertEquals(2, loadData.getFileOptions().size()); + } + + @Test + void testLoadDataOverwriteWithColumnDefinitions() throws JSQLParserException { + String sqlStr = "LOAD DATA OVERWRITE ds.t (id INT64, name STRING) " + + "FROM FILES (format = 'CSV', uris = ['gs://b/f.csv'])"; + LoadDataStatement loadData = + (LoadDataStatement) TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true); + + Assertions.assertTrue(loadData.isOverwrite()); + Assertions.assertEquals(2, loadData.getColumnDefinitions().size()); + } + + @Test + void testLoadDataWithOptionsAndConnection() throws JSQLParserException { + String sqlStr = "LOAD DATA INTO ds.t OPTIONS (description = 'x') " + + "FROM FILES (format = 'PARQUET') WITH PARTITION COLUMNS " + + "WITH CONNECTION myproject.us.conn"; + LoadDataStatement loadData = + (LoadDataStatement) TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true); + + Assertions.assertEquals("myproject.us.conn", loadData.getConnectionName()); + Assertions.assertTrue(loadData.isWithPartitionColumns()); + } + + @Test + void testLoadDataTablesNamesFinder() throws JSQLParserException { + String sqlStr = "LOAD DATA INTO ds.t FROM FILES (format = 'CSV')"; + + Assertions.assertEquals(java.util.Collections.singletonList("ds.t"), + new net.sf.jsqlparser.util.TablesNamesFinder() + .getTableList(net.sf.jsqlparser.parser.CCJSqlParserUtil.parse(sqlStr))); + } + + @Test + void testCreateSnapshotTableClone() throws JSQLParserException { + String sqlStr = "CREATE SNAPSHOT TABLE ds.snap CLONE ds.t"; + CreateTable createTable = + (CreateTable) TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true); + + Assertions.assertEquals("ds.t", createTable.getCloneTable().getFullyQualifiedName()); + } + + @Test + void testCreateSnapshotTableCloneForSystemTimeAsOf() throws JSQLParserException { + String sqlStr = "CREATE SNAPSHOT TABLE IF NOT EXISTS ds.snap CLONE ds.t " + + "FOR SYSTEM_TIME AS OF CURRENT_TIMESTAMP()"; + TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true); + } + + @Test + void testCreateTableCloneWithOptions() throws JSQLParserException { + String sqlStr = "CREATE TABLE ds.copy CLONE ds.t OPTIONS (description = 'x')"; + TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true); + } + + @Test + void testCloneRemainsUsableAsIdentifier() throws JSQLParserException { + String sqlStr = "SELECT clone FROM t"; + TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true); + } } diff --git a/src/test/java/net/sf/jsqlparser/statement/select/ClickHouseTest.java b/src/test/java/net/sf/jsqlparser/statement/select/ClickHouseTest.java index 90f69ffd43..265c9621dd 100644 --- a/src/test/java/net/sf/jsqlparser/statement/select/ClickHouseTest.java +++ b/src/test/java/net/sf/jsqlparser/statement/select/ClickHouseTest.java @@ -11,11 +11,24 @@ import static net.sf.jsqlparser.test.TestUtils.assertSqlCanBeParsedAndDeparsed; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + import net.sf.jsqlparser.JSQLParserException; +import net.sf.jsqlparser.expression.Alias; +import net.sf.jsqlparser.expression.ColumnsExpression; +import net.sf.jsqlparser.expression.ColumnsTransformer; import net.sf.jsqlparser.expression.Function; +import net.sf.jsqlparser.expression.ExpressionVisitorAdapter; +import net.sf.jsqlparser.expression.LambdaExpression; +import net.sf.jsqlparser.expression.operators.relational.ParenthesedExpressionList; import net.sf.jsqlparser.parser.CCJSqlParserUtil; +import net.sf.jsqlparser.util.TablesNamesFinder; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; public class ClickHouseTest { @@ -147,6 +160,105 @@ public void testMultipleSettingsClauseIssue2362() throws JSQLParserException { Assertions.assertEquals(2, select.getSettings().size()); } + @Test + public void testOrderByWithFillIssue2467() throws JSQLParserException { + String sql = + "SELECT d, v FROM t ORDER BY d WITH FILL FROM toDate('2024-01-01') TO toDate('2024-02-01') STEP INTERVAL 1 DAY INTERPOLATE (v AS v + 1)"; + PlainSelect select = (PlainSelect) assertSqlCanBeParsedAndDeparsed(sql, true); + + WithFill withFill = select.getOrderByElements().get(0).getWithFill(); + Assertions.assertNotNull(withFill); + Assertions.assertEquals("toDate('2024-01-01')", withFill.getFrom().toString()); + Assertions.assertEquals("toDate('2024-02-01')", withFill.getTo().toString()); + Assertions.assertEquals("INTERVAL 1 DAY", withFill.getStep().toString()); + Assertions.assertNull(withFill.getStaleness()); + + Assertions.assertEquals(1, select.getInterpolate().size()); + Assertions.assertEquals("v", select.getInterpolate().get(0).getColumn().getColumnName()); + Assertions.assertEquals("v + 1", select.getInterpolate().get(0).getExpression().toString()); + } + + @Test + public void testOrderByWithFillOnly() throws JSQLParserException { + String sql = "SELECT key, value FROM t ORDER BY key WITH FILL"; + PlainSelect select = (PlainSelect) assertSqlCanBeParsedAndDeparsed(sql, true); + Assertions.assertNotNull(select.getOrderByElements().get(0).getWithFill()); + Assertions.assertNull(select.getOrderByElements().get(0).getWithFill().getFrom()); + } + + @Test + public void testOrderByWithFillFromToStep() throws JSQLParserException { + String sql = "SELECT n FROM t ORDER BY n WITH FILL FROM 0 TO 5.51 STEP 0.5"; + PlainSelect select = (PlainSelect) assertSqlCanBeParsedAndDeparsed(sql, true); + WithFill withFill = select.getOrderByElements().get(0).getWithFill(); + Assertions.assertEquals("0", withFill.getFrom().toString()); + Assertions.assertEquals("5.51", withFill.getTo().toString()); + Assertions.assertEquals("0.5", withFill.getStep().toString()); + } + + @Test + public void testOrderByWithFillStepIntervalAndStaleness() throws JSQLParserException { + String sql = + "SELECT d1, d2 FROM t ORDER BY d1 WITH FILL STEP INTERVAL 1 DAY STALENESS 3, d2 WITH FILL"; + PlainSelect select = (PlainSelect) assertSqlCanBeParsedAndDeparsed(sql, true); + Assertions.assertEquals(2, select.getOrderByElements().size()); + WithFill first = select.getOrderByElements().get(0).getWithFill(); + Assertions.assertEquals("INTERVAL 1 DAY", first.getStep().toString()); + Assertions.assertEquals("3", first.getStaleness().toString()); + Assertions.assertNotNull(select.getOrderByElements().get(1).getWithFill()); + } + + @Test + public void testOrderByWithFillDescNullsLast() throws JSQLParserException { + String sql = "SELECT d FROM t ORDER BY d DESC NULLS LAST WITH FILL TO 10"; + PlainSelect select = (PlainSelect) assertSqlCanBeParsedAndDeparsed(sql, true); + OrderByElement element = select.getOrderByElements().get(0); + Assertions.assertFalse(element.isAsc()); + Assertions.assertEquals(OrderByElement.NullOrdering.NULLS_LAST, element.getNullOrdering()); + Assertions.assertEquals("10", element.getWithFill().getTo().toString()); + } + + @Test + public void testOrderByWithFillInterpolateVariants() throws JSQLParserException { + // multiple items, with and without AS + String sql = "SELECT a, b, c FROM t ORDER BY a WITH FILL INTERPOLATE (b, c AS c + 1)"; + PlainSelect select = (PlainSelect) assertSqlCanBeParsedAndDeparsed(sql, true); + Assertions.assertEquals(2, select.getInterpolate().size()); + Assertions.assertNull(select.getInterpolate().get(0).getExpression()); + Assertions.assertEquals("c + 1", select.getInterpolate().get(1).getExpression().toString()); + + // bare INTERPOLATE fills all allowed columns + sql = "SELECT a, b FROM t ORDER BY a WITH FILL INTERPOLATE"; + select = (PlainSelect) assertSqlCanBeParsedAndDeparsed(sql, true); + Assertions.assertNotNull(select.getInterpolate()); + Assertions.assertTrue(select.getInterpolate().isEmpty()); + } + + @Test + public void testOrderByWithFillUnionInterpolate() throws JSQLParserException { + String sql = + "SELECT a FROM t1 UNION SELECT a FROM t2 ORDER BY a WITH FILL INTERPOLATE (a AS a + 1) LIMIT 10"; + Select select = (Select) assertSqlCanBeParsedAndDeparsed(sql, true); + SetOperationList setOperationList = (SetOperationList) select.getSelectBody(); + Assertions.assertNotNull(setOperationList.getOrderByElements().get(0).getWithFill()); + Assertions.assertEquals(1, setOperationList.getInterpolate().size()); + } + + @Test + public void testOrderByWithFillSubquery() throws JSQLParserException { + String sql = "SELECT * FROM (SELECT d, v FROM t ORDER BY d WITH FILL FROM 0 TO 9) AS sub"; + assertSqlCanBeParsedAndDeparsed(sql, true); + } + + @Test + public void testFillStepInterpolateStalenessAsColumnNames() throws JSQLParserException { + // the new keywords stay non-reserved and must remain usable as plain column names + assertSqlCanBeParsedAndDeparsed("SELECT fill, step, staleness FROM t ORDER BY interpolate", + true); + assertSqlCanBeParsedAndDeparsed("SELECT interpolate FROM t WHERE fill = 1 AND step = 2", + true); + } + @Test public void testCastToNestedParametricTypeIssue2441() throws JSQLParserException { // ClickHouse allows parametric (constructor-style) data types as a CAST target, @@ -158,4 +270,394 @@ public void testCastToNestedParametricTypeIssue2441() throws JSQLParserException sql = "SELECT CAST(x AS LowCardinality(Decimal(10, 2))) FROM cast_demo"; assertSqlCanBeParsedAndDeparsed(sql, true); } + + @Test + public void testColumnsApplyLambdaIssue2631() throws JSQLParserException { + // ClickHouse applies a function or lambda expression to all columns matching + // the regular expression: https://github.com/JSQLParser/JSqlParser/issues/2631 + String sql = "SELECT COLUMNS('^metric_') APPLY(x -> round(x, 2)) FROM metrics"; + Select select = (Select) CCJSqlParserUtil.parse(sql); + PlainSelect plainSelect = (PlainSelect) select.getSelectBody(); + ColumnsExpression columnsExpression = Assertions.assertInstanceOf(ColumnsExpression.class, + plainSelect.getSelectItems().get(0).getExpression()); + Assertions.assertEquals(1, columnsExpression.getTransformers().size()); + ColumnsTransformer transformer = columnsExpression.getTransformers().get(0); + Assertions.assertEquals(ColumnsTransformer.ColumnsTransformerType.APPLY, + transformer.getType()); + Assertions.assertInstanceOf(LambdaExpression.class, transformer.getApplyExpression()); + assertSqlCanBeParsedAndDeparsed(sql, true); + } + + @Test + public void testColumnsApplyFunctionName() throws JSQLParserException { + // chained APPLY modifiers, taken from the ClickHouse SELECT documentation + String sql = "SELECT COLUMNS('[jk]') APPLY(toString) APPLY(length) APPLY(max)" + + " FROM columns_transformers"; + Select select = (Select) CCJSqlParserUtil.parse(sql); + PlainSelect plainSelect = (PlainSelect) select.getSelectBody(); + ColumnsExpression columnsExpression = Assertions.assertInstanceOf(ColumnsExpression.class, + plainSelect.getSelectItems().get(0).getExpression()); + Assertions.assertEquals(3, columnsExpression.getTransformers().size()); + assertSqlCanBeParsedAndDeparsed(sql, true); + } + + @Test + public void testColumnsExcept() throws JSQLParserException { + String sql = "SELECT COLUMNS('^metric_') EXCEPT (metric_disk) FROM metrics"; + Select select = (Select) CCJSqlParserUtil.parse(sql); + PlainSelect plainSelect = (PlainSelect) select.getSelectBody(); + ColumnsExpression columnsExpression = Assertions.assertInstanceOf(ColumnsExpression.class, + plainSelect.getSelectItems().get(0).getExpression()); + Assertions.assertEquals(ColumnsTransformer.ColumnsTransformerType.EXCEPT, + columnsExpression.getTransformers().get(0).getType()); + assertSqlCanBeParsedAndDeparsed(sql, true); + } + + @Test + public void testColumnsReplace() throws JSQLParserException { + String sql = + "SELECT COLUMNS('^metric_') REPLACE(metric_cpu * 100 AS metric_cpu) FROM metrics"; + Select select = (Select) CCJSqlParserUtil.parse(sql); + PlainSelect plainSelect = (PlainSelect) select.getSelectBody(); + ColumnsExpression columnsExpression = Assertions.assertInstanceOf(ColumnsExpression.class, + plainSelect.getSelectItems().get(0).getExpression()); + Assertions.assertEquals(ColumnsTransformer.ColumnsTransformerType.REPLACE, + columnsExpression.getTransformers().get(0).getType()); + assertSqlCanBeParsedAndDeparsed(sql, true); + } + + @Test + public void testColumnsCombinedTransformers() throws JSQLParserException { + // ClickHouse parses its transformers in a loop, so they combine in any order + String sql = + "SELECT COLUMNS('m') APPLY(x -> round(x, 2)) EXCEPT (metric_disk) FROM metrics"; + assertSqlCanBeParsedAndDeparsed(sql, true); + + sql = "SELECT COLUMNS('m') EXCEPT (metric_disk) APPLY(x -> round(x, 2)) FROM metrics"; + assertSqlCanBeParsedAndDeparsed(sql, true); + } + + @Test + public void testColumnsApplyMultiParamLambda() throws JSQLParserException { + String sql = "SELECT COLUMNS('m') APPLY((k, v) -> v > 5) FROM metrics"; + assertSqlCanBeParsedAndDeparsed(sql, true); + } + + @Test + public void testBareColumnsExpressionStaysFunction() throws JSQLParserException { + // without a transformer, COLUMNS(...) keeps parsing as a regular function + String sql = "SELECT COLUMNS('^metric_') FROM metrics"; + Select select = (Select) CCJSqlParserUtil.parse(sql); + PlainSelect plainSelect = (PlainSelect) select.getSelectBody(); + Assertions.assertInstanceOf(Function.class, + plainSelect.getSelectItems().get(0).getExpression()); + assertSqlCanBeParsedAndDeparsed(sql, true); + } + + @Test + public void testColumnsItemFollowedBySetOperation() throws JSQLParserException { + // a follower keyword without its parenthesis never starts a transformer, so + // EXCEPT stays a set operation (a bare APPLY or REPLACE stays an alias) + assertSqlCanBeParsedAndDeparsed("SELECT COLUMNS('m') EXCEPT SELECT b FROM t", true); + assertSqlCanBeParsedAndDeparsed("SELECT COLUMNS('m') EXCEPT ALL SELECT b FROM t", true); + assertSqlCanBeParsedAndDeparsed("SELECT a, COLUMNS('m') EXCEPT SELECT b FROM t", true); + assertSqlCanBeParsedAndDeparsed("SELECT COLUMNS('m') EXCEPT (SELECT b FROM t)", true); + // parenthesized set operation operands that do not start a column list + assertSqlCanBeParsedAndDeparsed("SELECT COLUMNS('m') EXCEPT ((SELECT b FROM t))", true); + assertSqlCanBeParsedAndDeparsed("SELECT COLUMNS('m') EXCEPT (VALUES (1, 2))", true); + } + + @Test + public void testBareTransformerKeywordRemainsAlias() throws JSQLParserException { + assertSqlCanBeParsedAndDeparsed("SELECT COLUMNS('m') APPLY FROM t", true); + assertSqlCanBeParsedAndDeparsed("SELECT COLUMNS('m') REPLACE FROM t", true); + assertSqlCanBeParsedAndDeparsed("SELECT COLUMNS('m') APPLY", true); + } + + @Test + public void testAliasColumnsBodyRemainsAlias() throws JSQLParserException { + // T(name[type], ...) is the alias-columns reading of the regular alias path, + // not a transformer body (APPLY takes a function name or a lambda in ClickHouse) + assertSqlCanBeParsedAndDeparsed("SELECT COLUMNS('m') APPLY(a, b) FROM t", true); + assertSqlCanBeParsedAndDeparsed("SELECT COLUMNS('m') APPLY(a, b)", true); + assertSqlCanBeParsedAndDeparsed("SELECT COLUMNS('m') APPLY(a INT) FROM t", true); + assertSqlCanBeParsedAndDeparsed("SELECT COLUMNS('m') APPLY(`a`, `b`) FROM t", true); + assertSqlCanBeParsedAndDeparsed( + "SELECT COLUMNS('m') REPLACE(a INT, b VARCHAR(10)) FROM t", true); + } + + @Test + public void testReplaceWithBareIdentifierAsAlias() throws JSQLParserException { + // the plain ClickHouse rename form: REPLACE(column AS alias) + assertSqlCanBeParsedAndDeparsed("SELECT COLUMNS('m') REPLACE(b AS c) FROM t", true); + assertSqlCanBeParsedAndDeparsed("SELECT COLUMNS('m') REPLACE(b AS c, d AS e) FROM t", true); + // keyword-predicate lambda bodies stay on the transformer reading + assertSqlCanBeParsedAndDeparsed("SELECT COLUMNS('m') APPLY(x IS NULL) FROM t", true); + assertSqlCanBeParsedAndDeparsed("SELECT COLUMNS('m') APPLY(x AND y) FROM t", true); + } + + @Test + public void testTypedAliasColumnWithCompoundTimeZoneType() throws JSQLParserException { + // compound timezone types are lexed as a single DT_ZONE token but are still + // alias-columns types, so the input belongs to the alias path + assertSqlCanBeParsedAndDeparsed( + "SELECT COLUMNS('m') APPLY(a TIMESTAMP WITH TIME ZONE) FROM t", true); + assertSqlCanBeParsedAndDeparsed( + "SELECT COLUMNS('m') APPLY(a TIMESTAMP(3) WITH TIME ZONE) FROM t", true); + assertSqlCanBeParsedAndDeparsed( + "SELECT COLUMNS('m') APPLY(a TIMESTAMP WITHOUT TIME ZONE) FROM t", true); + } + + @ParameterizedTest + @ValueSource(strings = {"[x, x + 1]", "x IN (1, 2)", + "CASE x WHEN 1 THEN 0 ELSE 1 END"}) + public void testColumnsApplyExpressionBodies(String body) throws JSQLParserException { + for (String prefix : new String[] {"", " APPLY(sum)"}) { + String sql = "SELECT COLUMNS('m')" + prefix + " APPLY(x -> " + body + ") FROM t"; + PlainSelect select = (PlainSelect) assertSqlCanBeParsedAndDeparsed(sql, true); + ColumnsExpression expression = Assertions.assertInstanceOf(ColumnsExpression.class, + select.getSelectItem(0).getExpression()); + ColumnsTransformer transformer = expression.getTransformers() + .get(expression.getTransformers().size() - 1); + LambdaExpression lambda = Assertions.assertInstanceOf(LambdaExpression.class, + transformer.getApplyExpression()); + Assertions.assertEquals(body, lambda.getExpression().toString()); + } + } + + @Test + public void testColumnsReplaceCaseExpression() throws JSQLParserException { + String sql = "SELECT COLUMNS('m') REPLACE(CASE m WHEN 1 THEN 0 ELSE 1 END AS m) FROM t"; + PlainSelect select = (PlainSelect) assertSqlCanBeParsedAndDeparsed(sql, true); + ColumnsExpression expression = Assertions.assertInstanceOf(ColumnsExpression.class, + select.getSelectItem(0).getExpression()); + SelectItem replacement = expression.getTransformers().get(0).getReplaceItems().get(0); + Assertions.assertEquals("CASE m WHEN 1 THEN 0 ELSE 1 END", + replacement.getExpression().toString()); + Assertions.assertEquals("m", replacement.getAlias().getName()); + } + + @ParameterizedTest + @ValueSource(strings = { + "SELECT * FROM (SELECT COLUMNS('m') APPLY(sum) FROM t) q", + "WITH q AS (SELECT COLUMNS('m') APPLY(x -> x + 1) FROM t) SELECT * FROM q", + "SELECT (SELECT COLUMNS('m') APPLY(sum) FROM t) AS result", + "SELECT COLUMNS('m') APPLY(sum) EXCEPT (SELECT b FROM t)", + "SELECT 1 UNION SELECT COLUMNS('m') APPLY(sum) FROM t", + "INSERT INTO dst SELECT COLUMNS('m') APPLY(sum) FROM t", + "SELECT JSON_VALUE(doc, '$' DEFAULT (SELECT COLUMNS('m') APPLY(sum) APPLY(max) FROM t) ON EMPTY) FROM src"}) + public void testColumnsInSelectContexts(String sql) throws JSQLParserException { + assertSqlCanBeParsedAndDeparsed(sql, true); + } + + @ParameterizedTest + @ValueSource(strings = {"COLUMNS('m').x", "{fn COLUMNS('m')}", "COLUMNS('m')(x)"}) + public void testDecoratedColumnsFunctionKeepsAlias(String function) throws JSQLParserException { + String sql = "SELECT " + function + " APPLY(sum) FROM t"; + PlainSelect select = (PlainSelect) assertSqlCanBeParsedAndDeparsed(sql, true); + SelectItem item = select.getSelectItem(0); + Function expression = Assertions.assertInstanceOf(Function.class, item.getExpression()); + Assertions.assertSame(item, expression.getParent()); + Assertions.assertEquals("APPLY", item.getAlias().getName()); + Assertions.assertEquals("sum", item.getAlias().getAliasColumns().get(0).name); + } + + @ParameterizedTest + @ValueSource(strings = {"APPLY(a, b)", "APPLY(a INT)", "REPLACE(a INT)", + "REPLACE(a)", "REPLACE(a, b)"}) + public void testColumnsAliasAstAtEachBoundary(String suffix) throws JSQLParserException { + for (String prefix : new String[] {"", " APPLY(sum)"}) { + String sql = "SELECT COLUMNS('m')" + prefix + " " + suffix + " FROM t"; + PlainSelect select = (PlainSelect) assertSqlCanBeParsedAndDeparsed(sql, true); + SelectItem item = select.getSelectItem(0); + if (prefix.isEmpty()) { + Assertions.assertInstanceOf(Function.class, item.getExpression()); + } else { + ColumnsExpression expression = Assertions.assertInstanceOf(ColumnsExpression.class, + item.getExpression()); + Assertions.assertEquals(1, expression.getTransformers().size()); + Assertions.assertEquals("sum", expression.getTransformers().get(0) + .getApplyExpression().toString()); + } + Alias alias = item.getAlias(); + Assertions.assertNotNull(alias); + Assertions.assertEquals(suffix.substring(0, suffix.indexOf('(')), alias.getName()); + Assertions.assertFalse(alias.isUseAs()); + Assertions.assertEquals("a", alias.getAliasColumns().get(0).name); + if (suffix.contains("INT")) { + Assertions.assertEquals("INT", + alias.getAliasColumns().get(0).colDataType.toString()); + } else { + Assertions.assertNull(alias.getAliasColumns().get(0).colDataType); + if (suffix.contains(",")) { + Assertions.assertEquals("b", alias.getAliasColumns().get(1).name); + } + } + } + } + + @Test + public void testWithLambdaExpressionAliasIssue2632() throws JSQLParserException { + // https://github.com/JSQLParser/JSqlParser/issues/2632 + String sql = "WITH (x -> x * 2) AS double SELECT double(5)"; + Select select = (Select) assertSqlCanBeParsedAndDeparsed(sql, true); + WithItem withItem = select.getWithItemsList().get(0); + Assertions.assertTrue(withItem.getExpression() instanceof ParenthesedExpressionList); + LambdaExpression lambda = (LambdaExpression) ((ParenthesedExpressionList) withItem + .getExpression()).get(0); + Assertions.assertEquals(List.of("x"), lambda.getIdentifiers()); + Assertions.assertEquals("double", withItem.getAliasName()); + Assertions.assertEquals("double(5)", + ((PlainSelect) select.getSelectBody()).getSelectItems().get(0).getExpression() + .toString()); + } + + @Test + public void testWithLambdaExpressionAliasDocForms() throws JSQLParserException { + // documented lambda form: (param) -> expr — the AST keeps the parameter name only, + // so the single-parameter parentheses follow the same deparse convention as + // function-argument lambdas and are not preserved + Select select = (Select) CCJSqlParserUtil + .parse("WITH (value) -> value + 1 AS increment SELECT increment(5)"); + LambdaExpression lambda = (LambdaExpression) select.getWithItemsList().get(0) + .getExpression(); + Assertions.assertEquals(List.of("value"), lambda.getIdentifiers()); + Assertions.assertEquals("increment", select.getWithItemsList().get(0).getAliasName()); + assertSqlCanBeParsedAndDeparsed("WITH value -> value + 1 AS increment SELECT increment(5)", + true); + + // multi-parameter lambda with a parenthesized parameter list + assertSqlCanBeParsedAndDeparsed( + "WITH (id, extension) -> concat(lower(id), extension) AS gen_name " + + "SELECT gen_name('A', 'B')", + true); + + // the whole lambda may be wrapped in parentheses (issue #2632 style), multi-param too + assertSqlCanBeParsedAndDeparsed("WITH ((x, y) -> x + y) AS f SELECT f(1, 2)", true); + + // unparenthesized parameter is accepted by ClickHouse as well + assertSqlCanBeParsedAndDeparsed("WITH x -> x * 2 AS double SELECT double(5)", true); + } + + @Test + public void testWithExpressionAlias() throws JSQLParserException { + assertSqlCanBeParsedAndDeparsed("WITH 1 AS one SELECT one + 1", true); + assertSqlCanBeParsedAndDeparsed("WITH 2 + 2 AS a SELECT a", true); + assertSqlCanBeParsedAndDeparsed( + "WITH '2019-08-01 15:23:00' AS ts_upper_bound SELECT ts_upper_bound", true); + assertSqlCanBeParsedAndDeparsed("WITH sum(number) AS s SELECT s FROM numbers(3)", true); + + // a bare column reference aliased to another name + assertSqlCanBeParsedAndDeparsed("WITH x AS y SELECT y FROM (SELECT 42 AS x)", true); + + // an expression alias may reference an earlier WITH item, and the expression + // may itself be a compound one + assertSqlCanBeParsedAndDeparsed( + "WITH (SELECT sum(number) FROM numbers(10)) AS total, total * 2 AS doubled " + + "SELECT doubled", + true); + assertSqlCanBeParsedAndDeparsed("WITH sum(x) + 1 AS y SELECT y", true); + assertSqlCanBeParsedAndDeparsed( + "WITH CASE WHEN 1 < 2 THEN 'a' ELSE 'b' END AS c SELECT c", true); + assertSqlCanBeParsedAndDeparsed("WITH t AS u, v AS w SELECT u, w", true); + } + + @Test + public void testWithParenthesedSubqueryAlias() throws JSQLParserException { + String sql = "WITH (SELECT a FROM src) AS v SELECT a FROM v, dst"; + assertSqlCanBeParsedAndDeparsed(sql, true); + Assertions.assertEquals(Set.of("src", "dst"), TablesNamesFinder.findTables(sql)); + } + + @Test + public void testWithExpressionAliasMixedItems() throws JSQLParserException { + String sql = "WITH 1 AS one, (x -> x * 2) AS double, t AS (SELECT 1 AS a) " + + "SELECT one, double(5), a FROM t"; + assertSqlCanBeParsedAndDeparsed(sql, true); + } + + @Test + public void testWithStandardCteUnaffected() throws JSQLParserException { + // guards: the classic CTE shapes must keep parsing exactly as before + assertSqlCanBeParsedAndDeparsed("WITH t AS (SELECT 1 AS a) SELECT a FROM t", true); + assertSqlCanBeParsedAndDeparsed("WITH t (a, b) AS (SELECT 1 AS a, 2 AS b) SELECT a FROM t", + true); + assertSqlCanBeParsedAndDeparsed( + "WITH RECURSIVE q AS (SELECT 1 AS n UNION ALL SELECT n + 1 FROM q WHERE n < 3) " + + "SELECT n FROM q", + true); + assertSqlCanBeParsedAndDeparsed( + "WITH a AS MATERIALIZED (SELECT 1 AS b) SELECT b FROM a", true); + assertSqlCanBeParsedAndDeparsed( + "WITH a AS NOT MATERIALIZED (SELECT 1 AS b) SELECT b FROM a", true); + } + + @Test + public void testDataTypeNumericArgsFunctionCall() throws JSQLParserException { + // ClickHouse names lambda aliases after type-like keywords (issue #2632 uses double), + // and calls them like functions: double(5). DATA_TYPE(N) without a trailing literal + // is a function call, not a typed literal. + Select select = (Select) assertSqlCanBeParsedAndDeparsed("SELECT double(5)", true); + Assertions.assertTrue(((PlainSelect) select.getSelectBody()).getSelectItems().get(0) + .getExpression() instanceof Function); + assertSqlCanBeParsedAndDeparsed("SELECT int(5), varchar(5), double(5) + 1", true); + + // the same family: a DATA_TYPE keyword used as a plain column name, e.g. the + // documented ClickHouse corpus sum(number) FROM numbers(10) + assertSqlCanBeParsedAndDeparsed("SELECT number FROM t", true); + assertSqlCanBeParsedAndDeparsed("SELECT sum(number) FROM numbers(3)", true); + + // guards: typed literals with a trailing literal keep their cast reading + assertSqlCanBeParsedAndDeparsed("SELECT INT '5'", true); + assertSqlCanBeParsedAndDeparsed("SELECT DECIMAL(10,2) '1.5'", true); + } + + @Test + public void testWithExpressionAliasTraversableByVisitorAdapter() throws JSQLParserException { + // a generic SelectVisitorAdapter traversal must reach the expression body of a + // WITH AS item, including nested table references, + // exactly as it reaches the body of a classic CTE + List plainSelects = new ArrayList<>(); + SelectVisitorAdapter visitor = new SelectVisitorAdapter<>() { + @Override + public Void visit(PlainSelect plainSelect, S context) { + plainSelects.add(String.valueOf(plainSelect.getFromItem())); + return super.visit(plainSelect, context); + } + }; + Select select = (Select) CCJSqlParserUtil + .parse("WITH (SELECT 1 FROM src) AS v SELECT a FROM v, dst"); + select.getSelectBody().accept(visitor, null); + Assertions.assertTrue(plainSelects.contains("src"), + "expression body table missing from traversal: " + plainSelects); + + // a lambda alias is handed to the expression visitor + List expressions = new ArrayList<>(); + SelectVisitorAdapter exprVisitor = + new SelectVisitorAdapter<>(new ExpressionVisitorAdapter<>() { + @Override + public Void visit(LambdaExpression lambdaExpression, S context) { + expressions.add(String.valueOf(lambdaExpression)); + return super.visit(lambdaExpression, context); + } + }); + Select lambdaSelect = (Select) CCJSqlParserUtil + .parse("WITH (x -> x * 2) AS double SELECT double(5)"); + Assertions.assertNotNull(lambdaSelect.getWithItemsList().get(0).getExpression()); + lambdaSelect.getSelectBody().accept(exprVisitor, null); + Assertions.assertEquals(List.of("x -> x * 2"), expressions); + } + + @Test + public void testWithExpressionAliasRecursiveClausesSerialize() throws JSQLParserException { + // the tolerant-grammar SEARCH/CYCLE suffix must survive serialization on the + // expression branch instead of being dropped + String sql = "WITH 1 AS one SEARCH DEPTH FIRST BY one SET o SELECT one"; + Select select = (Select) assertSqlCanBeParsedAndDeparsed(sql, true); + Assertions.assertNotNull(select.getWithItemsList().get(0).getSearchClause()); + + String sql2 = "WITH 1 AS one CYCLE one SET c USING p SELECT one"; + Select select2 = (Select) assertSqlCanBeParsedAndDeparsed(sql2, true); + Assertions.assertNotNull(select2.getWithItemsList().get(0).getCycleClause()); + } } diff --git a/src/test/java/net/sf/jsqlparser/statement/select/CompositeRowExpansionTest.java b/src/test/java/net/sf/jsqlparser/statement/select/CompositeRowExpansionTest.java new file mode 100644 index 0000000000..c2e2a09f8d --- /dev/null +++ b/src/test/java/net/sf/jsqlparser/statement/select/CompositeRowExpansionTest.java @@ -0,0 +1,97 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2019 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.statement.select; + +import static net.sf.jsqlparser.test.TestUtils.assertSqlCanBeParsedAndDeparsed; + +import net.sf.jsqlparser.JSQLParserException; +import net.sf.jsqlparser.expression.Expression; +import net.sf.jsqlparser.parser.CCJSqlParserUtil; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +/** + * PostgreSQL composite row expansion {@code (function_returning_composite).*}. + * + *

+ * This feature was merged via #2207 and afterwards disabled, because the speculative syntactic + * lookahead used back then caused a severe performance regression. It is re-enabled here through a + * bounded semantic follower check, see {@code FunctionAllColumns} in the grammar. + */ +public class CompositeRowExpansionTest { + + private static FunctionAllColumns assertFunctionAllColumns(String sql) + throws JSQLParserException { + PlainSelect select = (PlainSelect) assertSqlCanBeParsedAndDeparsed(sql, true); + Expression expression = select.getSelectItems().get(0).getExpression(); + Assertions.assertTrue(expression instanceof FunctionAllColumns, + "Expected a FunctionAllColumns select item but got " + expression.getClass()); + return (FunctionAllColumns) expression; + } + + @Test + public void testIssue2412JsonPopulateRecord() throws JSQLParserException { + FunctionAllColumns result = assertFunctionAllColumns( + "SELECT (json_populate_record(NULL::users, data)).* FROM staging_users"); + Assertions.assertEquals("json_populate_record", result.getFunction().getName()); + } + + @Test + public void testSimpleFunctionAllColumns() throws JSQLParserException { + FunctionAllColumns result = assertFunctionAllColumns("SELECT (foo(a, b)).* FROM t"); + Assertions.assertEquals("foo", result.getFunction().getName()); + } + + @Test + public void testPgStatFileExampleFrom2207() throws JSQLParserException { + FunctionAllColumns result = assertFunctionAllColumns( + "SELECT (pg_stat_file('postgresql.conf')).*"); + Assertions.assertEquals("pg_stat_file", result.getFunction().getName()); + } + + @Test + public void testIssue2412InsertSelectUseCase() throws JSQLParserException { + assertSqlCanBeParsedAndDeparsed( + "INSERT INTO users SELECT (json_populate_record(NULL::users, data)).* FROM staging_users", + true); + } + + @Test + public void testMultipleSurroundingParensAreUnwrapped() throws JSQLParserException { + // Redundant parentheses around a single value are semantically transparent in + // PostgreSQL, so they are unwrapped to the inner function. The round-trip + // therefore normalises to a single surrounding pair. + PlainSelect select = (PlainSelect) CCJSqlParserUtil.parse("SELECT ((((foo(a))))).* FROM t"); + Expression expression = select.getSelectItems().get(0).getExpression(); + Assertions.assertTrue(expression instanceof FunctionAllColumns); + Assertions.assertEquals("foo", ((FunctionAllColumns) expression).getFunction().getName()); + Assertions.assertEquals("(foo(a)).*", expression.toString()); + } + + @Test + public void testParenthesedFunctionWithoutExpansionUnchanged() throws JSQLParserException { + // Without the trailing .* a parenthesised function stays a plain expression. + assertSqlCanBeParsedAndDeparsed("SELECT (foo(a, b)) FROM t", true); + } + + @Test + public void testRowGetExpressionAfterParenthesedFunctionUnchanged() throws JSQLParserException { + // (function()).name must keep parsing as a RowGetExpression, not be swallowed. + assertSqlCanBeParsedAndDeparsed("SELECT (foo(a, b)).colname FROM t", true); + } + + @Test + public void testNonFunctionCompositeExpansionStillUnsupported() { + // Expanding an arbitrary (non-function) expression is out of scope and must + // keep failing cleanly instead of producing a wrong AST. + Assertions.assertThrows(JSQLParserException.class, + () -> CCJSqlParserUtil.parse("SELECT (a + b).* FROM t")); + } +} diff --git a/src/test/java/net/sf/jsqlparser/statement/select/DorisJoinHintTest.java b/src/test/java/net/sf/jsqlparser/statement/select/DorisJoinHintTest.java new file mode 100644 index 0000000000..958d2f878c --- /dev/null +++ b/src/test/java/net/sf/jsqlparser/statement/select/DorisJoinHintTest.java @@ -0,0 +1,119 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.statement.select; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import java.util.List; +import java.util.Set; +import net.sf.jsqlparser.JSQLParserException; +import net.sf.jsqlparser.parser.AbstractJSqlParser.Dialect; +import net.sf.jsqlparser.parser.CCJSqlParserUtil; +import net.sf.jsqlparser.schema.Table; +import net.sf.jsqlparser.statement.Statement; +import net.sf.jsqlparser.test.TestUtils; +import net.sf.jsqlparser.util.TablesNamesFinder; +import net.sf.jsqlparser.util.deparser.StatementDeParser; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +class DorisJoinHintTest { + private static PlainSelect parse(String sql, Dialect dialect) throws JSQLParserException { + return (PlainSelect) CCJSqlParserUtil.parse(sql, parser -> parser.withDialect(dialect)); + } + + @Test + void parsesOriginalReproducerIssue1620() throws Exception { + String sql = "SELECT * FROM uba.events a LEFT JOIN [shuffle] uba.events b " + + "ON a.event_id = b.event_id"; + PlainSelect select = (PlainSelect) TestUtils.assertSqlCanBeParsedAndDeparsed(sql, false, + parser -> parser.withDialect(Dialect.DORIS)); + Join join = select.getJoins().get(0); + assertEquals("shuffle", join.getJoinHint().getKeyword()); + assertEquals(JoinHint.Position.AFTER_JOIN, join.getJoinHint().getPosition()); + assertEquals("uba.events", ((Table) join.getFromItem()).getFullyQualifiedName()); + assertEquals("b", join.getFromItem().getAlias().getName()); + assertEquals(Set.of("uba.events"), new TablesNamesFinder<>().getTables((Statement) select)); + } + + @ParameterizedTest + @ValueSource(strings = {"shuffle", "broadcast", "SHUFFLE", "BROADCAST"}) + void preservesHintPositionAndRoundTrips(String hint) throws Exception { + for (String kind : List.of("", "INNER ", "LEFT OUTER ", "RIGHT ", "LEFT SEMI ")) { + String sql = "SELECT a.id FROM a " + kind + "JOIN [" + hint + "] b USING (id)"; + PlainSelect select = (PlainSelect) TestUtils.assertSqlCanBeParsedAndDeparsed(sql, false, + parser -> parser.withDialect(Dialect.DORIS)); + StringBuilder output = new StringBuilder(); + select.accept(new StatementDeParser(output), null); + for (String rendered : List.of(select.toString(), output.toString())) { + JoinHint reparsed = parse(rendered, Dialect.DORIS).getJoins().get(0).getJoinHint(); + assertEquals(hint, reparsed.getKeyword()); + assertEquals(JoinHint.Position.AFTER_JOIN, reparsed.getPosition()); + } + } + } + + @Test + void keepsHintPerJoinAndSupportsAstMutation() throws Exception { + PlainSelect select = parse("SELECT * FROM a JOIN [shuffle] b ON a.id = b.id " + + "JOIN [broadcast] c ON b.id = c.id JOIN d ON c.id = d.id", Dialect.DORIS); + assertEquals("shuffle", select.getJoins().get(0).getJoinHint().getKeyword()); + assertEquals("broadcast", select.getJoins().get(1).getJoinHint().getKeyword()); + assertNull(select.getJoins().get(2).getJoinHint()); + select.getJoins().get(0) + .setJoinHint(new JoinHint("broadcast", JoinHint.Position.AFTER_JOIN)); + TestUtils.assertDeparse(select, "SELECT * FROM a JOIN [broadcast] b ON a.id = b.id " + + "JOIN [broadcast] c ON b.id = c.id JOIN d ON c.id = d.id"); + select.getJoins().get(0).setJoinHint(null); + assertEquals("JOIN b ON a.id = b.id", select.getJoins().get(0).toString()); + } + + @Test + void keepsSqlServerHintsAndQuotedTableNames() throws Exception { + for (String keyword : List.of("LOOP", "HASH", "MERGE", "REMOTE")) { + String sql = "SELECT * FROM a INNER " + keyword + " JOIN b ON a.id = b.id"; + PlainSelect select = (PlainSelect) TestUtils.assertSqlCanBeParsedAndDeparsed(sql); + assertEquals(JoinHint.Position.BEFORE_JOIN, + select.getJoins().get(0).getJoinHint().getPosition()); + assertEquals(keyword, new JoinHint(keyword).toString()); + } + PlainSelect quoted = parse("SELECT * FROM a JOIN [shuffle] b ON a.id = b.id", + Dialect.SQLSERVER); + assertNull(quoted.getJoins().get(0).getJoinHint()); + assertEquals("[shuffle]", ((Table) quoted.getJoins().get(0).getFromItem()).getName()); + PlainSelect dorisTable = parse("SELECT * FROM a JOIN `shuffle` b ON a.id = b.id", + Dialect.DORIS); + assertNull(dorisTable.getJoins().get(0).getJoinHint()); + } + + @Test + void requiresDorisDialect() { + String sql = "SELECT * FROM a JOIN [shuffle] db.b b ON a.id = b.id"; + assertThrows(JSQLParserException.class, () -> CCJSqlParserUtil.parse(sql)); + for (Dialect dialect : Dialect.values()) { + if (dialect != Dialect.DORIS) { + assertThrows(JSQLParserException.class, () -> parse(sql, dialect), dialect.name()); + } + } + } + + @Test + void rejectsUnknownAndDuplicateHints() { + for (String sql : List.of("SELECT * FROM a JOIN [unknown] b ON a.id = b.id", + "SELECT * FROM a JOIN [shuffle, broadcast] b ON a.id = b.id", + "SELECT * FROM a JOIN [shuffle] [broadcast] b ON a.id = b.id", + "SELECT * FROM a INNER HASH JOIN [shuffle] b ON a.id = b.id", + "SELECT * FROM a JOIN [shuffle b ON a.id = b.id")) { + assertThrows(JSQLParserException.class, () -> parse(sql, Dialect.DORIS), sql); + } + } +} diff --git a/src/test/java/net/sf/jsqlparser/statement/select/DuckDBCompatibilityTest.java b/src/test/java/net/sf/jsqlparser/statement/select/DuckDBCompatibilityTest.java new file mode 100644 index 0000000000..739cc6fc6c --- /dev/null +++ b/src/test/java/net/sf/jsqlparser/statement/select/DuckDBCompatibilityTest.java @@ -0,0 +1,70 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.statement.select; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; + +import net.sf.jsqlparser.JSQLParserException; +import net.sf.jsqlparser.expression.MapExpression; +import net.sf.jsqlparser.statement.Statement; +import net.sf.jsqlparser.statement.create.view.CreateView; +import net.sf.jsqlparser.test.TestUtils; +import net.sf.jsqlparser.util.TablesNamesFinder; +import org.junit.jupiter.api.Test; + +class DuckDBCompatibilityTest { + + @Test + void testMapLiteral() throws JSQLParserException { + String sql = "SELECT MAP {'key': 'value', 'key2': 'other'} AS m FROM t"; + + Select select = (Select) TestUtils.assertSqlCanBeParsedAndDeparsed(sql, true); + MapExpression mapExpression = assertInstanceOf(MapExpression.class, + select.getPlainSelect().getSelectItem(0).getExpression()); + + assertThat(mapExpression.getEntries()).hasSize(2); + assertThat(TablesNamesFinder.findTables(sql)).containsExactly("t"); + } + + @Test + void testCreateViewWithMapLiteral() throws JSQLParserException { + String sql = "CREATE VIEW v AS SELECT " + + "MAP {'category': name, 'count': name} AS summary FROM products"; + + CreateView createView = + (CreateView) TestUtils.assertSqlCanBeParsedAndDeparsed(sql, true); + MapExpression mapExpression = assertInstanceOf(MapExpression.class, + createView.getSelect().getPlainSelect().getSelectItem(0).getExpression()); + + assertThat(mapExpression.getEntries()).hasSize(2); + assertThat(TablesNamesFinder.findTables(sql)).contains("products"); + } + + @Test + void testPivotStatement() throws JSQLParserException { + String sql = "PIVOT sales ON region USING SUM(amount)"; + + Statement statement = TestUtils.assertSqlCanBeParsedAndDeparsed(sql, true); + + assertInstanceOf(PivotQuery.class, statement); + } + + @Test + void testPivotInSubquery() throws JSQLParserException { + String sql = "SELECT * FROM (PIVOT sales ON region USING SUM(amount))"; + + Select select = (Select) TestUtils.assertSqlCanBeParsedAndDeparsed(sql, true); + ParenthesedSelect parenthesedSelect = + assertInstanceOf(ParenthesedSelect.class, select.getPlainSelect().getFromItem()); + + assertInstanceOf(PivotQuery.class, parenthesedSelect.getSelect()); + } +} diff --git a/src/test/java/net/sf/jsqlparser/statement/select/DuckDBTest.java b/src/test/java/net/sf/jsqlparser/statement/select/DuckDBTest.java index aad68683bb..68e98f1019 100644 --- a/src/test/java/net/sf/jsqlparser/statement/select/DuckDBTest.java +++ b/src/test/java/net/sf/jsqlparser/statement/select/DuckDBTest.java @@ -11,6 +11,21 @@ import net.sf.jsqlparser.JSQLParserException; import net.sf.jsqlparser.schema.Table; +import net.sf.jsqlparser.statement.create.table.CreateTable; +import net.sf.jsqlparser.statement.insert.Insert; +import net.sf.jsqlparser.statement.DescribeStatement; +import net.sf.jsqlparser.statement.PragmaStatement; +import net.sf.jsqlparser.statement.ExtensionStatement; +import net.sf.jsqlparser.statement.AttachStatement; +import net.sf.jsqlparser.statement.DetachStatement; +import net.sf.jsqlparser.statement.ConnectStatement; +import net.sf.jsqlparser.statement.DisconnectStatement; +import net.sf.jsqlparser.statement.PrepareStatement; +import net.sf.jsqlparser.statement.DeallocateStatement; +import net.sf.jsqlparser.statement.CopyStatement; +import net.sf.jsqlparser.statement.create.macro.CreateMacro; +import net.sf.jsqlparser.statement.create.extension.CreateExtensionRepository; +import net.sf.jsqlparser.statement.create.trigger.CreateTrigger; import net.sf.jsqlparser.test.TestUtils; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; @@ -36,4 +51,642 @@ void testCreateWithStruct() throws JSQLParserException { ");"; TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true); } + + @Test + void testGlobOperator() throws JSQLParserException { + String sqlStr = "SELECT * FROM t WHERE b GLOB 'y*'"; + TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true); + } + + @Test + void testNotGlobOperator() throws JSQLParserException { + String sqlStr = "SELECT * FROM t WHERE b NOT GLOB 'y*'"; + TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true); + } + + @Test + void testGlobRemainsUsableAsIdentifier() throws JSQLParserException { + String sqlStr = "SELECT glob FROM t"; + TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true); + } + + @Test + void testSemiJoinWithoutLeft() throws JSQLParserException { + String sqlStr = "SELECT * FROM t SEMI JOIN u USING (id)"; + PlainSelect select = (PlainSelect) TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true); + Join join = select.getJoins().get(0); + + Assertions.assertTrue(join.isSemi()); + Assertions.assertFalse(join.isLeft()); + Assertions.assertNull(((Table) select.getFromItem()).getAlias()); + } + + @Test + void testAntiJoinWithoutLeft() throws JSQLParserException { + String sqlStr = "SELECT * FROM t ANTI JOIN u ON t.id = u.id"; + PlainSelect select = (PlainSelect) TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true); + Join join = select.getJoins().get(0); + + Assertions.assertTrue(join.isAnti()); + Assertions.assertNull(((Table) select.getFromItem()).getAlias()); + } + + @Test + void testLeftAntiJoin() throws JSQLParserException { + String sqlStr = "SELECT * FROM t LEFT ANTI JOIN u ON t.id = u.id"; + PlainSelect select = (PlainSelect) TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true); + Join join = select.getJoins().get(0); + + Assertions.assertTrue(join.isLeft()); + Assertions.assertTrue(join.isAnti()); + } + + @Test + void testAntiRemainsUsableAsIdentifier() throws JSQLParserException { + String sqlStr = "SELECT anti FROM t anti"; + TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true); + } + + @Test + void testMapColumnType() throws JSQLParserException { + String sqlStr = "CREATE TABLE t (m MAP(VARCHAR, INTEGER))"; + CreateTable createTable = + (CreateTable) TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true); + + Assertions.assertEquals("MAP", + createTable.getColumnDefinitions().get(0).getColDataType().getDataType()); + Assertions.assertEquals(java.util.Arrays.asList("VARCHAR", "INTEGER"), + createTable.getColumnDefinitions().get(0).getColDataType() + .getArgumentsStringList()); + } + + @Test + void testMapCast() throws JSQLParserException { + String sqlStr = "SELECT CAST(m AS MAP(VARCHAR, INTEGER)) FROM t"; + TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true); + } + + @Test + void testDecimalTypeArgumentsStillParse() throws JSQLParserException { + String sqlStr = "CREATE TABLE t (a DECIMAL(10, 2), b VARCHAR(255))"; + TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true); + } + + @Test + void testInsertByName() throws JSQLParserException { + String sqlStr = "INSERT INTO t BY NAME SELECT 1 AS b, 2 AS a"; + Insert insert = (Insert) TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true); + + Assertions.assertEquals(Insert.ColumnMatching.BY_NAME, insert.getColumnMatching()); + } + + @Test + void testInsertByPosition() throws JSQLParserException { + String sqlStr = "INSERT INTO t BY POSITION SELECT 1, 2"; + Insert insert = (Insert) TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true); + + Assertions.assertEquals(Insert.ColumnMatching.BY_POSITION, insert.getColumnMatching()); + } + + @Test + void testInsertWithoutColumnMatching() throws JSQLParserException { + String sqlStr = "INSERT INTO t (a, b) VALUES (1, 2)"; + Insert insert = (Insert) TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true); + + Assertions.assertNull(insert.getColumnMatching()); + } + + @Test + void testUsingSamplePercentShorthand() throws JSQLParserException { + String sqlStr = "SELECT * FROM t USING SAMPLE 10%"; + PlainSelect select = (PlainSelect) TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true); + SampleClause sampleClause = select.getFromItem().getSampleClause(); + + Assertions.assertEquals("%", sampleClause.getPercentageUnit()); + Assertions.assertNull(sampleClause.getMethod()); + } + + @Test + void testUsingSamplePercentWithMethodInBrackets() throws JSQLParserException { + String sqlStr = "SELECT * FROM t USING SAMPLE 10 PERCENT (bernoulli)"; + PlainSelect select = (PlainSelect) TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true); + + Assertions.assertEquals(SampleClause.SampleMethod.BERNOULLI, + select.getFromItem().getSampleClause().getMethod()); + } + + @Test + void testUsingSampleRowsWithSeed() throws JSQLParserException { + String sqlStr = "SELECT * FROM t USING SAMPLE 10 ROWS (system, 377)"; + TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true); + } + + @Test + void testUsingSampleReservoir() throws JSQLParserException { + String sqlStr = "SELECT * FROM t USING SAMPLE reservoir (50 ROWS)"; + PlainSelect select = (PlainSelect) TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true); + + Assertions.assertEquals(SampleClause.SampleMethod.RESERVOIR, + select.getFromItem().getSampleClause().getMethod()); + } + + @Test + void testDescribeQuery() throws JSQLParserException { + String sqlStr = "DESCRIBE SELECT * FROM t"; + DescribeStatement describe = + (DescribeStatement) TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true); + + Assertions.assertNotNull(describe.getSelect()); + Assertions.assertNull(describe.getTable()); + } + + @Test + void testDescribeQueryTablesNamesFinder() throws JSQLParserException { + String sqlStr = "DESCRIBE SELECT * FROM ds.t"; + + Assertions.assertEquals(java.util.Collections.singletonList("ds.t"), + new net.sf.jsqlparser.util.TablesNamesFinder() + .getTableList(net.sf.jsqlparser.parser.CCJSqlParserUtil.parse(sqlStr))); + } + + @Test + void testDescribeTableStillParses() throws JSQLParserException { + String sqlStr = "DESCRIBE t"; + DescribeStatement describe = + (DescribeStatement) TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true); + + Assertions.assertEquals("t", describe.getTable().getName()); + } + + @Test + void testPragmaWithArguments() throws JSQLParserException { + String sqlStr = "PRAGMA table_info('t')"; + PragmaStatement pragma = + (PragmaStatement) TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true); + + Assertions.assertEquals("table_info", pragma.getName()); + Assertions.assertEquals(1, pragma.getParameters().size()); + } + + @Test + void testPragmaAssignment() throws JSQLParserException { + String sqlStr = "PRAGMA memory_limit = '1GB'"; + PragmaStatement pragma = + (PragmaStatement) TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true); + + Assertions.assertEquals("'1GB'", pragma.getValue().toString()); + } + + @Test + void testPragmaWithoutArguments() throws JSQLParserException { + String sqlStr = "PRAGMA database_list"; + TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true); + } + + @Test + void testPragmaRemainsUsableAsIdentifier() throws JSQLParserException { + String sqlStr = "SELECT pragma FROM t"; + TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true); + } + + @Test + void testLoadExtension() throws JSQLParserException { + String sqlStr = "LOAD httpfs"; + ExtensionStatement extension = + (ExtensionStatement) TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true); + + Assertions.assertEquals(ExtensionStatement.Operation.LOAD, extension.getOperation()); + Assertions.assertEquals("httpfs", extension.getExtensionName()); + } + + @Test + void testInstallExtension() throws JSQLParserException { + String sqlStr = "INSTALL spatial"; + ExtensionStatement extension = + (ExtensionStatement) TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true); + + Assertions.assertEquals(ExtensionStatement.Operation.INSTALL, extension.getOperation()); + Assertions.assertFalse(extension.isForce()); + } + + @Test + void testForceInstallExtensionFromRepository() throws JSQLParserException { + String sqlStr = "FORCE INSTALL h3 FROM community"; + ExtensionStatement extension = + (ExtensionStatement) TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true); + + Assertions.assertTrue(extension.isForce()); + Assertions.assertEquals("community", extension.getRepository()); + } + + @Test + void testInstallExtensionFromUrl() throws JSQLParserException { + String sqlStr = "INSTALL 'path/to/ext.duckdb_extension'"; + TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true); + } + + @Test + void testLoadRemainsUsableAsIdentifier() throws JSQLParserException { + String sqlStr = "SELECT load, install FROM t"; + TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true); + } + + @Test + void testAttachDatabaseWithAlias() throws JSQLParserException { + String sqlStr = "ATTACH 'file.db' AS mydb"; + AttachStatement attach = + (AttachStatement) TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true); + + Assertions.assertEquals("'file.db'", attach.getDatabasePath()); + Assertions.assertEquals("mydb", attach.getAlias()); + } + + @Test + void testAttachDatabaseWithOptions() throws JSQLParserException { + String sqlStr = "ATTACH DATABASE IF NOT EXISTS 'file.db' AS mydb (TYPE SQLITE, READ_ONLY)"; + AttachStatement attach = + (AttachStatement) TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true); + + Assertions.assertTrue(attach.isIfNotExists()); + Assertions.assertTrue(attach.isUsingDatabaseKeyword()); + Assertions.assertEquals(java.util.Arrays.asList("TYPE SQLITE", "READ_ONLY"), + attach.getOptions()); + } + + @Test + void testDetachDatabase() throws JSQLParserException { + String sqlStr = "DETACH mydb"; + DetachStatement detach = + (DetachStatement) TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true); + + Assertions.assertEquals("mydb", detach.getDatabaseName()); + } + + @Test + void testDetachDatabaseIfExists() throws JSQLParserException { + String sqlStr = "DETACH DATABASE IF EXISTS mydb"; + DetachStatement detach = + (DetachStatement) TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true); + + Assertions.assertTrue(detach.isIfExists()); + } + + @Test + void testAttachRemainsUsableAsIdentifier() throws JSQLParserException { + String sqlStr = "SELECT attach, detach FROM t"; + TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true); + } + + @Test + void testConnectToRemoteDatabase() throws JSQLParserException { + String sqlStr = "CONNECT 'postgres://localhost/mydb'"; + ConnectStatement connect = + (ConnectStatement) TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true); + + Assertions.assertEquals("'postgres://localhost/mydb'", connect.getTarget()); + Assertions.assertNull(connect.getAlias()); + } + + @Test + void testConnectWithAlias() throws JSQLParserException { + String sqlStr = "CONNECT 'postgres://localhost/mydb' AS remote"; + ConnectStatement connect = + (ConnectStatement) TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true); + + Assertions.assertEquals("remote", connect.getAlias()); + } + + @Test + void testDisconnect() throws JSQLParserException { + String sqlStr = "DISCONNECT"; + DisconnectStatement disconnect = + (DisconnectStatement) TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true); + + Assertions.assertNull(disconnect.getName()); + } + + @Test + void testDisconnectNamedConnection() throws JSQLParserException { + String sqlStr = "DISCONNECT remote"; + DisconnectStatement disconnect = + (DisconnectStatement) TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true); + + Assertions.assertEquals("remote", disconnect.getName()); + } + + @Test + void testConnectByStillParses() throws JSQLParserException { + String sqlStr = "SELECT * FROM t START WITH id = 1 CONNECT BY PRIOR id = parent_id"; + TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true); + } + + @Test + void testPrepareStatement() throws JSQLParserException { + String sqlStr = "PREPARE q AS SELECT * FROM t WHERE a = $1"; + PrepareStatement prepare = + (PrepareStatement) TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true); + + Assertions.assertEquals("q", prepare.getName()); + Assertions.assertNotNull(prepare.getStatement()); + } + + @Test + void testPrepareInsertStatement() throws JSQLParserException { + String sqlStr = "PREPARE ins AS INSERT INTO t VALUES ($1, $2)"; + TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true); + } + + @Test + void testPrepareTablesNamesFinder() throws JSQLParserException { + String sqlStr = "PREPARE q AS SELECT * FROM ds.t"; + + Assertions.assertEquals(java.util.Collections.singletonList("ds.t"), + new net.sf.jsqlparser.util.TablesNamesFinder() + .getTableList(net.sf.jsqlparser.parser.CCJSqlParserUtil.parse(sqlStr))); + } + + @Test + void testDeallocatePreparedStatement() throws JSQLParserException { + String sqlStr = "DEALLOCATE PREPARE q"; + DeallocateStatement deallocate = + (DeallocateStatement) TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true); + + Assertions.assertEquals("q", deallocate.getName()); + Assertions.assertTrue(deallocate.isUsingPrepareKeyword()); + } + + @Test + void testExecutePreparedStatement() throws JSQLParserException { + String sqlStr = "EXECUTE q (1)"; + TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true); + } + + @Test + void testCopyFromFile() throws JSQLParserException { + String sqlStr = "COPY t FROM 'in.csv'"; + CopyStatement copy = + (CopyStatement) TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true); + + Assertions.assertTrue(copy.isFrom()); + Assertions.assertEquals("t", copy.getTable().getName()); + Assertions.assertEquals("'in.csv'", copy.getPath()); + } + + @Test + void testCopyColumnsFromFileWithOptions() throws JSQLParserException { + String sqlStr = "COPY t (a, b) FROM 'in.csv' (AUTO_DETECT true)"; + CopyStatement copy = + (CopyStatement) TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true); + + Assertions.assertEquals(2, copy.getColumns().size()); + Assertions.assertEquals(java.util.Collections.singletonList("AUTO_DETECT true"), + copy.getOptions()); + } + + @Test + void testCopyQueryToFile() throws JSQLParserException { + String sqlStr = + "COPY (SELECT * FROM t) TO 'out.parquet' (FORMAT PARQUET, COMPRESSION ZSTD)"; + CopyStatement copy = + (CopyStatement) TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true); + + Assertions.assertFalse(copy.isFrom()); + Assertions.assertNotNull(copy.getSelect()); + } + + @Test + void testCopyTableToFile() throws JSQLParserException { + String sqlStr = "COPY t TO 'out.csv' (FORMAT CSV, DELIMITER '|')"; + TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true); + } + + @Test + void testCopyTablesNamesFinder() throws JSQLParserException { + String sqlStr = "COPY ds.t FROM 'in.csv'"; + + Assertions.assertEquals(java.util.Collections.singletonList("ds.t"), + new net.sf.jsqlparser.util.TablesNamesFinder() + .getTableList(net.sf.jsqlparser.parser.CCJSqlParserUtil.parse(sqlStr))); + } + + @Test + void testCreateScalarMacro() throws JSQLParserException { + String sqlStr = "CREATE MACRO add_one(a) AS a + 1"; + CreateMacro createMacro = + (CreateMacro) TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true); + + Assertions.assertEquals("add_one", createMacro.getName()); + Assertions.assertEquals(1, createMacro.getParameters().size()); + Assertions.assertFalse(createMacro.isTable()); + } + + @Test + void testCreateOrReplaceTemporaryMacroWithDefault() throws JSQLParserException { + String sqlStr = "CREATE OR REPLACE TEMPORARY MACRO add(a, b := 5) AS a + b"; + CreateMacro createMacro = + (CreateMacro) TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true); + + Assertions.assertTrue(createMacro.isOrReplace()); + Assertions.assertTrue(createMacro.isTemporary()); + Assertions.assertEquals("5", + createMacro.getParameters().get(1).getDefaultValue().toString()); + } + + @Test + void testCreateTableMacro() throws JSQLParserException { + String sqlStr = "CREATE MACRO get_t(i) AS TABLE SELECT * FROM t WHERE id = i"; + CreateMacro createMacro = + (CreateMacro) TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true); + + Assertions.assertTrue(createMacro.isTable()); + Assertions.assertNotNull(createMacro.getSelect()); + } + + @Test + void testCreateExtensionRepository() throws JSQLParserException { + String sqlStr = + "CREATE EXTENSION REPOSITORY my_repo WITH PREFIX 'https://extensions.example.org'"; + CreateExtensionRepository createRepository = + (CreateExtensionRepository) TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true); + + Assertions.assertEquals("my_repo", createRepository.getName()); + Assertions.assertEquals("'https://extensions.example.org'", + createRepository.getPrefix().toString()); + } + + @Test + void testCreateExtensionRepositoryIfNotExists() throws JSQLParserException { + String sqlStr = "CREATE EXTENSION REPOSITORY IF NOT EXISTS my_repo"; + CreateExtensionRepository createRepository = + (CreateExtensionRepository) TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true); + + Assertions.assertTrue(createRepository.isIfNotExists()); + } + + @Test + void testCreatePostgresExtensionStillParses() throws JSQLParserException { + String sqlStr = "CREATE EXTENSION IF NOT EXISTS hstore WITH SCHEMA public CASCADE"; + TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true); + } + + @Test + void testCreateTriggerWithTransitionTablesAndStatementBody() throws JSQLParserException { + String sqlStr = "CREATE TRIGGER trg_audit AFTER UPDATE ON target " + + "REFERENCING OLD TABLE AS o NEW TABLE AS n FOR EACH STATEMENT " + + "INSERT INTO audit SELECT n.id, o.val, n.val FROM o JOIN n ON o.id = n.id"; + CreateTrigger createTrigger = + (CreateTrigger) TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true); + + Assertions.assertEquals(2, createTrigger.getTransitionRelations().size()); + Assertions.assertEquals(CreateTrigger.Orientation.STATEMENT, + createTrigger.getOrientation()); + Assertions.assertNotNull(createTrigger.getBody()); + } + + @Test + void testCreateTriggerBeforeDeleteWithStatementBody() throws JSQLParserException { + String sqlStr = "CREATE TRIGGER trg BEFORE DELETE ON t REFERENCING OLD TABLE AS o " + + "FOR EACH STATEMENT DELETE FROM audit WHERE id IN (SELECT id FROM o)"; + TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true); + } + + @Test + void testPostgreSqlTriggerStillParses() throws JSQLParserException { + String sqlStr = "CREATE TRIGGER trg AFTER UPDATE ON target " + + "REFERENCING OLD TABLE AS o NEW TABLE AS n FOR EACH STATEMENT " + + "EXECUTE FUNCTION f()"; + TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true); + } + + @Test + void testFromFirstWithSelect() throws JSQLParserException { + String sqlStr = "FROM t SELECT a, b"; + PlainSelect select = (PlainSelect) TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true); + + Assertions.assertTrue(select.isFromFirst()); + Assertions.assertEquals(2, select.getSelectItems().size()); + } + + @Test + void testFromFirstWithWhere() throws JSQLParserException { + String sqlStr = "FROM t WHERE a > 1"; + PlainSelect select = (PlainSelect) TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true); + + Assertions.assertTrue(select.isFromFirst()); + Assertions.assertNull(select.getSelectItems()); + } + + @Test + void testFromFirstWithClauses() throws JSQLParserException { + String sqlStr = "FROM t SELECT a, count(*) WHERE a > 1 GROUP BY a HAVING count(*) > 2 " + + "ORDER BY a LIMIT 10"; + TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true); + } + + @Test + void testFromFirstWithJoin() throws JSQLParserException { + String sqlStr = "FROM t JOIN u ON t.id = u.id SELECT t.a"; + TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true); + } + + @Test + void testFromFirstTablesNamesFinder() throws JSQLParserException { + String sqlStr = "FROM ds.t WHERE a > 1"; + + Assertions.assertEquals(java.util.Collections.singletonList("ds.t"), + new net.sf.jsqlparser.util.TablesNamesFinder() + .getTableList(net.sf.jsqlparser.parser.CCJSqlParserUtil.parse(sqlStr))); + } + + @Test + void testBarePipeQueryStillParses() throws JSQLParserException { + String sqlStr = "FROM t |> WHERE a > 1"; + TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true); + } + + @Test + void testUnPivotStatement() throws JSQLParserException { + String sqlStr = "UNPIVOT monthly_sales ON jan, feb INTO NAME month VALUE sales"; + UnPivotQuery unPivotQuery = + (UnPivotQuery) TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true); + + Assertions.assertEquals("month", unPivotQuery.getNameColumn()); + Assertions.assertEquals(2, unPivotQuery.getOnExpressions().size()); + Assertions.assertEquals(1, unPivotQuery.getValueColumns().size()); + } + + @Test + void testUnPivotStatementWithoutIntoClause() throws JSQLParserException { + String sqlStr = "UNPIVOT monthly_sales ON jan, feb"; + UnPivotQuery unPivotQuery = + (UnPivotQuery) TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true); + + Assertions.assertNull(unPivotQuery.getNameColumn()); + } + + @Test + void testUnPivotStatementTablesNamesFinder() throws JSQLParserException { + String sqlStr = "UNPIVOT ds.monthly_sales ON jan, feb INTO NAME month VALUE sales"; + + Assertions.assertEquals(java.util.Collections.singletonList("ds.monthly_sales"), + new net.sf.jsqlparser.util.TablesNamesFinder() + .getTableList(net.sf.jsqlparser.parser.CCJSqlParserUtil.parse(sqlStr))); + } + + @Test + void testUnPivotInFromClauseStillParses() throws JSQLParserException { + String sqlStr = "SELECT * FROM monthly_sales UNPIVOT (sales FOR month IN (jan, feb))"; + TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true); + } + + @Test + void testApproxNearestJoin() throws JSQLParserException { + String sqlStr = "SELECT * FROM queries q INNER JOIN products t " + + "APPROX NEAREST 2 BY SIMILARITY array_cosine_similarity(q.vec, t.vec)"; + PlainSelect select = (PlainSelect) TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true); + Join join = select.getJoins().get(0); + + Assertions.assertEquals(2L, join.getApproxNearest()); + Assertions.assertEquals("array_cosine_similarity(q.vec, t.vec)", + join.getSimilarity().toString()); + } + + @Test + void testApproxNearestJoinWithoutAlias() throws JSQLParserException { + String sqlStr = "SELECT * FROM queries JOIN products " + + "APPROX NEAREST 5 BY SIMILARITY array_distance(queries.vec, products.vec)"; + PlainSelect select = (PlainSelect) TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true); + + Assertions.assertNull(((Table) select.getJoins().get(0).getFromItem()).getAlias()); + } + + @Test + void testApproxRemainsUsableAsIdentifier() throws JSQLParserException { + String sqlStr = "SELECT approx, nearest, similarity FROM t"; + TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true); + } + + @Test + void testRecursiveCteUsingKey() throws JSQLParserException { + String sqlStr = "WITH RECURSIVE tbl (a, b) USING KEY (a, avg(b)) AS " + + "(SELECT 1, 2 UNION ALL SELECT a + 1, b FROM tbl WHERE a < 3) SELECT * FROM tbl"; + PlainSelect select = (PlainSelect) TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true); + WithItem withItem = select.getWithItemsList().get(0); + + Assertions.assertEquals(2, withItem.getUsingKeyExpressions().size()); + } + + @Test + void testRecursiveCteUsingKeySingleColumn() throws JSQLParserException { + String sqlStr = "WITH RECURSIVE tbl USING KEY (a) AS (SELECT 1 AS a) SELECT * FROM tbl"; + TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true); + } + + @Test + void testRecursiveCteWithoutUsingKey() throws JSQLParserException { + String sqlStr = "WITH RECURSIVE tbl (a) AS (SELECT 1) SELECT * FROM tbl"; + PlainSelect select = (PlainSelect) TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true); + + Assertions.assertNull(select.getWithItemsList().get(0).getUsingKeyExpressions()); + } } diff --git a/src/test/java/net/sf/jsqlparser/statement/select/KSQLWindowOptionsTest.java b/src/test/java/net/sf/jsqlparser/statement/select/KSQLWindowOptionsTest.java new file mode 100644 index 0000000000..e4c682f442 --- /dev/null +++ b/src/test/java/net/sf/jsqlparser/statement/select/KSQLWindowOptionsTest.java @@ -0,0 +1,153 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2019 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.statement.select; + +import static net.sf.jsqlparser.test.TestUtils.assertSqlCanBeParsedAndDeparsed; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import java.util.List; +import net.sf.jsqlparser.JSQLParserException; +import net.sf.jsqlparser.parser.CCJSqlParserUtil; +import net.sf.jsqlparser.statement.Statements; +import net.sf.jsqlparser.statement.select.KSQLWindow.Duration; +import net.sf.jsqlparser.statement.select.KSQLWindow.TimeUnit; +import net.sf.jsqlparser.statement.select.PlainSelect.EmitMode; +import net.sf.jsqlparser.util.deparser.StatementDeParser; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; +import org.junit.jupiter.params.provider.ValueSource; + +class KSQLWindowOptionsTest { + private PlainSelect roundTrip(String sql) throws Exception { + PlainSelect select = (PlainSelect) assertSqlCanBeParsedAndDeparsed(sql); + StringBuilder builder = new StringBuilder(); + select.accept(new StatementDeParser(builder), null); + assertEquals(select.toString(), builder.toString()); + assertEquals(select.toString(), CCJSqlParserUtil.parse(builder.toString()).toString()); + return select; + } + + @ParameterizedTest + @EnumSource(TimeUnit.class) + void joinsAcceptAllTimeUnitsAndPreserveBrackets(TimeUnit unit) throws Exception { + for (boolean brackets : List.of(false, true)) { + String within = brackets ? "(1 " + unit + ")" : "1 " + unit; + PlainSelect select = roundTrip("SELECT a.id FROM a INNER JOIN b WITHIN " + within + + " GRACE PERIOD 0 SECONDS ON a.id = b.id EMIT CHANGES"); + KSQLJoinWindow window = select.getJoins().get(0).getJoinWindow(); + assertEquals(1, window.getDuration()); + assertEquals(unit, window.getTimeUnit()); + assertEquals(brackets, window.isUsingBrackets()); + assertEquals(0, window.getGracePeriod().getValue()); + assertEquals(TimeUnit.SECONDS, window.getGracePeriod().getTimeUnit()); + assertTrue(select.isEmitChanges()); + } + } + + @ParameterizedTest + @EnumSource(TimeUnit.class) + void aggregationWindowsShareTimeUnitsAndGrace(TimeUnit unit) throws Exception { + for (String shape : List.of("TUMBLING (SIZE 20 ", "SESSION (20 ", + "HOPPING (SIZE 20 ")) { + String spec = shape + unit + + (shape.startsWith("HOPPING") ? ", ADVANCE BY 5 " + unit : "") + + ", GRACE PERIOD 2 " + unit + ")"; + PlainSelect select = + roundTrip("SELECT item_id, SUM(quantity) FROM orders WINDOW " + spec + + " GROUP BY item_id EMIT FINAL LIMIT 10"); + KSQLWindow window = select.getKsqlWindow(); + assertEquals(20, window.getSizeDuration()); + assertEquals(unit, window.getSizeTimeUnit()); + assertEquals(2, window.getGracePeriod().getValue()); + assertEquals(unit, window.getGracePeriod().getTimeUnit()); + assertEquals(EmitMode.FINAL, select.getEmitMode()); + assertFalse(select.isEmitChanges()); + if (shape.startsWith("HOPPING")) { + assertEquals(5, window.getAdvanceDuration()); + assertEquals(unit, window.getAdvanceTimeUnit()); + } + } + } + + @Test + void preservesAsymmetricWindowsAndFollowingJoins() throws Exception { + PlainSelect select = roundTrip( + "SELECT a.id FROM a JOIN b WITHIN (1 HOUR, 5 MINUTES) GRACE PERIOD 2 SECONDS ON a.id = b.id JOIN c WITHIN 10 SECONDS ON c.id = a.id EMIT CHANGES"); + assertEquals(2, select.getJoins().size()); + KSQLJoinWindow window = select.getJoins().get(0).getJoinWindow(); + assertTrue(window.isBeforeAfterWindow()); + assertEquals(1, window.getBeforeDuration()); + assertEquals(TimeUnit.HOUR, window.getBeforeTimeUnit()); + assertEquals(5, window.getAfterDuration()); + assertEquals(TimeUnit.MINUTES, window.getAfterTimeUnit()); + assertNull(select.getJoins().get(1).getJoinWindow().getGracePeriod()); + } + + @Test + void parsesOriginalReproducers() throws Exception { + roundTrip( + "select count(*) from log_data_testtopic_1b138324 t1 inner join TestTopic t2 WITHIN 1 MINUTES on t1.email = t2.email EMIT CHANGES"); + PlainSelect select = roundTrip( + "SELECT count(*), sum('id'), avg('id') FROM TestTopic WINDOW TUMBLING (SIZE 60 SECONDS) EMIT FINAL"); + assertEquals(EmitMode.FINAL, select.getEmitMode()); + } + + @Test + void retainsLegacyBuilderSemanticsAndAllowsMutation() throws Exception { + PlainSelect select = (PlainSelect) CCJSqlParserUtil.parse("SELECT * FROM t"); + assertEquals(EmitMode.NONE, select.getEmitMode()); + select.setEmitChanges(true); + assertEquals(EmitMode.CHANGES, select.getEmitMode()); + assertTrue(select.toString().endsWith("EMIT CHANGES")); + select.setEmitMode(EmitMode.FINAL); + assertTrue(select.toString().endsWith("EMIT FINAL")); + select.setEmitChanges(false); + assertEquals("SELECT * FROM t", select.toString()); + KSQLJoinWindow window = new KSQLJoinWindow().withDuration(5).withTimeUnit(TimeUnit.MINUTES); + assertEquals("(5 MINUTES)", window.toString()); + window.withUsingBrackets(false).withGracePeriod(new Duration(0, TimeUnit.SECONDS)); + assertEquals("5 MINUTES GRACE PERIOD 0 SECONDS", window.toString()); + window.setGracePeriod(null); + assertEquals("5 MINUTES", window.toString()); + KSQLWindow aggregate = + new KSQLWindow().withSizeDuration(10).withSizeTimeUnit(TimeUnit.SECONDS) + .withGracePeriod(new Duration(2, TimeUnit.MINUTES)); + assertEquals("TUMBLING (SIZE 10 SECONDS, GRACE PERIOD 2 MINUTES)", aggregate.toString()); + aggregate.setGracePeriod(null); + assertEquals("TUMBLING (SIZE 10 SECONDS)", aggregate.toString()); + assertThrows(IllegalArgumentException.class, () -> new Duration(-1, TimeUnit.SECONDS)); + } + + @Test + void keepsFollowingStatementsAndKeywordIdentifiers() throws Exception { + Statements statements = CCJSqlParserUtil.parseStatements( + "SELECT item_id, COUNT(*) FROM orders WINDOW TUMBLING (SIZE 1 HOUR) GROUP BY item_id EMIT FINAL; SELECT grace, period FROM t;"); + assertEquals(2, statements.size()); + assertEquals("SELECT grace, period FROM t", statements.get(1).toString()); + roundTrip("SELECT * FROM a JOIN b within ON a.id = within.id"); + roundTrip("SELECT * FROM a JOIN b AS within ON a.id = within.id"); + } + + @ParameterizedTest + @ValueSource(strings = { + "SELECT * FROM a JOIN b WITHIN 1 BANANAS ON a.id = b.id", + "SELECT * FROM a JOIN b WITHIN 1 HOURS GRACE 2 MINUTES ON a.id = b.id", + "SELECT * FROM a JOIN b WITHIN 1 HOURS, 2 HOURS ON a.id = b.id", + "SELECT * FROM t WINDOW TUMBLING (SIZE 1 HOUR, GRACE PERIOD -1 SECONDS)", + "SELECT * FROM t WINDOW TUMBLING (SIZE 1 HOUR, GRACE PERIOD 1 SECOND, GRACE PERIOD 2 SECONDS)", + "SELECT * FROM t EMIT FINAL CHANGES"}) + void rejectsMalformedWindowOptions(String sql) { + assertThrows(JSQLParserException.class, () -> CCJSqlParserUtil.parse(sql)); + } +} diff --git a/src/test/java/net/sf/jsqlparser/statement/select/LegacyMySqlGroupByTest.java b/src/test/java/net/sf/jsqlparser/statement/select/LegacyMySqlGroupByTest.java new file mode 100644 index 0000000000..d9b91d0673 --- /dev/null +++ b/src/test/java/net/sf/jsqlparser/statement/select/LegacyMySqlGroupByTest.java @@ -0,0 +1,135 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.statement.select; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.function.Consumer; +import net.sf.jsqlparser.JSQLParserException; +import net.sf.jsqlparser.expression.ExpressionVisitorAdapter; +import net.sf.jsqlparser.expression.operators.relational.ExpressionList; +import net.sf.jsqlparser.parser.AbstractJSqlParser.Dialect; +import net.sf.jsqlparser.parser.CCJSqlParser; +import net.sf.jsqlparser.parser.CCJSqlParserUtil; +import net.sf.jsqlparser.schema.Column; +import net.sf.jsqlparser.util.deparser.ExpressionDeParser; +import net.sf.jsqlparser.util.deparser.GroupByDeParser; +import net.sf.jsqlparser.util.validation.ValidationContext; +import net.sf.jsqlparser.util.validation.feature.MySqlVersion; +import net.sf.jsqlparser.util.validation.validator.GroupByValidator; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import static net.sf.jsqlparser.test.TestUtils.assertSqlCanBeParsedAndDeparsed; +import static org.junit.jupiter.api.Assertions.*; + +class LegacyMySqlGroupByTest { + private static final Consumer LEGACY = + parser -> parser.withDialect(Dialect.MYSQL).withLegacyMySqlGroupBy(true); + + @ParameterizedTest + @ValueSource(strings = { + "SELECT a FROM b GROUP BY c DESC", + "SELECT COUNT(*) FROM t GROUP BY a ASC, b, c DESC", + "SELECT COUNT(*) FROM t GROUP BY (a + b) DESC, LOWER(c) ASC WITH ROLLUP", + "SELECT COUNT(*) FROM t GROUP BY a DESC HAVING COUNT(*) > 1 ORDER BY a ASC", + "SELECT COUNT(*) FROM t GROUP BY a, b WITH ROLLUP", + "SELECT COUNT(*) FROM t GROUP BY (a, b)" + }) + void parsesAndDeparsesLegacyGrouping(String sql) throws Exception { + for (boolean complex : new boolean[] {false, true}) { + Consumer config = parser -> { + LEGACY.accept(parser); + parser.withAllowComplexParsing(complex); + }; + PlainSelect select = (PlainSelect) assertSqlCanBeParsedAndDeparsed(sql, true, config); + assertEquals(select.toString(), + CCJSqlParserUtil.parse(select.toString(), config).toString()); + } + } + + @Test + void requiresBothMysqlDialectAndExplicitOptIn() { + String sql = "SELECT a FROM b GROUP BY c DESC"; + assertThrows(JSQLParserException.class, () -> CCJSqlParserUtil.parse(sql)); + assertThrows(JSQLParserException.class, () -> CCJSqlParserUtil.parse(sql, + parser -> parser.withDialect(Dialect.MYSQL))); + assertThrows(JSQLParserException.class, () -> CCJSqlParserUtil.parse(sql, + parser -> parser.withLegacyMySqlGroupBy(true))); + for (Dialect dialect : Dialect.values()) { + if (dialect != Dialect.MYSQL) { + assertThrows(JSQLParserException.class, () -> CCJSqlParserUtil.parse(sql, + parser -> parser.withDialect(dialect).withLegacyMySqlGroupBy(true))); + } + } + } + + @Test + void keepsExpressionsVisibleAndDirectionsDistinct() throws Exception { + PlainSelect select = (PlainSelect) CCJSqlParserUtil.parse( + "SELECT COUNT(*) FROM t GROUP BY a ASC, a DESC, b", LEGACY); + GroupByElement group = select.getGroupBy(); + assertInstanceOf(Column.class, group.getGroupByExpressionList().get(0)); + assertEquals(GroupByElement.SortDirection.ASC, group.getGroupBySortDirection(0)); + assertEquals(GroupByElement.SortDirection.DESC, group.getGroupBySortDirection(1)); + assertNull(group.getGroupBySortDirection(2)); + List columns = new ArrayList<>(); + new ExpressionVisitorAdapter() { + @Override + public Void visit(Column column, S context) { + columns.add(column.getColumnName()); + return null; + } + }.visit(group, null); + assertEquals(List.of("a", "a", "b"), columns); + + StringBuilder output = new StringBuilder(); + ExpressionDeParser expressions = new ExpressionDeParser() { + @Override + public StringBuilder visit(Column column, S context) { + return getBuilder().append("renamed_").append(column.getColumnName()); + } + }; + expressions.setBuilder(output); + new GroupByDeParser(expressions, output).deParse(group); + assertEquals("GROUP BY renamed_a ASC, renamed_a DESC, renamed_b", output.toString()); + + group.addGroupByExpressions(new Column("extra")); + assertEquals("GROUP BY a ASC, a DESC, b, extra", group.toString()); + group.setGroupByExpressions(new ExpressionList<>(new Column("replacement"))); + assertFalse(group.hasGroupBySortDirections()); + assertEquals("GROUP BY replacement", group.toString()); + } + + @Test + void validatesOrderingSeparatelyFromOrdinaryGrouping() throws Exception { + PlainSelect select = (PlainSelect) CCJSqlParserUtil.parse( + "SELECT a FROM t GROUP BY a DESC", LEGACY); + GroupByValidator validator = new GroupByValidator<>(); + validator.setContext(new ValidationContext() + .setCapabilities(Collections.singleton(MySqlVersion.V8_0))); + validator.validate(select.getGroupBy()); + assertTrue(validator.getValidationErrors().values().stream() + .flatMap(errors -> errors.stream()) + .anyMatch(error -> error.getMessage().contains("selectGroupByOrdering"))); + } + + @ParameterizedTest + @ValueSource(strings = { + "SELECT a FROM t GROUP BY a DESC ASC", + "SELECT a FROM t GROUP BY a DESC,", + "SELECT a FROM t GROUP BY a NULLS FIRST" + }) + void rejectsMalformedOrdering(String sql) { + assertThrows(JSQLParserException.class, () -> CCJSqlParserUtil.parse(sql, LEGACY)); + } +} diff --git a/src/test/java/net/sf/jsqlparser/statement/select/MatchRecognizeDialectTest.java b/src/test/java/net/sf/jsqlparser/statement/select/MatchRecognizeDialectTest.java new file mode 100644 index 0000000000..794b1abe62 --- /dev/null +++ b/src/test/java/net/sf/jsqlparser/statement/select/MatchRecognizeDialectTest.java @@ -0,0 +1,210 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.statement.select; + +import static org.junit.jupiter.api.Assertions.*; + +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Stream; +import net.sf.jsqlparser.expression.ExpressionVisitorAdapter; +import net.sf.jsqlparser.expression.LongValue; +import net.sf.jsqlparser.expression.RowPatternFunction; +import net.sf.jsqlparser.expression.operators.arithmetic.Addition; +import net.sf.jsqlparser.parser.AbstractJSqlParser.Dialect; +import net.sf.jsqlparser.parser.CCJSqlParserUtil; +import net.sf.jsqlparser.schema.Column; +import net.sf.jsqlparser.statement.StatementFeatureVisitor; +import net.sf.jsqlparser.util.TablesNamesFinder; +import net.sf.jsqlparser.util.deparser.ExpressionDeParser; +import net.sf.jsqlparser.util.deparser.StatementDeParser; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.junit.jupiter.params.provider.ValueSource; + +class MatchRecognizeDialectTest { + private static String read(String name) throws Exception { + try (InputStream input = MatchRecognizeDialectTest.class.getResourceAsStream( + "/net/sf/jsqlparser/statement/select/match-recognize/" + name)) { + assertNotNull(input, name); + return new String(input.readAllBytes(), StandardCharsets.UTF_8); + } + } + + static Stream executedCases() throws Exception { + return read("execution-cases.txt").lines().map(line -> line.split(" ")) + .map(parts -> Arguments.of(Dialect.valueOf(parts[0]), parts[1])); + } + + private static PlainSelect parse(String sql, Dialect dialect) throws Exception { + return (PlainSelect) CCJSqlParserUtil.parse(sql, p -> p.withDialect(dialect)); + } + + private static String deparse(PlainSelect select) { + StringBuilder builder = new StringBuilder(); + select.accept(new StatementDeParser(builder)); + return builder.toString(); + } + + private static PlainSelect roundTrip(String sql, Dialect dialect) throws Exception { + PlainSelect select = parse(sql, dialect); + assertEquals(select.toString(), deparse(select)); + assertEquals(select.toString(), parse(deparse(select), dialect).toString()); + return select; + } + + @ParameterizedTest(name = "{0}: {1}") + @MethodSource("executedCases") + void executionFixturesRoundTrip(Dialect dialect, String file) throws Exception { + roundTrip(read(file), dialect); + } + + private static String snowflake(String clause) { + return "SELECT * FROM events MATCH_RECOGNIZE (" + clause + ")"; + } + + @ParameterizedTest + @ValueSource(strings = { + "PATTERN (A+) DEFINE A AS price > 0", + "ALL ROWS PER MATCH PATTERN (A+) DEFINE A AS price > 0", + "MEASURES MATCH_SEQUENCE_NUMBER() AS n ALL ROWS PER MATCH PATTERN (A B) DEFINE A AS price > 0", + "MEASURES FINAL LAST(A.price) AS p ALL ROWS PER MATCH OMIT EMPTY MATCHES PATTERN (A*) DEFINE A AS price > 0", + "ALL ROWS PER MATCH SHOW EMPTY MATCHES PATTERN (A*) DEFINE A AS price > 0", + "ALL ROWS PER MATCH WITH UNMATCHED ROWS PATTERN (A+) DEFINE A AS price > 0", + "AFTER MATCH SKIP TO FIRST B PATTERN (A B+) DEFINE A AS price > 0, B AS price < 0", + "AFTER MATCH SKIP TO LAST B PATTERN (A B+) DEFINE A AS price > 0, B AS price < 0", + "AFTER MATCH SKIP TO B PATTERN (A B+) DEFINE A AS price > 0, B AS price < 0", + "PATTERN (PERMUTE(A, B){1,2}) DEFINE A AS price > 0, B AS price < 0", + "PATTERN ({- A+ -} B) DEFINE A AS price > 0, B AS price < 0", + "PATTERN (^ A B | C $) DEFINE A AS price > 0, B AS price = 0, C AS price < 0", + "PATTERN (A+) DEFINE A AS price > LAG(price)", + "MEASURES RUNNING SUM(A.price) + FINAL SUM(A.price) AS total PATTERN (A+) DEFINE A AS price > 0" + }) + void documentedSnowflakeSyntax(String clause) throws Exception { + roundTrip(snowflake(clause), Dialect.SNOWFLAKE); + } + + @Test + void snowflakeDocumentedPrecedenceIsExplicitInTheAst() throws Exception { + String query = snowflake("PATTERN (A B | C) DEFINE A AS price > 0"); + MatchRecognize node = (MatchRecognize) roundTrip(query, Dialect.SNOWFLAKE).getFromItem(); + RowPattern.Operation sequence = (RowPattern.Operation) node.getPattern(); + assertEquals(RowPattern.Operation.Type.SEQUENCE, sequence.getType()); + RowPattern.Operation alternative = (RowPattern.Operation) sequence.getPatterns().get(1); + assertEquals(RowPattern.Operation.Type.ALTERNATION, alternative.getType()); + assertEquals("A (B | C)", sequence.toString()); + MatchRecognize oracle = (MatchRecognize) parse(query, Dialect.ORACLE).getFromItem(); + assertEquals(RowPattern.Operation.Type.ALTERNATION, + ((RowPattern.Operation) oracle.getPattern()).getType()); + assertEquals("(A B) | C", oracle.getPattern().toString()); + } + + @Test + void prefixFunctionsRemainEditableAndVisibleToExistingVisitors() throws Exception { + PlainSelect statement = roundTrip(read("oracle-running-final.sql"), Dialect.ORACLE); + MatchRecognize node = (MatchRecognize) statement.getFromItem(); + Addition sum = (Addition) node.getMeasures().get(0).getExpression(); + RowPatternFunction running = (RowPatternFunction) sum.getLeftExpression(); + RowPatternFunction finalFunction = (RowPatternFunction) sum.getRightExpression(); + assertEquals(RowPatternFunction.EvaluationMode.RUNNING, running.getEvaluationMode()); + assertEquals(RowPatternFunction.EvaluationMode.FINAL, finalFunction.getEvaluationMode()); + running.getFunction().setName("custom_measure"); + running.getFunction().setParameters( + new net.sf.jsqlparser.expression.operators.relational.ExpressionList<>( + new LongValue(42))); + assertTrue(deparse(statement).contains("RUNNING custom_measure(42)")); + assertEquals(statement.toString(), deparse(statement)); + assertTrue(StatementFeatureVisitor.analyse(statement).getUnresolvedReferences() + .contains("custom_measure")); + List columns = new ArrayList<>(); + finalFunction.accept(new ExpressionVisitorAdapter() { + @Override + public Void visit(Column column, S context) { + assertEquals("context", context); + columns.add(column.toString()); + return null; + } + }, "context"); + assertEquals(List.of("A.price"), columns); + StringBuilder builder = new StringBuilder(); + finalFunction.accept(new ExpressionDeParser(null, builder) { + @Override + public StringBuilder visit(Column column, S context) { + return getBuilder().append("A.changed"); + } + }, null); + assertEquals("FINAL SUM(A.changed)", builder.toString()); + } + + @Test + void subsetNamesAreNotDiscoveredAsTables() throws Exception { + String query = read("oracle-subset.sql"); + net.sf.jsqlparser.expression.Expression expression = + CCJSqlParserUtil.parseExpression("(" + query.replace(";", "") + ")"); + assertFalse(new TablesNamesFinder().getTables(expression).contains("U")); + MatchRecognize node = (MatchRecognize) parse(query, Dialect.ORACLE).getFromItem(); + assertEquals("U", node.getSubsets().get(0).getName()); + assertEquals(List.of("A", "B"), node.getSubsets().get(0).getVariables()); + } + + @Test + void permutationAndExclusionVisitorsKeepStructureWithoutExpansion() throws Exception { + PlainSelect statement = + roundTrip(snowflake("PATTERN (PERMUTE(A, {- B{2} -}, C)) DEFINE A AS price > 0"), + Dialect.ORACLE); + MatchRecognize node = (MatchRecognize) statement.getFromItem(); + RowPattern.Permute permute = (RowPattern.Permute) node.getPattern(); + assertEquals(3, permute.getPatterns().size()); + assertInstanceOf(RowPattern.Exclusion.class, permute.getPatterns().get(1)); + List variables = new ArrayList<>(); + permute.accept(new RowPatternVisitorAdapter() { + @Override + public Void visit(RowPattern.Variable variable, S context) { + assertEquals("pattern", context); + variables.add(variable.getName()); + return null; + } + }, "pattern"); + assertEquals(List.of("A", "B", "C"), variables); + permute.getPatterns().set(2, new RowPattern.Variable("D")); + assertTrue(deparse(statement).contains("PERMUTE(A, {- B{2} -}, D)")); + } + + @Test + void outputPivotAliasAndSampleStayAfterMatchRecognize() throws Exception { + String query = snowflake( + "MEASURES CLASSIFIER() AS label, SUM(A.price) AS total ALL ROWS PER MATCH PATTERN (A+) DEFINE A AS price > 0") + + " PIVOT (SUM(total) FOR label IN ('A')) p SAMPLE (10)"; + PlainSelect statement = roundTrip(query, Dialect.SNOWFLAKE); + MatchRecognize node = (MatchRecognize) statement.getFromItem(); + assertNotNull(node.getPivot()); + assertNotNull(node.getSampleClause()); + List columns = new ArrayList<>(); + node.accept(new FromItemVisitorAdapter(new ExpressionVisitorAdapter() { + @Override + public Void visit(Column column, S context) { + columns.add(column.toString()); + return null; + } + }), null); + assertTrue(columns.containsAll(List.of("total", "label", "A.price"))); + } + + @ParameterizedTest + @ValueSource(strings = {"SELECT running, \"final\" FROM t", "SELECT running(price) FROM t", + "SELECT * FROM t FINAL", "SELECT 'RUNNING SUM(x)' FROM t"}) + void prefixWordsKeepExistingUses(String sql) throws Exception { + roundTrip(sql, Dialect.ORACLE); + } +} diff --git a/src/test/java/net/sf/jsqlparser/statement/select/MatchRecognizeTest.java b/src/test/java/net/sf/jsqlparser/statement/select/MatchRecognizeTest.java new file mode 100644 index 0000000000..b6c952b87b --- /dev/null +++ b/src/test/java/net/sf/jsqlparser/statement/select/MatchRecognizeTest.java @@ -0,0 +1,300 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.statement.select; + +import static org.junit.jupiter.api.Assertions.*; + +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import net.sf.jsqlparser.JSQLParserException; +import net.sf.jsqlparser.expression.ExpressionVisitorAdapter; +import net.sf.jsqlparser.expression.JdbcParameter; +import net.sf.jsqlparser.expression.LongValue; +import net.sf.jsqlparser.parser.AbstractJSqlParser.Dialect; +import net.sf.jsqlparser.parser.CCJSqlParserUtil; +import net.sf.jsqlparser.schema.Column; +import net.sf.jsqlparser.schema.Table; +import net.sf.jsqlparser.statement.StatementFeatureVisitor; +import net.sf.jsqlparser.util.TablesNamesFinder; +import net.sf.jsqlparser.util.deparser.ExpressionDeParser; +import net.sf.jsqlparser.util.deparser.SelectDeParser; +import net.sf.jsqlparser.util.deparser.StatementDeParser; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +class MatchRecognizeTest { + private static String sql(String pattern) { + return "SELECT * FROM events MATCH_RECOGNIZE (PARTITION BY account_id " + + "ORDER BY seq DESC NULLS LAST MEASURES SUM(A.price) AS total, COUNT(*) AS n " + + "PATTERN (" + pattern + + ") DEFINE A AS price > 0, B AS price = 0, C AS price < 0)"; + } + + private static PlainSelect parse(String sql) throws JSQLParserException { + return (PlainSelect) CCJSqlParserUtil.parse(sql, p -> p.withDialect(Dialect.BIGQUERY)); + } + + private static MatchRecognize match(PlainSelect select) { + return assertInstanceOf(MatchRecognize.class, select.getFromItem()); + } + + private static String deparse(Select select) { + StringBuilder builder = new StringBuilder(); + select.accept(new StatementDeParser(builder)); + return builder.toString(); + } + + private static PlainSelect roundTrip(String sql) throws JSQLParserException { + PlainSelect statement = parse(sql); + String rendered = deparse(statement); + assertEquals(statement.toString(), rendered); + assertEquals(rendered, parse(rendered).toString()); + return statement; + } + + @Test + void reportedQuery() throws Exception { + String input; + try (java.io.InputStream stream = getClass().getResourceAsStream( + "/net/sf/jsqlparser/statement/select/match-recognize-2350.sql")) { + assertNotNull(stream); + input = new String(stream.readAllBytes(), StandardCharsets.UTF_8); + } + MatchRecognize match = match(roundTrip(input)); + assertEquals(4, match.getMeasures().size()); + assertEquals(3, match.getDefinitions().size()); + assertEquals(Boolean.FALSE, match.getOptions().getUseLongestMatch()); + assertEquals(MatchRecognize.SkipMode.PAST_LAST_ROW, match.getSkipMode()); + assertEquals("low mid+ high+", match.getPattern().toString()); + } + + @ParameterizedTest + @ValueSource(strings = {"A", "A B | C", "(A B | C)+?", "A??", "A*?", "A+?", + "A{3}", "A{2,}", "A{,4}?", "A{2,4}?", "A{@lo,@hi}", "A{?,?}", + "A ()", "| A", "A |", "A||B", "||A||", "^ A+ $", "^A+$$", "A$", "(^)* A", "`$` A", + "(A?)?"}) + void patternRoundTrip(String pattern) throws Exception { + roundTrip(sql(pattern)); + } + + @Test + void precedenceAndMutation() throws Exception { + MatchRecognize match = match(roundTrip(sql("A B | C"))); + RowPattern.Operation alternative = + assertInstanceOf(RowPattern.Operation.class, match.getPattern()); + assertEquals(RowPattern.Operation.Type.ALTERNATION, alternative.getType()); + RowPattern.Operation sequence = + assertInstanceOf(RowPattern.Operation.class, alternative.getPatterns().get(0)); + assertEquals(RowPattern.Operation.Type.SEQUENCE, sequence.getType()); + assertEquals("A", ((RowPattern.Variable) sequence.getPatterns().get(0)).getName()); + sequence.getPatterns().set(1, + new RowPattern.Operation(RowPattern.Operation.Type.ALTERNATION, + List.of(new RowPattern.Variable("B"), new RowPattern.Variable("C")))); + assertEquals("(A (B | C)) | C", match.getPattern().toString()); + roundTrip("SELECT * FROM " + match); + } + + @ParameterizedTest + @ValueSource(strings = {"", " OPTIONS ()", " OPTIONS (use_longest_match = FALSE)", + " OPTIONS (use_longest_match = TRUE)"}) + void optionsPreservePresence(String option) throws Exception { + String query = sql("A"); + MatchRecognize match = + match(roundTrip(query.substring(0, query.length() - 1) + option + ")")); + if (option.isEmpty()) { + assertNull(match.getOptions()); + } else if (option.equals(" OPTIONS ()")) { + assertNull(match.getOptions().getUseLongestMatch()); + } else { + assertEquals(option.contains("TRUE"), match.getOptions().getUseLongestMatch()); + } + } + + @ParameterizedTest + @ValueSource(strings = {"PAST LAST ROW", "TO NEXT ROW"}) + void skip(String skip) throws Exception { + roundTrip(sql("A+").replace(" PATTERN", " AFTER MATCH SKIP " + skip + " PATTERN")); + } + + @Test + void aliasesAndJoinsBindToTheirOwnInput() throws Exception { + String query = sql("A").replace("FROM events", "FROM events AS src") + " AS result"; + MatchRecognize node = match(roundTrip(query)); + assertEquals("src", node.getInput().getAlias().getName()); + assertEquals("result", node.getAlias().getName()); + PlainSelect join = roundTrip(query.replace("FROM events", "FROM accounts x JOIN events") + + " ON x.id = result.n"); + assertInstanceOf(Table.class, join.getFromItem()); + assertInstanceOf(MatchRecognize.class, join.getJoins().get(0).getFromItem()); + node = match(roundTrip(sql("A").replace("events", + "(events JOIN accounts ON events.account_id = accounts.id)"))); + assertInstanceOf(ParenthesedFromItem.class, node.getInput()); + List inputTables = new ArrayList<>(); + node.accept(new FromItemVisitorAdapter() { + @Override + public Void visit(Table table, S context) { + inputTables.add(table.getName()); + return null; + } + }, null); + assertEquals(List.of("events", "accounts"), inputTables); + } + + @Test + void nestedSetOperationLookaheadStillLexesAnchors() throws Exception { + String nested = "SELECT * FROM ((" + sql("^A+$$") + ") UNION ALL (" + sql("A??") + ")) u"; + roundTrip(nested); + } + + @Test + void parameterIndicesExcludeQuantifiers() throws Exception { + PlainSelect select = roundTrip("SELECT ? AS start_param FROM events MATCH_RECOGNIZE (" + + "ORDER BY seq MEASURES COUNT(*) AS n PATTERN (A?? B{?}) " + + "DEFINE A AS price > ?, B AS price < 0) WHERE n > ?"); + MatchRecognize node = match(select); + List indices = new ArrayList<>(); + node.forEachExpression( + expression -> expression.accept(new ExpressionVisitorAdapter() { + @Override + public Void visit(JdbcParameter parameter, S context) { + indices.add(parameter.getIndex()); + return null; + } + }, null)); + assertEquals(List.of(2, 3), indices); + RowPattern.Quantified bound = + (RowPattern.Quantified) ((RowPattern.Operation) node.getPattern()).getPatterns() + .get(1); + bound.setLowerBound(new LongValue(4)); + assertTrue(deparse(select).contains("B{4}")); + assertEquals(1, ((JdbcParameter) select.getSelectItem(0).getExpression()).getIndex()); + } + + @Test + void expressionAndSourceCallbacksPreserveContext() throws Exception { + PlainSelect statement = parse(sql("A{@lo,3}")); + List columns = new ArrayList<>(); + Object marker = new Object(); + MatchRecognize node = match(statement); + node.accept(new FromItemVisitorAdapter(new ExpressionVisitorAdapter() { + @Override + public Void visit(Column column, S context) { + assertSame(marker, context); + columns.add(column.toString()); + return null; + } + }) { + @Override + public Void visit(Table table, S context) { + assertSame(marker, context); + columns.add("table:" + table.getName()); + return null; + } + }, marker); + assertEquals( + List.of("table:events", "account_id", "seq", "A.price", "price", "price", "price"), + columns); + StringBuilder builder = new StringBuilder(); + ExpressionDeParser expressions = new ExpressionDeParser() { + @Override + public StringBuilder visit(Column column, S context) { + return getBuilder().append("renamed_").append(column.getColumnName()); + } + }; + expressions.setBuilder(builder); + SelectDeParser selects = new SelectDeParser(expressions, builder); + expressions.setSelectVisitor(selects); + statement.accept((SelectVisitor) selects, marker); + assertTrue(builder.toString().contains("SUM(renamed_price)")); + assertTrue(builder.toString().contains("DEFINE A AS renamed_price > 0")); + assertTrue(builder.toString().contains("ORDER BY renamed_seq DESC NULLS LAST")); + } + + @Test + void discoveryAndFeatureAnalysisReachClauseExpressions() throws Exception { + PlainSelect statement = parse(sql("A").replace("SUM(A.price)", "custom_measure(A.price)") + .replace("A AS price > 0", + "A AS custom_predicate(price) > (SELECT min(limit_value) FROM limits)")); + assertEquals(Set.of("events", "limits"), new TablesNamesFinder() + .getTables((net.sf.jsqlparser.statement.Statement) statement)); + assertTrue(StatementFeatureVisitor.analyse(statement).getUnresolvedReferences() + .containsAll(Set.of("custom_measure", "custom_predicate", "min"))); + } + + @ParameterizedTest + @ValueSource(strings = {"SELECT match_recognize FROM t", "SELECT * FROM t match_recognize", + "SELECT * FROM match_recognize", "SELECT match_recognize(price) FROM t", + "SELECT pattern('$$') FROM t", "SELECT $$body$$", "SELECT 'A' || 'B' FROM t", + "SELECT * FROM t WHERE price > ?"}) + void existingNamesAndLiterals(String sql) throws Exception { + roundTrip(sql); + } + + @ParameterizedTest + @ValueSource(strings = {"A{3}?", "^+ A", "A{1+2}", "A{2,3,4}", "A{}", "A{,}", "A**", "{- A }", + "A+++"}) + void rejectsMalformedPatterns(String pattern) { + assertThrows(JSQLParserException.class, () -> parse(sql(pattern))); + } + + @Test + void expressionTableDiscoveryKeepsNestedScopes() throws Exception { + String query = sql("A").replace("SUM(A.price)", + "SUM(A.price) + (SELECT external.price FROM lookup external)"); + net.sf.jsqlparser.expression.Expression expression = + CCJSqlParserUtil.parseExpression("(" + query + ")"); + assertEquals(Set.of("events", "lookup", "external"), + new TablesNamesFinder().getTables(expression)); + } + + @Test + void inputUnionVisitorDoesNotRequirePlainSelect() throws Exception { + MatchRecognize node = match(roundTrip(sql("A").replace("FROM events", + "FROM (SELECT * FROM events UNION ALL SELECT * FROM archive_events)"))); + List tables = new ArrayList<>(); + FromItemVisitorAdapter visitor = new FromItemVisitorAdapter() { + @Override + public Void visit(Table table, S context) { + tables.add(table.getName()); + return null; + } + }; + SelectVisitorAdapter selects = + new SelectVisitorAdapter<>(new ExpressionVisitorAdapter<>(), visitor); + visitor.setSelectVisitor(selects); + node.accept(visitor, null); + assertEquals(List.of("events", "archive_events"), tables); + } + + @Test + void nestedStructRendersEditedExpressionsInsteadOfOriginalTokens() throws Exception { + PlainSelect statement = roundTrip(sql("A").replace("SUM(A.price)", + "ARRAY_AGG(STRUCT(MATCH_ROW_NUMBER() AS row_no, CLASSIFIER() AS label, A.price))")); + net.sf.jsqlparser.expression.Function array = + (net.sf.jsqlparser.expression.Function) match(statement).getMeasures().get(0) + .getExpression(); + net.sf.jsqlparser.expression.StructType struct = + (net.sf.jsqlparser.expression.StructType) array.getParameters().get(0); + struct.getArguments().set(2, new SelectItem<>(new LongValue(99))); + assertTrue(statement.toString().contains("99")); + assertFalse(statement.toString().contains("A.price")); + assertEquals(statement.toString(), deparse(statement)); + roundTrip(statement.toString()); + } + + @Test + void boundsRespectNestingLimit() { + String nested = "(".repeat(12) + "A" + ")".repeat(12); + assertThrows(JSQLParserException.class, () -> parse(sql(nested))); + } +} diff --git a/src/test/java/net/sf/jsqlparser/statement/select/MySqlProcedureAnalyseTest.java b/src/test/java/net/sf/jsqlparser/statement/select/MySqlProcedureAnalyseTest.java new file mode 100644 index 0000000000..7d90bdeb9e --- /dev/null +++ b/src/test/java/net/sf/jsqlparser/statement/select/MySqlProcedureAnalyseTest.java @@ -0,0 +1,72 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.statement.select; + +import static net.sf.jsqlparser.test.TestUtils.assertSqlCanBeParsedAndDeparsed; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import net.sf.jsqlparser.JSQLParserException; +import net.sf.jsqlparser.parser.CCJSqlParserUtil; +import org.junit.jupiter.api.Test; + +class MySqlProcedureAnalyseTest { + + @Test + void parseWithoutParameters() throws JSQLParserException { + Select select = (Select) assertSqlCanBeParsedAndDeparsed( + "SELECT col1, col2 FROM heavy_table PROCEDURE ANALYSE()"); + + MySqlProcedureAnalyse procedureAnalyse = select.getMySqlProcedureAnalyse(); + assertNotNull(procedureAnalyse); + assertNull(procedureAnalyse.getMaxElements()); + assertNull(procedureAnalyse.getMaxMemory()); + } + + @Test + void parseOneParameter() throws JSQLParserException { + Select select = (Select) assertSqlCanBeParsedAndDeparsed( + "SELECT col1 FROM heavy_table PROCEDURE ANALYSE(10)"); + + MySqlProcedureAnalyse procedureAnalyse = select.getMySqlProcedureAnalyse(); + assertEquals("10", procedureAnalyse.getMaxElements().getStringValue()); + assertNull(procedureAnalyse.getMaxMemory()); + } + + @Test + void parseTwoParametersAfterLimit() throws JSQLParserException { + Select select = (Select) assertSqlCanBeParsedAndDeparsed( + "SELECT col1 FROM heavy_table LIMIT 20 PROCEDURE ANALYSE(10, 256)"); + + MySqlProcedureAnalyse procedureAnalyse = select.getMySqlProcedureAnalyse(); + assertEquals("10", procedureAnalyse.getMaxElements().getStringValue()); + assertEquals("256", procedureAnalyse.getMaxMemory().getStringValue()); + } + + @Test + void keepAnalyseAndProcedureAvailableAsIdentifiers() throws JSQLParserException { + assertSqlCanBeParsedAndDeparsed("SELECT analyse FROM heavy_table"); + assertSqlCanBeParsedAndDeparsed("SELECT * FROM heavy_table procedure"); + } + + @Test + void rejectInvalidForms() { + assertThrows(JSQLParserException.class, + () -> CCJSqlParserUtil.parse("SELECT * FROM t PROCEDURE ANALYSE")); + assertThrows(JSQLParserException.class, + () -> CCJSqlParserUtil.parse("SELECT * FROM t PROCEDURE ANALYSE(-1)")); + assertThrows(JSQLParserException.class, + () -> CCJSqlParserUtil.parse("SELECT * FROM t PROCEDURE ANALYSE(1, 2, 3)")); + assertThrows(JSQLParserException.class, + () -> CCJSqlParserUtil.parse("SELECT * FROM t PROCEDURE ANALYZE()")); + } +} diff --git a/src/test/java/net/sf/jsqlparser/statement/select/MySqlSelectIntoVariablesTest.java b/src/test/java/net/sf/jsqlparser/statement/select/MySqlSelectIntoVariablesTest.java new file mode 100644 index 0000000000..41acb93216 --- /dev/null +++ b/src/test/java/net/sf/jsqlparser/statement/select/MySqlSelectIntoVariablesTest.java @@ -0,0 +1,155 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.statement.select; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import net.sf.jsqlparser.JSQLParserException; +import net.sf.jsqlparser.expression.ExpressionVisitorAdapter; +import net.sf.jsqlparser.expression.UserVariable; +import net.sf.jsqlparser.parser.AbstractJSqlParser.Dialect; +import net.sf.jsqlparser.parser.CCJSqlParserUtil; +import net.sf.jsqlparser.statement.StatementFeatureVisitor; +import net.sf.jsqlparser.statement.Statement; +import net.sf.jsqlparser.statement.StatementFeatures; +import net.sf.jsqlparser.statement.StmtFeature; +import net.sf.jsqlparser.test.TestUtils; +import net.sf.jsqlparser.util.TablesNamesFinder; +import net.sf.jsqlparser.util.deparser.ExpressionDeParser; +import net.sf.jsqlparser.util.deparser.SelectDeParser; +import net.sf.jsqlparser.util.deparser.StatementDeParser; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; + +class MySqlSelectIntoVariablesTest { + private static PlainSelect parse(String sql, Dialect dialect) throws JSQLParserException { + return (PlainSelect) CCJSqlParserUtil.parse(sql, parser -> parser.withDialect(dialect)); + } + + @ParameterizedTest + @EnumSource(value = Dialect.class, names = {"MYSQL", "MARIADB"}) + void parsesOriginalReproducerAndPreservesTargets(Dialect dialect) throws Exception { + PlainSelect select = (PlainSelect) TestUtils.assertSqlCanBeParsedAndDeparsed( + "SELECT COUNT(*) INTO @countTotal FROM employee", false, + parser -> parser.withDialect(dialect)); + assertNull(select.getIntoTables()); + MySqlSelectIntoClause into = select.getMySqlSelectIntoClause(); + assertEquals(MySqlSelectIntoClause.Type.VARIABLES, into.getType()); + assertEquals(MySqlSelectIntoClause.Position.BEFORE_FROM, into.getPosition()); + assertEquals("countTotal", into.getVariables().get(0).getName()); + assertEquals(Set.of("employee"), new TablesNamesFinder<>().getTables((Statement) select)); + } + + @ParameterizedTest + @EnumSource(value = Dialect.class, names = {"MYSQL", "MARIADB"}) + void preservesPositionsQuotedNamesAndRoundTrips(Dialect dialect) throws Exception { + for (String variables : List.of("@a, @b", "@`first name`, @'second name'")) { + for (String sql : List.of("SELECT a, b INTO " + variables + " FROM t", + "SELECT a, b FROM t ORDER BY a LIMIT 1 INTO " + variables, + "SELECT a, b FROM t FOR UPDATE INTO " + variables)) { + PlainSelect select = (PlainSelect) TestUtils.assertSqlCanBeParsedAndDeparsed(sql, + false, parser -> parser.withDialect(dialect)); + MySqlSelectIntoClause into = select.getMySqlSelectIntoClause(); + assertEquals(sql.endsWith("FROM t") ? MySqlSelectIntoClause.Position.BEFORE_FROM + : MySqlSelectIntoClause.Position.TRAILING, into.getPosition()); + assertEquals(variables, into.getVariables().toString()); + StringBuilder output = new StringBuilder(); + select.accept(new StatementDeParser(output), null); + for (String rendered : List.of(select.toString(), output.toString())) { + MySqlSelectIntoClause reparsed = + parse(rendered, dialect).getMySqlSelectIntoClause(); + assertEquals(into.getPosition(), reparsed.getPosition()); + assertEquals(variables, reparsed.getVariables().toString()); + } + } + } + assertEquals("@value", parse("SELECT 1 INTO @value", dialect) + .getMySqlSelectIntoClause().getVariables().toString()); + } + + @Test + void requiresMySqlDialectAndKeepsTableTargets() throws Exception { + String sql = "SELECT a INTO @value FROM t"; + assertThrows(JSQLParserException.class, () -> CCJSqlParserUtil.parse(sql)); + for (Dialect dialect : Dialect.values()) { + if (dialect != Dialect.MYSQL && dialect != Dialect.MARIADB) { + assertThrows(JSQLParserException.class, () -> parse(sql, dialect), dialect.name()); + } + } + PlainSelect tableInto = parse("SELECT a INTO target FROM source", Dialect.SQLSERVER); + assertNull(tableInto.getMySqlSelectIntoClause()); + assertEquals("target", tableInto.getIntoTables().get(0).getName()); + } + + @Test + void visitsAndRewritesVariables() throws Exception { + PlainSelect select = parse("SELECT a, b FROM t INTO @x, @y", Dialect.MYSQL); + List names = new ArrayList<>(); + select.accept(new SelectVisitorAdapter(new ExpressionVisitorAdapter() { + @Override + public Void visit(UserVariable variable, S context) { + names.add(variable.getName()); + return null; + } + }), null); + assertEquals(List.of("x", "y"), names); + StringBuilder output = new StringBuilder(); + ExpressionDeParser expressions = new ExpressionDeParser() { + @Override + public StringBuilder visit(UserVariable variable, S context) { + return getBuilder().append("@renamed_").append(variable.getName()); + } + }; + SelectDeParser selects = new SelectDeParser(expressions, output); + expressions.setSelectVisitor(selects); + expressions.setBuilder(output); + select.accept((SelectVisitor) selects, null); + assertEquals("SELECT a, b FROM t INTO @renamed_x, @renamed_y", output.toString()); + select.getMySqlSelectIntoClause().getVariables().get(0).setName("new_x"); + TestUtils.assertDeparse(select, "SELECT a, b FROM t INTO @new_x, @y"); + } + + @Test + void reportsSessionAssignmentInsteadOfTableCreationOrResultSet() throws Exception { + StatementFeatures features = StatementFeatureVisitor.analyse( + parse("SELECT a INTO @value FROM t", Dialect.MYSQL)); + assertTrue(features.is(StmtFeature.READS_DATA)); + assertTrue(features.is(StmtFeature.MODIFIES_SESSION)); + assertFalse(features.modifiesSchema()); + assertFalse(features.modifiesData()); + assertFalse(features.returnsResultSet()); + } + + @Test + void rejectsSystemVariablesMalformedListsAndDuplicateInto() { + for (String sql : List.of("SELECT 1 INTO @@sql_mode", "SELECT 1 INTO @", + "SELECT a, b INTO @x, FROM t", "SELECT a INTO @x + 1 FROM t", + "SELECT a INTO @x FROM t INTO @y", "SELECT a INTO @x INTO target FROM t", + "SELECT a INTO target FROM t INTO @x", + "SELECT a INTO @x FROM t INTO OUTFILE '/tmp/result'")) { + assertThrows(JSQLParserException.class, () -> parse(sql, Dialect.MYSQL), sql); + } + } + + @Test + void keepsStatementBoundaries() throws Exception { + assertEquals(2, CCJSqlParserUtil.parseStatements( + "SELECT a INTO @x FROM t; SELECT @x;", + parser -> parser.withDialect(Dialect.MYSQL)).size()); + } +} diff --git a/src/test/java/net/sf/jsqlparser/statement/select/NestedBracketsPerformanceTest.java b/src/test/java/net/sf/jsqlparser/statement/select/NestedBracketsPerformanceTest.java index fc5bd785d7..37a4ef87bc 100644 --- a/src/test/java/net/sf/jsqlparser/statement/select/NestedBracketsPerformanceTest.java +++ b/src/test/java/net/sf/jsqlparser/statement/select/NestedBracketsPerformanceTest.java @@ -15,6 +15,8 @@ import org.junit.jupiter.api.Timeout; import java.util.logging.Logger; +import java.util.Arrays; +import java.util.concurrent.TimeUnit; import static net.sf.jsqlparser.test.TestUtils.assertSqlCanBeParsedAndDeparsed; import org.junit.jupiter.api.Assertions; @@ -139,22 +141,23 @@ public void testRecursiveBracketExpressionIssue1019_2() throws JSQLParserExcepti doIncreaseOfParseTimeTesting("IF(1=1, $1, 2)", "1", 20); } - @Test void testIssue2422() throws JSQLParserException { + @Test + void testIssue2422() throws JSQLParserException { String sqlStr = "SELECT\n" - + "\t\t\t\t ((((position('-' IN (\n" - + "\t\t\t\t CASE WHEN ((\n" - + "\t\t\t\t CASE WHEN (5 < 0) THEN\n" - + "\t\t\t\t 'yes'\n" - + "\t\t\t\t ELSE\n" - + "\t\t\t\t 'no'\n" - + "\t\t\t\t END) = 'yes') THEN\n" - + "\t\t\t\t SUBSTRING('2012-january-18', (((LENGTH('2012-january-18')) + (5)) + (1)), ABS((0) - (5)))\n" - + "\t\t\t\t ELSE\n" - + "\t\t\t\t SUBSTRING('2012-january-18', ((5) + (1)))\n" - + "\t\t\t\t END)) - 1) + (1)) - (5)) + (0))\n" - + "\t\t\t\tFROM\n" - + "\t\t\t\t testtable"; + + "\t\t\t\t ((((position('-' IN (\n" + + "\t\t\t\t CASE WHEN ((\n" + + "\t\t\t\t CASE WHEN (5 < 0) THEN\n" + + "\t\t\t\t 'yes'\n" + + "\t\t\t\t ELSE\n" + + "\t\t\t\t 'no'\n" + + "\t\t\t\t END) = 'yes') THEN\n" + + "\t\t\t\t SUBSTRING('2012-january-18', (((LENGTH('2012-january-18')) + (5)) + (1)), ABS((0) - (5)))\n" + + "\t\t\t\t ELSE\n" + + "\t\t\t\t SUBSTRING('2012-january-18', ((5) + (1)))\n" + + "\t\t\t\t END)) - 1) + (1)) - (5)) + (0))\n" + + "\t\t\t\tFROM\n" + + "\t\t\t\t testtable"; assertSqlCanBeParsedAndDeparsed(sqlStr); } @@ -204,18 +207,17 @@ public void testIncreaseOfParseTime() throws JSQLParserException { private void doIncreaseOfParseTimeTesting(String template, String finalExpression, int maxDepth) throws JSQLParserException { - long oldDurationTime = 2000; + long oldDurationTime = TimeUnit.SECONDS.toNanos(2); int countProblematic = 0; for (int i = 0; i < maxDepth; i++) { String sql = "SELECT " + buildRecursiveBracketExpression(template, finalExpression, i) + " FROM mytbl"; - long startTime = System.currentTimeMillis(); - assertSqlCanBeParsedAndDeparsed(sql, true, parser -> parser.withTimeOut(12000)); - long durationTime = System.currentTimeMillis() - startTime; + long durationTime = medianParseDuration(sql); if (i > 0) { - System.out.println("old duration " + oldDurationTime + " new duration time " - + durationTime + " for " + sql); + System.out.println("old duration " + TimeUnit.NANOSECONDS.toMicros(oldDurationTime) + + " us, new duration " + TimeUnit.NANOSECONDS.toMicros(durationTime) + + " us for " + sql); } if (oldDurationTime * 10 < durationTime) { countProblematic++; @@ -228,6 +230,19 @@ private void doIncreaseOfParseTimeTesting(String template, String finalExpressio } } + private long medianParseDuration(String sql) throws JSQLParserException { + // A single millisecond sample can turn scheduler or GC jitter into a tenfold increase. + // Keep the growth limit, but compare representative durations from a monotonic clock. + long[] durations = new long[5]; + for (int sample = 0; sample < durations.length; sample++) { + long startTime = System.nanoTime(); + assertSqlCanBeParsedAndDeparsed(sql, true, parser -> parser.withTimeOut(12000)); + durations[sample] = System.nanoTime() - startTime; + } + Arrays.sort(durations); + return durations[durations.length / 2]; + } + @Test @Timeout(2000) public void testRecursiveBracketExpression() { @@ -552,8 +567,7 @@ JSQLParserException.class, new Executable() { public void execute() throws Throwable { TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true); } - } - ); + }); } @Test @@ -566,8 +580,7 @@ JSQLParserException.class, new Executable() { public void execute() throws Throwable { TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true); } - } - ); + }); } @Test diff --git a/src/test/java/net/sf/jsqlparser/statement/select/OptionClauseTest.java b/src/test/java/net/sf/jsqlparser/statement/select/OptionClauseTest.java new file mode 100644 index 0000000000..113d9235f2 --- /dev/null +++ b/src/test/java/net/sf/jsqlparser/statement/select/OptionClauseTest.java @@ -0,0 +1,219 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2019 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.statement.select; + +import static net.sf.jsqlparser.test.TestUtils.assertSqlCanBeParsedAndDeparsed; + +import net.sf.jsqlparser.JSQLParserException; +import net.sf.jsqlparser.parser.CCJSqlParserUtil; +import net.sf.jsqlparser.schema.Column; +import net.sf.jsqlparser.statement.Statement; +import net.sf.jsqlparser.statement.insert.Insert; +import net.sf.jsqlparser.statement.merge.Merge; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +public class OptionClauseTest { + + @Test + public void testOptionHintIssue161() throws JSQLParserException { + String sql = + "SELECT CustomerID, PersonID, StoreID FROM cte OPTION (MAXRECURSION 2)"; + Statement statement = assertSqlCanBeParsedAndDeparsed(sql, true); + PlainSelect plainSelect = (PlainSelect) ((Select) statement).getPlainSelect(); + Assertions.assertNotNull(plainSelect.getOption()); + Assertions.assertEquals(1, plainSelect.getOption().getOptionHints().size()); + Assertions.assertEquals("MAXRECURSION", + plainSelect.getOption().getOptionHints().get(0).getName()); + Assertions.assertEquals("2", + plainSelect.getOption().getOptionHints().get(0).getValue().toString()); + } + + @Test + public void testOptionWithoutArguments() throws JSQLParserException { + assertSqlCanBeParsedAndDeparsed("SELECT * FROM t WHERE x = 1 OPTION (RECOMPILE)", true); + } + + @Test + public void testOptionMultiWordHints() throws JSQLParserException { + String sql = + "SELECT * FROM t OPTION (KEEP PLAN, KEEPFIXED PLAN, FORCE ORDER, EXPAND VIEWS, NO_PERFORMANCE_SPOOL, IGNORE_NONCLUSTERED_COLUMNSTORE_INDEX)"; + PlainSelect plainSelect = + (PlainSelect) assertSqlCanBeParsedAndDeparsed(sql, true); + Assertions.assertEquals(6, plainSelect.getOption().getOptionHints().size()); + Assertions.assertEquals("KEEP PLAN", + plainSelect.getOption().getOptionHints().get(0).getName()); + Assertions.assertEquals("FORCE ORDER", + plainSelect.getOption().getOptionHints().get(2).getName()); + } + + @Test + public void testOptionJoinAndUnionHints() throws JSQLParserException { + String sql = "SELECT * FROM t OPTION (HASH JOIN, LOOP JOIN, MERGE JOIN, CONCAT UNION)"; + PlainSelect plainSelect = + (PlainSelect) assertSqlCanBeParsedAndDeparsed(sql, true); + Assertions.assertEquals(4, plainSelect.getOption().getOptionHints().size()); + Assertions.assertEquals("HASH JOIN", + plainSelect.getOption().getOptionHints().get(0).getName()); + Assertions.assertEquals("CONCAT UNION", + plainSelect.getOption().getOptionHints().get(3).getName()); + } + + @Test + public void testOptionNumericValueHints() throws JSQLParserException { + String sql = "SELECT * FROM t OPTION (FAST 100, MAXDOP 4, MAXRECURSION 0)"; + PlainSelect plainSelect = + (PlainSelect) assertSqlCanBeParsedAndDeparsed(sql, true); + Assertions.assertEquals("FAST", plainSelect.getOption().getOptionHints().get(0).getName()); + Assertions.assertEquals("100", + plainSelect.getOption().getOptionHints().get(0).getValue().toString()); + Assertions.assertFalse(plainSelect.getOption().getOptionHints().get(0).isUseEquals()); + } + + @Test + public void testOptionEqualsValueHints() throws JSQLParserException { + String sql = "SELECT * FROM t OPTION (MAX_GRANT_PERCENT = 25.5, MIN_GRANT_PERCENT = 10)"; + PlainSelect plainSelect = + (PlainSelect) assertSqlCanBeParsedAndDeparsed(sql, true); + Assertions.assertEquals("MAX_GRANT_PERCENT", + plainSelect.getOption().getOptionHints().get(0).getName()); + Assertions.assertTrue(plainSelect.getOption().getOptionHints().get(0).isUseEquals()); + Assertions.assertEquals("25.5", + plainSelect.getOption().getOptionHints().get(0).getValue().toString()); + } + + @Test + public void testOptionParameterizedHints() throws JSQLParserException { + String sql = + "SELECT * FROM t OPTION (OPTIMIZE FOR UNKNOWN, USE HINT ('ASSUME_JOIN_PREDICATE_DEPENDS_ON_COLUMNS', 'DISABLE_PARAMETER_SNIFFING'))"; + PlainSelect plainSelect = + (PlainSelect) assertSqlCanBeParsedAndDeparsed(sql, true); + Assertions.assertEquals("OPTIMIZE FOR UNKNOWN", + plainSelect.getOption().getOptionHints().get(0).getName()); + OptionHint useHint = plainSelect.getOption().getOptionHints().get(1); + Assertions.assertEquals("USE HINT", useHint.getName()); + Assertions.assertEquals(2, useHint.getParameters().size()); + } + + @Test + public void testOptionOptimizeForAssignments() throws JSQLParserException { + String sql = "SELECT * FROM t WHERE c = @p OPTION (OPTIMIZE FOR (@p = 1))"; + PlainSelect plainSelect = + (PlainSelect) assertSqlCanBeParsedAndDeparsed(sql, true); + OptionHint optimizeFor = plainSelect.getOption().getOptionHints().get(0); + Assertions.assertEquals("OPTIMIZE FOR", optimizeFor.getName()); + Assertions.assertEquals(1, optimizeFor.getParameters().size()); + Assertions.assertEquals("@p = 1", optimizeFor.getParameters().get(0).toString()); + } + + @Test + public void testOptionTableHint() throws JSQLParserException { + String sql = "SELECT * FROM t1 JOIN t2 ON t1.a = t2.a OPTION (TABLE HINT (t2, INDEX(ix2)))"; + PlainSelect plainSelect = + (PlainSelect) assertSqlCanBeParsedAndDeparsed(sql, true); + OptionHint tableHint = plainSelect.getOption().getOptionHints().get(0); + Assertions.assertEquals("TABLE HINT", tableHint.getName()); + Assertions.assertEquals("t2", tableHint.getParameters().get(0).toString()); + Assertions.assertEquals("INDEX(ix2)", tableHint.getParameters().get(1).toString()); + } + + @Test + public void testOptionAfterUnion() throws JSQLParserException { + String sql = + "SELECT a FROM t1 UNION SELECT a FROM t2 ORDER BY a OPTION (RECOMPILE, MAXDOP 1) LIMIT 10"; + Statement statement = assertSqlCanBeParsedAndDeparsed(sql, true); + SetOperationList setOperationList = + (SetOperationList) ((Select) statement).getSelectBody(); + Assertions.assertNotNull(setOperationList.getOption()); + Assertions.assertEquals(2, setOperationList.getOption().getOptionHints().size()); + } + + @Test + public void testOptionAfterForXml() throws JSQLParserException { + assertSqlCanBeParsedAndDeparsed( + "SELECT * FROM t FOR XML PATH('row') OPTION (RECOMPILE)", true); + } + + @Test + public void testOptionInUpdateAndDelete() throws JSQLParserException { + assertSqlCanBeParsedAndDeparsed("UPDATE t SET a = 1 WHERE b = 2 OPTION (RECOMPILE)", true); + assertSqlCanBeParsedAndDeparsed("DELETE FROM t WHERE a = 1 OPTION (LOOP JOIN)", true); + + Statement update = CCJSqlParserUtil.parse("UPDATE t SET a = 1 OPTION (RECOMPILE)"); + Assertions.assertNotNull(((net.sf.jsqlparser.statement.update.Update) update).getOption()); + } + + @Test + public void testOptionOptimizeForUnknownParameter() throws JSQLParserException { + String sql = "SELECT * FROM t WHERE c = @p OPTION (OPTIMIZE FOR (@p UNKNOWN))"; + PlainSelect plainSelect = + (PlainSelect) assertSqlCanBeParsedAndDeparsed(sql, true); + OptionHint optimizeFor = plainSelect.getOption().getOptionHints().get(0); + Assertions.assertEquals(1, optimizeFor.getParameters().size()); + Column parameter = + Assertions.assertInstanceOf(Column.class, optimizeFor.getParameters().get(0)); + Assertions.assertEquals("@p UNKNOWN", parameter.getColumnName()); + } + + @Test + public void testOptionOptimizeForMixedParameters() throws JSQLParserException { + String sql = "SELECT * FROM t WHERE c = @p OPTION (OPTIMIZE FOR (@p = 1, @q UNKNOWN))"; + PlainSelect plainSelect = + (PlainSelect) assertSqlCanBeParsedAndDeparsed(sql, true); + OptionHint optimizeFor = plainSelect.getOption().getOptionHints().get(0); + Assertions.assertEquals(2, optimizeFor.getParameters().size()); + Assertions.assertEquals("@p = 1", optimizeFor.getParameters().get(0).toString()); + Column parameter = + Assertions.assertInstanceOf(Column.class, optimizeFor.getParameters().get(1)); + Assertions.assertEquals("@q UNKNOWN", parameter.getColumnName()); + } + + @Test + public void testOptionAfterInsertValuesIsRejected() { + Assertions.assertThrows(JSQLParserException.class, + () -> CCJSqlParserUtil.parse( + "INSERT INTO t (a) VALUES (1) OPTION (RECOMPILE)")); + } + + @Test + public void testOptionAfterInsertSelect() throws JSQLParserException { + String sql = "INSERT INTO t (a) SELECT a FROM s OPTION (RECOMPILE)"; + Statement statement = assertSqlCanBeParsedAndDeparsed(sql, true); + Insert insert = (Insert) statement; + Assertions.assertNotNull(insert.getSelect().getOption()); + } + + @Test + public void testOptionAfterMergeStatement() throws JSQLParserException { + String sql = + "MERGE INTO t USING s ON t.id = s.id WHEN MATCHED THEN UPDATE SET a = s.a OPTION (HASH JOIN)"; + Statement statement = assertSqlCanBeParsedAndDeparsed(sql, true); + Merge merge = (Merge) statement; + Assertions.assertNotNull(merge.getOption()); + Assertions.assertEquals("HASH JOIN", merge.getOption().getOptionHints().get(0).getName()); + } + + @Test + public void testOptionAfterMergeOutputClause() throws JSQLParserException { + assertSqlCanBeParsedAndDeparsed( + "MERGE INTO t USING s ON t.id = s.id WHEN MATCHED THEN UPDATE SET a = s.a " + + "OUTPUT deleted.a OPTION (HASH JOIN)", + false); + } + + @Test + public void testOptionAsIdentifierStillWorks() throws JSQLParserException { + // OPTION stays a non-reserved keyword usable as column and table name + assertSqlCanBeParsedAndDeparsed("SELECT option FROM t", true); + assertSqlCanBeParsedAndDeparsed("SELECT * FROM option", true); + assertSqlCanBeParsedAndDeparsed( + "SELECT a, option AS o FROM t WHERE option = 1 ORDER BY option", true); + } +} diff --git a/src/test/java/net/sf/jsqlparser/statement/select/PivotQueryTest.java b/src/test/java/net/sf/jsqlparser/statement/select/PivotQueryTest.java new file mode 100644 index 0000000000..37b6a7b270 --- /dev/null +++ b/src/test/java/net/sf/jsqlparser/statement/select/PivotQueryTest.java @@ -0,0 +1,94 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.statement.select; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import net.sf.jsqlparser.JSQLParserException; +import net.sf.jsqlparser.schema.Table; +import net.sf.jsqlparser.statement.Statement; +import net.sf.jsqlparser.statement.create.view.CreateView; +import net.sf.jsqlparser.test.TestUtils; +import net.sf.jsqlparser.util.TablesNamesFinder; +import org.junit.jupiter.api.Test; + +class PivotQueryTest { + + @Test + void testDuckDBPivotStatement() throws JSQLParserException { + String sql = "PIVOT sales ON region USING SUM(amount)"; + + Statement statement = TestUtils.assertSqlCanBeParsedAndDeparsed(sql, true); + PivotQuery pivotQuery = assertInstanceOf(PivotQuery.class, statement); + Table table = assertInstanceOf(Table.class, pivotQuery.getFromItem()); + + assertThat(table.getFullyQualifiedName()).isEqualTo("sales"); + assertThat(pivotQuery.getOnExpressions()).hasSize(1); + assertThat(pivotQuery.getUsingItems()).hasSize(1); + assertThat(TablesNamesFinder.findTables(sql)).containsExactly("sales"); + } + + @Test + void testDuckDBPivotInSubquery() throws JSQLParserException { + String sql = "SELECT * FROM (PIVOT sales ON region USING SUM(amount))"; + + Select select = (Select) TestUtils.assertSqlCanBeParsedAndDeparsed(sql, true); + ParenthesedSelect parenthesedSelect = + assertInstanceOf(ParenthesedSelect.class, select.getPlainSelect().getFromItem()); + + assertInstanceOf(PivotQuery.class, parenthesedSelect.getSelect()); + assertThat(TablesNamesFinder.findTables(sql)).containsExactly("sales"); + } + + @Test + void testDuckDBPivotFullSimplifiedSyntax() throws JSQLParserException { + String sql = "PIVOT sales ON region, category " + + "USING SUM(amount) AS total, COUNT(*) AS count " + + "GROUP BY year ORDER BY year DESC LIMIT 10"; + + PivotQuery pivotQuery = (PivotQuery) TestUtils.assertSqlCanBeParsedAndDeparsed(sql, true); + + assertThat(pivotQuery.getOnExpressions()).hasSize(2); + assertThat(pivotQuery.getUsingItems()).hasSize(2); + assertThat(pivotQuery.getGroupByExpressions()).hasSize(1); + assertThat(pivotQuery.getOrderByElements()).hasSize(1); + assertNotNull(pivotQuery.getLimit()); + } + + @Test + void testDuckDBPivotOptionalClauses() throws JSQLParserException { + TestUtils.assertSqlCanBeParsedAndDeparsed("PIVOT sales ON region", true); + TestUtils.assertSqlCanBeParsedAndDeparsed("PIVOT sales USING SUM(amount)", true); + TestUtils.assertSqlCanBeParsedAndDeparsed("PIVOT sales GROUP BY region", true); + } + + @Test + void testDuckDBPivotInCreateView() throws JSQLParserException { + String sql = "CREATE VIEW v AS PIVOT sales ON region USING SUM(amount)"; + + CreateView createView = + (CreateView) TestUtils.assertSqlCanBeParsedAndDeparsed(sql, true); + + assertInstanceOf(PivotQuery.class, createView.getSelect()); + } + + @Test + void testExistingPivotClauseStillWorks() throws JSQLParserException { + String sql = "SELECT * FROM sales " + + "PIVOT (SUM(amount) FOR region IN ('US', 'EU'))"; + + Select select = (Select) TestUtils.assertSqlCanBeParsedAndDeparsed(sql, true); + Table table = assertInstanceOf(Table.class, select.getPlainSelect().getFromItem()); + + assertNotNull(table.getPivot()); + } +} diff --git a/src/test/java/net/sf/jsqlparser/statement/select/PostgresTest.java b/src/test/java/net/sf/jsqlparser/statement/select/PostgresTest.java index 9ded167869..fc97c2f4a4 100644 --- a/src/test/java/net/sf/jsqlparser/statement/select/PostgresTest.java +++ b/src/test/java/net/sf/jsqlparser/statement/select/PostgresTest.java @@ -13,6 +13,8 @@ import net.sf.jsqlparser.expression.Alias; import net.sf.jsqlparser.expression.JsonExpression; import net.sf.jsqlparser.expression.StringValue; +import net.sf.jsqlparser.expression.operators.relational.Intersects; +import net.sf.jsqlparser.parser.AbstractJSqlParser.Dialect; import net.sf.jsqlparser.parser.CCJSqlParserUtil; import net.sf.jsqlparser.schema.Column; import net.sf.jsqlparser.schema.Table; @@ -107,11 +109,10 @@ void testNextValueIssue1863() throws JSQLParserException { } @Test - @Disabled - // wip void testDollarQuotedText() throws JSQLParserException { String sqlStr = "SELECT $tag$This\nis\na\nselect\ntest\n$tag$ from dual where a=b"; - PlainSelect st = (PlainSelect) CCJSqlParserUtil.parse(sqlStr); + PlainSelect st = (PlainSelect) CCJSqlParserUtil.parse(sqlStr, + parser -> parser.withDialect(Dialect.POSTGRESQL)); StringValue stringValue = st.getSelectItem(0).getExpression(StringValue.class); @@ -138,4 +139,44 @@ void testQuotedIdentifier() throws JSQLParserException { Assertions.assertEquals("`This is a Test Table`", table.getName()); } + + @Test + public void testPostgresHashBinaryOperator() throws JSQLParserException { + // PostgreSQL 18, Table 9.4 (Mathematical Operators): + // integral_type # integral_type -> bitwise exclusive OR + assertSqlCanBeParsedAndDeparsed("SELECT 17 # 5"); + // PostgreSQL 18, Table 9.36 (Geometric Operators): + // geometric_type # geometric_type -> point of intersection + Select select = (Select) assertSqlCanBeParsedAndDeparsed("SELECT lseg1 # lseg2"); + Intersects intersects = Assertions.assertInstanceOf(Intersects.class, + select.getPlainSelect().getSelectItem(0).getExpression()); + Assertions.assertEquals("#", intersects.getStringExpression()); + Assertions.assertInstanceOf(Column.class, intersects.getLeftExpression()); + Assertions.assertInstanceOf(Column.class, intersects.getRightExpression()); + + assertSqlCanBeParsedAndDeparsed("SELECT a # (b + 1) FROM t"); + assertSqlCanBeParsedAndDeparsed("SELECT a # b AS x FROM t"); + // same tier as `&`: binds tighter than the comparison that follows + assertSqlCanBeParsedAndDeparsed("SELECT * FROM t WHERE a # b = 1"); + } + + @Test + public void testPostgresHashOperatorKeepsIdentifiersAndJsonOperators() + throws JSQLParserException { + // `#` stays an identifier character: SQL Server `#temp`, `##global` + // (#1197), Oracle `#$tab1#` and unquoted names containing `#` + assertSqlCanBeParsedAndDeparsed("SELECT #temp FROM t"); + assertSqlCanBeParsedAndDeparsed("SELECT ##global FROM t"); + assertSqlCanBeParsedAndDeparsed("SELECT #$tab1# FROM t"); + assertSqlCanBeParsedAndDeparsed("SELECT a#b FROM t"); + // `#>` / `#>>` keep their JSON lexing (#1695) + assertSqlCanBeParsedAndDeparsed("SELECT data #> '{a,b}' FROM t"); + assertSqlCanBeParsedAndDeparsed("SELECT data #>> '{0,1}' FROM t"); + // a lone `#` can no longer be an identifier (alias, column or table + // name), all such forms fail loudly now that `#` is an operator + Assertions.assertThrows(JSQLParserException.class, + () -> CCJSqlParserUtil.parse("SELECT 1 #")); + Assertions.assertThrows(JSQLParserException.class, + () -> CCJSqlParserUtil.parse("SELECT # FROM t")); + } } diff --git a/src/test/java/net/sf/jsqlparser/statement/select/SelectASTTest.java b/src/test/java/net/sf/jsqlparser/statement/select/SelectASTTest.java index ddc578d269..5fd6a79301 100644 --- a/src/test/java/net/sf/jsqlparser/statement/select/SelectASTTest.java +++ b/src/test/java/net/sf/jsqlparser/statement/select/SelectASTTest.java @@ -12,8 +12,12 @@ import java.util.ArrayList; import java.util.List; import net.sf.jsqlparser.JSQLParserException; +import net.sf.jsqlparser.expression.Expression; +import net.sf.jsqlparser.expression.operators.conditional.AndExpression; +import net.sf.jsqlparser.parser.ASTNodeAccess; import net.sf.jsqlparser.parser.CCJSqlParserDefaultVisitor; import net.sf.jsqlparser.parser.CCJSqlParserTreeConstants; +import net.sf.jsqlparser.parser.CCJSqlParser; import net.sf.jsqlparser.parser.CCJSqlParserUtil; import net.sf.jsqlparser.parser.Node; import net.sf.jsqlparser.parser.Token; @@ -175,10 +179,30 @@ public Object visit(Node node, Object data) { assertNotNull(subSelectStart); assertNotNull(subSelectEnd); - assertEquals(32, subSelectStart.beginColumn); + // the node spans the whole IN expression, so it starts at the left operand + assertEquals(30, subSelectStart.beginColumn); assertEquals(49, subSelectEnd.endColumn); } + @Test + public void testBinaryConditionNodeStartsAtItsLeftOperand() throws JSQLParserException { + String[][] cases = { + {"SELECT * FROM t WHERE z = 0 AND a = 1", "a = 1"}, + {"SELECT * FROM t WHERE z = 0 AND a IN (1, 2)", "a IN (1, 2)"}, + {"SELECT * FROM t WHERE z = 0 AND a LIKE 'p'", "a LIKE 'p'"}, + {"SELECT * FROM t WHERE z = 0 AND a SIMILAR TO 'p'", "a SIMILAR TO 'p'"}, + {"SELECT * FROM t WHERE z = 0 AND a IS DISTINCT FROM 1", "a IS DISTINCT FROM 1"}}; + + for (String[] testCase : cases) { + String sql = testCase[0]; + Expression condition = ((AndExpression) ((PlainSelect) CCJSqlParserUtil.parse(sql)) + .getWhere()).getRightExpression(); + Node node = ((ASTNodeAccess) condition).getASTNode(); + assertEquals(testCase[1], sql.substring(node.jjtGetFirstToken().absoluteBegin - 1, + node.jjtGetLastToken().absoluteEnd - 1), sql); + } + } + @Test public void testSelectASTExtractWithCommentsIssue1580() throws JSQLParserException { String sql = @@ -212,4 +236,21 @@ public void testSelectASTExtractWithCommentsIssue1580_2() throws JSQLParserExcep assertThat(root.jjtGetFirstToken().specialToken.image) .isEqualTo("/* I want this comment */"); } + + @Test + public void testSelectASTHashLineCommentImage() throws Exception { + // a `#` comment under allowHashLineComments is a normal special + // token carrying the original `# ...` text + CCJSqlParser parser = CCJSqlParserUtil.newParser("SELECT 1 # note\nFROM t"); + parser.withHashLineComments(true); + parser.Statement(); + List comments = new ArrayList<>(); + for (Token t = parser.getASTRoot().jjtGetFirstToken(); t.next != null; t = t.next) { + for (Token sp = t.specialToken; sp != null; sp = sp.specialToken) { + comments.add(sp); + } + } + + assertThat(comments).extracting(token -> token.image).containsExactly("# note"); + } } diff --git a/src/test/java/net/sf/jsqlparser/statement/select/SelectTest.java b/src/test/java/net/sf/jsqlparser/statement/select/SelectTest.java index d0000c1389..bb3fa06562 100644 --- a/src/test/java/net/sf/jsqlparser/statement/select/SelectTest.java +++ b/src/test/java/net/sf/jsqlparser/statement/select/SelectTest.java @@ -36,12 +36,14 @@ import net.sf.jsqlparser.expression.Alias; import net.sf.jsqlparser.expression.AllValue; import net.sf.jsqlparser.expression.BinaryExpression; +import net.sf.jsqlparser.expression.CollateExpression; import net.sf.jsqlparser.expression.DoubleValue; import net.sf.jsqlparser.expression.Expression; import net.sf.jsqlparser.expression.Function; import net.sf.jsqlparser.expression.IntervalExpression; import net.sf.jsqlparser.expression.JdbcNamedParameter; import net.sf.jsqlparser.expression.JdbcParameter; +import net.sf.jsqlparser.expression.JsonExpression; import net.sf.jsqlparser.expression.LongValue; import net.sf.jsqlparser.expression.NotExpression; import net.sf.jsqlparser.expression.NullValue; @@ -59,6 +61,7 @@ import net.sf.jsqlparser.expression.operators.relational.GreaterThan; import net.sf.jsqlparser.expression.operators.relational.InExpression; import net.sf.jsqlparser.expression.operators.relational.LikeExpression; +import net.sf.jsqlparser.expression.operators.relational.ParenthesedExpressionList; import net.sf.jsqlparser.parser.AbstractJSqlParser.Dialect; import net.sf.jsqlparser.parser.CCJSqlParserManager; import net.sf.jsqlparser.parser.CCJSqlParserUtil; @@ -1374,6 +1377,47 @@ public void testGroupBy() throws JSQLParserException { assertStatementCanBeDeparsedAs(select, statement); } + @Test + public void testIssue2485OracleParenthesizedFullyQualifiedGroupByColumn() + throws JSQLParserException { + String sql = "SELECT sys.dual.dummy, count(*)\n" + + "FROM sys.dual\n" + + "GROUP BY (sys.dual.dummy), (sys.dual.dummy)"; + + Statement statement = TestUtils.assertSqlCanBeParsedAndDeparsed(sql, true); + Select select = assertInstanceOf(Select.class, statement); + PlainSelect plainSelect = select.getPlainSelect(); + assertNotNull(plainSelect.getGroupBy()); + + ExpressionList groupByExpressions = plainSelect.getGroupBy() + .getGroupByExpressionList(); + assertEquals(2, groupByExpressions.size()); + for (Expression groupByExpression : groupByExpressions) { + ParenthesedExpressionList parenthesedExpression = assertInstanceOf( + ParenthesedExpressionList.class, groupByExpression); + Column column = assertInstanceOf(Column.class, parenthesedExpression.get(0)); + assertEquals("sys.dual.dummy", column.getFullyQualifiedName()); + } + } + + @Test + public void testIssue2485OracleParenthesizedFourPartGroupByColumn() + throws JSQLParserException { + String sql = "SELECT 1\n" + + "FROM sys.dual\n" + + "GROUP BY sys.dual.dummy, (catalog.sys.dual.dummy)"; + + Statement statement = TestUtils.assertSqlCanBeParsedAndDeparsed(sql, true); + Select select = assertInstanceOf(Select.class, statement); + ExpressionList groupByExpressions = select.getPlainSelect().getGroupBy() + .getGroupByExpressionList(); + assertEquals(2, groupByExpressions.size()); + ParenthesedExpressionList parenthesedExpression = assertInstanceOf( + ParenthesedExpressionList.class, groupByExpressions.get(1)); + Column column = assertInstanceOf(Column.class, parenthesedExpression.get(0)); + assertEquals("catalog.sys.dual.dummy", column.getFullyQualifiedName()); + } + @Test public void testHaving() throws JSQLParserException { String statement = @@ -1907,6 +1951,29 @@ public void testSelectFunction() throws JSQLParserException { assertSqlCanBeParsedAndDeparsed(statement); } + @Test + public void testSelectWithoutFromRetainsWhereGroupByHaving() throws JSQLParserException { + assertSqlCanBeParsedAndDeparsed("SELECT 1 WHERE 1 = 1"); + PlainSelect plainSelect = + (PlainSelect) assertSqlCanBeParsedAndDeparsed("SELECT 1 GROUP BY 1 HAVING 1 = 1"); + assertNotNull(plainSelect.getGroupBy()); + assertNotNull(plainSelect.getHaving()); + } + + @Test + public void testSelectWithoutFromRetainsWindowAndQualify() throws JSQLParserException { + PlainSelect plainSelect = + (PlainSelect) assertSqlCanBeParsedAndDeparsed("SELECT 1 WINDOW w AS (ORDER BY 1)"); + assertEquals(1, plainSelect.getWindowDefinitions().size()); + assertSqlCanBeParsedAndDeparsed("SELECT 1 QUALIFY 1 = 1"); + } + + @Test + public void testSelectWithoutFromRetainsHierarchicalAndPreferring() throws JSQLParserException { + assertSqlCanBeParsedAndDeparsed("SELECT 1 START WITH 1 = 1 CONNECT BY LEVEL <= 1"); + assertSqlCanBeParsedAndDeparsed("SELECT 1 PREFERRING HIGH 1"); + } + @Test public void testWeirdSelect() throws JSQLParserException { String sql = @@ -3181,6 +3248,13 @@ public void testJsonExpression() throws JSQLParserException { assertSqlCanBeParsedAndDeparsed( "SELECT json_typeof(sale->'items'), json_typeof(sale->'items'->'quantity') FROM sales"); + Select jsonSelect = (Select) CCJSqlParserUtil.parse("SELECT data->'images' FROM instagram"); + assertInstanceOf(JsonExpression.class, + jsonSelect.getPlainSelect().getSelectItem(0).getExpression()); + jsonSelect = (Select) CCJSqlParserUtil.parse("SELECT data->>'images' FROM instagram"); + assertInstanceOf(JsonExpression.class, + jsonSelect.getPlainSelect().getSelectItem(0).getExpression()); + // The following staments can be parsed but not deparsed for (String statement : new String[] { "SELECT doc->'site_name' FROM websites WHERE doc @> '{\"tags\":[{\"term\":\"paris\"}, {\"term\":\"food\"}]}'", @@ -4160,6 +4234,38 @@ public void testEscaped() throws JSQLParserException { assertSqlCanBeParsedAndDeparsed("SELECT _utf8'testvalue'"); } + @ParameterizedTest + @ValueSource(strings = {"_utf8mb4'testvalue'", "_utf8mb4 'testvalue'", + "_utf8 'testvalue'", "_latin1'testvalue'", "_binary'testvalue'", + "_ISO8859_1 'testvalue'"}) + public void testCharacterSetIntroducedString(String expression) throws JSQLParserException { + Select select = (Select) CCJSqlParserUtil.parse("SELECT " + expression); + SelectItem selectItem = select.getPlainSelect().getSelectItem(0); + StringValue stringValue = assertInstanceOf(StringValue.class, selectItem.getExpression()); + + assertEquals(expression.substring(0, expression.indexOf('\'')).trim(), + stringValue.getPrefix()); + assertEquals("testvalue", stringValue.getValue()); + assertNull(selectItem.getAlias()); + } + + @Test + public void testCharacterSetIntroducedStringWithCollate() throws JSQLParserException { + String sql = "SELECT _utf8mb4'some text' COLLATE utf8mb4_unicode_ci AS custom_string"; + Select select = (Select) CCJSqlParserUtil.parse(sql); + SelectItem selectItem = select.getPlainSelect().getSelectItem(0); + CollateExpression collateExpression = + assertInstanceOf(CollateExpression.class, selectItem.getExpression()); + StringValue stringValue = assertInstanceOf(StringValue.class, + collateExpression.getLeftExpression()); + + assertEquals("_utf8mb4", stringValue.getPrefix()); + assertEquals("some text", stringValue.getValue()); + assertEquals("utf8mb4_unicode_ci", collateExpression.getCollate()); + assertEquals("custom_string", selectItem.getAlias().getName()); + assertEquals(sql, select.toString()); + } + @Test public void testIssue563MultiSubJoin() throws JSQLParserException { assertSqlCanBeParsedAndDeparsed( @@ -6527,4 +6633,33 @@ void testIssue2445FunctionWithBracketParameters() throws JSQLParserException { " , cast(ex.value_date - f.appraisal_date AS DECIMAL) / 365 )"; TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true); } + + @Test + void testClickHouseArrayJoin() throws Exception { + TestUtils.assertSqlCanBeParsedAndDeparsed("SELECT s, x FROM t LEFT ARRAY JOIN arr AS x", + true); + TestUtils.assertSqlCanBeParsedAndDeparsed("SELECT s, x FROM t ARRAY JOIN arr AS x", true); + TestUtils.assertSqlCanBeParsedAndDeparsed("SELECT s, arr FROM t ARRAY JOIN arr", true); + TestUtils.assertSqlCanBeParsedAndDeparsed("SELECT s, arr FROM t LEFT ARRAY JOIN arr", true); + + TestUtils.assertSqlCanBeParsedAndDeparsed( + "SELECT s, x, o.name FROM t LEFT ARRAY JOIN arr AS x INNER JOIN other_table o ON t.id = o.t_id", + true); + TestUtils.assertSqlCanBeParsedAndDeparsed( + "WITH exploded AS (SELECT s, x FROM t LEFT ARRAY JOIN arr AS x) SELECT * FROM exploded", + true); + + PlainSelect selectLeft = + (PlainSelect) CCJSqlParserUtil.parse("SELECT s, x FROM t LEFT ARRAY JOIN arr AS x"); + Join joinLeft = selectLeft.getJoins().get(0); + Assertions.assertTrue(joinLeft.isArray(), "Should be an array join"); + Assertions.assertTrue(joinLeft.isLeft(), "Should be a left join"); + + PlainSelect selectInner = + (PlainSelect) CCJSqlParserUtil.parse("SELECT s, x FROM t ARRAY JOIN arr AS x"); + Join joinInner = selectInner.getJoins().get(0); + Assertions.assertTrue(joinInner.isArray(), "Should be an array join"); + Assertions.assertFalse(joinInner.isLeft(), "Should not be a left join"); + } + } diff --git a/src/test/java/net/sf/jsqlparser/statement/select/SelectXMLParseTest.java b/src/test/java/net/sf/jsqlparser/statement/select/SelectXMLParseTest.java new file mode 100644 index 0000000000..9bb5352558 --- /dev/null +++ b/src/test/java/net/sf/jsqlparser/statement/select/SelectXMLParseTest.java @@ -0,0 +1,65 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.statement.select; + +import net.sf.jsqlparser.JSQLParserException; +import org.junit.jupiter.api.Test; + +import static net.sf.jsqlparser.test.TestUtils.assertSqlCanBeParsedAndDeparsed; + +/** + * XML constructor functions taking a bare leading keyword before their first argument, e.g. + * {@code XMLPARSE(CONTENT expr)}. + */ +public class SelectXMLParseTest { + + @Test + public void testXmlParseContent() throws JSQLParserException { + assertSqlCanBeParsedAndDeparsed("SELECT xmlparse(content a) FROM mytable"); + } + + @Test + public void testXmlParseDocument() throws JSQLParserException { + assertSqlCanBeParsedAndDeparsed("SELECT xmlparse(document a) FROM mytable"); + } + + @Test + public void testXmlParseUpperCaseKeyword() throws JSQLParserException { + assertSqlCanBeParsedAndDeparsed("SELECT xmlparse(CONTENT a) FROM mytable"); + } + + @Test + public void testXmlAggWithXmlParse() throws JSQLParserException { + assertSqlCanBeParsedAndDeparsed( + "SELECT xmlagg(xmlparse(content sn.CODE || ',') ORDER BY sn.CODE).getclobval() AS SN FROM GV_SYS_CODEINFO sn"); + } + + @Test + public void testXmlElementName() throws JSQLParserException { + assertSqlCanBeParsedAndDeparsed("SELECT xmlelement(name a, b) FROM mytable"); + } + + @Test + public void testXmlForestName() throws JSQLParserException { + assertSqlCanBeParsedAndDeparsed("SELECT xmlforest(name a) FROM mytable"); + } + + /** + * CONTENT, DOCUMENT and NAME must stay plain identifiers everywhere else. + */ + @Test + public void testKeywordsRemainIdentifiers() throws JSQLParserException { + assertSqlCanBeParsedAndDeparsed("SELECT content, document, name FROM mytable"); + assertSqlCanBeParsedAndDeparsed("SELECT content FROM content c WHERE c.document = 1"); + assertSqlCanBeParsedAndDeparsed("SELECT t.name AS document FROM mytable t"); + assertSqlCanBeParsedAndDeparsed("SELECT f(content) FROM mytable"); + assertSqlCanBeParsedAndDeparsed("SELECT xmlparse(content) FROM mytable"); + } +} diff --git a/src/test/java/net/sf/jsqlparser/statement/select/SetOperationModifierTest.java b/src/test/java/net/sf/jsqlparser/statement/select/SetOperationModifierTest.java index a30f3e2fa0..5af4513f0a 100644 --- a/src/test/java/net/sf/jsqlparser/statement/select/SetOperationModifierTest.java +++ b/src/test/java/net/sf/jsqlparser/statement/select/SetOperationModifierTest.java @@ -16,6 +16,7 @@ import net.sf.jsqlparser.JSQLParserException; import net.sf.jsqlparser.parser.CCJSqlParserUtil; import net.sf.jsqlparser.statement.Statement; +import org.junit.jupiter.api.Test; import org.junit.jupiter.api.parallel.Execution; import org.junit.jupiter.api.parallel.ExecutionMode; import org.junit.jupiter.params.ParameterizedTest; @@ -50,6 +51,72 @@ void testSetOperationModifierRoundTrip(String sql) throws JSQLParserException { assertSqlCanBeParsedAndDeparsed(sql); } + /** + * The grammar records the modifier verbatim from the source, so the quantifier survives in + * whatever case it was written. Every case spells the same set operation. + * + * @see #2419 + */ + @ParameterizedTest + @ValueSource(strings = {"ALL", "all", "All", "aLL"}) + void testAllModifierRecognisedInAnyCase(String quantifier) throws JSQLParserException { + SetOperation operation = firstOperation( + "SELECT a FROM t1 UNION " + quantifier + " SELECT a FROM t2"); + assertTrue(operation.isAll(), "UNION " + quantifier + " should be an ALL union"); + assertFalse(operation.isDistinct(), "UNION " + quantifier + " is not DISTINCT"); + } + + @ParameterizedTest + @ValueSource(strings = {"DISTINCT", "distinct", "Distinct"}) + void testDistinctModifierRecognisedInAnyCase(String quantifier) throws JSQLParserException { + SetOperation operation = firstOperation( + "SELECT a FROM t1 UNION " + quantifier + " SELECT a FROM t2"); + assertTrue(operation.isDistinct(), "UNION " + quantifier + " should be a DISTINCT union"); + assertFalse(operation.isAll(), "UNION " + quantifier + " is not ALL"); + } + + @ParameterizedTest + @ValueSource(strings = {"EXCEPT", "INTERSECT", "MINUS"}) + void testAllModifierRecognisedInAnyCaseForEverySetOperation(String setOperation) + throws JSQLParserException { + assertTrue(firstOperation("SELECT a FROM t1 " + setOperation + " all SELECT a FROM t2") + .isAll(), setOperation + " all should be an ALL operation"); + } + + /** + * A plain set operation carries no quantifier, and neither predicate may claim one. + */ + @Test + void testUnqualifiedSetOperationIsNeitherAllNorDistinct() throws JSQLParserException { + SetOperation operation = firstOperation("SELECT a FROM t1 UNION SELECT a FROM t2"); + assertFalse(operation.isAll()); + assertFalse(operation.isDistinct()); + } + + /** + * The setters record what they are told rather than the keyword they are named after. + */ + @Test + void testSettersHonourTheirArgument() { + UnionOp union = new UnionOp(); + + union.setAll(true); + assertTrue(union.isAll()); + union.setAll(false); + assertFalse(union.isAll(), "setAll(false) must not leave an ALL modifier behind"); + + union.setDistinct(true); + assertTrue(union.isDistinct()); + union.setDistinct(false); + assertFalse(union.isDistinct(), "setDistinct(false) must not leave a DISTINCT modifier"); + } + + private static SetOperation firstOperation(String sql) throws JSQLParserException { + Statement statement = CCJSqlParserUtil.parse(sql); + assertInstanceOf(SetOperationList.class, statement); + return ((SetOperationList) statement).getOperations().get(0); + } + @ParameterizedTest @MethodSource("provideModifierLeakCases") void testModifierDoesNotLeakBetweenOperators(String sql, String forbidden) diff --git a/src/test/java/net/sf/jsqlparser/statement/select/SqlServerXmlMethodTest.java b/src/test/java/net/sf/jsqlparser/statement/select/SqlServerXmlMethodTest.java new file mode 100644 index 0000000000..eabfb60b4c --- /dev/null +++ b/src/test/java/net/sf/jsqlparser/statement/select/SqlServerXmlMethodTest.java @@ -0,0 +1,139 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.statement.select; + +import static net.sf.jsqlparser.test.TestUtils.assertSqlCanBeParsedAndDeparsed; +import static org.junit.jupiter.api.Assertions.*; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import net.sf.jsqlparser.JSQLParserException; +import net.sf.jsqlparser.expression.ExpressionVisitorAdapter; +import net.sf.jsqlparser.expression.Function; +import net.sf.jsqlparser.expression.MethodCallExpression; +import net.sf.jsqlparser.expression.StringValue; +import net.sf.jsqlparser.parser.AbstractJSqlParser.Dialect; +import net.sf.jsqlparser.parser.CCJSqlParserUtil; +import net.sf.jsqlparser.parser.feature.Feature; +import net.sf.jsqlparser.parser.feature.FeatureConfiguration; +import net.sf.jsqlparser.statement.Statement; +import net.sf.jsqlparser.statement.StatementVisitorAdapter; +import net.sf.jsqlparser.util.TablesNamesFinder; +import net.sf.jsqlparser.util.deparser.ExpressionDeParser; +import net.sf.jsqlparser.util.deparser.SelectDeParser; +import net.sf.jsqlparser.util.deparser.StatementDeParser; +import net.sf.jsqlparser.util.validation.Validation; +import net.sf.jsqlparser.util.validation.feature.FeaturesAllowed; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +class SqlServerXmlMethodTest { + private static final String SIMPLE = + "SELECT (SELECT body FROM docs FOR XML PATH (''), TYPE).value('.', 'varchar(max)')"; + + private static PlainSelect parse(String sql) throws Exception { + return (PlainSelect) CCJSqlParserUtil.parse(sql, p -> p.withDialect(Dialect.SQLSERVER)); + } + + @Test + void parsesOriginalStuffQueryIssue386() throws Exception { + String sql = "SELECT (STUFF((SELECT '|' + person_name FROM person " + + "JOIN person_group ON person.person_id = person_group.person_id " + + "WHERE person_group.group_id = 1 FOR XML PATH(''), TYPE)" + + ".value('.', 'varchar(max)'), 1, 1, '')) AS person_name"; + Statement statement = assertSqlCanBeParsedAndDeparsed(sql, true, + p -> p.withDialect(Dialect.SQLSERVER)); + assertEquals(Set.of("person", "person_group"), + new TablesNamesFinder().getTables(statement)); + assertEquals(statement.toString(), parse(statement.toString()).toString()); + } + + @ParameterizedTest + @ValueSource(strings = {"value('.', 'varchar(max)')", "query('/root')", "exist('/root')", + "query('/root').value('.', 'int')"}) + void preservesMethodsAndChains(String method) throws Exception { + String sql = "SELECT (SELECT body FROM docs FOR XML PATH(''), TYPE)." + method; + PlainSelect select = (PlainSelect) assertSqlCanBeParsedAndDeparsed(sql, true, + p -> p.withDialect(Dialect.SQLSERVER)); + MethodCallExpression call = (MethodCallExpression) select.getSelectItem(0).getExpression(); + assertInstanceOf(ParenthesedSelect.class, call.getExpression()); + assertEquals(method.substring(0, method.indexOf('(')), call.getMethod().getName()); + assertEquals(select.toString(), parse(select.toString()).toString()); + assertEquals(Set.of("docs"), new TablesNamesFinder().getTables((Statement) select)); + } + + @Test + void visitsReceiverAndMethodArgumentsWithContext() throws Exception { + List values = new ArrayList<>(); + ExpressionVisitorAdapter expressions = new ExpressionVisitorAdapter() { + @Override + public Void visit(StringValue value, S context) { + assertEquals("context", context); + values.add(value.getValue()); + return null; + } + }; + SelectVisitorAdapter selects = new SelectVisitorAdapter<>(expressions); + expressions.setSelectVisitor(selects); + parse(SIMPLE).accept(new StatementVisitorAdapter<>(selects), "context"); + assertTrue(values.contains(".")); + assertTrue(values.contains("varchar(max)")); + assertEquals(1, values.stream().filter("."::equals).count()); + } + + @Test + void editsAndDeparsesMethodArguments() throws Exception { + PlainSelect select = parse(SIMPLE); + MethodCallExpression call = (MethodCallExpression) select.getSelectItem(0).getExpression(); + call.setMethod(new Function("query", new StringValue("'/root'"))); + StringBuilder output = new StringBuilder(); + ExpressionDeParser expressions = new ExpressionDeParser() { + @Override + public StringBuilder visit(StringValue value, S context) { + return getBuilder().append("'changed'"); + } + }; + select.accept(new StatementDeParser(expressions, new SelectDeParser(), output)); + assertTrue(output.toString().endsWith(".query('changed')")); + assertTrue(select.toString().endsWith(".query('/root')")); + assertEquals(output.toString(), parse(output.toString()).toString()); + } + + @Test + void gatesDialectAndRetainsRowNavigation() throws Exception { + assertThrows(JSQLParserException.class, () -> CCJSqlParserUtil.parse(SIMPLE)); + for (Dialect dialect : Dialect.values()) { + if (dialect != Dialect.SQLSERVER) { + assertThrows(JSQLParserException.class, + () -> CCJSqlParserUtil.parse(SIMPLE, p -> p.withDialect(dialect))); + } + } + assertSqlCanBeParsedAndDeparsed("SELECT (row_value).field FROM t"); + assertSqlCanBeParsedAndDeparsed("SELECT (row_value).field COLLATE en_US FROM t"); + assertThrows(JSQLParserException.class, + () -> parse(SIMPLE.substring(0, SIMPLE.length() - 1))); + assertEquals(2, CCJSqlParserUtil.parseStatements(SIMPLE + "; SELECT 1;", + p -> p.withDialect(Dialect.SQLSERVER)).size()); + } + + @Test + void validatesMethodAsFunction() { + String sql = "SELECT 1 WHERE " + SIMPLE.substring("SELECT ".length()) + " = 'value'"; + FeatureConfiguration config = new FeatureConfiguration().setValue(Feature.dialect, + Dialect.SQLSERVER.name()); + assertTrue(new Validation(config, List.of(new FeaturesAllowed(Feature.values())), sql) + .validate().isEmpty()); + assertFalse(new Validation(config, + List.of(new FeaturesAllowed(Feature.values()).remove(Feature.function)), sql) + .validate().isEmpty()); + } +} diff --git a/src/test/java/net/sf/jsqlparser/statement/select/WithCycleClauseTest.java b/src/test/java/net/sf/jsqlparser/statement/select/WithCycleClauseTest.java new file mode 100644 index 0000000000..f2f6ce86a1 --- /dev/null +++ b/src/test/java/net/sf/jsqlparser/statement/select/WithCycleClauseTest.java @@ -0,0 +1,140 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.statement.select; + +import net.sf.jsqlparser.JSQLParserException; +import net.sf.jsqlparser.expression.ExpressionVisitorAdapter; +import net.sf.jsqlparser.expression.StringValue; +import net.sf.jsqlparser.parser.CCJSqlParserUtil; +import net.sf.jsqlparser.parser.feature.Feature; +import net.sf.jsqlparser.schema.Column; +import net.sf.jsqlparser.test.TestUtils; +import net.sf.jsqlparser.util.deparser.ExpressionDeParser; +import net.sf.jsqlparser.util.deparser.SelectDeParser; +import net.sf.jsqlparser.util.deparser.StatementDeParser; +import net.sf.jsqlparser.util.validation.Validation; +import net.sf.jsqlparser.util.validation.ValidationError; +import net.sf.jsqlparser.util.validation.feature.FeaturesAllowed; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import java.util.ArrayList; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +class WithCycleClauseTest { + private static final String CTE = "WITH RECURSIVE walk(id, parent) AS " + + "(SELECT id, parent FROM nodes UNION ALL " + + "SELECT n.id, n.parent FROM nodes n JOIN walk w ON n.parent = w.id) "; + + @ParameterizedTest + @ValueSource(strings = { + "CYCLE id SET is_cycle USING path", + "CYCLE id, parent SET is_cycle TO 'Y' DEFAULT 'N' USING path", + "CYCLE id SET is_cycle TO 1 DEFAULT 0 USING path", + "CYCLE id SET is_cycle TO true DEFAULT false USING path", + "SEARCH DEPTH FIRST BY id SET seq CYCLE id SET is_cycle USING path", + "SEARCH BREADTH FIRST BY id, parent SET seq CYCLE id, parent SET is_cycle USING path" + }) + void parsesRecursiveCycleVariants(String clause) throws Exception { + Select select = (Select) TestUtils.assertSqlCanBeParsedAndDeparsed( + CTE + clause + " SELECT * FROM walk", true); + assertNotNull(select.getWithItemsList().get(0).getCycleClause()); + assertEquals(select.toString(), CCJSqlParserUtil.parse(select.toString()).toString()); + } + + @Test + void retainsQuotedNamesAndTypedMarkValues() throws Exception { + Select select = (Select) CCJSqlParserUtil.parse(CTE + + "CYCLE id, parent SET \"Cycle\" TO 'yes' DEFAULT 'no' USING \"Path\" SELECT * FROM walk"); + WithCycleClause cycle = select.getWithItemsList().get(0).getCycleClause(); + assertEquals(2, cycle.getCycleColumns().size()); + assertEquals("parent", cycle.getCycleColumns().get(1).getColumnName()); + assertEquals("\"Cycle\"", cycle.getMarkColumnName()); + assertEquals("yes", ((StringValue) cycle.getMarkValue()).getValue()); + assertEquals("no", ((StringValue) cycle.getMarkDefault()).getValue()); + assertEquals("\"Path\"", cycle.getPathColumnName()); + } + + @Test + void preservesImplicitBooleanDefaultsAndMultipleCtes() throws Exception { + Select select = (Select) TestUtils.assertSqlCanBeParsedAndDeparsed(CTE + + "CYCLE id SET is_cycle USING path, chosen AS (SELECT * FROM walk) SELECT * FROM chosen", + true); + assertNull(select.getWithItemsList().get(0).getCycleClause().getMarkValue()); + assertNull(select.getWithItemsList().get(0).getCycleClause().getMarkDefault()); + assertNull(select.getWithItemsList().get(1).getCycleClause()); + } + + @ParameterizedTest + @ValueSource(strings = {"CYCLE id SET flag", "CYCLE id SET flag TO 'Y' USING path", + "CYCLE id SET flag DEFAULT 'N' USING path", "CYCLE SET flag USING path"}) + void rejectsIncompleteCycleClauses(String clause) { + assertThrows(JSQLParserException.class, + () -> CCJSqlParserUtil.parse(CTE + clause + " SELECT * FROM walk")); + } + + @Test + void visitsCycleExpressions() throws Exception { + Select select = (Select) CCJSqlParserUtil.parse(CTE + + "CYCLE id SET flag TO 'Y' DEFAULT 'N' USING path SELECT * FROM walk"); + List values = new ArrayList<>(); + select.accept(new SelectVisitorAdapter(new ExpressionVisitorAdapter() { + @Override + public Void visit(StringValue value, S context) { + values.add(value.getValue()); + return null; + } + }), null); + assertEquals(List.of("Y", "N"), values); + } + + @ParameterizedTest + @ValueSource(strings = {"TO upper('Y') DEFAULT 'N'", "TO 'Y' DEFAULT lower('N')"}) + void validatesCycleMarkExpressionsAlongsideTheCteBody(String markValues) { + String sql = CTE + "CYCLE id SET flag " + markValues + " USING path SELECT * FROM walk"; + assertTrue( + Validation.validate(List.of(new FeaturesAllowed(Feature.values())), sql).isEmpty()); + + FeaturesAllowed allowed = new FeaturesAllowed(Feature.values()).remove(Feature.function); + List errors = Validation.validate(List.of(allowed), sql); + assertEquals(1, errors.size()); + assertNotNull(errors.get(0).getParsedStatement()); + assertEquals(1, errors.get(0).getErrors().size()); + assertEquals("function not allowed.", + errors.get(0).getErrors().iterator().next().getMessage()); + } + + @Test + void sharesSearchAndCycleRenderingWithExpressionDeparser() throws Exception { + Select select = (Select) CCJSqlParserUtil.parse(CTE + + "SEARCH DEPTH FIRST BY id SET seq CYCLE id SET flag TO 'Y' DEFAULT 'N' USING path SELECT * FROM walk"); + StringBuilder sql = new StringBuilder(); + ExpressionDeParser expressions = new ExpressionDeParser() { + @Override + public StringBuilder visit(Column column, S context) { + getBuilder().append("changed_").append(column.getColumnName()); + return getBuilder(); + } + + @Override + public StringBuilder visit(StringValue value, S context) { + getBuilder().append("'changed_").append(value.getValue()).append("'"); + return getBuilder(); + } + }; + select.accept(new StatementDeParser(expressions, new SelectDeParser(), sql), null); + assertTrue(sql.toString().contains( + "SEARCH DEPTH FIRST BY changed_id SET seq CYCLE changed_id SET flag TO 'changed_Y' DEFAULT 'changed_N' USING path"), + sql.toString()); + } +} diff --git a/src/test/java/net/sf/jsqlparser/statement/select/WithItemTest.java b/src/test/java/net/sf/jsqlparser/statement/select/WithItemTest.java index f84bfa4b7c..ecc405a977 100644 --- a/src/test/java/net/sf/jsqlparser/statement/select/WithItemTest.java +++ b/src/test/java/net/sf/jsqlparser/statement/select/WithItemTest.java @@ -10,16 +10,117 @@ package net.sf.jsqlparser.statement.select; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import java.util.concurrent.CancellationException; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.stream.Collectors; +import java.util.stream.IntStream; import net.sf.jsqlparser.JSQLParserException; +import net.sf.jsqlparser.expression.Function; +import net.sf.jsqlparser.parser.CCJSqlParser; +import net.sf.jsqlparser.parser.CCJSqlParserConstants; +import net.sf.jsqlparser.parser.CCJSqlParserUtil; +import net.sf.jsqlparser.parser.ParseException; +import net.sf.jsqlparser.parser.Token; import net.sf.jsqlparser.test.TestUtils; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Timeout; import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; import org.junit.jupiter.params.provider.ValueSource; +import org.mockito.Mockito; class WithItemTest { + @ParameterizedTest + @CsvSource({"2047, 1", "2047, -1", "2048, 1", "4096, 1"}) + void testLargeExpressionAlias(int arguments, String firstArgument) throws JSQLParserException { + String expression = "coalesce(" + firstArgument + ", 1".repeat(arguments - 1) + ")"; + Select select = (Select) TestUtils.assertSqlCanBeParsedAndDeparsed( + "WITH " + expression + " AS v, t AS (SELECT v) SELECT v FROM t", true); + WithItem item = select.getWithItemsList().get(0); + Function function = assertInstanceOf(Function.class, item.getExpression()); + assertEquals("v", item.getAliasName()); + assertNull(item.getParenthesedStatement()); + assertEquals("coalesce", function.getName()); + assertEquals(arguments, function.getParameters().size()); + assertEquals(firstArgument, function.getParameters().get(0).toString()); + assertEquals("1", function.getParameters().get(arguments - 1).toString()); + assertNotNull(select.getWithItemsList().get(1).getParenthesedStatement()); + } + + @ParameterizedTest + @ValueSource(ints = {2047, 2048}) + void testLargeCteColumnList(int columns) throws JSQLParserException { + String names = IntStream.range(0, columns).mapToObj(i -> "c" + i) + .collect(Collectors.joining(", ")); + Select select = (Select) TestUtils.assertSqlCanBeParsedAndDeparsed( + "WITH t(" + names + ") AS (SELECT 1" + ", 1".repeat(columns - 1) + + ") SELECT * FROM t", + true); + WithItem item = select.getWithItemsList().get(0); + assertEquals("t", item.getAliasName()); + assertNull(item.getExpression()); + assertNotNull(item.getParenthesedStatement()); + assertEquals(columns, item.getWithItemList().size()); + assertEquals("c" + (columns - 1), item.getWithItemList().get(columns - 1).toString()); + } + + @Test + void testLargeNestedExpressionAlias() throws JSQLParserException { + String expression = "concat(concat('(', ')')" + ", 'x'".repeat(2047) + ")"; + Select select = (Select) TestUtils.assertSqlCanBeParsedAndDeparsed( + "WITH " + expression + " AS v SELECT v", true); + Function function = assertInstanceOf(Function.class, + select.getWithItemsList().get(0).getExpression()); + assertEquals(2048, function.getParameters().size()); + assertEquals("concat('(', ')')", function.getParameters().get(0).toString()); + } + + @ParameterizedTest + @ValueSource(ints = {1, 64, 65}) + void testQualifiedExpressionAlias(int qualifiers) throws JSQLParserException { + String name = "s.".repeat(qualifiers) + "coalesce"; + Select select = (Select) TestUtils.assertSqlCanBeParsedAndDeparsed( + "WITH " + name + "(1, 2) AS v SELECT v", true); + Function function = assertInstanceOf(Function.class, + select.getWithItemsList().get(0).getExpression()); + assertEquals(name, function.getName()); + assertEquals(2, function.getParameters().size()); + } + + @ParameterizedTest + @ValueSource(ints = {2, 2048}) + @Timeout(5) + void testUnclosedExpressionAlias(int arguments) { + String sql = "WITH coalesce(1" + ", 1".repeat(arguments - 1); + assertThrows(ParseException.class, () -> CCJSqlParserUtil.newParser(sql).Statement()); + } + + @Test + void testInterruptedWithItemLookaheadStopsReading() { + CCJSqlParser parser = CCJSqlParserUtil.newParser( + "WITH coalesce(1" + ", 1".repeat(512) + ") AS v SELECT v"); + AtomicBoolean reachedAlias = new AtomicBoolean(); + parser.token_source = Mockito.spy(parser.token_source); + Mockito.doAnswer(invocation -> { + Token token = (Token) invocation.callRealMethod(); + if (token.kind == CCJSqlParserConstants.S_LONG) { + parser.interrupted = true; + } else if (token.kind == CCJSqlParserConstants.K_AS) { + reachedAlias.set(true); + } + return token; + }).when(parser.token_source).getNextToken(); + assertThrows(CancellationException.class, parser::Statement); + assertFalse(reachedAlias.get()); + } + @Test void testNotMaterializedIssue2251() throws JSQLParserException { String sqlStr = "WITH devices AS NOT MATERIALIZED (\n" diff --git a/src/test/java/net/sf/jsqlparser/statement/select/XmlForestTest.java b/src/test/java/net/sf/jsqlparser/statement/select/XmlForestTest.java new file mode 100644 index 0000000000..c7cc4ddb08 --- /dev/null +++ b/src/test/java/net/sf/jsqlparser/statement/select/XmlForestTest.java @@ -0,0 +1,105 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.statement.select; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static net.sf.jsqlparser.test.TestUtils.assertSqlCanBeParsedAndDeparsed; +import static net.sf.jsqlparser.util.validation.ValidationTestAsserts.validateNotAllowed; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Set; +import net.sf.jsqlparser.JSQLParserException; +import net.sf.jsqlparser.expression.Alias; +import net.sf.jsqlparser.expression.AliasedExpression; +import net.sf.jsqlparser.expression.ExpressionVisitorAdapter; +import net.sf.jsqlparser.expression.Function; +import net.sf.jsqlparser.expression.LongValue; +import net.sf.jsqlparser.parser.CCJSqlParserUtil; +import net.sf.jsqlparser.parser.feature.Feature; +import net.sf.jsqlparser.schema.Column; +import net.sf.jsqlparser.util.TablesNamesFinder; +import net.sf.jsqlparser.util.deparser.ExpressionDeParser; +import net.sf.jsqlparser.util.deparser.SelectDeParser; +import net.sf.jsqlparser.util.deparser.StatementDeParser; +import net.sf.jsqlparser.util.validation.feature.FeaturesAllowed; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +class XmlForestTest { + @ParameterizedTest + @ValueSource(strings = {"XMLFOREST(name AS \"Name\")", "xmlforest(a, b AS item)", + "XMLFOREST(1 + 2 AS total, COALESCE(a, 'default') AS \"Value\")", + "XMLFOREST(XMLFOREST(a AS inner_name) AS outer_name)", + "XMLFOREST(name a)", "XMLFOREST(name)", "XMLPARSE(CONTENT a)"}) + void parsesAndRoundTripsNamedAndUnnamedArguments(String expression) throws Exception { + var statement = assertSqlCanBeParsedAndDeparsed("SELECT " + expression + " FROM t"); + assertEquals(statement.toString(), CCJSqlParserUtil.parse(statement.toString()).toString()); + } + + @Test + void aliasIsStructuredAndCanBeEditedWithoutReparsingSql() throws Exception { + var statement = + (PlainSelect) CCJSqlParserUtil.parse("SELECT XMLFOREST(name AS \"Name\") FROM t"); + Function function = (Function) statement.getSelectItem(0).getExpression(); + AliasedExpression argument = (AliasedExpression) function.getParameters().get(0); + assertEquals("name", ((Column) argument.getExpression()).getColumnName()); + assertEquals("\"Name\"", argument.getAlias().getName()); + argument.setExpression(new LongValue(7)); + argument.setAlias(new Alias("value", true)); + assertEquals("SELECT XMLFOREST(7 AS value) FROM t", statement.toString()); + StringBuilder output = new StringBuilder(); + statement.accept(new StatementDeParser(output), null); + assertEquals(statement.toString(), output.toString()); + } + + @Test + void customVisitorReachesValuesWithContextAndDeparserKeepsAliases() throws Exception { + var statement = (PlainSelect) CCJSqlParserUtil + .parse("SELECT XMLFOREST(1 + 2 AS total, 3 AS n) FROM t"); + List values = new ArrayList<>(); + statement.getSelectItem(0).getExpression().accept(new ExpressionVisitorAdapter() { + @Override + public Void visit(LongValue value, S context) { + assertEquals("context", context); + values.add(value.getValue()); + return null; + } + }, "context"); + assertEquals(Arrays.asList(1L, 2L, 3L), values); + StringBuilder output = new StringBuilder(); + ExpressionDeParser expressions = new ExpressionDeParser() { + @Override + public StringBuilder visit(LongValue value, S context) { + return getBuilder().append(value.getValue() + 100); + } + }; + statement.accept(new StatementDeParser(expressions, new SelectDeParser(), output), null); + assertEquals("SELECT XMLFOREST(101 + 102 AS total, 103 AS n) FROM t", output.toString()); + } + + @Test + void traversalAndValidationReachTheUnderlyingArgument() throws Exception { + assertEquals(Set.of("outer_t", "inner_t"), TablesNamesFinder.findTables( + "SELECT XMLFOREST((SELECT a FROM inner_t) AS item) FROM outer_t")); + validateNotAllowed("SELECT a FROM t WHERE XMLFOREST(? AS item) IS NULL", 1, 1, + new FeaturesAllowed().add(FeaturesAllowed.SELECT).remove(Feature.jdbcParameter), + Feature.jdbcParameter); + } + + @ParameterizedTest + @ValueSource(strings = {"SELECT XMLFOREST(a AS) FROM t", "SELECT XMLFOREST(a AS b.c) FROM t", + "SELECT f(a AS b) FROM t"}) + void rejectsMissingAliasesAndKeepsOrdinaryFunctionArgumentsStrict(String sql) { + assertThrows(JSQLParserException.class, () -> CCJSqlParserUtil.parse(sql)); + } +} diff --git a/src/test/java/net/sf/jsqlparser/statement/update/TeradataUpdateTest.java b/src/test/java/net/sf/jsqlparser/statement/update/TeradataUpdateTest.java new file mode 100644 index 0000000000..7c033fc057 --- /dev/null +++ b/src/test/java/net/sf/jsqlparser/statement/update/TeradataUpdateTest.java @@ -0,0 +1,96 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.statement.update; + +import static net.sf.jsqlparser.test.TestUtils.assertSqlCanBeParsedAndDeparsed; +import static org.junit.jupiter.api.Assertions.*; + +import java.util.Set; +import net.sf.jsqlparser.JSQLParserException; +import net.sf.jsqlparser.expression.LongValue; +import net.sf.jsqlparser.parser.AbstractJSqlParser.Dialect; +import net.sf.jsqlparser.parser.CCJSqlParserUtil; +import net.sf.jsqlparser.statement.Statement; +import net.sf.jsqlparser.util.TablesNamesFinder; +import net.sf.jsqlparser.util.deparser.ExpressionDeParser; +import net.sf.jsqlparser.util.deparser.SelectDeParser; +import net.sf.jsqlparser.util.deparser.StatementDeParser; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +class TeradataUpdateTest { + private static Update parse(String sql) throws JSQLParserException { + return (Update) CCJSqlParserUtil.parse(sql, p -> p.withDialect(Dialect.TERADATA)); + } + + @Test + void parsesIssue891AndFindsSourceTables() throws Exception { + String sql = "UPDATE a FROM db1.table1 a, db2.tabl2 b SET a.column1 = b.column1 " + + "WHERE a.column2 = b.column2"; + Update update = (Update) assertSqlCanBeParsedAndDeparsed(sql, true, + p -> p.withDialect(Dialect.TERADATA)); + assertTrue(update.isFromBeforeSet()); + assertTrue(update.isTargetTableAlias()); + assertEquals("a", update.getTable().getName()); + assertEquals(1, update.getJoins().size()); + assertEquals(Set.of("db1.table1", "db2.tabl2"), new TablesNamesFinder().getTables(update)); + assertEquals(sql, parse(update.toString()).toString()); + } + + @ParameterizedTest + @ValueSource(strings = { + "UPDATE t FROM source s SET id = s.id", + "UPDATE b FROM source a, target b SET b.id = a.id WHERE a.id > 1", + "UPDATE \"target\" FROM \"schema\".\"table\" AS \"target\" SET id = 1", + "UPDATE t SET id = 1"}) + void roundTripsTargetsAndOptionalFrom(String sql) throws Exception { + Statement statement = assertSqlCanBeParsedAndDeparsed(sql, true, + p -> p.withDialect(Dialect.TERADATA)); + assertEquals(statement.toString(), parse(statement.toString()).toString()); + } + + @Test + void gatesClauseOrderAndRejectsDuplicates() throws Exception { + String sql = "UPDATE t FROM s SET id = 1"; + assertThrows(JSQLParserException.class, () -> CCJSqlParserUtil.parse(sql)); + for (Dialect dialect : Dialect.values()) { + if (dialect != Dialect.TERADATA) { + assertThrows(JSQLParserException.class, + () -> CCJSqlParserUtil.parse(sql, p -> p.withDialect(dialect))); + } + } + assertThrows(JSQLParserException.class, () -> parse(sql + " FROM other")); + assertThrows(JSQLParserException.class, () -> parse("UPDATE t FROM SET id = 1")); + Update legacy = (Update) assertSqlCanBeParsedAndDeparsed("UPDATE t SET id = 1 FROM s"); + assertFalse(legacy.isFromBeforeSet()); + legacy.setFromBeforeSet(true); + assertEquals(sql, legacy.toString()); + assertEquals(2, CCJSqlParserUtil.parseStatements(sql + "; SELECT 1;", + p -> p.withDialect(Dialect.TERADATA)).size()); + } + + @Test + void customDeparserKeepsFromPosition() throws Exception { + Update update = parse("UPDATE t FROM s SET id = 1 WHERE s.id > 2"); + StringBuilder builder = new StringBuilder(); + ExpressionDeParser expressions = new ExpressionDeParser() { + @Override + public StringBuilder visit(LongValue value, S context) { + return getBuilder().append(value.getValue() + 10); + } + }; + update.accept(new StatementDeParser(expressions, new SelectDeParser(), builder)); + assertEquals("UPDATE t FROM s SET id = 11 WHERE s.id > 12", builder.toString()); + assertEquals(builder.toString(), parse(builder.toString()).toString()); + update.withFromBeforeSet(false); + assertEquals("UPDATE t SET id = 1 FROM s WHERE s.id > 2", update.toString()); + } +} diff --git a/src/test/java/net/sf/jsqlparser/util/TablesNamesFinderCompletenessTest.java b/src/test/java/net/sf/jsqlparser/util/TablesNamesFinderCompletenessTest.java new file mode 100644 index 0000000000..d05a56d695 --- /dev/null +++ b/src/test/java/net/sf/jsqlparser/util/TablesNamesFinderCompletenessTest.java @@ -0,0 +1,91 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.util; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +import org.junit.jupiter.api.Test; + +/** + * TablesNamesFinder collects tables by dispatching through the visitor interfaces it declares. Most + * dispatch methods are abstract, so a missing implementation fails the build already. But the + * interfaces progressively gain default methods (e.g. SelectVisitor#visit(PivotQuery, context) + * returns null), and for those a missing TablesNamesFinder implementation compiles silently and + * loses the tables of that node type without any test failing. + * + * This test fails the build when a declared visitor interface has a dispatch method visit(Node, + * context) for an AST node that is not implemented in the TablesNamesFinder class hierarchy (it + * resolves to an interface default instead). It does not catch empty-shell implementations (an + * override that visits nothing) nor visitor families the finder does not declare at all: both + * remain manual review duties. + */ +public class TablesNamesFinderCompletenessTest { + + // GroupByElement: ExpressionVisitor's default forwards the group-by expressions and the + // grouping sets to the visitor, TablesNamesFinder relies on it from visit(PlainSelect). + private static final Set INHERITED_DISPATCH_NODES = + Set.of("net.sf.jsqlparser.statement.select.GroupByElement"); + + @Test + void testImplementsEveryDispatchMethodOfEveryDeclaredVisitorInterface() + throws NoSuchMethodException { + Class[] visitorInterfaces = TablesNamesFinder.class.getInterfaces(); + assertTrue(visitorInterfaces.length >= 8, "TablesNamesFinder must keep declaring the" + + " visitor families it dispatches through (currently 8), dropping one shrinks" + + " the completeness walk silently."); + + List gaps = new ArrayList<>(); + List interfacesWithoutDispatch = new ArrayList<>(); + for (Class visitorInterface : visitorInterfaces) { + int dispatchMethods = 0; + for (Method dispatch : visitorInterface.getMethods()) { + if (!dispatch.getName().equals("visit") || dispatch.getParameterCount() != 2) { + continue; + } + Class nodeType = dispatch.getParameterTypes()[0]; + // skips bulk dispatch helpers carrying a java.util collection + if (!nodeType.getName().startsWith("net.sf.jsqlparser.")) { + continue; + } + if (INHERITED_DISPATCH_NODES.contains(nodeType.getName())) { + continue; + } + dispatchMethods++; + Method implementation = TablesNamesFinder.class.getMethod(dispatch.getName(), + dispatch.getParameterTypes()); + if (implementation.getDeclaringClass().isInterface()) { + gaps.add(visitorInterface.getSimpleName() + "#" + dispatch.getName() + "(" + + nodeType.getSimpleName() + ", context)"); + } + } + if (dispatchMethods == 0) { + interfacesWithoutDispatch.add(visitorInterface.getSimpleName()); + } + } + + assertTrue(interfacesWithoutDispatch.isEmpty(), + () -> "No guarded dispatch method found for: " + + String.join(", ", interfacesWithoutDispatch) + ". Either the reflection" + + " walk lost the dispatch methods (filters too narrow, the completeness" + + " check would pass vacuously) or the interface is not a dispatch" + + " interface and must not be declared by TablesNamesFinder."); + assertTrue(gaps.isEmpty(), () -> "TablesNamesFinder must implement every dispatch" + + " method of the visitor interfaces it declares, otherwise the tables of that" + + " node type are silently not collected. Missing:\n" + String.join("\n", gaps) + + "\nImplement visit(Node, context) in TablesNamesFinder, or - when the" + + " inherited default already forwards every child - document the node in" + + " INHERITED_DISPATCH_NODES."); + } +} diff --git a/src/test/java/net/sf/jsqlparser/util/TablesNamesFinderTest.java b/src/test/java/net/sf/jsqlparser/util/TablesNamesFinderTest.java index 8581a0e59b..79532abbcb 100644 --- a/src/test/java/net/sf/jsqlparser/util/TablesNamesFinderTest.java +++ b/src/test/java/net/sf/jsqlparser/util/TablesNamesFinderTest.java @@ -367,23 +367,19 @@ public void testRemoteLink() throws JSQLParserException { } @Test - public void testCreateSequence_throwsException() throws JSQLParserException { + public void testCreateSequenceHasNoTableReferences() throws JSQLParserException { String sql = "CREATE SEQUENCE my_seq"; Statement stmt = CCJSqlParserUtil.parse(sql); TablesNamesFinder tablesNamesFinder = new TablesNamesFinder(); - assertThatThrownBy(() -> tablesNamesFinder.getTables(stmt)) - .isInstanceOf(UnsupportedOperationException.class) - .hasMessage("Finding tables from CreateSequence is not supported"); + assertThat(tablesNamesFinder.getTables(stmt)).isEmpty(); } @Test - public void testAlterSequence_throwsException() throws JSQLParserException { + public void testAlterSequenceHasNoTableReferences() throws JSQLParserException { String sql = "ALTER SEQUENCE my_seq"; Statement stmt = CCJSqlParserUtil.parse(sql); TablesNamesFinder tablesNamesFinder = new TablesNamesFinder(); - assertThatThrownBy(() -> tablesNamesFinder.getTables(stmt)) - .isInstanceOf(UnsupportedOperationException.class) - .hasMessage("Finding tables from AlterSequence is not supported"); + assertThat(tablesNamesFinder.getTables(stmt)).isEmpty(); } @Test @@ -773,10 +769,297 @@ void testJsonTable() throws JSQLParserException { @Test void testWindowExpressionWithNoRangeAndNoOffsetDoesNotThrowException() { - String sqlStr = "SELECT c, SUM(COUNT(*)) OVER (ORDER BY c ASC ROWS UNBOUNDED PRECEDING) FROM tbl GROUP BY c"; + String sqlStr = + "SELECT c, SUM(COUNT(*)) OVER (ORDER BY c ASC ROWS UNBOUNDED PRECEDING) FROM tbl GROUP BY c"; assertThatCode(() -> TablesNamesFinder.findTables(sqlStr)) .doesNotThrowAnyException(); } + @Test + void testPipedQuery() throws JSQLParserException { + String sqlStr = "FROM MY_TABLE1\n" + + "|> WHERE id IN (SELECT id FROM MY_TABLE2)\n" + + "|> LEFT JOIN (SELECT item, id FROM MY_TABLE3) AS t3 ON t3.item = item\n" + + "|> UNION ALL (SELECT * FROM MY_TABLE4)\n" + + "|> SELECT item"; + + assertThat(TablesNamesFinder.findTables(sqlStr)).containsExactlyInAnyOrder("MY_TABLE1", + "MY_TABLE2", "MY_TABLE3", "MY_TABLE4"); + } + + @Test + void testDeleteWithWithItemList() throws JSQLParserException { + String sqlStr = + "WITH cte AS (SELECT * FROM MY_TABLE2) DELETE FROM MY_TABLE1 WHERE id IN (SELECT id FROM cte)"; + + assertThat(TablesNamesFinder.findTables(sqlStr)).containsExactlyInAnyOrder("MY_TABLE1", + "MY_TABLE2"); + } + + @Test + void testMergeOnConditionAndOperations() throws JSQLParserException { + String sqlStr = "MERGE INTO MY_TABLE1 t USING MY_TABLE2 s " + + "ON t.id IN (SELECT id FROM MY_TABLE3) " + + "WHEN MATCHED AND t.v > (SELECT MIN(v) FROM MY_TABLE4) THEN UPDATE SET t.v = (SELECT MAX(v) FROM MY_TABLE5) " + + "WHEN NOT MATCHED THEN INSERT (v) VALUES (1)"; + + assertThat(TablesNamesFinder.findTables(sqlStr)).containsExactlyInAnyOrder("MY_TABLE1", + "MY_TABLE2", "MY_TABLE3", "MY_TABLE4", "MY_TABLE5"); + } + + @Test + void testInsertWithSetUpdateSets() throws JSQLParserException { + String sqlStr = "INSERT INTO MY_TABLE1 SET a = (SELECT x FROM MY_TABLE2)"; + + assertThat(TablesNamesFinder.findTables(sqlStr)).containsExactlyInAnyOrder("MY_TABLE1", + "MY_TABLE2"); + } + + @Test + void testInsertWithOnDuplicateKeyUpdate() throws JSQLParserException { + String sqlStr = + "INSERT INTO MY_TABLE1 (a) VALUES (1) ON DUPLICATE KEY UPDATE b = (SELECT x FROM MY_TABLE2)"; + + assertThat(TablesNamesFinder.findTables(sqlStr)).containsExactlyInAnyOrder("MY_TABLE1", + "MY_TABLE2"); + } + + @Test + void testInsertWithOnConflictDoUpdate() throws JSQLParserException { + String sqlStr = + "INSERT INTO MY_TABLE1 (a) VALUES (1) ON CONFLICT (a) DO UPDATE SET b = (SELECT x FROM MY_TABLE2)"; + + assertThat(TablesNamesFinder.findTables(sqlStr)).containsExactlyInAnyOrder("MY_TABLE1", + "MY_TABLE2"); + } + + @Test + void testWithDataModifyingCte() throws JSQLParserException { + String sqlStr = + "WITH del AS (DELETE FROM MY_TABLE2) INSERT INTO MY_TABLE1 SELECT * FROM del"; + + assertThat(TablesNamesFinder.findTables(sqlStr)).containsExactlyInAnyOrder("MY_TABLE1", + "MY_TABLE2"); + } + + @Test + void testUpdateWithOutputClause() throws JSQLParserException { + String sqlStr = + "UPDATE MY_TABLE1 SET a = 1 OUTPUT (SELECT x FROM MY_TABLE2) INTO @tv"; + + assertThat(TablesNamesFinder.findTables(sqlStr)).containsExactlyInAnyOrder("MY_TABLE1", + "MY_TABLE2"); + } + + @Test + void testUpdateWithReturningClause() throws JSQLParserException { + String sqlStr = "UPDATE MY_TABLE1 SET a = 1 RETURNING (SELECT x FROM MY_TABLE2)"; + + assertThat(TablesNamesFinder.findTables(sqlStr)).containsExactlyInAnyOrder("MY_TABLE1", + "MY_TABLE2"); + } + + @Test + void testInsertWithReturningClause() throws JSQLParserException { + String sqlStr = + "INSERT INTO MY_TABLE1 (a) VALUES (1) RETURNING (SELECT x FROM MY_TABLE2)"; + + assertThat(TablesNamesFinder.findTables(sqlStr)).containsExactlyInAnyOrder("MY_TABLE1", + "MY_TABLE2"); + } + + @Test + void testDeleteWithReturningClause() throws JSQLParserException { + String sqlStr = "DELETE FROM MY_TABLE1 RETURNING (SELECT x FROM MY_TABLE2)"; + + assertThat(TablesNamesFinder.findTables(sqlStr)).containsExactlyInAnyOrder("MY_TABLE1", + "MY_TABLE2"); + } + + @Test + void testAnalyticFunctionsWithFunctionOrderBy() throws JSQLParserException { + String sqlStr = + "SELECT string_agg(name, ',' ORDER BY id) OVER (PARTITION BY grp) FROM MY_TABLE1"; + + assertThat(TablesNamesFinder.findTables(sqlStr)).containsExactlyInAnyOrder("MY_TABLE1"); + } + + @Test + void testAnalyticFunctionsWithWindowOrderBy() throws JSQLParserException { + String sqlStr = + "SELECT SUM(v) OVER (ORDER BY (SELECT MAX(k) FROM MY_TABLE2)) FROM MY_TABLE1"; + + assertThat(TablesNamesFinder.findTables(sqlStr)).containsExactlyInAnyOrder("MY_TABLE1", + "MY_TABLE2"); + } + + @Test + void testAnalyticFunctionsWithFilterClause() throws JSQLParserException { + String sqlStr = + "SELECT SUM(v) FILTER (WHERE id IN (SELECT id FROM MY_TABLE2)) OVER () FROM MY_TABLE1"; + + assertThat(TablesNamesFinder.findTables(sqlStr)).containsExactlyInAnyOrder("MY_TABLE1", + "MY_TABLE2"); + } + + @Test + void testSelectIntoTables() throws JSQLParserException { + String sqlStr = "SELECT * INTO MY_TABLE1 FROM MY_TABLE2"; + assertThat(TablesNamesFinder.findTables(sqlStr)).containsExactlyInAnyOrder("MY_TABLE1", + "MY_TABLE2"); + } + + @Test + void testSelectIntoTempTable() throws JSQLParserException { + String sqlStr = "SELECT * FROM MY_TABLE2 INTO TEMP MY_TABLE1"; + assertThat(TablesNamesFinder.findTables(sqlStr)).containsExactlyInAnyOrder("MY_TABLE1", + "MY_TABLE2"); + } + + @Test + void testGroupBySubquery() throws JSQLParserException { + String sqlStr = + "SELECT A FROM MY_TABLE1 GROUP BY A, (SELECT B FROM MY_TABLE2)"; + assertThat(TablesNamesFinder.findTables(sqlStr)).containsExactlyInAnyOrder("MY_TABLE1", + "MY_TABLE2"); + } + + @Test + void testGroupByGroupingSetsSubquery() throws JSQLParserException { + String sqlStr = + "SELECT A FROM MY_TABLE1 GROUP BY GROUPING SETS ((A, (SELECT B FROM MY_TABLE2)), (C))"; + assertThat(TablesNamesFinder.findTables(sqlStr)).containsExactlyInAnyOrder("MY_TABLE1", + "MY_TABLE2"); + } + + @Test + void testSelectQualifySubquery() throws JSQLParserException { + String sqlStr = "SELECT * FROM MY_TABLE1 QUALIFY (SELECT B FROM MY_TABLE2) = 1"; + assertThat(TablesNamesFinder.findTables(sqlStr)).containsExactlyInAnyOrder("MY_TABLE1", + "MY_TABLE2"); + } + + @Test + void testWindowDefinitionSubquery() throws JSQLParserException { + String sqlStr = + "SELECT * FROM MY_TABLE1 WINDOW W AS (PARTITION BY (SELECT B FROM MY_TABLE2))"; + assertThat(TablesNamesFinder.findTables(sqlStr)).containsExactlyInAnyOrder("MY_TABLE1", + "MY_TABLE2"); + } + + @Test + void testSelectOrderBySubquery() throws JSQLParserException { + String sqlStr = "SELECT A FROM MY_TABLE1 ORDER BY A, (SELECT B FROM MY_TABLE2)"; + assertThat(TablesNamesFinder.findTables(sqlStr)).containsExactlyInAnyOrder("MY_TABLE1", + "MY_TABLE2"); + } + + @Test + void testSelectLimitSubquery() throws JSQLParserException { + String sqlStr = "SELECT A FROM MY_TABLE1 LIMIT (SELECT B FROM MY_TABLE2)"; + assertThat(TablesNamesFinder.findTables(sqlStr)).containsExactlyInAnyOrder("MY_TABLE1", + "MY_TABLE2"); + } + + @Test + void testSelectLimitBySubquery() throws JSQLParserException { + String sqlStr = "SELECT A FROM MY_TABLE1 LIMIT 2 BY (SELECT B FROM MY_TABLE2)"; + assertThat(TablesNamesFinder.findTables(sqlStr)).containsExactlyInAnyOrder("MY_TABLE1", + "MY_TABLE2"); + } + + @Test + void testSelectOffsetSubquery() throws JSQLParserException { + String sqlStr = + "SELECT A FROM MY_TABLE1 LIMIT 1 OFFSET (SELECT B FROM MY_TABLE2)"; + assertThat(TablesNamesFinder.findTables(sqlStr)).containsExactlyInAnyOrder("MY_TABLE1", + "MY_TABLE2"); + } + + @Test + void testSelectFetchSubquery() throws JSQLParserException { + String sqlStr = + "SELECT A FROM MY_TABLE1 OFFSET 1 ROWS FETCH NEXT (SELECT B FROM MY_TABLE2) ROWS ONLY"; + assertThat(TablesNamesFinder.findTables(sqlStr)).containsExactlyInAnyOrder("MY_TABLE1", + "MY_TABLE2"); + } + + @Test + void testSelectDistinctOnSubquery() throws JSQLParserException { + String sqlStr = "SELECT DISTINCT ON ((SELECT B FROM MY_TABLE2)) A FROM MY_TABLE1"; + assertThat(TablesNamesFinder.findTables(sqlStr)).containsExactlyInAnyOrder("MY_TABLE1", + "MY_TABLE2"); + } + + @Test + void testLateralViewSubquery() throws JSQLParserException { + String sqlStr = + "SELECT * FROM MY_TABLE1 LATERAL VIEW EXPLODE(ARRAY((SELECT B FROM MY_TABLE2))) V AS X"; + assertThat(TablesNamesFinder.findTables(sqlStr)).containsExactlyInAnyOrder("MY_TABLE1", + "MY_TABLE2"); + } + + @Test + void testTablePivotXmlSubquery() throws JSQLParserException { + String sqlStr = + "SELECT * FROM MY_TABLE1 PIVOT XML (SUM(X) FOR Y IN (SELECT Z FROM MY_TABLE2))"; + assertThat(TablesNamesFinder.findTables(sqlStr)).containsExactlyInAnyOrder("MY_TABLE1", + "MY_TABLE2"); + } + + @Test + void testTablePivotSimple() throws JSQLParserException { + String sqlStr = "SELECT * FROM MY_TABLE1 PIVOT (SUM(X) FOR Y IN ('a', 'b'))"; + assertThat(TablesNamesFinder.findTables(sqlStr)).containsExactlyInAnyOrder("MY_TABLE1"); + } + + @Test + void testParenthesedOrderByLimitSubquery() throws JSQLParserException { + String sqlStr = + "(SELECT A FROM MY_TABLE1) ORDER BY (SELECT B FROM MY_TABLE2) LIMIT (SELECT C FROM MY_TABLE3)"; + assertThat(TablesNamesFinder.findTables(sqlStr)).containsExactlyInAnyOrder("MY_TABLE1", + "MY_TABLE2", "MY_TABLE3"); + } + + @Test + void testSetOperationOrderByLimitSubquery() throws JSQLParserException { + String sqlStr = + "SELECT A FROM MY_TABLE1 UNION SELECT B FROM MY_TABLE2 ORDER BY (SELECT C FROM MY_TABLE3) LIMIT (SELECT D FROM MY_TABLE4)"; + assertThat(TablesNamesFinder.findTables(sqlStr)).containsExactlyInAnyOrder("MY_TABLE1", + "MY_TABLE2", "MY_TABLE3", "MY_TABLE4"); + } + + @Test + void testSelectSettingsSubquery() throws JSQLParserException { + String sqlStr = + "SELECT A FROM MY_TABLE1 SETTINGS X = (SELECT B FROM MY_TABLE2)"; + assertThat(TablesNamesFinder.findTables(sqlStr)).containsExactlyInAnyOrder("MY_TABLE1", + "MY_TABLE2"); + } + + @Test + void testValuesOrderByLimitSubquery() throws JSQLParserException { + String sqlStr = + "VALUES (1), (2) ORDER BY (SELECT A FROM MY_TABLE1) LIMIT (SELECT B FROM MY_TABLE2)"; + assertThat(TablesNamesFinder.findTables(sqlStr)).containsExactlyInAnyOrder("MY_TABLE1", + "MY_TABLE2"); + } + + @Test + void testDeleteOrderByLimitSubquery() throws JSQLParserException { + String sqlStr = + "DELETE FROM MY_TABLE1 WHERE A = 1 ORDER BY (SELECT B FROM MY_TABLE2) LIMIT 2"; + assertThat(TablesNamesFinder.findTables(sqlStr)).containsExactlyInAnyOrder("MY_TABLE1", + "MY_TABLE2"); + } + + @Test + void testUpdateOrderByLimitSubquery() throws JSQLParserException { + String sqlStr = + "UPDATE MY_TABLE1 SET A = 1 ORDER BY (SELECT B FROM MY_TABLE2) LIMIT 2"; + assertThat(TablesNamesFinder.findTables(sqlStr)).containsExactlyInAnyOrder("MY_TABLE1", + "MY_TABLE2"); + } + } diff --git a/src/test/java/net/sf/jsqlparser/util/UpdateJoinTablesTest.java b/src/test/java/net/sf/jsqlparser/util/UpdateJoinTablesTest.java new file mode 100644 index 0000000000..f2d5055747 --- /dev/null +++ b/src/test/java/net/sf/jsqlparser/util/UpdateJoinTablesTest.java @@ -0,0 +1,54 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2019 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.util; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertSame; +import java.util.Set; +import net.sf.jsqlparser.parser.CCJSqlParserUtil; +import net.sf.jsqlparser.schema.Table; +import net.sf.jsqlparser.statement.update.Update; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +class UpdateJoinTablesTest { + @ParameterizedTest + @ValueSource(strings = { + "UPDATE target t JOIN source s ON s.id IN (SELECT id FROM hidden) SET t.a = 1", + "UPDATE target t LEFT JOIN source s ON EXISTS (SELECT 1 FROM hidden h WHERE h.id = s.id) SET t.a = 1", + "UPDATE target SET a = 1 FROM source s JOIN target t ON s.id IN (SELECT id FROM hidden)", + "WITH h AS (SELECT id FROM hidden) UPDATE target t JOIN source s ON s.id IN (SELECT id FROM h) SET t.a = 1"}) + void includesTablesInsideJoinConditions(String sql) throws Exception { + assertEquals(Set.of("target", "source", "hidden"), TablesNamesFinder.findTables(sql)); + } + + @Test + void preservesContextAndVisitsEachSourceOnce() throws Exception { + Update update = (Update) CCJSqlParserUtil.parse( + "UPDATE target t JOIN source s ON s.id IN (SELECT id FROM hidden) SET t.a = 1"); + Object context = new Object(); + java.util.List seen = new java.util.ArrayList<>(); + TablesNamesFinder finder = new TablesNamesFinder() { + { + init(false); + } + + @Override + public Void visit(Table table, S actual) { + assertSame(context, actual); + seen.add(table.getName()); + return null; + } + }; + update.accept(finder, context); + assertEquals(java.util.List.of("target", "source", "hidden"), seen); + } +} diff --git a/src/test/java/net/sf/jsqlparser/util/deparser/CreatePolicyDeParserTest.java b/src/test/java/net/sf/jsqlparser/util/deparser/CreatePolicyDeParserTest.java new file mode 100644 index 0000000000..41428e00b1 --- /dev/null +++ b/src/test/java/net/sf/jsqlparser/util/deparser/CreatePolicyDeParserTest.java @@ -0,0 +1,42 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.util.deparser; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import net.sf.jsqlparser.JSQLParserException; +import net.sf.jsqlparser.parser.CCJSqlParserUtil; +import net.sf.jsqlparser.schema.Column; +import net.sf.jsqlparser.statement.create.policy.CreatePolicy; + +import org.junit.jupiter.api.Test; + +public class CreatePolicyDeParserTest { + + @Test + public void testUseExternalExpressionDeParser() throws JSQLParserException { + StringBuilder builder = new StringBuilder(); + ExpressionDeParser expressionDeParser = new ExpressionDeParser() { + @Override + public StringBuilder visit(Column column, S context) { + getBuilder().append('"').append(column.getColumnName()).append('"'); + return getBuilder(); + } + }; + expressionDeParser.setBuilder(builder); + + CreatePolicy policy = (CreatePolicy) CCJSqlParserUtil.parse( + "CREATE POLICY tenant_policy ON users AS RESTRICTIVE USING (tenant_id = owner_id)"); + new CreatePolicyDeParser(expressionDeParser, builder).deParse(policy); + + assertEquals("CREATE POLICY tenant_policy ON users AS RESTRICTIVE " + + "USING (\"tenant_id\" = \"owner_id\")", builder.toString()); + } +} diff --git a/src/test/java/net/sf/jsqlparser/util/deparser/DmlReturningExpressionTest.java b/src/test/java/net/sf/jsqlparser/util/deparser/DmlReturningExpressionTest.java new file mode 100644 index 0000000000..a5f247f06f --- /dev/null +++ b/src/test/java/net/sf/jsqlparser/util/deparser/DmlReturningExpressionTest.java @@ -0,0 +1,57 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2019 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.util.deparser; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import java.util.ArrayList; +import java.util.List; +import net.sf.jsqlparser.expression.LongValue; +import net.sf.jsqlparser.parser.CCJSqlParserUtil; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +class DmlReturningExpressionTest { + @ParameterizedTest + @ValueSource(strings = {"UPDATE t SET a = 7 RETURNING a + 8 AS result", + "DELETE FROM t WHERE a = 7 RETURNING a + 8 AS result", + "INSERT INTO t(a) VALUES (7) RETURNING a + 8 AS result", + "UPDATE t SET a = 7 RETURNING (SELECT b + 8 FROM source) AS result"}) + void visitsReturnedExpressionsExactlyOnce(String sql) throws Exception { + var statement = CCJSqlParserUtil.parse(sql); + String original = statement.toString(); + List seen = new ArrayList<>(); + ExpressionDeParser expressions = new ExpressionDeParser() { + @Override + public StringBuilder visit(LongValue value, S context) { + seen.add(value.getValue()); + return getBuilder().append(value.getValue() + 100); + } + }; + StringBuilder output = new StringBuilder(); + statement.accept(new StatementDeParser(expressions, new SelectDeParser(), output), null); + assertEquals(List.of(7L, 8L), seen); + assertEquals(original.replace("7", "107").replace("8", "108"), output.toString()); + assertEquals(original, statement.toString()); + assertEquals(output.toString(), CCJSqlParserUtil.parse(output.toString()).toString()); + } + + @ParameterizedTest + @ValueSource(strings = {"INSERT INTO t(a) VALUES (1) RETURNING *", + "DELETE FROM t RETURNING t.*, a AS result", + "UPDATE t SET a = 1 RETURNING a, b INTO x, y", + "DELETE FROM t RETURNING WITH (OLD AS o, NEW AS n) o.*, n.id"}) + void preservesWildcardsAliasesAndOutputTargets(String sql) throws Exception { + var statement = CCJSqlParserUtil.parse(sql); + StringBuilder output = new StringBuilder(); + statement.accept(new StatementDeParser(output), null); + assertEquals(statement.toString(), output.toString()); + assertEquals(statement.toString(), CCJSqlParserUtil.parse(output.toString()).toString()); + } +} diff --git a/src/test/java/net/sf/jsqlparser/util/deparser/DmlSourceExpressionTest.java b/src/test/java/net/sf/jsqlparser/util/deparser/DmlSourceExpressionTest.java new file mode 100644 index 0000000000..b4aa25757a --- /dev/null +++ b/src/test/java/net/sf/jsqlparser/util/deparser/DmlSourceExpressionTest.java @@ -0,0 +1,121 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2019 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.util.deparser; + +import static org.junit.jupiter.api.Assertions.*; +import java.util.ArrayList; +import java.util.List; +import net.sf.jsqlparser.expression.LongValue; +import net.sf.jsqlparser.expression.ExpressionVisitorAdapter; +import net.sf.jsqlparser.schema.Column; +import net.sf.jsqlparser.schema.Table; +import net.sf.jsqlparser.parser.CCJSqlParserUtil; +import net.sf.jsqlparser.statement.Statement; +import net.sf.jsqlparser.statement.update.Update; +import net.sf.jsqlparser.statement.delete.Delete; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +class DmlSourceExpressionTest { + private static Statement parse(String sql) throws Exception { + return CCJSqlParserUtil.parse(sql, parser -> { + if (sql.startsWith("UPDATE t FROM")) { + parser.withDialect(net.sf.jsqlparser.parser.AbstractJSqlParser.Dialect.TERADATA); + } + }); + } + + @ParameterizedTest + @ValueSource(strings = { + "UPDATE t JOIN s ON s.a = 7 SET t.a = 8 WHERE t.b = 9", + "UPDATE t SET a = 8 FROM (SELECT 7 AS a) s JOIN q ON s.a = q.a AND q.b = 9", + "UPDATE t FROM s JOIN q ON q.a = 7 SET t.a = 8", + "WITH c AS (SELECT 7 AS a), d AS (SELECT 8 AS b) UPDATE t SET a = 9 FROM c", + "WITH c AS (DELETE FROM s WHERE a = 7 RETURNING a) UPDATE t SET a = 8", + "WITH c AS (UPDATE s SET a = 7 RETURNING a) DELETE FROM t WHERE a = 8", + "WITH c AS (INSERT INTO s (a) VALUES (7) RETURNING a) DELETE FROM t WHERE a = 8", + "DELETE t FROM t JOIN s ON s.a = 7 WHERE t.b = 8", + "DELETE FROM t USING (SELECT 7 AS a) s WHERE t.a = 8", + "DELETE FROM t USING s, (SELECT 7 AS a) q WHERE t.a = 8" + }) + void visitsEverySourceExpressionOnceAndLeavesTheAstUnchanged(String sql) throws Exception { + Statement statement = parse(sql); + String original = statement.toString(); + for (boolean direct : new boolean[] {false, true}) { + StringBuilder output = new StringBuilder(); + List visited = new ArrayList<>(); + SelectDeParser select = new SelectDeParser(); + ExpressionDeParser expression = new ExpressionDeParser(select, output) { + @Override + public StringBuilder visit(LongValue value, S context) { + visited.add(value.getValue()); + return output.append(value.getValue() + 100); + } + }; + select.setExpressionVisitor(expression); + select.setBuilder(output); + if (direct && statement instanceof Update) { + new UpdateDeParser(expression, output).deParse((Update) statement); + } else if (direct) { + new DeleteDeParser(expression, output).deParse((Delete) statement); + } else { + statement.accept(new StatementDeParser(expression, select, output), null); + } + String expected = sql.replace("7", "107").replace("8", "108").replace("9", "109"); + assertEquals(parse(expected).toString(), + parse(output.toString()).toString()); + for (long value : new long[] {7, 8, 9}) { + assertEquals(sql.contains(Long.toString(value)) ? 1 : 0, + visited.stream().filter(v -> v == value).count()); + } + assertEquals(original, statement.toString()); + } + } + + @Test + void usesTheConfiguredSelectDeParserForDmlSources() throws Exception { + StringBuilder output = new StringBuilder(); + List tables = new ArrayList<>(); + SelectDeParser selects = new SelectDeParser() { + @Override + public StringBuilder visit(Table table, S context) { + tables.add(table.getName()); + return super.visit(table, context); + } + }; + parse("WITH c AS (SELECT a FROM hidden) UPDATE t " + + "SET a = 1 FROM source JOIN other ON source.a = other.a") + .accept(new StatementDeParser(new ExpressionDeParser(), selects, output), null); + assertEquals(List.of("hidden", "source", "other"), tables); + } + + @Test + void existingSettersAndGenericExpressionVisitorsCanRenderWithItems() throws Exception { + StringBuilder output = new StringBuilder(); + ExpressionVisitorAdapter expressions = new ExpressionVisitorAdapter<>() { + @Override + public StringBuilder visit(LongValue value, S context) { + return output.append(value.getValue() + 100); + } + + @Override + public StringBuilder visit(Column column, S context) { + return output.append(column); + } + }; + UpdateDeParser deparser = new UpdateDeParser(); + deparser.setBuilder(output); + deparser.setExpressionVisitor(expressions); + deparser.deParse( + (Update) parse("WITH c AS (SELECT 7) UPDATE t SET a = 8")); + assertEquals("WITH c AS (SELECT 107) UPDATE t SET a = 108", output.toString()); + } +} diff --git a/src/test/java/net/sf/jsqlparser/util/deparser/TableConstraintExpressionDeParserTest.java b/src/test/java/net/sf/jsqlparser/util/deparser/TableConstraintExpressionDeParserTest.java new file mode 100644 index 0000000000..f3973613ec --- /dev/null +++ b/src/test/java/net/sf/jsqlparser/util/deparser/TableConstraintExpressionDeParserTest.java @@ -0,0 +1,115 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.util.deparser; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.Arrays; +import net.sf.jsqlparser.JSQLParserException; +import net.sf.jsqlparser.expression.LongValue; +import net.sf.jsqlparser.parser.CCJSqlParserUtil; +import net.sf.jsqlparser.schema.Column; +import net.sf.jsqlparser.statement.Statement; +import net.sf.jsqlparser.statement.alter.Alter; +import net.sf.jsqlparser.statement.create.table.ConstraintAttributes; +import net.sf.jsqlparser.statement.create.table.CreateTable; +import net.sf.jsqlparser.statement.create.table.Index; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +class TableConstraintExpressionDeParserTest { + @ParameterizedTest + @ValueSource(strings = {"", " NOT ENFORCED"}) + void alterCheckUsesExpressionVisitorAndPreservesAttributes(String attributes) + throws JSQLParserException { + assertRewrite("ALTER TABLE t ADD CONSTRAINT c CHECK (id > 0)" + attributes, + "ALTER TABLE t ADD CONSTRAINT c CHECK (mapped_id > 100)" + attributes); + } + + @Test + void createAndAlterFunctionalIndexesUseExpressionVisitor() throws JSQLParserException { + assertRewrite("CREATE TABLE t (id INT, INDEX idx ((id + 1)))", + "CREATE TABLE t (id INT, INDEX idx ((mapped_id + 101)))"); + assertRewrite("ALTER TABLE t ADD INDEX idx ((id + 1))", + "ALTER TABLE t ADD INDEX idx ((mapped_id + 101))"); + } + + @Test + void alterExcludeUsesVisitorForKeysAndPredicate() throws JSQLParserException { + CreateTable table = (CreateTable) CCJSqlParserUtil.parse( + "CREATE TABLE t (id INT, CONSTRAINT c EXCLUDE USING gist " + + "((id + 1) WITH =) WHERE (id > 0) DEFERRABLE)"); + Alter alter = (Alter) CCJSqlParserUtil.parse( + "ALTER TABLE t ADD CONSTRAINT placeholder CHECK (id > 0)"); + alter.getAlterExpressions().get(0).setIndex(table.getIndexes().get(0)); + assertEquals("ALTER TABLE t ADD CONSTRAINT c EXCLUDE USING gist " + + "((mapped_id + 101) WITH =) WHERE (mapped_id > 100) DEFERRABLE", rewrite(alter)); + } + + @Test + void checkAttributesRemainAfterExpressionRewrite() throws JSQLParserException { + Alter alter = + (Alter) CCJSqlParserUtil.parse("ALTER TABLE t ADD CONSTRAINT c CHECK (id > 0)"); + ConstraintAttributes attributes = new ConstraintAttributes(); + attributes.setNotValid(true); + alter.getAlterExpressions().get(0).getIndex().setConstraintAttributes(attributes); + assertEquals("ALTER TABLE t ADD CONSTRAINT c CHECK (mapped_id > 100) NOT VALID", + rewrite(alter)); + } + + @Test + void constraintStorageOptionsUseExpressionVisitor() throws JSQLParserException { + CreateTable table = (CreateTable) CCJSqlParserUtil.parse( + "CREATE TABLE t (id INT, CONSTRAINT c UNIQUE (id))"); + table.getIndexes().get(0).setStorageParameters( + Arrays.asList(new Index.Option("fillfactor", new LongValue(70), true))); + assertEquals("CREATE TABLE t (id INT, CONSTRAINT c UNIQUE (id) WITH (fillfactor = 170))", + rewrite(table)); + } + + @ParameterizedTest + @ValueSource(strings = { + "ALTER TABLE t ADD CONSTRAINT c FOREIGN KEY (id) REFERENCES p(id) ON DELETE CASCADE", + "ALTER TABLE t ADD CONSTRAINT c UNIQUE (id) DEFERRABLE", + "CREATE TABLE t (id INT, CONSTRAINT c PRIMARY KEY pk (id), FOREIGN KEY (id) REFERENCES p(id))", + "ALTER TABLE t ADD INDEX idx (id) COMMENT 'lookup'", + "ALTER TABLE t ADD CONSTRAINT c DEFAULT 1 FOR id", + "ALTER TABLE t ALTER INDEX idx INVISIBLE" + }) + void defaultDeparserPreservesCompleteConstraint(String sql) throws JSQLParserException { + Statement statement = CCJSqlParserUtil.parse(sql); + StringBuilder buffer = new StringBuilder(); + statement.accept(new StatementDeParser(buffer), null); + assertEquals(statement.toString(), buffer.toString()); + assertEquals(statement.toString(), CCJSqlParserUtil.parse(buffer.toString()).toString()); + } + + private static void assertRewrite(String sql, String expected) throws JSQLParserException { + assertEquals(expected, rewrite(CCJSqlParserUtil.parse(sql))); + } + + private static String rewrite(Statement statement) { + StringBuilder buffer = new StringBuilder(); + ExpressionDeParser expressions = new ExpressionDeParser() { + @Override + public StringBuilder visit(Column column, S context) { + return getBuilder().append("mapped_").append(column.getColumnName()); + } + + @Override + public StringBuilder visit(LongValue value, S context) { + return getBuilder().append(value.getValue() + 100); + } + }; + statement.accept(new StatementDeParser(expressions, new SelectDeParser(), buffer), null); + return buffer.toString(); + } +} diff --git a/src/test/java/net/sf/jsqlparser/util/validation/DataModifyingCteValidationTest.java b/src/test/java/net/sf/jsqlparser/util/validation/DataModifyingCteValidationTest.java new file mode 100644 index 0000000000..8801af514e --- /dev/null +++ b/src/test/java/net/sf/jsqlparser/util/validation/DataModifyingCteValidationTest.java @@ -0,0 +1,97 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.util.validation; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.List; +import java.util.stream.Stream; +import net.sf.jsqlparser.parser.feature.Feature; +import net.sf.jsqlparser.util.validation.feature.FeaturesAllowed; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.junit.jupiter.params.provider.ValueSource; + +class DataModifyingCteValidationTest { + static Stream modifyingBodies() { + return Stream.of( + Arguments.of("DELETE FROM foo RETURNING id", Feature.delete), + Arguments.of("UPDATE foo SET id = 1 RETURNING id", Feature.update), + Arguments.of("INSERT INTO foo VALUES (1) RETURNING id", Feature.insert)); + } + + @ParameterizedTest + @MethodSource("modifyingBodies") + void reportsDisallowedDmlInsteadOfCastingToSelect(String body, Feature operation) { + String sql = "WITH x AS (" + body + ") SELECT * FROM x"; + for (FeaturesAllowed allowed : List.of(FeaturesAllowed.SELECT, + FeaturesAllowed.SELECT.copy().add(Feature.withItem))) { + List errors = Validation.validate(List.of(allowed), sql); + assertThat(errors).hasSize(1); + assertThat(errors.get(0).getParsedStatement()).isNotNull(); + assertThat(errors.get(0).getErrors()).extracting(Throwable::getMessage) + .contains(operation + " not allowed."); + } + } + + @ParameterizedTest + @MethodSource("modifyingBodies") + void allowsDmlWhenItsFeaturesAreEnabled(String body, Feature operation) { + String sql = "WITH x AS (" + body + ") SELECT * FROM x"; + List errors = + Validation.validate(List.of(new FeaturesAllowed(Feature.values())), sql); + assertThat(errors).as("allowed %s CTE", operation).isEmpty(); + } + + @ParameterizedTest + @ValueSource(strings = { + "WITH x AS (SELECT id FROM foo) SELECT * FROM x", + "WITH RECURSIVE x(id) AS (SELECT 1 UNION ALL SELECT id + 1 FROM x WHERE id < 3) SELECT * FROM x", + "WITH x AS (WITH y AS (SELECT id FROM foo) SELECT * FROM y) SELECT * FROM x"}) + void keepsSelectAndRecursiveCteValidation(String sql) { + assertThat(Validation.validate(List.of(FeaturesAllowed.SELECT.copy() + .add(Feature.withItem, Feature.withItemRecursive, Feature.setOperation, + Feature.setOperationUnion)), + sql)).isEmpty(); + } + + @Test + void collectsErrorsFromMultipleBodiesAndFollowingStatements() { + String sql = "WITH x AS (DELETE FROM foo RETURNING id), " + + "y AS (UPDATE bar SET id = 2 RETURNING id) SELECT * FROM x; SELECT 1"; + Validation validation = new Validation(List.of(FeaturesAllowed.SELECT.copy() + .add(Feature.withItem)), sql); + List errors = validation.validate(); + assertThat(validation.getParsedStatements()).hasSize(2); + assertThat(errors).hasSize(1); + assertThat(errors.get(0).getErrors()).extracting(Throwable::getMessage) + .contains("delete not allowed.", "update not allowed."); + } + + @Test + void visitsNestedBodiesAndReturningExpressions() { + String sql = "WITH x AS (WITH y AS (DELETE FROM foo RETURNING id) " + + "SELECT id FROM y) SELECT * FROM x"; + List errors = Validation.validate(List.of(FeaturesAllowed.SELECT.copy() + .add(Feature.withItem)), sql); + assertThat(errors).hasSize(1); + assertThat(errors.get(0).getErrors()).extracting(Throwable::getMessage) + .contains("delete not allowed."); + + FeaturesAllowed allowed = new FeaturesAllowed(Feature.values()).remove(Feature.function); + errors = Validation.validate(List.of(allowed), + "WITH x AS (DELETE FROM foo RETURNING upper(id)) SELECT * FROM x"); + assertThat(errors).hasSize(1); + assertThat(errors.get(0).getErrors()).extracting(Throwable::getMessage) + .containsExactly("function not allowed."); + } +} diff --git a/src/test/java/net/sf/jsqlparser/util/validation/validator/AlterValidatorTest.java b/src/test/java/net/sf/jsqlparser/util/validation/validator/AlterValidatorTest.java index f2aef4e403..408e0efa5c 100644 --- a/src/test/java/net/sf/jsqlparser/util/validation/validator/AlterValidatorTest.java +++ b/src/test/java/net/sf/jsqlparser/util/validation/validator/AlterValidatorTest.java @@ -151,4 +151,10 @@ public void testAlterTableAlterColumnDropNotNullIssue918() throws JSQLParserExce DatabaseType.DATABASES); } + @Test + public void testAlterTableAlterColumnSetNotNull() throws JSQLParserException { + validateNoErrors("ALTER TABLE user_table ALTER COLUMN name SET NOT NULL", 1, + DatabaseType.DATABASES); + } + } diff --git a/src/test/java/net/sf/jsqlparser/util/validation/validator/CommentValidatorTest.java b/src/test/java/net/sf/jsqlparser/util/validation/validator/CommentValidatorTest.java new file mode 100644 index 0000000000..276e416e91 --- /dev/null +++ b/src/test/java/net/sf/jsqlparser/util/validation/validator/CommentValidatorTest.java @@ -0,0 +1,54 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.util.validation.validator; + +import java.util.stream.Stream; +import net.sf.jsqlparser.parser.feature.Feature; +import net.sf.jsqlparser.util.validation.ValidationTestAsserts; +import net.sf.jsqlparser.util.validation.feature.DatabaseType; +import net.sf.jsqlparser.util.validation.feature.FeaturesAllowed; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +class CommentValidatorTest extends ValidationTestAsserts { + static Stream targets() { + return Stream.of(Arguments.of("INDEX idx", Feature.commentOnIndex), + Arguments.of("SCHEMA app", Feature.commentOnSchema), + Arguments.of("SEQUENCE seq", Feature.commentOnSequence), + Arguments.of("DOMAIN d", Feature.commentOnDomain), + Arguments.of("TYPE ty", Feature.commentOnType), + Arguments.of("MATERIALIZED VIEW mv", Feature.commentOnMaterializedView), + Arguments.of("FUNCTION f(IN x integer)", Feature.commentOnFunction), + Arguments.of("CONSTRAINT ck ON t", Feature.commentOnConstraint), + Arguments.of("CONSTRAINT ck ON DOMAIN d", Feature.commentOnConstraint)); + } + + @ParameterizedTest + @MethodSource("targets") + void validatesEachTargetCapability(String target, Feature feature) { + String sql = "COMMENT ON " + target + " IS 'body'"; + validateNoErrors(sql, 1, DatabaseType.POSTGRESQL); + validateNotAllowed(sql, 1, 1, new FeaturesAllowed("comment only", Feature.comment), + feature); + validateNotAllowed(sql, 1, 1, FeaturesAllowed.DML, Feature.comment, feature); + } + + @Test + void retainsLegacyCommentCapabilities() { + validateNoErrors( + "COMMENT ON TABLE t IS 'body'; COMMENT ON COLUMN t.c IS NULL; COMMENT ON VIEW v IS ''", + 3, DatabaseType.POSTGRESQL, DatabaseType.ORACLE, DatabaseType.H2); + validateNotAllowed("COMMENT ON COLUMN t.c IS NULL", 1, 1, + new FeaturesAllowed("table comments", Feature.comment, Feature.commentOnTable), + Feature.commentOnColumn); + } +} diff --git a/src/test/java/net/sf/jsqlparser/util/validation/validator/CreateIndexValidatorTest.java b/src/test/java/net/sf/jsqlparser/util/validation/validator/CreateIndexValidatorTest.java index ae3d9fe538..081321e39a 100644 --- a/src/test/java/net/sf/jsqlparser/util/validation/validator/CreateIndexValidatorTest.java +++ b/src/test/java/net/sf/jsqlparser/util/validation/validator/CreateIndexValidatorTest.java @@ -16,6 +16,8 @@ import net.sf.jsqlparser.util.validation.feature.DatabaseType; import net.sf.jsqlparser.util.validation.feature.FeaturesAllowed; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; public class CreateIndexValidatorTest extends ValidationTestAsserts { @@ -36,4 +38,16 @@ public void testValidateCreateIndexNotAllowed() throws JSQLParserException { } } + @ParameterizedTest + @ValueSource(strings = { + "CREATE INDEX ix ON t ((lower(name)))", + "CREATE INDEX ix ON t (name text_ops (option = lower('value')))", + "CREATE INDEX ix ON t (name) WITH (option = lower('value'))", + "CREATE INDEX ix ON t (name) WHERE lower(name) = 'value'"}) + void validatesExpressionsInAllIndexClauses(String sql) { + FeaturesAllowed allowed = new FeaturesAllowed(Feature.values()); + validateNoErrors(sql, 1, allowed); + validateNotAllowed(sql, 1, 1, allowed.remove(Feature.function), Feature.function); + } + } diff --git a/src/test/java/net/sf/jsqlparser/util/validation/validator/MatchRecognizeValidatorTest.java b/src/test/java/net/sf/jsqlparser/util/validation/validator/MatchRecognizeValidatorTest.java new file mode 100644 index 0000000000..03f786d281 --- /dev/null +++ b/src/test/java/net/sf/jsqlparser/util/validation/validator/MatchRecognizeValidatorTest.java @@ -0,0 +1,169 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.util.validation.validator; + +import static org.junit.jupiter.api.Assertions.*; +import java.util.ArrayList; +import java.util.List; +import net.sf.jsqlparser.parser.feature.Feature; +import net.sf.jsqlparser.parser.feature.FeatureConfiguration; +import net.sf.jsqlparser.util.validation.Validation; +import net.sf.jsqlparser.util.validation.ValidationError; +import net.sf.jsqlparser.util.validation.feature.FeaturesAllowed; +import net.sf.jsqlparser.util.validation.metadata.DatabaseMetaDataValidation; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +class MatchRecognizeValidatorTest { + private static final String SQL = "SELECT * FROM events MATCH_RECOGNIZE (ORDER BY seq " + + "MEASURES SUM(A.price) AS total PATTERN (A+) DEFINE A AS price > 0)"; + + private static FeatureConfiguration configuration() { + FeatureConfiguration configuration = new FeatureConfiguration(); + configuration.setValue(Feature.dialect, "BIGQUERY"); + return configuration; + } + + @Test + void permitsSupportedFeaturesAndDoesNotResolveSymbolsAsTables() { + List names = new ArrayList<>(); + DatabaseMetaDataValidation metadata = name -> { + names.add(name.getFqn()); + return true; + }; + List errors = + new Validation(configuration(), List.of(FeaturesAllowed.SELECT, metadata), SQL) + .validate(); + assertTrue(errors.isEmpty(), errors.toString()); + assertTrue(names.contains("events")); + assertTrue(names.contains("price")); + assertFalse(names.contains("A.price")); + } + + @ParameterizedTest + @ValueSource(strings = {"A{10001}", "A{4,2}", "A B", "B", "A{@@lo}"}) + void invalidBoundsAndSymbols(String pattern) { + List errors = new Validation(configuration(), + List.of(FeaturesAllowed.SELECT), SQL.replace("A+", pattern)).validate(); + assertFalse(errors.isEmpty()); + } + + @Test + void duplicateSymbolsAreCaseInsensitive() { + String sql = + SQL.replace("DEFINE A AS price > 0", "DEFINE A AS price > 0, `a` AS price < 0"); + List errors = + new Validation(configuration(), List.of(FeaturesAllowed.SELECT), sql).validate(); + assertTrue(errors.toString().contains("Duplicate row pattern variable")); + } + + @Test + void featureCanBeDisabledIndependently() { + List errors = new Validation(configuration(), + List.of(new FeaturesAllowed("basic", Feature.select, Feature.function, + Feature.orderBy)), + SQL).validate(); + assertTrue(errors.toString().contains("matchRecognize")); + } + + private static List validate(String dialect, String sql) { + FeatureConfiguration configuration = new FeatureConfiguration(); + configuration.setValue(Feature.dialect, dialect); + return new Validation(configuration, List.of(FeaturesAllowed.SELECT), sql).validate(); + } + + @ParameterizedTest + @ValueSource( + strings = {"A{0}", "A{0,0}", "A{3,1}", "A{@lo}", "| A", "A |"}) + void oracleRejectsInvalidBoundsAndEmptyAlternatives(String pattern) { + List errors = validate("ORACLE", SQL.replace("A+", pattern)); + assertFalse(errors.isEmpty(), pattern); + } + + @Test + void oracleAllowsImplicitVariablesAndPreservesQuotedCase() { + assertTrue(validate("ORACLE", SQL.replace("A+", "A B+")).isEmpty()); + String caseSensitive = SQL.replace("A+", "A \"a\"").replace("DEFINE A AS price > 0", + "DEFINE A AS price > 0, \"a\" AS price < 0"); + assertTrue(validate("ORACLE", caseSensitive).isEmpty()); + assertFalse(validate("ORACLE", caseSensitive.replace("\"a\" AS", "\"A\" AS")).isEmpty()); + } + + @Test + void validatesSubsetAndSkipTargets() { + String base = SQL.replace("A+", "A B+").replace("DEFINE", "SUBSET U = (A, B) DEFINE"); + assertTrue(validate("ORACLE", base).isEmpty()); + assertFalse(validate("ORACLE", base.replace("U = (A, B)", "U = (A, C)")).isEmpty()); + assertFalse(validate("ORACLE", base.replace("U = (A, B)", "A = (A, B)")).isEmpty()); + assertTrue(validate("ORACLE", base.replace("PATTERN", "AFTER MATCH SKIP TO LAST B PATTERN")) + .isEmpty()); + assertFalse(validate("ORACLE", base.replace("PATTERN", "AFTER MATCH SKIP TO C PATTERN")) + .isEmpty()); + assertFalse(validate("SNOWFLAKE", base).isEmpty()); + } + + @Test + void exclusionCannotReturnUnmatchedRows() { + String query = SQL.replace("PATTERN (A+)", + "ALL ROWS PER MATCH WITH UNMATCHED ROWS PATTERN ({- A+ -})"); + for (String dialect : List.of("ORACLE", "SNOWFLAKE")) { + List errors = validate(dialect, query); + assertTrue(errors.toString().contains("exclusion cannot be combined"), + errors.toString()); + } + } + + @Test + void validatesDialectSpecificOptionsAndFunctionModes() { + String options = + SQL.substring(0, SQL.length() - 1) + " OPTIONS (use_longest_match = TRUE))"; + assertFalse(validate("ORACLE", options).isEmpty()); + assertFalse(validate("SNOWFLAKE", options).isEmpty()); + assertFalse(validate("BIGQUERY", SQL.replace("SUM(A.price)", "RUNNING SUM(A.price)")) + .isEmpty()); + assertFalse(validate("ORACLE", + SQL.replace("DEFINE A AS price > 0", "DEFINE A AS FINAL SUM(price) > 0")) + .isEmpty()); + assertTrue(validate("ORACLE", + SQL.replace("DEFINE A AS price > 0", "DEFINE A AS RUNNING SUM(price) > 0")) + .isEmpty()); + } + + @Test + void inputAliasScopeAndNestedQueriesStillReachMetadataValidation() { + List names = new ArrayList<>(); + DatabaseMetaDataValidation metadata = name -> { + names.add(name.getFqn()); + return true; + }; + String query = SQL.replace("FROM events", "FROM events A") + .replace("ORDER BY seq", "ORDER BY A.seq") + .replace("SUM(A.price)", + "SUM(A.price) + (SELECT 1 FROM lookup A WHERE A.nested_value > 0)"); + List errors = + new Validation(configuration(), List.of(FeaturesAllowed.SELECT, metadata), query) + .validate(); + assertTrue(errors.isEmpty(), errors.toString()); + assertTrue(names.contains("A.seq"), names.toString()); + assertTrue(names.contains("lookup"), names.toString()); + assertTrue(names.contains("A.nested_value"), names.toString()); + } + + @Test + void oracleFeatureSetRecognizesTheNewClause() { + FeatureConfiguration configuration = new FeatureConfiguration(); + configuration.setValue(Feature.dialect, "ORACLE"); + List errors = new Validation(configuration, + List.of(net.sf.jsqlparser.util.validation.feature.OracleVersion.V19C), SQL) + .validate(); + assertTrue(errors.isEmpty(), errors.toString()); + } +} diff --git a/src/test/java/net/sf/jsqlparser/util/validation/validator/UpdateSetValidationTest.java b/src/test/java/net/sf/jsqlparser/util/validation/validator/UpdateSetValidationTest.java new file mode 100644 index 0000000000..5a9186c48e --- /dev/null +++ b/src/test/java/net/sf/jsqlparser/util/validation/validator/UpdateSetValidationTest.java @@ -0,0 +1,44 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.util.validation.validator; + +import net.sf.jsqlparser.parser.feature.Feature; +import net.sf.jsqlparser.util.validation.ValidationTestAsserts; +import net.sf.jsqlparser.util.validation.feature.FeaturesAllowed; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +class UpdateSetValidationTest extends ValidationTestAsserts { + @ParameterizedTest + @ValueSource(strings = {"UPDATE t SET a = ?, b = 1", "UPDATE t SET a = 1, b = ?", + "UPDATE t SET a = 1, b = ?, c = 3", "UPDATE t SET a = 1, b = 2, c = ?", + "UPDATE t SET (b, c) = (2, ?), a = 1", + "UPDATE t SET a = 1, b = (SELECT x FROM s WHERE x = ?)", + "MERGE INTO t USING s ON t.id = s.id WHEN MATCHED THEN UPDATE SET a = 1, b = ?"}) + void validatesValuesInEveryAssignment(String sql) { + validateNotAllowed(sql, 1, 1, FeaturesAllowed.DML.copy().remove(Feature.jdbcParameter), + Feature.jdbcParameter); + validateNoErrors(sql, 1, FeaturesAllowed.DML.copy().add(FeaturesAllowed.JDBC)); + } + + @Test + void reportsIndependentFeaturesAcrossDifferentAssignments() { + validateNotAllowed("UPDATE t SET a = 1, b = COALESCE(c, 0), d = ?", 1, 1, + FeaturesAllowed.UPDATE.copy().remove(Feature.function, Feature.jdbcParameter), + Feature.function, Feature.jdbcParameter); + } + + @Test + void keepsTupleAndSubqueryAssignmentsValid() { + validateNoErrors("UPDATE t SET (a, b) = (SELECT c, d FROM s), e = 1", 1, + FeaturesAllowed.UPDATE); + } +} diff --git a/src/test/resources/net/sf/jsqlparser/performance.sql b/src/test/resources/net/sf/jsqlparser/performance.sql index d83065643d..a8c043ad99 100644 --- a/src/test/resources/net/sf/jsqlparser/performance.sql +++ b/src/test/resources/net/sf/jsqlparser/performance.sql @@ -222,7 +222,7 @@ else 'Contact BO Developers' end, (CLM.TOT_BILLED_AMT), (CLM.TOT_NET_PAYABLE), CASE - WHEN CLM.AP_STS_C=3 THEN CKR.AP_RUN_DATE + WHEN CLM.AP_STS_C=3 THEN CKR.AP_RUN_DATE END, CASE WHEN ( case when D_CLARITY_DATABASE.CLARITY_DATABASE like '%HCCLHI%' then 'HI' @@ -315,7 +315,7 @@ END, POS_ST.ABBR, TRUNC((sysdate-( PAT.BIRTH_DATE ))/365,0), case - when D_AA_INDICATOR.CLAIM_ID is null + when D_AA_INDICATOR.CLAIM_ID is null then 'Not AA' else 'AA' end, ZC_REG_CD.NAME, @@ -392,9 +392,9 @@ AND CLM.ORIG_ADJST_CLM_ID IS NULL)) LEFT OUTER JOIN CLARITY_VENDOR VENCLM ON (VENCLM.VENDOR_ID=CLM.VENDOR_ID) LEFT OUTER JOIN ( select t1.vendor_id,t1.line,t1.TAX_ID -from vendor_tax_id t1 , -(select vendor_id,max(line) as line from vendor_tax_id group by vendor_id) t2 Where t1.vendor_id=t2.vendor_id and t1.line=t2.line -group by t1.vendor_id,t1.line,t1.TAX_ID +from vendor_tax_id t1 , +(select vendor_id,max(line) as line from vendor_tax_id group by vendor_id) t2 Where t1.vendor_id=t2.vendor_id and t1.line=t2.line +group by t1.vendor_id,t1.line,t1.TAX_ID ) D_VTN ON (VENCLM.VENDOR_ID=D_VTN.VENDOR_ID) LEFT OUTER JOIN ( SELECT DISTINCT VENDOR_ID, PLACE_OF_SERVICE_ID FROM VENDOR_POS @@ -405,12 +405,12 @@ SELECT distinct ,999 as PLACE_OF_SERVICE_ID from vendor_tax_id t1 inner join - ( - select vendor_id, - max(line) as line - from vendor_tax_id - group by vendor_id - ) t2 on t1.vendor_id=t2.vendor_id + ( + select vendor_id, + max(line) as line + from vendor_tax_id + group by vendor_id + ) t2 on t1.vendor_id=t2.vendor_id and t1.line=t2.line WHERE t1.TAX_ID = '811559375' @@ -427,23 +427,23 @@ CLAIM_ID FROM (SELECT DISTINCT CLM_EOB.CLAIM_ID ,CASE WHEN MAX(EOB.ROUTE_FROM_DISC_C)=2 THEN 'Yes' ELSE - 'No' - END AS PAT_LIABILITY + 'No' + END AS PAT_LIABILITY ,EOB.MNEMONIC ,RMC.RMC_EXTERNAL_ID||'/'||RMK.ABBR AS REMIT_CD FROM AP_CLAIM_PX_EOBS CLM_EOB, CLARITY_EOB_CODE EOB, - CLARITY_RMC RMC, - ZC_REMARK_CODE RMK + CLARITY_RMC RMC, + ZC_REMARK_CODE RMK WHERE CLM_EOB.EOB_CODE_ID=EOB.EOB_CODE_ID - AND EOB.REMIT_CODE_ID=RMC.REMIT_CODE_ID(+) - AND EOB.REMARK_CODE_C=RMK.REMARK_CODE_C(+) + AND EOB.REMIT_CODE_ID=RMC.REMIT_CODE_ID(+) + AND EOB.REMARK_CODE_C=RMK.REMARK_CODE_C(+) --1 IS PEND CODE 2 IS DENIAL CODE 3 IS INFO CODE AND EOB.CODE_TYPE_C=3 GROUP BY CLM_EOB.CLAIM_ID,EOB.ROUTE_FROM_DISC_C,EOB.MNEMONIC,RMC.RMC_EXTERNAL_ID,RMK.ABBR - ) + ) GROUP BY CLAIM_ID,PAT_LIABILITY ) LISTAGG_DTL_INFO ON (CLM.CLAIM_ID=LISTAGG_DTL_INFO.CLAIM_ID) LEFT OUTER JOIN ( @@ -479,113 +479,113 @@ FROM (SELECT DISTINCT FROM AP_CLAIM_EOB_CODE CLM_EOB, CLARITY_EOB_CODE EOB, - CLARITY_RMC RMC, - ZC_REMARK_CODE RMK + CLARITY_RMC RMC, + ZC_REMARK_CODE RMK WHERE CLM_EOB.EOB_CODE_ID=EOB.EOB_CODE_ID - AND EOB.REMIT_CODE_ID=RMC.REMIT_CODE_ID(+) - AND EOB.REMARK_CODE_C=RMK.REMARK_CODE_C(+) + AND EOB.REMIT_CODE_ID=RMC.REMIT_CODE_ID(+) + AND EOB.REMARK_CODE_C=RMK.REMARK_CODE_C(+) AND CLM_EOB.RESOLUTION_DATE IS NULL --1 IS PEND CODE 2 IS DENIAL CODE 3 IS INFO CODE AND EOB.CODE_TYPE_C=3 - ) + ) GROUP BY CLAIM_ID ) LISTAGG_HDR_INFO ON (CLM.CLAIM_ID=LISTAGG_HDR_INFO.CLAIM_ID) LEFT OUTER JOIN AP_CLAIM_CHECK CLM_CHK ON (CLM.CLAIM_ID=CLM_CHK.CLAIM_ID) LEFT OUTER JOIN AP_CHECK CKR ON (CLM_CHK.CHECK_ID=CKR.CHECK_ID) WHERE -( COALESCE(CLM.WORKFLOW_C,0) IN @Prompt(P_WorkflowTypeInclude) and COALESCE(CLM_TRAIT_4.NAME,'CCA') In @Prompt(P_CCA-TPMG) ) +( COALESCE(CLM.WORKFLOW_C,0) IN (PARAM_VALUE('P_WorkflowTypeInclude')) and COALESCE(CLM_TRAIT_4.NAME,'CCA') In (PARAM_VALUE('P_CCA-TPMG')) ) AND ( - coalesce(EPPCL.PRODUCT_TYPE ,ZC_PRD_TYP.NAME) IN @Prompt('Enter Product Type or Leave Blank for All:','A','Claim Header (CLM)\Claim Header IDs (CLM)\Benefit Plan Id (CLM)\Product Type',Multi,Free,Persistent,,User:1,Optional) + coalesce(EPPCL.PRODUCT_TYPE ,ZC_PRD_TYP.NAME) IN (PARAM_VALUE('Enter Product Type or Leave Blank for All:', 'A', 'Claim Header (CLM)\Claim Header IDs (CLM)\Benefit Plan Id (CLM)\Product Type', 'Multi', 'Free', 'Persistent', NULL, 'User:1', 'Optional')) AND - nvl(LOBCL.LOB_NAME,'UNKNOWN') LIKE @Prompt('Enter Line of Business or Leave Blank for All:','A','Claim Header (CLM)\Line Of Business (CLM)\Line Of Business Name (CLM)',Mono,Free,Persistent,,User:2,Optional) + nvl(LOBCL.LOB_NAME,'UNKNOWN') LIKE PARAM_VALUE('Enter Line of Business or Leave Blank for All:', 'A', 'Claim Header (CLM)\Line Of Business (CLM)\Line Of Business Name (CLM)', 'Mono', 'Free', 'Persistent', NULL, 'User:2', 'Optional') AND - SERREN.PROV_NAME LIKE @Prompt('Enter Provider Name or Leave Blank for All:','A','Provider (PRV)\Rendering (SERREN)\Prov Name (SERREN)',Mono,Free,Persistent,,User:3,Optional) + SERREN.PROV_NAME LIKE PARAM_VALUE('Enter Provider Name or Leave Blank for All:', 'A', 'Provider (PRV)\Rendering (SERREN)\Prov Name (SERREN)', 'Mono', 'Free', 'Persistent', NULL, 'User:3', 'Optional') AND - SERREN_2.NPI LIKE @Prompt('Enter Provider ID or Leave Blank for All:','A',,Mono,Free,Persistent,,User:4,Optional) + SERREN_2.NPI LIKE PARAM_VALUE('Enter Provider ID or Leave Blank for All:', 'A', NULL, 'Mono', 'Free', 'Persistent', NULL, 'User:4', 'Optional') AND - D_VTN.TAX_ID LIKE @Prompt('Enter Vendor Tax ID or Leave Blank for All:','A',,Mono,Free,Persistent,,User:5,Optional) + D_VTN.TAX_ID LIKE PARAM_VALUE('Enter Vendor Tax ID or Leave Blank for All:', 'A', NULL, 'Mono', 'Free', 'Persistent', NULL, 'User:5', 'Optional') AND - VENCLM.VENDOR_NAME LIKE @Prompt('Enter Vendor Name or Leave Blank for All:','A','Claim Header Vendor (VENCLM)\Vendor Name (VENCLM)',Mono,Free,Persistent,,User:6,Optional) + VENCLM.VENDOR_NAME LIKE PARAM_VALUE('Enter Vendor Name or Leave Blank for All:', 'A', 'Claim Header Vendor (VENCLM)\Vendor Name (VENCLM)', 'Mono', 'Free', 'Persistent', NULL, 'User:6', 'Optional') AND - GRP.PLAN_GRP_NAME LIKE @Prompt('Enter Plan Group Name or Leave Blank for All:','A','Plan / Group (GRP)\Plan Grp Name (GRP)',Mono,Free,Persistent,,User:0,Optional) + GRP.PLAN_GRP_NAME LIKE PARAM_VALUE('Enter Plan Group Name or Leave Blank for All:', 'A', 'Plan / Group (GRP)\Plan Grp Name (GRP)', 'Mono', 'Free', 'Persistent', NULL, 'User:0', 'Optional') AND CLM.STATUS_C = 3 AND CASE - WHEN CLM.AP_STS_C=3 THEN CKR.AP_RUN_DATE + WHEN CLM.AP_STS_C=3 THEN CKR.AP_RUN_DATE END BETWEEN (CASE - WHEN ( @Prompt(Enter Claim Finalized From Date:§(relative or absolute date§)) )='t' THEN - trunc(sysdate) - WHEN ( @Prompt(Enter Claim Finalized From Date:§(relative or absolute date§)) ) Like 't-%' THEN - trunc(sysdate)-to_number(Substr(( @Prompt(Enter Claim Finalized From Date:§(relative or absolute date§)) ),3,3)) - WHEN ( @Prompt(Enter Claim Finalized From Date:§(relative or absolute date§)) )='wb' THEN + WHEN ( PARAM_VALUE('Enter Claim Finalized From Date:(relative or absolute date)') )='t' THEN + trunc(sysdate) + WHEN ( PARAM_VALUE('Enter Claim Finalized From Date:(relative or absolute date)') ) Like 't-%' THEN + trunc(sysdate)-to_number(Substr(( PARAM_VALUE('Enter Claim Finalized From Date:(relative or absolute date)') ),3,3)) + WHEN ( PARAM_VALUE('Enter Claim Finalized From Date:(relative or absolute date)') )='wb' THEN TRUNC(sysdate, 'IW')-1 - WHEN ( @Prompt(Enter Claim Finalized From Date:§(relative or absolute date§)) )='wb-1' THEN + WHEN ( PARAM_VALUE('Enter Claim Finalized From Date:(relative or absolute date)') )='wb-1' THEN TRUNC(sysdate, 'IW')-8 - WHEN ( @Prompt(Enter Claim Finalized From Date:§(relative or absolute date§)) )='we' THEN + WHEN ( PARAM_VALUE('Enter Claim Finalized From Date:(relative or absolute date)') )='we' THEN NEXT_DAY(TRUNC(sysdate,'IW'),'SATURDAY') - WHEN ( @Prompt(Enter Claim Finalized From Date:§(relative or absolute date§)) )='we-1' THEN + WHEN ( PARAM_VALUE('Enter Claim Finalized From Date:(relative or absolute date)') )='we-1' THEN NEXT_DAY(TRUNC(sysdate,'IW')-8,'SATURDAY') - WHEN ( @Prompt(Enter Claim Finalized From Date:§(relative or absolute date§)) )='mb' THEN - trunc(sysdate,'MM') - WHEN ( @Prompt(Enter Claim Finalized From Date:§(relative or absolute date§)) )='me' THEN - trunc(last_day(sysdate)) - WHEN ( @Prompt(Enter Claim Finalized From Date:§(relative or absolute date§)) )='mb-1' THEN - trunc(trunc(sysdate, 'MM') - 1, 'MM') - WHEN ( @Prompt(Enter Claim Finalized From Date:§(relative or absolute date§)) )='me-1' THEN - (trunc(sysdate, 'MM') - 1) - WHEN ( @Prompt(Enter Claim Finalized From Date:§(relative or absolute date§)) )='mb-2' THEN - add_months(trunc(sysdate, 'MM'), - 2) - WHEN ( @Prompt(Enter Claim Finalized From Date:§(relative or absolute date§)) )='me-2' THEN - (last_day(add_months (sysdate,-2))) - WHEN ( @Prompt(Enter Claim Finalized From Date:§(relative or absolute date§)) )='yb' THEN - trunc(sysdate,'YY') - WHEN ( @Prompt(Enter Claim Finalized From Date:§(relative or absolute date§)) )='yb-1' THEN - trunc(trunc(sysdate, 'YY') - 1, 'YY') + WHEN ( PARAM_VALUE('Enter Claim Finalized From Date:(relative or absolute date)') )='mb' THEN + trunc(sysdate,'MM') + WHEN ( PARAM_VALUE('Enter Claim Finalized From Date:(relative or absolute date)') )='me' THEN + trunc(last_day(sysdate)) + WHEN ( PARAM_VALUE('Enter Claim Finalized From Date:(relative or absolute date)') )='mb-1' THEN + trunc(trunc(sysdate, 'MM') - 1, 'MM') + WHEN ( PARAM_VALUE('Enter Claim Finalized From Date:(relative or absolute date)') )='me-1' THEN + (trunc(sysdate, 'MM') - 1) + WHEN ( PARAM_VALUE('Enter Claim Finalized From Date:(relative or absolute date)') )='mb-2' THEN + add_months(trunc(sysdate, 'MM'), - 2) + WHEN ( PARAM_VALUE('Enter Claim Finalized From Date:(relative or absolute date)') )='me-2' THEN + (last_day(add_months (sysdate,-2))) + WHEN ( PARAM_VALUE('Enter Claim Finalized From Date:(relative or absolute date)') )='yb' THEN + trunc(sysdate,'YY') + WHEN ( PARAM_VALUE('Enter Claim Finalized From Date:(relative or absolute date)') )='yb-1' THEN + trunc(trunc(sysdate, 'YY') - 1, 'YY') else - TO_DATE(( @Prompt(Enter Claim Finalized From Date:§(relative or absolute date§)) ),'MM/dd/yyyy') + TO_DATE(( PARAM_VALUE('Enter Claim Finalized From Date:(relative or absolute date)') ),'MM/dd/yyyy') END) AND (CASE - WHEN ( @Prompt(Enter Claim Finalized Thru Date:§(relative or absolute date§)) )='t' THEN - trunc(sysdate) - WHEN ( @Prompt(Enter Claim Finalized Thru Date:§(relative or absolute date§)) ) Like't-%' THEN - trunc(sysdate)-to_number(Substr(( @Prompt(Enter Claim Finalized Thru Date:§(relative or absolute date§)) ),3,3)) - WHEN ( @Prompt(Enter Claim Finalized Thru Date:§(relative or absolute date§)) )='wb' THEN + WHEN ( PARAM_VALUE('Enter Claim Finalized Thru Date:(relative or absolute date)') )='t' THEN + trunc(sysdate) + WHEN ( PARAM_VALUE('Enter Claim Finalized Thru Date:(relative or absolute date)') ) Like't-%' THEN + trunc(sysdate)-to_number(Substr(( PARAM_VALUE('Enter Claim Finalized Thru Date:(relative or absolute date)') ),3,3)) + WHEN ( PARAM_VALUE('Enter Claim Finalized Thru Date:(relative or absolute date)') )='wb' THEN TRUNC(sysdate, 'IW')-1 - WHEN ( @Prompt(Enter Claim Finalized Thru Date:§(relative or absolute date§)) )='wb-1' THEN + WHEN ( PARAM_VALUE('Enter Claim Finalized Thru Date:(relative or absolute date)') )='wb-1' THEN TRUNC(sysdate, 'IW')-8 - WHEN ( @Prompt(Enter Claim Finalized Thru Date:§(relative or absolute date§)) )='we' THEN + WHEN ( PARAM_VALUE('Enter Claim Finalized Thru Date:(relative or absolute date)') )='we' THEN NEXT_DAY(TRUNC(sysdate,'IW'),'SATURDAY') - WHEN ( @Prompt(Enter Claim Finalized Thru Date:§(relative or absolute date§)) )='we-1' THEN + WHEN ( PARAM_VALUE('Enter Claim Finalized Thru Date:(relative or absolute date)') )='we-1' THEN NEXT_DAY(TRUNC(sysdate,'IW')-8,'SATURDAY') - WHEN ( @Prompt(Enter Claim Finalized Thru Date:§(relative or absolute date§)) )='mb' THEN - trunc(sysdate,'MM') - WHEN ( @Prompt(Enter Claim Finalized Thru Date:§(relative or absolute date§)) )='me' THEN - trunc(last_day(sysdate)) - WHEN ( @Prompt(Enter Claim Finalized Thru Date:§(relative or absolute date§)) )='mb-1' THEN - trunc(trunc(sysdate, 'MM') - 1, 'MM') - WHEN ( @Prompt(Enter Claim Finalized Thru Date:§(relative or absolute date§)) )='me-1' THEN - (trunc(sysdate, 'MM') - 1) - WHEN ( @Prompt(Enter Claim Finalized Thru Date:§(relative or absolute date§)) )='mb-2' THEN - add_months(trunc(sysdate, 'MM'), - 2) - WHEN ( @Prompt(Enter Claim Finalized Thru Date:§(relative or absolute date§)) )='me-2' THEN - (last_day(add_months (sysdate,-2))) - WHEN ( @Prompt(Enter Claim Finalized Thru Date:§(relative or absolute date§)) )='yb' THEN - trunc(sysdate,'YY') - WHEN ( @Prompt(Enter Claim Finalized Thru Date:§(relative or absolute date§)) )='yb-1' THEN - trunc(trunc(sysdate, 'YY') - 1, 'YY') + WHEN ( PARAM_VALUE('Enter Claim Finalized Thru Date:(relative or absolute date)') )='mb' THEN + trunc(sysdate,'MM') + WHEN ( PARAM_VALUE('Enter Claim Finalized Thru Date:(relative or absolute date)') )='me' THEN + trunc(last_day(sysdate)) + WHEN ( PARAM_VALUE('Enter Claim Finalized Thru Date:(relative or absolute date)') )='mb-1' THEN + trunc(trunc(sysdate, 'MM') - 1, 'MM') + WHEN ( PARAM_VALUE('Enter Claim Finalized Thru Date:(relative or absolute date)') )='me-1' THEN + (trunc(sysdate, 'MM') - 1) + WHEN ( PARAM_VALUE('Enter Claim Finalized Thru Date:(relative or absolute date)') )='mb-2' THEN + add_months(trunc(sysdate, 'MM'), - 2) + WHEN ( PARAM_VALUE('Enter Claim Finalized Thru Date:(relative or absolute date)') )='me-2' THEN + (last_day(add_months (sysdate,-2))) + WHEN ( PARAM_VALUE('Enter Claim Finalized Thru Date:(relative or absolute date)') )='yb' THEN + trunc(sysdate,'YY') + WHEN ( PARAM_VALUE('Enter Claim Finalized Thru Date:(relative or absolute date)') )='yb-1' THEN + trunc(trunc(sysdate, 'YY') - 1, 'YY') else - TO_DATE(( @Prompt(Enter Claim Finalized Thru Date:§(relative or absolute date§)) ),'MM/dd/yyyy') + TO_DATE(( PARAM_VALUE('Enter Claim Finalized Thru Date:(relative or absolute date)') ),'MM/dd/yyyy') END) AND ( - EOB_CODE.MNEMONIC IN @Prompt('Enter Claim Denial Code(s) or Leave Blank for All:','A','Claim EOB (EOB)\Eob Code Id (CLM_EOB)\Mnemonic (EOB_CODE)',Multi,Free,Persistent,,User:8,Optional) + EOB_CODE.MNEMONIC IN (PARAM_VALUE('Enter Claim Denial Code(s) or Leave Blank for All:', 'A', 'Claim EOB (EOB)\Eob Code Id (CLM_EOB)\Mnemonic (EOB_CODE)', 'Multi', 'Free', 'Persistent', NULL, 'User:8', 'Optional')) OR - EOB_CODE2.MNEMONIC IN @Prompt('Enter Claim Denial Code(s) or Leave Blank for All:','A','Claim Line (CLD)\Claim Line EOB\Eob Code Id (CLD)\Mnemonic (CLD)',Multi,Free,Persistent,,User:7,Optional) + EOB_CODE2.MNEMONIC IN (PARAM_VALUE('Enter Claim Denial Code(s) or Leave Blank for All:', 'A', 'Claim Line (CLD)\Claim Line EOB\Eob Code Id (CLD)\Mnemonic (CLD)', 'Multi', 'Free', 'Persistent', NULL, 'User:7', 'Optional')) ) AND - CLMPOS.NAME IN @Prompt('Enter POS Type(s) or Leave Blank for All:','A','Claim Line (CLD)\Pos Type C (CLD)\Name (CLMPOS)',Multi,Free,Persistent,,User:9,Optional) + CLMPOS.NAME IN (PARAM_VALUE('Enter POS Type(s) or Leave Blank for All:', 'A', 'Claim Line (CLD)\Pos Type C (CLD)\Name (CLMPOS)', 'Multi', 'Free', 'Persistent', NULL, 'User:9', 'Optional')) AND CASE WHEN CLM.ORIG_REV_CLM_ID IS NOT NULL @@ -597,19 +597,19 @@ WHEN CLM.ORIG_ADJST_CLM_ID IS NOT NULL AND CLM.ORIG_REV_CLM_ID IS NULL AND CLM. WHEN CLM.ORIG_ADJST_CLM_ID IS NULL AND CLM.ORIG_REV_CLM_ID IS NULL AND "LEGACY_ADJST"."CLAIM_ID" IS NULL THEN 'Original Claim' ELSE NULL -END IN @Prompt('Enter Claim Adjudication Type:','A','Claim Header (CLM)\Clm Adj Type (CLM)\Clm Adj Type Desc (CLM)',Multi,Free,Persistent,,User:13) +END IN (PARAM_VALUE('Enter Claim Adjudication Type:', 'A', 'Claim Header (CLM)\Clm Adj Type (CLM)\Clm Adj Type Desc (CLM)', 'Multi', 'Free', 'Persistent', NULL, 'User:13')) AND - NVL(CLM_TRAIT.NAME,'KFHP') IN @Prompt('Enter ANIC Claim Trait or Leave Blank for All:','A','Claim Header (CLM)\Company Code (CLM)',Multi,Free,Persistent,,User:10,Optional) + NVL(CLM_TRAIT.NAME,'KFHP') IN (PARAM_VALUE('Enter ANIC Claim Trait or Leave Blank for All:', 'A', 'Claim Header (CLM)\Company Code (CLM)', 'Multi', 'Free', 'Persistent', NULL, 'User:10', 'Optional')) AND - nvl(CLM2.DENTAL_INFO_YN,'N') IN @Prompt('Enter Dental Indicator (Y/N):','A','Claim Header (CLM)\Dental Info Yn (CLM2)',Multi,Free,Persistent,{'N'},User:11,Optional) + nvl(CLM2.DENTAL_INFO_YN,'N') IN (PARAM_VALUE('Enter Dental Indicator (Y/N):', 'A', 'Claim Header (CLM)\Dental Info Yn (CLM2)', 'Multi', 'Free', 'Persistent', 'N', 'User:11', 'Optional')) AND CLM.ADJST_CLM_ID Is Null AND - ZC_REG_CD.NAME IN @Prompt('Enter CO Service Area Name:','A','Plan / Group (GRP)\Current Region Code C (GRP)\Current Region Code Name (GRP)',Multi,Free,Persistent,,User:12,Optional) + ZC_REG_CD.NAME IN (PARAM_VALUE('Enter CO Service Area Name:', 'A', 'Plan / Group (GRP)\Current Region Code C (GRP)\Current Region Code Name (GRP)', 'Multi', 'Free', 'Persistent', NULL, 'User:12', 'Optional')) AND ( ( CASE WHEN D_VM.PLACE_OF_SERVICE_ID IS NOT NULL THEN 'Y' ELSE 'N' -END ) in @Prompt(Visiting Member) OR ( 'ALL') IN @Prompt(Visiting Member) ) +END ) in (PARAM_VALUE('Visiting Member')) OR ( 'ALL') IN (PARAM_VALUE('Visiting Member')) ) ) --END-- SELECT DISTINCT @@ -639,9 +639,9 @@ FROM LEFT OUTER JOIN CLARITY_VENDOR VENCLM ON (VENCLM.VENDOR_ID=CLM.VENDOR_ID) LEFT OUTER JOIN ( select t1.vendor_id,t1.line,t1.TAX_ID -from vendor_tax_id t1 , -(select vendor_id,max(line) as line from vendor_tax_id group by vendor_id) t2 Where t1.vendor_id=t2.vendor_id and t1.line=t2.line -group by t1.vendor_id,t1.line,t1.TAX_ID +from vendor_tax_id t1 , +(select vendor_id,max(line) as line from vendor_tax_id group by vendor_id) t2 Where t1.vendor_id=t2.vendor_id and t1.line=t2.line +group by t1.vendor_id,t1.line,t1.TAX_ID ) D_VTN ON (VENCLM.VENDOR_ID=D_VTN.VENDOR_ID) LEFT OUTER JOIN ( SELECT DISTINCT VENDOR_ID, PLACE_OF_SERVICE_ID FROM VENDOR_POS @@ -652,12 +652,12 @@ SELECT distinct ,999 as PLACE_OF_SERVICE_ID from vendor_tax_id t1 inner join - ( - select vendor_id, - max(line) as line - from vendor_tax_id - group by vendor_id - ) t2 on t1.vendor_id=t2.vendor_id + ( + select vendor_id, + max(line) as line + from vendor_tax_id + group by vendor_id + ) t2 on t1.vendor_id=t2.vendor_id and t1.line=t2.line WHERE t1.TAX_ID = '811559375' @@ -687,98 +687,98 @@ else 'Contact BO Developers' end ) IN ('SC','NC') AND EOB_CODE.MNEMONIC IN ('CI LEFT OUTER JOIN AP_CLAIM_CHECK CLM_CHK ON (CLM.CLAIM_ID=CLM_CHK.CLAIM_ID) LEFT OUTER JOIN AP_CHECK CKR ON (CLM_CHK.CHECK_ID=CKR.CHECK_ID) WHERE -( COALESCE(CLM.WORKFLOW_C,0) IN @Prompt(P_WorkflowTypeInclude) and COALESCE(CLM_TRAIT_4.NAME,'CCA') In @Prompt(P_CCA-TPMG) ) +( COALESCE(CLM.WORKFLOW_C,0) IN (PARAM_VALUE('P_WorkflowTypeInclude')) and COALESCE(CLM_TRAIT_4.NAME,'CCA') In (PARAM_VALUE('P_CCA-TPMG')) ) AND ( - coalesce(EPPCL.PRODUCT_TYPE ,ZC_PRD_TYP.NAME) IN @Prompt('Enter Product Type or Leave Blank for All:','A','Claim Header (CLM)\Claim Header IDs (CLM)\Benefit Plan Id (CLM)\Product Type',Multi,Free,Persistent,,User:1,Optional) + coalesce(EPPCL.PRODUCT_TYPE ,ZC_PRD_TYP.NAME) IN (PARAM_VALUE('Enter Product Type or Leave Blank for All:', 'A', 'Claim Header (CLM)\Claim Header IDs (CLM)\Benefit Plan Id (CLM)\Product Type', 'Multi', 'Free', 'Persistent', NULL, 'User:1', 'Optional')) AND - nvl(LOBCL.LOB_NAME,'UNKNOWN') LIKE @Prompt('Enter Line of Business or Leave Blank for All:','A','Claim Header (CLM)\Line Of Business (CLM)\Line Of Business Name (CLM)',Mono,Free,Persistent,,User:2,Optional) + nvl(LOBCL.LOB_NAME,'UNKNOWN') LIKE PARAM_VALUE('Enter Line of Business or Leave Blank for All:', 'A', 'Claim Header (CLM)\Line Of Business (CLM)\Line Of Business Name (CLM)', 'Mono', 'Free', 'Persistent', NULL, 'User:2', 'Optional') AND - SERREN.PROV_NAME LIKE @Prompt('Enter Provider Name or Leave Blank for All:','A','Provider (PRV)\Rendering (SERREN)\Prov Name (SERREN)',Mono,Free,Persistent,,User:3,Optional) + SERREN.PROV_NAME LIKE PARAM_VALUE('Enter Provider Name or Leave Blank for All:', 'A', 'Provider (PRV)\Rendering (SERREN)\Prov Name (SERREN)', 'Mono', 'Free', 'Persistent', NULL, 'User:3', 'Optional') AND - SERREN_2.NPI LIKE @Prompt('Enter Provider ID or Leave Blank for All:','A',,Mono,Free,Persistent,,User:4,Optional) + SERREN_2.NPI LIKE PARAM_VALUE('Enter Provider ID or Leave Blank for All:', 'A', NULL, 'Mono', 'Free', 'Persistent', NULL, 'User:4', 'Optional') AND - D_VTN.TAX_ID LIKE @Prompt('Enter Vendor Tax ID or Leave Blank for All:','A',,Mono,Free,Persistent,,User:5,Optional) + D_VTN.TAX_ID LIKE PARAM_VALUE('Enter Vendor Tax ID or Leave Blank for All:', 'A', NULL, 'Mono', 'Free', 'Persistent', NULL, 'User:5', 'Optional') AND - VENCLM.VENDOR_NAME LIKE @Prompt('Enter Vendor Name or Leave Blank for All:','A','Claim Header Vendor (VENCLM)\Vendor Name (VENCLM)',Mono,Free,Persistent,,User:6,Optional) + VENCLM.VENDOR_NAME LIKE PARAM_VALUE('Enter Vendor Name or Leave Blank for All:', 'A', 'Claim Header Vendor (VENCLM)\Vendor Name (VENCLM)', 'Mono', 'Free', 'Persistent', NULL, 'User:6', 'Optional') AND - GRP.PLAN_GRP_NAME LIKE @Prompt('Enter Plan Group Name or Leave Blank for All:','A','Plan / Group (GRP)\Plan Grp Name (GRP)',Mono,Free,Persistent,,User:0,Optional) + GRP.PLAN_GRP_NAME LIKE PARAM_VALUE('Enter Plan Group Name or Leave Blank for All:', 'A', 'Plan / Group (GRP)\Plan Grp Name (GRP)', 'Mono', 'Free', 'Persistent', NULL, 'User:0', 'Optional') AND CLM.STATUS_C = 3 AND CASE - WHEN CLM.AP_STS_C=3 THEN CKR.AP_RUN_DATE + WHEN CLM.AP_STS_C=3 THEN CKR.AP_RUN_DATE END BETWEEN (CASE - WHEN ( @Prompt(Enter Claim Finalized From Date:§(relative or absolute date§)) )='t' THEN - trunc(sysdate) - WHEN ( @Prompt(Enter Claim Finalized From Date:§(relative or absolute date§)) ) Like 't-%' THEN - trunc(sysdate)-to_number(Substr(( @Prompt(Enter Claim Finalized From Date:§(relative or absolute date§)) ),3,3)) - WHEN ( @Prompt(Enter Claim Finalized From Date:§(relative or absolute date§)) )='wb' THEN + WHEN ( PARAM_VALUE('Enter Claim Finalized From Date:(relative or absolute date)') )='t' THEN + trunc(sysdate) + WHEN ( PARAM_VALUE('Enter Claim Finalized From Date:(relative or absolute date)') ) Like 't-%' THEN + trunc(sysdate)-to_number(Substr(( PARAM_VALUE('Enter Claim Finalized From Date:(relative or absolute date)') ),3,3)) + WHEN ( PARAM_VALUE('Enter Claim Finalized From Date:(relative or absolute date)') )='wb' THEN TRUNC(sysdate, 'IW')-1 - WHEN ( @Prompt(Enter Claim Finalized From Date:§(relative or absolute date§)) )='wb-1' THEN + WHEN ( PARAM_VALUE('Enter Claim Finalized From Date:(relative or absolute date)') )='wb-1' THEN TRUNC(sysdate, 'IW')-8 - WHEN ( @Prompt(Enter Claim Finalized From Date:§(relative or absolute date§)) )='we' THEN + WHEN ( PARAM_VALUE('Enter Claim Finalized From Date:(relative or absolute date)') )='we' THEN NEXT_DAY(TRUNC(sysdate,'IW'),'SATURDAY') - WHEN ( @Prompt(Enter Claim Finalized From Date:§(relative or absolute date§)) )='we-1' THEN + WHEN ( PARAM_VALUE('Enter Claim Finalized From Date:(relative or absolute date)') )='we-1' THEN NEXT_DAY(TRUNC(sysdate,'IW')-8,'SATURDAY') - WHEN ( @Prompt(Enter Claim Finalized From Date:§(relative or absolute date§)) )='mb' THEN - trunc(sysdate,'MM') - WHEN ( @Prompt(Enter Claim Finalized From Date:§(relative or absolute date§)) )='me' THEN - trunc(last_day(sysdate)) - WHEN ( @Prompt(Enter Claim Finalized From Date:§(relative or absolute date§)) )='mb-1' THEN - trunc(trunc(sysdate, 'MM') - 1, 'MM') - WHEN ( @Prompt(Enter Claim Finalized From Date:§(relative or absolute date§)) )='me-1' THEN - (trunc(sysdate, 'MM') - 1) - WHEN ( @Prompt(Enter Claim Finalized From Date:§(relative or absolute date§)) )='mb-2' THEN - add_months(trunc(sysdate, 'MM'), - 2) - WHEN ( @Prompt(Enter Claim Finalized From Date:§(relative or absolute date§)) )='me-2' THEN - (last_day(add_months (sysdate,-2))) - WHEN ( @Prompt(Enter Claim Finalized From Date:§(relative or absolute date§)) )='yb' THEN - trunc(sysdate,'YY') - WHEN ( @Prompt(Enter Claim Finalized From Date:§(relative or absolute date§)) )='yb-1' THEN - trunc(trunc(sysdate, 'YY') - 1, 'YY') + WHEN ( PARAM_VALUE('Enter Claim Finalized From Date:(relative or absolute date)') )='mb' THEN + trunc(sysdate,'MM') + WHEN ( PARAM_VALUE('Enter Claim Finalized From Date:(relative or absolute date)') )='me' THEN + trunc(last_day(sysdate)) + WHEN ( PARAM_VALUE('Enter Claim Finalized From Date:(relative or absolute date)') )='mb-1' THEN + trunc(trunc(sysdate, 'MM') - 1, 'MM') + WHEN ( PARAM_VALUE('Enter Claim Finalized From Date:(relative or absolute date)') )='me-1' THEN + (trunc(sysdate, 'MM') - 1) + WHEN ( PARAM_VALUE('Enter Claim Finalized From Date:(relative or absolute date)') )='mb-2' THEN + add_months(trunc(sysdate, 'MM'), - 2) + WHEN ( PARAM_VALUE('Enter Claim Finalized From Date:(relative or absolute date)') )='me-2' THEN + (last_day(add_months (sysdate,-2))) + WHEN ( PARAM_VALUE('Enter Claim Finalized From Date:(relative or absolute date)') )='yb' THEN + trunc(sysdate,'YY') + WHEN ( PARAM_VALUE('Enter Claim Finalized From Date:(relative or absolute date)') )='yb-1' THEN + trunc(trunc(sysdate, 'YY') - 1, 'YY') else - TO_DATE(( @Prompt(Enter Claim Finalized From Date:§(relative or absolute date§)) ),'MM/dd/yyyy') + TO_DATE(( PARAM_VALUE('Enter Claim Finalized From Date:(relative or absolute date)') ),'MM/dd/yyyy') END) AND (CASE - WHEN ( @Prompt(Enter Claim Finalized Thru Date:§(relative or absolute date§)) )='t' THEN - trunc(sysdate) - WHEN ( @Prompt(Enter Claim Finalized Thru Date:§(relative or absolute date§)) ) Like't-%' THEN - trunc(sysdate)-to_number(Substr(( @Prompt(Enter Claim Finalized Thru Date:§(relative or absolute date§)) ),3,3)) - WHEN ( @Prompt(Enter Claim Finalized Thru Date:§(relative or absolute date§)) )='wb' THEN + WHEN ( PARAM_VALUE('Enter Claim Finalized Thru Date:(relative or absolute date)') )='t' THEN + trunc(sysdate) + WHEN ( PARAM_VALUE('Enter Claim Finalized Thru Date:(relative or absolute date)') ) Like't-%' THEN + trunc(sysdate)-to_number(Substr(( PARAM_VALUE('Enter Claim Finalized Thru Date:(relative or absolute date)') ),3,3)) + WHEN ( PARAM_VALUE('Enter Claim Finalized Thru Date:(relative or absolute date)') )='wb' THEN TRUNC(sysdate, 'IW')-1 - WHEN ( @Prompt(Enter Claim Finalized Thru Date:§(relative or absolute date§)) )='wb-1' THEN + WHEN ( PARAM_VALUE('Enter Claim Finalized Thru Date:(relative or absolute date)') )='wb-1' THEN TRUNC(sysdate, 'IW')-8 - WHEN ( @Prompt(Enter Claim Finalized Thru Date:§(relative or absolute date§)) )='we' THEN + WHEN ( PARAM_VALUE('Enter Claim Finalized Thru Date:(relative or absolute date)') )='we' THEN NEXT_DAY(TRUNC(sysdate,'IW'),'SATURDAY') - WHEN ( @Prompt(Enter Claim Finalized Thru Date:§(relative or absolute date§)) )='we-1' THEN + WHEN ( PARAM_VALUE('Enter Claim Finalized Thru Date:(relative or absolute date)') )='we-1' THEN NEXT_DAY(TRUNC(sysdate,'IW')-8,'SATURDAY') - WHEN ( @Prompt(Enter Claim Finalized Thru Date:§(relative or absolute date§)) )='mb' THEN - trunc(sysdate,'MM') - WHEN ( @Prompt(Enter Claim Finalized Thru Date:§(relative or absolute date§)) )='me' THEN - trunc(last_day(sysdate)) - WHEN ( @Prompt(Enter Claim Finalized Thru Date:§(relative or absolute date§)) )='mb-1' THEN - trunc(trunc(sysdate, 'MM') - 1, 'MM') - WHEN ( @Prompt(Enter Claim Finalized Thru Date:§(relative or absolute date§)) )='me-1' THEN - (trunc(sysdate, 'MM') - 1) - WHEN ( @Prompt(Enter Claim Finalized Thru Date:§(relative or absolute date§)) )='mb-2' THEN - add_months(trunc(sysdate, 'MM'), - 2) - WHEN ( @Prompt(Enter Claim Finalized Thru Date:§(relative or absolute date§)) )='me-2' THEN - (last_day(add_months (sysdate,-2))) - WHEN ( @Prompt(Enter Claim Finalized Thru Date:§(relative or absolute date§)) )='yb' THEN - trunc(sysdate,'YY') - WHEN ( @Prompt(Enter Claim Finalized Thru Date:§(relative or absolute date§)) )='yb-1' THEN - trunc(trunc(sysdate, 'YY') - 1, 'YY') + WHEN ( PARAM_VALUE('Enter Claim Finalized Thru Date:(relative or absolute date)') )='mb' THEN + trunc(sysdate,'MM') + WHEN ( PARAM_VALUE('Enter Claim Finalized Thru Date:(relative or absolute date)') )='me' THEN + trunc(last_day(sysdate)) + WHEN ( PARAM_VALUE('Enter Claim Finalized Thru Date:(relative or absolute date)') )='mb-1' THEN + trunc(trunc(sysdate, 'MM') - 1, 'MM') + WHEN ( PARAM_VALUE('Enter Claim Finalized Thru Date:(relative or absolute date)') )='me-1' THEN + (trunc(sysdate, 'MM') - 1) + WHEN ( PARAM_VALUE('Enter Claim Finalized Thru Date:(relative or absolute date)') )='mb-2' THEN + add_months(trunc(sysdate, 'MM'), - 2) + WHEN ( PARAM_VALUE('Enter Claim Finalized Thru Date:(relative or absolute date)') )='me-2' THEN + (last_day(add_months (sysdate,-2))) + WHEN ( PARAM_VALUE('Enter Claim Finalized Thru Date:(relative or absolute date)') )='yb' THEN + trunc(sysdate,'YY') + WHEN ( PARAM_VALUE('Enter Claim Finalized Thru Date:(relative or absolute date)') )='yb-1' THEN + trunc(trunc(sysdate, 'YY') - 1, 'YY') else - TO_DATE(( @Prompt(Enter Claim Finalized Thru Date:§(relative or absolute date§)) ),'MM/dd/yyyy') + TO_DATE(( PARAM_VALUE('Enter Claim Finalized Thru Date:(relative or absolute date)') ),'MM/dd/yyyy') END) AND ( - EOB_CODE.MNEMONIC IN @Prompt('Enter Claim Denial Code(s) or Leave Blank for All:','A','Claim EOB (EOB)\Eob Code Id (CLM_EOB)\Mnemonic (EOB_CODE)',Multi,Free,Persistent,,User:8,Optional) + EOB_CODE.MNEMONIC IN (PARAM_VALUE('Enter Claim Denial Code(s) or Leave Blank for All:', 'A', 'Claim EOB (EOB)\Eob Code Id (CLM_EOB)\Mnemonic (EOB_CODE)', 'Multi', 'Free', 'Persistent', NULL, 'User:8', 'Optional')) OR - EOB_CODE2.MNEMONIC IN @Prompt('Enter Claim Denial Code(s) or Leave Blank for All:','A','Claim Line (CLD)\Claim Line EOB\Eob Code Id (CLD)\Mnemonic (CLD)',Multi,Free,Persistent,,User:7,Optional) + EOB_CODE2.MNEMONIC IN (PARAM_VALUE('Enter Claim Denial Code(s) or Leave Blank for All:', 'A', 'Claim Line (CLD)\Claim Line EOB\Eob Code Id (CLD)\Mnemonic (CLD)', 'Multi', 'Free', 'Persistent', NULL, 'User:7', 'Optional')) ) AND - CLMPOS.NAME IN @Prompt('Enter POS Type(s) or Leave Blank for All:','A','Claim Line (CLD)\Pos Type C (CLD)\Name (CLMPOS)',Multi,Free,Persistent,,User:9,Optional) + CLMPOS.NAME IN (PARAM_VALUE('Enter POS Type(s) or Leave Blank for All:', 'A', 'Claim Line (CLD)\Pos Type C (CLD)\Name (CLMPOS)', 'Multi', 'Free', 'Persistent', NULL, 'User:9', 'Optional')) AND EOB_CODE.CODE_TYPE_C IN ( 2 ) AND @@ -792,19 +792,19 @@ WHEN CLM.ORIG_ADJST_CLM_ID IS NOT NULL AND CLM.ORIG_REV_CLM_ID IS NULL AND CLM. WHEN CLM.ORIG_ADJST_CLM_ID IS NULL AND CLM.ORIG_REV_CLM_ID IS NULL AND "LEGACY_ADJST"."CLAIM_ID" IS NULL THEN 'Original Claim' ELSE NULL -END IN @Prompt('Enter Claim Adjudication Type:','A','Claim Header (CLM)\Clm Adj Type (CLM)\Clm Adj Type Desc (CLM)',Multi,Free,Persistent,,User:13) +END IN (PARAM_VALUE('Enter Claim Adjudication Type:', 'A', 'Claim Header (CLM)\Clm Adj Type (CLM)\Clm Adj Type Desc (CLM)', 'Multi', 'Free', 'Persistent', NULL, 'User:13')) AND - NVL(CLM_TRAIT.NAME,'KFHP') IN @Prompt('Enter ANIC Claim Trait or Leave Blank for All:','A','Claim Header (CLM)\Company Code (CLM)',Multi,Free,Persistent,,User:10,Optional) + NVL(CLM_TRAIT.NAME,'KFHP') IN (PARAM_VALUE('Enter ANIC Claim Trait or Leave Blank for All:', 'A', 'Claim Header (CLM)\Company Code (CLM)', 'Multi', 'Free', 'Persistent', NULL, 'User:10', 'Optional')) AND - nvl(CLM2.DENTAL_INFO_YN,'N') IN @Prompt('Enter Dental Indicator (Y/N):','A','Claim Header (CLM)\Dental Info Yn (CLM2)',Multi,Free,Persistent,{'N'},User:11,Optional) + nvl(CLM2.DENTAL_INFO_YN,'N') IN (PARAM_VALUE('Enter Dental Indicator (Y/N):', 'A', 'Claim Header (CLM)\Dental Info Yn (CLM2)', 'Multi', 'Free', 'Persistent', 'N', 'User:11', 'Optional')) AND CLM.ADJST_CLM_ID Is Null AND - ZC_REG_CD.NAME IN @Prompt('Enter CO Service Area Name:','A','Plan / Group (GRP)\Current Region Code C (GRP)\Current Region Code Name (GRP)',Multi,Free,Persistent,,User:12,Optional) + ZC_REG_CD.NAME IN (PARAM_VALUE('Enter CO Service Area Name:', 'A', 'Plan / Group (GRP)\Current Region Code C (GRP)\Current Region Code Name (GRP)', 'Multi', 'Free', 'Persistent', NULL, 'User:12', 'Optional')) AND ( ( CASE WHEN D_VM.PLACE_OF_SERVICE_ID IS NOT NULL THEN 'Y' ELSE 'N' -END ) in @Prompt(Visiting Member) OR ( 'ALL') IN @Prompt(Visiting Member) ) +END ) in (PARAM_VALUE('Visiting Member')) OR ( 'ALL') IN (PARAM_VALUE('Visiting Member')) ) ) --END-- SELECT DISTINCT @@ -823,7 +823,7 @@ SELECT DISTINCT CKR.CHECK_STATUS_C, CLM_APSTS.ABBR, CASE - WHEN CLM.AP_STS_C=3 THEN CKR.AP_RUN_DATE + WHEN CLM.AP_STS_C=3 THEN CKR.AP_RUN_DATE END, CASE WHEN ( case when D_CLARITY_DATABASE.CLARITY_DATABASE like '%HCCLHI%' then 'HI' @@ -933,7 +933,7 @@ END, Upper(SERRENADDR.CITY), SERRENST.ABBR, case - when D_AA_INDICATOR.CLAIM_ID is null + when D_AA_INDICATOR.CLAIM_ID is null then 'Not AA' else 'AA' end, ZC_REG_CD.NAME, @@ -1013,9 +1013,9 @@ group by PROV_ID,SPECIALTY_C LEFT OUTER JOIN CLARITY_VENDOR VENCLM ON (VENCLM.VENDOR_ID=CLM.VENDOR_ID) LEFT OUTER JOIN ( select t1.vendor_id,t1.line,t1.TAX_ID -from vendor_tax_id t1 , -(select vendor_id,max(line) as line from vendor_tax_id group by vendor_id) t2 Where t1.vendor_id=t2.vendor_id and t1.line=t2.line -group by t1.vendor_id,t1.line,t1.TAX_ID +from vendor_tax_id t1 , +(select vendor_id,max(line) as line from vendor_tax_id group by vendor_id) t2 Where t1.vendor_id=t2.vendor_id and t1.line=t2.line +group by t1.vendor_id,t1.line,t1.TAX_ID ) D_VTN ON (VENCLM.VENDOR_ID=D_VTN.VENDOR_ID) LEFT OUTER JOIN ( SELECT DISTINCT VENDOR_ID, PLACE_OF_SERVICE_ID FROM VENDOR_POS @@ -1026,12 +1026,12 @@ SELECT distinct ,999 as PLACE_OF_SERVICE_ID from vendor_tax_id t1 inner join - ( - select vendor_id, - max(line) as line - from vendor_tax_id - group by vendor_id - ) t2 on t1.vendor_id=t2.vendor_id + ( + select vendor_id, + max(line) as line + from vendor_tax_id + group by vendor_id + ) t2 on t1.vendor_id=t2.vendor_id and t1.line=t2.line WHERE t1.TAX_ID = '811559375' @@ -1046,23 +1046,23 @@ CLAIM_ID FROM (SELECT DISTINCT CLM_EOB.CLAIM_ID ,CASE WHEN MAX(EOB.ROUTE_FROM_DISC_C)=2 THEN 'Yes' ELSE - 'No' - END AS PAT_LIABILITY + 'No' + END AS PAT_LIABILITY ,EOB.MNEMONIC ,RMC.RMC_EXTERNAL_ID||'/'||RMK.ABBR AS REMIT_CD FROM AP_CLAIM_PX_EOBS CLM_EOB, CLARITY_EOB_CODE EOB, - CLARITY_RMC RMC, - ZC_REMARK_CODE RMK + CLARITY_RMC RMC, + ZC_REMARK_CODE RMK WHERE CLM_EOB.EOB_CODE_ID=EOB.EOB_CODE_ID - AND EOB.REMIT_CODE_ID=RMC.REMIT_CODE_ID(+) - AND EOB.REMARK_CODE_C=RMK.REMARK_CODE_C(+) + AND EOB.REMIT_CODE_ID=RMC.REMIT_CODE_ID(+) + AND EOB.REMARK_CODE_C=RMK.REMARK_CODE_C(+) --1 IS PEND CODE 2 IS DENIAL CODE 3 IS INFO CODE AND EOB.CODE_TYPE_C=3 GROUP BY CLM_EOB.CLAIM_ID,EOB.ROUTE_FROM_DISC_C,EOB.MNEMONIC,RMC.RMC_EXTERNAL_ID,RMK.ABBR - ) + ) GROUP BY CLAIM_ID,PAT_LIABILITY ) LISTAGG_DTL_INFO ON (CLM.CLAIM_ID=LISTAGG_DTL_INFO.CLAIM_ID) LEFT OUTER JOIN ( @@ -1098,59 +1098,59 @@ FROM (SELECT DISTINCT FROM AP_CLAIM_EOB_CODE CLM_EOB, CLARITY_EOB_CODE EOB, - CLARITY_RMC RMC, - ZC_REMARK_CODE RMK + CLARITY_RMC RMC, + ZC_REMARK_CODE RMK WHERE CLM_EOB.EOB_CODE_ID=EOB.EOB_CODE_ID - AND EOB.REMIT_CODE_ID=RMC.REMIT_CODE_ID(+) - AND EOB.REMARK_CODE_C=RMK.REMARK_CODE_C(+) + AND EOB.REMIT_CODE_ID=RMC.REMIT_CODE_ID(+) + AND EOB.REMARK_CODE_C=RMK.REMARK_CODE_C(+) AND CLM_EOB.RESOLUTION_DATE IS NULL --1 IS PEND CODE 2 IS DENIAL CODE 3 IS INFO CODE AND EOB.CODE_TYPE_C=3 - ) + ) GROUP BY CLAIM_ID ) LISTAGG_HDR_INFO ON (CLM.CLAIM_ID=LISTAGG_HDR_INFO.CLAIM_ID) LEFT OUTER JOIN AP_CLAIM_CHECK CLM_CHK ON (CLM.CLAIM_ID=CLM_CHK.CLAIM_ID) LEFT OUTER JOIN AP_CHECK CKR ON (CLM_CHK.CHECK_ID=CKR.CHECK_ID) WHERE -( COALESCE(CLM.WORKFLOW_C,0) IN @Prompt(P_WorkflowTypeInclude) and COALESCE(CLM_TRAIT_4.NAME,'CCA') In @Prompt(P_CCA-TPMG) ) +( COALESCE(CLM.WORKFLOW_C,0) IN (PARAM_VALUE('P_WorkflowTypeInclude')) and COALESCE(CLM_TRAIT_4.NAME,'CCA') In (PARAM_VALUE('P_CCA-TPMG')) ) AND ( - coalesce(EPPCL.PRODUCT_TYPE ,ZC_PRD_TYP.NAME) IN @Prompt('Enter Product Type or Leave Blank for All:','A','Claim Header (CLM)\Claim Header IDs (CLM)\Benefit Plan Id (CLM)\Product Type',Multi,Free,Persistent,,User:1,Optional) + coalesce(EPPCL.PRODUCT_TYPE ,ZC_PRD_TYP.NAME) IN (PARAM_VALUE('Enter Product Type or Leave Blank for All:', 'A', 'Claim Header (CLM)\Claim Header IDs (CLM)\Benefit Plan Id (CLM)\Product Type', 'Multi', 'Free', 'Persistent', NULL, 'User:1', 'Optional')) AND - nvl(LOBCL.LOB_NAME,'UNKNOWN') LIKE @Prompt('Enter Line of Business or Leave Blank for All:','A','Claim Header (CLM)\Line Of Business (CLM)\Line Of Business Name (CLM)',Mono,Free,Persistent,,User:2,Optional) + nvl(LOBCL.LOB_NAME,'UNKNOWN') LIKE PARAM_VALUE('Enter Line of Business or Leave Blank for All:', 'A', 'Claim Header (CLM)\Line Of Business (CLM)\Line Of Business Name (CLM)', 'Mono', 'Free', 'Persistent', NULL, 'User:2', 'Optional') AND - SERREN.PROV_NAME LIKE @Prompt('Enter Provider Name or Leave Blank for All:','A','Provider (PRV)\Rendering (SERREN)\Prov Name (SERREN)',Mono,Free,Persistent,,User:3,Optional) + SERREN.PROV_NAME LIKE PARAM_VALUE('Enter Provider Name or Leave Blank for All:', 'A', 'Provider (PRV)\Rendering (SERREN)\Prov Name (SERREN)', 'Mono', 'Free', 'Persistent', NULL, 'User:3', 'Optional') AND - SERREN_2.NPI LIKE @Prompt('Enter Provider ID or Leave Blank for All:','A',,Mono,Free,Persistent,,User:4,Optional) + SERREN_2.NPI LIKE PARAM_VALUE('Enter Provider ID or Leave Blank for All:', 'A', NULL, 'Mono', 'Free', 'Persistent', NULL, 'User:4', 'Optional') AND - D_VTN.TAX_ID LIKE @Prompt('Enter Vendor Tax ID or Leave Blank for All:','A',,Mono,Free,Persistent,,User:5,Optional) + D_VTN.TAX_ID LIKE PARAM_VALUE('Enter Vendor Tax ID or Leave Blank for All:', 'A', NULL, 'Mono', 'Free', 'Persistent', NULL, 'User:5', 'Optional') AND - VENCLM.VENDOR_NAME LIKE @Prompt('Enter Vendor Name or Leave Blank for All:','A','Claim Header Vendor (VENCLM)\Vendor Name (VENCLM)',Mono,Free,Persistent,,User:6,Optional) + VENCLM.VENDOR_NAME LIKE PARAM_VALUE('Enter Vendor Name or Leave Blank for All:', 'A', 'Claim Header Vendor (VENCLM)\Vendor Name (VENCLM)', 'Mono', 'Free', 'Persistent', NULL, 'User:6', 'Optional') AND - GRP.PLAN_GRP_NAME LIKE @Prompt('Enter Plan Group Name or Leave Blank for All:','A','Plan / Group (GRP)\Plan Grp Name (GRP)',Mono,Free,Persistent,,User:0,Optional) + GRP.PLAN_GRP_NAME LIKE PARAM_VALUE('Enter Plan Group Name or Leave Blank for All:', 'A', 'Plan / Group (GRP)\Plan Grp Name (GRP)', 'Mono', 'Free', 'Persistent', NULL, 'User:0', 'Optional') AND CLM.STATUS_C = 3 AND CASE - WHEN CLM.AP_STS_C=3 THEN CKR.AP_RUN_DATE + WHEN CLM.AP_STS_C=3 THEN CKR.AP_RUN_DATE END Is Null AND - CLMPOS.NAME IN @Prompt('Enter POS Type(s) or Leave Blank for All:','A','Claim Line (CLD)\Pos Type C (CLD)\Name (CLMPOS)',Multi,Free,Persistent,,User:7,Optional) + CLMPOS.NAME IN (PARAM_VALUE('Enter POS Type(s) or Leave Blank for All:', 'A', 'Claim Line (CLD)\Pos Type C (CLD)\Name (CLMPOS)', 'Multi', 'Free', 'Persistent', NULL, 'User:7', 'Optional')) AND ( - EOB_CODE2.MNEMONIC IN @Prompt('Enter Claim Denial Code(s) or Leave Blank for All:','A','Claim Line (CLD)\Claim Line EOB\Eob Code Id (CLD)\Mnemonic (CLD)',Multi,Free,Persistent,,User:8,Optional) + EOB_CODE2.MNEMONIC IN (PARAM_VALUE('Enter Claim Denial Code(s) or Leave Blank for All:', 'A', 'Claim Line (CLD)\Claim Line EOB\Eob Code Id (CLD)\Mnemonic (CLD)', 'Multi', 'Free', 'Persistent', NULL, 'User:8', 'Optional')) OR - EOB_CODE.MNEMONIC IN @Prompt('Enter Claim Denial Code(s) or Leave Blank for All:','A','Claim EOB (EOB)\Eob Code Id (CLM_EOB)\Mnemonic (EOB_CODE)',Multi,Free,Persistent,,User:9,Optional) + EOB_CODE.MNEMONIC IN (PARAM_VALUE('Enter Claim Denial Code(s) or Leave Blank for All:', 'A', 'Claim EOB (EOB)\Eob Code Id (CLM_EOB)\Mnemonic (EOB_CODE)', 'Multi', 'Free', 'Persistent', NULL, 'User:9', 'Optional')) ) AND - NVL(CLM_TRAIT.NAME,'KFHP') IN @Prompt('Enter ANIC Claim Trait or Leave Blank for All:','A','Claim Header (CLM)\Company Code (CLM)',Multi,Free,Persistent,,User:10,Optional) + NVL(CLM_TRAIT.NAME,'KFHP') IN (PARAM_VALUE('Enter ANIC Claim Trait or Leave Blank for All:', 'A', 'Claim Header (CLM)\Company Code (CLM)', 'Multi', 'Free', 'Persistent', NULL, 'User:10', 'Optional')) AND - nvl(CLM2.DENTAL_INFO_YN,'N') IN @Prompt('Enter Dental Indicator (Y/N):','A','Claim Header (CLM)\Dental Info Yn (CLM2)',Multi,Free,Persistent,{'N'},User:11,Optional) + nvl(CLM2.DENTAL_INFO_YN,'N') IN (PARAM_VALUE('Enter Dental Indicator (Y/N):', 'A', 'Claim Header (CLM)\Dental Info Yn (CLM2)', 'Multi', 'Free', 'Persistent', 'N', 'User:11', 'Optional')) AND CLM.ADJST_CLM_ID Is Null AND - ZC_REG_CD.NAME IN @Prompt('Enter CO Service Area Name:','A','Plan / Group (GRP)\Current Region Code C (GRP)\Current Region Code Name (GRP)',Multi,Free,Persistent,,User:12,Optional) + ZC_REG_CD.NAME IN (PARAM_VALUE('Enter CO Service Area Name:', 'A', 'Plan / Group (GRP)\Current Region Code C (GRP)\Current Region Code Name (GRP)', 'Multi', 'Free', 'Persistent', NULL, 'User:12', 'Optional')) AND CASE WHEN CLM.ORIG_REV_CLM_ID IS NOT NULL @@ -1162,11 +1162,11 @@ WHEN CLM.ORIG_ADJST_CLM_ID IS NOT NULL AND CLM.ORIG_REV_CLM_ID IS NULL AND CLM. WHEN CLM.ORIG_ADJST_CLM_ID IS NULL AND CLM.ORIG_REV_CLM_ID IS NULL AND "LEGACY_ADJST"."CLAIM_ID" IS NULL THEN 'Original Claim' ELSE NULL -END IN @Prompt('Enter Claim Adjudication Type:','A','Claim Header (CLM)\Clm Adj Type (CLM)\Clm Adj Type Desc (CLM)',Multi,Free,Persistent,{'Original Claim'},User:13) +END IN (PARAM_VALUE('Enter Claim Adjudication Type:', 'A', 'Claim Header (CLM)\Clm Adj Type (CLM)\Clm Adj Type Desc (CLM)', 'Multi', 'Free', 'Persistent', 'Original Claim', 'User:13')) AND ( ( CASE WHEN D_VM.PLACE_OF_SERVICE_ID IS NOT NULL THEN 'Y' ELSE 'N' -END ) in @Prompt(Visiting Member) OR ( 'ALL') IN @Prompt(Visiting Member) ) +END ) in (PARAM_VALUE('Visiting Member')) OR ( 'ALL') IN (PARAM_VALUE('Visiting Member')) ) ) --END-- SELECT DISTINCT @@ -1275,7 +1275,7 @@ END, (case when length(CLD.MODIFIERS) > 6 then substr(CLD.MODIFIERS,7,2) else '0' end), (case when length(CLD.MODIFIERS) > 9 then substr(CLD.MODIFIERS,10,2) else '0' end ), CASE - WHEN CLM.AP_STS_C=3 THEN CKR.AP_RUN_DATE + WHEN CLM.AP_STS_C=3 THEN CKR.AP_RUN_DATE END, CLM.DATE_RECEIVED, CLM.SERVICE_END_DATE, @@ -1297,7 +1297,7 @@ END, Upper(SERRENADDR.CITY), SERRENST.ABBR, case - when D_AA_INDICATOR.CLAIM_ID is null + when D_AA_INDICATOR.CLAIM_ID is null then 'Not AA' else 'AA' end, ZC_REG_CD.NAME, @@ -1378,9 +1378,9 @@ group by PROV_ID,SPECIALTY_C LEFT OUTER JOIN CLARITY_VENDOR VENCLM ON (VENCLM.VENDOR_ID=CLM.VENDOR_ID) LEFT OUTER JOIN ( select t1.vendor_id,t1.line,t1.TAX_ID -from vendor_tax_id t1 , -(select vendor_id,max(line) as line from vendor_tax_id group by vendor_id) t2 Where t1.vendor_id=t2.vendor_id and t1.line=t2.line -group by t1.vendor_id,t1.line,t1.TAX_ID +from vendor_tax_id t1 , +(select vendor_id,max(line) as line from vendor_tax_id group by vendor_id) t2 Where t1.vendor_id=t2.vendor_id and t1.line=t2.line +group by t1.vendor_id,t1.line,t1.TAX_ID ) D_VTN ON (VENCLM.VENDOR_ID=D_VTN.VENDOR_ID) LEFT OUTER JOIN ( SELECT DISTINCT VENDOR_ID, PLACE_OF_SERVICE_ID FROM VENDOR_POS @@ -1391,12 +1391,12 @@ SELECT distinct ,999 as PLACE_OF_SERVICE_ID from vendor_tax_id t1 inner join - ( - select vendor_id, - max(line) as line - from vendor_tax_id - group by vendor_id - ) t2 on t1.vendor_id=t2.vendor_id + ( + select vendor_id, + max(line) as line + from vendor_tax_id + group by vendor_id + ) t2 on t1.vendor_id=t2.vendor_id and t1.line=t2.line WHERE t1.TAX_ID = '811559375' @@ -1411,23 +1411,23 @@ CLAIM_ID FROM (SELECT DISTINCT CLM_EOB.CLAIM_ID ,CASE WHEN MAX(EOB.ROUTE_FROM_DISC_C)=2 THEN 'Yes' ELSE - 'No' - END AS PAT_LIABILITY + 'No' + END AS PAT_LIABILITY ,EOB.MNEMONIC ,RMC.RMC_EXTERNAL_ID||'/'||RMK.ABBR AS REMIT_CD FROM AP_CLAIM_PX_EOBS CLM_EOB, CLARITY_EOB_CODE EOB, - CLARITY_RMC RMC, - ZC_REMARK_CODE RMK + CLARITY_RMC RMC, + ZC_REMARK_CODE RMK WHERE CLM_EOB.EOB_CODE_ID=EOB.EOB_CODE_ID - AND EOB.REMIT_CODE_ID=RMC.REMIT_CODE_ID(+) - AND EOB.REMARK_CODE_C=RMK.REMARK_CODE_C(+) + AND EOB.REMIT_CODE_ID=RMC.REMIT_CODE_ID(+) + AND EOB.REMARK_CODE_C=RMK.REMARK_CODE_C(+) --1 IS PEND CODE 2 IS DENIAL CODE 3 IS INFO CODE AND EOB.CODE_TYPE_C=3 GROUP BY CLM_EOB.CLAIM_ID,EOB.ROUTE_FROM_DISC_C,EOB.MNEMONIC,RMC.RMC_EXTERNAL_ID,RMK.ABBR - ) + ) GROUP BY CLAIM_ID,PAT_LIABILITY ) LISTAGG_DTL_INFO ON (CLM.CLAIM_ID=LISTAGG_DTL_INFO.CLAIM_ID) LEFT OUTER JOIN ( @@ -1463,16 +1463,16 @@ FROM (SELECT DISTINCT FROM AP_CLAIM_EOB_CODE CLM_EOB, CLARITY_EOB_CODE EOB, - CLARITY_RMC RMC, - ZC_REMARK_CODE RMK + CLARITY_RMC RMC, + ZC_REMARK_CODE RMK WHERE CLM_EOB.EOB_CODE_ID=EOB.EOB_CODE_ID - AND EOB.REMIT_CODE_ID=RMC.REMIT_CODE_ID(+) - AND EOB.REMARK_CODE_C=RMK.REMARK_CODE_C(+) + AND EOB.REMIT_CODE_ID=RMC.REMIT_CODE_ID(+) + AND EOB.REMARK_CODE_C=RMK.REMARK_CODE_C(+) AND CLM_EOB.RESOLUTION_DATE IS NULL --1 IS PEND CODE 2 IS DENIAL CODE 3 IS INFO CODE AND EOB.CODE_TYPE_C=3 - ) + ) GROUP BY CLAIM_ID ) LISTAGG_HDR_INFO ON (CLM.CLAIM_ID=LISTAGG_HDR_INFO.CLAIM_ID) LEFT OUTER JOIN AP_CLAIM_WQ_ITEM WQI ON (CLM.CLAIM_ID=WQI.CLAIM_ID) @@ -1480,22 +1480,22 @@ GROUP BY CLAIM_ID LEFT OUTER JOIN AP_CLAIM_CHECK CLM_CHK ON (CLM.CLAIM_ID=CLM_CHK.CLAIM_ID) LEFT OUTER JOIN AP_CHECK CKR ON (CLM_CHK.CHECK_ID=CKR.CHECK_ID) WHERE -( COALESCE(CLM.WORKFLOW_C,0) IN @Prompt(P_WorkflowTypeInclude) and COALESCE(CLM_TRAIT_4.NAME,'CCA') In @Prompt(P_CCA-TPMG) ) +( COALESCE(CLM.WORKFLOW_C,0) IN (PARAM_VALUE('P_WorkflowTypeInclude')) and COALESCE(CLM_TRAIT_4.NAME,'CCA') In (PARAM_VALUE('P_CCA-TPMG')) ) AND ( - coalesce(EPPCL.PRODUCT_TYPE ,ZC_PRD_TYP.NAME) IN @Prompt('Enter Product Type or Leave Blank for All:','A','Claim Header (CLM)\Claim Header IDs (CLM)\Benefit Plan Id (CLM)\Product Type',Multi,Free,Persistent,,User:1,Optional) + coalesce(EPPCL.PRODUCT_TYPE ,ZC_PRD_TYP.NAME) IN (PARAM_VALUE('Enter Product Type or Leave Blank for All:', 'A', 'Claim Header (CLM)\Claim Header IDs (CLM)\Benefit Plan Id (CLM)\Product Type', 'Multi', 'Free', 'Persistent', NULL, 'User:1', 'Optional')) AND - nvl(LOBCL.LOB_NAME,'UNKNOWN') LIKE @Prompt('Enter Line of Business or Leave Blank for All:','A','Claim Header (CLM)\Line Of Business (CLM)\Line Of Business Name (CLM)',Mono,Free,Persistent,,User:2,Optional) + nvl(LOBCL.LOB_NAME,'UNKNOWN') LIKE PARAM_VALUE('Enter Line of Business or Leave Blank for All:', 'A', 'Claim Header (CLM)\Line Of Business (CLM)\Line Of Business Name (CLM)', 'Mono', 'Free', 'Persistent', NULL, 'User:2', 'Optional') AND - SERREN.PROV_NAME LIKE @Prompt('Enter Provider Name or Leave Blank for All:','A','Provider (PRV)\Rendering (SERREN)\Prov Name (SERREN)',Mono,Free,Persistent,,User:3,Optional) + SERREN.PROV_NAME LIKE PARAM_VALUE('Enter Provider Name or Leave Blank for All:', 'A', 'Provider (PRV)\Rendering (SERREN)\Prov Name (SERREN)', 'Mono', 'Free', 'Persistent', NULL, 'User:3', 'Optional') AND - SERREN_2.NPI LIKE @Prompt('Enter Provider ID or Leave Blank for All:','A',,Mono,Free,Persistent,,User:4,Optional) + SERREN_2.NPI LIKE PARAM_VALUE('Enter Provider ID or Leave Blank for All:', 'A', NULL, 'Mono', 'Free', 'Persistent', NULL, 'User:4', 'Optional') AND - D_VTN.TAX_ID LIKE @Prompt('Enter Vendor Tax ID or Leave Blank for All:','A',,Mono,Free,Persistent,,User:5,Optional) + D_VTN.TAX_ID LIKE PARAM_VALUE('Enter Vendor Tax ID or Leave Blank for All:', 'A', NULL, 'Mono', 'Free', 'Persistent', NULL, 'User:5', 'Optional') AND - VENCLM.VENDOR_NAME LIKE @Prompt('Enter Vendor Name or Leave Blank for All:','A','Claim Header Vendor (VENCLM)\Vendor Name (VENCLM)',Mono,Free,Persistent,,User:6,Optional) + VENCLM.VENDOR_NAME LIKE PARAM_VALUE('Enter Vendor Name or Leave Blank for All:', 'A', 'Claim Header Vendor (VENCLM)\Vendor Name (VENCLM)', 'Mono', 'Free', 'Persistent', NULL, 'User:6', 'Optional') AND - GRP.PLAN_GRP_NAME LIKE @Prompt('Enter Plan Group Name or Leave Blank for All:','A','Plan / Group (GRP)\Plan Grp Name (GRP)',Mono,Free,Persistent,,User:0,Optional) + GRP.PLAN_GRP_NAME LIKE PARAM_VALUE('Enter Plan Group Name or Leave Blank for All:', 'A', 'Plan / Group (GRP)\Plan Grp Name (GRP)', 'Mono', 'Free', 'Persistent', NULL, 'User:0', 'Optional') AND ( (case when length(substr(CLD.MODIFIERS, 0,instr(CLD.MODIFIERS, ',',1,1))) = 0 or length(substr(CLD.MODIFIERS, 0,instr(CLD.MODIFIERS, ',',1,1))) Is Null then CLD.MODIFIERS else substr(CLD.MODIFIERS, 0,instr(CLD.MODIFIERS, ',',1,1)-1) end) IN ( '77','76' ) @@ -1515,15 +1515,15 @@ WHERE AND EOB_CODE.MNEMONIC IN ( 'CED12','CED44' ) AND - CLMPOS.NAME IN @Prompt('Enter POS Type(s) or Leave Blank for All:','A','Claim Line (CLD)\Pos Type C (CLD)\Name (CLMPOS)',Multi,Free,Persistent,,User:7,Optional) + CLMPOS.NAME IN (PARAM_VALUE('Enter POS Type(s) or Leave Blank for All:', 'A', 'Claim Line (CLD)\Pos Type C (CLD)\Name (CLMPOS)', 'Multi', 'Free', 'Persistent', NULL, 'User:7', 'Optional')) AND - NVL(CLM_TRAIT.NAME,'KFHP') IN @Prompt('Enter ANIC Claim Trait or Leave Blank for All:','A','Claim Header (CLM)\Company Code (CLM)',Multi,Free,Persistent,,User:8,Optional) + NVL(CLM_TRAIT.NAME,'KFHP') IN (PARAM_VALUE('Enter ANIC Claim Trait or Leave Blank for All:', 'A', 'Claim Header (CLM)\Company Code (CLM)', 'Multi', 'Free', 'Persistent', NULL, 'User:8', 'Optional')) AND - nvl(CLM2.DENTAL_INFO_YN,'N') IN @Prompt('Enter Dental Indicator (Y/N):','A','Claim Header (CLM)\Dental Info Yn (CLM2)',Multi,Free,Persistent,{'N'},User:9,Optional) + nvl(CLM2.DENTAL_INFO_YN,'N') IN (PARAM_VALUE('Enter Dental Indicator (Y/N):', 'A', 'Claim Header (CLM)\Dental Info Yn (CLM2)', 'Multi', 'Free', 'Persistent', 'N', 'User:9', 'Optional')) AND CLM.ADJST_CLM_ID Is Null AND - ZC_REG_CD.NAME IN @Prompt('Enter CO Service Area Name:','A','Plan / Group (GRP)\Current Region Code C (GRP)\Current Region Code Name (GRP)',Multi,Free,Persistent,,User:10,Optional) + ZC_REG_CD.NAME IN (PARAM_VALUE('Enter CO Service Area Name:', 'A', 'Plan / Group (GRP)\Current Region Code C (GRP)\Current Region Code Name (GRP)', 'Multi', 'Free', 'Persistent', NULL, 'User:10', 'Optional')) AND CASE WHEN CLM.ORIG_REV_CLM_ID IS NOT NULL @@ -1535,11 +1535,11 @@ WHEN CLM.ORIG_ADJST_CLM_ID IS NOT NULL AND CLM.ORIG_REV_CLM_ID IS NULL AND CLM. WHEN CLM.ORIG_ADJST_CLM_ID IS NULL AND CLM.ORIG_REV_CLM_ID IS NULL AND "LEGACY_ADJST"."CLAIM_ID" IS NULL THEN 'Original Claim' ELSE NULL -END IN @Prompt('Enter Claim Adjudication Type:','A','Claim Header (CLM)\Clm Adj Type (CLM)\Clm Adj Type Desc (CLM)',Multi,Free,Persistent,,User:11) +END IN (PARAM_VALUE('Enter Claim Adjudication Type:', 'A', 'Claim Header (CLM)\Clm Adj Type (CLM)\Clm Adj Type Desc (CLM)', 'Multi', 'Free', 'Persistent', NULL, 'User:11')) AND ( ( CASE WHEN D_VM.PLACE_OF_SERVICE_ID IS NOT NULL THEN 'Y' ELSE 'N' -END ) in @Prompt(Visiting Member) OR ( 'ALL') IN @Prompt(Visiting Member) ) +END ) in (PARAM_VALUE('Visiting Member')) OR ( 'ALL') IN (PARAM_VALUE('Visiting Member')) ) ) GROUP BY CLM_MAP_1.INTERNAL_ID, @@ -1647,7 +1647,7 @@ END, (case when length(CLD.MODIFIERS) > 6 then substr(CLD.MODIFIERS,7,2) else '0' end), (case when length(CLD.MODIFIERS) > 9 then substr(CLD.MODIFIERS,10,2) else '0' end ), CASE - WHEN CLM.AP_STS_C=3 THEN CKR.AP_RUN_DATE + WHEN CLM.AP_STS_C=3 THEN CKR.AP_RUN_DATE END, CLM.DATE_RECEIVED, CLM.SERVICE_END_DATE, @@ -1662,7 +1662,7 @@ END, Upper(SERRENADDR.CITY), SERRENST.ABBR, case - when D_AA_INDICATOR.CLAIM_ID is null + when D_AA_INDICATOR.CLAIM_ID is null then 'Not AA' else 'AA' end, ZC_REG_CD.NAME, @@ -1932,17 +1932,17 @@ select MAX(k+2), COUNT(*), MYCOL from K; SELECT * FROM TA2 LEFT JOIN O USING (col1, col2) where D.OasSD = 'asdf' And (kj >= 4 OR l < 'sdf'); -seLECT my as KIO, lio aS -NE fRom TA2 LEFT OUter JOIN O as TA3 +seLECT my as KIO, lio aS +NE fRom TA2 LEFT OUter JOIN O as TA3 where D.OasSD = 'asdf' And (kj >= 4 OR l < 'sdf'); select * from a INNer Join TAB_2 ON i.o = p.l whEre 'sdf'>'asdf' AND - ( - OL<>? - OR - L NOT IN (SELECT * FROM KJSD) - ); + ( + OL<>? + OR + L NOT IN (SELECT * FROM KJSD) + ); select * from k where L IS NOT NUll; @@ -2106,5 +2106,4 @@ AND THIS_EMP.WORKDEPT = DINFO.DEPTNO; select * from Person where deptname='it' AND NOT (age=24); -select * from unnest(array[4,5,6]) with ordinality; - +select * from unnest(array[4,5,6]) with ordinality; \ No newline at end of file diff --git a/src/test/resources/net/sf/jsqlparser/statement/select/match-recognize-2350.sql b/src/test/resources/net/sf/jsqlparser/statement/select/match-recognize-2350.sql new file mode 100644 index 0000000000..916753a6e8 --- /dev/null +++ b/src/test/resources/net/sf/jsqlparser/statement/select/match-recognize-2350.sql @@ -0,0 +1,38 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +WITH Operations AS ( + SELECT 1 AS OperationID, 120.00 AS Amount, 'C001' AS CatalogID, + DATE '2025-01-03' AS OperationDate UNION ALL + SELECT 2, 20.00, 'C001', DATE '2025-01-04' UNION ALL + SELECT 3, 175.00, 'C001', DATE '2025-01-05' UNION ALL + SELECT 4, 30.00, 'C001', DATE '2025-01-10' UNION ALL + SELECT 5, 190.00, 'C001', DATE '2025-01-11' UNION ALL + SELECT 6, 250.00, 'C001', DATE '2025-01-12' +) + +SELECT * +FROM Operations +MATCH_RECOGNIZE ( + PARTITION BY CatalogID + ORDER BY OperationDate ASC + MEASURES + FIRST(OperationDate) AS START_DT, + LAST(OperationDate) AS END_DT, + SUM(Amount) AS TOTAL_AMOUNT, + COUNT(*) AS ROW_COUNT + AFTER MATCH SKIP PAST LAST ROW + PATTERN (low mid+ high+) + DEFINE + low AS Amount < 50, + mid AS Amount between 100 and 200, + high AS Amount > 200 + OPTIONS ( use_longest_match = FALSE ) +) +ORDER BY CatalogID, START_DT; diff --git a/src/test/resources/net/sf/jsqlparser/statement/select/match-recognize/README.md b/src/test/resources/net/sf/jsqlparser/statement/select/match-recognize/README.md new file mode 100644 index 0000000000..bf844489af --- /dev/null +++ b/src/test/resources/net/sf/jsqlparser/statement/select/match-recognize/README.md @@ -0,0 +1,36 @@ +These self-contained fixtures cover MATCH_RECOGNIZE parsing, AST rendering and +reparsing. `MatchRecognizeDialectTest.executionFixturesRoundTrip` reads the files +listed in `execution-cases.txt`; unit tests do not require a database connection. + +The following external checks were run on 2026-09-13. For each fixture, both the +original SQL and SQL emitted by `StatementDeParser` executed successfully and +returned identical output: + +| Fixture prefix | Executor | Cases | +| --- | --- | ---: | +| `bigquery-` | GoogleSQL reference evaluator, release `2026.9.1` | 15 | +| `oracle-` | Oracle AI Database 26ai Free, version `23.26.2.0.0` | 17 | + +GoogleSQL is the reference evaluator, not the BigQuery service. Snowflake was not +available for execution; its documented syntax and precedence are covered by +separate parser/AST tests. Function eligibility, types, aggregation rules and +data-dependent errors are outside the parser's static validation. + +The BigQuery cases exercise greedy/reluctant matching, overlap, longest-match +options, empty alternatives, adjacent anchors, parameter bounds, nested STRUCT +measures, aliases and the query reported in issue #2350. The Oracle cases cover +row output modes, empty/unmatched rows, SUBSET, PERMUTE, exclusion, RUNNING/FINAL, +skip targets, result aliases, empty groups and fixed reluctant quantifiers. + +To reproduce execution, use the [official GoogleSQL executable](https://github.com/google/googlesql/releases/tag/2026.9.1) +with `--output_mode=json --sql_file=`. For `bigquery-parameter-bound.sql`, +also pass `--parameters='lo=1;hi=2'`. The macOS executable used here has SHA-256 +`4237e700ddedbdc331bda4f2715ba97879b5fe4ab5bfdfd8812dbcc8cfbfc7c9`. +For Oracle, run each fixture through SQL*Plus with `WHENEVER SQLERROR EXIT SQL.SQLCODE`. +The local Oracle image was `ghcr.io/gvenzl/oracle-free:23.26.2-slim`, digest +`sha256:6c6b2555e19b81beb79f3429ac34f241b196785c0137125b871f0af8507685b1`. + +Render the same statement through `StatementDeParser`, using the corresponding +`Dialect.BIGQUERY` or `Dialect.ORACLE`, and run that SQL with the same parameters +and data. Compare the original and rendered outputs. The Java tests additionally +check `toString()`/deparser agreement and stable output after reparsing. diff --git a/src/test/resources/net/sf/jsqlparser/statement/select/match-recognize/bigquery-adjacent-anchors.sql b/src/test/resources/net/sf/jsqlparser/statement/select/match-recognize/bigquery-adjacent-anchors.sql new file mode 100644 index 0000000000..1766c2c2a0 --- /dev/null +++ b/src/test/resources/net/sf/jsqlparser/statement/select/match-recognize/bigquery-adjacent-anchors.sql @@ -0,0 +1,11 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +WITH events AS (SELECT 1 AS seq, 10 AS price UNION ALL SELECT 2, 20 UNION ALL SELECT 3, 30 UNION ALL SELECT 4, -5) +SELECT * FROM events MATCH_RECOGNIZE (ORDER BY seq MEASURES FIRST(seq) AS first_seq, LAST(seq) AS last_seq, COUNT(*) AS n PATTERN (^A+$) DEFINE A AS price != 0 ) ORDER BY first_seq, last_seq; diff --git a/src/test/resources/net/sf/jsqlparser/statement/select/match-recognize/bigquery-baseline.sql b/src/test/resources/net/sf/jsqlparser/statement/select/match-recognize/bigquery-baseline.sql new file mode 100644 index 0000000000..cda877279a --- /dev/null +++ b/src/test/resources/net/sf/jsqlparser/statement/select/match-recognize/bigquery-baseline.sql @@ -0,0 +1,11 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +WITH events AS (SELECT 1 AS seq, 10 AS price UNION ALL SELECT 2, 20 UNION ALL SELECT 3, 30 UNION ALL SELECT 4, -5) +SELECT * FROM events MATCH_RECOGNIZE (ORDER BY seq MEASURES FIRST(seq) AS first_seq, LAST(seq) AS last_seq, COUNT(*) AS n PATTERN (A+) DEFINE A AS price > 0 ) ORDER BY first_seq, last_seq; diff --git a/src/test/resources/net/sf/jsqlparser/statement/select/match-recognize/bigquery-bounds.sql b/src/test/resources/net/sf/jsqlparser/statement/select/match-recognize/bigquery-bounds.sql new file mode 100644 index 0000000000..63a2406e93 --- /dev/null +++ b/src/test/resources/net/sf/jsqlparser/statement/select/match-recognize/bigquery-bounds.sql @@ -0,0 +1,11 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +WITH events AS (SELECT 1 AS seq, 10 AS price UNION ALL SELECT 2, 20 UNION ALL SELECT 3, 30 UNION ALL SELECT 4, -5) +SELECT * FROM events MATCH_RECOGNIZE (ORDER BY seq MEASURES FIRST(seq) AS first_seq, LAST(seq) AS last_seq, COUNT(*) AS n PATTERN (A{1,2}) DEFINE A AS price > 0 ) ORDER BY first_seq, last_seq; diff --git a/src/test/resources/net/sf/jsqlparser/statement/select/match-recognize/bigquery-empty-alternative.sql b/src/test/resources/net/sf/jsqlparser/statement/select/match-recognize/bigquery-empty-alternative.sql new file mode 100644 index 0000000000..9b7b14690f --- /dev/null +++ b/src/test/resources/net/sf/jsqlparser/statement/select/match-recognize/bigquery-empty-alternative.sql @@ -0,0 +1,11 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +WITH events AS (SELECT 1 AS seq, 10 AS price UNION ALL SELECT 2, 20 UNION ALL SELECT 3, 30 UNION ALL SELECT 4, -5) +SELECT * FROM events MATCH_RECOGNIZE (ORDER BY seq MEASURES FIRST(seq) AS first_seq, LAST(seq) AS last_seq, COUNT(*) AS n PATTERN (A|) DEFINE A AS price > 0 ) ORDER BY first_seq, last_seq; diff --git a/src/test/resources/net/sf/jsqlparser/statement/select/match-recognize/bigquery-greedy.sql b/src/test/resources/net/sf/jsqlparser/statement/select/match-recognize/bigquery-greedy.sql new file mode 100644 index 0000000000..158e7b4d31 --- /dev/null +++ b/src/test/resources/net/sf/jsqlparser/statement/select/match-recognize/bigquery-greedy.sql @@ -0,0 +1,11 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +WITH events AS (SELECT 1 AS seq, 10 AS price UNION ALL SELECT 2, 20 UNION ALL SELECT 3, 30 UNION ALL SELECT 4, -5) +SELECT * FROM events MATCH_RECOGNIZE (ORDER BY seq MEASURES FIRST(seq) AS first_seq, LAST(seq) AS last_seq, COUNT(*) AS n PATTERN (A+ B) DEFINE A AS price > 0, B AS price > 0 ) ORDER BY first_seq, last_seq; diff --git a/src/test/resources/net/sf/jsqlparser/statement/select/match-recognize/bigquery-issue2350.sql b/src/test/resources/net/sf/jsqlparser/statement/select/match-recognize/bigquery-issue2350.sql new file mode 100644 index 0000000000..916753a6e8 --- /dev/null +++ b/src/test/resources/net/sf/jsqlparser/statement/select/match-recognize/bigquery-issue2350.sql @@ -0,0 +1,38 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +WITH Operations AS ( + SELECT 1 AS OperationID, 120.00 AS Amount, 'C001' AS CatalogID, + DATE '2025-01-03' AS OperationDate UNION ALL + SELECT 2, 20.00, 'C001', DATE '2025-01-04' UNION ALL + SELECT 3, 175.00, 'C001', DATE '2025-01-05' UNION ALL + SELECT 4, 30.00, 'C001', DATE '2025-01-10' UNION ALL + SELECT 5, 190.00, 'C001', DATE '2025-01-11' UNION ALL + SELECT 6, 250.00, 'C001', DATE '2025-01-12' +) + +SELECT * +FROM Operations +MATCH_RECOGNIZE ( + PARTITION BY CatalogID + ORDER BY OperationDate ASC + MEASURES + FIRST(OperationDate) AS START_DT, + LAST(OperationDate) AS END_DT, + SUM(Amount) AS TOTAL_AMOUNT, + COUNT(*) AS ROW_COUNT + AFTER MATCH SKIP PAST LAST ROW + PATTERN (low mid+ high+) + DEFINE + low AS Amount < 50, + mid AS Amount between 100 and 200, + high AS Amount > 200 + OPTIONS ( use_longest_match = FALSE ) +) +ORDER BY CatalogID, START_DT; diff --git a/src/test/resources/net/sf/jsqlparser/statement/select/match-recognize/bigquery-longest-false.sql b/src/test/resources/net/sf/jsqlparser/statement/select/match-recognize/bigquery-longest-false.sql new file mode 100644 index 0000000000..8a6a26b9f6 --- /dev/null +++ b/src/test/resources/net/sf/jsqlparser/statement/select/match-recognize/bigquery-longest-false.sql @@ -0,0 +1,11 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +WITH events AS (SELECT 1 AS seq, 10 AS price UNION ALL SELECT 2, 20 UNION ALL SELECT 3, 30 UNION ALL SELECT 4, -5) +SELECT * FROM events MATCH_RECOGNIZE (ORDER BY seq MEASURES FIRST(seq) AS first_seq, LAST(seq) AS last_seq, COUNT(*) AS n PATTERN (A | A B+) DEFINE A AS price > 0, B AS price > 0 OPTIONS (use_longest_match=FALSE)) ORDER BY first_seq, last_seq; diff --git a/src/test/resources/net/sf/jsqlparser/statement/select/match-recognize/bigquery-longest-true.sql b/src/test/resources/net/sf/jsqlparser/statement/select/match-recognize/bigquery-longest-true.sql new file mode 100644 index 0000000000..948f5d7ec1 --- /dev/null +++ b/src/test/resources/net/sf/jsqlparser/statement/select/match-recognize/bigquery-longest-true.sql @@ -0,0 +1,11 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +WITH events AS (SELECT 1 AS seq, 10 AS price UNION ALL SELECT 2, 20 UNION ALL SELECT 3, 30 UNION ALL SELECT 4, -5) +SELECT * FROM events MATCH_RECOGNIZE (ORDER BY seq MEASURES FIRST(seq) AS first_seq, LAST(seq) AS last_seq, COUNT(*) AS n PATTERN (A | A B+) DEFINE A AS price > 0, B AS price > 0 OPTIONS (use_longest_match=TRUE)) ORDER BY first_seq, last_seq; diff --git a/src/test/resources/net/sf/jsqlparser/statement/select/match-recognize/bigquery-nested-struct.sql b/src/test/resources/net/sf/jsqlparser/statement/select/match-recognize/bigquery-nested-struct.sql new file mode 100644 index 0000000000..198bdccd52 --- /dev/null +++ b/src/test/resources/net/sf/jsqlparser/statement/select/match-recognize/bigquery-nested-struct.sql @@ -0,0 +1,11 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +WITH events AS (SELECT 1 AS seq, 10 AS price UNION ALL SELECT 2, 20 UNION ALL SELECT 3, 30 UNION ALL SELECT 4, -5) +SELECT * FROM events MATCH_RECOGNIZE (ORDER BY seq MEASURES FIRST(seq) AS first_seq, LAST(seq) AS last_seq, ARRAY_AGG(STRUCT(MATCH_ROW_NUMBER() AS row_no, CLASSIFIER() AS label, price)) AS details PATTERN (A+) DEFINE A AS price > 0 ) ORDER BY first_seq, last_seq; diff --git a/src/test/resources/net/sf/jsqlparser/statement/select/match-recognize/bigquery-options-empty.sql b/src/test/resources/net/sf/jsqlparser/statement/select/match-recognize/bigquery-options-empty.sql new file mode 100644 index 0000000000..e5ade1c4ca --- /dev/null +++ b/src/test/resources/net/sf/jsqlparser/statement/select/match-recognize/bigquery-options-empty.sql @@ -0,0 +1,11 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +WITH events AS (SELECT 1 AS seq, 10 AS price UNION ALL SELECT 2, 20 UNION ALL SELECT 3, 30 UNION ALL SELECT 4, -5) +SELECT * FROM events MATCH_RECOGNIZE (ORDER BY seq MEASURES FIRST(seq) AS first_seq, LAST(seq) AS last_seq, COUNT(*) AS n PATTERN (A+) DEFINE A AS price > 0 OPTIONS ()) ORDER BY first_seq, last_seq; diff --git a/src/test/resources/net/sf/jsqlparser/statement/select/match-recognize/bigquery-overlap.sql b/src/test/resources/net/sf/jsqlparser/statement/select/match-recognize/bigquery-overlap.sql new file mode 100644 index 0000000000..907491c877 --- /dev/null +++ b/src/test/resources/net/sf/jsqlparser/statement/select/match-recognize/bigquery-overlap.sql @@ -0,0 +1,11 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +WITH events AS (SELECT 1 AS seq, 10 AS price UNION ALL SELECT 2, 20 UNION ALL SELECT 3, 30 UNION ALL SELECT 4, -5) +SELECT * FROM events MATCH_RECOGNIZE (ORDER BY seq MEASURES FIRST(seq) AS first_seq, LAST(seq) AS last_seq, COUNT(*) AS n AFTER MATCH SKIP TO NEXT ROW PATTERN (A+) DEFINE A AS price > 0 ) ORDER BY first_seq, last_seq; diff --git a/src/test/resources/net/sf/jsqlparser/statement/select/match-recognize/bigquery-parameter-bound.sql b/src/test/resources/net/sf/jsqlparser/statement/select/match-recognize/bigquery-parameter-bound.sql new file mode 100644 index 0000000000..d8cb062495 --- /dev/null +++ b/src/test/resources/net/sf/jsqlparser/statement/select/match-recognize/bigquery-parameter-bound.sql @@ -0,0 +1,11 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +WITH events AS (SELECT 1 AS seq, 10 AS price UNION ALL SELECT 2, 20 UNION ALL SELECT 3, 30 UNION ALL SELECT 4, -5) +SELECT * FROM events MATCH_RECOGNIZE (ORDER BY seq MEASURES FIRST(seq) AS first_seq, LAST(seq) AS last_seq, COUNT(*) AS n PATTERN (A{@lo,@hi}) DEFINE A AS price > 0 ) ORDER BY first_seq, last_seq; diff --git a/src/test/resources/net/sf/jsqlparser/statement/select/match-recognize/bigquery-reluctant.sql b/src/test/resources/net/sf/jsqlparser/statement/select/match-recognize/bigquery-reluctant.sql new file mode 100644 index 0000000000..b064ba9aa6 --- /dev/null +++ b/src/test/resources/net/sf/jsqlparser/statement/select/match-recognize/bigquery-reluctant.sql @@ -0,0 +1,11 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +WITH events AS (SELECT 1 AS seq, 10 AS price UNION ALL SELECT 2, 20 UNION ALL SELECT 3, 30 UNION ALL SELECT 4, -5) +SELECT * FROM events MATCH_RECOGNIZE (ORDER BY seq MEASURES FIRST(seq) AS first_seq, LAST(seq) AS last_seq, COUNT(*) AS n PATTERN (A+? B) DEFINE A AS price > 0, B AS price > 0 ) ORDER BY first_seq, last_seq; diff --git a/src/test/resources/net/sf/jsqlparser/statement/select/match-recognize/bigquery-repeated-anchors.sql b/src/test/resources/net/sf/jsqlparser/statement/select/match-recognize/bigquery-repeated-anchors.sql new file mode 100644 index 0000000000..d99d747934 --- /dev/null +++ b/src/test/resources/net/sf/jsqlparser/statement/select/match-recognize/bigquery-repeated-anchors.sql @@ -0,0 +1,11 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +WITH events AS (SELECT 1 AS seq, 10 AS price UNION ALL SELECT 2, 20 UNION ALL SELECT 3, 30 UNION ALL SELECT 4, -5) +SELECT * FROM events MATCH_RECOGNIZE (ORDER BY seq MEASURES FIRST(seq) AS first_seq, LAST(seq) AS last_seq, COUNT(*) AS n PATTERN (^A+$$) DEFINE A AS price != 0 ) ORDER BY first_seq, last_seq; diff --git a/src/test/resources/net/sf/jsqlparser/statement/select/match-recognize/bigquery-source-alias.sql b/src/test/resources/net/sf/jsqlparser/statement/select/match-recognize/bigquery-source-alias.sql new file mode 100644 index 0000000000..750e1f2574 --- /dev/null +++ b/src/test/resources/net/sf/jsqlparser/statement/select/match-recognize/bigquery-source-alias.sql @@ -0,0 +1,11 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +WITH events AS (SELECT 1 AS seq, 10 AS price UNION ALL SELECT 2, 20 UNION ALL SELECT 3, 30 UNION ALL SELECT 4, -5) +SELECT * FROM events AS e MATCH_RECOGNIZE (ORDER BY seq MEASURES FIRST(seq) AS first_seq, LAST(seq) AS last_seq, COUNT(*) AS n PATTERN (A+) DEFINE A AS price > 0 ) AS m ORDER BY first_seq, last_seq; diff --git a/src/test/resources/net/sf/jsqlparser/statement/select/match-recognize/execution-cases.txt b/src/test/resources/net/sf/jsqlparser/statement/select/match-recognize/execution-cases.txt new file mode 100644 index 0000000000..7c57d20006 --- /dev/null +++ b/src/test/resources/net/sf/jsqlparser/statement/select/match-recognize/execution-cases.txt @@ -0,0 +1,32 @@ +BIGQUERY bigquery-baseline.sql +BIGQUERY bigquery-options-empty.sql +BIGQUERY bigquery-longest-false.sql +BIGQUERY bigquery-longest-true.sql +BIGQUERY bigquery-reluctant.sql +BIGQUERY bigquery-greedy.sql +BIGQUERY bigquery-overlap.sql +BIGQUERY bigquery-bounds.sql +BIGQUERY bigquery-parameter-bound.sql +BIGQUERY bigquery-empty-alternative.sql +BIGQUERY bigquery-adjacent-anchors.sql +BIGQUERY bigquery-repeated-anchors.sql +BIGQUERY bigquery-nested-struct.sql +BIGQUERY bigquery-source-alias.sql +BIGQUERY bigquery-issue2350.sql +ORACLE oracle-baseline.sql +ORACLE oracle-one.sql +ORACLE oracle-all.sql +ORACLE oracle-show.sql +ORACLE oracle-omit.sql +ORACLE oracle-unmatched.sql +ORACLE oracle-subset.sql +ORACLE oracle-permute.sql +ORACLE oracle-exclusion.sql +ORACLE oracle-running-final.sql +ORACLE oracle-skip-first.sql +ORACLE oracle-skip-last.sql +ORACLE oracle-skip-symbol.sql +ORACLE oracle-result-alias.sql +ORACLE oracle-empty-group.sql +ORACLE oracle-fixed-reluctant.sql +ORACLE oracle-minimal.sql diff --git a/src/test/resources/net/sf/jsqlparser/statement/select/match-recognize/oracle-all.sql b/src/test/resources/net/sf/jsqlparser/statement/select/match-recognize/oracle-all.sql new file mode 100644 index 0000000000..0aaa34150e --- /dev/null +++ b/src/test/resources/net/sf/jsqlparser/statement/select/match-recognize/oracle-all.sql @@ -0,0 +1,11 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +WITH events AS (SELECT 1 seq, 10 price FROM dual UNION ALL SELECT 2, 20 FROM dual UNION ALL SELECT 3, -5 FROM dual UNION ALL SELECT 4, 15 FROM dual) +SELECT * FROM events MATCH_RECOGNIZE (ORDER BY seq MEASURES COUNT(*) AS n ALL ROWS PER MATCH PATTERN (A+) DEFINE A AS price > 0) ; diff --git a/src/test/resources/net/sf/jsqlparser/statement/select/match-recognize/oracle-baseline.sql b/src/test/resources/net/sf/jsqlparser/statement/select/match-recognize/oracle-baseline.sql new file mode 100644 index 0000000000..c633a83882 --- /dev/null +++ b/src/test/resources/net/sf/jsqlparser/statement/select/match-recognize/oracle-baseline.sql @@ -0,0 +1,11 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +WITH events AS (SELECT 1 seq, 10 price FROM dual UNION ALL SELECT 2, 20 FROM dual UNION ALL SELECT 3, -5 FROM dual UNION ALL SELECT 4, 15 FROM dual) +SELECT * FROM events MATCH_RECOGNIZE (ORDER BY seq MEASURES COUNT(*) AS n PATTERN (A+) DEFINE A AS price > 0) ; diff --git a/src/test/resources/net/sf/jsqlparser/statement/select/match-recognize/oracle-empty-group.sql b/src/test/resources/net/sf/jsqlparser/statement/select/match-recognize/oracle-empty-group.sql new file mode 100644 index 0000000000..032f4e5994 --- /dev/null +++ b/src/test/resources/net/sf/jsqlparser/statement/select/match-recognize/oracle-empty-group.sql @@ -0,0 +1,11 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +WITH events AS (SELECT 1 seq, 10 price FROM dual UNION ALL SELECT 2, 20 FROM dual UNION ALL SELECT 3, -5 FROM dual UNION ALL SELECT 4, 15 FROM dual) +SELECT * FROM events MATCH_RECOGNIZE (ORDER BY seq MEASURES COUNT(*) AS n PATTERN (A ()) DEFINE A AS price > 0) ; diff --git a/src/test/resources/net/sf/jsqlparser/statement/select/match-recognize/oracle-exclusion.sql b/src/test/resources/net/sf/jsqlparser/statement/select/match-recognize/oracle-exclusion.sql new file mode 100644 index 0000000000..aa4e925e38 --- /dev/null +++ b/src/test/resources/net/sf/jsqlparser/statement/select/match-recognize/oracle-exclusion.sql @@ -0,0 +1,11 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +WITH events AS (SELECT 1 seq, 10 price FROM dual UNION ALL SELECT 2, 20 FROM dual UNION ALL SELECT 3, -5 FROM dual UNION ALL SELECT 4, 15 FROM dual) +SELECT * FROM events MATCH_RECOGNIZE (ORDER BY seq MEASURES COUNT(*) AS n ALL ROWS PER MATCH PATTERN ({- A+ -} B) DEFINE A AS price > 0, B AS price <= 0) ; diff --git a/src/test/resources/net/sf/jsqlparser/statement/select/match-recognize/oracle-fixed-reluctant.sql b/src/test/resources/net/sf/jsqlparser/statement/select/match-recognize/oracle-fixed-reluctant.sql new file mode 100644 index 0000000000..8f8a94b6b9 --- /dev/null +++ b/src/test/resources/net/sf/jsqlparser/statement/select/match-recognize/oracle-fixed-reluctant.sql @@ -0,0 +1,11 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +WITH events AS (SELECT 1 seq, 10 price FROM dual UNION ALL SELECT 2, 20 FROM dual UNION ALL SELECT 3, -5 FROM dual UNION ALL SELECT 4, 15 FROM dual) +SELECT * FROM events MATCH_RECOGNIZE (ORDER BY seq MEASURES COUNT(*) AS n PATTERN (A{1}?) DEFINE A AS price > 0) ; diff --git a/src/test/resources/net/sf/jsqlparser/statement/select/match-recognize/oracle-minimal.sql b/src/test/resources/net/sf/jsqlparser/statement/select/match-recognize/oracle-minimal.sql new file mode 100644 index 0000000000..f371d1c910 --- /dev/null +++ b/src/test/resources/net/sf/jsqlparser/statement/select/match-recognize/oracle-minimal.sql @@ -0,0 +1,11 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +WITH events AS (SELECT 1 seq, 10 price FROM dual UNION ALL SELECT 2, 20 FROM dual UNION ALL SELECT 3, -5 FROM dual UNION ALL SELECT 4, 15 FROM dual) +SELECT * FROM events MATCH_RECOGNIZE (ALL ROWS PER MATCH PATTERN (A+) DEFINE A AS price > 0); diff --git a/src/test/resources/net/sf/jsqlparser/statement/select/match-recognize/oracle-omit.sql b/src/test/resources/net/sf/jsqlparser/statement/select/match-recognize/oracle-omit.sql new file mode 100644 index 0000000000..fda09ec681 --- /dev/null +++ b/src/test/resources/net/sf/jsqlparser/statement/select/match-recognize/oracle-omit.sql @@ -0,0 +1,11 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +WITH events AS (SELECT 1 seq, 10 price FROM dual UNION ALL SELECT 2, 20 FROM dual UNION ALL SELECT 3, -5 FROM dual UNION ALL SELECT 4, 15 FROM dual) +SELECT * FROM events MATCH_RECOGNIZE (ORDER BY seq MEASURES COUNT(*) AS n ALL ROWS PER MATCH OMIT EMPTY MATCHES PATTERN (A*) DEFINE A AS price > 0) ; diff --git a/src/test/resources/net/sf/jsqlparser/statement/select/match-recognize/oracle-one.sql b/src/test/resources/net/sf/jsqlparser/statement/select/match-recognize/oracle-one.sql new file mode 100644 index 0000000000..1bf773d9fd --- /dev/null +++ b/src/test/resources/net/sf/jsqlparser/statement/select/match-recognize/oracle-one.sql @@ -0,0 +1,11 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +WITH events AS (SELECT 1 seq, 10 price FROM dual UNION ALL SELECT 2, 20 FROM dual UNION ALL SELECT 3, -5 FROM dual UNION ALL SELECT 4, 15 FROM dual) +SELECT * FROM events MATCH_RECOGNIZE (ORDER BY seq MEASURES COUNT(*) AS n ONE ROW PER MATCH PATTERN (A+) DEFINE A AS price > 0) ; diff --git a/src/test/resources/net/sf/jsqlparser/statement/select/match-recognize/oracle-permute.sql b/src/test/resources/net/sf/jsqlparser/statement/select/match-recognize/oracle-permute.sql new file mode 100644 index 0000000000..9263e24746 --- /dev/null +++ b/src/test/resources/net/sf/jsqlparser/statement/select/match-recognize/oracle-permute.sql @@ -0,0 +1,11 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +WITH events AS (SELECT 1 seq, 10 price FROM dual UNION ALL SELECT 2, 20 FROM dual UNION ALL SELECT 3, -5 FROM dual UNION ALL SELECT 4, 15 FROM dual) +SELECT * FROM events MATCH_RECOGNIZE (ORDER BY seq MEASURES COUNT(*) AS n PATTERN (PERMUTE(A,B)) DEFINE A AS price > 0, B AS price <= 0) ; diff --git a/src/test/resources/net/sf/jsqlparser/statement/select/match-recognize/oracle-result-alias.sql b/src/test/resources/net/sf/jsqlparser/statement/select/match-recognize/oracle-result-alias.sql new file mode 100644 index 0000000000..afd89a8fbf --- /dev/null +++ b/src/test/resources/net/sf/jsqlparser/statement/select/match-recognize/oracle-result-alias.sql @@ -0,0 +1,11 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +WITH events AS (SELECT 1 seq, 10 price FROM dual UNION ALL SELECT 2, 20 FROM dual UNION ALL SELECT 3, -5 FROM dual UNION ALL SELECT 4, 15 FROM dual) +SELECT * FROM events MATCH_RECOGNIZE (ORDER BY seq MEASURES COUNT(*) AS n PATTERN (A+) DEFINE A AS price > 0) m; diff --git a/src/test/resources/net/sf/jsqlparser/statement/select/match-recognize/oracle-running-final.sql b/src/test/resources/net/sf/jsqlparser/statement/select/match-recognize/oracle-running-final.sql new file mode 100644 index 0000000000..f6b68f2222 --- /dev/null +++ b/src/test/resources/net/sf/jsqlparser/statement/select/match-recognize/oracle-running-final.sql @@ -0,0 +1,11 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +WITH events AS (SELECT 1 seq, 10 price FROM dual UNION ALL SELECT 2, 20 FROM dual UNION ALL SELECT 3, -5 FROM dual UNION ALL SELECT 4, 15 FROM dual) +SELECT * FROM events MATCH_RECOGNIZE (ORDER BY seq MEASURES RUNNING SUM(A.price) + FINAL SUM(A.price) AS total ALL ROWS PER MATCH PATTERN (A+) DEFINE A AS price > 0) ; diff --git a/src/test/resources/net/sf/jsqlparser/statement/select/match-recognize/oracle-show.sql b/src/test/resources/net/sf/jsqlparser/statement/select/match-recognize/oracle-show.sql new file mode 100644 index 0000000000..c85d50fb5c --- /dev/null +++ b/src/test/resources/net/sf/jsqlparser/statement/select/match-recognize/oracle-show.sql @@ -0,0 +1,11 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +WITH events AS (SELECT 1 seq, 10 price FROM dual UNION ALL SELECT 2, 20 FROM dual UNION ALL SELECT 3, -5 FROM dual UNION ALL SELECT 4, 15 FROM dual) +SELECT * FROM events MATCH_RECOGNIZE (ORDER BY seq MEASURES COUNT(*) AS n ALL ROWS PER MATCH SHOW EMPTY MATCHES PATTERN (A*) DEFINE A AS price > 0) ; diff --git a/src/test/resources/net/sf/jsqlparser/statement/select/match-recognize/oracle-skip-first.sql b/src/test/resources/net/sf/jsqlparser/statement/select/match-recognize/oracle-skip-first.sql new file mode 100644 index 0000000000..f7aaf33a26 --- /dev/null +++ b/src/test/resources/net/sf/jsqlparser/statement/select/match-recognize/oracle-skip-first.sql @@ -0,0 +1,11 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +WITH events AS (SELECT 1 seq, 10 price FROM dual UNION ALL SELECT 2, 20 FROM dual UNION ALL SELECT 3, -5 FROM dual UNION ALL SELECT 4, 15 FROM dual) +SELECT * FROM events MATCH_RECOGNIZE (ORDER BY seq MEASURES COUNT(*) AS n AFTER MATCH SKIP TO FIRST B PATTERN (A+ B) DEFINE A AS price > 0, B AS price <= 0) ; diff --git a/src/test/resources/net/sf/jsqlparser/statement/select/match-recognize/oracle-skip-last.sql b/src/test/resources/net/sf/jsqlparser/statement/select/match-recognize/oracle-skip-last.sql new file mode 100644 index 0000000000..bfe76985c3 --- /dev/null +++ b/src/test/resources/net/sf/jsqlparser/statement/select/match-recognize/oracle-skip-last.sql @@ -0,0 +1,11 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +WITH events AS (SELECT 1 seq, 10 price FROM dual UNION ALL SELECT 2, 20 FROM dual UNION ALL SELECT 3, -5 FROM dual UNION ALL SELECT 4, 15 FROM dual) +SELECT * FROM events MATCH_RECOGNIZE (ORDER BY seq MEASURES COUNT(*) AS n AFTER MATCH SKIP TO LAST B PATTERN (A+ B) DEFINE A AS price > 0, B AS price <= 0) ; diff --git a/src/test/resources/net/sf/jsqlparser/statement/select/match-recognize/oracle-skip-symbol.sql b/src/test/resources/net/sf/jsqlparser/statement/select/match-recognize/oracle-skip-symbol.sql new file mode 100644 index 0000000000..19d03e6067 --- /dev/null +++ b/src/test/resources/net/sf/jsqlparser/statement/select/match-recognize/oracle-skip-symbol.sql @@ -0,0 +1,11 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +WITH events AS (SELECT 1 seq, 10 price FROM dual UNION ALL SELECT 2, 20 FROM dual UNION ALL SELECT 3, -5 FROM dual UNION ALL SELECT 4, 15 FROM dual) +SELECT * FROM events MATCH_RECOGNIZE (ORDER BY seq MEASURES COUNT(*) AS n AFTER MATCH SKIP TO B PATTERN (A+ B) DEFINE A AS price > 0, B AS price <= 0) ; diff --git a/src/test/resources/net/sf/jsqlparser/statement/select/match-recognize/oracle-subset.sql b/src/test/resources/net/sf/jsqlparser/statement/select/match-recognize/oracle-subset.sql new file mode 100644 index 0000000000..3ead0335d9 --- /dev/null +++ b/src/test/resources/net/sf/jsqlparser/statement/select/match-recognize/oracle-subset.sql @@ -0,0 +1,11 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +WITH events AS (SELECT 1 seq, 10 price FROM dual UNION ALL SELECT 2, 20 FROM dual UNION ALL SELECT 3, -5 FROM dual UNION ALL SELECT 4, 15 FROM dual) +SELECT * FROM events MATCH_RECOGNIZE (ORDER BY seq MEASURES SUM(U.price) AS total PATTERN (A+ B) SUBSET U = (A,B) DEFINE A AS price > 0, B AS price <= 0) ; diff --git a/src/test/resources/net/sf/jsqlparser/statement/select/match-recognize/oracle-unmatched.sql b/src/test/resources/net/sf/jsqlparser/statement/select/match-recognize/oracle-unmatched.sql new file mode 100644 index 0000000000..c75ce5759a --- /dev/null +++ b/src/test/resources/net/sf/jsqlparser/statement/select/match-recognize/oracle-unmatched.sql @@ -0,0 +1,11 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +WITH events AS (SELECT 1 seq, 10 price FROM dual UNION ALL SELECT 2, 20 FROM dual UNION ALL SELECT 3, -5 FROM dual UNION ALL SELECT 4, 15 FROM dual) +SELECT * FROM events MATCH_RECOGNIZE (ORDER BY seq MEASURES COUNT(*) AS n ALL ROWS PER MATCH WITH UNMATCHED ROWS PATTERN (A+) DEFINE A AS price > 0) ; diff --git a/src/test/resources/postgresql/access-ddl.sql b/src/test/resources/postgresql/access-ddl.sql new file mode 100644 index 0000000000..29de42e431 --- /dev/null +++ b/src/test/resources/postgresql/access-ddl.sql @@ -0,0 +1,104 @@ +--- +-- #%L +-- JSQLParser library +-- %% +-- Copyright (C) 2004 - 2026 JSQLParser +-- %% +-- Dual licensed under GNU LGPL 2.1 or Apache License 2.0 +-- #L% +--- +CREATE ROLE replicator WITH REPLICATION LOGIN PASSWORD 'example-only' +CREATE ROLE app +CREATE ROLE login +CREATE ROLE admin WITH SUPERUSER CREATEDB CREATEROLE INHERIT LOGIN BYPASSRLS +CREATE ROLE guest NOSUPERUSER NOCREATEDB NOCREATEROLE NOINHERIT NOLOGIN NOREPLICATION NOBYPASSRLS +CREATE ROLE reader WITH CONNECTION LIMIT -1 PASSWORD NULL +CREATE ROLE analyst ENCRYPTED PASSWORD 'example-only' VALID UNTIL '2030-01-01' +CREATE ROLE member IN ROLE reader, writer +CREATE ROLE staff IN GROUP reader ROLE app, analyst ADMIN owner USER member +CREATE ROLE legacy SYSID 100 +CREATE ROLE "Mixed Role" LOGIN +CREATE USER app_user WITH LOGIN PASSWORD 'example-only' +CREATE USER app_user NOLOGIN +CREATE GROUP app_group +CREATE GROUP staff WITH USER app, analyst +ALTER ROLE replicator CONNECTION LIMIT 10 +ALTER ROLE app WITH LOGIN NOSUPERUSER PASSWORD NULL +ALTER ROLE app ENCRYPTED PASSWORD 'example-only' VALID UNTIL 'infinity' +ALTER ROLE app RENAME TO new_app +ALTER ROLE CURRENT_USER SET search_path TO app, public +ALTER ROLE CURRENT_ROLE IN DATABASE appdb SET statement_timeout = '5s' +ALTER ROLE ALL SET work_mem TO DEFAULT +ALTER ROLE app SET work_mem FROM CURRENT +ALTER ROLE app RESET work_mem +ALTER ROLE ALL IN DATABASE appdb RESET ALL +ALTER USER app_user WITH NOLOGIN +ALTER USER SESSION_USER SET enable_seqscan TO on +ALTER GROUP staff ADD USER app, analyst +ALTER GROUP staff DROP USER analyst +ALTER GROUP staff RENAME TO team +GRANT SELECT ON app.users TO reader +GRANT SELECT ON type TO app +GRANT SELECT ON tables TO app +GRANT SELECT ON function TO granted +GRANT SELECT ON tables, types TO app +GRANT SELECT ON type.items TO app +GRANT SELECT, INSERT, UPDATE ON TABLE app.users, app.orders TO reader, writer +GRANT SELECT (id, name), UPDATE (name) ON app.users TO app +GRANT ALL PRIVILEGES ON TABLE app.users TO PUBLIC WITH GRANT OPTION +GRANT REFERENCES (id) ON app.users TO GROUP app +GRANT TRUNCATE, TRIGGER, MAINTAIN ON app.users TO owner +GRANT USAGE, SELECT, UPDATE ON SEQUENCE app.user_id_seq TO app +GRANT ALL ON ALL SEQUENCES IN SCHEMA app, public TO app +GRANT SELECT ON ALL TABLES IN SCHEMA app TO reader +GRANT CREATE, CONNECT, TEMPORARY ON DATABASE appdb TO app +GRANT TEMP ON DATABASE appdb TO app +GRANT USAGE ON DOMAIN app.positive_int TO app +GRANT USAGE ON FOREIGN DATA WRAPPER postgres_fdw TO app +GRANT USAGE ON FOREIGN SERVER foreign_server TO app +GRANT EXECUTE ON FUNCTION add(integer, integer) TO app +GRANT EXECUTE ON FUNCTION app.f(IN value integer, VARIADIC args text[]) TO app +GRANT EXECUTE ON FUNCTION app.no_args(), app.unspecified TO app +GRANT EXECUTE ON PROCEDURE app.process(integer) TO app +GRANT EXECUTE ON ROUTINE app.process(integer) TO CURRENT_USER +GRANT EXECUTE ON ALL FUNCTIONS IN SCHEMA app TO PUBLIC +GRANT EXECUTE ON ALL PROCEDURES IN SCHEMA app TO app +GRANT EXECUTE ON ALL ROUTINES IN SCHEMA app TO app +GRANT USAGE ON LANGUAGE plpgsql TO app +GRANT SELECT, UPDATE ON LARGE OBJECT 123, 456 TO app +GRANT SET, ALTER SYSTEM ON PARAMETER work_mem, maintenance_work_mem TO app +GRANT USAGE, CREATE ON SCHEMA public TO app GRANTED BY CURRENT_ROLE +GRANT CREATE ON TABLESPACE appspace TO app +GRANT USAGE ON TYPE app.status TO app +GRANT reader, writer TO app WITH ADMIN OPTION +GRANT reader TO app WITH INHERIT FALSE +GRANT reader TO app WITH SET TRUE GRANTED BY owner +REVOKE EXECUTE ON FUNCTION add(integer, integer) FROM app +REVOKE GRANT OPTION FOR SELECT ON TABLE app.users FROM app CASCADE +REVOKE SELECT (id) ON app.users FROM app RESTRICT +REVOKE ALL PRIVILEGES ON ALL TABLES IN SCHEMA app FROM PUBLIC +REVOKE USAGE ON SCHEMA public FROM app GRANTED BY CURRENT_USER CASCADE +REVOKE reader, writer FROM app +REVOKE ADMIN OPTION FOR reader FROM app CASCADE +REVOKE INHERIT OPTION FOR reader FROM app RESTRICT +REVOKE SET OPTION FOR reader FROM app GRANTED BY owner +ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO app +ALTER DEFAULT PRIVILEGES FOR ROLE owner, admin IN SCHEMA app, public GRANT USAGE ON SEQUENCES TO app WITH GRANT OPTION +ALTER DEFAULT PRIVILEGES FOR USER owner REVOKE EXECUTE ON FUNCTIONS FROM PUBLIC +ALTER DEFAULT PRIVILEGES GRANT EXECUTE ON ROUTINES TO app +ALTER DEFAULT PRIVILEGES GRANT USAGE ON TYPES TO app +ALTER DEFAULT PRIVILEGES GRANT CREATE ON SCHEMAS TO app +ALTER DEFAULT PRIVILEGES GRANT SELECT ON LARGE OBJECTS TO app +ALTER DEFAULT PRIVILEGES REVOKE GRANT OPTION FOR SELECT ON TABLES FROM app CASCADE +CREATE TRIGGER audit_trigger AFTER INSERT OR UPDATE ON users FOR EACH ROW EXECUTE FUNCTION audit_row() +CREATE CONSTRAINT TRIGGER fk_trigger AFTER INSERT ON child DEFERRABLE INITIALLY DEFERRED FOR EACH ROW EXECUTE FUNCTION check_parent() +CREATE CONSTRAINT TRIGGER fk_trigger AFTER UPDATE ON child FROM parent NOT DEFERRABLE FOR EACH ROW EXECUTE FUNCTION check_parent() +CREATE CONSTRAINT TRIGGER fk_trigger AFTER INSERT ON child INITIALLY IMMEDIATE FOR EACH ROW EXECUTE PROCEDURE check_parent() +CREATE OR REPLACE TRIGGER audit_trigger BEFORE UPDATE OF balance, owner ON accounts FOR EACH ROW WHEN (OLD.balance IS DISTINCT FROM NEW.balance) EXECUTE FUNCTION audit_row('balance', 1, flag) +CREATE TRIGGER insert_view INSTEAD OF INSERT ON app.users_view FOR EACH ROW EXECUTE FUNCTION app.insert_row() +CREATE TRIGGER truncate_audit AFTER TRUNCATE ON users EXECUTE FUNCTION audit_all() +CREATE TRIGGER statement_audit AFTER DELETE ON users FOR STATEMENT EXECUTE FUNCTION audit_all() +CREATE TRIGGER update_audit AFTER UPDATE ON users REFERENCING OLD TABLE AS old_rows NEW TABLE AS new_rows FOR EACH STATEMENT EXECUTE FUNCTION audit_all() +CREATE TRIGGER insert_audit AFTER INSERT ON users REFERENCING NEW TABLE inserted FOR EACH STATEMENT EXECUTE PROCEDURE audit_all() +CREATE TRIGGER row_audit AFTER UPDATE ON users FOR ROW WHEN (OLD.id <> NEW.id) EXECUTE FUNCTION audit_row() +CREATE TRIGGER "Mixed Trigger" BEFORE DELETE ON "App"."Users" FOR EACH ROW EXECUTE FUNCTION "App"."audit_row"('it''s an example') diff --git a/src/test/resources/postgresql/do-issue1946.sql b/src/test/resources/postgresql/do-issue1946.sql new file mode 100644 index 0000000000..33b3092d58 --- /dev/null +++ b/src/test/resources/postgresql/do-issue1946.sql @@ -0,0 +1,41 @@ +--- +-- #%L +-- JSQLParser library +-- %% +-- Copyright (C) 2004 - 2026 JSQLParser +-- %% +-- Dual licensed under GNU LGPL 2.1 or Apache License 2.0 +-- #L% +--- +DO $$ +BEGIN + IF NOT EXISTS( select 1 from comm.permission_operation where permission_operation_code = 'ecg_report_time_modify') and EXISTS( select 1 from comm.permission where permission_code = 'data_modify') + THEN + INSERT INTO comm.permission_operation + (permission_operation_id, + permission_id, + permission_operation_code, + permission_operation_name, + "type", + "version", + his_org_id, + his_creater_id, + his_creater_name, + his_create_time, + his_updater_id, + his_update_time) + VALUES + ((select max(permission_operation_id) + 1 from comm.permission_operation), + (select permission_id from comm.permission where permission_code = 'data_modify' limit 1), + 'ecg_report_time_modify', + '心电报告时间修改', + '1', + 0, + (select his_org_id from comm.hospital limit 1), + 1, + '系统管理员', + now(), + 1, + now()) on conflict(permission_operation_id) do nothing; + END IF; +END $$; diff --git a/src/test/resources/postgresql/replication-ddl.sql b/src/test/resources/postgresql/replication-ddl.sql new file mode 100644 index 0000000000..d42e8bfe3d --- /dev/null +++ b/src/test/resources/postgresql/replication-ddl.sql @@ -0,0 +1,53 @@ +--- +-- #%L +-- JSQLParser library +-- %% +-- Copyright (C) 2004 - 2026 JSQLParser +-- %% +-- Dual licensed under GNU LGPL 2.1 or Apache License 2.0 +-- #L% +--- +CREATE PUBLICATION empty_publication +CREATE PUBLICATION all_tables FOR ALL TABLES +CREATE PUBLICATION dbz_publication FOR TABLE users, orders WITH (publish = 'insert, update, delete') +CREATE PUBLICATION filtered FOR TABLE ONLY app.users (id, email) WHERE (active = true) WITH (publish_via_partition_root = true) +CREATE PUBLICATION inherited FOR TABLE app.users * +CREATE PUBLICATION schemas FOR TABLES IN SCHEMA public, app +CREATE PUBLICATION current_schema_pub FOR TABLES IN SCHEMA CURRENT_SCHEMA +CREATE PUBLICATION mixed FOR TABLE users, orders, TABLES IN SCHEMA app, audit, TABLE events +CREATE PUBLICATION explicit_groups FOR TABLE users, TABLE orders +CREATE PUBLICATION stored_columns FOR ALL TABLES WITH (publish_generated_columns = stored, publish_via_partition_root) +CREATE PUBLICATION flags FOR ALL TABLES WITH (publish_via_partition_root = off) +ALTER PUBLICATION dbz_publication ADD TABLE events +ALTER PUBLICATION dbz_publication ADD TABLE users (id, email), orders WHERE (amount > 10) +ALTER PUBLICATION dbz_publication SET TABLE ONLY users, TABLES IN SCHEMA app +ALTER PUBLICATION dbz_publication DROP TABLE users *, orders +ALTER PUBLICATION dbz_publication DROP TABLES IN SCHEMA app, audit +ALTER PUBLICATION dbz_publication SET (publish = 'update, delete, truncate') +ALTER PUBLICATION dbz_publication SET (publish_via_partition_root = on, publish_generated_columns = 'stored') +ALTER PUBLICATION dbz_publication OWNER TO CURRENT_USER +ALTER PUBLICATION dbz_publication RENAME TO live_publication +CREATE SUBSCRIPTION app_sub CONNECTION 'host=localhost dbname=appdb' PUBLICATION dbz_publication WITH (connect = false) +CREATE SUBSCRIPTION app_sub CONNECTION 'dbname=app' PUBLICATION one, two +CREATE SUBSCRIPTION app_sub CONNECTION 'dbname=app' PUBLICATION one WITH (enabled = false, create_slot = false, slot_name = NONE) +CREATE SUBSCRIPTION app_sub CONNECTION 'dbname=app' PUBLICATION one WITH (slot_name = 'NONE', enabled = false, create_slot = false) +CREATE SUBSCRIPTION app_sub CONNECTION 'dbname=app' PUBLICATION one WITH (binary, copy_data = false, streaming = parallel, synchronous_commit = remote_apply) +CREATE SUBSCRIPTION app_sub CONNECTION 'dbname=app' PUBLICATION one WITH (two_phase = true, disable_on_error = true, password_required = true, run_as_owner = false, origin = none, failover = true) +CREATE SUBSCRIPTION app_sub CONNECTION 'dbname=app' PUBLICATION one WITH (streaming = on, synchronous_commit = off) +CREATE SUBSCRIPTION app_sub CONNECTION 'dbname=app' PUBLICATION one WITH (binary = 'yes', enabled = 0) +ALTER SUBSCRIPTION app_sub CONNECTION 'host=localhost dbname=app2' +ALTER SUBSCRIPTION app_sub SET PUBLICATION one, two WITH (refresh = false) +ALTER SUBSCRIPTION app_sub ADD PUBLICATION three WITH (copy_data = false) +ALTER SUBSCRIPTION app_sub DROP PUBLICATION two WITH (refresh = true, copy_data = false) +ALTER SUBSCRIPTION app_sub REFRESH PUBLICATION WITH (copy_data = false) +ALTER SUBSCRIPTION app_sub REFRESH PUBLICATION +ALTER SUBSCRIPTION app_sub ENABLE +ALTER SUBSCRIPTION app_sub DISABLE +ALTER SUBSCRIPTION app_sub SET (slot_name = NONE) +ALTER SUBSCRIPTION app_sub SET (streaming = off, synchronous_commit = local, origin = any) +ALTER SUBSCRIPTION app_sub SET (binary = true, two_phase = false, failover = false) +ALTER SUBSCRIPTION app_sub SKIP (lsn = '0/14C0378') +ALTER SUBSCRIPTION app_sub SKIP (lsn = NONE) +ALTER SUBSCRIPTION app_sub OWNER TO CURRENT_ROLE +ALTER SUBSCRIPTION app_sub RENAME TO next_sub +CREATE PUBLICATION "Pub" FOR TABLE "App"."Orders" ("Id") WHERE ("Id" > 0) diff --git a/src/test/resources/postgresql/type-ddl.sql b/src/test/resources/postgresql/type-ddl.sql new file mode 100644 index 0000000000..375b79446c --- /dev/null +++ b/src/test/resources/postgresql/type-ddl.sql @@ -0,0 +1,84 @@ +--- +-- #%L +-- JSQLParser library +-- %% +-- Copyright (C) 2004 - 2026 JSQLParser +-- %% +-- Dual licensed under GNU LGPL 2.1 or Apache License 2.0 +-- #L% +--- +CREATE TYPE mood AS ENUM ('sad', 'ok', 'happy') +CREATE TYPE app."Mood" AS ENUM ('can''t', 'OK') +CREATE TYPE empty_enum AS ENUM () +CREATE TYPE shell_type +CREATE TYPE inventory_item AS (name text, supplier_id integer, price numeric(10, 2)) +CREATE TYPE empty_composite AS () +CREATE TYPE localized AS (label text COLLATE pg_catalog."C", tags text[], created timestamp with time zone) +CREATE TYPE float8range AS RANGE (SUBTYPE = float8, SUBTYPE_DIFF = float8mi) +CREATE TYPE app.r AS RANGE (SUBTYPE_OPCLASS = pg_catalog.text_ops, SUBTYPE = text, COLLATION = pg_catalog."C", CANONICAL = app.canonical, MULTIRANGE_TYPE_NAME = app.mr) +ALTER TYPE mood ADD VALUE IF NOT EXISTS 'great' AFTER 'happy' +ALTER TYPE mood ADD VALUE 'bad' BEFORE 'sad' +ALTER TYPE mood ADD VALUE 'new' +ALTER TYPE mood RENAME VALUE 'ok' TO 'fine' +ALTER TYPE mood OWNER TO CURRENT_USER +ALTER TYPE mood RENAME TO feeling +ALTER TYPE mood SET SCHEMA app +ALTER TYPE inventory_item RENAME ATTRIBUTE name TO title CASCADE +ALTER TYPE inventory_item ADD ATTRIBUTE sku text COLLATE "C" RESTRICT +ALTER TYPE inventory_item DROP ATTRIBUTE IF EXISTS price CASCADE +ALTER TYPE inventory_item ALTER ATTRIBUTE supplier_id SET DATA TYPE bigint RESTRICT +ALTER TYPE inventory_item ADD ATTRIBUTE sku text, DROP ATTRIBUTE price, ALTER ATTRIBUTE name TYPE varchar(80) +CREATE DOMAIN positive_integer AS integer CHECK (VALUE > 0) +CREATE DOMAIN app.label text COLLATE "C" DEFAULT 'ok' CONSTRAINT required NOT NULL CONSTRAINT nonempty CHECK (VALUE <> '') +CREATE DOMAIN nullable_integer AS integer NULL +CREATE DOMAIN percent AS numeric(5, 2) DEFAULT 0 CHECK (VALUE >= 0) CHECK (VALUE <= 100) +ALTER DOMAIN positive_integer ADD CONSTRAINT positive CHECK (VALUE > 0) NOT VALID +ALTER DOMAIN positive_integer ADD CHECK (VALUE < 100) +ALTER DOMAIN positive_integer ADD CONSTRAINT required NOT NULL +ALTER DOMAIN positive_integer SET DEFAULT 1 + 2 +ALTER DOMAIN positive_integer DROP DEFAULT +ALTER DOMAIN positive_integer SET NOT NULL +ALTER DOMAIN positive_integer DROP NOT NULL +ALTER DOMAIN positive_integer DROP CONSTRAINT IF EXISTS positive CASCADE +ALTER DOMAIN positive_integer DROP CONSTRAINT positive RESTRICT +ALTER DOMAIN positive_integer RENAME CONSTRAINT positive TO strictly_positive +ALTER DOMAIN positive_integer VALIDATE CONSTRAINT positive +ALTER DOMAIN positive_integer OWNER TO SESSION_USER +ALTER DOMAIN positive_integer RENAME TO positive_number +ALTER DOMAIN positive_integer SET SCHEMA app +CREATE EXTENSION hstore +CREATE EXTENSION IF NOT EXISTS hstore WITH SCHEMA public +CREATE EXTENSION hstore VERSION '1.8' SCHEMA public CASCADE +CREATE EXTENSION hstore WITH VERSION version_two +ALTER EXTENSION hstore UPDATE +ALTER EXTENSION hstore UPDATE TO '1.8' +ALTER EXTENSION hstore UPDATE TO version_two +ALTER EXTENSION hstore SET SCHEMA app +ALTER EXTENSION hstore ADD TABLE app.settings +ALTER EXTENSION hstore DROP VIEW app.settings_view +ALTER EXTENSION hstore ADD MATERIALIZED VIEW app.cached_settings +ALTER EXTENSION hstore ADD FOREIGN TABLE app.remote_settings +ALTER EXTENSION hstore ADD FUNCTION app.populate_record(anyelement, hstore) +ALTER EXTENSION hstore DROP FUNCTION app.populate_record +ALTER EXTENSION hstore ADD FUNCTION app.f(IN input integer, OUT result text, VARIADIC rest text[]) +ALTER EXTENSION hstore ADD PROCEDURE app.refresh() +ALTER EXTENSION hstore ADD ROUTINE app.f(double precision, timestamp with time zone) +ALTER EXTENSION hstore ADD AGGREGATE app.total(integer) +ALTER EXTENSION hstore ADD AGGREGATE app.count_all(*) +ALTER EXTENSION hstore ADD AGGREGATE app.percentile(double precision ORDER BY double precision) +ALTER EXTENSION hstore ADD CAST (text AS hstore) +ALTER EXTENSION hstore ADD TYPE app.mood +ALTER EXTENSION hstore ADD DOMAIN app.positive +ALTER EXTENSION hstore ADD COLLATION app.localized +ALTER EXTENSION hstore ADD TEXT SEARCH CONFIGURATION app.search +ALTER EXTENSION hstore ADD TEXT SEARCH DICTIONARY app.words +ALTER EXTENSION hstore ADD OPERATOR CLASS app.ops USING btree +ALTER EXTENSION hstore ADD OPERATOR FAMILY app.ops USING gist +ALTER EXTENSION hstore ADD ACCESS METHOD my_index +ALTER EXTENSION hstore ADD FOREIGN DATA WRAPPER my_wrapper +ALTER EXTENSION hstore ADD EVENT TRIGGER install_hook +ALTER EXTENSION hstore ADD PROCEDURAL LANGUAGE plpgsql +ALTER EXTENSION hstore ADD TRANSFORM FOR hstore LANGUAGE plpython3u +ALTER EXTENSION hstore ADD OPERATOR app.&& (integer, integer) +ALTER EXTENSION hstore DROP OPERATOR = (hstore, hstore) +ALTER EXTENSION hstore ADD OPERATOR - (NONE, integer)