From f4c2137e68c47c9c9a77422d6c53c3a9d813a5c2 Mon Sep 17 00:00:00 2001 From: Tobias Warneke Date: Thu, 28 Dec 2023 01:17:16 +0100 Subject: [PATCH 01/31] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index da8534c2e..1539772df 100644 --- a/pom.xml +++ b/pom.xml @@ -2,7 +2,7 @@ 4.0.0 com.github.jsqlparser jsqlparser - 4.8 + 4.9-SNAPSHOT JSQLParser library 2004 @@ -107,7 +107,7 @@ scm:git:https://github.com/JSQLParser/JSqlParser.git scm:git:ssh://git@github.com:JSQLParser/JSqlParser.git https://github.com/JSQLParser/JSqlParser.git - jsqlparser-4.8 + HEAD From 6c1caff118f84bd548db95842dd4e50eb7bbbf81 Mon Sep 17 00:00:00 2001 From: Tobias Warneke Date: Thu, 28 Dec 2023 01:29:26 +0100 Subject: [PATCH 02/31] finally done --- pom.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/pom.xml b/pom.xml index 1539772df..166924e06 100644 --- a/pom.xml +++ b/pom.xml @@ -279,6 +279,7 @@ org.apache.maven.plugins maven-release-plugin + 3.0.0-M7 true From e5ba5c7607074647b00fb4e7f97e9156ad287268 Mon Sep 17 00:00:00 2001 From: Andreas Reichel Date: Thu, 28 Dec 2023 15:35:25 +0700 Subject: [PATCH 03/31] build: disable snasphot signing --- .github/workflows/gradle.yml | 6 ++--- .github/workflows/gradle_publish.yml | 38 ++++++++++++++++++++++++++++ README.md | 2 +- build.gradle | 11 +++++--- 4 files changed, 49 insertions(+), 8 deletions(-) create mode 100644 .github/workflows/gradle_publish.yml diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index 14411e519..f18e39d01 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -33,8 +33,8 @@ jobs: - name: Build with Gradle uses: gradle/gradle-build-action@v2.4.2 with: - arguments: build check + arguments: check # arguments: build check publish env: - ossrhUsername: ${{ secrets.OSSRH_USERNAME }} - ossrhPassword: ${{ secrets.OSSRH_TOKEN }} + ossrhUsername: ${{ secrets.OSSRHPASSWORD }} + ossrhPassword: ${{ secrets.OSSRHUSERNAME }} diff --git a/.github/workflows/gradle_publish.yml b/.github/workflows/gradle_publish.yml new file mode 100644 index 000000000..216ff90d2 --- /dev/null +++ b/.github/workflows/gradle_publish.yml @@ -0,0 +1,38 @@ +# This workflow uses actions that are not certified by GitHub. +# They are provided by a third-party and are governed by +# separate terms of service, privacy policy, and support +# documentation. +# This workflow will build a Java project with Gradle and cache/restore any dependencies to improve the workflow execution time +# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-java-with-gradle + +name: Java CI with Gradle + +on: + push: + branches: [ "master" ] + +permissions: + contents: read + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Set up JDK 11 + uses: actions/setup-java@v3 + with: + java-version: '11' + distribution: 'temurin' + - name: Build with Gradle + uses: gradle/gradle-build-action@v2.6.0 + with: + arguments: publish + # arguments: build check publish + env: + ossrhUsername: ${{ secrets.OSSRHPASSWORD }} + ossrhPassword: ${{ secrets.OSSRHUSERNAME }} diff --git a/README.md b/README.md index f3449956a..5a011d6a6 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# [JSqlParser 4.7 Website](https://jsqlparser.github.io/JSqlParser) drawing +# [JSqlParser 4.8 Website](https://jsqlparser.github.io/JSqlParser) drawing ![Build Status](https://github.com/JSQLParser/JSqlParser/actions/workflows/maven.yml/badge.svg) diff --git a/build.gradle b/build.gradle index 4974164dc..79a478f9d 100644 --- a/build.gradle +++ b/build.gradle @@ -544,10 +544,9 @@ publishing { def releasesRepoUrl = "https://oss.sonatype.org/content/repositories/snapshots" def snapshotsRepoUrl= "https://oss.sonatype.org/service/local/staging/deploy/maven2" url = version.endsWith('SNAPSHOT') ? snapshotsRepoUrl : releasesRepoUrl - //credentials(PasswordCredentials) credentials { - username = System.getenv("ossrhUsername") - password = System.getenv("ossrhPassword") + username = System.getenv("OSSRHPASSWORD") + password = System.getenv("OSSRHUSERNAME") } } // maven { @@ -563,7 +562,11 @@ signing { //def signingKey = findProperty("signingKey") //def signingPassword = findProperty("signingPassword") //useInMemoryPgpKeys(signingKey, signingPassword) - sign publishing.publications.mavenJava + + // don't sign SNAPSHOTS + if (!version.endsWith('SNAPSHOT')) { + sign publishing.publications.mavenJava + } } tasks.withType(JavaCompile).configureEach { From 258ee2d7f11af7b10b1dd6670a69dde5c5b1ade4 Mon Sep 17 00:00:00 2001 From: Andreas Reichel Date: Thu, 28 Dec 2023 15:49:43 +0700 Subject: [PATCH 04/31] build: disable snasphot signing --- build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index 79a478f9d..c1ea46173 100644 --- a/build.gradle +++ b/build.gradle @@ -545,8 +545,8 @@ publishing { def snapshotsRepoUrl= "https://oss.sonatype.org/service/local/staging/deploy/maven2" url = version.endsWith('SNAPSHOT') ? snapshotsRepoUrl : releasesRepoUrl credentials { - username = System.getenv("OSSRHPASSWORD") - password = System.getenv("OSSRHUSERNAME") + username = System.getenv("ossrhUsername") + password = System.getenv("ossrhPassword") } } // maven { From c73ea3759932e9be4f4210c941592581d21ac825 Mon Sep 17 00:00:00 2001 From: Andreas Reichel Date: Thu, 28 Dec 2023 15:58:37 +0700 Subject: [PATCH 05/31] build: disable snasphot signing --- build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index c1ea46173..4927f7a59 100644 --- a/build.gradle +++ b/build.gradle @@ -541,8 +541,8 @@ publishing { maven { name "ossrh" - def releasesRepoUrl = "https://oss.sonatype.org/content/repositories/snapshots" - def snapshotsRepoUrl= "https://oss.sonatype.org/service/local/staging/deploy/maven2" + def releasesRepoUrl = "https://oss.sonatype.org/service/local/staging/deploy/maven2" + def snapshotsRepoUrl= "https://oss.sonatype.org/content/repositories/snapshots" url = version.endsWith('SNAPSHOT') ? snapshotsRepoUrl : releasesRepoUrl credentials { username = System.getenv("ossrhUsername") From f0d3ab6b42193ae680290522d89b6670345dfa8a Mon Sep 17 00:00:00 2001 From: Tobias Warneke Date: Thu, 28 Dec 2023 23:17:15 +0100 Subject: [PATCH 06/31] corrected hopefully maven snapshot deployment --- .github/workflows/maven.yml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml index dad694833..a35274835 100644 --- a/.github/workflows/maven.yml +++ b/.github/workflows/maven.yml @@ -15,7 +15,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - java: [8, 11] + java: [8] name: Java ${{ matrix.java }} building ... steps: @@ -26,5 +26,11 @@ jobs: java-version: ${{ matrix.java }} distribution: 'temurin' cache: maven + server-id: sonatype-nexus-snapshots + server-username: MAVEN_USERNAME + server-password: MAVEN_PASSWORD - name: Build with Maven run: mvn -B package --file pom.xml -DdisableXmlReport=true -Djacoco.skip=true -Dpmd.skip=true + env: + MAVEN_USERNAME: ${{ secrets.OSSRHUSERNAME }} + MAVEN_PASSWORD: ${{ secrets.OSSRHPASSWORD }} From a70f0d1f3f3d91ea585f74a4684546229ca917f5 Mon Sep 17 00:00:00 2001 From: Tobias Warneke Date: Thu, 28 Dec 2023 23:20:08 +0100 Subject: [PATCH 07/31] corrected hopefully maven snapshot deployment --- .github/workflows/maven.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml index a35274835..cfd8ebb86 100644 --- a/.github/workflows/maven.yml +++ b/.github/workflows/maven.yml @@ -26,7 +26,7 @@ jobs: java-version: ${{ matrix.java }} distribution: 'temurin' cache: maven - server-id: sonatype-nexus-snapshots + server-id: sonatype-nexus-snapshots server-username: MAVEN_USERNAME server-password: MAVEN_PASSWORD - name: Build with Maven From b40705785751b49e9914f8a5b0c9048c38c0795e Mon Sep 17 00:00:00 2001 From: Tobias Date: Thu, 28 Dec 2023 23:31:54 +0100 Subject: [PATCH 08/31] Create maven_deploy.yml --- .github/workflows/maven_deploy.yml | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 .github/workflows/maven_deploy.yml diff --git a/.github/workflows/maven_deploy.yml b/.github/workflows/maven_deploy.yml new file mode 100644 index 000000000..71e6caaca --- /dev/null +++ b/.github/workflows/maven_deploy.yml @@ -0,0 +1,30 @@ +name: deploy to sonatype snapshots + +on: + workflow_dispatch: + +jobs: + build: + + runs-on: ubuntu-latest + strategy: + matrix: + java: [8] + name: Java ${{ matrix.java }} building ... + + steps: + - uses: actions/checkout@v3 + - name: Set up Java ${{ matrix.java }} + uses: actions/setup-java@v3 + with: + java-version: ${{ matrix.java }} + distribution: 'temurin' + cache: maven + server-id: sonatype-nexus-snapshots + server-username: MAVEN_USERNAME + server-password: MAVEN_PASSWORD + - name: Build with Maven + run: mvn -B deploy --file pom.xml -DdisableXmlReport=true -Djacoco.skip=true -Dpmd.skip=true + env: + MAVEN_USERNAME: ${{ secrets.OSSRHUSERNAME }} + MAVEN_PASSWORD: ${{ secrets.OSSRHPASSWORD }} From 34561d8ebe9806a443388841525bed241d23ecca Mon Sep 17 00:00:00 2001 From: Andreas Reichel Date: Sat, 30 Dec 2023 14:55:30 +0700 Subject: [PATCH 09/31] style: tidy up the GH actions --- .github/workflows/gradle.yml | 11 +++-------- .github/workflows/gradle_publish.yml | 7 +++---- .github/workflows/maven.yml | 4 ++-- .github/workflows/maven_deploy.yml | 7 ++++--- build.gradle | 15 ++++----------- 5 files changed, 16 insertions(+), 28 deletions(-) diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index f18e39d01..67ccc3166 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -1,11 +1,7 @@ -# This workflow uses actions that are not certified by GitHub. -# They are provided by a third-party and are governed by -# separate terms of service, privacy policy, and support -# documentation. # This workflow will build a Java project with Gradle and cache/restore any dependencies to improve the workflow execution time # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-java-with-gradle -name: Java CI with Gradle +name: Gradle CI on: push: @@ -17,7 +13,7 @@ permissions: contents: read jobs: - build: + check: runs-on: ubuntu-latest @@ -31,10 +27,9 @@ jobs: java-version: '11' distribution: 'temurin' - name: Build with Gradle - uses: gradle/gradle-build-action@v2.4.2 + uses: gradle/gradle-build-action@v2.6.0 with: arguments: check - # arguments: build check publish env: ossrhUsername: ${{ secrets.OSSRHPASSWORD }} ossrhPassword: ${{ secrets.OSSRHUSERNAME }} diff --git a/.github/workflows/gradle_publish.yml b/.github/workflows/gradle_publish.yml index 216ff90d2..4e8cbddea 100644 --- a/.github/workflows/gradle_publish.yml +++ b/.github/workflows/gradle_publish.yml @@ -5,17 +5,16 @@ # This workflow will build a Java project with Gradle and cache/restore any dependencies to improve the workflow execution time # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-java-with-gradle -name: Java CI with Gradle +name: Gradle publish Snapshot on: - push: - branches: [ "master" ] + workflow_dispatch: permissions: contents: read jobs: - build: + publish: runs-on: ubuntu-latest diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml index cfd8ebb86..1d566e3ef 100644 --- a/.github/workflows/maven.yml +++ b/.github/workflows/maven.yml @@ -1,7 +1,7 @@ # This workflow will build a Java project with Maven, and cache/restore any dependencies to improve the workflow execution time # For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven -name: Java CI with Maven +name: Maven CI on: push: @@ -10,7 +10,7 @@ on: branches: [ master ] jobs: - build: + package: runs-on: ubuntu-latest strategy: diff --git a/.github/workflows/maven_deploy.yml b/.github/workflows/maven_deploy.yml index 71e6caaca..c14858ffb 100644 --- a/.github/workflows/maven_deploy.yml +++ b/.github/workflows/maven_deploy.yml @@ -1,10 +1,11 @@ -name: deploy to sonatype snapshots +name: Maven deploy snapshot on: - workflow_dispatch: + push: + branches: [ "master" ] jobs: - build: + deploy: runs-on: ubuntu-latest strategy: diff --git a/build.gradle b/build.gradle index 4927f7a59..87384835f 100644 --- a/build.gradle +++ b/build.gradle @@ -64,7 +64,7 @@ def getVersion = { boolean considerSnapshot -> } // for publishing a release, call Gradle with Environment Variable RELEASE: -// RELEASE=true gradle JSQLParser:publish +// RELEASE=true gradle JSQLParser:publish version = getVersion( !System.getenv("RELEASE") ) group = 'com.github.jsqlparser' description = 'JSQLParser library' @@ -298,7 +298,7 @@ pmd { checkstyle { sourceSets = [sourceSets.main, sourceSets.test] - configFile =rootProject.file('config/checkstyle/checkstyle.xml') + configFile = rootProject.file('config/checkstyle/checkstyle.xml') } spotless { @@ -540,21 +540,14 @@ publishing { repositories { maven { name "ossrh" - - def releasesRepoUrl = "https://oss.sonatype.org/service/local/staging/deploy/maven2" - def snapshotsRepoUrl= "https://oss.sonatype.org/content/repositories/snapshots" + def releasesRepoUrl = "https://oss.sonatype.org/service/local/staging/deploy/maven2/" + def snapshotsRepoUrl= "https://oss.sonatype.org/content/repositories/snapshots/" url = version.endsWith('SNAPSHOT') ? snapshotsRepoUrl : releasesRepoUrl credentials { username = System.getenv("ossrhUsername") password = System.getenv("ossrhPassword") } } -// maven { -// name = "GitHubPackages" -// -// url = uri("https://maven.pkg.github.com/JSQLParser/jsqlparser") -// credentials(PasswordCredentials) -// } } } From f1c525a1eaf3087bc46261af955dc08e1e190586 Mon Sep 17 00:00:00 2001 From: David Goss Date: Fri, 5 Jan 2024 10:26:18 +0000 Subject: [PATCH 10/31] feat: support any number/order of merge operations (#1938) * feat: support any number/order of merge operations * fix: readd standard fields and add compatibility logic --- .../sf/jsqlparser/statement/merge/Merge.java | 63 ++++++++-- .../statement/merge/MergeDelete.java | 47 +++++++ .../statement/merge/MergeInsert.java | 7 +- .../statement/merge/MergeOperation.java | 17 +++ .../merge/MergeOperationVisitor.java | 19 +++ .../merge/MergeOperationVisitorAdapter.java | 28 +++++ .../statement/merge/MergeUpdate.java | 7 +- .../util/deparser/MergeDeParser.java | 118 ++++++++++++++++++ .../util/deparser/StatementDeParser.java | 85 +------------ .../validation/validator/MergeValidator.java | 40 +++--- .../net/sf/jsqlparser/parser/JSqlParserCC.jjt | 60 ++++++--- .../jsqlparser/statement/merge/MergeTest.java | 95 +++++++++++--- 12 files changed, 440 insertions(+), 146 deletions(-) create mode 100644 src/main/java/net/sf/jsqlparser/statement/merge/MergeDelete.java create mode 100644 src/main/java/net/sf/jsqlparser/statement/merge/MergeOperation.java create mode 100644 src/main/java/net/sf/jsqlparser/statement/merge/MergeOperationVisitor.java create mode 100644 src/main/java/net/sf/jsqlparser/statement/merge/MergeOperationVisitorAdapter.java create mode 100644 src/main/java/net/sf/jsqlparser/util/deparser/MergeDeParser.java diff --git a/src/main/java/net/sf/jsqlparser/statement/merge/Merge.java b/src/main/java/net/sf/jsqlparser/statement/merge/Merge.java index 0be4d2528..ed2c9ee50 100644 --- a/src/main/java/net/sf/jsqlparser/statement/merge/Merge.java +++ b/src/main/java/net/sf/jsqlparser/statement/merge/Merge.java @@ -26,6 +26,7 @@ import java.util.Iterator; import java.util.List; import java.util.Optional; +import java.util.stream.Collectors; public class Merge implements Statement { @@ -37,9 +38,43 @@ public class Merge implements Statement { private MergeInsert mergeInsert; private MergeUpdate mergeUpdate; private boolean insertFirst = false; + private List operations; private OutputClause outputClause; + private void deriveOperationsFromStandardClauses() { + List operations = new ArrayList<>(); + if (insertFirst) { + Optional.ofNullable(mergeInsert).ifPresent(operations::add); + Optional.ofNullable(mergeUpdate).ifPresent(operations::add); + } else { + Optional.ofNullable(mergeUpdate).ifPresent(operations::add); + Optional.ofNullable(mergeInsert).ifPresent(operations::add); + } + this.operations = operations; + } + + private void deriveStandardClausesFromOperations() { + List applicableOperations = + Optional.ofNullable(operations).orElse(Collections.emptyList()).stream() + .filter(o -> o instanceof MergeUpdate || o instanceof MergeInsert) + .collect(Collectors.toList()); + mergeUpdate = applicableOperations.stream() + .filter(o -> o instanceof MergeUpdate) + .map(MergeUpdate.class::cast) + .findFirst() + .orElse(null); + mergeInsert = applicableOperations.stream() + .filter(o -> o instanceof MergeInsert) + .map(MergeInsert.class::cast) + .findFirst() + .orElse(null); + insertFirst = applicableOperations.stream() + .findFirst() + .map(o -> o instanceof MergeInsert) + .orElse(false); + } + public List getWithItemsList() { return withItemsList; } @@ -129,12 +164,22 @@ public void setOnCondition(Expression onCondition) { this.onCondition = onCondition; } + public List getOperations() { + return operations; + } + + public void setOperations(List operations) { + this.operations = operations; + deriveStandardClausesFromOperations(); + } + public MergeInsert getMergeInsert() { return mergeInsert; } - public void setMergeInsert(MergeInsert insert) { - this.mergeInsert = insert; + public void setMergeInsert(MergeInsert mergeInsert) { + this.mergeInsert = mergeInsert; + deriveOperationsFromStandardClauses(); } public MergeUpdate getMergeUpdate() { @@ -143,6 +188,7 @@ public MergeUpdate getMergeUpdate() { public void setMergeUpdate(MergeUpdate mergeUpdate) { this.mergeUpdate = mergeUpdate; + deriveOperationsFromStandardClauses(); } @Override @@ -156,6 +202,7 @@ public boolean isInsertFirst() { public void setInsertFirst(boolean insertFirst) { this.insertFirst = insertFirst; + deriveOperationsFromStandardClauses(); } public OutputClause getOutputClause() { @@ -193,16 +240,8 @@ public String toString() { b.append(" ON "); b.append(onCondition); - if (insertFirst && mergeInsert != null) { - b.append(mergeInsert); - } - - if (mergeUpdate != null) { - b.append(mergeUpdate); - } - - if (!insertFirst && mergeInsert != null) { - b.append(mergeInsert); + if (operations != null && !operations.isEmpty()) { + operations.forEach(b::append); } if (outputClause != null) { diff --git a/src/main/java/net/sf/jsqlparser/statement/merge/MergeDelete.java b/src/main/java/net/sf/jsqlparser/statement/merge/MergeDelete.java new file mode 100644 index 000000000..fee068a99 --- /dev/null +++ b/src/main/java/net/sf/jsqlparser/statement/merge/MergeDelete.java @@ -0,0 +1,47 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2024 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.statement.merge; + +import net.sf.jsqlparser.expression.Expression; + +import java.io.Serializable; + +public class MergeDelete implements Serializable, MergeOperation { + private Expression andPredicate; + + public Expression getAndPredicate() { + return andPredicate; + } + + public void setAndPredicate(Expression andPredicate) { + this.andPredicate = andPredicate; + } + + public MergeDelete withAndPredicate(Expression andPredicate) { + this.setAndPredicate(andPredicate); + return this; + } + + @Override + public void accept(MergeOperationVisitor mergeOperationVisitor) { + mergeOperationVisitor.visit(this); + } + + @Override + public String toString() { + StringBuilder b = new StringBuilder(); + b.append(" WHEN MATCHED"); + if (andPredicate != null) { + b.append(" AND ").append(andPredicate.toString()); + } + b.append(" THEN DELETE"); + return b.toString(); + } +} diff --git a/src/main/java/net/sf/jsqlparser/statement/merge/MergeInsert.java b/src/main/java/net/sf/jsqlparser/statement/merge/MergeInsert.java index f6affa3ef..95b69bbf3 100644 --- a/src/main/java/net/sf/jsqlparser/statement/merge/MergeInsert.java +++ b/src/main/java/net/sf/jsqlparser/statement/merge/MergeInsert.java @@ -18,7 +18,7 @@ import java.util.Collection; import java.util.Optional; -public class MergeInsert implements Serializable { +public class MergeInsert implements Serializable, MergeOperation { private Expression andPredicate; private ExpressionList columns; @@ -57,6 +57,11 @@ public void setWhereCondition(Expression whereCondition) { this.whereCondition = whereCondition; } + @Override + public void accept(MergeOperationVisitor mergeOperationVisitor) { + mergeOperationVisitor.visit(this); + } + @Override public String toString() { StringBuilder b = new StringBuilder(); diff --git a/src/main/java/net/sf/jsqlparser/statement/merge/MergeOperation.java b/src/main/java/net/sf/jsqlparser/statement/merge/MergeOperation.java new file mode 100644 index 000000000..6a4a9ceff --- /dev/null +++ b/src/main/java/net/sf/jsqlparser/statement/merge/MergeOperation.java @@ -0,0 +1,17 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2024 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.statement.merge; + +/** + * Marker interface to cover {@link MergeDelete}, {@link MergeUpdate} and {@link MergeInsert} + */ +public interface MergeOperation { + void accept(MergeOperationVisitor mergeOperationVisitor); +} diff --git a/src/main/java/net/sf/jsqlparser/statement/merge/MergeOperationVisitor.java b/src/main/java/net/sf/jsqlparser/statement/merge/MergeOperationVisitor.java new file mode 100644 index 000000000..5a0f217d6 --- /dev/null +++ b/src/main/java/net/sf/jsqlparser/statement/merge/MergeOperationVisitor.java @@ -0,0 +1,19 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2024 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.statement.merge; + +public interface MergeOperationVisitor { + + void visit(MergeDelete mergeDelete); + + void visit(MergeUpdate mergeUpdate); + + void visit(MergeInsert mergeInsert); +} diff --git a/src/main/java/net/sf/jsqlparser/statement/merge/MergeOperationVisitorAdapter.java b/src/main/java/net/sf/jsqlparser/statement/merge/MergeOperationVisitorAdapter.java new file mode 100644 index 000000000..80883192c --- /dev/null +++ b/src/main/java/net/sf/jsqlparser/statement/merge/MergeOperationVisitorAdapter.java @@ -0,0 +1,28 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2024 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.statement.merge; + +@SuppressWarnings({"PMD.UncommentedEmptyMethodBody"}) +public class MergeOperationVisitorAdapter implements MergeOperationVisitor { + @Override + public void visit(MergeDelete mergeDelete) { + + } + + @Override + public void visit(MergeUpdate mergeUpdate) { + + } + + @Override + public void visit(MergeInsert mergeInsert) { + + } +} diff --git a/src/main/java/net/sf/jsqlparser/statement/merge/MergeUpdate.java b/src/main/java/net/sf/jsqlparser/statement/merge/MergeUpdate.java index 38e9261a2..c744ac4f2 100644 --- a/src/main/java/net/sf/jsqlparser/statement/merge/MergeUpdate.java +++ b/src/main/java/net/sf/jsqlparser/statement/merge/MergeUpdate.java @@ -15,7 +15,7 @@ import java.io.Serializable; import java.util.List; -public class MergeUpdate implements Serializable { +public class MergeUpdate implements Serializable, MergeOperation { private List updateSets; private Expression andPredicate; @@ -61,6 +61,11 @@ public void setDeleteWhereCondition(Expression deleteWhereCondition) { this.deleteWhereCondition = deleteWhereCondition; } + @Override + public void accept(MergeOperationVisitor mergeOperationVisitor) { + mergeOperationVisitor.visit(this); + } + @Override public String toString() { StringBuilder b = new StringBuilder(); diff --git a/src/main/java/net/sf/jsqlparser/util/deparser/MergeDeParser.java b/src/main/java/net/sf/jsqlparser/util/deparser/MergeDeParser.java new file mode 100644 index 000000000..dfcf83399 --- /dev/null +++ b/src/main/java/net/sf/jsqlparser/util/deparser/MergeDeParser.java @@ -0,0 +1,118 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2024 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.util.deparser; + +import net.sf.jsqlparser.statement.merge.*; +import net.sf.jsqlparser.statement.select.WithItem; + +import java.util.Iterator; +import java.util.List; + +public class MergeDeParser extends AbstractDeParser implements MergeOperationVisitor { + private final ExpressionDeParser expressionDeParser; + + private final SelectDeParser selectDeParser; + + public MergeDeParser(ExpressionDeParser expressionDeParser, SelectDeParser selectDeParser, + StringBuilder buffer) { + super(buffer); + this.expressionDeParser = expressionDeParser; + this.selectDeParser = selectDeParser; + } + + @Override + void deParse(Merge merge) { + List withItemsList = merge.getWithItemsList(); + if (withItemsList != null && !withItemsList.isEmpty()) { + buffer.append("WITH "); + for (Iterator iter = withItemsList.iterator(); iter.hasNext();) { + iter.next().accept(expressionDeParser); + if (iter.hasNext()) { + buffer.append(","); + } + buffer.append(" "); + } + } + + buffer.append("MERGE "); + if (merge.getOracleHint() != null) { + buffer.append(merge.getOracleHint()).append(" "); + } + buffer.append("INTO "); + merge.getTable().accept(selectDeParser); + + buffer.append(" USING "); + merge.getFromItem().accept(selectDeParser); + + buffer.append(" ON "); + merge.getOnCondition().accept(expressionDeParser); + + List operations = merge.getOperations(); + if (operations != null && !operations.isEmpty()) { + operations.forEach(operation -> operation.accept(this)); + } + + if (merge.getOutputClause() != null) { + merge.getOutputClause().appendTo(buffer); + } + } + + @Override + public void visit(MergeDelete mergeDelete) { + buffer.append(" WHEN MATCHED"); + if (mergeDelete.getAndPredicate() != null) { + buffer.append(" AND "); + mergeDelete.getAndPredicate().accept(expressionDeParser); + } + buffer.append(" THEN DELETE"); + } + + @Override + public void visit(MergeUpdate mergeUpdate) { + buffer.append(" WHEN MATCHED"); + if (mergeUpdate.getAndPredicate() != null) { + buffer.append(" AND "); + mergeUpdate.getAndPredicate().accept(expressionDeParser); + } + buffer.append(" THEN UPDATE SET "); + deparseUpdateSets(mergeUpdate.getUpdateSets(), buffer, expressionDeParser); + + if (mergeUpdate.getWhereCondition() != null) { + buffer.append(" WHERE "); + mergeUpdate.getWhereCondition().accept(expressionDeParser); + } + + if (mergeUpdate.getDeleteWhereCondition() != null) { + buffer.append(" DELETE WHERE "); + mergeUpdate.getDeleteWhereCondition().accept(expressionDeParser); + } + } + + @Override + public void visit(MergeInsert mergeInsert) { + buffer.append(" WHEN NOT MATCHED"); + if (mergeInsert.getAndPredicate() != null) { + buffer.append(" AND "); + mergeInsert.getAndPredicate().accept(expressionDeParser); + } + buffer.append(" THEN INSERT "); + if (mergeInsert.getColumns() != null) { + mergeInsert.getColumns().accept(expressionDeParser); + } + buffer.append(" VALUES "); + mergeInsert.getValues().accept(expressionDeParser); + + if (mergeInsert.getWhereCondition() != null) { + buffer.append(" WHERE "); + mergeInsert.getWhereCondition().accept(expressionDeParser); + } + } + +} 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 f5326ae41..f9d314642 100644 --- a/src/main/java/net/sf/jsqlparser/util/deparser/StatementDeParser.java +++ b/src/main/java/net/sf/jsqlparser/util/deparser/StatementDeParser.java @@ -9,8 +9,6 @@ */ package net.sf.jsqlparser.util.deparser; -import java.util.Iterator; -import java.util.List; import java.util.stream.Collectors; import net.sf.jsqlparser.statement.Block; import net.sf.jsqlparser.statement.Commit; @@ -50,12 +48,9 @@ import net.sf.jsqlparser.statement.execute.Execute; import net.sf.jsqlparser.statement.grant.Grant; import net.sf.jsqlparser.statement.insert.Insert; -import net.sf.jsqlparser.statement.merge.Merge; -import net.sf.jsqlparser.statement.merge.MergeInsert; -import net.sf.jsqlparser.statement.merge.MergeUpdate; +import net.sf.jsqlparser.statement.merge.*; import net.sf.jsqlparser.statement.refresh.RefreshMaterializedViewStatement; import net.sf.jsqlparser.statement.select.Select; -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; @@ -203,83 +198,7 @@ public void visit(ResetStatement reset) { @SuppressWarnings({"PMD.CyclomaticComplexity"}) @Override public void visit(Merge merge) { - List withItemsList = merge.getWithItemsList(); - if (withItemsList != null && !withItemsList.isEmpty()) { - buffer.append("WITH "); - for (Iterator iter = withItemsList.iterator(); iter.hasNext();) { - iter.next().accept(expressionDeParser); - if (iter.hasNext()) { - buffer.append(","); - } - buffer.append(" "); - } - } - - buffer.append("MERGE "); - if (merge.getOracleHint() != null) { - buffer.append(merge.getOracleHint()).append(" "); - } - buffer.append("INTO "); - merge.getTable().accept(selectDeParser); - - buffer.append(" USING "); - merge.getFromItem().accept(selectDeParser); - - buffer.append(" ON "); - merge.getOnCondition().accept(expressionDeParser); - - MergeInsert mergeInsert = merge.getMergeInsert(); - MergeUpdate mergeUpdate = merge.getMergeUpdate(); - if (merge.isInsertFirst() && mergeInsert != null) { - deparseMergeInsert(mergeInsert); - } - - if (mergeUpdate != null) { - buffer.append(" WHEN MATCHED"); - if (mergeUpdate.getAndPredicate() != null) { - buffer.append(" AND "); - mergeUpdate.getAndPredicate().accept(expressionDeParser); - } - buffer.append(" THEN UPDATE SET "); - deparseUpdateSets(mergeUpdate.getUpdateSets(), buffer, expressionDeParser); - - if (mergeUpdate.getWhereCondition() != null) { - buffer.append(" WHERE "); - mergeUpdate.getWhereCondition().accept(expressionDeParser); - } - - if (mergeUpdate.getDeleteWhereCondition() != null) { - buffer.append(" DELETE WHERE "); - mergeUpdate.getDeleteWhereCondition().accept(expressionDeParser); - } - } - - if (!merge.isInsertFirst() && mergeInsert != null) { - deparseMergeInsert(mergeInsert); - } - - if (merge.getOutputClause() != null) { - merge.getOutputClause().appendTo(buffer); - } - } - - private void deparseMergeInsert(MergeInsert mergeInsert) { - buffer.append(" WHEN NOT MATCHED"); - if (mergeInsert.getAndPredicate() != null) { - buffer.append(" AND "); - mergeInsert.getAndPredicate().accept(expressionDeParser); - } - buffer.append(" THEN INSERT "); - if (mergeInsert.getColumns() != null) { - mergeInsert.getColumns().accept(expressionDeParser); - } - buffer.append(" VALUES "); - mergeInsert.getValues().accept(expressionDeParser); - - if (mergeInsert.getWhereCondition() != null) { - buffer.append(" WHERE "); - mergeInsert.getWhereCondition().accept(expressionDeParser); - } + new MergeDeParser(expressionDeParser, selectDeParser, buffer).deParse(merge); } @Override 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 0b2dd5711..e9e0765cc 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 @@ -10,14 +10,14 @@ package net.sf.jsqlparser.util.validation.validator; import net.sf.jsqlparser.parser.feature.Feature; -import net.sf.jsqlparser.statement.merge.Merge; +import net.sf.jsqlparser.statement.merge.*; import net.sf.jsqlparser.statement.update.UpdateSet; import net.sf.jsqlparser.util.validation.ValidationCapability; /** * @author gitmotte */ -public class MergeValidator extends AbstractValidator { +public class MergeValidator extends AbstractValidator implements MergeOperationVisitor { @Override @@ -26,20 +26,32 @@ public void validate(Merge merge) { validateFeature(c, Feature.merge); } validateOptionalExpression(merge.getOnCondition()); - // validateOptionalExpression(merge.getFromItem()); - if (merge.getMergeInsert() != null) { - validateOptionalExpressions(merge.getMergeInsert().getColumns()); - validateOptionalExpressions(merge.getMergeInsert().getValues()); - } - if (merge.getMergeUpdate() != null) { - for (UpdateSet updateSet : merge.getMergeUpdate().getUpdateSets()) { - validateOptionalExpressions(updateSet.getColumns()); - validateOptionalExpressions(updateSet.getValues()); - } - validateOptionalExpression(merge.getMergeUpdate().getDeleteWhereCondition()); - validateOptionalExpression(merge.getMergeUpdate().getWhereCondition()); + if (merge.getOperations() != null) { + merge.getOperations().forEach(operation -> operation.accept(this)); } validateOptionalFromItems(merge.getFromItem()); } + @Override + public void visit(MergeDelete mergeDelete) { + validateOptionalExpression(mergeDelete.getAndPredicate()); + } + + @Override + public void visit(MergeUpdate mergeUpdate) { + validateOptionalExpression(mergeUpdate.getAndPredicate()); + for (UpdateSet updateSet : mergeUpdate.getUpdateSets()) { + validateOptionalExpressions(updateSet.getColumns()); + validateOptionalExpressions(updateSet.getValues()); + } + validateOptionalExpression(mergeUpdate.getDeleteWhereCondition()); + validateOptionalExpression(mergeUpdate.getWhereCondition()); + } + + @Override + public void visit(MergeInsert mergeInsert) { + validateOptionalExpression(mergeInsert.getAndPredicate()); + validateOptionalExpressions(mergeInsert.getColumns()); + validateOptionalExpressions(mergeInsert.getValues()); + } } diff --git a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt index 3144b3103..f757a02ad 100644 --- a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt +++ b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt @@ -1701,8 +1701,7 @@ Statement Merge( List with ) : { Table table; FromItem fromItem; Expression condition; - MergeUpdate update; - MergeInsert insert; + List operations; OutputClause outputClause; } { @@ -1710,39 +1709,60 @@ Statement Merge( List with ) : { fromItem = FromItem() { merge.setFromItem(fromItem); } condition = Expression() { merge.setOnCondition(condition); } - [ - ( LOOKAHEAD(2) update = MergeUpdateClause() { merge.setMergeUpdate(update); } - [ insert = MergeInsertClause() { merge.setMergeInsert(insert); } ] - | insert = MergeInsertClause() { merge.withMergeInsert(insert).withInsertFirst(true); } - [ update = MergeUpdateClause() { merge.setMergeUpdate(update); } ] - ) - ] + operations = MergeOperations() { merge.setOperations(operations); } [ outputClause = OutputClause() { merge.setOutputClause(outputClause); } ] { return merge.withWithItemsList(with); } } -MergeUpdate MergeUpdateClause() : { - MergeUpdate mu = new MergeUpdate(); - List updateSets; - Expression predicate; - Expression condition; +List MergeOperations() : { + List operationsList = new ArrayList(); + MergeOperation operation; +} +{ + ( + LOOKAHEAD(2) operation = MergeWhenMatched() { operationsList.add(operation); } + | + operation = MergeWhenNotMatched() { operationsList.add(operation); } + )* + { return operationsList; } +} + +MergeOperation MergeWhenMatched() : { + Expression predicate = null; + MergeOperation operation; } { - [ predicate = Expression() { mu.setAndPredicate(predicate); } ] - - - updateSets = UpdateSets() { mu.setUpdateSets(updateSets); } + [ predicate = Expression() ] + + ( + operation = MergeDeleteClause(predicate) | operation = MergeUpdateClause(predicate) + ) + { return operation; } +} +MergeOperation MergeDeleteClause(Expression predicate) : { + MergeDelete md = new MergeDelete().withAndPredicate(predicate); +} +{ + { return md; } +} + +MergeOperation MergeUpdateClause(Expression predicate) : { + MergeUpdate mu = new MergeUpdate().withAndPredicate(predicate); + List updateSets; + Expression condition; +} +{ + updateSets = UpdateSets() { mu.setUpdateSets(updateSets); } [ condition = Expression() { mu.setWhereCondition(condition); }] [ condition = Expression() { mu.setDeleteWhereCondition(condition); } ] - { return mu; } } -MergeInsert MergeInsertClause() : { +MergeOperation MergeWhenNotMatched() : { MergeInsert mi = new MergeInsert(); Expression predicate; ExpressionList columns; 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 edf0baa92..678b8e7f8 100644 --- a/src/test/java/net/sf/jsqlparser/statement/merge/MergeTest.java +++ b/src/test/java/net/sf/jsqlparser/statement/merge/MergeTest.java @@ -12,12 +12,20 @@ import net.sf.jsqlparser.JSQLParserException; import net.sf.jsqlparser.parser.CCJSqlParserUtil; import net.sf.jsqlparser.statement.Statement; -import org.assertj.core.api.Assertions; 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 java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.stream.Stream; import static net.sf.jsqlparser.test.TestUtils.assertOracleHintExists; import static net.sf.jsqlparser.test.TestUtils.assertSqlCanBeParsedAndDeparsed; -import static org.junit.jupiter.api.Assertions.fail; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; /** * @@ -126,17 +134,6 @@ public void testMergeUpdateInsertOrderIssue401_2() throws JSQLParserException { "MERGE INTO a USING dual ON (col3 = ? AND col1 = ? AND col2 = ?) WHEN MATCHED THEN UPDATE SET col4 = col4 + ? WHEN NOT MATCHED THEN INSERT (col1, col2, col3, col4) VALUES (?, ?, ?, ?)"); } - @Test - public void testMergeUpdateInsertOrderIssue401_3() throws JSQLParserException { - try { - assertSqlCanBeParsedAndDeparsed( - "MERGE INTO a USING dual ON (col3 = ? AND col1 = ? AND col2 = ?) WHEN MATCHED THEN UPDATE SET col4 = col4 + ? WHEN NOT MATCHED THEN INSERT (col1, col2, col3, col4) VALUES (?, ?, ?, ?) WHEN MATCHED THEN UPDATE SET col4 = col4 + ?"); - fail("syntaxerror parsed"); - } catch (JSQLParserException ex) { - // expected to fail - } - } - @Test public void testOracleHint() throws JSQLParserException { String sql = "MERGE /*+ SOMEHINT */ INTO bonuses B\n" + "USING (\n" @@ -165,10 +162,10 @@ public void testInsertMergeWhere() throws JSQLParserException { Merge merge = (Merge) statement; MergeInsert mergeInsert = merge.getMergeInsert(); - Assertions.assertThat(mergeInsert.getWhereCondition()); + assertThat(mergeInsert.getWhereCondition()); MergeUpdate mergeUpdate = merge.getMergeUpdate(); - Assertions.assertThat(mergeUpdate.getWhereCondition()); + assertThat(mergeUpdate.getWhereCondition()); } @Test @@ -265,4 +262,72 @@ void testSnowflakeMergeStatementWithNotMatchedAndPredicate() throws JSQLParserEx assertSqlCanBeParsedAndDeparsed(sql, true); } + + @Test + void testSnowflakeMergeStatementWithManyWhensAndDelete() throws JSQLParserException { + String sql = + "MERGE INTO t1 USING t2 ON t1.t1Key = t2.t2Key\n" + + " WHEN MATCHED AND t2.marked = 1 THEN DELETE\n" + + " WHEN MATCHED AND t2.isNewStatus = 1 THEN UPDATE SET val = t2.newVal, status = t2.newStatus\n" + + + " WHEN MATCHED THEN UPDATE SET val = t2.newVal\n" + + " WHEN NOT MATCHED THEN INSERT (val, status) VALUES (t2.newVal, t2.newStatus)"; + + assertSqlCanBeParsedAndDeparsed(sql, true); + } + + @ParameterizedTest + @MethodSource("deriveOperationsFromStandardClausesCases") + void testDeriveOperationsFromStandardClauses(List expectedOperations, + MergeUpdate update, MergeInsert insert, boolean insertFirst) { + Merge merge = new Merge(); + merge.setMergeUpdate(update); + merge.setMergeInsert(insert); + merge.setInsertFirst(insertFirst); + + assertThat(merge.getOperations()).isEqualTo(expectedOperations); + } + + private static Stream deriveOperationsFromStandardClausesCases() { + MergeUpdate update = mock(MergeUpdate.class); + MergeInsert insert = mock(MergeInsert.class); + + return Stream.of( + Arguments.of(Arrays.asList(update, insert), update, insert, false), + Arguments.of(Arrays.asList(insert, update), update, insert, true)); + } + + @ParameterizedTest + @MethodSource("deriveStandardClausesFromOperationsCases") + void testDeriveStandardClausesFromOperations(List operations, + MergeUpdate expectedUpdate, MergeInsert expectedInsert, boolean expectedInsertFirst) { + Merge merge = new Merge(); + merge.setOperations(operations); + + assertThat(merge.getMergeUpdate()).isEqualTo(expectedUpdate); + assertThat(merge.getMergeInsert()).isEqualTo(expectedInsert); + assertThat(merge.isInsertFirst()).isEqualTo(expectedInsertFirst); + } + + private static Stream deriveStandardClausesFromOperationsCases() { + MergeDelete delete1 = mock(MergeDelete.class); + MergeUpdate update1 = mock(MergeUpdate.class); + MergeUpdate update2 = mock(MergeUpdate.class); + MergeInsert insert1 = mock(MergeInsert.class); + MergeInsert insert2 = mock(MergeInsert.class); + + return Stream.of( + // just the two standard clauses present + Arguments.of(Arrays.asList(update1, insert1), update1, insert1, false), + Arguments.of(Arrays.asList(insert1, update1), update1, insert1, true), + // some clause(s) missing + Arguments.of(Collections.singletonList(update1), update1, null, false), + Arguments.of(Collections.singletonList(insert1), null, insert1, true), + Arguments.of(Collections.emptyList(), null, null, false), + // many clauses (non-standard) + Arguments.of(Arrays.asList(update1, update2, delete1, insert1, insert2), update1, + insert1, false), + Arguments.of(Arrays.asList(insert1, insert2, update1, update2, delete1), update1, + insert1, true)); + } } From 92e02c6da69d9174f9a7c2b699e7f579e15308fb Mon Sep 17 00:00:00 2001 From: "Brian S. O'Neill" Date: Sat, 6 Jan 2024 06:04:45 -0800 Subject: [PATCH 11/31] Build with Automatic-Module-Name for compatibility with the Java module system. (#1941) --- build.gradle | 8 ++++++++ pom.xml | 11 +++++++++++ 2 files changed, 19 insertions(+) diff --git a/build.gradle b/build.gradle index 87384835f..771048f7c 100644 --- a/build.gradle +++ b/build.gradle @@ -141,6 +141,14 @@ javadoc { options.addBooleanOption("Xdoclint:none", true) } +jar { + manifest { + attributes ( + "Automatic-Module-Name": "net.sf.jsqlparser" + ) + } +} + tasks.register('xmldoc', Javadoc) { def outFile = reporting.file( version.endsWith("-SNAPSHOT") diff --git a/pom.xml b/pom.xml index 166924e06..f0b2c9d8b 100644 --- a/pom.xml +++ b/pom.xml @@ -328,6 +328,17 @@ + + org.apache.maven.plugins + maven-jar-plugin + + + + net.sf.jsqlparser + + + + maven-site-plugin 3.12.1 From bc16618eaa8fd936e8acb496521b94620b4ab0ad Mon Sep 17 00:00:00 2001 From: "Brian S. O'Neill" Date: Sat, 6 Jan 2024 09:44:12 -0800 Subject: [PATCH 12/31] fix: ExpressionVisitor.visit(AllTableColumns) method isn't being called. (#1942) This fixes a regression introduced in change #4b4ae04f44ff18b669d0f30d637e5b7c64b085e4: feat: BigQuery Except(..) Replace(..) syntax --- .../statement/select/AllTableColumns.java | 6 ++++++ .../ExpressionVisitorAdapterTest.java | 18 ++++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/src/main/java/net/sf/jsqlparser/statement/select/AllTableColumns.java b/src/main/java/net/sf/jsqlparser/statement/select/AllTableColumns.java index 429ebcd35..cda2e8136 100644 --- a/src/main/java/net/sf/jsqlparser/statement/select/AllTableColumns.java +++ b/src/main/java/net/sf/jsqlparser/statement/select/AllTableColumns.java @@ -9,6 +9,7 @@ */ package net.sf.jsqlparser.statement.select; +import net.sf.jsqlparser.expression.ExpressionVisitor; import net.sf.jsqlparser.expression.operators.relational.ExpressionList; import net.sf.jsqlparser.schema.Column; import net.sf.jsqlparser.schema.Table; @@ -50,4 +51,9 @@ public AllTableColumns withTable(Table table) { public StringBuilder appendTo(StringBuilder builder) { return super.appendTo(table.appendTo(builder).append(".")); } + + @Override + public void accept(ExpressionVisitor expressionVisitor) { + expressionVisitor.visit(this); + } } diff --git a/src/test/java/net/sf/jsqlparser/expression/ExpressionVisitorAdapterTest.java b/src/test/java/net/sf/jsqlparser/expression/ExpressionVisitorAdapterTest.java index fa301142a..52e71b8bf 100644 --- a/src/test/java/net/sf/jsqlparser/expression/ExpressionVisitorAdapterTest.java +++ b/src/test/java/net/sf/jsqlparser/expression/ExpressionVisitorAdapterTest.java @@ -15,6 +15,7 @@ import net.sf.jsqlparser.expression.operators.relational.InExpression; import net.sf.jsqlparser.parser.CCJSqlParserUtil; 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 net.sf.jsqlparser.statement.select.SelectVisitorAdapter; @@ -241,4 +242,21 @@ public void testRowConstructor() throws JSQLParserException { "CAST(ROW(dataid, value, calcMark) AS ROW(datapointid CHAR, value CHAR, calcMark CHAR))") .accept(adapter); } + + @Test + public void testAllTableColumns() throws JSQLParserException { + PlainSelect plainSelect = (PlainSelect) CCJSqlParserUtil.parse("select a.* from foo a"); + final AllTableColumns[] holder = new AllTableColumns[1]; + Expression from = plainSelect.getSelectItems().get(0).getExpression(); + from.accept(new ExpressionVisitorAdapter() { + + @Override + public void visit(AllTableColumns all) { + holder[0] = all; + } + }); + + assertNotNull(holder[0]); + assertEquals("a.*", holder[0].toString()); + } } From f1676dd992911d987f0a7da8b29fe80443b70ff4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8C=AB=E5=B1=8E=E5=92=96=E5=95=A1?= Date: Thu, 1 Feb 2024 15:07:36 +0800 Subject: [PATCH 13/31] feat: support keyword "only" for postgresql (#1952) Co-authored-by: mjh --- .../statement/select/PlainSelect.java | 21 ++++++++++++++++++- .../util/deparser/SelectDeParser.java | 3 +++ .../net/sf/jsqlparser/parser/JSqlParserCC.jjt | 4 ++++ .../statement/select/SelectTest.java | 7 +++++++ 4 files changed, 34 insertions(+), 1 deletion(-) diff --git a/src/main/java/net/sf/jsqlparser/statement/select/PlainSelect.java b/src/main/java/net/sf/jsqlparser/statement/select/PlainSelect.java index 72a4e530c..560622c67 100644 --- a/src/main/java/net/sf/jsqlparser/statement/select/PlainSelect.java +++ b/src/main/java/net/sf/jsqlparser/statement/select/PlainSelect.java @@ -65,6 +65,8 @@ public class PlainSelect extends Select { */ private boolean isUsingFinal = false; + private boolean isUsingOnly = false; + @Deprecated public boolean isUseBrackets() { return false; @@ -213,6 +215,19 @@ public PlainSelect withUsingFinal(boolean usingFinal) { return this; } + public boolean isUsingOnly() { + return isUsingOnly; + } + + public void setUsingOnly(boolean usingOnly) { + isUsingOnly = usingOnly; + } + + public PlainSelect withUsingOnly(boolean usingOnly) { + this.setUsingOnly(usingOnly); + return this; + } + @Override public void accept(SelectVisitor selectVisitor) { selectVisitor.visit(this); @@ -439,7 +454,11 @@ public StringBuilder appendSelectBodyTo(StringBuilder builder) { } if (fromItem != null) { - builder.append(" FROM ").append(fromItem); + builder.append(" FROM "); + if (isUsingOnly) { + builder.append("ONLY "); + } + builder.append(fromItem); if (lateralViews != null) { for (LateralView lateralView : lateralViews) { builder.append(" ").append(lateralView); 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 a6b718baf..832989022 100644 --- a/src/main/java/net/sf/jsqlparser/util/deparser/SelectDeParser.java +++ b/src/main/java/net/sf/jsqlparser/util/deparser/SelectDeParser.java @@ -210,6 +210,9 @@ public void visit(PlainSelect plainSelect) { if (plainSelect.getFromItem() != null) { buffer.append(" FROM "); + if (plainSelect.isUsingOnly()) { + buffer.append("ONLY "); + } plainSelect.getFromItem().accept(this); if (plainSelect.getFromItem() instanceof Table) { diff --git a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt index f757a02ad..13e5d2dd6 100644 --- a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt +++ b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt @@ -2277,6 +2277,10 @@ PlainSelect PlainSelect() #PlainSelect: [ lateralViews=LateralViews() ] [ LOOKAHEAD(2) joins=JoinsList() ] ] + [ LOOKAHEAD(3) { plainSelect.setUsingOnly(true); } fromItem=FromItem() + [ lateralViews=LateralViews() ] + [ LOOKAHEAD(2) joins=JoinsList() ] + ] // Clickhouse FINAL as shown at https://clickhouse.com/docs/en/operations/settings/settings#final [ LOOKAHEAD(2) { plainSelect.setUsingFinal(true); } ] 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 527313bfe..781b3af59 100644 --- a/src/test/java/net/sf/jsqlparser/statement/select/SelectTest.java +++ b/src/test/java/net/sf/jsqlparser/statement/select/SelectTest.java @@ -5806,4 +5806,11 @@ public void testIssue1907() throws JSQLParserException { "SELECT * FROM (SELECT year, person, SUM(amount) FROM rentals GROUP BY year, person) t1 ORDER BY year DESC WITH ROLLUP"; assertSqlCanBeParsedAndDeparsed(stmt2); } + + @Test + public void testIssue1908() throws JSQLParserException { + // postgresql14 + String stmt = "SELECT * FROM ONLY sys_business_rule"; + assertSqlCanBeParsedAndDeparsed(stmt); + } } From d9c44499d096b1f11fe4a635d7665ed8f226ab81 Mon Sep 17 00:00:00 2001 From: mjh Date: Sun, 4 Feb 2024 14:57:22 +0800 Subject: [PATCH 14/31] feat: with no log (#1953) Co-authored-by: mjh --- .../statement/select/PlainSelect.java | 36 +++++++++++++++++++ .../util/deparser/SelectDeParser.java | 6 ++++ .../net/sf/jsqlparser/parser/JSqlParserCC.jjt | 3 ++ .../statement/select/SelectTest.java | 6 ++++ 4 files changed, 51 insertions(+) diff --git a/src/main/java/net/sf/jsqlparser/statement/select/PlainSelect.java b/src/main/java/net/sf/jsqlparser/statement/select/PlainSelect.java index 560622c67..2745dae6d 100644 --- a/src/main/java/net/sf/jsqlparser/statement/select/PlainSelect.java +++ b/src/main/java/net/sf/jsqlparser/statement/select/PlainSelect.java @@ -67,6 +67,10 @@ public class PlainSelect extends Select { private boolean isUsingOnly = false; + private boolean useWithNoLog = false; + + private Table intoTempTable = null; + @Deprecated public boolean isUseBrackets() { return false; @@ -228,6 +232,32 @@ public PlainSelect withUsingOnly(boolean usingOnly) { return this; } + public boolean isUseWithNoLog() { + return useWithNoLog; + } + + public void setUseWithNoLog(boolean useWithNoLog) { + this.useWithNoLog = useWithNoLog; + } + + public PlainSelect withUseWithNoLog(boolean useWithNoLog) { + this.setUseWithNoLog(useWithNoLog); + return this; + } + + public Table getIntoTempTable() { + return intoTempTable; + } + + public void setIntoTempTable(Table intoTempTable) { + this.intoTempTable = intoTempTable; + } + + public PlainSelect withIntoTempTable(Table intoTempTable) { + this.setIntoTempTable(intoTempTable); + return this; + } + @Override public void accept(SelectVisitor selectVisitor) { selectVisitor.visit(this); @@ -529,6 +559,12 @@ public StringBuilder appendSelectBodyTo(StringBuilder builder) { builder.append(" WHERE ").append(where); } } + if (intoTempTable != null) { + builder.append(" INTO TEMP ").append(intoTempTable); + } + if (useWithNoLog) { + builder.append(" WITH NO LOG"); + } return builder; } 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 832989022..d2a53028c 100644 --- a/src/main/java/net/sf/jsqlparser/util/deparser/SelectDeParser.java +++ b/src/main/java/net/sf/jsqlparser/util/deparser/SelectDeParser.java @@ -320,6 +320,12 @@ public void visit(PlainSelect plainSelect) { if (plainSelect.getForXmlPath() != null) { buffer.append(" FOR XML PATH(").append(plainSelect.getForXmlPath()).append(")"); } + if (plainSelect.getIntoTempTable() != null) { + buffer.append(" INTO TEMP ").append(plainSelect.getIntoTempTable()); + } + if (plainSelect.isUseWithNoLog()) { + buffer.append(" WITH NO LOG"); + } } diff --git a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt index 13e5d2dd6..c857e4cbb 100644 --- a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt +++ b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt @@ -2237,6 +2237,7 @@ PlainSelect PlainSelect() #PlainSelect: boolean noWait = false; String windowName = null; WindowDefinition winDef; + Table intoTempTable = null; } { @@ -2322,6 +2323,8 @@ PlainSelect PlainSelect() #PlainSelect: | { plainSelect.setSkipLocked(true); }) ] ] [ LOOKAHEAD() optimize = OptimizeFor() { plainSelect.setOptimizeFor(optimize); } ] + [ LOOKAHEAD(3) intoTempTable = Table() { plainSelect.setIntoTempTable(intoTempTable);} ] + [ LOOKAHEAD(3) { plainSelect.setUseWithNoLog(true); } ] { plainSelect.setSelectItems(selectItems); plainSelect.setFromItem(fromItem); 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 781b3af59..fd4f28099 100644 --- a/src/test/java/net/sf/jsqlparser/statement/select/SelectTest.java +++ b/src/test/java/net/sf/jsqlparser/statement/select/SelectTest.java @@ -5813,4 +5813,10 @@ public void testIssue1908() throws JSQLParserException { String stmt = "SELECT * FROM ONLY sys_business_rule"; assertSqlCanBeParsedAndDeparsed(stmt); } + + @Test + public void testIssue1833() throws JSQLParserException { + String stmt = "SELECT age, name, gender FROM user_info INTO TEMP user_temp WITH NO LOG"; + assertSqlCanBeParsedAndDeparsed(stmt); + } } From cc7aa01913a720141be0da69bac41b6e4497f422 Mon Sep 17 00:00:00 2001 From: mjh Date: Sun, 4 Feb 2024 15:19:19 +0800 Subject: [PATCH 15/31] 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 --- .../statement/alter/AlterExpression.java | 18 ++++++++++++++++++ .../statement/alter/AlterOperation.java | 2 +- .../net/sf/jsqlparser/parser/JSqlParserCC.jjt | 3 +++ .../jsqlparser/statement/alter/AlterTest.java | 7 +++++++ 4 files changed, 29 insertions(+), 1 deletion(-) 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 f31de0da3..1ea5d4292 100644 --- a/src/main/java/net/sf/jsqlparser/statement/alter/AlterExpression.java +++ b/src/main/java/net/sf/jsqlparser/statement/alter/AlterExpression.java @@ -68,6 +68,8 @@ public class AlterExpression implements Serializable { private boolean useBrackets = false; + private String truncatePartitionName = null; + public Index getOldIndex() { return oldIndex; } @@ -402,6 +404,19 @@ public void setUk(boolean uk) { this.uk = uk; } + public String getTruncatePartitionName() { + return truncatePartitionName; + } + + public void setTruncatePartitionName(String truncatePartitionName) { + this.truncatePartitionName = truncatePartitionName; + } + + public AlterExpression withTruncatePartitionName(String truncatePartitionName) { + this.truncatePartitionName = truncatePartitionName; + return this; + } + @Override @SuppressWarnings({"PMD.CyclomaticComplexity", "PMD.NPathComplexity", "PMD.ExcessiveMethodLength", "PMD.SwitchStmtsShouldHaveDefault"}) @@ -441,6 +456,9 @@ public String toString() { && !pkColumns.isEmpty()) { // Oracle Multi Column Drop b.append("DROP (").append(PlainSelect.getStringList(pkColumns)).append(')'); + } else if (operation == AlterOperation.TRUNCATE_PARTITION + && truncatePartitionName != null) { + b.append("TRUNCATE PARTITION ").append(truncatePartitionName); } else { if (operation == AlterOperation.COMMENT_WITH_EQUAL_SIGN) { b.append("COMMENT =").append(" "); 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 adddaa14d..3d0ead731 100644 --- a/src/main/java/net/sf/jsqlparser/statement/alter/AlterOperation.java +++ b/src/main/java/net/sf/jsqlparser/statement/alter/AlterOperation.java @@ -10,7 +10,7 @@ package net.sf.jsqlparser.statement.alter; public enum AlterOperation { - ADD, ALTER, DROP, DROP_PRIMARY_KEY, DROP_UNIQUE, DROP_FOREIGN_KEY, MODIFY, CHANGE, ALGORITHM, RENAME, RENAME_TABLE, RENAME_INDEX, RENAME_KEY, RENAME_CONSTRAINT, COMMENT, COMMENT_WITH_EQUAL_SIGN, UNSPECIFIC; + ADD, ALTER, DROP, DROP_PRIMARY_KEY, DROP_UNIQUE, DROP_FOREIGN_KEY, MODIFY, CHANGE, ALGORITHM, RENAME, RENAME_TABLE, RENAME_INDEX, RENAME_KEY, RENAME_CONSTRAINT, COMMENT, COMMENT_WITH_EQUAL_SIGN, UNSPECIFIC, TRUNCATE_PARTITION; public static AlterOperation from(String operation) { return Enum.valueOf(AlterOperation.class, operation.toUpperCase()); diff --git a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt index c857e4cbb..40b9d82b8 100644 --- a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt +++ b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt @@ -6255,6 +6255,7 @@ AlterExpression AlterExpression(): AlterExpression.ColumnDropNotNull alterExpressionColumnDropNotNull = null; AlterExpression.ColumnDropDefault alterExpressionColumnDropDefault = null; ReferentialAction.Action action = null; + String truncatePartitionName = null; // for captureRest() List tokens = new LinkedList(); @@ -6546,6 +6547,8 @@ AlterExpression AlterExpression(): } ) | + LOOKAHEAD(2) { alterExp.setOperation(AlterOperation.TRUNCATE_PARTITION); } truncatePartitionName = RelObjectName() { alterExp.setTruncatePartitionName(truncatePartitionName); } + | tokens = captureRest() { alterExp.setOperation(AlterOperation.UNSPECIFIC); StringBuilder optionalSpecifier = new StringBuilder(); 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 ab07437a6..b43780dce 100644 --- a/src/test/java/net/sf/jsqlparser/statement/alter/AlterTest.java +++ b/src/test/java/net/sf/jsqlparser/statement/alter/AlterTest.java @@ -981,4 +981,11 @@ public void testAlterColumnSetCommitTimestamp1() throws JSQLParserException { assertEquals("UPDATE_DATE_TIME_GMT SET OPTIONS (allow_commit_timestamp=true)", type.toString()); } + + @Test + public void testIssue1890() throws JSQLParserException { + String stmt = + "ALTER TABLE xdmiddle.ft_mid_sop_sms_send_list_daily TRUNCATE PARTITION sum_date"; + assertSqlCanBeParsedAndDeparsed(stmt); + } } From 98aa90cb988580a4d7065bbbd23d31c29278b271 Mon Sep 17 00:00:00 2001 From: mjh Date: Wed, 7 Feb 2024 11:32:34 +0800 Subject: [PATCH 16/31] fix: issue1875 (#1957) * fix: issue1875 * fix: code format --------- Co-authored-by: mjh --- .../sf/jsqlparser/statement/alter/Alter.java | 23 ++++++++++++++++--- .../statement/alter/AlterExpression.java | 19 +++++++++++++++ .../net/sf/jsqlparser/parser/JSqlParserCC.jjt | 10 +++----- .../jsqlparser/statement/alter/AlterTest.java | 7 ++++++ 4 files changed, 49 insertions(+), 10 deletions(-) diff --git a/src/main/java/net/sf/jsqlparser/statement/alter/Alter.java b/src/main/java/net/sf/jsqlparser/statement/alter/Alter.java index 8257aabac..0aabfc181 100644 --- a/src/main/java/net/sf/jsqlparser/statement/alter/Alter.java +++ b/src/main/java/net/sf/jsqlparser/statement/alter/Alter.java @@ -24,6 +24,8 @@ public class Alter implements Statement { private Table table; private boolean useOnly = false; + private boolean useTableIfExists = false; + private List alterExpressions; public Table getTable() { @@ -42,6 +44,19 @@ public void setUseOnly(boolean useOnly) { this.useOnly = useOnly; } + public boolean isUseTableIfExists() { + return useTableIfExists; + } + + public void setUseTableIfExists(boolean useTableIfExists) { + this.useTableIfExists = useTableIfExists; + } + + public Alter withUseTableIfExists(boolean useTableIfExists) { + this.useTableIfExists = useTableIfExists; + return this; + } + public void addAlterExpression(AlterExpression alterExpression) { if (alterExpressions == null) { alterExpressions = new ArrayList(); @@ -71,7 +86,7 @@ public String toString() { b.append("ONLY "); } - if (alterExpressions.size()>0 && alterExpressions.get(0).getOperation()==AlterOperation.RENAME_TABLE && alterExpressions.get(0).isUsingIfExists()) { + if (useTableIfExists) { b.append("IF EXISTS "); } @@ -108,13 +123,15 @@ public Alter withAlterExpressions(List alterExpressions) { } public Alter addAlterExpressions(AlterExpression... alterExpressions) { - List collection = Optional.ofNullable(getAlterExpressions()).orElseGet(ArrayList::new); + List collection = + Optional.ofNullable(getAlterExpressions()).orElseGet(ArrayList::new); Collections.addAll(collection, alterExpressions); return this.withAlterExpressions(collection); } public Alter addAlterExpressions(Collection alterExpressions) { - List collection = Optional.ofNullable(getAlterExpressions()).orElseGet(ArrayList::new); + List collection = + Optional.ofNullable(getAlterExpressions()).orElseGet(ArrayList::new); collection.addAll(alterExpressions); return this.withAlterExpressions(collection); } 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 1ea5d4292..744d967a4 100644 --- a/src/main/java/net/sf/jsqlparser/statement/alter/AlterExpression.java +++ b/src/main/java/net/sf/jsqlparser/statement/alter/AlterExpression.java @@ -70,6 +70,8 @@ public class AlterExpression implements Serializable { private String truncatePartitionName = null; + private boolean useIfNotExists = false; + public Index getOldIndex() { return oldIndex; } @@ -417,6 +419,19 @@ public AlterExpression withTruncatePartitionName(String truncatePartitionName) { return this; } + public boolean isUseIfNotExists() { + return useIfNotExists; + } + + public void setUseIfNotExists(boolean useIfNotExists) { + this.useIfNotExists = useIfNotExists; + } + + public AlterExpression withUserIfNotExists(boolean userIfNotExists) { + this.useIfNotExists = userIfNotExists; + return this; + } + @Override @SuppressWarnings({"PMD.CyclomaticComplexity", "PMD.NPathComplexity", "PMD.ExcessiveMethodLength", "PMD.SwitchStmtsShouldHaveDefault"}) @@ -493,6 +508,10 @@ public String toString() { if (hasColumn) { b.append("COLUMN "); } + if (useIfNotExists + && operation == AlterOperation.ADD) { + b.append("IF NOT EXISTS "); + } } if (useBrackets && colDataTypeList.size() == 1) { b.append(" ( "); diff --git a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt index 40b9d82b8..40f393769 100644 --- a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt +++ b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt @@ -6293,7 +6293,7 @@ AlterExpression AlterExpression(): | LOOKAHEAD(3) ( ( LOOKAHEAD(2) { alterExp.hasColumn(true); } )? - + [ { alterExp.setUseIfNotExists(true); } ] ( LOOKAHEAD(4) ( "(" @@ -6625,14 +6625,10 @@ Alter AlterTable(): { [ { alter.setUseOnly(true); } ] - [ LOOKAHEAD(2) { usingIfExists = true; } ] - + [ LOOKAHEAD(2) { alter.setUseTableIfExists(true); } ] table=Table() { alter.setTable(table); } - alterExp=AlterExpression() { if (usingIfExists) - alter.addAlterExpression( alterExp.withUsingIfExists(true) ); - else - alter.addAlterExpression(alterExp); } + alterExp=AlterExpression() { alter.addAlterExpression(alterExp); } ("," alterExp=AlterExpression() { alter.addAlterExpression(alterExp); } )* 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 b43780dce..86962ae0f 100644 --- a/src/test/java/net/sf/jsqlparser/statement/alter/AlterTest.java +++ b/src/test/java/net/sf/jsqlparser/statement/alter/AlterTest.java @@ -988,4 +988,11 @@ public void testIssue1890() throws JSQLParserException { "ALTER TABLE xdmiddle.ft_mid_sop_sms_send_list_daily TRUNCATE PARTITION sum_date"; assertSqlCanBeParsedAndDeparsed(stmt); } + + @Test + public void testIssue1875() throws JSQLParserException { + String stmt = + "ALTER TABLE IF EXISTS usercenter.dict_surgeries ADD COLUMN IF NOT EXISTS operation_grade_id int8 NULL"; + assertSqlCanBeParsedAndDeparsed(stmt); + } } From 182f484dc43945b145a74aa44b9b6a60e1168646 Mon Sep 17 00:00:00 2001 From: hancher Date: Wed, 7 Feb 2024 16:49:59 +0800 Subject: [PATCH 17/31] fix: tables not find in parentheses join sql. (#1956) * fix: tables not find in parentheses join sql. * fix: test unit fix * fix: code format * fix: remove import * * fix: remove import * in unit test --- .../jsqlparser/parser/AbstractJSqlParser.java | 10 ++--- .../sf/jsqlparser/util/TablesNamesFinder.java | 41 +++++++++++-------- .../util/TablesNamesFinderTest.java | 32 ++++++++++----- 3 files changed, 49 insertions(+), 34 deletions(-) diff --git a/src/main/java/net/sf/jsqlparser/parser/AbstractJSqlParser.java b/src/main/java/net/sf/jsqlparser/parser/AbstractJSqlParser.java index 771402b1a..13ebcd781 100644 --- a/src/main/java/net/sf/jsqlparser/parser/AbstractJSqlParser.java +++ b/src/main/java/net/sf/jsqlparser/parser/AbstractJSqlParser.java @@ -9,12 +9,12 @@ */ package net.sf.jsqlparser.parser; -import java.util.ArrayList; -import java.util.List; - import net.sf.jsqlparser.parser.feature.Feature; import net.sf.jsqlparser.parser.feature.FeatureConfiguration; +import java.util.ArrayList; +import java.util.List; + public abstract class AbstractJSqlParser

{ protected int jdbcParameterIndex = 0; @@ -26,7 +26,7 @@ public P withSquareBracketQuotation(boolean allowSquareBracketQuotation) { } public P withAllowComplexParsing(boolean allowComplexParsing) { - return withFeature(Feature.allowComplexParsing, allowComplexParsing); + return withFeature(Feature.allowComplexParsing, allowComplexParsing); } public P withUnsupportedStatements(boolean allowUnsupportedStatements) { @@ -40,7 +40,7 @@ public P withTimeOut(long timeOutMillSeconds) { public P withBackslashEscapeCharacter(boolean allowBackslashEscapeCharacter) { return withFeature(Feature.allowBackslashEscapeCharacter, allowBackslashEscapeCharacter); } - + public P withFeature(Feature f, boolean enabled) { getConfiguration().setValue(f, enabled); return me(); diff --git a/src/main/java/net/sf/jsqlparser/util/TablesNamesFinder.java b/src/main/java/net/sf/jsqlparser/util/TablesNamesFinder.java index 7a94ceff7..4dc760925 100644 --- a/src/main/java/net/sf/jsqlparser/util/TablesNamesFinder.java +++ b/src/main/java/net/sf/jsqlparser/util/TablesNamesFinder.java @@ -176,6 +176,7 @@ import net.sf.jsqlparser.statement.update.Update; import net.sf.jsqlparser.statement.upsert.Upsert; + /** * Find all used tables within an select statement. * @@ -294,15 +295,7 @@ public void visit(PlainSelect plainSelect) { plainSelect.getFromItem().accept(this); } - if (plainSelect.getJoins() != null) { - for (Join join : plainSelect.getJoins()) { - join.getFromItem().accept(this); - join.getRightItem().accept(this); - for (Expression expression : join.getOnExpressions()) { - expression.accept(this); - } - } - } + visitJoins(plainSelect.getJoins()); if (plainSelect.getWhere() != null) { plainSelect.getWhere().accept(this); } @@ -807,15 +800,7 @@ public void visit(Delete delete) { } } - if (delete.getJoins() != null) { - for (Join join : delete.getJoins()) { - join.getFromItem().accept(this); - join.getRightItem().accept(this); - for (Expression expression : join.getOnExpressions()) { - expression.accept(this); - } - } - } + visitJoins(delete.getJoins()); if (delete.getWhere() != null) { delete.getWhere().accept(this); @@ -1033,6 +1018,26 @@ public void visit(UseStatement use) { @Override public void visit(ParenthesedFromItem parenthesis) { parenthesis.getFromItem().accept(this); + // support join keyword in fromItem + visitJoins(parenthesis.getJoins()); + } + + /** + * visit join block + * + * @param parenthesis join sql block + */ + private void visitJoins(List parenthesis) { + if (parenthesis == null) { + return; + } + for (Join join : parenthesis) { + join.getFromItem().accept(this); + join.getRightItem().accept(this); + for (Expression expression : join.getOnExpressions()) { + expression.accept(this); + } + } } @Override diff --git a/src/test/java/net/sf/jsqlparser/util/TablesNamesFinderTest.java b/src/test/java/net/sf/jsqlparser/util/TablesNamesFinderTest.java index 2f9566088..ab63d1f9f 100644 --- a/src/test/java/net/sf/jsqlparser/util/TablesNamesFinderTest.java +++ b/src/test/java/net/sf/jsqlparser/util/TablesNamesFinderTest.java @@ -9,17 +9,6 @@ */ package net.sf.jsqlparser.util; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNull; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.io.BufferedReader; -import java.io.InputStreamReader; -import java.io.StringReader; -import java.util.List; -import java.util.Set; import net.sf.jsqlparser.JSQLParserException; import net.sf.jsqlparser.expression.OracleHint; import net.sf.jsqlparser.parser.CCJSqlParserManager; @@ -36,6 +25,18 @@ import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.io.StringReader; +import java.util.List; +import java.util.Set; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + public class TablesNamesFinderTest { private static final CCJSqlParserManager PARSER_MANAGER = new CCJSqlParserManager(); @@ -522,4 +523,13 @@ void testRefreshMaterializedView() throws JSQLParserException { Set tableNames6 = TablesNamesFinder.findTables(sqlStr6); assertThat(tableNames6).isEmpty(); } + + @Test + void testFromParenthesesJoin() throws JSQLParserException { + String sqlStr = "select * from (t1 left join t2 on t1.id = t2.id) t_select"; + Set tables = TablesNamesFinder.findTables(sqlStr); + assertThat(tables).containsExactly("t1", "t2"); + + } } + From 029fd42e84e65eeebc96fe4b6b4248d6667122f1 Mon Sep 17 00:00:00 2001 From: Tanish Grover Date: Thu, 8 Feb 2024 11:26:41 +0530 Subject: [PATCH 18/31] fix: Fixes parsing failing for ALTER MODIFY queries not containing datatype (#1961) * fix: Fixes parsing failing for ALTER MODIFY queries not containing datatype * fixed spotcheck formatting issues --- .../statement/alter/AlterExpression.java | 3 +- .../create/table/ColumnDefinition.java | 7 +++-- .../net/sf/jsqlparser/parser/JSqlParserCC.jjt | 10 ++++--- .../jsqlparser/statement/alter/AlterTest.java | 28 +++++++++++++++++++ 4 files changed, 40 insertions(+), 8 deletions(-) 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 744d967a4..0a99c97d5 100644 --- a/src/main/java/net/sf/jsqlparser/statement/alter/AlterExpression.java +++ b/src/main/java/net/sf/jsqlparser/statement/alter/AlterExpression.java @@ -773,7 +773,8 @@ public ColumnDataType( @Override public String toString() { - return getColumnName() + (withType ? " TYPE " : " ") + toStringDataTypeAndSpec(); + return getColumnName() + (withType ? " TYPE " : getColDataType() == null ? "" : " ") + + toStringDataTypeAndSpec(); } @Override 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 f19962d8e..90a0e0e40 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 @@ -69,9 +69,10 @@ public String toString() { } public String toStringDataTypeAndSpec() { - return colDataType + (columnSpecs != null && !columnSpecs.isEmpty() - ? " " + PlainSelect.getStringList(columnSpecs, false, false) - : ""); + return (colDataType == null ? "" : colDataType) + + (columnSpecs != null && !columnSpecs.isEmpty() + ? " " + PlainSelect.getStringList(columnSpecs, false, false) + : ""); } public ColumnDefinition withColumnName(String columnName) { diff --git a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt index 40f393769..7c01cb12d 100644 --- a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt +++ b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt @@ -6144,9 +6144,9 @@ AlterExpression.ColumnDataType AlterExpressionColumnDataType(): List parameter = null; } { - columnName = RelObjectName() + columnName = RelObjectName() { columnSpecs = new ArrayList(); } ( LOOKAHEAD(2) { withType = true; } )? - dataType = ColDataType() { columnSpecs = new ArrayList(); } + ( LOOKAHEAD(2) dataType = ColDataType() )? ( parameter = CreateParameter() { columnSpecs.addAll(parameter); } )* { return new AlterExpression.ColumnDataType(columnName, withType, dataType, columnSpecs); @@ -6291,6 +6291,10 @@ AlterExpression AlterExpression(): [ index = IndexWithComment(index) { alterExp.setIndex(index); } ] ) | + LOOKAHEAD(2) ( + sk3=RelObjectName() tk= { alterExp.withColumnName(sk3).withCommentText(tk.image); } + ) + | LOOKAHEAD(3) ( ( LOOKAHEAD(2) { alterExp.hasColumn(true); } )? [ { alterExp.setUseIfNotExists(true); } ] @@ -6433,8 +6437,6 @@ AlterExpression AlterExpression(): ) ) ) - | - ( sk3=RelObjectName() tk= { alterExp.withColumnName(sk3).withCommentText(tk.image); } ) ) ) | 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 86962ae0f..24995bc3e 100644 --- a/src/test/java/net/sf/jsqlparser/statement/alter/AlterTest.java +++ b/src/test/java/net/sf/jsqlparser/statement/alter/AlterTest.java @@ -360,6 +360,34 @@ public void testAlterTableModifyColumn2() throws JSQLParserException { assertFalse(alterExpression.hasColumn()); } + @Test + public void testAlterTableModifyColumn3() throws JSQLParserException { + Alter alter = + (Alter) CCJSqlParserUtil.parse("ALTER TABLE mytable modify col1 NULL"); + AlterExpression alterExpression = alter.getAlterExpressions().get(0); + + // COLUMN keyword DOES NOT appear in deparsed statement, modify becomes all caps + assertStatementCanBeDeparsedAs(alter, "ALTER TABLE mytable MODIFY col1 NULL"); + + assertEquals(AlterOperation.MODIFY, alterExpression.getOperation()); + + assertFalse(alterExpression.hasColumn()); + } + + @Test + public void testAlterTableModifyColumn4() throws JSQLParserException { + Alter alter = + (Alter) CCJSqlParserUtil.parse("ALTER TABLE mytable modify col1 DEFAULT 0"); + AlterExpression alterExpression = alter.getAlterExpressions().get(0); + + // COLUMN keyword DOES NOT appear in deparsed statement, modify becomes all caps + assertStatementCanBeDeparsedAs(alter, "ALTER TABLE mytable MODIFY col1 DEFAULT 0"); + + assertEquals(AlterOperation.MODIFY, alterExpression.getOperation()); + + assertFalse(alterExpression.hasColumn()); + } + @Test public void testAlterTableAlterColumn() throws JSQLParserException { // http://www.postgresqltutorial.com/postgresql-change-column-type/ From 67e220425f241481f8c7f6eb40fddbaa3c35ae56 Mon Sep 17 00:00:00 2001 From: David Goss Date: Tue, 13 Feb 2024 11:59:36 +0000 Subject: [PATCH 19/31] feat: add additional CREATE VIEW modifiers (#1964) * feat: add volatile, alias of temp(orary) * feat: add support for CREATE SECURE VIEW * chore: regenerate reserved keywords --- .../jsqlparser/statement/create/view/CreateView.java | 12 ++++++++++++ .../statement/create/view/TemporaryOption.java | 2 +- .../jsqlparser/util/deparser/CreateViewDeParser.java | 3 +++ .../jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt | 6 +++++- .../jsqlparser/statement/create/CreateViewTest.java | 10 ++++++++++ 5 files changed, 31 insertions(+), 2 deletions(-) diff --git a/src/main/java/net/sf/jsqlparser/statement/create/view/CreateView.java b/src/main/java/net/sf/jsqlparser/statement/create/view/CreateView.java index 8381e50af..034902cca 100644 --- a/src/main/java/net/sf/jsqlparser/statement/create/view/CreateView.java +++ b/src/main/java/net/sf/jsqlparser/statement/create/view/CreateView.java @@ -26,6 +26,7 @@ public class CreateView implements Statement { private ExpressionList columnNames = null; private boolean materialized = false; private ForceOption force = ForceOption.NONE; + private boolean secure = false; private TemporaryOption temp = TemporaryOption.NONE; private AutoRefreshOption autoRefresh = AutoRefreshOption.NONE; private boolean withReadOnly = false; @@ -88,6 +89,14 @@ public void setForce(ForceOption force) { this.force = force; } + public boolean isSecure() { + return secure; + } + + public void setSecure(boolean secure) { + this.secure = secure; + } + public TemporaryOption getTemporary() { return temp; } @@ -127,6 +136,9 @@ public String toString() { sql.append("OR REPLACE "); } appendForceOptionIfApplicable(sql); + if (secure) { + sql.append("SECURE "); + } if (temp != TemporaryOption.NONE) { sql.append(temp.name()).append(" "); diff --git a/src/main/java/net/sf/jsqlparser/statement/create/view/TemporaryOption.java b/src/main/java/net/sf/jsqlparser/statement/create/view/TemporaryOption.java index 4caae850e..c543af966 100644 --- a/src/main/java/net/sf/jsqlparser/statement/create/view/TemporaryOption.java +++ b/src/main/java/net/sf/jsqlparser/statement/create/view/TemporaryOption.java @@ -10,7 +10,7 @@ package net.sf.jsqlparser.statement.create.view; public enum TemporaryOption { - NONE, TEMP, TEMPORARY; + NONE, TEMP, TEMPORARY, VOLATILE; public static TemporaryOption from(String option) { return Enum.valueOf(TemporaryOption.class, option.toUpperCase()); 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 1f5662e68..c0afab53e 100644 --- a/src/main/java/net/sf/jsqlparser/util/deparser/CreateViewDeParser.java +++ b/src/main/java/net/sf/jsqlparser/util/deparser/CreateViewDeParser.java @@ -53,6 +53,9 @@ public void deParse(CreateView createView) { default: // nothing } + if (createView.isSecure()) { + buffer.append("SECURE "); + } if (createView.getTemporary() != TemporaryOption.NONE) { buffer.append(createView.getTemporary().name()).append(" "); } diff --git a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt index 7c01cb12d..2fac2ca2a 100644 --- a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt +++ b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt @@ -391,6 +391,7 @@ TOKEN: /* SQL Keywords. prefixed with K_ to avoid name clashes */ | | | +| | | | @@ -460,6 +461,7 @@ TOKEN: /* SQL Keywords. prefixed with K_ to avoid name clashes */ | | | +| | | | @@ -1837,7 +1839,7 @@ String RelObjectNameWithoutValue() : { Token tk = null; } { ( tk= | tk= | tk= | tk= | tk= | tk= | tk= - | tk="ACTION" | tk="ACTIVE" | tk="ADD" | tk="ADVANCE" | tk="ADVISE" | tk="AGAINST" | tk="ALGORITHM" | tk="ALTER" | tk="ANALYZE" | tk="APPLY" | tk="ARCHIVE" | tk="ARRAY" | tk="ASC" | tk="AT" | tk="AUTHORIZATION" | tk="AUTO" | tk="BEGIN" | tk="BERNOULLI" | tk="BINARY" | tk="BIT" | tk="BLOCK" | tk="BROWSE" | tk="BUFFERS" | tk="BY" | tk="BYTE" | tk="BYTES" | tk="CACHE" | tk="CALL" | tk="CASCADE" | tk="CASE" | tk="CAST" | tk="CHANGE" | tk="CHANGES" | tk="CHAR" | tk="CHARACTER" | tk="CHECKPOINT" | tk="CLOSE" | tk="COLLATE" | tk="COLUMN" | tk="COLUMNS" | tk="COMMENT" | tk="COMMIT" | tk="CONCURRENTLY" | tk="CONFLICT" | tk="CONSTRAINTS" | tk="CONVERT" | tk="COSTS" | tk="CS" | tk="CYCLE" | tk="DATA" | tk="DATABASE" | tk="DDL" | tk="DECLARE" | tk="DEFAULT" | tk="DEFERRABLE" | tk="DELAYED" | tk="DELETE" | tk="DESC" | tk="DESCRIBE" | tk="DISABLE" | tk="DISCONNECT" | tk="DIV" | tk="DML" | tk="DO" | tk="DOMAIN" | tk="DROP" | tk="DUMP" | tk="DUPLICATE" | tk="ELEMENTS" | tk="EMIT" | tk="ENABLE" | tk="END" | tk="ESCAPE" | tk="EXCLUDE" | tk="EXEC" | tk="EXECUTE" | tk="EXPLAIN" | tk="EXPLICIT" | tk="EXTENDED" | tk="EXTRACT" | tk="FALSE" | tk="FILTER" | tk="FIRST" | tk="FLUSH" | tk="FN" | tk="FOLLOWING" | tk="FORMAT" | tk="FULLTEXT" | tk="FUNCTION" | tk="GRANT" | tk="GUARD" | tk="HASH" | tk="HISTORY" | tk="HOPPING" | tk="INCLUDE" | tk="INCREMENT" | tk="INDEX" | tk="INSERT" | tk="INTERLEAVE" | tk="INTERPRET" | tk="INVALIDATE" | tk="ISNULL" | tk="JSON" | tk="KEEP" | tk="KEY" | tk="KEYS" | tk="LAST" | tk="LEADING" | tk="LINK" | tk="LOCAL" | tk="LOCKED" | tk="LOG" | tk="LOOP" | tk="MATCH" | tk="MATCHED" | tk="MATERIALIZED" | tk="MAXVALUE" | tk="MEMBER" | tk="MERGE" | tk="MINVALUE" | tk="MODIFY" | tk="MOVEMENT" | tk="NEXT" | tk="NO" | tk="NOCACHE" | tk="NOKEEP" | tk="NOLOCK" | tk="NOMAXVALUE" | tk="NOMINVALUE" | tk="NOORDER" | tk="NOTHING" | tk="NOTNULL" | tk="NOVALIDATE" | tk="NOWAIT" | tk="NULLS" | tk="OF" | tk="OFF" | tk="OPEN" | tk="OVER" | tk="OVERLAPS" | tk="PARALLEL" | tk="PARENT" | tk="PARTITION" | tk="PATH" | tk="PERCENT" | tk="PLACING" | tk="PRECEDING" | tk="PRECISION" | tk="PRIMARY" | tk="PRIOR" | tk="PURGE" | tk="QUERY" | tk="QUICK" | tk="QUIESCE" | tk="RANGE" | tk="RAW" | tk="READ" | tk="RECYCLEBIN" | tk="REFERENCES" | tk="REFRESH" | tk="REGISTER" | tk="REMOTE" | tk="RENAME" | tk="REPEATABLE" | tk="REPLACE" | tk="RESET" | tk="RESTART" | tk="RESTRICT" | tk="RESTRICTED" | tk="RESUMABLE" | tk="RESUME" | tk="RETURN" | tk="RLIKE" | tk="ROLLBACK" | tk="ROLLUP" | tk="ROOT" | tk="ROW" | tk="ROWS" | tk="RR" | tk="RS" | tk="SAVEPOINT" | tk="SCHEMA" | tk="SEED" | tk="SEPARATOR" | tk="SEQUENCE" | tk="SESSION" | tk="SETS" | tk="SHARE" | tk="SHOW" | tk="SHUTDOWN" | tk="SIBLINGS" | tk="SIGNED" | tk="SIMILAR" | tk="SIZE" | tk="SKIP" | tk="STORED" | tk="STRING" | tk="SUSPEND" | tk="SWITCH" | tk="SYNONYM" | tk="SYSTEM" | tk="TABLE" | tk="TABLESPACE" | tk="TEMP" | tk="TEMPORARY" | tk="THEN" | tk="TIMEOUT" | tk="TIMESTAMPTZ" | tk="TO" | tk="TRIGGER" | tk="TRUE" | tk="TRUNCATE" | tk="TUMBLING" | tk="TYPE" | tk="UNLOGGED" | tk="UNQIESCE" | tk="UNSIGNED" | tk="UPDATE" | tk="UPSERT" | tk="UR" | tk="USER" | tk="VALIDATE" | tk="VERBOSE" | tk="VIEW" | tk="WAIT" | tk="WITHIN" | tk="WITHOUT" | tk="WORK" | tk="XML" | tk="XMLAGG" | tk="XMLDATA" | tk="XMLSCHEMA" | tk="XMLTEXT" | tk="XSINIL" | tk="YAML" | tk="YES" | tk="ZONE" ) + | tk="ACTION" | tk="ACTIVE" | tk="ADD" | tk="ADVANCE" | tk="ADVISE" | tk="AGAINST" | tk="ALGORITHM" | tk="ALTER" | tk="ANALYZE" | tk="APPLY" | tk="ARCHIVE" | tk="ARRAY" | tk="ASC" | tk="AT" | tk="AUTHORIZATION" | tk="AUTO" | tk="BEGIN" | tk="BERNOULLI" | tk="BINARY" | tk="BIT" | tk="BLOCK" | tk="BROWSE" | tk="BUFFERS" | tk="BY" | tk="BYTE" | tk="BYTES" | tk="CACHE" | tk="CALL" | tk="CASCADE" | tk="CASE" | tk="CAST" | tk="CHANGE" | tk="CHANGES" | tk="CHAR" | tk="CHARACTER" | tk="CHECKPOINT" | tk="CLOSE" | tk="COLLATE" | tk="COLUMN" | tk="COLUMNS" | tk="COMMENT" | tk="COMMIT" | tk="CONCURRENTLY" | tk="CONFLICT" | tk="CONSTRAINTS" | tk="CONVERT" | tk="COSTS" | tk="CS" | tk="CYCLE" | tk="DATA" | tk="DATABASE" | tk="DDL" | tk="DECLARE" | tk="DEFAULT" | tk="DEFERRABLE" | tk="DELAYED" | tk="DELETE" | tk="DESC" | tk="DESCRIBE" | tk="DISABLE" | tk="DISCONNECT" | tk="DIV" | tk="DML" | tk="DO" | tk="DOMAIN" | tk="DROP" | tk="DUMP" | tk="DUPLICATE" | tk="ELEMENTS" | tk="EMIT" | tk="ENABLE" | tk="END" | tk="ESCAPE" | tk="EXCLUDE" | tk="EXEC" | tk="EXECUTE" | tk="EXPLAIN" | tk="EXPLICIT" | tk="EXTENDED" | tk="EXTRACT" | tk="FALSE" | tk="FILTER" | tk="FIRST" | tk="FLUSH" | tk="FN" | tk="FOLLOWING" | tk="FORMAT" | tk="FULLTEXT" | tk="FUNCTION" | tk="GRANT" | tk="GUARD" | tk="HASH" | tk="HISTORY" | tk="HOPPING" | tk="INCLUDE" | tk="INCREMENT" | tk="INDEX" | tk="INSERT" | tk="INTERLEAVE" | tk="INTERPRET" | tk="INVALIDATE" | tk="ISNULL" | tk="JSON" | tk="KEEP" | tk="KEY" | tk="KEYS" | tk="LAST" | tk="LEADING" | tk="LINK" | tk="LOCAL" | tk="LOCKED" | tk="LOG" | tk="LOOP" | tk="MATCH" | tk="MATCHED" | tk="MATERIALIZED" | tk="MAXVALUE" | tk="MEMBER" | tk="MERGE" | tk="MINVALUE" | tk="MODIFY" | tk="MOVEMENT" | tk="NEXT" | tk="NO" | tk="NOCACHE" | tk="NOKEEP" | tk="NOLOCK" | tk="NOMAXVALUE" | tk="NOMINVALUE" | tk="NOORDER" | tk="NOTHING" | tk="NOTNULL" | tk="NOVALIDATE" | tk="NOWAIT" | tk="NULLS" | tk="OF" | tk="OFF" | tk="OPEN" | tk="OVER" | tk="OVERLAPS" | tk="PARALLEL" | tk="PARENT" | tk="PARTITION" | tk="PATH" | tk="PERCENT" | tk="PLACING" | tk="PRECEDING" | tk="PRECISION" | tk="PRIMARY" | tk="PRIOR" | tk="PURGE" | tk="QUERY" | tk="QUICK" | tk="QUIESCE" | tk="RANGE" | tk="RAW" | tk="READ" | tk="RECYCLEBIN" | tk="REFERENCES" | tk="REFRESH" | tk="REGISTER" | tk="REMOTE" | tk="RENAME" | tk="REPEATABLE" | tk="REPLACE" | tk="RESET" | tk="RESTART" | tk="RESTRICT" | tk="RESTRICTED" | tk="RESUMABLE" | tk="RESUME" | tk="RETURN" | tk="RLIKE" | tk="ROLLBACK" | tk="ROLLUP" | tk="ROOT" | tk="ROW" | tk="ROWS" | tk="RR" | tk="RS" | tk="SAVEPOINT" | tk="SCHEMA" | tk="SECURE" | tk="SEED" | tk="SEPARATOR" | tk="SEQUENCE" | tk="SESSION" | tk="SETS" | tk="SHARE" | tk="SHOW" | tk="SHUTDOWN" | tk="SIBLINGS" | tk="SIGNED" | tk="SIMILAR" | tk="SIZE" | tk="SKIP" | tk="STORED" | tk="STRING" | tk="SUSPEND" | tk="SWITCH" | tk="SYNONYM" | tk="SYSTEM" | tk="TABLE" | tk="TABLESPACE" | tk="TEMP" | tk="TEMPORARY" | tk="THEN" | tk="TIMEOUT" | tk="TIMESTAMPTZ" | tk="TO" | tk="TRIGGER" | tk="TRUE" | tk="TRUNCATE" | tk="TUMBLING" | tk="TYPE" | tk="UNLOGGED" | tk="UNQIESCE" | tk="UNSIGNED" | tk="UPDATE" | tk="UPSERT" | tk="UR" | tk="USER" | tk="VALIDATE" | tk="VERBOSE" | tk="VIEW" | tk="VOLATILE" | tk="WAIT" | tk="WITHIN" | tk="WITHOUT" | tk="WORK" | tk="XML" | tk="XMLAGG" | tk="XMLDATA" | tk="XMLSCHEMA" | tk="XMLTEXT" | tk="XSINIL" | tk="YAML" | tk="YES" | tk="ZONE" ) { return tk.image; } } @@ -5811,9 +5813,11 @@ CreateView CreateView(boolean isUsingOrReplace): { createView.setForce(ForceOption.NO_FORCE); } | { createView.setForce(ForceOption.FORCE); } ] + [ { createView.setSecure(true);} ] [ { createView.setTemporary(TemporaryOption.TEMP); } | { createView.setTemporary(TemporaryOption.TEMPORARY); } + | { createView.setTemporary(TemporaryOption.VOLATILE); } ] [ { createView.setMaterialized(true);} ] view=Table() { createView.setView(view); } diff --git a/src/test/java/net/sf/jsqlparser/statement/create/CreateViewTest.java b/src/test/java/net/sf/jsqlparser/statement/create/CreateViewTest.java index 240a22fc3..328b8ecca 100644 --- a/src/test/java/net/sf/jsqlparser/statement/create/CreateViewTest.java +++ b/src/test/java/net/sf/jsqlparser/statement/create/CreateViewTest.java @@ -119,6 +119,16 @@ public void testCreateForceView3() throws JSQLParserException { "CREATE OR REPLACE NO FORCE VIEW view1 AS SELECT a, b FROM testtab"); } + @Test + public void testCreateSecureView() throws JSQLParserException { + assertSqlCanBeParsedAndDeparsed("CREATE SECURE VIEW myview AS SELECT * FROM mytable"); + } + + @Test + public void testCreateVolatileView() throws JSQLParserException { + assertSqlCanBeParsedAndDeparsed("CREATE VOLATILE VIEW myview AS SELECT * FROM mytable"); + } + @Test public void testCreateTemporaryViewIssue604() throws JSQLParserException { assertSqlCanBeParsedAndDeparsed("CREATE TEMPORARY VIEW myview AS SELECT * FROM mytable"); From b00322efa0c77d215cdbf9cd819a489c9403b2fc Mon Sep 17 00:00:00 2001 From: Heewon Lee <94441510+pingpingy1@users.noreply.github.com> Date: Wed, 14 Feb 2024 16:34:40 +0900 Subject: [PATCH 20/31] 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 --- .../sf/jsqlparser/expression/DoubleValue.java | 3 ++ .../sf/jsqlparser/expression/LongValue.java | 3 ++ .../sf/jsqlparser/expression/TimeValue.java | 3 ++ .../expression/DoubleValueTest.java | 31 +++++++++++++++++++ .../jsqlparser/expression/LongValueTest.java | 17 +++++++++- .../jsqlparser/expression/TimeValueTest.java | 31 +++++++++++++++++++ 6 files changed, 87 insertions(+), 1 deletion(-) create mode 100644 src/test/java/net/sf/jsqlparser/expression/DoubleValueTest.java create mode 100644 src/test/java/net/sf/jsqlparser/expression/TimeValueTest.java diff --git a/src/main/java/net/sf/jsqlparser/expression/DoubleValue.java b/src/main/java/net/sf/jsqlparser/expression/DoubleValue.java index 43e072dcf..910ad722e 100644 --- a/src/main/java/net/sf/jsqlparser/expression/DoubleValue.java +++ b/src/main/java/net/sf/jsqlparser/expression/DoubleValue.java @@ -24,6 +24,9 @@ public DoubleValue() { } public DoubleValue(final String value) { + if (value == null || value.length() == 0) { + throw new IllegalArgumentException("value can neither be null nor empty."); + } String val = value; if (val.charAt(0) == '+') { val = val.substring(1); diff --git a/src/main/java/net/sf/jsqlparser/expression/LongValue.java b/src/main/java/net/sf/jsqlparser/expression/LongValue.java index 014802fb5..730a8871e 100644 --- a/src/main/java/net/sf/jsqlparser/expression/LongValue.java +++ b/src/main/java/net/sf/jsqlparser/expression/LongValue.java @@ -26,6 +26,9 @@ public LongValue() { } public LongValue(final String value) { + if (value == null || value.length() == 0) { + throw new IllegalArgumentException("value can neither be null nor empty."); + } String val = value; if (val.charAt(0) == '+') { val = val.substring(1); diff --git a/src/main/java/net/sf/jsqlparser/expression/TimeValue.java b/src/main/java/net/sf/jsqlparser/expression/TimeValue.java index 100945147..f87970fa1 100644 --- a/src/main/java/net/sf/jsqlparser/expression/TimeValue.java +++ b/src/main/java/net/sf/jsqlparser/expression/TimeValue.java @@ -25,6 +25,9 @@ public TimeValue() { } public TimeValue(String value) { + if (value == null || value.length() == 0) { + throw new IllegalArgumentException("value can neither be null nor empty."); + } this.value = Time.valueOf(value.substring(1, value.length() - 1)); } diff --git a/src/test/java/net/sf/jsqlparser/expression/DoubleValueTest.java b/src/test/java/net/sf/jsqlparser/expression/DoubleValueTest.java new file mode 100644 index 000000000..1ef676c6d --- /dev/null +++ b/src/test/java/net/sf/jsqlparser/expression/DoubleValueTest.java @@ -0,0 +1,31 @@ +/*- + * #%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 org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertThrows; + +public class DoubleValueTest { + + @Test + public void testNullValue() { + assertThrows(IllegalArgumentException.class, () -> { + new DoubleValue(null); + }); + } + + @Test + public void testEmptyValue() { + assertThrows(IllegalArgumentException.class, () -> { + new DoubleValue(""); + }); + } +} diff --git a/src/test/java/net/sf/jsqlparser/expression/LongValueTest.java b/src/test/java/net/sf/jsqlparser/expression/LongValueTest.java index 91905614f..ab8119392 100644 --- a/src/test/java/net/sf/jsqlparser/expression/LongValueTest.java +++ b/src/test/java/net/sf/jsqlparser/expression/LongValueTest.java @@ -11,6 +11,7 @@ import java.math.BigInteger; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.fail; import org.junit.jupiter.api.Test; @@ -39,8 +40,22 @@ public void testLargeNumber() { value.getValue(); fail("should not work"); } catch (Exception e) { - //expected to fail + // expected to fail } assertEquals(new BigInteger(largeNumber), value.getBigIntegerValue()); } + + @Test + public void testNullStringValue() { + assertThrows(IllegalArgumentException.class, () -> { + new LongValue((String) null); + }); + } + + @Test + public void testEmptyStringValue() { + assertThrows(IllegalArgumentException.class, () -> { + new LongValue(""); + }); + } } diff --git a/src/test/java/net/sf/jsqlparser/expression/TimeValueTest.java b/src/test/java/net/sf/jsqlparser/expression/TimeValueTest.java new file mode 100644 index 000000000..c931dbcd8 --- /dev/null +++ b/src/test/java/net/sf/jsqlparser/expression/TimeValueTest.java @@ -0,0 +1,31 @@ +/*- + * #%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 org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertThrows; + +public class TimeValueTest { + + @Test + public void testNullValue() { + assertThrows(IllegalArgumentException.class, () -> { + new TimeValue(null); + }); + } + + @Test + public void testEmptyValue() { + assertThrows(IllegalArgumentException.class, () -> { + new TimeValue(""); + }); + } +} From 768c63f4660509b1807d1e158ad506a421533583 Mon Sep 17 00:00:00 2001 From: David Goss Date: Wed, 14 Feb 2024 21:11:42 +0000 Subject: [PATCH 21/31] fix: make analytic expression visitor null-safe (#1944) --- .../expression/ExpressionVisitorAdapter.java | 18 ++++++++++++++---- .../ExpressionVisitorAdapterTest.java | 9 +++++++++ 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/src/main/java/net/sf/jsqlparser/expression/ExpressionVisitorAdapter.java b/src/main/java/net/sf/jsqlparser/expression/ExpressionVisitorAdapter.java index a93efedb5..fc6c323c8 100644 --- a/src/main/java/net/sf/jsqlparser/expression/ExpressionVisitorAdapter.java +++ b/src/main/java/net/sf/jsqlparser/expression/ExpressionVisitorAdapter.java @@ -64,6 +64,8 @@ import net.sf.jsqlparser.statement.select.UnPivot; import net.sf.jsqlparser.statement.select.WithItem; +import java.util.Optional; + @SuppressWarnings({"PMD.CyclomaticComplexity", "PMD.UncommentedEmptyMethodBody"}) public class ExpressionVisitorAdapter implements ExpressionVisitor, PivotVisitor, SelectItemVisitor { @@ -382,11 +384,19 @@ public void visit(AnalyticExpression expr) { element.getExpression().accept(this); } } - if (expr.getWindowElement() != null) { - expr.getWindowElement().getRange().getStart().getExpression().accept(this); - expr.getWindowElement().getRange().getEnd().getExpression().accept(this); - expr.getWindowElement().getOffset().getExpression().accept(this); + /* + * 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.: + * expr.getWindowElement().getOffset().getExpression().accept(this); + */ + Optional.ofNullable(expr.getWindowElement().getRange()).map(WindowRange::getStart) + .map(WindowOffset::getExpression).ifPresent(e -> e.accept(this)); + Optional.ofNullable(expr.getWindowElement().getRange()).map(WindowRange::getEnd) + .map(WindowOffset::getExpression).ifPresent(e -> e.accept(this)); + Optional.ofNullable(expr.getWindowElement().getOffset()) + .map(WindowOffset::getExpression).ifPresent(e -> e.accept(this)); } } diff --git a/src/test/java/net/sf/jsqlparser/expression/ExpressionVisitorAdapterTest.java b/src/test/java/net/sf/jsqlparser/expression/ExpressionVisitorAdapterTest.java index 52e71b8bf..14ae67fed 100644 --- a/src/test/java/net/sf/jsqlparser/expression/ExpressionVisitorAdapterTest.java +++ b/src/test/java/net/sf/jsqlparser/expression/ExpressionVisitorAdapterTest.java @@ -259,4 +259,13 @@ public void visit(AllTableColumns all) { assertNotNull(holder[0]); assertEquals("a.*", holder[0].toString()); } + + @Test + public void testAnalyticExpressionWithPartialWindowElement() throws JSQLParserException { + ExpressionVisitorAdapter adapter = new ExpressionVisitorAdapter(); + Expression expression = CCJSqlParserUtil.parseExpression( + "SUM(\"Spent\") OVER (PARTITION BY \"ID\" ORDER BY \"Name\" ASC ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)"); + + expression.accept(adapter); + } } From 8dcfb4a3bf5682dc8e79d0c4f78ce9a24908f245 Mon Sep 17 00:00:00 2001 From: manticore-projects Date: Sat, 17 Feb 2024 19:03:43 +0700 Subject: [PATCH 22/31] Update README.md Fixes #1968 --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 5a011d6a6..b223c27fa 100644 --- a/README.md +++ b/README.md @@ -64,6 +64,8 @@ Assertions.assertEquals("b", b.getColumnName()); ## Alternatives to JSqlParser? [**General SQL Parser**](http://www.sqlparser.com/features/introduce.php?utm_source=github-jsqlparser&utm_medium=text-general) looks pretty good, with extended SQL syntax (like PL/SQL and T-SQL) and java + .NET APIs. The tool is commercial (license available online), with a free download option. +Alternatively the dual-licensed [JOOQ](https://www.jooq.org/doc/latest/manual/sql-building/sql-parser/) provides a hand-written Parser supporting a lot of RDBMS, translation between dialects, SQL transformation, can be used as a JDBC proxy for translation and transformation purposes. + ## [Documentation](https://jsqlparser.github.io/JSqlParser) 1. [Samples](https://jsqlparser.github.io/JSqlParser/usage.html#parse-a-sql-statements) 2. [Build Instructions](https://jsqlparser.github.io/JSqlParser/usage.html) and [Maven Artifact](https://jsqlparser.github.io/JSqlParser/usage.html#build-dependencies) From 72a51e58413a291c6aaa4d393f4500bba8efd6df Mon Sep 17 00:00:00 2001 From: Andreas Reichel Date: Fri, 23 Feb 2024 07:02:35 +0700 Subject: [PATCH 23/31] fix: allow `DATA` as `ColumnType()` keyword - fixes #1969 Signed-off-by: Andreas Reichel --- src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt | 2 ++ .../net/sf/jsqlparser/expression/CastExpressionTest.java | 6 ++++++ 2 files changed, 8 insertions(+) diff --git a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt index 2fac2ca2a..5fc9ee140 100644 --- a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt +++ b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt @@ -4361,6 +4361,7 @@ Expression PrimaryExpression() #PrimaryExpression: if (not) { retval = new NotExpression(retval, exclamationMarkNot); } + linkAST(retval, jjtThis); return retval; } } @@ -5743,6 +5744,7 @@ ColDataType ColDataType(): | tk= | tk= | tk= + | tk= ) { schema = tk.image; } [ "." type = RelObjectName() { schema += "." + type; } ] { colDataType.setDataType(schema); } diff --git a/src/test/java/net/sf/jsqlparser/expression/CastExpressionTest.java b/src/test/java/net/sf/jsqlparser/expression/CastExpressionTest.java index 8f1894663..da049cdad 100644 --- a/src/test/java/net/sf/jsqlparser/expression/CastExpressionTest.java +++ b/src/test/java/net/sf/jsqlparser/expression/CastExpressionTest.java @@ -24,4 +24,10 @@ public void testCastToRowConstructorIssue1267() throws JSQLParserException { TestUtils.assertExpressionCanBeParsedAndDeparsed("CAST(ROW(dataid, value, calcMark) AS ROW(datapointid CHAR, value CHAR, calcMark CHAR))", true); TestUtils.assertExpressionCanBeParsedAndDeparsed("CAST(ROW(dataid, value, calcMark) AS testcol)", true); } + + @Test + void testDataKeywordIssue1969() throws Exception { + String sqlStr = "SELECT * FROM myschema.myfunction('test'::data.text_not_null)"; + TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true); + } } From 17f5f2ad680dfdb6d706bfc268df94c5a7ee3906 Mon Sep 17 00:00:00 2001 From: Andreas Reichel Date: Fri, 23 Feb 2024 08:32:34 +0700 Subject: [PATCH 24/31] fix: allow Parameters like `$1`,`$2` - fixes #1970 Signed-off-by: Andreas Reichel --- .../jsqlparser/expression/JdbcParameter.java | 31 ++++++++++++-- .../util/deparser/ExpressionDeParser.java | 2 +- .../net/sf/jsqlparser/parser/JSqlParserCC.jjt | 41 ++++++++----------- .../expression/JdbcNamedParameterTest.java | 14 +++++++ 4 files changed, 60 insertions(+), 28 deletions(-) diff --git a/src/main/java/net/sf/jsqlparser/expression/JdbcParameter.java b/src/main/java/net/sf/jsqlparser/expression/JdbcParameter.java index a03d230ff..aaedebc68 100644 --- a/src/main/java/net/sf/jsqlparser/expression/JdbcParameter.java +++ b/src/main/java/net/sf/jsqlparser/expression/JdbcParameter.java @@ -11,20 +11,43 @@ import net.sf.jsqlparser.parser.ASTNodeAccessImpl; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + /** * A '?' in a statement or a ?<number> e.g. ?4 */ public class JdbcParameter extends ASTNodeAccessImpl implements Expression { + private String parameterCharacter = "?"; private Integer index; private boolean useFixedIndex = false; - public JdbcParameter() { - } + public JdbcParameter() {} - public JdbcParameter(Integer index, boolean useFixedIndex) { + public JdbcParameter(Integer index, boolean useFixedIndex, String parameterCharacter) { this.index = index; this.useFixedIndex = useFixedIndex; + this.parameterCharacter = parameterCharacter; + + // This is needed for Parameters starting with "$" like "$2" + // Those will contain the index in the parameterCharacter + final Pattern pattern = Pattern.compile("(\\$)(\\d*)"); + final Matcher matcher = pattern.matcher(parameterCharacter); + if (matcher.find() && matcher.groupCount() == 2) { + this.useFixedIndex = true; + this.parameterCharacter = matcher.group(1); + this.index = Integer.valueOf(matcher.group(2)); + } + } + + public String getParameterCharacter() { + return parameterCharacter; + } + + public JdbcParameter setParameterCharacter(String parameterCharacter) { + this.parameterCharacter = parameterCharacter; + return this; } public Integer getIndex() { @@ -50,7 +73,7 @@ public void accept(ExpressionVisitor expressionVisitor) { @Override public String toString() { - return useFixedIndex ? "?" + index : "?"; + return useFixedIndex ? parameterCharacter + index : parameterCharacter; } public JdbcParameter withIndex(Integer index) { 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 494444348..ad82ac582 100644 --- a/src/main/java/net/sf/jsqlparser/util/deparser/ExpressionDeParser.java +++ b/src/main/java/net/sf/jsqlparser/util/deparser/ExpressionDeParser.java @@ -328,7 +328,7 @@ public void visit(IsBooleanExpression isBooleanExpression) { @Override public void visit(JdbcParameter jdbcParameter) { - buffer.append("?"); + buffer.append(jdbcParameter.getParameterCharacter()); if (jdbcParameter.isUseFixedIndex()) { buffer.append(jdbcParameter.getIndex()); } diff --git a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt index 5fc9ee140..47a8c8937 100644 --- a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt +++ b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt @@ -530,6 +530,8 @@ SPECIAL_TOKEN: TOKEN: { + +| ()*> | <#LETTER: | | [ "$" , "#", "_" ] // Not SQL:2016 compliant! @@ -3126,27 +3128,20 @@ OrderByElement OrderByElement(): } } -JdbcParameter SimpleJdbcParameter() : { +JdbcParameter JdbcParameter() : { + Token tk; JdbcParameter retval; } { - "?" { retval = new JdbcParameter(++jdbcParameterIndex, false); } - [ LOOKAHEAD(2) token = { retval.setUseFixedIndex(true); retval.setIndex(Integer.valueOf(token.image)); } ] - { - return retval; - } -} + ( tk="?" | tk= ) + { retval = new JdbcParameter(++jdbcParameterIndex, false, tk.image); } -JdbcNamedParameter SimpleJdbcNamedParameter() : { - String name; -} -{ - ":" name = RelObjectNameExt() - { - return new JdbcNamedParameter(token.image); - } + [ LOOKAHEAD(2) token = { retval.setUseFixedIndex(true); retval.setIndex(Integer.valueOf(token.image)); } ] + + { return retval; } } + Limit LimitWithOffset() #LimitWithOffset: { Limit limit = new Limit(); @@ -3323,7 +3318,7 @@ Top Top(): ( token= { top.setExpression(new LongValue(token.image)); } | - jdbc = SimpleJdbcParameter() { top.setExpression(jdbc); } + jdbc = JdbcParameter() { top.setExpression(jdbc); } /*"?" { top.setExpression(new JdbcParameter(++jdbcParameterIndex, false)); } [ LOOKAHEAD(2) token = { ((JdbcParameter)(top.getExpression())).setUseFixedIndex(true); ((JdbcParameter)(top.getExpression())).setIndex(Integer.valueOf(token.image)); } ]*/ | ":" { top.setExpression(new JdbcNamedParameter()); } [ LOOKAHEAD(2) token = { ((JdbcNamedParameter)top.getExpression()).setName(token.image); } ] @@ -3354,7 +3349,7 @@ Skip Skip(): ( token= { skip.setRowCount(Long.parseLong(token.image)); } | token= { skip.setVariable(token.image); } - | jdbc = SimpleJdbcParameter() { skip.setJdbcParameter(jdbc); } + | jdbc = JdbcParameter() { skip.setJdbcParameter(jdbc); } /* "?" { skip.setJdbcParameter(new JdbcParameter(++jdbcParameterIndex, false)); } [ LOOKAHEAD(2) token = { skip.getJdbcParameter().setUseFixedIndex(true); skip.getJdbcParameter().setIndex(Integer.valueOf(token.image)); } ] */ ) { @@ -3396,7 +3391,7 @@ First First(): | token= { first.setVariable(token.image); } | - jdbc = SimpleJdbcParameter() { first.setJdbcParameter(jdbc); } + jdbc = JdbcParameter() { first.setJdbcParameter(jdbc); } ) { return first; @@ -4235,9 +4230,9 @@ Expression PrimaryExpression() #PrimaryExpression: | LOOKAHEAD(3, {!interrupted}) retval=CaseWhenExpression() - | LOOKAHEAD(3) retval = SimpleJdbcParameter() + | retval = JdbcParameter() - | LOOKAHEAD(2) retval=JdbcNamedParameter() + | LOOKAHEAD(2) retval =JdbcNamedParameter() | LOOKAHEAD(3) retval=UserVariable() @@ -4758,7 +4753,7 @@ IntervalExpression IntervalExpression() : { { { interval = new IntervalExpression(); } - ["-" {signed=true;}] (token= | token= | token= | LOOKAHEAD(SimpleJdbcParameter()) expr = SimpleJdbcParameter() | expr = JdbcNamedParameter() | LOOKAHEAD(Function()) expr = Function() | expr = Column()) + ["-" {signed=true;}] (token= | token= | token= | LOOKAHEAD(JdbcParameter()) expr = JdbcParameter() | expr = JdbcNamedParameter() | LOOKAHEAD(Function()) expr = Function() | expr = Column()) { if (expr != null) { if (signed) expr = new SignedExpression('-', expr); @@ -5099,9 +5094,9 @@ FullTextSearch FullTextSearch() : { ( againstValue= { fs.setAgainstValue(new StringValue(againstValue.image)); } | - jdbcParameter=SimpleJdbcParameter() { fs.setAgainstValue( jdbcParameter ); } + jdbcParameter=JdbcParameter() { fs.setAgainstValue( jdbcParameter ); } | - jdbcNamedParameter=SimpleJdbcNamedParameter() { fs.setAgainstValue( jdbcNamedParameter ); } + jdbcNamedParameter=JdbcNamedParameter() { fs.setAgainstValue( jdbcNamedParameter ); } ) [ ( diff --git a/src/test/java/net/sf/jsqlparser/expression/JdbcNamedParameterTest.java b/src/test/java/net/sf/jsqlparser/expression/JdbcNamedParameterTest.java index f2a4bfe70..2f7c46db7 100644 --- a/src/test/java/net/sf/jsqlparser/expression/JdbcNamedParameterTest.java +++ b/src/test/java/net/sf/jsqlparser/expression/JdbcNamedParameterTest.java @@ -11,8 +11,10 @@ import net.sf.jsqlparser.JSQLParserException; import net.sf.jsqlparser.expression.operators.arithmetic.BitwiseAnd; +import net.sf.jsqlparser.expression.operators.relational.EqualsTo; import net.sf.jsqlparser.statement.select.PlainSelect; import net.sf.jsqlparser.test.TestUtils; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -39,4 +41,16 @@ void testIssue1785() throws JSQLParserException { + "where owner = &myowner"; TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true); } + + @Test + void testIssue1970() throws JSQLParserException { + String sqlStr = "SELECT a from tbl where col = $2"; + PlainSelect select = (PlainSelect) TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true); + + EqualsTo where = (EqualsTo) select.getWhere(); + Assertions.assertInstanceOf(JdbcParameter.class, where.getRightExpression()); + + JdbcParameter p = (JdbcParameter) where.getRightExpression(); + Assertions.assertEquals(2, p.getIndex()); + } } From 94fb87237f36cce7f993496c0e0eeadd00d8cab7 Mon Sep 17 00:00:00 2001 From: Andreas Reichel Date: Fri, 23 Feb 2024 08:47:04 +0700 Subject: [PATCH 25/31] fix: return NULL when parsing empty Strings - fixes #1963 Signed-off-by: Andreas Reichel --- .../jsqlparser/parser/CCJSqlParserUtil.java | 54 +++++++++++++++++-- .../parser/CCJSqlParserUtilTest.java | 7 +++ 2 files changed, 57 insertions(+), 4 deletions(-) diff --git a/src/main/java/net/sf/jsqlparser/parser/CCJSqlParserUtil.java b/src/main/java/net/sf/jsqlparser/parser/CCJSqlParserUtil.java index 5ea6af47f..eb6e8b2ce 100644 --- a/src/main/java/net/sf/jsqlparser/parser/CCJSqlParserUtil.java +++ b/src/main/java/net/sf/jsqlparser/parser/CCJSqlParserUtil.java @@ -79,6 +79,10 @@ public static Statement parse(String sql) throws JSQLParserException { public static Statement parse(String sql, Consumer consumer) throws JSQLParserException { + if (sql == null || sql.isEmpty()) { + return null; + } + ExecutorService executorService = Executors.newSingleThreadExecutor(); Statement statement = null; try { @@ -92,8 +96,11 @@ public static Statement parse(String sql, Consumer consumer) public static Statement parse(String sql, ExecutorService executorService, Consumer consumer) throws JSQLParserException { - Statement statement = null; + if (sql == null || sql.isEmpty()) { + return null; + } + Statement statement = null; // first, try to parse fast and simple CCJSqlParser parser = newParser(sql); if (consumer != null) { @@ -122,6 +129,10 @@ public static Statement parse(String sql, ExecutorService executorService, } public static CCJSqlParser newParser(String sql) { + if (sql == null || sql.isEmpty()) { + return null; + } + return new CCJSqlParser(new StringProvider(sql)); } @@ -134,6 +145,10 @@ public static CCJSqlParser newParser(InputStream is, String encoding) throws IOE } public static Node parseAST(String sql) throws JSQLParserException { + if (sql == null || sql.isEmpty()) { + return null; + } + CCJSqlParser parser = newParser(sql); try { parser.Statement(); @@ -162,11 +177,19 @@ public static Statement parse(InputStream is, String encoding) throws JSQLParser } public static Expression parseExpression(String expression) throws JSQLParserException { + if (expression == null || expression.isEmpty()) { + return null; + } + return parseExpression(expression, true); } public static Expression parseExpression(String expression, boolean allowPartialParse) throws JSQLParserException { + if (expression == null || expression.isEmpty()) { + return null; + } + return parseExpression(expression, allowPartialParse, p -> { }); } @@ -174,8 +197,11 @@ public static Expression parseExpression(String expression, boolean allowPartial @SuppressWarnings("PMD.CyclomaticComplexity") public static Expression parseExpression(String expressionStr, boolean allowPartialParse, Consumer consumer) throws JSQLParserException { - Expression expression = null; + if (expressionStr == null || expressionStr.isEmpty()) { + return null; + } + Expression expression = null; // first, try to parse fast and simple try { CCJSqlParser parser = newParser(expressionStr).withAllowComplexParsing(false); @@ -225,6 +251,9 @@ public static Expression parseExpression(String expressionStr, boolean allowPart * @see #parseCondExpression(String, boolean) */ public static Expression parseCondExpression(String condExpr) throws JSQLParserException { + if (condExpr == null || condExpr.isEmpty()) { + return null; + } return parseCondExpression(condExpr, true); } @@ -238,6 +267,9 @@ public static Expression parseCondExpression(String condExpr) throws JSQLParserE */ public static Expression parseCondExpression(String condExpr, boolean allowPartialParse) throws JSQLParserException { + if (condExpr == null || condExpr.isEmpty()) { + return null; + } return parseCondExpression(condExpr, allowPartialParse, p -> { }); } @@ -245,8 +277,11 @@ public static Expression parseCondExpression(String condExpr, boolean allowParti @SuppressWarnings("PMD.CyclomaticComplexity") public static Expression parseCondExpression(String conditionalExpressionStr, boolean allowPartialParse, Consumer consumer) throws JSQLParserException { - Expression expression = null; + if (conditionalExpressionStr == null || conditionalExpressionStr.isEmpty()) { + return null; + } + Expression expression = null; // first, try to parse fast and simple try { CCJSqlParser parser = @@ -323,11 +358,19 @@ public Statement call() throws ParseException { * @return the statements parsed */ public static Statements parseStatements(String sqls) throws JSQLParserException { + if (sqls == null || sqls.isEmpty()) { + return null; + } + return parseStatements(sqls, null); } public static Statements parseStatements(String sqls, Consumer consumer) throws JSQLParserException { + if (sqls == null || sqls.isEmpty()) { + return null; + } + ExecutorService executorService = Executors.newSingleThreadExecutor(); final Statements statements = parseStatements(sqls, executorService, consumer); executorService.shutdown(); @@ -343,8 +386,11 @@ public static Statements parseStatements(String sqls, Consumer con public static Statements parseStatements(String sqls, ExecutorService executorService, Consumer consumer) throws JSQLParserException { - Statements statements = null; + if (sqls == null || sqls.isEmpty()) { + return null; + } + Statements statements = null; CCJSqlParser parser = newParser(sqls); if (consumer != null) { consumer.accept(parser); diff --git a/src/test/java/net/sf/jsqlparser/parser/CCJSqlParserUtilTest.java b/src/test/java/net/sf/jsqlparser/parser/CCJSqlParserUtilTest.java index 1ec07b531..b7caf3a64 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 static org.junit.jupiter.api.Assertions.assertDoesNotThrow; 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; @@ -507,4 +508,10 @@ void testUnbalancedPosition() { " from tab"; assertEquals(1122, CCJSqlParserUtil.getUnbalancedPosition(sqlStr)); } + + @Test + void testParseEmpty() throws JSQLParserException { + assertNull(CCJSqlParserUtil.parse("")); + assertNull(CCJSqlParserUtil.parse((String) null)); + } } From b9453f228adf9adbc68703b1e3e0bc0f56bc50fb Mon Sep 17 00:00:00 2001 From: Andreas Reichel Date: Fri, 23 Feb 2024 09:00:47 +0700 Subject: [PATCH 26/31] fix: issue #1948 `Between` with expression fixes #1948 Signed-off-by: Andreas Reichel --- .../net/sf/jsqlparser/parser/JSqlParserCC.jjt | 4 ---- .../operators/relational/BetweenTest.java | 15 +++++++++++++++ 2 files changed, 15 insertions(+), 4 deletions(-) create mode 100644 src/test/java/net/sf/jsqlparser/expression/operators/relational/BetweenTest.java diff --git a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt index 47a8c8937..811b04f4f 100644 --- a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt +++ b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt @@ -3666,8 +3666,6 @@ Expression Between(Expression leftExpression) : ( LOOKAHEAD( 3 ) betweenExpressionStart = ParenthesedSelect() | - LOOKAHEAD( SimpleFunction() ) betweenExpressionStart = SimpleFunction() - | LOOKAHEAD( RegularCondition() ) betweenExpressionStart = RegularCondition() | betweenExpressionStart = SimpleExpression() @@ -3677,8 +3675,6 @@ Expression Between(Expression leftExpression) : ( LOOKAHEAD( 3 ) betweenExpressionEnd = ParenthesedSelect() | - LOOKAHEAD( SimpleFunction() ) betweenExpressionEnd = SimpleFunction() - | LOOKAHEAD( RegularCondition() ) betweenExpressionEnd = RegularCondition() | betweenExpressionEnd = SimpleExpression() 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 new file mode 100644 index 000000000..8452c86fe --- /dev/null +++ b/src/test/java/net/sf/jsqlparser/expression/operators/relational/BetweenTest.java @@ -0,0 +1,15 @@ +package net.sf.jsqlparser.expression.operators.relational; + +import net.sf.jsqlparser.JSQLParserException; +import net.sf.jsqlparser.test.TestUtils; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class BetweenTest { + @Test + void testBetweenWithAdditionIssue1948() throws JSQLParserException { + String sqlStr = "select col FROM tbl WHERE start_time BETWEEN 1706024185 AND MyFunc() - 734400"; + TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true); + } +} \ No newline at end of file From c412d6a52f9b2ea21bb1723abf4d0561f4ba5b33 Mon Sep 17 00:00:00 2001 From: Andreas Reichel Date: Fri, 23 Feb 2024 09:04:52 +0700 Subject: [PATCH 27/31] feat: add DB2 special register `CURRENT TIMEZONE` fixes #1949 Signed-off-by: Andreas Reichel --- .../net/sf/jsqlparser/parser/JSqlParserCC.jjt | 1 + .../net/sf/jsqlparser/statement/select/DB2Test.java | 13 +++++++++++++ 2 files changed, 14 insertions(+) create mode 100644 src/test/java/net/sf/jsqlparser/statement/select/DB2Test.java diff --git a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt index 811b04f4f..a727fd66d 100644 --- a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt +++ b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt @@ -427,6 +427,7 @@ TOKEN: /* SQL Keywords. prefixed with K_ to avoid name clashes */ ("CURRENT" ( "_" | (" ")+ ) "TIMESTAMP") | ("CURRENT" ( "_" | (" ")+ ) "TIME") | ("CURRENT" ( "_" | (" ")+ ) "DATE") + | ("CURRENT" ( "_" | (" ")+ ) "TIMEZONE") ) ( "()" )?> | | diff --git a/src/test/java/net/sf/jsqlparser/statement/select/DB2Test.java b/src/test/java/net/sf/jsqlparser/statement/select/DB2Test.java new file mode 100644 index 000000000..b86abf648 --- /dev/null +++ b/src/test/java/net/sf/jsqlparser/statement/select/DB2Test.java @@ -0,0 +1,13 @@ +package net.sf.jsqlparser.statement.select; + +import net.sf.jsqlparser.JSQLParserException; +import net.sf.jsqlparser.test.TestUtils; +import org.junit.jupiter.api.Test; + +public class DB2Test { + @Test + void testDB2SpecialRegister() throws JSQLParserException { + String sqlStr = "SELECT * FROM TABLE1 where COL_WITH_TIMESTAMP <= CURRENT TIMESTAMP - CURRENT TIMEZONE"; + TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true); + } +} From 424a852ac8071d733fe4721397551e750dccd2ff Mon Sep 17 00:00:00 2001 From: Kaartic Sivaraam Date: Sat, 24 Feb 2024 05:02:07 +0530 Subject: [PATCH 28/31] Handle select in ExpressionVisitorAdapter (#1972) --- .../expression/ExpressionVisitorAdapter.java | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/main/java/net/sf/jsqlparser/expression/ExpressionVisitorAdapter.java b/src/main/java/net/sf/jsqlparser/expression/ExpressionVisitorAdapter.java index fc6c323c8..7c8a25c3b 100644 --- a/src/main/java/net/sf/jsqlparser/expression/ExpressionVisitorAdapter.java +++ b/src/main/java/net/sf/jsqlparser/expression/ExpressionVisitorAdapter.java @@ -283,14 +283,7 @@ public void visit(Column column) { @Override public void visit(ParenthesedSelect selectBody) { - if (selectVisitor != null) { - if (selectBody.getWithItemsList() != null) { - for (WithItem item : selectBody.getWithItemsList()) { - item.accept(selectVisitor); - } - } - selectBody.accept(selectVisitor); - } + visit((Select) selectBody); if (selectBody.getPivot() != null) { selectBody.getPivot().accept(this); } @@ -663,7 +656,14 @@ public void visit(GeometryDistance geometryDistance) { @Override public void visit(Select selectBody) { - + if (selectVisitor != null) { + if (selectBody.getWithItemsList() != null) { + for (WithItem item : selectBody.getWithItemsList()) { + item.accept(selectVisitor); + } + } + selectBody.accept(selectVisitor); + } } @Override From 9805581accf89d215390fe7cdce1750e2f2f8ca8 Mon Sep 17 00:00:00 2001 From: Andreas Reichel Date: Sat, 24 Feb 2024 11:55:05 +0700 Subject: [PATCH 29/31] fix: chained function calls of `SimpleFunction` fixes #1971 fixes #1949 Signed-off-by: Andreas Reichel --- .../net/sf/jsqlparser/parser/JSqlParserCC.jjt | 19 +++++++++++++++++-- .../jsqlparser/expression/FunctionTest.java | 11 +++++++++++ 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt index a727fd66d..880804cd5 100644 --- a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt +++ b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt @@ -1842,7 +1842,7 @@ String RelObjectNameWithoutValue() : { Token tk = null; } { ( tk= | tk= | tk= | tk= | tk= | tk= | tk= - | tk="ACTION" | tk="ACTIVE" | tk="ADD" | tk="ADVANCE" | tk="ADVISE" | tk="AGAINST" | tk="ALGORITHM" | tk="ALTER" | tk="ANALYZE" | tk="APPLY" | tk="ARCHIVE" | tk="ARRAY" | tk="ASC" | tk="AT" | tk="AUTHORIZATION" | tk="AUTO" | tk="BEGIN" | tk="BERNOULLI" | tk="BINARY" | tk="BIT" | tk="BLOCK" | tk="BROWSE" | tk="BUFFERS" | tk="BY" | tk="BYTE" | tk="BYTES" | tk="CACHE" | tk="CALL" | tk="CASCADE" | tk="CASE" | tk="CAST" | tk="CHANGE" | tk="CHANGES" | tk="CHAR" | tk="CHARACTER" | tk="CHECKPOINT" | tk="CLOSE" | tk="COLLATE" | tk="COLUMN" | tk="COLUMNS" | tk="COMMENT" | tk="COMMIT" | tk="CONCURRENTLY" | tk="CONFLICT" | tk="CONSTRAINTS" | tk="CONVERT" | tk="COSTS" | tk="CS" | tk="CYCLE" | tk="DATA" | tk="DATABASE" | tk="DDL" | tk="DECLARE" | tk="DEFAULT" | tk="DEFERRABLE" | tk="DELAYED" | tk="DELETE" | tk="DESC" | tk="DESCRIBE" | tk="DISABLE" | tk="DISCONNECT" | tk="DIV" | tk="DML" | tk="DO" | tk="DOMAIN" | tk="DROP" | tk="DUMP" | tk="DUPLICATE" | tk="ELEMENTS" | tk="EMIT" | tk="ENABLE" | tk="END" | tk="ESCAPE" | tk="EXCLUDE" | tk="EXEC" | tk="EXECUTE" | tk="EXPLAIN" | tk="EXPLICIT" | tk="EXTENDED" | tk="EXTRACT" | tk="FALSE" | tk="FILTER" | tk="FIRST" | tk="FLUSH" | tk="FN" | tk="FOLLOWING" | tk="FORMAT" | tk="FULLTEXT" | tk="FUNCTION" | tk="GRANT" | tk="GUARD" | tk="HASH" | tk="HISTORY" | tk="HOPPING" | tk="INCLUDE" | tk="INCREMENT" | tk="INDEX" | tk="INSERT" | tk="INTERLEAVE" | tk="INTERPRET" | tk="INVALIDATE" | tk="ISNULL" | tk="JSON" | tk="KEEP" | tk="KEY" | tk="KEYS" | tk="LAST" | tk="LEADING" | tk="LINK" | tk="LOCAL" | tk="LOCKED" | tk="LOG" | tk="LOOP" | tk="MATCH" | tk="MATCHED" | tk="MATERIALIZED" | tk="MAXVALUE" | tk="MEMBER" | tk="MERGE" | tk="MINVALUE" | tk="MODIFY" | tk="MOVEMENT" | tk="NEXT" | tk="NO" | tk="NOCACHE" | tk="NOKEEP" | tk="NOLOCK" | tk="NOMAXVALUE" | tk="NOMINVALUE" | tk="NOORDER" | tk="NOTHING" | tk="NOTNULL" | tk="NOVALIDATE" | tk="NOWAIT" | tk="NULLS" | tk="OF" | tk="OFF" | tk="OPEN" | tk="OVER" | tk="OVERLAPS" | tk="PARALLEL" | tk="PARENT" | tk="PARTITION" | tk="PATH" | tk="PERCENT" | tk="PLACING" | tk="PRECEDING" | tk="PRECISION" | tk="PRIMARY" | tk="PRIOR" | tk="PURGE" | tk="QUERY" | tk="QUICK" | tk="QUIESCE" | tk="RANGE" | tk="RAW" | tk="READ" | tk="RECYCLEBIN" | tk="REFERENCES" | tk="REFRESH" | tk="REGISTER" | tk="REMOTE" | tk="RENAME" | tk="REPEATABLE" | tk="REPLACE" | tk="RESET" | tk="RESTART" | tk="RESTRICT" | tk="RESTRICTED" | tk="RESUMABLE" | tk="RESUME" | tk="RETURN" | tk="RLIKE" | tk="ROLLBACK" | tk="ROLLUP" | tk="ROOT" | tk="ROW" | tk="ROWS" | tk="RR" | tk="RS" | tk="SAVEPOINT" | tk="SCHEMA" | tk="SECURE" | tk="SEED" | tk="SEPARATOR" | tk="SEQUENCE" | tk="SESSION" | tk="SETS" | tk="SHARE" | tk="SHOW" | tk="SHUTDOWN" | tk="SIBLINGS" | tk="SIGNED" | tk="SIMILAR" | tk="SIZE" | tk="SKIP" | tk="STORED" | tk="STRING" | tk="SUSPEND" | tk="SWITCH" | tk="SYNONYM" | tk="SYSTEM" | tk="TABLE" | tk="TABLESPACE" | tk="TEMP" | tk="TEMPORARY" | tk="THEN" | tk="TIMEOUT" | tk="TIMESTAMPTZ" | tk="TO" | tk="TRIGGER" | tk="TRUE" | tk="TRUNCATE" | tk="TUMBLING" | tk="TYPE" | tk="UNLOGGED" | tk="UNQIESCE" | tk="UNSIGNED" | tk="UPDATE" | tk="UPSERT" | tk="UR" | tk="USER" | tk="VALIDATE" | tk="VERBOSE" | tk="VIEW" | tk="VOLATILE" | tk="WAIT" | tk="WITHIN" | tk="WITHOUT" | tk="WORK" | tk="XML" | tk="XMLAGG" | tk="XMLDATA" | tk="XMLSCHEMA" | tk="XMLTEXT" | tk="XSINIL" | tk="YAML" | tk="YES" | tk="ZONE" ) + | tk="ACTION" | tk="ACTIVE" | tk="ADD" | tk="ADVANCE" | tk="ADVISE" | tk="AGAINST" | tk="ALGORITHM" | tk="ALTER" | tk="ANALYZE" | tk="APPLY" | tk="ARCHIVE" | tk="ARRAY" | tk="ASC" | tk="AT" | tk="AUTHORIZATION" | tk="AUTO" | tk="BEGIN" | tk="BERNOULLI" | tk="BINARY" | tk="BIT" | tk="BLOCK" | tk="BROWSE" | tk="BUFFERS" | tk="BY" | tk="BYTE" | tk="BYTES" | tk="CACHE" | tk="CALL" | tk="CASCADE" | tk="CASE" | tk="CAST" | tk="CHANGE" | tk="CHANGES" | tk="CHAR" | tk="CHARACTER" | tk="CHECKPOINT" | tk="CLOSE" | tk="COLLATE" | tk="COLUMN" | tk="COLUMNS" | tk="COMMENT" | tk="COMMIT" | tk="CONCURRENTLY" | tk="CONFLICT" | tk="CONSTRAINTS" | tk="CONVERT" | tk="COSTS" | tk="CS" | tk="CYCLE" | tk="DATA" | tk="DATABASE" | tk="DDL" | tk="DECLARE" | tk="DEFAULT" | tk="DEFERRABLE" | tk="DELAYED" | tk="DELETE" | tk="DESC" | tk="DESCRIBE" | tk="DISABLE" | tk="DISCONNECT" | tk="DIV" | tk="DML" | tk="DO" | tk="DOMAIN" | tk="DROP" | tk="DUMP" | tk="DUPLICATE" | tk="ELEMENTS" | tk="EMIT" | tk="ENABLE" | tk="END" | tk="ESCAPE" | tk="EXCLUDE" | tk="EXEC" | tk="EXECUTE" | tk="EXPLAIN" | tk="EXPLICIT" | tk="EXTENDED" | tk="EXTRACT" | tk="FALSE" | tk="FILTER" | tk="FIRST" | tk="FLUSH" | tk="FN" | tk="FOLLOWING" | tk="FORMAT" | tk="FULLTEXT" | tk="FUNCTION" | tk="GRANT" | tk="GUARD" | tk="HASH" | tk="HISTORY" | tk="HOPPING" | tk="INCLUDE" | tk="INCREMENT" | tk="INDEX" | tk="INSERT" | tk="INTERLEAVE" | tk="INTERPRET" | tk="INVALIDATE" | tk="ISNULL" | tk="JSON" | tk="KEEP" | tk="KEY" | tk="KEYS" | tk="LAST" | tk="LEADING" | tk="LINK" | tk="LOCAL" | tk="LOCKED" | tk="LOG" | tk="LOOP" | tk="MATCH" | tk="MATCHED" | tk="MATERIALIZED" | tk="MAXVALUE" | tk="MEMBER" | tk="MERGE" | tk="MINVALUE" | tk="MODIFY" | tk="MOVEMENT" | tk="NEXT" | tk="NO" | tk="NOCACHE" | tk="NOKEEP" | tk="NOLOCK" | tk="NOMAXVALUE" | tk="NOMINVALUE" | tk="NOORDER" | tk="NOTHING" | tk="NOTNULL" | tk="NOVALIDATE" | tk="NOWAIT" | tk="NULLS" | tk="OF" | tk="OFF" | tk="OPEN" | tk="OVER" | tk="OVERLAPS" | tk="PARALLEL" | tk="PARENT" | tk="PARTITION" | tk="PATH" | tk="PERCENT" | tk="PLACING" | tk="PRECEDING" | tk="PRECISION" | tk="PRIMARY" | tk="PRIOR" | tk="PURGE" | tk="QUERY" | tk="QUICK" | tk="QUIESCE" | tk="RANGE" | tk="RAW" | tk="READ" | tk="RECYCLEBIN" | tk="REFERENCES" | tk="REFRESH" | tk="REGISTER" | tk="REMOTE" | tk="RENAME" | tk="REPEATABLE" | tk="REPLACE" | tk="RESET" | tk="RESTART" | tk="RESTRICT" | tk="RESTRICTED" | tk="RESUMABLE" | tk="RESUME" | tk="RETURN" | tk="RLIKE" | tk="ROLLBACK" | tk="ROLLUP" | tk="ROOT" | tk="ROW" | tk="ROWS" | tk="RR" | tk="RS" | tk="SAVEPOINT" | tk="SCHEMA" | tk="SECURE" | tk="SEED" | tk="SEPARATOR" | tk="SEQUENCE" | tk="SESSION" | tk="SETS" | tk="SHARE" | tk="SHOW" | tk="SHUTDOWN" | tk="SIBLINGS" | tk="SIGNED" | tk="SIMILAR" | tk="SIZE" | tk="SKIP" | tk="STORED" | tk="STRING" | tk="SUSPEND" | tk="SWITCH" | tk="SYNONYM" | tk="SYSTEM" | tk="TABLE" | tk="TABLESPACE" | tk="TEMP" | tk="TEMPORARY" | tk="THEN" | tk="TIMEOUT" | tk="TIMESTAMPTZ" | tk="TIMEZONE" | tk="TO" | tk="TRIGGER" | tk="TRUE" | tk="TRUNCATE" | tk="TUMBLING" | tk="TYPE" | tk="UNLOGGED" | tk="UNQIESCE" | tk="UNSIGNED" | tk="UPDATE" | tk="UPSERT" | tk="UR" | tk="USER" | tk="VALIDATE" | tk="VERBOSE" | tk="VIEW" | tk="VOLATILE" | tk="WAIT" | tk="WITHIN" | tk="WITHOUT" | tk="WORK" | tk="XML" | tk="XMLAGG" | tk="XMLDATA" | tk="XMLSCHEMA" | tk="XMLTEXT" | tk="XSINIL" | tk="YAML" | tk="YES" | tk="ZONE" ) { return tk.image; } } @@ -5158,8 +5158,11 @@ Function SpecialStringFunctionWithNamedParameters() : // useful for parsing nested functions fast Function SimpleFunction(): { + Function function = new Function(); List name; Expression expr=null; + Expression attributeExpression = null; + Column attributeColumn = null; } { name = RelObjectNameList() @@ -5181,11 +5184,23 @@ Function SimpleFunction(): ] ")" { - Function function = new Function().withName(name); + function.setName(name); if (expr!=null) { function.setParameters(expr); } + } + [ "." ( + // tricky lookahead since we do need to support the following constructs + // schema.f1().f2() - Function with Function Column + // schema.f1().f2.f3 - Function with Attribute Column + LOOKAHEAD( Function() ) attributeExpression=Function() { function.setAttribute(attributeExpression); } + | + attributeColumn=Column() { function.setAttribute(attributeColumn); } + ) + ] + + { return function; } } diff --git a/src/test/java/net/sf/jsqlparser/expression/FunctionTest.java b/src/test/java/net/sf/jsqlparser/expression/FunctionTest.java index 28ce1d956..d5e132282 100644 --- a/src/test/java/net/sf/jsqlparser/expression/FunctionTest.java +++ b/src/test/java/net/sf/jsqlparser/expression/FunctionTest.java @@ -38,4 +38,15 @@ void testCallFunction() throws JSQLParserException { TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true); } + @Test + void testChainedFunctions() throws JSQLParserException { + String sqlStr = + "select f1(a1=1).f2 = 1"; + TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true); + + sqlStr = + "select f1(a1=1).f2(b).f2 = 1"; + TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true); + } + } From 2319da81bb27f4ed14c241117af027af7fc0b8c6 Mon Sep 17 00:00:00 2001 From: Tobias Warneke Date: Sat, 9 Mar 2024 21:49:14 +0100 Subject: [PATCH 30/31] --- .../expression/operators/relational/BetweenTest.java | 11 ++++++++++- .../net/sf/jsqlparser/statement/select/DB2Test.java | 9 +++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) 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 8452c86fe..ebe632c17 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 @@ -1,3 +1,12 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2024 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.JSQLParserException; @@ -12,4 +21,4 @@ void testBetweenWithAdditionIssue1948() throws JSQLParserException { String sqlStr = "select col FROM tbl WHERE start_time BETWEEN 1706024185 AND MyFunc() - 734400"; TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true); } -} \ No newline at end of file +} diff --git a/src/test/java/net/sf/jsqlparser/statement/select/DB2Test.java b/src/test/java/net/sf/jsqlparser/statement/select/DB2Test.java index b86abf648..2aab3162b 100644 --- a/src/test/java/net/sf/jsqlparser/statement/select/DB2Test.java +++ b/src/test/java/net/sf/jsqlparser/statement/select/DB2Test.java @@ -1,3 +1,12 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2024 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; From 6809832dd584ebbad6b10b6cf0c3f835e031c1d3 Mon Sep 17 00:00:00 2001 From: Tobias Warneke Date: Sat, 9 Mar 2024 21:59:49 +0100 Subject: [PATCH 31/31] [maven-release-plugin] prepare release jsqlparser-4.9 --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index f0b2c9d8b..f03dba3d2 100644 --- a/pom.xml +++ b/pom.xml @@ -2,7 +2,7 @@ 4.0.0 com.github.jsqlparser jsqlparser - 4.9-SNAPSHOT + 4.9 JSQLParser library 2004 @@ -107,7 +107,7 @@ scm:git:https://github.com/JSQLParser/JSqlParser.git scm:git:ssh://git@github.com:JSQLParser/JSqlParser.git https://github.com/JSQLParser/JSqlParser.git - HEAD + jsqlparser-4.9